diff --git a/demos/client/css-value-syntax-parser.ts b/demos/client/css-value-syntax-parser.ts new file mode 100644 index 00000000..1dd8537c --- /dev/null +++ b/demos/client/css-value-syntax-parser.ts @@ -0,0 +1,32 @@ +import { initPreview } from './view'; +import { parseValueSyntax } from '@tokey/css-value-parser'; +// import { valueDefinitions } from '../../packages/css-value-parser/src/value-definitions'; + +const valueDefinitions = { props: {} as Record }; + +const topBar = document.getElementById('top-bar') as HTMLElement; +const input = document.getElementById('input') as HTMLTextAreaElement; +const output = document.getElementById('output') as HTMLElement; +const select = document.createElement('select'); + +Object.keys(valueDefinitions.props).forEach((key) => { + const option = document.createElement('option'); + option.value = valueDefinitions.props[key].syntax; + option.textContent = key; + select.appendChild(option); +}); + +select.selectedIndex = 0; +topBar.appendChild(select); + +initPreview(input, output, (...args) => [parseValueSyntax(...args)]); + +select.onchange = () => { + setValue(select.value); +}; + +function setValue(value: string) { + input.value = value; + document.dispatchEvent(new CustomEvent('playground:updateOutput')); +} +setValue(select.value); diff --git a/demos/client/tsconfig.json b/demos/client/tsconfig.json index 191fef12..121f4682 100644 --- a/demos/client/tsconfig.json +++ b/demos/client/tsconfig.json @@ -1,6 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "../dist" - } + "outDir": "../dist", + "noEmit": true + }, + "references": [{ "path": "../../packages/css-value-parser/src" }] } diff --git a/demos/css-value-syntax-parser.html b/demos/css-value-syntax-parser.html new file mode 100644 index 00000000..e91a22a6 --- /dev/null +++ b/demos/css-value-syntax-parser.html @@ -0,0 +1,26 @@ + + + + + + + Tokens + + + + +
+
+ +

+    
+ + + diff --git a/demos/css/style.css b/demos/css/style.css index 994d8a81..4f809e88 100644 --- a/demos/css/style.css +++ b/demos/css/style.css @@ -10,6 +10,7 @@ html { main { height: 100%; display: flex; + overflow: hidden; } main > * { flex: 1 0 50%; @@ -33,3 +34,8 @@ main > * { [data-in-rage='true'] { font-weight: bolder; } + +.flex-col { + display: flex; + flex-direction: column; +} diff --git a/package-lock.json b/package-lock.json index 43d2d910..e2c93dbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,6 +120,10 @@ "resolved": "packages/css-selector-parser", "link": true }, + "node_modules/@tokey/css-value-parser": { + "resolved": "packages/css-value-parser", + "link": true + }, "node_modules/@tokey/experiments": { "resolved": "packages/experiments", "link": true @@ -351,6 +355,15 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "node_modules/@webref/css": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@webref/css/-/css-2.1.9.tgz", + "integrity": "sha512-wSWwY0wGA0Vuw1SuJ5j1hrj+nTgE43+Y8MH8jyG9TkcrUwfRbqPqoShNiRReeepc7ZzhimjbrSyfTSDpdtH/XQ==", + "dev": true, + "peerDependencies": { + "css-tree": "^1.1.3" + } + }, "node_modules/acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", @@ -652,6 +665,20 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "peer": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -1467,6 +1494,13 @@ "node": ">=10" } }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true, + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2282,6 +2316,17 @@ "@tokey/core": "^1.2.1" } }, + "packages/css-value-parser": { + "name": "@tokey/css-value-parser", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@tokey/core": "^1.2.1" + }, + "devDependencies": { + "@webref/css": "^2.0.12" + } + }, "packages/experiments": { "name": "@tokey/experiments", "version": "0.0.2", @@ -2390,6 +2435,13 @@ "@tokey/core": "^1.2.1" } }, + "@tokey/css-value-parser": { + "version": "file:packages/css-value-parser", + "requires": { + "@tokey/core": "^1.2.1", + "@webref/css": "^2.0.12" + } + }, "@tokey/experiments": { "version": "file:packages/experiments", "requires": { @@ -2541,6 +2593,13 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "@webref/css": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@webref/css/-/css-2.1.9.tgz", + "integrity": "sha512-wSWwY0wGA0Vuw1SuJ5j1hrj+nTgE43+Y8MH8jyG9TkcrUwfRbqPqoShNiRReeepc7ZzhimjbrSyfTSDpdtH/XQ==", + "dev": true, + "requires": {} + }, "acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", @@ -2770,6 +2829,17 @@ "which": "^2.0.1" } }, + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "peer": true, + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + } + }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -3371,6 +3441,13 @@ "yallist": "^4.0.0" } }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true, + "peer": true + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 3a7d935a..99b95917 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -168,6 +168,6 @@ export function trimTokens[]>( /** * get last item in array */ -export function last(arr: T[]): T { +export function last(arr: T[]): T | undefined { return arr[arr.length - 1]; } diff --git a/packages/core/src/seeker.ts b/packages/core/src/seeker.ts index 818bffad..cc61947f 100644 --- a/packages/core/src/seeker.ts +++ b/packages/core/src/seeker.ts @@ -25,6 +25,12 @@ export class Seeker> { } return undefined; } + eat(type: T['type']) { + while (this.peek().type === type) { + this.index++; + } + return this; + } takeMany(type: T['type']) { const tokens = []; while (this.peek().type === type) { diff --git a/packages/css-selector-parser/src/helpers.ts b/packages/css-selector-parser/src/helpers.ts index a384db19..37b34eef 100644 --- a/packages/css-selector-parser/src/helpers.ts +++ b/packages/css-selector-parser/src/helpers.ts @@ -97,19 +97,19 @@ export function trimCombinators(selector: Selector) { const firstNode = nodes[0]; const lastNode = last(nodes); // remove first space combinator and add to selector before - // (going between comment is not required for the start becuase they are taken care + // (going between comment is not required for the start because they are taken care // of during parsing) if (firstNode?.type === 'combinator' && firstNode.combinator === 'space') { selector.nodes.shift(); selector.before += firstNode.before + firstNode.value + firstNode.after; } // remove any edge space combinators (last and between comments) - if (lastNode !== firstNode) { + if (lastNode && lastNode !== firstNode) { let index = nodes.length - 1; let current = lastNode; let lastComment: Comment | undefined; while ( - (current && current.type === `comment`) || + current.type === `comment` || (current.type === `combinator` && current.combinator === `space`) ) { if (current.type === `combinator`) { diff --git a/packages/css-selector-parser/src/selector-parser.ts b/packages/css-selector-parser/src/selector-parser.ts index 2ac0dc0a..4fe0633d 100644 --- a/packages/css-selector-parser/src/selector-parser.ts +++ b/packages/css-selector-parser/src/selector-parser.ts @@ -374,8 +374,8 @@ function handleToken( end: ended?.end ?? s.peekBack().end, }); } else { - if (res.length) { - const lastSelector = last(res); + const lastSelector = last(res); + if (lastSelector) { trimCombinators(lastSelector); } prev.nodes = res; @@ -384,7 +384,8 @@ function handleToken( } else if (isComment(token.type)) { ast.push(createCommentAst(token)); } else if (token.type === ',') { - const selector = last(selectors); + // we ensure at least one selector present + const selector = last(selectors)!; selector.end = token.start; trimCombinators(selector); const newSelector = createEmptySelector(); diff --git a/packages/css-value-parser/package.json b/packages/css-value-parser/package.json new file mode 100644 index 00000000..f49f6baf --- /dev/null +++ b/packages/css-value-parser/package.json @@ -0,0 +1,34 @@ +{ + "name": "@tokey/css-value-parser", + "description": "value parser for css", + "version": "0.0.1", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "test": "mocha \"./dist/test/**/*.spec.js\"" + }, + "dependencies": { + "@tokey/core": "^1.2.1" + }, + "devDependencies": { + "@webref/css": "^2.0.12" + }, + "keywords": [ + "parser", + "css", + "value" + ], + "files": [ + "src", + "dist", + "!dist/test", + "!*/tsconfig.{json,tsbuildinfo}" + ], + "publishConfig": { + "access": "public" + }, + "author": "Wix.com", + "license": "MIT", + "repository": "https://github.com/wixplosives/tokey/packages/css-value-parser", + "sideEffects": false +} diff --git a/packages/css-value-parser/src/ast-types.ts b/packages/css-value-parser/src/ast-types.ts new file mode 100644 index 00000000..06f3b53a --- /dev/null +++ b/packages/css-value-parser/src/ast-types.ts @@ -0,0 +1,291 @@ +import type { + lengthValidUnits, + angleValidUnits, + frequencyValidUnits, + resolutionValidUnits, + timeValidUnits, +} from './units'; + +export interface CSSValueAST { + type: TYPE; + start: number; + end: number; + value: string; +} + +export type BaseAstNode = + | Literal + | CssWideKeyword + | Space + | Comment + | Invalid + | CustomIdent + | DashedIdent + | Call + | String + | Number + | Integer + | Length + | Angle + | Time + | Percentage + | Frequency + | Resolution + | Flex + | UnknownUnit + | Color; + +/* types */ + +// custom +export type BuildVarAst = CSSValueAST<`build-var`> & { + subType: string; + id: string; +}; +export type Literal = CSSValueAST<`literal`> & { + before: string; + after: string; +}; +export type CssWideKeyword = CSSValueAST<`css-wide-keyword`> & { + value: AnyCase<`inherit` | `unset` | `initial`>; +}; +export type Space = CSSValueAST<`space`> & { + before: string; + after: string; +}; +export type Comment = CSSValueAST<`comment`> & { + before: string; + after: string; +}; +export type Call = CSSValueAST & { + args: CSSValueAST[]; + before: string; + after: string; +}; +type Unit = CSSValueAST & { + unit: UNIT; + integer: boolean; +}; +export type Invalid = CSSValueAST<`invalid`>; +export type UnknownUnit = Unit<`unknown-unit`, string>; + +// textual +export type CustomIdent = CSSValueAST<``>; +export type DashedIdent = CSSValueAST<``>; +export type String = CSSValueAST<``>; +export type Url = CSSValueAST<``>; +// numeric +export type Integer = CSSValueAST<``>; +export type Number = CSSValueAST<``>; +export type Percentage = Unit<``, `%`>; +export type Ratio = CSSValueAST<``>; +// distance +export type Length = Unit<``, AnyCase>; +// other quantities +export type Flex = Unit<``, AnyCase<`fr`>>; +export type Angle = Unit<``, AnyCase>; +export type Time = Unit<`