@react-x11/components 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/charts/node.d.ts +11 -1
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +44 -14
- package/dist/charts/node.js.map +1 -1
- package/dist/charts/render.d.ts +12 -3
- package/dist/charts/render.d.ts.map +1 -1
- package/dist/charts/render.js +94 -25
- package/dist/charts/render.js.map +1 -1
- package/dist/code/index.d.ts.map +1 -1
- package/dist/code/index.js +27 -24
- package/dist/code/index.js.map +1 -1
- package/dist/code-editor/completion.d.ts.map +1 -1
- package/dist/code-editor/completion.js +3 -2
- package/dist/code-editor/completion.js.map +1 -1
- package/dist/code-editor/index.d.ts.map +1 -1
- package/dist/code-editor/index.js +19 -0
- package/dist/code-editor/index.js.map +1 -1
- package/dist/code-editor/node.d.ts +177 -7
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +1329 -164
- package/dist/code-editor/node.js.map +1 -1
- package/dist/code-language/index.d.ts +1 -1
- package/dist/code-language/index.d.ts.map +1 -1
- package/dist/code-language/index.js +1 -1
- package/dist/code-language/index.js.map +1 -1
- package/dist/code-language/languages/glsl.d.ts.map +1 -1
- package/dist/code-language/languages/glsl.js +10 -2
- package/dist/code-language/languages/glsl.js.map +1 -1
- package/dist/code-language/languages/javascript.d.ts.map +1 -1
- package/dist/code-language/languages/javascript.js +18 -3
- package/dist/code-language/languages/javascript.js.map +1 -1
- package/dist/code-language/languages/json.d.ts.map +1 -1
- package/dist/code-language/languages/json.js +10 -2
- package/dist/code-language/languages/json.js.map +1 -1
- package/dist/code-language/languages/shell.d.ts.map +1 -1
- package/dist/code-language/languages/shell.js +10 -2
- package/dist/code-language/languages/shell.js.map +1 -1
- package/dist/code-language/languages/sql.d.ts.map +1 -1
- package/dist/code-language/languages/sql.js +20 -2
- package/dist/code-language/languages/sql.js.map +1 -1
- package/dist/code-language/runs.d.ts +31 -0
- package/dist/code-language/runs.d.ts.map +1 -1
- package/dist/code-language/runs.js +105 -0
- package/dist/code-language/runs.js.map +1 -1
- package/dist/code-language/stream.d.ts +22 -0
- package/dist/code-language/stream.d.ts.map +1 -1
- package/dist/code-language/stream.js +259 -9
- package/dist/code-language/stream.js.map +1 -1
- package/dist/code-language/types.d.ts +5 -4
- package/dist/code-language/types.d.ts.map +1 -1
- package/dist/codeblock/index.d.ts +26 -2
- package/dist/codeblock/index.d.ts.map +1 -1
- package/dist/codeblock/index.js +57 -4
- package/dist/codeblock/index.js.map +1 -1
- package/dist/flow/draw.d.ts +50 -0
- package/dist/flow/draw.d.ts.map +1 -1
- package/dist/flow/draw.js +132 -33
- package/dist/flow/draw.js.map +1 -1
- package/dist/flow/gl/color.d.ts +13 -0
- package/dist/flow/gl/color.d.ts.map +1 -0
- package/dist/flow/gl/color.js +82 -0
- package/dist/flow/gl/color.js.map +1 -0
- package/dist/flow/gl/index.d.ts +60 -0
- package/dist/flow/gl/index.d.ts.map +1 -0
- package/dist/flow/gl/index.js +283 -0
- package/dist/flow/gl/index.js.map +1 -0
- package/dist/flow/gl/pack.d.ts +133 -0
- package/dist/flow/gl/pack.d.ts.map +1 -0
- package/dist/flow/gl/pack.js +408 -0
- package/dist/flow/gl/pack.js.map +1 -0
- package/dist/flow/gl/renderer.d.ts +95 -0
- package/dist/flow/gl/renderer.d.ts.map +1 -0
- package/dist/flow/gl/renderer.js +459 -0
- package/dist/flow/gl/renderer.js.map +1 -0
- package/dist/flow/gl/shaders.d.ts +79 -0
- package/dist/flow/gl/shaders.d.ts.map +1 -0
- package/dist/flow/gl/shaders.js +346 -0
- package/dist/flow/gl/shaders.js.map +1 -0
- package/dist/flow/gl/text.d.ts +318 -0
- package/dist/flow/gl/text.d.ts.map +1 -0
- package/dist/flow/gl/text.js +1103 -0
- package/dist/flow/gl/text.js.map +1 -0
- package/dist/flow/index.d.ts +1 -1
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +619 -64
- package/dist/flow/index.js.map +1 -1
- package/dist/flow/node.d.ts +621 -72
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +2253 -1018
- package/dist/flow/node.js.map +1 -1
- package/dist/flow/paint.d.ts +35 -0
- package/dist/flow/paint.d.ts.map +1 -0
- package/dist/flow/paint.js +393 -0
- package/dist/flow/paint.js.map +1 -0
- package/dist/flow/paths.d.ts +14 -0
- package/dist/flow/paths.d.ts.map +1 -1
- package/dist/flow/paths.js +45 -19
- package/dist/flow/paths.js.map +1 -1
- package/dist/flow/scene.d.ts +584 -0
- package/dist/flow/scene.d.ts.map +1 -0
- package/dist/flow/scene.js +1453 -0
- package/dist/flow/scene.js.map +1 -0
- package/dist/flow/types.d.ts +111 -1
- package/dist/flow/types.d.ts.map +1 -1
- package/dist/html/controls.d.ts +28 -0
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +52 -2
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/calc.d.ts +14 -0
- package/dist/html/css/calc.d.ts.map +1 -0
- package/dist/html/css/calc.js +344 -0
- package/dist/html/css/calc.js.map +1 -0
- package/dist/html/css/cascade.d.ts +262 -6
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +1373 -67
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +23 -0
- package/dist/html/css/color.d.ts.map +1 -0
- package/dist/html/css/color.js +740 -0
- package/dist/html/css/color.js.map +1 -0
- package/dist/html/css/content.d.ts +92 -0
- package/dist/html/css/content.d.ts.map +1 -0
- package/dist/html/css/content.js +388 -0
- package/dist/html/css/content.js.map +1 -0
- package/dist/html/css/counter-styles.d.ts +76 -0
- package/dist/html/css/counter-styles.d.ts.map +1 -0
- package/dist/html/css/counter-styles.js +893 -0
- package/dist/html/css/counter-styles.js.map +1 -0
- package/dist/html/css/decode.d.ts +12 -0
- package/dist/html/css/decode.d.ts.map +1 -0
- package/dist/html/css/decode.js +116 -0
- package/dist/html/css/decode.js.map +1 -0
- package/dist/html/css/parse.d.ts +110 -10
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +1473 -180
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +501 -15
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +3729 -206
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.d.ts.map +1 -1
- package/dist/html/css/ua.js +65 -16
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +37 -3
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +122 -48
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/css/vars.d.ts +52 -0
- package/dist/html/css/vars.d.ts.map +1 -0
- package/dist/html/css/vars.js +358 -0
- package/dist/html/css/vars.js.map +1 -0
- package/dist/html/dom.d.ts +30 -4
- package/dist/html/dom.d.ts.map +1 -1
- package/dist/html/dom.js +278 -17
- package/dist/html/dom.js.map +1 -1
- package/dist/html/fonts.d.ts +106 -0
- package/dist/html/fonts.d.ts.map +1 -0
- package/dist/html/fonts.js +565 -0
- package/dist/html/fonts.js.map +1 -0
- package/dist/html/gif.d.ts +15 -0
- package/dist/html/gif.d.ts.map +1 -0
- package/dist/html/gif.js +229 -0
- package/dist/html/gif.js.map +1 -0
- package/dist/html/index.d.ts +50 -11
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +115 -9
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +226 -3
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +3077 -219
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +367 -17
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +1742 -182
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +58 -0
- package/dist/html/layout/cache.d.ts.map +1 -0
- package/dist/html/layout/cache.js +224 -0
- package/dist/html/layout/cache.js.map +1 -0
- package/dist/html/layout/collapse.d.ts +45 -0
- package/dist/html/layout/collapse.d.ts.map +1 -0
- package/dist/html/layout/collapse.js +246 -0
- package/dist/html/layout/collapse.js.map +1 -0
- package/dist/html/layout/css-grid.d.ts +7 -0
- package/dist/html/layout/css-grid.d.ts.map +1 -0
- package/dist/html/layout/css-grid.js +900 -0
- package/dist/html/layout/css-grid.js.map +1 -0
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +1025 -89
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/floats.d.ts +45 -6
- package/dist/html/layout/floats.d.ts.map +1 -1
- package/dist/html/layout/floats.js +113 -14
- package/dist/html/layout/floats.js.map +1 -1
- package/dist/html/layout/grid-lines.d.ts +39 -0
- package/dist/html/layout/grid-lines.d.ts.map +1 -0
- package/dist/html/layout/grid-lines.js +119 -0
- package/dist/html/layout/grid-lines.js.map +1 -0
- package/dist/html/layout/grid.d.ts +53 -0
- package/dist/html/layout/grid.d.ts.map +1 -0
- package/dist/html/layout/grid.js +153 -0
- package/dist/html/layout/grid.js.map +1 -0
- package/dist/html/layout/inline.d.ts +154 -1
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +3598 -175
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/shaping.d.ts +6 -0
- package/dist/html/layout/shaping.d.ts.map +1 -0
- package/dist/html/layout/shaping.js +100 -0
- package/dist/html/layout/shaping.js.map +1 -0
- package/dist/html/layout/table.d.ts +3 -0
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +681 -175
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/layout/tracks.d.ts +44 -0
- package/dist/html/layout/tracks.d.ts.map +1 -0
- package/dist/html/layout/tracks.js +349 -0
- package/dist/html/layout/tracks.js.map +1 -0
- package/dist/html/node.d.ts +169 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +1429 -137
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +92 -3
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +3635 -107
- package/dist/html/paint.js.map +1 -1
- package/dist/html/resources.d.ts +60 -14
- package/dist/html/resources.d.ts.map +1 -1
- package/dist/html/resources.js +122 -36
- package/dist/html/resources.js.map +1 -1
- package/dist/html/surfaces.d.ts +23 -0
- package/dist/html/surfaces.d.ts.map +1 -0
- package/dist/html/surfaces.js +68 -0
- package/dist/html/surfaces.js.map +1 -0
- package/dist/html/svg.d.ts +80 -0
- package/dist/html/svg.d.ts.map +1 -0
- package/dist/html/svg.js +442 -0
- package/dist/html/svg.js.map +1 -0
- package/dist/html/url.d.ts +23 -0
- package/dist/html/url.d.ts.map +1 -0
- package/dist/html/url.js +80 -0
- package/dist/html/url.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/codelines.d.ts +42 -0
- package/dist/internal/codelines.d.ts.map +1 -0
- package/dist/internal/codelines.js +170 -0
- package/dist/internal/codelines.js.map +1 -0
- package/dist/internal/collate.d.ts +3 -0
- package/dist/internal/collate.d.ts.map +1 -0
- package/dist/internal/collate.js +11 -0
- package/dist/internal/collate.js.map +1 -0
- package/dist/internal/markdown/ast.d.ts +11 -0
- package/dist/internal/markdown/ast.d.ts.map +1 -1
- package/dist/internal/markdown/parse.d.ts.map +1 -1
- package/dist/internal/markdown/parse.js +109 -8
- package/dist/internal/markdown/parse.js.map +1 -1
- package/dist/internal/prewarm.d.ts +18 -0
- package/dist/internal/prewarm.d.ts.map +1 -0
- package/dist/internal/prewarm.js +42 -0
- package/dist/internal/prewarm.js.map +1 -0
- package/dist/internal/sdf.d.ts +20 -0
- package/dist/internal/sdf.d.ts.map +1 -0
- package/dist/internal/sdf.js +216 -0
- package/dist/internal/sdf.js.map +1 -0
- package/dist/internal/splice.d.ts +12 -0
- package/dist/internal/splice.d.ts.map +1 -0
- package/dist/internal/splice.js +23 -0
- package/dist/internal/splice.js.map +1 -0
- package/dist/internal/window.d.ts +12 -0
- package/dist/internal/window.d.ts.map +1 -1
- package/dist/internal/window.js +23 -6
- package/dist/internal/window.js.map +1 -1
- package/dist/maps/anchors.d.ts +6 -1
- package/dist/maps/anchors.d.ts.map +1 -1
- package/dist/maps/anchors.js +260 -1
- package/dist/maps/anchors.js.map +1 -1
- package/dist/maps/controller.d.ts +57 -0
- package/dist/maps/controller.d.ts.map +1 -1
- package/dist/maps/controller.js +97 -18
- package/dist/maps/controller.js.map +1 -1
- package/dist/maps/gl/buckets.d.ts +5 -1
- package/dist/maps/gl/buckets.d.ts.map +1 -1
- package/dist/maps/gl/buckets.js +5 -2
- package/dist/maps/gl/buckets.js.map +1 -1
- package/dist/maps/gl/build-worker.d.ts +2 -0
- package/dist/maps/gl/build-worker.d.ts.map +1 -1
- package/dist/maps/gl/build-worker.js +1 -1
- package/dist/maps/gl/build-worker.js.map +1 -1
- package/dist/maps/gl/cover.d.ts +23 -4
- package/dist/maps/gl/cover.d.ts.map +1 -1
- package/dist/maps/gl/cover.js +61 -48
- package/dist/maps/gl/cover.js.map +1 -1
- package/dist/maps/gl/placement.d.ts +21 -8
- package/dist/maps/gl/placement.d.ts.map +1 -1
- package/dist/maps/gl/placement.js +133 -58
- package/dist/maps/gl/placement.js.map +1 -1
- package/dist/maps/gl/renderer.d.ts +36 -4
- package/dist/maps/gl/renderer.d.ts.map +1 -1
- package/dist/maps/gl/renderer.js +81 -24
- package/dist/maps/gl/renderer.js.map +1 -1
- package/dist/maps/gl/shaders.d.ts +18 -17
- package/dist/maps/gl/shaders.d.ts.map +1 -1
- package/dist/maps/gl/shaders.js +34 -32
- package/dist/maps/gl/shaders.js.map +1 -1
- package/dist/maps/gl/store.d.ts +10 -0
- package/dist/maps/gl/store.d.ts.map +1 -1
- package/dist/maps/gl/store.js +47 -4
- package/dist/maps/gl/store.js.map +1 -1
- package/dist/maps/gl/text.d.ts +153 -24
- package/dist/maps/gl/text.d.ts.map +1 -1
- package/dist/maps/gl/text.js +379 -63
- package/dist/maps/gl/text.js.map +1 -1
- package/dist/maps/gl/view.d.ts +293 -2
- package/dist/maps/gl/view.d.ts.map +1 -1
- package/dist/maps/gl/view.js +317 -82
- package/dist/maps/gl/view.js.map +1 -1
- package/dist/maps/icons.d.ts +32 -0
- package/dist/maps/icons.d.ts.map +1 -0
- package/dist/maps/icons.js +151 -0
- package/dist/maps/icons.js.map +1 -0
- package/dist/maps/index.d.ts +2 -0
- package/dist/maps/index.d.ts.map +1 -1
- package/dist/maps/index.js +2 -0
- package/dist/maps/index.js.map +1 -1
- package/dist/maps/labels.d.ts +56 -4
- package/dist/maps/labels.d.ts.map +1 -1
- package/dist/maps/labels.js +213 -46
- package/dist/maps/labels.js.map +1 -1
- package/dist/maps/node.d.ts +17 -0
- package/dist/maps/node.d.ts.map +1 -1
- package/dist/maps/node.js +64 -30
- package/dist/maps/node.js.map +1 -1
- package/dist/maps/proj.d.ts +48 -0
- package/dist/maps/proj.d.ts.map +1 -1
- package/dist/maps/proj.js +73 -0
- package/dist/maps/proj.js.map +1 -1
- package/dist/maps/sources.d.ts +5 -3
- package/dist/maps/sources.d.ts.map +1 -1
- package/dist/maps/sources.js +5 -3
- package/dist/maps/sources.js.map +1 -1
- package/dist/maps/style.d.ts +35 -0
- package/dist/maps/style.d.ts.map +1 -1
- package/dist/maps/style.js.map +1 -1
- package/dist/maps/styles.d.ts +12 -0
- package/dist/maps/styles.d.ts.map +1 -1
- package/dist/maps/styles.js +120 -6
- package/dist/maps/styles.js.map +1 -1
- package/dist/maps/tiles.d.ts +31 -11
- package/dist/maps/tiles.d.ts.map +1 -1
- package/dist/maps/tiles.js +61 -27
- package/dist/maps/tiles.js.map +1 -1
- package/dist/maps/types.d.ts +28 -4
- package/dist/maps/types.d.ts.map +1 -1
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +209 -68
- package/dist/markdown/index.js.map +1 -1
- package/dist/markdown/spans.d.ts.map +1 -1
- package/dist/markdown/spans.js +6 -0
- package/dist/markdown/spans.js.map +1 -1
- package/dist/qml/containers.d.ts.map +1 -1
- package/dist/qml/containers.js +6 -1
- package/dist/qml/containers.js.map +1 -1
- package/dist/qml/interaction.d.ts.map +1 -1
- package/dist/qml/interaction.js +38 -5
- package/dist/qml/interaction.js.map +1 -1
- package/dist/qml/layouts.d.ts.map +1 -1
- package/dist/qml/layouts.js +21 -6
- package/dist/qml/layouts.js.map +1 -1
- package/dist/qml/qtquick.d.ts.map +1 -1
- package/dist/qml/qtquick.js +9 -2
- package/dist/qml/qtquick.js.map +1 -1
- package/dist/qml/react.d.ts.map +1 -1
- package/dist/qml/react.js +9 -7
- package/dist/qml/react.js.map +1 -1
- package/dist/reorder/clock.d.ts +7 -0
- package/dist/reorder/clock.d.ts.map +1 -0
- package/dist/reorder/clock.js +26 -0
- package/dist/reorder/clock.js.map +1 -0
- package/dist/reorder/index.d.ts +25 -3
- package/dist/reorder/index.d.ts.map +1 -1
- package/dist/reorder/index.js +64 -19
- package/dist/reorder/index.js.map +1 -1
- package/dist/rich-text-editor/index.d.ts +0 -11
- package/dist/rich-text-editor/index.d.ts.map +1 -1
- package/dist/rich-text-editor/index.js +38 -9
- package/dist/rich-text-editor/index.js.map +1 -1
- package/dist/rich-text-editor/keymap.d.ts.map +1 -1
- package/dist/rich-text-editor/keymap.js +2 -1
- package/dist/rich-text-editor/keymap.js.map +1 -1
- package/dist/rich-text-editor/keys.d.ts +17 -6
- package/dist/rich-text-editor/keys.d.ts.map +1 -1
- package/dist/rich-text-editor/keys.js +135 -26
- package/dist/rich-text-editor/keys.js.map +1 -1
- package/dist/rich-text-editor/markdown.d.ts.map +1 -1
- package/dist/rich-text-editor/markdown.js +30 -1
- package/dist/rich-text-editor/markdown.js.map +1 -1
- package/dist/rich-text-editor/marks.d.ts +9 -0
- package/dist/rich-text-editor/marks.d.ts.map +1 -0
- package/dist/rich-text-editor/marks.js +83 -0
- package/dist/rich-text-editor/marks.js.map +1 -0
- package/dist/rich-text-editor/render.d.ts.map +1 -1
- package/dist/rich-text-editor/render.js +28 -10
- package/dist/rich-text-editor/render.js.map +1 -1
- package/dist/rich-text-editor/replace.d.ts +13 -0
- package/dist/rich-text-editor/replace.d.ts.map +1 -0
- package/dist/rich-text-editor/replace.js +64 -0
- package/dist/rich-text-editor/replace.js.map +1 -0
- package/dist/rich-text-editor/toolbar.d.ts.map +1 -1
- package/dist/rich-text-editor/toolbar.js +1 -1
- package/dist/rich-text-editor/toolbar.js.map +1 -1
- package/dist/rich-text-editor/virtual.d.ts.map +1 -1
- package/dist/rich-text-editor/virtual.js +17 -0
- package/dist/rich-text-editor/virtual.js.map +1 -1
- package/dist/richtext/node.d.ts +46 -2
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +187 -4
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +13 -8
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +18 -8
- package/dist/richtext/runs.js.map +1 -1
- package/dist/table/index.d.ts.map +1 -1
- package/dist/table/index.js +23 -0
- package/dist/table/index.js.map +1 -1
- package/dist/table/rows.d.ts +3 -2
- package/dist/table/rows.d.ts.map +1 -1
- package/dist/table/rows.js +20 -6
- package/dist/table/rows.js.map +1 -1
- package/dist/terminal/index.d.ts.map +1 -1
- package/dist/terminal/index.js +4 -0
- package/dist/terminal/index.js.map +1 -1
- package/dist/terminal/vt/pty.d.ts +13 -2
- package/dist/terminal/vt/pty.d.ts.map +1 -1
- package/dist/terminal/vt/pty.js +18 -3
- package/dist/terminal/vt/pty.js.map +1 -1
- package/dist/terminal-output/index.d.ts.map +1 -1
- package/dist/terminal-output/index.js +103 -23
- package/dist/terminal-output/index.js.map +1 -1
- package/dist/terminal-output/runs.d.ts +4 -1
- package/dist/terminal-output/runs.d.ts.map +1 -1
- package/dist/terminal-output/runs.js +5 -1
- package/dist/terminal-output/runs.js.map +1 -1
- package/dist/three/canvas.d.ts.map +1 -1
- package/dist/three/canvas.js +85 -7
- package/dist/three/canvas.js.map +1 -1
- package/dist/three/events.d.ts +22 -4
- package/dist/three/events.d.ts.map +1 -1
- package/dist/three/events.js +34 -4
- package/dist/three/events.js.map +1 -1
- package/dist/tree/index.d.ts.map +1 -1
- package/dist/tree/index.js +17 -0
- package/dist/tree/index.js.map +1 -1
- package/package.json +42 -33
- package/src/charts/node.ts +62 -26
- package/src/charts/render.ts +120 -30
- package/src/code/index.ts +36 -32
- package/src/code-editor/completion.ts +3 -2
- package/src/code-editor/index.ts +19 -0
- package/src/code-editor/node.ts +1574 -184
- package/src/code-language/index.ts +1 -1
- package/src/code-language/languages/glsl.ts +12 -2
- package/src/code-language/languages/javascript.ts +21 -4
- package/src/code-language/languages/json.ts +12 -2
- package/src/code-language/languages/shell.ts +12 -2
- package/src/code-language/languages/sql.ts +22 -2
- package/src/code-language/runs.ts +111 -1
- package/src/code-language/stream.ts +257 -17
- package/src/code-language/types.ts +5 -4
- package/src/codeblock/index.ts +68 -4
- package/src/flow/draw.ts +167 -41
- package/src/flow/gl/color.ts +85 -0
- package/src/flow/gl/index.ts +342 -0
- package/src/flow/gl/pack.ts +585 -0
- package/src/flow/gl/renderer.ts +679 -0
- package/src/flow/gl/shaders.ts +371 -0
- package/src/flow/gl/text.ts +1295 -0
- package/src/flow/index.ts +794 -67
- package/src/flow/node.ts +2496 -1256
- package/src/flow/paint.ts +511 -0
- package/src/flow/paths.ts +80 -15
- package/src/flow/scene.ts +2199 -0
- package/src/flow/types.ts +113 -1
- package/src/html/controls.ts +73 -2
- package/src/html/css/calc.ts +385 -0
- package/src/html/css/cascade.ts +1481 -62
- package/src/html/css/color.ts +755 -0
- package/src/html/css/content.ts +417 -0
- package/src/html/css/counter-styles.ts +973 -0
- package/src/html/css/decode.ts +130 -0
- package/src/html/css/parse.ts +1514 -160
- package/src/html/css/style.ts +4131 -223
- package/src/html/css/ua.ts +66 -17
- package/src/html/css/values.ts +150 -50
- package/src/html/css/vars.ts +348 -0
- package/src/html/dom.ts +284 -17
- package/src/html/fonts.ts +641 -0
- package/src/html/gif.ts +233 -0
- package/src/html/index.ts +180 -20
- package/src/html/layout/block.ts +3560 -208
- package/src/html/layout/boxes.ts +2141 -190
- package/src/html/layout/cache.ts +289 -0
- package/src/html/layout/collapse.ts +317 -0
- package/src/html/layout/css-grid.ts +1063 -0
- package/src/html/layout/flex.ts +1115 -106
- package/src/html/layout/floats.ts +128 -14
- package/src/html/layout/grid-lines.ts +141 -0
- package/src/html/layout/grid.ts +181 -0
- package/src/html/layout/inline.ts +4187 -192
- package/src/html/layout/shaping.ts +140 -0
- package/src/html/layout/table.ts +696 -186
- package/src/html/layout/tracks.ts +437 -0
- package/src/html/node.ts +1585 -156
- package/src/html/paint.ts +4582 -267
- package/src/html/resources.ts +161 -50
- package/src/html/surfaces.ts +81 -0
- package/src/html/svg.ts +504 -0
- package/src/html/url.ts +86 -0
- package/src/index.ts +2 -0
- package/src/internal/codelines.ts +200 -0
- package/src/internal/collate.ts +12 -0
- package/src/internal/markdown/ast.ts +11 -0
- package/src/internal/markdown/parse.ts +154 -11
- package/src/internal/prewarm.ts +45 -0
- package/src/internal/sdf.ts +227 -0
- package/src/internal/splice.ts +25 -0
- package/src/internal/window.ts +36 -5
- package/src/maps/anchors.ts +307 -1
- package/src/maps/controller.ts +116 -31
- package/src/maps/gl/buckets.ts +5 -1
- package/src/maps/gl/build-worker.ts +13 -2
- package/src/maps/gl/cover.ts +79 -44
- package/src/maps/gl/placement.ts +221 -69
- package/src/maps/gl/renderer.ts +104 -20
- package/src/maps/gl/shaders.ts +35 -32
- package/src/maps/gl/store.ts +66 -17
- package/src/maps/gl/text.ts +444 -71
- package/src/maps/gl/view.ts +361 -96
- package/src/maps/icons.ts +183 -0
- package/src/maps/index.ts +4 -0
- package/src/maps/labels.ts +269 -49
- package/src/maps/node.ts +82 -49
- package/src/maps/proj.ts +87 -0
- package/src/maps/sources.ts +5 -3
- package/src/maps/style.ts +32 -0
- package/src/maps/styles.ts +215 -4
- package/src/maps/tiles.ts +64 -31
- package/src/maps/types.ts +28 -4
- package/src/markdown/index.ts +293 -92
- package/src/markdown/spans.ts +5 -0
- package/src/qml/containers.tsx +6 -1
- package/src/qml/interaction.tsx +42 -5
- package/src/qml/layouts.tsx +21 -6
- package/src/qml/qtquick.tsx +9 -2
- package/src/qml/react.tsx +7 -5
- package/src/reorder/clock.ts +27 -0
- package/src/reorder/index.ts +100 -27
- package/src/rich-text-editor/index.ts +37 -12
- package/src/rich-text-editor/keymap.ts +1 -1
- package/src/rich-text-editor/keys.ts +135 -26
- package/src/rich-text-editor/markdown.ts +36 -1
- package/src/rich-text-editor/marks.ts +101 -0
- package/src/rich-text-editor/render.ts +49 -12
- package/src/rich-text-editor/replace.ts +85 -0
- package/src/rich-text-editor/toolbar.ts +1 -1
- package/src/rich-text-editor/virtual.ts +25 -0
- package/src/richtext/node.ts +235 -14
- package/src/richtext/runs.ts +23 -12
- package/src/table/index.ts +29 -0
- package/src/table/rows.ts +18 -8
- package/src/terminal/index.ts +4 -0
- package/src/terminal/vt/pty.ts +16 -2
- package/src/terminal-output/index.ts +129 -31
- package/src/terminal-output/runs.ts +16 -2
- package/src/three/canvas.ts +102 -8
- package/src/three/events.ts +49 -9
- package/src/tree/index.ts +19 -0
package/src/html/css/parse.ts
CHANGED
|
@@ -14,13 +14,19 @@
|
|
|
14
14
|
// Selector *matching* is still `css-select`'s (see cascade.ts). Parsing a
|
|
15
15
|
// stylesheet is easy; matching `li:nth-child(2n+1) > a[href^="/"]` correctly
|
|
16
16
|
// and quickly is not, and that is the part worth importing.
|
|
17
|
+
import { resolveUrl } from '../url.js';
|
|
17
18
|
import { parseLength } from './values.js';
|
|
19
|
+
import { hasVar, unbalanced, validVars } from './vars.js';
|
|
18
20
|
|
|
19
21
|
/** One `prop: value` pair, with `!important` already taken off the value. */
|
|
20
22
|
export interface Declaration {
|
|
21
23
|
prop: string;
|
|
22
24
|
value: string;
|
|
23
25
|
important: boolean;
|
|
26
|
+
/** A custom property, `--name`, whose name keeps its case. */
|
|
27
|
+
custom?: true;
|
|
28
|
+
/** The value has a `var()` to replace before it is read. */
|
|
29
|
+
vars?: true;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export interface StyleRule {
|
|
@@ -38,17 +44,54 @@ export interface StyleRule {
|
|
|
38
44
|
* `(max-width: 60em)` is not the same set as the four conditions in a
|
|
39
45
|
* row, and flattening cannot tell them apart. */
|
|
40
46
|
media: MediaCondition[][] | null;
|
|
47
|
+
/** The cascade layer this rule is in (CSS Cascade 5): the rank of each
|
|
48
|
+
* layer on its path, outermost first, in the order the document first
|
|
49
|
+
* names them. Null for a rule in no layer, which outranks every layer. */
|
|
50
|
+
layer: readonly number[] | null;
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
export interface Stylesheet {
|
|
44
54
|
rules: StyleRule[];
|
|
45
55
|
/** `@import` targets, in order — the host fetches them through the
|
|
46
|
-
* resource seam and splices the result in ahead of this sheet.
|
|
56
|
+
* resource seam and splices the result in ahead of this sheet. Absolute
|
|
57
|
+
* when the sheet was parsed with a base. */
|
|
47
58
|
imports: string[];
|
|
48
59
|
/** Every width a `@media` rule in this sheet switches on. The renderer
|
|
49
60
|
* keeps these so a resize can tell "the layout changed" from "the
|
|
50
61
|
* *cascade* changed", and restyle only when it crossed one. */
|
|
51
62
|
breakpoints: number[];
|
|
63
|
+
/** The `@font-face` rules, in order (see `fonts.ts`). */
|
|
64
|
+
fontFaces: FontFaceRule[];
|
|
65
|
+
/** `@counter-style` rules, in order: a name and its descriptors, which
|
|
66
|
+
* the cascade reads into counter styles (CSS Counter Styles 3, 3). */
|
|
67
|
+
counterStyles?: { prelude: string; declarations: Declaration[] }[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* One `@font-face` (CSS Fonts 4, 4): a face of a family, and where to get
|
|
72
|
+
* it. The descriptors are kept to what choosing and registering a face
|
|
73
|
+
* reads — `font-stretch`, `font-display` and the feature descriptors are
|
|
74
|
+
* not.
|
|
75
|
+
*/
|
|
76
|
+
export interface FontFaceRule {
|
|
77
|
+
/** The family as the document names it, unquoted. */
|
|
78
|
+
family: string;
|
|
79
|
+
/** The `url()`s of `src`, in order, each with its `format()` hint
|
|
80
|
+
* lowercased; `local()` entries are left out. */
|
|
81
|
+
sources: FontFaceSource[];
|
|
82
|
+
/** The weights the face covers: `[400, 400]` for a static regular, a
|
|
83
|
+
* range for a variable face. */
|
|
84
|
+
weight: [number, number];
|
|
85
|
+
style: 'normal' | 'italic';
|
|
86
|
+
/** Code point ranges, inclusive; null for every code point. */
|
|
87
|
+
unicodeRange: [number, number][] | null;
|
|
88
|
+
/** The `@media` blocks the rule sits under, as a style rule's. */
|
|
89
|
+
media: MediaCondition[][] | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface FontFaceSource {
|
|
93
|
+
url: string;
|
|
94
|
+
format: string | null;
|
|
52
95
|
}
|
|
53
96
|
|
|
54
97
|
/** The tests this evaluates live: a width, a colour scheme, or both.
|
|
@@ -67,112 +110,429 @@ export interface MediaCondition {
|
|
|
67
110
|
|
|
68
111
|
const IMPORTANT_RE = /!\s*important\s*$/i;
|
|
69
112
|
|
|
113
|
+
/** A layer's name: identifiers joined by dots. */
|
|
114
|
+
const LAYER_NAME =
|
|
115
|
+
/^-?[_a-zA-Z\u00a0-\uffff][\w\u00a0-\uffff-]*(?:\.-?[_a-zA-Z\u00a0-\uffff][\w\u00a0-\uffff-]*)*$/;
|
|
116
|
+
|
|
117
|
+
/** The at-rules that are rules with a block — which an `@import` after them
|
|
118
|
+
* is too late for. */
|
|
119
|
+
const BLOCK_AT_RULES = new Set([
|
|
120
|
+
'media',
|
|
121
|
+
'supports',
|
|
122
|
+
'font-face',
|
|
123
|
+
'page',
|
|
124
|
+
'keyframes',
|
|
125
|
+
'-webkit-keyframes',
|
|
126
|
+
'counter-style',
|
|
127
|
+
'layer',
|
|
128
|
+
'container',
|
|
129
|
+
'property',
|
|
130
|
+
'font-feature-values',
|
|
131
|
+
]);
|
|
132
|
+
|
|
70
133
|
/**
|
|
71
134
|
* Parse a stylesheet. `order` continues across sheets, so the caller passes
|
|
72
135
|
* a running counter and the cascade's document-order tie-break holds across
|
|
73
136
|
* the whole document rather than restarting per `<style>`.
|
|
137
|
+
*
|
|
138
|
+
* The structure is CSS Syntax's: an at-rule is an at-keyword up to its `;`
|
|
139
|
+
* or its block, a style rule is everything up to its block, and what lies
|
|
140
|
+
* between is read a component value at a time — a string, an escape, a
|
|
141
|
+
* bracketed block with its match — so a brace or a semicolon in a string, in
|
|
142
|
+
* an escape or inside brackets never ends anything. A rule whose selector is
|
|
143
|
+
* not one is dropped whole, and so is a group with any selector in it that
|
|
144
|
+
* is not one (CSS 2.1 4.1.7).
|
|
74
145
|
*/
|
|
75
|
-
export function parseStylesheet(
|
|
76
|
-
|
|
146
|
+
export function parseStylesheet(
|
|
147
|
+
text: string,
|
|
148
|
+
startOrder = 0,
|
|
149
|
+
/** The document's layers, by their full names, ranked in the order the
|
|
150
|
+
* document first names them: one map across all of its sheets, since a
|
|
151
|
+
* layer one sheet names first is the same layer in the next. */
|
|
152
|
+
layers: Map<string, number> = new Map(),
|
|
153
|
+
/** The URL the sheet's own relative URLs resolve against — its own, or
|
|
154
|
+
* the document's for a `<style>` — or null to leave them as written.
|
|
155
|
+
* Every `url()`, `@import` and `@font-face` source comes out absolute. */
|
|
156
|
+
base: string | null = null,
|
|
157
|
+
): Stylesheet {
|
|
158
|
+
text = withoutComments(text);
|
|
159
|
+
const sheet: Stylesheet = {
|
|
160
|
+
rules: [],
|
|
161
|
+
imports: [],
|
|
162
|
+
breakpoints: [],
|
|
163
|
+
fontFaces: [],
|
|
164
|
+
};
|
|
77
165
|
let order = startOrder;
|
|
78
166
|
const breakpoints = new Set<number>();
|
|
167
|
+
// `@import` counts only ahead of every other rule, `@charset` aside
|
|
168
|
+
let importsAllowed = true;
|
|
79
169
|
|
|
80
|
-
|
|
170
|
+
/** A layer's rank, named for the first time if it has not been. */
|
|
171
|
+
const rankOf = (name: string): number => {
|
|
172
|
+
let rank = layers.get(name);
|
|
173
|
+
if (rank === undefined) {
|
|
174
|
+
rank = layers.size;
|
|
175
|
+
layers.set(name, rank);
|
|
176
|
+
}
|
|
177
|
+
return rank;
|
|
178
|
+
};
|
|
179
|
+
/** The path of ranks down to a layer, `a.b` inside `outer` being three
|
|
180
|
+
* levels, and the layer's full name. */
|
|
181
|
+
const enter = (
|
|
182
|
+
name: string,
|
|
183
|
+
layer: readonly number[] | null,
|
|
184
|
+
path: string,
|
|
185
|
+
): [number[], string] => {
|
|
186
|
+
const ranks = layer ? [...layer] : [];
|
|
187
|
+
let full = path;
|
|
188
|
+
for (const part of name.split('.')) {
|
|
189
|
+
full = full ? `${full}.${part}` : part;
|
|
190
|
+
ranks.push(rankOf(full));
|
|
191
|
+
}
|
|
192
|
+
return [ranks, full];
|
|
193
|
+
};
|
|
194
|
+
let anonymous = 0;
|
|
195
|
+
|
|
196
|
+
const walk = (
|
|
197
|
+
source: string,
|
|
198
|
+
media: MediaCondition[][] | null,
|
|
199
|
+
layer: readonly number[] | null = null,
|
|
200
|
+
path = '',
|
|
201
|
+
): void => {
|
|
81
202
|
let i = 0;
|
|
82
203
|
const n = source.length;
|
|
83
204
|
while (i < n) {
|
|
84
205
|
i = skipTrivia(source, i);
|
|
85
206
|
if (i >= n) break;
|
|
86
207
|
|
|
87
|
-
if (source[i] === '@') {
|
|
208
|
+
if (source[i] === '@' && startsIdent(source, i + 1)) {
|
|
88
209
|
const at = readAtRule(source, i);
|
|
89
210
|
i = at.end;
|
|
90
211
|
const name = at.name.toLowerCase();
|
|
212
|
+
if (name === 'charset') continue;
|
|
91
213
|
if (name === 'import') {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
214
|
+
if (importsAllowed && media === null) {
|
|
215
|
+
const url = importUrl(at.prelude);
|
|
216
|
+
if (url && importApplies(at.prelude)) {
|
|
217
|
+
sheet.imports.push(resolveUrl(url, base));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
// Only a rule that is one closes the imports: `@media;`, `@page;`
|
|
223
|
+
// and an at-rule nobody knows are dropped as though never written.
|
|
224
|
+
if (at.block !== null && BLOCK_AT_RULES.has(name)) {
|
|
225
|
+
importsAllowed = false;
|
|
226
|
+
}
|
|
227
|
+
if (name === 'media' && at.block !== null) {
|
|
95
228
|
const conditions = parseMediaQuery(at.prelude);
|
|
96
229
|
for (const c of conditions) {
|
|
97
230
|
if (c.min !== undefined) breakpoints.add(c.min);
|
|
98
|
-
if (c.max !== undefined) breakpoints.add(c.max +
|
|
231
|
+
if (c.max !== undefined) breakpoints.add(c.max + MAX_EDGE);
|
|
99
232
|
}
|
|
100
233
|
// A nested `@media` intersects with the one above it; pushing a
|
|
101
234
|
// level rather than merging keeps "all of these blocks hold" exact
|
|
102
235
|
// when two of them overlap.
|
|
103
|
-
walk(
|
|
236
|
+
walk(
|
|
237
|
+
at.block,
|
|
238
|
+
media ? [...media, conditions] : [conditions],
|
|
239
|
+
layer,
|
|
240
|
+
path,
|
|
241
|
+
);
|
|
104
242
|
} else if (name === 'supports' && at.block !== null) {
|
|
105
243
|
// Everything in a `@supports` block is markup this renderer either
|
|
106
244
|
// understands or ignores per-declaration, so entering it is closer
|
|
107
245
|
// to right than skipping it.
|
|
108
|
-
walk(at.block, media);
|
|
246
|
+
walk(at.block, media, layer, path);
|
|
247
|
+
} else if (name === 'layer') {
|
|
248
|
+
// `@layer a, b;` names layers, and so fixes their order, and
|
|
249
|
+
// `@layer a { … }` puts rules in one; one with no name is a layer
|
|
250
|
+
// of its own. Tailwind 4 writes all of its CSS in four of them.
|
|
251
|
+
const names = at.prelude
|
|
252
|
+
.split(',')
|
|
253
|
+
.map((part) => part.trim())
|
|
254
|
+
.filter(Boolean);
|
|
255
|
+
if (!names.every((part) => LAYER_NAME.test(part))) continue;
|
|
256
|
+
if (at.block === null) {
|
|
257
|
+
for (const part of names) enter(part, layer, path);
|
|
258
|
+
} else if (names.length <= 1) {
|
|
259
|
+
const [ranks, full] = enter(
|
|
260
|
+
names[0] ?? `\u0000${anonymous++}`,
|
|
261
|
+
layer,
|
|
262
|
+
path,
|
|
263
|
+
);
|
|
264
|
+
walk(at.block, media, ranks, full);
|
|
265
|
+
}
|
|
266
|
+
} else if (name === 'font-face' && at.block !== null) {
|
|
267
|
+
const face = parseFontFace(at.block, base, media);
|
|
268
|
+
if (face) sheet.fontFaces.push(face);
|
|
269
|
+
} else if (name === 'counter-style' && at.block !== null) {
|
|
270
|
+
(sheet.counterStyles ??= []).push({
|
|
271
|
+
prelude: at.prelude,
|
|
272
|
+
declarations: parseDeclarations(at.block),
|
|
273
|
+
});
|
|
109
274
|
}
|
|
110
|
-
// @
|
|
111
|
-
//
|
|
275
|
+
// @keyframes, @page: nothing to do, and the block was already
|
|
276
|
+
// consumed.
|
|
112
277
|
continue;
|
|
113
278
|
}
|
|
114
279
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
280
|
+
// A style rule runs to its block, whatever comes first: a stray `;`
|
|
281
|
+
// or an `@` that names nothing is part of its selector, which then is
|
|
282
|
+
// not one, and the rule goes with it.
|
|
283
|
+
const blockAt = scanTo(source, i, '{');
|
|
284
|
+
if (blockAt >= n) break;
|
|
285
|
+
const prelude = source.slice(i, blockAt).trim();
|
|
286
|
+
const block = readBlock(source, blockAt);
|
|
119
287
|
i = block.end;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
288
|
+
const selectors = rawSelectors(prelude);
|
|
289
|
+
if (!selectors) continue;
|
|
290
|
+
importsAllowed = false;
|
|
291
|
+
styleRule(selectors, block.body, media, layer, path, 0);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* A style rule, and the rules nested in it (CSS Nesting 1): a rule inside
|
|
297
|
+
* a rule's block is relative to it, `&` standing for it and a selector
|
|
298
|
+
* without one a descendant of it, and an `@media`, `@supports` or
|
|
299
|
+
* `@layer` inside one holds declarations for the same selectors, under
|
|
300
|
+
* its condition. Tailwind 4 writes its variants so — `md:flex` is
|
|
301
|
+
* `.md\:flex { @media (width >= 48rem) { display: flex } }`, and
|
|
302
|
+
* `hover:` is `&:hover` — and they were dropped with the rule. Each
|
|
303
|
+
* nested rule comes after its parent's declarations in the cascade's
|
|
304
|
+
* order, as it comes after them in the sheet.
|
|
305
|
+
*/
|
|
306
|
+
const styleRule = (
|
|
307
|
+
selectors: string[],
|
|
308
|
+
body: string,
|
|
309
|
+
media: MediaCondition[][] | null,
|
|
310
|
+
layer: readonly number[] | null,
|
|
311
|
+
path: string,
|
|
312
|
+
depth: number,
|
|
313
|
+
): void => {
|
|
314
|
+
// the plain case, a block with nothing nested in it
|
|
315
|
+
if (!body.includes('{')) {
|
|
316
|
+
emit(selectors, parseDeclarations(body), media, layer);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const { declarations, nested } = splitNested(body);
|
|
320
|
+
emit(selectors, parseDeclarations(declarations), media, layer);
|
|
321
|
+
if (depth >= MAX_NESTING) return;
|
|
322
|
+
for (const item of nested) {
|
|
323
|
+
if (item.kind === 'rule') {
|
|
324
|
+
const inner = nestedSelectors(item.prelude, selectors);
|
|
325
|
+
if (inner) styleRule(inner, item.body, media, layer, path, depth + 1);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const name = item.name.toLowerCase();
|
|
329
|
+
if (item.block === null) continue;
|
|
330
|
+
if (name === 'media') {
|
|
331
|
+
const conditions = parseMediaQuery(item.prelude);
|
|
332
|
+
for (const c of conditions) {
|
|
333
|
+
if (c.min !== undefined) breakpoints.add(c.min);
|
|
334
|
+
if (c.max !== undefined) breakpoints.add(c.max + MAX_EDGE);
|
|
335
|
+
}
|
|
336
|
+
styleRule(
|
|
337
|
+
selectors,
|
|
338
|
+
item.block,
|
|
339
|
+
media ? [...media, conditions] : [conditions],
|
|
340
|
+
layer,
|
|
341
|
+
path,
|
|
342
|
+
depth + 1,
|
|
343
|
+
);
|
|
344
|
+
} else if (name === 'supports') {
|
|
345
|
+
styleRule(selectors, item.block, media, layer, path, depth + 1);
|
|
346
|
+
} else if (name === 'layer' && LAYER_NAME.test(item.prelude.trim())) {
|
|
347
|
+
const [ranks, full] = enter(item.prelude.trim(), layer, path);
|
|
348
|
+
styleRule(selectors, item.block, media, ranks, full, depth + 1);
|
|
132
349
|
}
|
|
133
350
|
}
|
|
134
351
|
};
|
|
135
352
|
|
|
353
|
+
const emit = (
|
|
354
|
+
selectors: string[],
|
|
355
|
+
declarations: Declaration[],
|
|
356
|
+
media: MediaCondition[][] | null,
|
|
357
|
+
layer: readonly number[] | null,
|
|
358
|
+
): void => {
|
|
359
|
+
if (!declarations.length) return;
|
|
360
|
+
if (base !== null) {
|
|
361
|
+
for (const d of declarations) d.value = absoluteUrls(d.value, base);
|
|
362
|
+
}
|
|
363
|
+
for (const raw of selectors) {
|
|
364
|
+
const selector = raw.includes('\\') ? forMatcher(raw) : raw;
|
|
365
|
+
sheet.rules.push({
|
|
366
|
+
selector,
|
|
367
|
+
specificity: specificityOf(selector),
|
|
368
|
+
order: order++,
|
|
369
|
+
declarations,
|
|
370
|
+
media,
|
|
371
|
+
layer,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
136
376
|
walk(text, null);
|
|
137
377
|
sheet.breakpoints = [...breakpoints].sort((a, b) => a - b);
|
|
138
378
|
return sheet;
|
|
139
379
|
}
|
|
140
380
|
|
|
381
|
+
/**
|
|
382
|
+
* A style sheet with its comments taken out, strings left alone. CSS drops a
|
|
383
|
+
* comment wherever it stands (CSS 2.1 4.1.9) — between rules, which the
|
|
384
|
+
* scanner already skipped, but also inside a selector: `div /* note *\/ {`
|
|
385
|
+
* kept the note in the selector, the matcher refused it, and the rule was
|
|
386
|
+
* dropped whole. In a selector a comment is replaced by nothing, as the
|
|
387
|
+
* tokenizer does: `.a/**\/.b` is one compound selector. In a declaration
|
|
388
|
+
* it is replaced by a space, since there it still ends the token before it:
|
|
389
|
+
* `1/**\/0px` is two values and no length, and `-/**\/10px` is no
|
|
390
|
+
* negative one. `decls` says the text starts inside a declaration block,
|
|
391
|
+
* which a `style` attribute does.
|
|
392
|
+
*/
|
|
393
|
+
function withoutComments(text: string, decls = false): string {
|
|
394
|
+
if (!text.includes('/*')) return text;
|
|
395
|
+
let out = '';
|
|
396
|
+
let from = 0;
|
|
397
|
+
let quote = '';
|
|
398
|
+
// for each open block, whether it holds declarations rather than rules
|
|
399
|
+
const blocks: boolean[] = [];
|
|
400
|
+
let prelude = 0;
|
|
401
|
+
// whether the rule being read is an at-rule, once its first token says
|
|
402
|
+
let atRule: boolean | null = null;
|
|
403
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
404
|
+
const c = text[i];
|
|
405
|
+
if (quote) {
|
|
406
|
+
if (c === '\\') i = escapeEnd(text, i) - 1;
|
|
407
|
+
else if (c === quote || c === '\n') quote = '';
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (atRule === null && !isSpace(c) && !(c === '/' && text[i + 1] === '*')) {
|
|
411
|
+
atRule = c === '@';
|
|
412
|
+
}
|
|
413
|
+
if (c === '\\') {
|
|
414
|
+
// an escape: `\/*` is a slash and an asterisk, not a comment
|
|
415
|
+
i += 1;
|
|
416
|
+
} else if (c === '"' || c === "'") {
|
|
417
|
+
quote = c;
|
|
418
|
+
} else if (
|
|
419
|
+
(c === 'u' || c === 'U') &&
|
|
420
|
+
text[i + 3] === '(' &&
|
|
421
|
+
text.slice(i, i + 3).toLowerCase() === 'url' &&
|
|
422
|
+
!isNameChar(text[i - 1])
|
|
423
|
+
) {
|
|
424
|
+
// an unquoted url is one token, `/*` and all: `url(a/*b)` is no
|
|
425
|
+
// comment, and taken for one it ran on through the rest of the sheet
|
|
426
|
+
i = urlEnd(text, i + 3) - 1;
|
|
427
|
+
} else if (c === '{') {
|
|
428
|
+
const inDecls = blocks.length ? blocks[blocks.length - 1] : decls;
|
|
429
|
+
blocks.push(inDecls || !opensRuleList(text, prelude, i));
|
|
430
|
+
prelude = i + 1;
|
|
431
|
+
atRule = null;
|
|
432
|
+
} else if (c === '}') {
|
|
433
|
+
blocks.pop();
|
|
434
|
+
prelude = i + 1;
|
|
435
|
+
atRule = null;
|
|
436
|
+
} else if (c === ';') {
|
|
437
|
+
prelude = i + 1;
|
|
438
|
+
atRule = null;
|
|
439
|
+
} else if (c === '/' && text[i + 1] === '*') {
|
|
440
|
+
const close = text.indexOf('*/', i + 2);
|
|
441
|
+
const inDecls = blocks.length ? blocks[blocks.length - 1] : decls;
|
|
442
|
+
// `@media/**\/all` is `@media all`
|
|
443
|
+
out += text.slice(from, i) + (inDecls || atRule ? ' ' : '');
|
|
444
|
+
i = close < 0 ? text.length : close + 1;
|
|
445
|
+
from = i + 1;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return out + text.slice(from);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** Whether the block a `{` at `end` opens holds rules rather than
|
|
452
|
+
* declarations: a conditional or grouping at-rule's does. */
|
|
453
|
+
function opensRuleList(text: string, from: number, end: number): boolean {
|
|
454
|
+
let j = from;
|
|
455
|
+
while (j < end && (isSpace(text[j]) || text[j] === '/')) {
|
|
456
|
+
if (text[j] === '/' && text[j + 1] === '*') {
|
|
457
|
+
const close = text.indexOf('*/', j + 2);
|
|
458
|
+
j = close < 0 ? end : close + 2;
|
|
459
|
+
} else if (text[j] === '/') {
|
|
460
|
+
break;
|
|
461
|
+
} else {
|
|
462
|
+
j += 1;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return text[j] === '@' && RULE_LIST_AT.test(text.slice(j + 1, end));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const RULE_LIST_AT =
|
|
469
|
+
/^(?:media|supports|document|-moz-document|layer|container|scope|starting-style)(?![\w-])/i;
|
|
470
|
+
|
|
141
471
|
/**
|
|
142
472
|
* Parse a declaration block — also the parser for a `style=""` attribute,
|
|
143
|
-
* which is the same grammar with no braces around it.
|
|
473
|
+
* which is the same grammar with no braces around it. CSS Syntax's list of
|
|
474
|
+
* declarations: a name, a colon, and everything up to the next `;` that no
|
|
475
|
+
* string or bracket holds; an at-rule in the list is read to its `;` or its
|
|
476
|
+
* block and dropped; anything else is dropped to the next `;`. A value with
|
|
477
|
+
* a `!` left in it once `!important` is taken off is not a value.
|
|
144
478
|
*/
|
|
145
479
|
export function parseDeclarations(text: string): Declaration[] {
|
|
480
|
+
// a `style` attribute comes here without the sheet's pass over comments
|
|
481
|
+
text = withoutComments(text, true);
|
|
146
482
|
const out: Declaration[] = [];
|
|
147
483
|
let i = 0;
|
|
148
484
|
const n = text.length;
|
|
149
485
|
while (i < n) {
|
|
150
|
-
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
// understand (a nested rule, an `@media` inside a rule); skip it whole
|
|
154
|
-
// rather than reading its declarations as if they were the parent's.
|
|
155
|
-
const colon = findChar(text, i, ':', ';{');
|
|
156
|
-
if (colon < 0) {
|
|
157
|
-
const semi = findChar(text, i, ';', '');
|
|
158
|
-
if (semi < 0) break;
|
|
159
|
-
i = semi + 1;
|
|
486
|
+
const c = text[i];
|
|
487
|
+
if (isSpace(c) || c === ';') {
|
|
488
|
+
i += 1;
|
|
160
489
|
continue;
|
|
161
490
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
491
|
+
if (c === '@' && startsIdent(text, i + 1)) {
|
|
492
|
+
i = readAtRule(text, i).end;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const end = scanTo(text, i, ';');
|
|
496
|
+
if (startsIdent(text, i)) {
|
|
497
|
+
const name = readIdent(text, i);
|
|
498
|
+
let colon = name.end;
|
|
499
|
+
while (colon < end && isSpace(text[colon])) colon += 1;
|
|
500
|
+
if (text[colon] === ':') {
|
|
501
|
+
const raw = text.slice(colon + 1, end);
|
|
502
|
+
let value = raw.trim();
|
|
503
|
+
const important = IMPORTANT_RE.test(value);
|
|
504
|
+
if (important) value = value.replace(IMPORTANT_RE, '').trim();
|
|
505
|
+
const custom = name.value.startsWith('--');
|
|
506
|
+
// `--` alone is no custom property's name, and a bracket closed
|
|
507
|
+
// that nothing opened makes a value none
|
|
508
|
+
if (
|
|
509
|
+
(value || custom) &&
|
|
510
|
+
!hasBang(value) &&
|
|
511
|
+
name.value !== '--' &&
|
|
512
|
+
!(custom && unbalanced(value))
|
|
513
|
+
) {
|
|
514
|
+
const declaration: Declaration = {
|
|
515
|
+
// a custom property's name is case-sensitive
|
|
516
|
+
prop: custom ? name.value : name.value.toLowerCase(),
|
|
517
|
+
value: unescapeValue(value),
|
|
518
|
+
important,
|
|
519
|
+
};
|
|
520
|
+
if (custom) declaration.custom = true;
|
|
521
|
+
if (hasVar(value)) {
|
|
522
|
+
// a `var()` that is none is a declaration that is none — read
|
|
523
|
+
// untrimmed, since a newline in a string the end left open
|
|
524
|
+
// makes a bad string, where the end alone closes it
|
|
525
|
+
if (!validVars(raw)) {
|
|
526
|
+
i = end + 1;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
declaration.vars = true;
|
|
530
|
+
}
|
|
531
|
+
out.push(declaration);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
166
535
|
i = end + 1;
|
|
167
|
-
if (!prop || !value) continue;
|
|
168
|
-
const important = IMPORTANT_RE.test(value);
|
|
169
|
-
if (important)
|
|
170
|
-
value = value
|
|
171
|
-
.replace(IMPORTANT_RE, '')
|
|
172
|
-
.replace(/\s*!\s*$/, '')
|
|
173
|
-
.trim();
|
|
174
|
-
if (!value) continue;
|
|
175
|
-
out.push({ prop, value, important });
|
|
176
536
|
}
|
|
177
537
|
return out;
|
|
178
538
|
}
|
|
@@ -180,27 +540,354 @@ export function parseDeclarations(text: string): Declaration[] {
|
|
|
180
540
|
/** Split `a, b > c, d` on top-level commas. */
|
|
181
541
|
export function splitSelectors(prelude: string): string[] {
|
|
182
542
|
const out: string[] = [];
|
|
183
|
-
let depth = 0;
|
|
184
|
-
let quote = '';
|
|
185
543
|
let start = 0;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
if (c === quote && prelude[i - 1] !== '\\') quote = '';
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
if (c === '"' || c === "'") quote = c;
|
|
193
|
-
else if (c === '(' || c === '[') depth += 1;
|
|
194
|
-
else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
|
|
195
|
-
else if (c === ',' && depth === 0) {
|
|
544
|
+
let i = 0;
|
|
545
|
+
while (i < prelude.length) {
|
|
546
|
+
if (prelude[i] === ',') {
|
|
196
547
|
out.push(prelude.slice(start, i).trim());
|
|
197
548
|
start = i + 1;
|
|
549
|
+
i += 1;
|
|
550
|
+
} else {
|
|
551
|
+
i = opens(prelude.charCodeAt(i)) ? componentEnd(prelude, i) : i + 1;
|
|
198
552
|
}
|
|
199
553
|
}
|
|
200
554
|
out.push(prelude.slice(start).trim());
|
|
201
555
|
return out.filter(Boolean);
|
|
202
556
|
}
|
|
203
557
|
|
|
558
|
+
/**
|
|
559
|
+
* A rule's selectors, or null where any of them is not a selector — an
|
|
560
|
+
* empty one in the list, an identifier that starts with a digit, a
|
|
561
|
+
* combinator with nothing after it — and the rule is dropped whole.
|
|
562
|
+
*/
|
|
563
|
+
export function selectorList(prelude: string): string[] | null {
|
|
564
|
+
const raw = rawSelectors(prelude);
|
|
565
|
+
return raw && raw.map((s) => (s.includes('\\') ? forMatcher(s) : s));
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/** A rule's selectors as written, or null where any of them is not one:
|
|
569
|
+
* what `selectorList` hands the matcher before its escapes are read, and
|
|
570
|
+
* what a nested rule's `&` stands for. */
|
|
571
|
+
function rawSelectors(prelude: string): string[] | null {
|
|
572
|
+
if (!prelude) return null;
|
|
573
|
+
const out: string[] = [];
|
|
574
|
+
let start = 0;
|
|
575
|
+
let i = 0;
|
|
576
|
+
const push = (end: number): boolean => {
|
|
577
|
+
const selector = prelude.slice(start, end).trim();
|
|
578
|
+
if (!selector || !isSelector(selector)) return false;
|
|
579
|
+
out.push(selector);
|
|
580
|
+
return true;
|
|
581
|
+
};
|
|
582
|
+
while (i < prelude.length) {
|
|
583
|
+
if (prelude[i] === ',') {
|
|
584
|
+
if (!push(i)) return null;
|
|
585
|
+
start = i + 1;
|
|
586
|
+
i += 1;
|
|
587
|
+
} else {
|
|
588
|
+
i = opens(prelude.charCodeAt(i)) ? componentEnd(prelude, i) : i + 1;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return push(prelude.length) ? out : null;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* A selector's escapes as css-select reads them right. Its hex escapes go
|
|
596
|
+
* in lower case: css-what takes the space that ends `\\6C ` for a
|
|
597
|
+
* descendant combinator, where after `\\6c ` it does not. And in a string
|
|
598
|
+
* an escaped newline goes, since it only continues the line (CSS 2.1
|
|
599
|
+
* 4.3.7): `[title="a\\` and a newline and `b"]` is `[title="ab"]`.
|
|
600
|
+
*/
|
|
601
|
+
function forMatcher(selector: string): string {
|
|
602
|
+
let quote = '';
|
|
603
|
+
let out = '';
|
|
604
|
+
for (let i = 0; i < selector.length; i += 1) {
|
|
605
|
+
const c = selector[i];
|
|
606
|
+
if (c === '\\') {
|
|
607
|
+
const end = escapeEnd(selector, i);
|
|
608
|
+
const escape = selector.slice(i, end);
|
|
609
|
+
if (/^\\[0-9a-f]/i.test(escape)) {
|
|
610
|
+
// the hex digits, and the one space that may end them
|
|
611
|
+
out += escape.toLowerCase().replace(/\s+$/, ' ');
|
|
612
|
+
} else if (!(quote && /^\\[\n\r\f]/.test(escape))) {
|
|
613
|
+
out += escape;
|
|
614
|
+
}
|
|
615
|
+
i = end - 1;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (quote) {
|
|
619
|
+
if (c === quote) quote = '';
|
|
620
|
+
} else if (c === '"' || c === "'") {
|
|
621
|
+
quote = c;
|
|
622
|
+
}
|
|
623
|
+
out += c;
|
|
624
|
+
}
|
|
625
|
+
return out;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Whether a selector parses as one: compounds of type, universal, class,
|
|
630
|
+
* id, attribute and pseudo selectors, each name a CSS identifier, joined by
|
|
631
|
+
* combinators. What a selector *means* — which pseudo-classes exist — is
|
|
632
|
+
* the matcher's to decide; this only refuses what no grammar would read.
|
|
633
|
+
*/
|
|
634
|
+
function isSelector(s: string): boolean {
|
|
635
|
+
const n = s.length;
|
|
636
|
+
let i = 0;
|
|
637
|
+
let expectCompound = true;
|
|
638
|
+
// a pseudo-element ends its selector (CSS 2.1 5.10): `p:first-line p`
|
|
639
|
+
// is none, and neither is `p:first-line[id]` — but for the user action
|
|
640
|
+
// pseudo-classes Selectors 4 lets follow one, as in `a::before:hover`
|
|
641
|
+
let ended = false;
|
|
642
|
+
while (i < n) {
|
|
643
|
+
const c = s[i];
|
|
644
|
+
if (isSpace(c)) {
|
|
645
|
+
i += 1;
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (ended) return false;
|
|
649
|
+
if (c === '>' || c === '+' || c === '~') {
|
|
650
|
+
if (expectCompound) return false;
|
|
651
|
+
expectCompound = true;
|
|
652
|
+
i += 1;
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
// one compound: simple selectors with nothing between them
|
|
656
|
+
const from = i;
|
|
657
|
+
while (i < n && !isSpace(s[i]) && !'>+~'.includes(s[i])) {
|
|
658
|
+
const d = s[i];
|
|
659
|
+
if (
|
|
660
|
+
ended &&
|
|
661
|
+
!(
|
|
662
|
+
d === ':' &&
|
|
663
|
+
s[i + 1] !== ':' &&
|
|
664
|
+
USER_ACTIONS.test(readIdent(s, i + 1).value)
|
|
665
|
+
)
|
|
666
|
+
) {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
if (d === '*' || d === '|') {
|
|
670
|
+
i += 1;
|
|
671
|
+
} else if (d === '.' || d === '#') {
|
|
672
|
+
if (!startsIdent(s, i + 1)) return false;
|
|
673
|
+
i = identEnd(s, i + 1);
|
|
674
|
+
} else if (d === '[') {
|
|
675
|
+
let j = i + 1;
|
|
676
|
+
while (j < n && isSpace(s[j])) j += 1;
|
|
677
|
+
if (s[j] === '*' || s[j] === '|') j += 1;
|
|
678
|
+
if (s[j] === '|') j += 1;
|
|
679
|
+
if (!startsIdent(s, j)) return false;
|
|
680
|
+
const end = componentEnd(s, i);
|
|
681
|
+
if (s[end - 1] !== ']') return false;
|
|
682
|
+
// an operator needs a value after it: `[title~=]` is no selector,
|
|
683
|
+
// and takes its group with it (CSS 2.1 4.1.7)
|
|
684
|
+
let k = identEnd(s, j);
|
|
685
|
+
if (s[k] === '|' && s[k + 1] !== '=' && startsIdent(s, k + 1)) {
|
|
686
|
+
k = identEnd(s, k + 1);
|
|
687
|
+
}
|
|
688
|
+
while (k < end - 1 && isSpace(s[k])) k += 1;
|
|
689
|
+
if (k < end - 1) {
|
|
690
|
+
if (s[k] === '=') k += 1;
|
|
691
|
+
else if ('~|^$*'.includes(s[k]) && s[k + 1] === '=') k += 2;
|
|
692
|
+
else return false;
|
|
693
|
+
while (k < end - 1 && isSpace(s[k])) k += 1;
|
|
694
|
+
if (k >= end - 1) return false;
|
|
695
|
+
}
|
|
696
|
+
i = end;
|
|
697
|
+
} else if (d === ':') {
|
|
698
|
+
const element = s[i + 1] === ':';
|
|
699
|
+
const at = element ? i + 2 : i + 1;
|
|
700
|
+
if (!startsIdent(s, at)) return false;
|
|
701
|
+
const name = readIdent(s, at);
|
|
702
|
+
// ASCII's case only (CSS 2.1 4.1.3): Unicode's makes `:lin\u212A`,
|
|
703
|
+
// with a Kelvin sign, `:link`
|
|
704
|
+
const lower = asciiLower(name.value);
|
|
705
|
+
if (!knownPseudo(lower, element)) return false;
|
|
706
|
+
if (element || LEGACY_PSEUDO_ELEMENTS.test(lower)) ended = true;
|
|
707
|
+
i = name.end;
|
|
708
|
+
if (s[i] === '(') {
|
|
709
|
+
const end = componentEnd(s, i);
|
|
710
|
+
if (s[end - 1] !== ')') return false;
|
|
711
|
+
if (!element && !argumentFits(lower, s.slice(i + 1, end - 1))) {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
i = end;
|
|
715
|
+
} else if (!element && NEEDS_ARGUMENT.has(lower)) return false;
|
|
716
|
+
} else if (startsIdent(s, i) && i === from) {
|
|
717
|
+
i = identEnd(s, i);
|
|
718
|
+
} else {
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
expectCompound = false;
|
|
723
|
+
}
|
|
724
|
+
return !expectCompound;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Whether a pseudo-class takes the argument it is given: one where it
|
|
729
|
+
* takes one — `:lang()` names no language, and makes its group invalid as
|
|
730
|
+
* an unknown name does — and none where it takes none. `:is()` and
|
|
731
|
+
* `:where()` forgive an empty list; a vendor's are its own affair.
|
|
732
|
+
*/
|
|
733
|
+
function argumentFits(name: string, argument: string): boolean {
|
|
734
|
+
if (name.startsWith('-')) return true;
|
|
735
|
+
if (NEEDS_ARGUMENT.has(name)) {
|
|
736
|
+
return name === 'is' || name === 'where' || /\S/.test(argument);
|
|
737
|
+
}
|
|
738
|
+
return MAY_TAKE_ARGUMENT.has(name);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** A name with its ASCII letters in lower case, and no other letter's. */
|
|
742
|
+
export function asciiLower(name: string): string {
|
|
743
|
+
return name.replace(/[A-Z]+/g, (upper) => upper.toLowerCase());
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/** The pseudo-classes that are functions, and are nothing without it. */
|
|
747
|
+
const NEEDS_ARGUMENT = new Set([
|
|
748
|
+
'contains',
|
|
749
|
+
'dir',
|
|
750
|
+
'has',
|
|
751
|
+
'host-context',
|
|
752
|
+
'icontains',
|
|
753
|
+
'is',
|
|
754
|
+
'lang',
|
|
755
|
+
'matches',
|
|
756
|
+
'not',
|
|
757
|
+
'nth-child',
|
|
758
|
+
'nth-col',
|
|
759
|
+
'nth-last-child',
|
|
760
|
+
'nth-last-col',
|
|
761
|
+
'nth-last-of-type',
|
|
762
|
+
'nth-of-type',
|
|
763
|
+
'state',
|
|
764
|
+
'where',
|
|
765
|
+
]);
|
|
766
|
+
|
|
767
|
+
/** The ones that are a pseudo-class with an argument or without. */
|
|
768
|
+
const MAY_TAKE_ARGUMENT = new Set(['current', 'future', 'host', 'past']);
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Whether a pseudo-class or pseudo-element is one: an unknown one makes its
|
|
772
|
+
* selector invalid, and with it the group (CSS 2.1 4.1.7) — which is also
|
|
773
|
+
* what a browser does with another engine's `:-moz-…`. A vendor prefix is
|
|
774
|
+
* let through: which of them a browser knows is its own affair.
|
|
775
|
+
*/
|
|
776
|
+
function knownPseudo(name: string, element: boolean): boolean {
|
|
777
|
+
if (name.startsWith('-')) return true;
|
|
778
|
+
if (element) return PSEUDO_ELEMENTS.has(name);
|
|
779
|
+
return PSEUDO_CLASSES.has(name) || LEGACY_PSEUDO_ELEMENTS.test(name);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const PSEUDO_ELEMENTS = new Set([
|
|
783
|
+
'before',
|
|
784
|
+
'after',
|
|
785
|
+
'first-line',
|
|
786
|
+
'first-letter',
|
|
787
|
+
'selection',
|
|
788
|
+
'placeholder',
|
|
789
|
+
'marker',
|
|
790
|
+
'backdrop',
|
|
791
|
+
'file-selector-button',
|
|
792
|
+
'cue',
|
|
793
|
+
'part',
|
|
794
|
+
'slotted',
|
|
795
|
+
'spelling-error',
|
|
796
|
+
'grammar-error',
|
|
797
|
+
'target-text',
|
|
798
|
+
'highlight',
|
|
799
|
+
'details-content',
|
|
800
|
+
]);
|
|
801
|
+
|
|
802
|
+
const PSEUDO_CLASSES = new Set([
|
|
803
|
+
// css-select's own, which the matcher answers and the docs promise
|
|
804
|
+
'button',
|
|
805
|
+
'checkbox',
|
|
806
|
+
'file',
|
|
807
|
+
'header',
|
|
808
|
+
'icontains',
|
|
809
|
+
'image',
|
|
810
|
+
'input',
|
|
811
|
+
'parent',
|
|
812
|
+
'password',
|
|
813
|
+
'radio',
|
|
814
|
+
'reset',
|
|
815
|
+
'selected',
|
|
816
|
+
'submit',
|
|
817
|
+
'text',
|
|
818
|
+
// CSS's
|
|
819
|
+
'active',
|
|
820
|
+
'any-link',
|
|
821
|
+
'autofill',
|
|
822
|
+
'blank',
|
|
823
|
+
'checked',
|
|
824
|
+
'closed',
|
|
825
|
+
'contains',
|
|
826
|
+
'current',
|
|
827
|
+
'default',
|
|
828
|
+
'defined',
|
|
829
|
+
'dir',
|
|
830
|
+
'disabled',
|
|
831
|
+
'empty',
|
|
832
|
+
'enabled',
|
|
833
|
+
'first',
|
|
834
|
+
'first-child',
|
|
835
|
+
'first-of-type',
|
|
836
|
+
'focus',
|
|
837
|
+
'focus-visible',
|
|
838
|
+
'focus-within',
|
|
839
|
+
'fullscreen',
|
|
840
|
+
'future',
|
|
841
|
+
'has',
|
|
842
|
+
'host',
|
|
843
|
+
'host-context',
|
|
844
|
+
'hover',
|
|
845
|
+
'in-range',
|
|
846
|
+
'indeterminate',
|
|
847
|
+
'invalid',
|
|
848
|
+
'is',
|
|
849
|
+
'lang',
|
|
850
|
+
'last-child',
|
|
851
|
+
'last-of-type',
|
|
852
|
+
'left',
|
|
853
|
+
'link',
|
|
854
|
+
'local-link',
|
|
855
|
+
'matches',
|
|
856
|
+
'modal',
|
|
857
|
+
'not',
|
|
858
|
+
'nth-child',
|
|
859
|
+
'nth-col',
|
|
860
|
+
'nth-last-child',
|
|
861
|
+
'nth-last-col',
|
|
862
|
+
'nth-last-of-type',
|
|
863
|
+
'nth-of-type',
|
|
864
|
+
'only-child',
|
|
865
|
+
'only-of-type',
|
|
866
|
+
'open',
|
|
867
|
+
'optional',
|
|
868
|
+
'out-of-range',
|
|
869
|
+
'past',
|
|
870
|
+
'paused',
|
|
871
|
+
'picture-in-picture',
|
|
872
|
+
'placeholder-shown',
|
|
873
|
+
'playing',
|
|
874
|
+
'popover-open',
|
|
875
|
+
'read-only',
|
|
876
|
+
'read-write',
|
|
877
|
+
'required',
|
|
878
|
+
'right',
|
|
879
|
+
'root',
|
|
880
|
+
'scope',
|
|
881
|
+
'state',
|
|
882
|
+
'target',
|
|
883
|
+
'target-within',
|
|
884
|
+
'user-invalid',
|
|
885
|
+
'user-valid',
|
|
886
|
+
'valid',
|
|
887
|
+
'visited',
|
|
888
|
+
'where',
|
|
889
|
+
]);
|
|
890
|
+
|
|
204
891
|
/**
|
|
205
892
|
* Specificity, packed. Counted from the selector text rather than from a
|
|
206
893
|
* parse: `#a` is an id, `.a`/`[a]`/`:a` is a class, a bare name is a type,
|
|
@@ -210,6 +897,11 @@ export function splitSelectors(prelude: string): string[] {
|
|
|
210
897
|
* specificity is their argument's. The approximation costs an author who
|
|
211
898
|
* writes `:is(#id)` and expects it to beat a class; nothing else.
|
|
212
899
|
*/
|
|
900
|
+
const LEGACY_PSEUDO_ELEMENTS = /^(?:before|after|first-line|first-letter)$/i;
|
|
901
|
+
|
|
902
|
+
/** The pseudo-classes that may follow a pseudo-element. */
|
|
903
|
+
const USER_ACTIONS = /^(?:hover|active|focus|focus-visible|focus-within)$/i;
|
|
904
|
+
|
|
213
905
|
export function specificityOf(selector: string): number {
|
|
214
906
|
let ids = 0;
|
|
215
907
|
let classes = 0;
|
|
@@ -220,25 +912,34 @@ export function specificityOf(selector: string): number {
|
|
|
220
912
|
const c = selector[i];
|
|
221
913
|
if (c === '#') {
|
|
222
914
|
ids += 1;
|
|
223
|
-
i =
|
|
915
|
+
i = identEnd(selector, i + 1);
|
|
224
916
|
} else if (c === '.') {
|
|
225
917
|
classes += 1;
|
|
226
|
-
i =
|
|
918
|
+
i = identEnd(selector, i + 1);
|
|
227
919
|
} else if (c === '[') {
|
|
228
920
|
classes += 1;
|
|
229
|
-
i =
|
|
921
|
+
i = componentEnd(selector, i);
|
|
230
922
|
} else if (c === ':') {
|
|
231
923
|
if (selector[i + 1] === ':') {
|
|
232
924
|
types += 1;
|
|
233
|
-
i =
|
|
925
|
+
i = identEnd(selector, i + 2);
|
|
234
926
|
} else {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (
|
|
927
|
+
const name = readIdent(selector, i + 1);
|
|
928
|
+
// CSS 2 spelled the four pseudo-elements it had with one colon
|
|
929
|
+
if (LEGACY_PSEUDO_ELEMENTS.test(name.value)) {
|
|
930
|
+
types += 1;
|
|
931
|
+
} else {
|
|
932
|
+
classes += 1;
|
|
933
|
+
}
|
|
934
|
+
i = name.end;
|
|
935
|
+
if (selector[i] === '(') i = componentEnd(selector, i);
|
|
238
936
|
}
|
|
239
|
-
} else if (
|
|
937
|
+
} else if (c === '*' || c === '|') {
|
|
938
|
+
// the universal selector, and a namespace's bar, count nothing
|
|
939
|
+
i += 1;
|
|
940
|
+
} else if (startsIdent(selector, i)) {
|
|
240
941
|
types += 1;
|
|
241
|
-
i =
|
|
942
|
+
i = identEnd(selector, i);
|
|
242
943
|
} else {
|
|
243
944
|
i += 1;
|
|
244
945
|
}
|
|
@@ -274,99 +975,347 @@ function isSpace(c: string): boolean {
|
|
|
274
975
|
return c === ' ' || c === '\t' || c === '\n' || c === '\r' || c === '\f';
|
|
275
976
|
}
|
|
276
977
|
|
|
277
|
-
|
|
278
|
-
|
|
978
|
+
/** A character an identifier may start with, before escapes: a letter, an
|
|
979
|
+
* underscore, anything past ASCII. */
|
|
980
|
+
function isNameStart(c: string | undefined): boolean {
|
|
981
|
+
if (c === undefined) return false;
|
|
982
|
+
const code = c.charCodeAt(0);
|
|
983
|
+
return (
|
|
984
|
+
(code >= 0x61 && code <= 0x7a) ||
|
|
985
|
+
(code >= 0x41 && code <= 0x5a) ||
|
|
986
|
+
code === 0x5f ||
|
|
987
|
+
code >= 0x80
|
|
988
|
+
);
|
|
279
989
|
}
|
|
280
990
|
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
|
|
991
|
+
function isNameChar(c: string | undefined): boolean {
|
|
992
|
+
if (c === undefined) return false;
|
|
993
|
+
const code = c.charCodeAt(0);
|
|
994
|
+
return isNameStart(c) || (code >= 0x30 && code <= 0x39) || code === 0x2d;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/** Whether a backslash at `at` escapes what follows — not a newline, and
|
|
998
|
+
* not the end of the text. */
|
|
999
|
+
function isEscape(text: string, at: number): boolean {
|
|
1000
|
+
return text[at] === '\\' && at + 1 < text.length && text[at + 1] !== '\n';
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Whether an identifier starts at `at` (CSS Syntax 4.3.10): a name-start
|
|
1005
|
+
* character or an escape, after at most one hyphen — or two hyphens. Every
|
|
1006
|
+
* character this accepts, `readIdent` steps over: a scan that finds an
|
|
1007
|
+
* identifier and then reads none of it never moves again, which is how a
|
|
1008
|
+
* `*` selector once hung the application.
|
|
1009
|
+
*/
|
|
1010
|
+
export function startsIdent(text: string, at: number): boolean {
|
|
1011
|
+
const c = text[at];
|
|
1012
|
+
if (c === '-') {
|
|
1013
|
+
const d = text[at + 1];
|
|
1014
|
+
return isNameStart(d) || d === '-' || isEscape(text, at + 1);
|
|
1015
|
+
}
|
|
1016
|
+
return isNameStart(c) || isEscape(text, at);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/** Where an identifier from `at` ends, its escapes stepped over. */
|
|
1020
|
+
function identEnd(text: string, at: number): number {
|
|
1021
|
+
let i = at;
|
|
1022
|
+
while (i < text.length) {
|
|
1023
|
+
const code = text.charCodeAt(i);
|
|
1024
|
+
if (
|
|
1025
|
+
(code >= 0x61 && code <= 0x7a) ||
|
|
1026
|
+
(code >= 0x41 && code <= 0x5a) ||
|
|
1027
|
+
(code >= 0x30 && code <= 0x39) ||
|
|
1028
|
+
code === 0x2d ||
|
|
1029
|
+
code === 0x5f ||
|
|
1030
|
+
code >= 0x80
|
|
1031
|
+
) {
|
|
1032
|
+
i += 1;
|
|
1033
|
+
} else if (code === 0x5c && isEscape(text, i)) {
|
|
1034
|
+
i = readEscape(text, i).end;
|
|
1035
|
+
} else {
|
|
1036
|
+
break;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
284
1039
|
return i;
|
|
285
1040
|
}
|
|
286
1041
|
|
|
287
|
-
|
|
1042
|
+
/** An identifier from `at`, its escapes resolved, and where it ends. */
|
|
1043
|
+
export function readIdent(
|
|
288
1044
|
text: string,
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
let
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
depth -= 1;
|
|
305
|
-
if (depth === 0) return i + 1;
|
|
1045
|
+
at: number,
|
|
1046
|
+
): { value: string; end: number } {
|
|
1047
|
+
const end = identEnd(text, at);
|
|
1048
|
+
const raw = text.slice(at, end);
|
|
1049
|
+
if (!raw.includes('\\')) return { value: raw, end };
|
|
1050
|
+
let value = '';
|
|
1051
|
+
let i = at;
|
|
1052
|
+
while (i < end) {
|
|
1053
|
+
if (text[i] === '\\') {
|
|
1054
|
+
const escape = readEscape(text, i);
|
|
1055
|
+
value += escape.char;
|
|
1056
|
+
i = escape.end;
|
|
1057
|
+
} else {
|
|
1058
|
+
value += text[i];
|
|
1059
|
+
i += 1;
|
|
306
1060
|
}
|
|
307
1061
|
}
|
|
1062
|
+
return { value, end };
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/** The character an escape at `at` stands for, and where it ends: up to six
|
|
1066
|
+
* hex digits and one white space after them, or the next character. */
|
|
1067
|
+
function readEscape(text: string, at: number): { char: string; end: number } {
|
|
1068
|
+
let i = at + 1;
|
|
1069
|
+
let hex = '';
|
|
1070
|
+
while (hex.length < 6 && i < text.length && /[0-9a-fA-F]/.test(text[i])) {
|
|
1071
|
+
hex += text[i];
|
|
1072
|
+
i += 1;
|
|
1073
|
+
}
|
|
1074
|
+
if (!hex) return { char: text[i] ?? '�', end: i + 1 };
|
|
1075
|
+
if (text[i] === '\r' && text[i + 1] === '\n') i += 2;
|
|
1076
|
+
else if (isSpace(text[i] ?? '')) i += 1;
|
|
1077
|
+
const code = parseInt(hex, 16);
|
|
1078
|
+
const char =
|
|
1079
|
+
code === 0 || code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)
|
|
1080
|
+
? '�'
|
|
1081
|
+
: String.fromCodePoint(code);
|
|
1082
|
+
return { char, end: i };
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Where the component value starting at `at` ends (CSS Syntax 5.4.7): a
|
|
1087
|
+
* string to its closing quote (or the line that breaks it), an escape, a
|
|
1088
|
+
* `url(…)` to its parenthesis, a bracketed block to its match — whatever
|
|
1089
|
+
* closing brackets of another kind it holds — and otherwise one character.
|
|
1090
|
+
* Every scan below goes through this, so a brace in a string, a bracket
|
|
1091
|
+
* escaped with a backslash and a semicolon in `url(a;b)` are what they are
|
|
1092
|
+
* everywhere at once.
|
|
1093
|
+
*/
|
|
1094
|
+
function componentEnd(text: string, at: number): number {
|
|
1095
|
+
const c = text[at];
|
|
1096
|
+
if (c === '"' || c === "'") return stringEnd(text, at);
|
|
1097
|
+
if (c === '\\') return isEscape(text, at) ? readEscape(text, at).end : at + 1;
|
|
1098
|
+
if (c === '(' || c === '[' || c === '{') return blockEnd(text, at);
|
|
1099
|
+
if (
|
|
1100
|
+
(c === 'u' || c === 'U') &&
|
|
1101
|
+
text[at + 3] === '(' &&
|
|
1102
|
+
text.slice(at, at + 3).toLowerCase() === 'url' &&
|
|
1103
|
+
!isNameChar(text[at - 1])
|
|
1104
|
+
) {
|
|
1105
|
+
return urlEnd(text, at + 3);
|
|
1106
|
+
}
|
|
1107
|
+
return at + 1;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function stringEnd(text: string, at: number): number {
|
|
1111
|
+
const quote = text[at];
|
|
1112
|
+
let i = at + 1;
|
|
1113
|
+
while (i < text.length) {
|
|
1114
|
+
const c = text[i];
|
|
1115
|
+
if (c === quote) return i + 1;
|
|
1116
|
+
// a newline ends a string that has not ended: it is a bad string, and
|
|
1117
|
+
// the newline is not in it
|
|
1118
|
+
if (c === '\n' || c === '\r' || c === '\f') return i;
|
|
1119
|
+
i = c === '\\' ? escapeEnd(text, i) : i + 1;
|
|
1120
|
+
}
|
|
308
1121
|
return text.length;
|
|
309
1122
|
}
|
|
310
1123
|
|
|
311
|
-
/**
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
1124
|
+
/** Where a backslash at `at` stops meaning something inside a string: a
|
|
1125
|
+
* newline after it continues the line, and an escape — six hex digits and
|
|
1126
|
+
* the white space after them included — is read whole. */
|
|
1127
|
+
/** Where an escape at `at` ends: past its hex digits and the one space
|
|
1128
|
+
* that may close them, or past the character it escapes. */
|
|
1129
|
+
export function escapeEnd(text: string, at: number): number {
|
|
1130
|
+
if (text[at + 1] === '\r' && text[at + 2] === '\n') return at + 3;
|
|
1131
|
+
if (isEscape(text, at)) return readEscape(text, at).end;
|
|
1132
|
+
return Math.min(text.length, at + 2);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function blockEnd(text: string, at: number): number {
|
|
1136
|
+
const open = text.charCodeAt(at);
|
|
1137
|
+
const close = open === 0x28 ? 0x29 : open === 0x5b ? 0x5d : 0x7d;
|
|
1138
|
+
let i = at + 1;
|
|
1139
|
+
while (i < text.length) {
|
|
1140
|
+
const code = text.charCodeAt(i);
|
|
1141
|
+
if (code === close) return i + 1;
|
|
1142
|
+
// a closing bracket of another kind inside is only a token
|
|
1143
|
+
i = opens(code) ? componentEnd(text, i) : i + 1;
|
|
1144
|
+
}
|
|
1145
|
+
return text.length;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/** Whether a character starts a component value longer than itself: a
|
|
1149
|
+
* quote, an escape, an opening bracket, or the `u` of a `url(`. Every other
|
|
1150
|
+
* character is its own, which is what keeps a scan a loop over codes. */
|
|
1151
|
+
function opens(code: number): boolean {
|
|
1152
|
+
return (
|
|
1153
|
+
code === 0x22 ||
|
|
1154
|
+
code === 0x27 ||
|
|
1155
|
+
code === 0x5c ||
|
|
1156
|
+
code === 0x28 ||
|
|
1157
|
+
code === 0x5b ||
|
|
1158
|
+
code === 0x7b ||
|
|
1159
|
+
code === 0x75 ||
|
|
1160
|
+
code === 0x55
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/** `url(` at the parenthesis: a quoted argument is a function like any
|
|
1165
|
+
* other; an unquoted one runs to its `)` with its escapes. */
|
|
1166
|
+
/** Where a `url(` whose parenthesis is at `paren` ends: past its `)`. */
|
|
1167
|
+
export function urlEnd(text: string, paren: number): number {
|
|
1168
|
+
let i = paren + 1;
|
|
1169
|
+
while (i < text.length && isSpace(text[i])) i += 1;
|
|
1170
|
+
if (text[i] === '"' || text[i] === "'") return blockEnd(text, paren);
|
|
1171
|
+
while (i < text.length) {
|
|
316
1172
|
const c = text[i];
|
|
317
|
-
if (
|
|
318
|
-
|
|
1173
|
+
if (c === ')') return i + 1;
|
|
1174
|
+
i += c === '\\' ? 2 : 1;
|
|
1175
|
+
}
|
|
1176
|
+
return text.length;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* A `url(…)` as CSS Syntax 3 reads it (4.3.6): quoted, a string, which the
|
|
1181
|
+
* end of the sheet may close as it closes the parenthesis; or unquoted, to
|
|
1182
|
+
* its `)`, with white space only around it and its escapes resolved. `null`
|
|
1183
|
+
* for `none`, or a url of nothing. `undefined` for a bad url — a quote, a
|
|
1184
|
+
* parenthesis or white space inside an unquoted one, or anything after a
|
|
1185
|
+
* quoted one's string — which makes the declaration it is in invalid.
|
|
1186
|
+
*/
|
|
1187
|
+
export function parseUrl(value: string): string | null | undefined {
|
|
1188
|
+
const v = value.trim();
|
|
1189
|
+
if (!v || v.toLowerCase() === 'none') return null;
|
|
1190
|
+
if (!/^url\(/i.test(v)) return unquote(v) || null;
|
|
1191
|
+
let i = 4;
|
|
1192
|
+
while (i < v.length && isSpace(v[i])) i += 1;
|
|
1193
|
+
let out = '';
|
|
1194
|
+
const quote = v[i];
|
|
1195
|
+
if (quote === '"' || quote === "'") {
|
|
1196
|
+
i += 1;
|
|
1197
|
+
while (i < v.length && v[i] !== quote) {
|
|
1198
|
+
if (v[i] === '\\') {
|
|
1199
|
+
if (v[i + 1] === '\n') {
|
|
1200
|
+
i += 2;
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
const escape = readEscape(v, i);
|
|
1204
|
+
out += escape.char;
|
|
1205
|
+
i = escape.end;
|
|
1206
|
+
} else {
|
|
1207
|
+
out += v[i];
|
|
1208
|
+
i += 1;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
i += 1;
|
|
1212
|
+
while (i < v.length && isSpace(v[i])) i += 1;
|
|
1213
|
+
if (i < v.length && v[i] !== ')') return undefined;
|
|
1214
|
+
return rest(v, i) ? undefined : out || null;
|
|
1215
|
+
}
|
|
1216
|
+
while (i < v.length && v[i] !== ')') {
|
|
1217
|
+
const c = v[i];
|
|
1218
|
+
if (isSpace(c)) {
|
|
1219
|
+
while (i < v.length && isSpace(v[i])) i += 1;
|
|
1220
|
+
if (i < v.length && v[i] !== ')') return undefined;
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
if (c === '"' || c === "'" || c === '(') return undefined;
|
|
1224
|
+
if (c === '\\') {
|
|
1225
|
+
if (!isEscape(v, i)) return undefined;
|
|
1226
|
+
const escape = readEscape(v, i);
|
|
1227
|
+
out += escape.char;
|
|
1228
|
+
i = escape.end;
|
|
319
1229
|
continue;
|
|
320
1230
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const close = text.indexOf('*/', i + 2);
|
|
324
|
-
i = close < 0 ? text.length : close + 1;
|
|
325
|
-
} else if (c === '(' || c === '[') bracket += 1;
|
|
326
|
-
else if (c === ')' || c === ']') bracket = Math.max(0, bracket - 1);
|
|
327
|
-
else if (c === '{' && bracket === 0) return i;
|
|
328
|
-
else if (c === ';' && bracket === 0) return -1; // a stray declaration
|
|
1231
|
+
out += c;
|
|
1232
|
+
i += 1;
|
|
329
1233
|
}
|
|
330
|
-
return
|
|
1234
|
+
return rest(v, i) ? undefined : out || null;
|
|
331
1235
|
}
|
|
332
1236
|
|
|
333
|
-
/**
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
):
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
1237
|
+
/** Whether anything follows a url's `)` at `at`: `url(a) repeat` is no
|
|
1238
|
+
* image, and the declaration it is in is invalid. */
|
|
1239
|
+
function rest(v: string, at: number): boolean {
|
|
1240
|
+
return at + 1 < v.length && v.slice(at + 1).trim() !== '';
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function unquote(value: string): string {
|
|
1244
|
+
const v = value.trim();
|
|
1245
|
+
if (
|
|
1246
|
+
v.length >= 2 &&
|
|
1247
|
+
(v[0] === '"' || v[0] === "'") &&
|
|
1248
|
+
v[v.length - 1] === v[0]
|
|
1249
|
+
) {
|
|
1250
|
+
return v.slice(1, -1);
|
|
1251
|
+
}
|
|
1252
|
+
return v;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/** The first `target` from `at` that no string, escape or bracket holds, or
|
|
1256
|
+
* the length of the text. */
|
|
1257
|
+
function scanTo(text: string, at: number, target: string): number {
|
|
1258
|
+
const want = target.charCodeAt(0);
|
|
1259
|
+
let i = at;
|
|
1260
|
+
while (i < text.length) {
|
|
1261
|
+
const code = text.charCodeAt(i);
|
|
1262
|
+
if (code === want) return i;
|
|
1263
|
+
i = opens(code) ? componentEnd(text, i) : i + 1;
|
|
1264
|
+
}
|
|
1265
|
+
return text.length;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/** Whether a `!` stands in a value outside its strings and brackets. */
|
|
1269
|
+
function hasBang(value: string): boolean {
|
|
1270
|
+
if (!value.includes('!')) return false;
|
|
1271
|
+
for (let at = scanTo(value, 0, '!'); at < value.length;) {
|
|
1272
|
+
// `<!--` is a token of its own, which a custom property may hold
|
|
1273
|
+
if (value[at - 1] !== '<' || !value.startsWith('--', at + 1)) return true;
|
|
1274
|
+
at = scanTo(value, at + 1, '!');
|
|
1275
|
+
}
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* A value with its escapes resolved outside its strings and `url()`s:
|
|
1281
|
+
* `\67reen` is `green`. A string keeps its escapes for whoever reads its
|
|
1282
|
+
* text, `content` among them.
|
|
1283
|
+
*/
|
|
1284
|
+
function unescapeValue(value: string): string {
|
|
1285
|
+
if (!value.includes('\\')) return value;
|
|
1286
|
+
let out = '';
|
|
1287
|
+
let i = 0;
|
|
1288
|
+
while (i < value.length) {
|
|
1289
|
+
if (isEscape(value, i)) {
|
|
1290
|
+
// Resolved where it stands for a name character; any other it is part
|
|
1291
|
+
// of an identifier that a value parser should see as one, not as the
|
|
1292
|
+
// tab or the semicolon it names — `red \9` is not `red`.
|
|
1293
|
+
const escape = readEscape(value, i);
|
|
1294
|
+
out += isNameChar(escape.char) ? escape.char : value.slice(i, escape.end);
|
|
1295
|
+
i = escape.end;
|
|
346
1296
|
continue;
|
|
347
1297
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
i = close < 0 ? text.length : close + 1;
|
|
352
|
-
} else if (c === '(' || c === '[' || c === '{') depth += 1;
|
|
353
|
-
else if (c === ')' || c === ']' || c === '}') {
|
|
354
|
-
if (depth === 0 && stopAt.includes(c)) return -1;
|
|
355
|
-
depth -= 1;
|
|
356
|
-
} else if (depth === 0) {
|
|
357
|
-
if (c === target) return i;
|
|
358
|
-
if (stopAt.includes(c)) return -1;
|
|
359
|
-
}
|
|
1298
|
+
const end = componentEnd(value, i);
|
|
1299
|
+
out += value.slice(i, end);
|
|
1300
|
+
i = end;
|
|
360
1301
|
}
|
|
361
|
-
return
|
|
1302
|
+
return out;
|
|
362
1303
|
}
|
|
363
1304
|
|
|
1305
|
+
/**
|
|
1306
|
+
* A `{…}` block: its body, and where the text goes on after it. The end of
|
|
1307
|
+
* a style sheet closes a block still open (CSS 2.1 4.2), so a block the end
|
|
1308
|
+
* cut off is everything after its brace. Cutting its last character as if
|
|
1309
|
+
* it were the `}` made `color: blue` `color: blu` — and left an unclosed
|
|
1310
|
+
* `rgb(` to reach paint whole or not, by where the sheet's whitespace fell.
|
|
1311
|
+
*/
|
|
364
1312
|
function readBlock(
|
|
365
1313
|
text: string,
|
|
366
1314
|
braceAt: number,
|
|
367
1315
|
): { body: string; end: number } {
|
|
368
|
-
const end =
|
|
369
|
-
|
|
1316
|
+
const end = blockEnd(text, braceAt);
|
|
1317
|
+
const closed = text[end - 1] === '}' && end - 1 > braceAt;
|
|
1318
|
+
return { body: text.slice(braceAt + 1, closed ? end - 1 : end), end };
|
|
370
1319
|
}
|
|
371
1320
|
|
|
372
1321
|
interface AtRule {
|
|
@@ -376,29 +1325,205 @@ interface AtRule {
|
|
|
376
1325
|
end: number;
|
|
377
1326
|
}
|
|
378
1327
|
|
|
1328
|
+
/** An at-rule: its name, and everything up to its `;` or its block. */
|
|
379
1329
|
function readAtRule(text: string, at: number): AtRule {
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
let depth = 0;
|
|
385
|
-
for (; i < text.length; i += 1) {
|
|
386
|
-
const c = text[i];
|
|
387
|
-
if (quote) {
|
|
388
|
-
if (c === quote && text[i - 1] !== '\\') quote = '';
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
if (c === '"' || c === "'") quote = c;
|
|
392
|
-
else if (c === '(' || c === '[') depth += 1;
|
|
393
|
-
else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
|
|
394
|
-
else if (depth === 0 && (c === ';' || c === '{')) break;
|
|
1330
|
+
const name = readIdent(text, at + 1);
|
|
1331
|
+
let i = name.end;
|
|
1332
|
+
while (i < text.length && text[i] !== ';' && text[i] !== '{') {
|
|
1333
|
+
i = opens(text.charCodeAt(i)) ? componentEnd(text, i) : i + 1;
|
|
395
1334
|
}
|
|
396
|
-
const prelude = text.slice(
|
|
1335
|
+
const prelude = text.slice(name.end, i).trim();
|
|
397
1336
|
if (text[i] === '{') {
|
|
398
1337
|
const block = readBlock(text, i);
|
|
399
|
-
return { name, prelude, block: block.body, end: block.end };
|
|
1338
|
+
return { name: name.value, prelude, block: block.body, end: block.end };
|
|
1339
|
+
}
|
|
1340
|
+
return {
|
|
1341
|
+
name: name.value,
|
|
1342
|
+
prelude,
|
|
1343
|
+
block: null,
|
|
1344
|
+
end: Math.min(text.length, i + 1),
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/** Where a width past a maximum starts, for the widths a restyle is due
|
|
1349
|
+
* at: a sixty-fourth of a pixel past it, where the widths a display scale
|
|
1350
|
+
* divides a device width into (`640.5` at 2x) fall on the right side. */
|
|
1351
|
+
const MAX_EDGE = 1 / 64;
|
|
1352
|
+
|
|
1353
|
+
/**
|
|
1354
|
+
* A width range in Media Queries 4's syntax — `(width >= 48rem)`, `(60rem >
|
|
1355
|
+
* width)`, `(40rem <= width < 60rem)` — as the widths it holds between, or
|
|
1356
|
+
* null for a term that is not one. A strict bound is a sixty-fourth of a
|
|
1357
|
+
* pixel inside the value, where a viewport's width never lands.
|
|
1358
|
+
*/
|
|
1359
|
+
function widthRange(term: string): { min?: number; max?: number } | null {
|
|
1360
|
+
const inner = /^\(\s*(.*?)\s*\)$/.exec(term)?.[1];
|
|
1361
|
+
if (!inner || !/[<>=]/.test(inner)) return null;
|
|
1362
|
+
const parts = inner.split(/\s*(<=|>=|<|>|=)\s*/);
|
|
1363
|
+
const isWidth = (part: string) => part.toLowerCase() === 'width';
|
|
1364
|
+
const out: { min?: number; max?: number } = {};
|
|
1365
|
+
// `width OP value`, with the operator read from the width's side
|
|
1366
|
+
const bound = (op: string, value: string): boolean => {
|
|
1367
|
+
const len = parseLength(value, ZERO_UNITS);
|
|
1368
|
+
if (typeof len !== 'number') return false;
|
|
1369
|
+
const edge = 1 / 64;
|
|
1370
|
+
if (op === '>=' || op === '=') out.min = Math.max(out.min ?? 0, len);
|
|
1371
|
+
if (op === '>') out.min = Math.max(out.min ?? 0, len + edge);
|
|
1372
|
+
if (op === '<=' || op === '=') out.max = Math.min(out.max ?? Infinity, len);
|
|
1373
|
+
if (op === '<') out.max = Math.min(out.max ?? Infinity, len - edge);
|
|
1374
|
+
return true;
|
|
1375
|
+
};
|
|
1376
|
+
const flip: Record<string, string> = {
|
|
1377
|
+
'<': '>',
|
|
1378
|
+
'<=': '>=',
|
|
1379
|
+
'>': '<',
|
|
1380
|
+
'>=': '<=',
|
|
1381
|
+
'=': '=',
|
|
1382
|
+
};
|
|
1383
|
+
if (parts.length === 3 && isWidth(parts[0])) {
|
|
1384
|
+
return bound(parts[1], parts[2]) ? out : null;
|
|
1385
|
+
}
|
|
1386
|
+
if (parts.length === 3 && isWidth(parts[2])) {
|
|
1387
|
+
return bound(flip[parts[1]], parts[0]) ? out : null;
|
|
1388
|
+
}
|
|
1389
|
+
if (parts.length === 5 && isWidth(parts[2])) {
|
|
1390
|
+
return bound(flip[parts[1]], parts[0]) && bound(parts[3], parts[4])
|
|
1391
|
+
? out
|
|
1392
|
+
: null;
|
|
1393
|
+
}
|
|
1394
|
+
return null;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/** How deep rules may nest before the ones further in are dropped: far
|
|
1398
|
+
* past anything written by hand or by a framework, and short of what a
|
|
1399
|
+
* generated sheet could make of the stack. */
|
|
1400
|
+
const MAX_NESTING = 16;
|
|
1401
|
+
|
|
1402
|
+
type Nested =
|
|
1403
|
+
| { kind: 'rule'; prelude: string; body: string }
|
|
1404
|
+
| { kind: 'at'; name: string; prelude: string; block: string | null };
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* A style rule's block, parted into its declarations and the rules nested
|
|
1408
|
+
* in it. A declaration runs to its `;` and a nested rule to its block,
|
|
1409
|
+
* whichever comes first — `color: red;` and `a:hover { … }` — and a custom
|
|
1410
|
+
* property is a declaration whatever its value holds.
|
|
1411
|
+
*/
|
|
1412
|
+
function splitNested(body: string): {
|
|
1413
|
+
declarations: string;
|
|
1414
|
+
nested: Nested[];
|
|
1415
|
+
} {
|
|
1416
|
+
let declarations = '';
|
|
1417
|
+
const nested: Nested[] = [];
|
|
1418
|
+
let i = 0;
|
|
1419
|
+
while (i < body.length) {
|
|
1420
|
+
i = skipTrivia(body, i);
|
|
1421
|
+
if (i >= body.length) break;
|
|
1422
|
+
if (body[i] === ';') {
|
|
1423
|
+
i += 1;
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
if (body[i] === '@' && startsIdent(body, i + 1)) {
|
|
1427
|
+
const at = readAtRule(body, i);
|
|
1428
|
+
nested.push({
|
|
1429
|
+
kind: 'at',
|
|
1430
|
+
name: at.name,
|
|
1431
|
+
prelude: at.prelude,
|
|
1432
|
+
block: at.block,
|
|
1433
|
+
});
|
|
1434
|
+
i = at.end;
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
const semi = scanTo(body, i, ';');
|
|
1438
|
+
const brace = scanTo(body, i, '{');
|
|
1439
|
+
if (brace < semi && !CUSTOM_DECLARATION.test(body.slice(i, brace))) {
|
|
1440
|
+
const block = readBlock(body, brace);
|
|
1441
|
+
nested.push({
|
|
1442
|
+
kind: 'rule',
|
|
1443
|
+
prelude: body.slice(i, brace).trim(),
|
|
1444
|
+
body: block.body,
|
|
1445
|
+
});
|
|
1446
|
+
i = block.end;
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
declarations += `${body.slice(i, semi)};`;
|
|
1450
|
+
i = semi + 1;
|
|
400
1451
|
}
|
|
401
|
-
return {
|
|
1452
|
+
return { declarations, nested };
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
const CUSTOM_DECLARATION = /^\s*--[\w-]*\s*:/;
|
|
1456
|
+
|
|
1457
|
+
/**
|
|
1458
|
+
* A nested rule's selectors against its parent's, as written (CSS Nesting
|
|
1459
|
+
* 1, 3): a selector that starts with `&` is the parent's with the rest
|
|
1460
|
+
* after it, `&:hover` the parent hovered; any other `&` is `:is()` of the
|
|
1461
|
+
* parent; one with none is relative to the parent, a descendant unless it
|
|
1462
|
+
* starts with a combinator. Null where one of them comes to no selector.
|
|
1463
|
+
*/
|
|
1464
|
+
function nestedSelectors(prelude: string, parents: string[]): string[] | null {
|
|
1465
|
+
const out: string[] = [];
|
|
1466
|
+
for (const part of splitTopLevel(prelude, ',')) {
|
|
1467
|
+
const written = part.trim();
|
|
1468
|
+
if (!written) return null;
|
|
1469
|
+
const own = hasNesting(written) ? written : `& ${written}`;
|
|
1470
|
+
for (const parent of parents) {
|
|
1471
|
+
const resolved =
|
|
1472
|
+
own[0] === '&' &&
|
|
1473
|
+
!hasNesting(own.slice(1)) &&
|
|
1474
|
+
!/^[\w-]/.test(own.slice(1))
|
|
1475
|
+
? parent + own.slice(1)
|
|
1476
|
+
: replaceNesting(own, `:is(${parent})`);
|
|
1477
|
+
if (!isSelector(resolved)) return null;
|
|
1478
|
+
out.push(resolved);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return out;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/** Whether a selector has a `&` outside its strings and escapes. */
|
|
1485
|
+
function hasNesting(selector: string): boolean {
|
|
1486
|
+
return replaceNesting(selector, '') !== selector;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/** A selector with every `&` outside its strings and escapes replaced. */
|
|
1490
|
+
function replaceNesting(selector: string, by: string): string {
|
|
1491
|
+
if (!selector.includes('&')) return selector;
|
|
1492
|
+
let out = '';
|
|
1493
|
+
let quote = '';
|
|
1494
|
+
for (let i = 0; i < selector.length; i += 1) {
|
|
1495
|
+
const c = selector[i];
|
|
1496
|
+
if (c === '\\') {
|
|
1497
|
+
out += selector.slice(i, i + 2);
|
|
1498
|
+
i += 1;
|
|
1499
|
+
} else if (quote) {
|
|
1500
|
+
if (c === quote) quote = '';
|
|
1501
|
+
out += c;
|
|
1502
|
+
} else if (c === '"' || c === "'") {
|
|
1503
|
+
quote = c;
|
|
1504
|
+
out += c;
|
|
1505
|
+
} else out += c === '&' ? by : c;
|
|
1506
|
+
}
|
|
1507
|
+
return out;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/** A list's items at the top level: no string, escape or bracket parts
|
|
1511
|
+
* them. */
|
|
1512
|
+
function splitTopLevel(text: string, at: string): string[] {
|
|
1513
|
+
const out: string[] = [];
|
|
1514
|
+
let start = 0;
|
|
1515
|
+
let i = 0;
|
|
1516
|
+
while (i < text.length) {
|
|
1517
|
+
if (text[i] === at) {
|
|
1518
|
+
out.push(text.slice(start, i));
|
|
1519
|
+
start = i + 1;
|
|
1520
|
+
i += 1;
|
|
1521
|
+
} else {
|
|
1522
|
+
i = opens(text.charCodeAt(i)) ? componentEnd(text, i) : i + 1;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
out.push(text.slice(start));
|
|
1526
|
+
return out;
|
|
402
1527
|
}
|
|
403
1528
|
|
|
404
1529
|
function importUrl(prelude: string): string | null {
|
|
@@ -406,6 +1531,218 @@ function importUrl(prelude: string): string | null {
|
|
|
406
1531
|
return m ? m[1] : null;
|
|
407
1532
|
}
|
|
408
1533
|
|
|
1534
|
+
/** Whether an `@import`'s media list, the rest of its prelude after the
|
|
1535
|
+
* URL, lets it apply here: a list that only names other media, `print` or
|
|
1536
|
+
* `braille`, leaves the sheet out (CSS 2.1 6.3). A list that depends on the
|
|
1537
|
+
* width is taken to apply; an import is fetched once, not per width. */
|
|
1538
|
+
function importApplies(prelude: string): boolean {
|
|
1539
|
+
const m =
|
|
1540
|
+
/^\s*(?:url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)|"[^"]*"|'[^']*')(.*)$/s.exec(
|
|
1541
|
+
prelude,
|
|
1542
|
+
);
|
|
1543
|
+
const list = m?.[1].trim();
|
|
1544
|
+
if (!list) return true;
|
|
1545
|
+
return parseMediaQuery(list).some((c) => c.staticPass !== false);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* A declaration value with every `url()` in it made absolute against `base`
|
|
1550
|
+
* — inside a function too, `image-set()` or a `var()` fallback, and never
|
|
1551
|
+
* inside a string. `url(#id)`, a reference into the document itself, is left
|
|
1552
|
+
* as it is, as are a `data:` URL, absolute already, and a url that is none.
|
|
1553
|
+
*/
|
|
1554
|
+
export function absoluteUrls(value: string, base: string): string {
|
|
1555
|
+
if (!/url\(/i.test(value)) return value;
|
|
1556
|
+
let out = '';
|
|
1557
|
+
let from = 0;
|
|
1558
|
+
let i = 0;
|
|
1559
|
+
while (i < value.length) {
|
|
1560
|
+
const c = value[i];
|
|
1561
|
+
if (c === '"' || c === "'") {
|
|
1562
|
+
i = stringEnd(value, i);
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
if (c === '\\') {
|
|
1566
|
+
i = escapeEnd(value, i);
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
if (
|
|
1570
|
+
(c === 'u' || c === 'U') &&
|
|
1571
|
+
value[i + 3] === '(' &&
|
|
1572
|
+
value.slice(i, i + 3).toLowerCase() === 'url' &&
|
|
1573
|
+
!isNameChar(value[i - 1])
|
|
1574
|
+
) {
|
|
1575
|
+
const end = urlEnd(value, i + 3);
|
|
1576
|
+
const url = parseUrl(value.slice(i, end));
|
|
1577
|
+
if (typeof url === 'string' && url[0] !== '#' && !/^data:/i.test(url)) {
|
|
1578
|
+
const resolved = resolveUrl(url, base);
|
|
1579
|
+
if (resolved !== url) {
|
|
1580
|
+
out += value.slice(from, i) + cssUrl(resolved);
|
|
1581
|
+
from = end;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
i = end;
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
i += 1;
|
|
1588
|
+
}
|
|
1589
|
+
return from === 0 ? value : out + value.slice(from);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
/** A URL as a `url()` that reads back as it: quoted, with the characters a
|
|
1593
|
+
* string cannot hold as they are escaped. */
|
|
1594
|
+
function cssUrl(url: string): string {
|
|
1595
|
+
const escaped = url.replace(/[\\"\n]/g, (c) =>
|
|
1596
|
+
c === '\n' ? '\\a ' : `\\${c}`,
|
|
1597
|
+
);
|
|
1598
|
+
return `url("${escaped}")`;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
/** The generic families: a `@font-face` may not take one's name, and one
|
|
1602
|
+
* that did would take over every element that asks for it. */
|
|
1603
|
+
const GENERIC_FAMILIES = new Set([
|
|
1604
|
+
'serif',
|
|
1605
|
+
'sans-serif',
|
|
1606
|
+
'monospace',
|
|
1607
|
+
'cursive',
|
|
1608
|
+
'fantasy',
|
|
1609
|
+
'system-ui',
|
|
1610
|
+
'ui-serif',
|
|
1611
|
+
'ui-sans-serif',
|
|
1612
|
+
'ui-monospace',
|
|
1613
|
+
'ui-rounded',
|
|
1614
|
+
'math',
|
|
1615
|
+
'emoji',
|
|
1616
|
+
'fangsong',
|
|
1617
|
+
'-apple-system',
|
|
1618
|
+
'blinkmacsystemfont',
|
|
1619
|
+
'inherit',
|
|
1620
|
+
'initial',
|
|
1621
|
+
'unset',
|
|
1622
|
+
'revert',
|
|
1623
|
+
'default',
|
|
1624
|
+
]);
|
|
1625
|
+
|
|
1626
|
+
/**
|
|
1627
|
+
* A `@font-face` block, or null when it names no family, or no source this
|
|
1628
|
+
* can ask for: `local()` names a face the system has, which this does not
|
|
1629
|
+
* look up, and a family that is only `local()`s is left to the system as it
|
|
1630
|
+
* would be anyway.
|
|
1631
|
+
*/
|
|
1632
|
+
function parseFontFace(
|
|
1633
|
+
block: string,
|
|
1634
|
+
base: string | null,
|
|
1635
|
+
media: MediaCondition[][] | null,
|
|
1636
|
+
): FontFaceRule | null {
|
|
1637
|
+
let family: string | null = null;
|
|
1638
|
+
let src: string | null = null;
|
|
1639
|
+
let weight: [number, number] = [400, 400];
|
|
1640
|
+
let style: 'normal' | 'italic' = 'normal';
|
|
1641
|
+
let unicodeRange: [number, number][] | null = null;
|
|
1642
|
+
// a descriptor that is not one is dropped, as a declaration is, and the
|
|
1643
|
+
// one before it stands
|
|
1644
|
+
for (const d of parseDeclarations(block)) {
|
|
1645
|
+
if (d.prop === 'font-family') {
|
|
1646
|
+
family = faceFamily(d.value) ?? family;
|
|
1647
|
+
} else if (d.prop === 'src') {
|
|
1648
|
+
src = d.value;
|
|
1649
|
+
} else if (d.prop === 'font-weight') {
|
|
1650
|
+
weight = faceWeight(d.value) ?? weight;
|
|
1651
|
+
} else if (d.prop === 'font-style') {
|
|
1652
|
+
const v = d.value.trim().toLowerCase();
|
|
1653
|
+
if (v === 'normal') style = 'normal';
|
|
1654
|
+
else if (v === 'italic' || /^oblique\b/.test(v)) style = 'italic';
|
|
1655
|
+
} else if (d.prop === 'unicode-range') {
|
|
1656
|
+
unicodeRange = unicodeRanges(d.value) ?? unicodeRange;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
if (!family || src === null) return null;
|
|
1660
|
+
const sources: FontFaceSource[] = [];
|
|
1661
|
+
for (const part of splitTopLevel(src, ',')) {
|
|
1662
|
+
const item = part.trim();
|
|
1663
|
+
if (!/^url\(/i.test(item)) continue;
|
|
1664
|
+
const end = urlEnd(item, 3);
|
|
1665
|
+
const url = parseUrl(item.slice(0, end));
|
|
1666
|
+
if (typeof url !== 'string') continue;
|
|
1667
|
+
// `format("woff2")`, the older `format(woff2)`, or a list of them, of
|
|
1668
|
+
// which the first says enough
|
|
1669
|
+
const hint = /format\(\s*(?:"([^"]*)"|'([^']*)'|([\w-]+))/i.exec(
|
|
1670
|
+
item.slice(end),
|
|
1671
|
+
);
|
|
1672
|
+
const format = hint ? (hint[1] ?? hint[2] ?? hint[3]).toLowerCase() : null;
|
|
1673
|
+
sources.push({ url: resolveUrl(url, base), format });
|
|
1674
|
+
}
|
|
1675
|
+
if (!sources.length) return null;
|
|
1676
|
+
return { family, sources, weight, style, unicodeRange, media };
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
/** A `@font-face`'s one family name: a string, or identifiers, which one
|
|
1680
|
+
* space joins. Not a list, and not a generic family's name. */
|
|
1681
|
+
function faceFamily(value: string): string | null {
|
|
1682
|
+
const v = value.trim();
|
|
1683
|
+
let name: string;
|
|
1684
|
+
if (v[0] === '"' || v[0] === "'") {
|
|
1685
|
+
if (stringEnd(v, 0) !== v.length) return null;
|
|
1686
|
+
name = unquote(v).trim();
|
|
1687
|
+
} else {
|
|
1688
|
+
const words = v.split(/\s+/);
|
|
1689
|
+
for (const word of words) {
|
|
1690
|
+
if (!startsIdent(word, 0) || readIdent(word, 0).end !== word.length) {
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
name = words.join(' ');
|
|
1695
|
+
}
|
|
1696
|
+
if (!name || name.includes(',')) return null;
|
|
1697
|
+
return GENERIC_FAMILIES.has(name.toLowerCase()) ? null : name;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/** A `@font-face`'s `font-weight`: one weight, or the range a variable face
|
|
1701
|
+
* covers. `auto` — the file's own range — is taken to be every weight. */
|
|
1702
|
+
function faceWeight(value: string): [number, number] | null {
|
|
1703
|
+
const parts = value.trim().toLowerCase().split(/\s+/);
|
|
1704
|
+
const one = (p: string): number | null => {
|
|
1705
|
+
if (p === 'normal') return 400;
|
|
1706
|
+
if (p === 'bold') return 700;
|
|
1707
|
+
if (!/^\d+(?:\.\d+)?$/.test(p)) return null;
|
|
1708
|
+
const n = Number(p);
|
|
1709
|
+
return n >= 1 && n <= 1000 ? n : null;
|
|
1710
|
+
};
|
|
1711
|
+
if (parts.length === 1) {
|
|
1712
|
+
if (parts[0] === 'auto') return [1, 1000];
|
|
1713
|
+
const w = one(parts[0]);
|
|
1714
|
+
return w === null ? null : [w, w];
|
|
1715
|
+
}
|
|
1716
|
+
if (parts.length !== 2) return null;
|
|
1717
|
+
const a = one(parts[0]);
|
|
1718
|
+
const b = one(parts[1]);
|
|
1719
|
+
if (a === null || b === null) return null;
|
|
1720
|
+
return a <= b ? [a, b] : [b, a];
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
/** `unicode-range`: `U+26`, `U+0-7F`, `U+4??` — inclusive ranges, or null
|
|
1724
|
+
* when any part is not one, and the descriptor is dropped. */
|
|
1725
|
+
function unicodeRanges(value: string): [number, number][] | null {
|
|
1726
|
+
const out: [number, number][] = [];
|
|
1727
|
+
for (const part of value.split(',')) {
|
|
1728
|
+
const m = /^\s*u\+([0-9a-f?]{1,6})(?:-([0-9a-f]{1,6}))?\s*$/i.exec(part);
|
|
1729
|
+
if (!m) return null;
|
|
1730
|
+
let lo: number;
|
|
1731
|
+
let hi: number;
|
|
1732
|
+
if (m[1].includes('?')) {
|
|
1733
|
+
if (m[2] || !/^[0-9a-f]*\?+$/i.test(m[1])) return null;
|
|
1734
|
+
lo = parseInt(m[1].replace(/\?/g, '0'), 16);
|
|
1735
|
+
hi = parseInt(m[1].replace(/\?/g, 'f'), 16);
|
|
1736
|
+
} else {
|
|
1737
|
+
lo = parseInt(m[1], 16);
|
|
1738
|
+
hi = m[2] ? parseInt(m[2], 16) : lo;
|
|
1739
|
+
}
|
|
1740
|
+
if (hi < lo || lo > 0x10ffff) return null;
|
|
1741
|
+
out.push([lo, Math.min(hi, 0x10ffff)]);
|
|
1742
|
+
}
|
|
1743
|
+
return out.length ? out : null;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
409
1746
|
/**
|
|
410
1747
|
* A `@media` prelude, reduced to the width and colour-scheme tests this can
|
|
411
1748
|
* honour. Each comma group is one condition and they are OR-ed; within a
|
|
@@ -425,6 +1762,19 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
425
1762
|
.replace(/^not\s+/i, '')
|
|
426
1763
|
.replace(/^only\s+/i, '');
|
|
427
1764
|
if (!term) continue;
|
|
1765
|
+
// Media Queries 4's ranges, which Tailwind 4 writes its breakpoints
|
|
1766
|
+
// in: `(width >= 48rem)`, `(40rem <= width < 60rem)`
|
|
1767
|
+
const range = widthRange(term);
|
|
1768
|
+
if (range) {
|
|
1769
|
+
if (range.min !== undefined) {
|
|
1770
|
+
condition.min = Math.max(condition.min ?? 0, range.min);
|
|
1771
|
+
}
|
|
1772
|
+
if (range.max !== undefined) {
|
|
1773
|
+
condition.max = Math.min(condition.max ?? Infinity, range.max);
|
|
1774
|
+
}
|
|
1775
|
+
sawWidth = true;
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
428
1778
|
const feature = /^\(\s*([a-z-]+)\s*:\s*([^)]+?)\s*\)$/i.exec(term);
|
|
429
1779
|
if (feature) {
|
|
430
1780
|
const key = feature[1].toLowerCase();
|
|
@@ -460,7 +1810,11 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
460
1810
|
}
|
|
461
1811
|
const type = term.toLowerCase();
|
|
462
1812
|
if (type === 'screen' || type === 'all') continue;
|
|
463
|
-
|
|
1813
|
+
// any other medium is not this one: `print`, `speech`, and the ones
|
|
1814
|
+
// CSS 2.1 named that Media Queries retired, `braille`, `embossed`,
|
|
1815
|
+
// `handheld`, `projection`, `tty`, `tv`. A term that is no name is
|
|
1816
|
+
// left as it was.
|
|
1817
|
+
if (/^[a-z-]+$/.test(type)) pass = false;
|
|
464
1818
|
}
|
|
465
1819
|
if (negated) {
|
|
466
1820
|
// `not` over a scheme is the other scheme. `not` over a width range
|