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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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