mirror of https://github.com/sveltejs/svelte
commit
b6032eee88
@ -1,28 +0,0 @@
|
||||
// This script generates the TypeScript definitions
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const { readFileSync, writeFileSync } = require('fs');
|
||||
|
||||
try {
|
||||
execSync('tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly');
|
||||
} catch (err) {
|
||||
console.error(err.stderr.toString());
|
||||
throw err;
|
||||
}
|
||||
// We need to add these types to the .d.ts files here because if we add them before building, the build will fail,
|
||||
// because the TS->JS transformation doesn't know these exports are types and produces code that fails at runtime.
|
||||
// We can't use `export type` syntax either because the TS version we're on doesn't have this feature yet.
|
||||
|
||||
function modify(path, modifyFn) {
|
||||
const content = readFileSync(path, 'utf8');
|
||||
writeFileSync(path, modifyFn(content));
|
||||
}
|
||||
|
||||
modify(
|
||||
'types/runtime/index.d.ts',
|
||||
content => content.replace('SvelteComponentTyped', 'SvelteComponentTyped, ComponentType, ComponentConstructorOptions, ComponentProps, ComponentEvents')
|
||||
);
|
||||
modify(
|
||||
'types/compiler/index.d.ts',
|
||||
content => content + '\nexport { CompileOptions, ModuleFormat, EnableSourcemap, CssHashGetter } from "./interfaces"'
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +0,0 @@
|
||||
if (process.env.SKIP_PREPARE) {
|
||||
console.log('Skipped "prepare" script');
|
||||
} else {
|
||||
const { execSync } = require("child_process");
|
||||
const command = process.argv.slice(2).join(" ");
|
||||
execSync(command, { stdio: "inherit" });
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
let selection = '';
|
||||
|
||||
const handleSelectionChange = (e) => selection = document.getSelection();
|
||||
</script>
|
||||
|
||||
<svelte:document on:selectionchange={handleSelectionChange} />
|
||||
|
||||
<p>Select this text to fire events</p>
|
||||
<p>Selection: {selection}</p>
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "<svelte:document>"
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
---
|
||||
title: <svelte:body>
|
||||
---
|
||||
|
||||
Similar to `<svelte:window>`, the `<svelte:body>` element allows you to listen for events that fire on `document.body`. This is useful with the `mouseenter` and `mouseleave` events, which don't fire on `window`.
|
||||
|
||||
Add the `mouseenter` and `mouseleave` handlers to the `<svelte:body>` tag:
|
||||
|
||||
```html
|
||||
<svelte:body
|
||||
on:mouseenter={handleMouseenter}
|
||||
on:mouseleave={handleMouseleave}
|
||||
/>
|
||||
```
|
||||
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
let selection = '';
|
||||
|
||||
const handleSelectionChange = (e) => selection = document.getSelection();
|
||||
</script>
|
||||
|
||||
<svelte:document />
|
||||
|
||||
<p>Select this text to fire events</p>
|
||||
<p>Selection: {selection}</p>
|
||||
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
let selection = '';
|
||||
|
||||
const handleSelectionChange = (e) => selection = document.getSelection();
|
||||
</script>
|
||||
|
||||
<svelte:document on:selectionchange={handleSelectionChange} />
|
||||
|
||||
<p>Select this text to fire events</p>
|
||||
<p>Selection: {selection}</p>
|
||||
@ -0,0 +1,13 @@
|
||||
---
|
||||
title: <svelte:document>
|
||||
---
|
||||
|
||||
Similar to `<svelte:window>`, the `<svelte:document>` element allows you to listen for events that fire on `document`. This is useful with events like `selectionchange`, which doesn't fire on `window`.
|
||||
|
||||
Add the `selectionchange` handler to the `<svelte:document>` tag:
|
||||
|
||||
```html
|
||||
<svelte:document on:selectionchange={handleSelectionChange} />
|
||||
```
|
||||
|
||||
> Avoid `mouseenter` and `mouseleave` handlers on this element, these events are not fired on `document` in all browsers. Use `<svelte:body>` for this instead.
|
||||
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: <svelte:body>
|
||||
---
|
||||
|
||||
Similar to `<svelte:window>` and `<svelte:document>`, the `<svelte:body>` element allows you to listen for events that fire on `document.body`. This is useful with the `mouseenter` and `mouseleave` events, which don't fire on `window`.
|
||||
|
||||
Add the `mouseenter` and `mouseleave` handlers to the `<svelte:body>` tag:
|
||||
|
||||
```html
|
||||
<svelte:body
|
||||
on:mouseenter={handleMouseenter}
|
||||
on:mouseleave={handleMouseleave}
|
||||
/>
|
||||
```
|
||||
@ -0,0 +1,41 @@
|
||||
import Node from './shared/Node';
|
||||
import EventHandler from './EventHandler';
|
||||
import Action from './Action';
|
||||
import Component from '../Component';
|
||||
import TemplateScope from './shared/TemplateScope';
|
||||
import { Element } from '../../interfaces';
|
||||
import compiler_warnings from '../compiler_warnings';
|
||||
|
||||
export default class Document extends Node {
|
||||
type: 'Document';
|
||||
handlers: EventHandler[] = [];
|
||||
actions: Action[] = [];
|
||||
|
||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) {
|
||||
super(component, parent, scope, info);
|
||||
|
||||
info.attributes.forEach((node) => {
|
||||
if (node.type === 'EventHandler') {
|
||||
this.handlers.push(new EventHandler(component, this, scope, node));
|
||||
} else if (node.type === 'Action') {
|
||||
this.actions.push(new Action(component, this, scope, node));
|
||||
} else {
|
||||
// TODO there shouldn't be anything else here...
|
||||
}
|
||||
});
|
||||
|
||||
this.validate();
|
||||
}
|
||||
|
||||
private validate() {
|
||||
const handlers_map = new Set();
|
||||
|
||||
this.handlers.forEach(handler => (
|
||||
handlers_map.add(handler.name)
|
||||
));
|
||||
|
||||
if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) {
|
||||
this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import Renderer from '../Renderer';
|
||||
import Block from '../Block';
|
||||
import Comment from '../../nodes/Comment';
|
||||
import Wrapper from './shared/Wrapper';
|
||||
import { x } from 'code-red';
|
||||
import { Identifier } from 'estree';
|
||||
|
||||
export default class CommentWrapper extends Wrapper {
|
||||
node: Comment;
|
||||
var: Identifier;
|
||||
|
||||
constructor(
|
||||
renderer: Renderer,
|
||||
block: Block,
|
||||
parent: Wrapper,
|
||||
node: Comment
|
||||
) {
|
||||
super(renderer, block, parent, node);
|
||||
this.var = x`c` as Identifier;
|
||||
}
|
||||
|
||||
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
|
||||
if (!this.renderer.options.preserveComments) return;
|
||||
|
||||
const string_literal = {
|
||||
type: 'Literal',
|
||||
value: this.node.data,
|
||||
loc: {
|
||||
start: this.renderer.locate(this.node.start),
|
||||
end: this.renderer.locate(this.node.end)
|
||||
}
|
||||
};
|
||||
|
||||
block.add_element(
|
||||
this.var,
|
||||
x`@comment(${string_literal})`,
|
||||
parent_nodes && x`@claim_comment(${parent_nodes}, ${string_literal})`,
|
||||
parent_node
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
import Block from '../Block';
|
||||
import Wrapper from './shared/Wrapper';
|
||||
import { x } from 'code-red';
|
||||
import Document from '../../nodes/Document';
|
||||
import { Identifier } from 'estree';
|
||||
import EventHandler from './Element/EventHandler';
|
||||
import add_event_handlers from './shared/add_event_handlers';
|
||||
import { TemplateNode } from '../../../interfaces';
|
||||
import Renderer from '../Renderer';
|
||||
import add_actions from './shared/add_actions';
|
||||
|
||||
export default class DocumentWrapper extends Wrapper {
|
||||
node: Document;
|
||||
handlers: EventHandler[];
|
||||
|
||||
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
|
||||
super(renderer, block, parent, node);
|
||||
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this));
|
||||
}
|
||||
|
||||
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
|
||||
add_event_handlers(block, x`@_document`, this.handlers);
|
||||
add_actions(block, x`@_document`, this.node.actions);
|
||||
}
|
||||
}
|
||||
@ -1,24 +1,33 @@
|
||||
import ConstTag from '../../../nodes/ConstTag';
|
||||
import Block from '../../Block';
|
||||
import { b, x } from 'code-red';
|
||||
import { b, Node, x } from 'code-red';
|
||||
import Renderer from '../../Renderer';
|
||||
import Expression from '../../../nodes/shared/Expression';
|
||||
|
||||
export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string) {
|
||||
const const_tags_props = [];
|
||||
const_tags.forEach((const_tag, i) => {
|
||||
const name = `#constants_${i}`;
|
||||
const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`);
|
||||
const_tag.contexts.forEach(context => {
|
||||
const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), name => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name })};`);
|
||||
});
|
||||
});
|
||||
return const_tags_props;
|
||||
const const_tags_props = [];
|
||||
const_tags.forEach((const_tag, i) => {
|
||||
const name = `#constants_${i}`;
|
||||
const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`);
|
||||
const to_ctx = (name: string) => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name } as Node;
|
||||
|
||||
const_tag.contexts.forEach(context => {
|
||||
if (context.type === 'DestructuredVariable') {
|
||||
const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), to_ctx)}`);
|
||||
} else {
|
||||
const expression = new Expression(block.renderer.component, const_tag, const_tag.scope, context.key);
|
||||
const_tags_props.push(b`const ${context.property_name} = ${expression.manipulate(block, ctx)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
return const_tags_props;
|
||||
}
|
||||
|
||||
export function add_const_tags_context(renderer: Renderer, const_tags: ConstTag[]) {
|
||||
const_tags.forEach(const_tag => {
|
||||
const_tag.contexts.forEach(context => {
|
||||
renderer.add_to_context(context.key.name, true);
|
||||
});
|
||||
});
|
||||
const_tags.forEach(const_tag => {
|
||||
const_tag.contexts.forEach(context => {
|
||||
if (context.type !== 'DestructuredVariable') return;
|
||||
renderer.add_to_context(context.key.name, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
// Utilities for managing contenteditable nodes
|
||||
import Attribute from '../nodes/Attribute';
|
||||
import Element from '../nodes/Element';
|
||||
|
||||
export const CONTENTEDITABLE_BINDINGS = [
|
||||
'textContent',
|
||||
'innerHTML',
|
||||
'innerText'
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns true if node is an 'input' or 'textarea'.
|
||||
* @param {Element} node The element to be checked
|
||||
*/
|
||||
function is_input_or_textarea(node: Element): boolean {
|
||||
return node.name === 'textarea' || node.name === 'input';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given attribute is 'contenteditable'.
|
||||
* @param {Attribute} attribute A node.attribute
|
||||
*/
|
||||
function is_attr_contenteditable(attribute: Attribute): boolean {
|
||||
return attribute.name === 'contenteditable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any of a node's attributes are 'contentenditable'.
|
||||
* @param {Element} node The element to be checked
|
||||
*/
|
||||
export function has_contenteditable_attr(node: Element): boolean {
|
||||
return node.attributes.some(is_attr_contenteditable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if node is not textarea or input, but has 'contenteditable' attribute.
|
||||
* @param {Element} node The element to be tested
|
||||
*/
|
||||
export function is_contenteditable(node: Element): boolean {
|
||||
return !is_input_or_textarea(node) && has_contenteditable_attr(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a given binding/node is contenteditable.
|
||||
* @param {string} name A binding or node name to be checked
|
||||
*/
|
||||
export function is_name_contenteditable(name: string): boolean {
|
||||
return CONTENTEDITABLE_BINDINGS.includes(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contenteditable attribute from the node (if it exists).
|
||||
* @param {Element} node The element to get the attribute from
|
||||
*/
|
||||
export function get_contenteditable_attr(node: Element): Attribute | undefined {
|
||||
return node.attributes.find(is_attr_contenteditable);
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
// @ts-nocheck
|
||||
// Note: Must import from the `css-tree` browser bundled distribution due to `createRequire` usage if importing from
|
||||
// `css-tree` Node module directly. This allows the production build of Svelte to work correctly.
|
||||
import { fork } from '../../../../../node_modules/css-tree/dist/csstree.esm.js';
|
||||
|
||||
import * as node from './node';
|
||||
|
||||
/**
|
||||
* Extends `css-tree` for container query support by forking and adding new nodes and at-rule support for `@container`.
|
||||
*
|
||||
* The new nodes are located in `./node`.
|
||||
*/
|
||||
const cqSyntax = fork({
|
||||
atrule: { // extend or override at-rule dictionary
|
||||
container: {
|
||||
parse: {
|
||||
prelude() {
|
||||
return this.createSingleNodeList(
|
||||
this.ContainerQuery()
|
||||
);
|
||||
},
|
||||
block(isStyleBlock = false) {
|
||||
return this.Block(isStyleBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
node
|
||||
});
|
||||
|
||||
export const parse = cqSyntax.parse;
|
||||
@ -0,0 +1,48 @@
|
||||
// @ts-nocheck
|
||||
import { Delim } from 'css-tree/tokenizer';
|
||||
|
||||
export const name = 'Comparison';
|
||||
export const structure = {
|
||||
value: String
|
||||
};
|
||||
|
||||
export function parse() {
|
||||
const start = this.tokenStart;
|
||||
|
||||
const char1 = this.consume(Delim);
|
||||
|
||||
// The first character in the comparison operator must match '<', '=', or '>'.
|
||||
if (char1 !== '<' && char1 !== '>' && char1 !== '=') {
|
||||
this.error('Malformed comparison operator');
|
||||
}
|
||||
|
||||
let char2;
|
||||
|
||||
if (this.tokenType === Delim) {
|
||||
char2 = this.consume(Delim);
|
||||
|
||||
// The second character in the comparison operator must match '='.
|
||||
if (char2 !== '=') {
|
||||
this.error('Malformed comparison operator');
|
||||
}
|
||||
}
|
||||
|
||||
// If the next token is also 'Delim' then it is malformed.
|
||||
if (this.tokenType === Delim) {
|
||||
this.error('Malformed comparison operator');
|
||||
}
|
||||
|
||||
const value = char2 ? `${char1}${char2}` : char1;
|
||||
|
||||
return {
|
||||
type: 'Comparison',
|
||||
loc: this.getLocation(start, this.tokenStart),
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
export function generate(node) {
|
||||
for (let index = 0; index < node.value.length; index++) {
|
||||
this.token(Delim, node.value.charAt(index));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
Function,
|
||||
Ident,
|
||||
Number,
|
||||
Dimension,
|
||||
RightParenthesis,
|
||||
Colon,
|
||||
Delim
|
||||
} from 'css-tree/tokenizer';
|
||||
|
||||
export const name = 'ContainerFeatureStyle';
|
||||
export const structure = {
|
||||
name: String,
|
||||
value: ['Function', 'Identifier', 'Number', 'Dimension', 'QueryCSSFunction', 'Ratio', null]
|
||||
};
|
||||
|
||||
export function parse() {
|
||||
const start = this.tokenStart;
|
||||
let value = null;
|
||||
|
||||
const function_name = this.consumeFunctionName();
|
||||
if (function_name !== 'style') {
|
||||
this.error('Unknown container style query identifier; "style" is expected');
|
||||
}
|
||||
|
||||
this.skipSC();
|
||||
|
||||
const name = this.consume(Ident);
|
||||
this.skipSC();
|
||||
|
||||
if (this.tokenType !== RightParenthesis) {
|
||||
this.eat(Colon);
|
||||
this.skipSC();
|
||||
|
||||
switch (this.tokenType) {
|
||||
case Number:
|
||||
if (this.lookupNonWSType(1) === Delim) {
|
||||
value = this.Ratio();
|
||||
} else {
|
||||
value = this.Number();
|
||||
}
|
||||
break;
|
||||
|
||||
case Dimension:
|
||||
value = this.Dimension();
|
||||
break;
|
||||
|
||||
case Function:
|
||||
value = this.QueryCSSFunction();
|
||||
break;
|
||||
|
||||
case Ident:
|
||||
value = this.Identifier();
|
||||
break;
|
||||
|
||||
default:
|
||||
this.error('Number, dimension, ratio, function or identifier is expected');
|
||||
break;
|
||||
}
|
||||
|
||||
this.skipSC();
|
||||
}
|
||||
|
||||
this.eat(RightParenthesis);
|
||||
|
||||
return {
|
||||
type: 'ContainerFeatureStyle',
|
||||
loc: this.getLocation(start, this.tokenStart),
|
||||
name,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
export function generate(node) {
|
||||
this.token(Function, 'style(');
|
||||
this.token(Ident, node.name);
|
||||
|
||||
if (node.value !== null) {
|
||||
this.token(Colon, ':');
|
||||
this.node(node.value);
|
||||
}
|
||||
|
||||
this.token(RightParenthesis, ')');
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
WhiteSpace,
|
||||
Comment,
|
||||
Function,
|
||||
Ident,
|
||||
LeftParenthesis
|
||||
} from 'css-tree/tokenizer';
|
||||
|
||||
import { lookahead_is_range } from './lookahead_is_range';
|
||||
|
||||
const CONTAINER_QUERY_KEYWORDS = new Set(['none', 'and', 'not', 'or']);
|
||||
|
||||
export const name = 'ContainerQuery';
|
||||
export const structure = {
|
||||
name: 'Identifier',
|
||||
children: [[
|
||||
'Identifier',
|
||||
'QueryFeature',
|
||||
'QueryFeatureRange',
|
||||
'ContainerFeatureStyle',
|
||||
'WhiteSpace'
|
||||
]]
|
||||
};
|
||||
|
||||
export function parse() {
|
||||
const start = this.tokenStart;
|
||||
const children = this.createList();
|
||||
let child = null;
|
||||
let name = null;
|
||||
|
||||
// Parse potential container name.
|
||||
if (this.tokenType === Ident) {
|
||||
const container_name = this.substring(this.tokenStart, this.tokenEnd);
|
||||
|
||||
// Container name doesn't match a query keyword, so assign it as container name.
|
||||
if (!CONTAINER_QUERY_KEYWORDS.has(container_name.toLowerCase())) {
|
||||
name = container_name;
|
||||
this.eatIdent(container_name);
|
||||
}
|
||||
}
|
||||
|
||||
this.skipSC();
|
||||
|
||||
scan:
|
||||
while (!this.eof) {
|
||||
switch (this.tokenType) {
|
||||
case Comment:
|
||||
case WhiteSpace:
|
||||
this.next();
|
||||
continue;
|
||||
|
||||
case Ident:
|
||||
child = this.Identifier();
|
||||
break;
|
||||
|
||||
case Function:
|
||||
child = this.ContainerFeatureStyle();
|
||||
break;
|
||||
|
||||
case LeftParenthesis:
|
||||
// Lookahead to determine if range feature.
|
||||
child = lookahead_is_range.call(this) ? this.QueryFeatureRange() : this.QueryFeature();
|
||||
break;
|
||||
|
||||
default:
|
||||
break scan;
|
||||
}
|
||||
|
||||
children.push(child);
|
||||
}
|
||||
|
||||
if (child === null) {
|
||||
this.error('Identifier or parenthesis is expected');
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'ContainerQuery',
|
||||
loc: this.getLocation(start, this.tokenStart - 1),
|
||||
name,
|
||||
children
|
||||
};
|
||||
}
|
||||
|
||||
export function generate(node) {
|
||||
if (typeof node.name === 'string') {
|
||||
this.token(Ident, node.name);
|
||||
}
|
||||
|
||||
this.children(node);
|
||||
}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
export * as Comparison from './comparison';
|
||||
export * as ContainerFeatureStyle from './container_feature_style';
|
||||
export * as ContainerQuery from './container_query';
|
||||
export * as MediaQuery from './media_query';
|
||||
export * as QueryFeature from './query_feature';
|
||||
export * as QueryFeatureRange from './query_feature_range';
|
||||
export * as QueryCSSFunction from './query_css_function';
|
||||
@ -0,0 +1,44 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
EOF,
|
||||
WhiteSpace,
|
||||
Delim,
|
||||
RightParenthesis,
|
||||
LeftCurlyBracket,
|
||||
Colon
|
||||
} from 'css-tree/tokenizer';
|
||||
|
||||
/**
|
||||
* Looks ahead to determine if query feature is a range query. This involves locating at least one delimiter and no
|
||||
* colon tokens.
|
||||
*
|
||||
* @returns {boolean} Is potential range query.
|
||||
*/
|
||||
export function lookahead_is_range() {
|
||||
let type;
|
||||
let offset = 0;
|
||||
|
||||
let count = 0;
|
||||
let delim_found = false;
|
||||
let no_colon = true;
|
||||
|
||||
// A range query has maximum 5 tokens when formatted as 'mf-range' /
|
||||
// '<mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value>'. So only look ahead maximum of 6 non-whitespace tokens.
|
||||
do {
|
||||
type = this.lookupNonWSType(offset++);
|
||||
if (type !== WhiteSpace) {
|
||||
count++;
|
||||
}
|
||||
if (type === Delim) {
|
||||
delim_found = true;
|
||||
}
|
||||
if (type === Colon) {
|
||||
no_colon = false;
|
||||
}
|
||||
if (type === LeftCurlyBracket || type === RightParenthesis) {
|
||||
break;
|
||||
}
|
||||
} while (type !== EOF && count <= 6);
|
||||
|
||||
return delim_found && no_colon;
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
WhiteSpace,
|
||||
Comment,
|
||||
Ident,
|
||||
LeftParenthesis
|
||||
} from 'css-tree/tokenizer';
|
||||
|
||||
import { lookahead_is_range } from './lookahead_is_range';
|
||||
|
||||
export const name = 'MediaQuery';
|
||||
export const structure = {
|
||||
children: [[
|
||||
'Identifier',
|
||||
'QueryFeature',
|
||||
'QueryFeatureRange',
|
||||
'WhiteSpace'
|
||||
]]
|
||||
};
|
||||
|
||||
export function parse() {
|
||||
const children = this.createList();
|
||||
let child = null;
|
||||
|
||||
this.skipSC();
|
||||
|
||||
scan:
|
||||
while (!this.eof) {
|
||||
switch (this.tokenType) {
|
||||
case Comment:
|
||||
case WhiteSpace:
|
||||
this.next();
|
||||
continue;
|
||||
|
||||
case Ident:
|
||||
child = this.Identifier();
|
||||
break;
|
||||
|
||||
case LeftParenthesis:
|
||||
// Lookahead to determine if range feature.
|
||||
child = lookahead_is_range.call(this) ? this.QueryFeatureRange() : this.QueryFeature();
|
||||
break;
|
||||
|
||||
default:
|
||||
break scan;
|
||||
}
|
||||
|
||||
children.push(child);
|
||||
}
|
||||
|
||||
if (child === null) {
|
||||
this.error('Identifier or parenthesis is expected');
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'MediaQuery',
|
||||
loc: this.getLocationFromList(children),
|
||||
children
|
||||
};
|
||||
}
|
||||
|
||||
export function generate(node) {
|
||||
this.children(node);
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
RightParenthesis
|
||||
} from 'css-tree/tokenizer';
|
||||
|
||||
const QUERY_CSS_FUNCTIONS = new Set(['calc', 'clamp', 'min', 'max']);
|
||||
|
||||
export const name = 'QueryCSSFunction';
|
||||
export const structure = {
|
||||
name: String,
|
||||
expression: String
|
||||
};
|
||||
|
||||
export function parse() {
|
||||
const start = this.tokenStart;
|
||||
|
||||
const name = this.consumeFunctionName();
|
||||
|
||||
if (!QUERY_CSS_FUNCTIONS.has(name)) {
|
||||
this.error('Unknown query single value function; expected: "calc", "clamp", "max", min"');
|
||||
}
|
||||
|
||||
const body = this.Raw(this.tokenIndex, null, false);
|
||||
|
||||
this.eat(RightParenthesis);
|
||||
|
||||
return {
|
||||
type: 'QueryCSSFunction',
|
||||
loc: this.getLocation(start, this.tokenStart),
|
||||
name,
|
||||
expression: body.value
|
||||
};
|
||||
}
|
||||
|
||||
export function generate(node) {
|
||||
this.token(Function, `${node.name}(`);
|
||||
|
||||
this.node(node.expression);
|
||||
|
||||
this.token(RightParenthesis, ')');
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
Ident,
|
||||
Number,
|
||||
Dimension,
|
||||
Function,
|
||||
LeftParenthesis,
|
||||
RightParenthesis,
|
||||
Colon,
|
||||
Delim
|
||||
} from 'css-tree/tokenizer';
|
||||
|
||||
export const name = 'QueryFeature';
|
||||
export const structure = {
|
||||
name: String,
|
||||
value: ['Identifier', 'Number', 'Dimension', 'QueryCSSFunction', 'Ratio', null]
|
||||
};
|
||||
|
||||
export function parse() {
|
||||
const start = this.tokenStart;
|
||||
let value = null;
|
||||
|
||||
this.eat(LeftParenthesis);
|
||||
this.skipSC();
|
||||
|
||||
const name = this.consume(Ident);
|
||||
this.skipSC();
|
||||
|
||||
if (this.tokenType !== RightParenthesis) {
|
||||
this.eat(Colon);
|
||||
this.skipSC();
|
||||
|
||||
switch (this.tokenType) {
|
||||
case Number:
|
||||
if (this.lookupNonWSType(1) === Delim) {
|
||||
value = this.Ratio();
|
||||
} else {
|
||||
value = this.Number();
|
||||
}
|
||||
break;
|
||||
|
||||
case Dimension:
|
||||
value = this.Dimension();
|
||||
break;
|
||||
|
||||
case Function:
|
||||
value = this.QueryCSSFunction();
|
||||
break;
|
||||
|
||||
case Ident:
|
||||
value = this.Identifier();
|
||||
break;
|
||||
|
||||
default:
|
||||
this.error('Number, dimension, ratio, function, or identifier is expected');
|
||||
break;
|
||||
}
|
||||
|
||||
this.skipSC();
|
||||
}
|
||||
|
||||
this.eat(RightParenthesis);
|
||||
|
||||
return {
|
||||
type: 'QueryFeature',
|
||||
loc: this.getLocation(start, this.tokenStart),
|
||||
name,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
export function generate(node) {
|
||||
this.token(LeftParenthesis, '(');
|
||||
this.token(Ident, node.name);
|
||||
|
||||
if (node.value !== null) {
|
||||
this.token(Colon, ':');
|
||||
this.node(node.value);
|
||||
}
|
||||
|
||||
this.token(RightParenthesis, ')');
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
Ident,
|
||||
Number,
|
||||
Delim,
|
||||
Dimension,
|
||||
Function,
|
||||
LeftParenthesis,
|
||||
RightParenthesis,
|
||||
WhiteSpace
|
||||
} from 'css-tree/tokenizer';
|
||||
|
||||
export const name = 'QueryFeatureRange';
|
||||
export const structure = {
|
||||
name: String,
|
||||
value: ['Identifier', 'Number', 'Comparison', 'Dimension', 'QueryCSSFunction', 'Ratio', null]
|
||||
};
|
||||
|
||||
function lookup_non_WS_type_and_value(offset, type, referenceStr) {
|
||||
let current_type;
|
||||
|
||||
do {
|
||||
current_type = this.lookupType(offset++);
|
||||
if (current_type !== WhiteSpace) {
|
||||
break;
|
||||
}
|
||||
} while (current_type !== 0); // NULL -> 0
|
||||
|
||||
return current_type === type ? this.lookupValue(offset - 1, referenceStr) : false;
|
||||
}
|
||||
|
||||
export function parse() {
|
||||
const start = this.tokenStart;
|
||||
const children = this.createList();
|
||||
let child = null;
|
||||
|
||||
this.eat(LeftParenthesis);
|
||||
this.skipSC();
|
||||
|
||||
while (!this.eof && this.tokenType !== RightParenthesis) {
|
||||
switch (this.tokenType) {
|
||||
case Number:
|
||||
if (lookup_non_WS_type_and_value.call(this, 1, Delim, '/')) {
|
||||
child = this.Ratio();
|
||||
} else {
|
||||
child = this.Number();
|
||||
}
|
||||
break;
|
||||
|
||||
case Delim:
|
||||
child = this.Comparison();
|
||||
break;
|
||||
|
||||
case Dimension:
|
||||
child = this.Dimension();
|
||||
break;
|
||||
|
||||
case Function:
|
||||
child = this.QueryCSSFunction();
|
||||
break;
|
||||
|
||||
case Ident:
|
||||
child = this.Identifier();
|
||||
break;
|
||||
|
||||
default:
|
||||
this.error('Number, dimension, comparison, ratio, function, or identifier is expected');
|
||||
break;
|
||||
}
|
||||
|
||||
children.push(child);
|
||||
|
||||
this.skipSC();
|
||||
}
|
||||
|
||||
this.eat(RightParenthesis);
|
||||
|
||||
return {
|
||||
type: 'QueryFeatureRange',
|
||||
loc: this.getLocation(start, this.tokenStart),
|
||||
children
|
||||
};
|
||||
}
|
||||
|
||||
export function generate(node) {
|
||||
this.children(node);
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
import { globals } from './globals';
|
||||
|
||||
/**
|
||||
* Resize observer singleton.
|
||||
* One listener per element only!
|
||||
* https://groups.google.com/a/chromium.org/g/blink-dev/c/z6ienONUb5A/m/F5-VcUZtBAAJ
|
||||
*/
|
||||
export class ResizeObserverSingleton {
|
||||
constructor(readonly options?: ResizeObserverOptions) {}
|
||||
|
||||
observe(element: Element, listener: Listener) {
|
||||
this._listeners.set(element, listener);
|
||||
this._getObserver().observe(element, this.options);
|
||||
return () => {
|
||||
this._listeners.delete(element);
|
||||
this._observer.unobserve(element); // this line can probably be removed
|
||||
};
|
||||
}
|
||||
|
||||
private readonly _listeners: WeakMap<Element, Listener> = 'WeakMap' in globals ? new WeakMap() : undefined;
|
||||
private _observer?: ResizeObserver;
|
||||
private _getObserver() {
|
||||
return this._observer ?? (this._observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
(ResizeObserverSingleton as any).entries.set(entry.target, entry);
|
||||
this._listeners.get(entry.target)?.(entry);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
// Needs to be written like this to pass the tree-shake-test
|
||||
(ResizeObserverSingleton as any).entries = 'WeakMap' in globals ? new WeakMap() : undefined;
|
||||
|
||||
type Listener = (entry: ResizeObserverEntry)=>any;
|
||||
|
||||
// TODO: Remove this
|
||||
interface ResizeObserverSize {
|
||||
readonly blockSize: number;
|
||||
readonly inlineSize: number;
|
||||
}
|
||||
|
||||
interface ResizeObserverEntry {
|
||||
readonly borderBoxSize: readonly ResizeObserverSize[];
|
||||
readonly contentBoxSize: readonly ResizeObserverSize[];
|
||||
readonly contentRect: DOMRectReadOnly;
|
||||
readonly devicePixelContentBoxSize: readonly ResizeObserverSize[];
|
||||
readonly target: Element;
|
||||
}
|
||||
|
||||
type ResizeObserverBoxOptions = 'border-box' | 'content-box' | 'device-pixel-content-box';
|
||||
|
||||
interface ResizeObserverOptions {
|
||||
box?: ResizeObserverBoxOptions;
|
||||
}
|
||||
|
||||
interface ResizeObserver {
|
||||
disconnect(): void;
|
||||
observe(target: Element, options?: ResizeObserverOptions): void;
|
||||
unobserve(target: Element): void;
|
||||
}
|
||||
|
||||
interface ResizeObserverCallback {
|
||||
(entries: ResizeObserverEntry[], observer: ResizeObserver): void;
|
||||
}
|
||||
|
||||
declare let ResizeObserver: {
|
||||
prototype: ResizeObserver;
|
||||
new(callback: ResizeObserverCallback): ResizeObserver;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue