mirror of https://github.com/sveltejs/svelte
Merge branch 'feat/conditional-slots' of https://github.com/tanhauhau/svelte into feat/conditional-slots
commit
c9367716a4
@ -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);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue