@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/cascade.ts
CHANGED
|
@@ -21,12 +21,36 @@ import { Element as DomElement, isTag } from 'domhandler';
|
|
|
21
21
|
import type { Element } from 'domhandler';
|
|
22
22
|
|
|
23
23
|
import { attr, tagOf } from '../dom.js';
|
|
24
|
-
import {
|
|
24
|
+
import { svgSizeHint } from '../svg.js';
|
|
25
|
+
import {
|
|
26
|
+
asciiLower,
|
|
27
|
+
escapeEnd,
|
|
28
|
+
mediaMatches,
|
|
29
|
+
readIdent,
|
|
30
|
+
startsIdent,
|
|
31
|
+
} from './parse.js';
|
|
25
32
|
import type { Declaration, StyleRule, Stylesheet } from './parse.js';
|
|
26
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
applyDeclaration,
|
|
35
|
+
blockify,
|
|
36
|
+
settleContentVisibility,
|
|
37
|
+
settleOverflow,
|
|
38
|
+
settleClamp,
|
|
39
|
+
copyStyle,
|
|
40
|
+
decorate,
|
|
41
|
+
inherit,
|
|
42
|
+
initialOne,
|
|
43
|
+
initialStyle,
|
|
44
|
+
isInherited,
|
|
45
|
+
} from './style.js';
|
|
27
46
|
import type { ComputedStyle, RootLook } from './style.js';
|
|
47
|
+
import type { FontFamilies } from '../fonts.js';
|
|
28
48
|
import { parseDeclarations } from './parse.js';
|
|
49
|
+
import { CounterStyles, counterStyleRule } from './counter-styles.js';
|
|
50
|
+
import type { CounterStyleRule } from './counter-styles.js';
|
|
29
51
|
import type { UnitContext } from './values.js';
|
|
52
|
+
import { customProperties, substituteIn } from './vars.js';
|
|
53
|
+
import type { CustomProps } from './vars.js';
|
|
30
54
|
|
|
31
55
|
/** Where a declaration came from. Higher wins before specificity is asked. */
|
|
32
56
|
const enum Origin {
|
|
@@ -40,6 +64,8 @@ const enum Origin {
|
|
|
40
64
|
|
|
41
65
|
interface Candidate {
|
|
42
66
|
origin: Origin;
|
|
67
|
+
/** The rule's cascade layer, null for none (`StyleRule.layer`). */
|
|
68
|
+
layer: readonly number[] | null;
|
|
43
69
|
specificity: number;
|
|
44
70
|
order: number;
|
|
45
71
|
declarations: Declaration[];
|
|
@@ -59,6 +85,13 @@ type CssSelectAdapter = typeof DomUtils & {
|
|
|
59
85
|
isVisited(el: Element): boolean;
|
|
60
86
|
};
|
|
61
87
|
|
|
88
|
+
/** A style handed to every element with the same sharing key, and the key
|
|
89
|
+
* that element's children share under (`Cascade.sharedStyleFor`). */
|
|
90
|
+
export interface SharedStyle {
|
|
91
|
+
style: ComputedStyle;
|
|
92
|
+
key: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
62
95
|
/** A compiled matcher, kept beside the rule it came from. */
|
|
63
96
|
interface IndexedRule {
|
|
64
97
|
rule: StyleRule;
|
|
@@ -66,8 +99,26 @@ interface IndexedRule {
|
|
|
66
99
|
/** Set once compilation has been attempted, so a selector `css-select`
|
|
67
100
|
* refuses is not recompiled once per element for the rest of the pass. */
|
|
68
101
|
compiled: boolean;
|
|
102
|
+
/** Which rule this is, in a sharing key (`Cascade.sharedStyleFor`). */
|
|
103
|
+
id: number;
|
|
69
104
|
}
|
|
70
105
|
|
|
106
|
+
/**
|
|
107
|
+
* What makes a selector's match depend on more than the element's own tag and
|
|
108
|
+
* attributes and its ancestors' — its siblings, where it sits among them,
|
|
109
|
+
* what it contains, or where the document is scrolled to. Two elements that
|
|
110
|
+
* look alike from the root down can still differ under a rule with one of
|
|
111
|
+
* these, so an element such a rule could reach does not share its style
|
|
112
|
+
* (`Cascade.sharedStyleFor`). css-select spells some of them under other
|
|
113
|
+
* names, and those count too: `:checked` and `:selected` read which option
|
|
114
|
+
* comes first, `:disabled` and `:enabled` which legend does, and `:parent`
|
|
115
|
+
* and `:contains()` the element's contents. Over-broad on purpose: a `+` or
|
|
116
|
+
* `~` inside an attribute value, or the `~=` operator, costs sharing and
|
|
117
|
+
* nothing else.
|
|
118
|
+
*/
|
|
119
|
+
const UNSHAREABLE =
|
|
120
|
+
/[+~]|:(?:first|last|only)-(?:child|of-type)|:nth-|:empty|:blank|:has\(|:focus-within|:target|:scope|:(?:checked|selected|disabled|enabled|parent)\b|:i?contains\(/;
|
|
121
|
+
|
|
71
122
|
/**
|
|
72
123
|
* Rules bucketed by the key of their rightmost compound selector. An element
|
|
73
124
|
* only ever tries `id`, its classes, its tag and the universal bucket.
|
|
@@ -80,13 +131,59 @@ class RuleIndex {
|
|
|
80
131
|
/** Whether any rule in here is pointer-sensitive, so the renderer knows
|
|
81
132
|
* whether a pointer move can change the cascade at all. */
|
|
82
133
|
hoverSensitive = false;
|
|
134
|
+
/** The buckets holding a rule an element cannot share its style under
|
|
135
|
+
* (`UNSHAREABLE`): the ids, classes and tags whose elements compute
|
|
136
|
+
* their own, and whether the universal bucket makes every element do so. */
|
|
137
|
+
readonly ownStyleIds = new Set<string>();
|
|
138
|
+
readonly ownStyleClasses = new Set<string>();
|
|
139
|
+
readonly ownStyleTags = new Set<string>();
|
|
140
|
+
ownStyleEverywhere = false;
|
|
141
|
+
/** How many rules are in here, so an empty index costs one comparison. */
|
|
142
|
+
size = 0;
|
|
143
|
+
private _nextId = 0;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Whether a bucket here could hold a rule for `el`, by its tag, id and
|
|
147
|
+
* classes and without matching anything: an index of a rule or two is
|
|
148
|
+
* asked of every element — the user-agent sheet's `q::before` and
|
|
149
|
+
* `q::after` made every element of a document split its `class` twice —
|
|
150
|
+
* and almost none of them has a rule in it.
|
|
151
|
+
*/
|
|
152
|
+
reaches(el: Element): boolean {
|
|
153
|
+
if (this.universal.length || this.byTag.has(tagOf(el))) return true;
|
|
154
|
+
if (this.byId.size) {
|
|
155
|
+
const id = attr(el, 'id');
|
|
156
|
+
if (id && this.byId.has(id)) return true;
|
|
157
|
+
}
|
|
158
|
+
if (this.byClass.size) {
|
|
159
|
+
const className = attr(el, 'class');
|
|
160
|
+
if (className) {
|
|
161
|
+
for (const name of className.split(/\s+/)) {
|
|
162
|
+
if (name && this.byClass.has(name)) return true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
83
168
|
|
|
84
169
|
add(rule: StyleRule): void {
|
|
85
|
-
|
|
170
|
+
this.size += 1;
|
|
171
|
+
const indexed: IndexedRule = {
|
|
172
|
+
rule,
|
|
173
|
+
match: null,
|
|
174
|
+
compiled: false,
|
|
175
|
+
id: this._nextId++,
|
|
176
|
+
};
|
|
86
177
|
if (rule.selector.includes(':hover') || rule.selector.includes(':active')) {
|
|
87
178
|
this.hoverSensitive = true;
|
|
88
179
|
}
|
|
89
180
|
const key = rightmostKey(rule.selector);
|
|
181
|
+
if (UNSHAREABLE.test(rule.selector)) {
|
|
182
|
+
if (key.kind === 'id') this.ownStyleIds.add(key.name);
|
|
183
|
+
else if (key.kind === 'class') this.ownStyleClasses.add(key.name);
|
|
184
|
+
else if (key.kind === 'tag') this.ownStyleTags.add(key.name);
|
|
185
|
+
else this.ownStyleEverywhere = true;
|
|
186
|
+
}
|
|
90
187
|
const bucket =
|
|
91
188
|
key.kind === 'id'
|
|
92
189
|
? mapBucket(this.byId, key.name)
|
|
@@ -99,6 +196,204 @@ class RuleIndex {
|
|
|
99
196
|
}
|
|
100
197
|
}
|
|
101
198
|
|
|
199
|
+
/** The pseudo-elements a rule can style here. */
|
|
200
|
+
type PseudoElement =
|
|
201
|
+
'before' | 'after' | 'first-letter' | 'first-line' | 'marker';
|
|
202
|
+
|
|
203
|
+
/** A selector's trailing `::before`, `::after`, `::first-letter`,
|
|
204
|
+
* `::first-line` or `::marker`, or CSS 2's single-colon spelling of any of
|
|
205
|
+
* its four. */
|
|
206
|
+
const PSEUDO_ELEMENT = /::?(before|after|first-letter|first-line)$|::marker$/i;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A rule for a pseudo-element, as the pseudo-element it styles and a rule
|
|
210
|
+
* for the element it hangs off, which is what gets matched. The specificity
|
|
211
|
+
* is the whole selector's, the pseudo-element counted in. `p::before`
|
|
212
|
+
* matches `p`; `p ::before` and `p > ::before` have nothing left of their
|
|
213
|
+
* last compound and match any child, `p *` and `p > *`.
|
|
214
|
+
*/
|
|
215
|
+
function splitPseudoElement(
|
|
216
|
+
rule: StyleRule,
|
|
217
|
+
): { which: PseudoElement; rule: StyleRule } | null {
|
|
218
|
+
const m = PSEUDO_ELEMENT.exec(rule.selector);
|
|
219
|
+
if (!m) return null;
|
|
220
|
+
const head = rule.selector.slice(0, m.index);
|
|
221
|
+
const trimmed = head.trimEnd();
|
|
222
|
+
const selector = !trimmed
|
|
223
|
+
? '*'
|
|
224
|
+
: head !== trimmed || /[>+~]$/.test(trimmed)
|
|
225
|
+
? `${trimmed} *`
|
|
226
|
+
: trimmed;
|
|
227
|
+
return {
|
|
228
|
+
which: (m[1] ?? 'marker').toLowerCase() as PseudoElement,
|
|
229
|
+
rule: { ...rule, selector },
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The compounds of a selector that test `:hover`, each without it (and
|
|
235
|
+
* without an `:active` beside it), and whether one is followed by a sibling
|
|
236
|
+
* combinator, which reaches the element's later siblings. `nested` where a
|
|
237
|
+
* `:hover` sits inside a functional pseudo-class — `:not(:hover)`,
|
|
238
|
+
* `:is(:hover)` — and reaches elements no compound names.
|
|
239
|
+
*
|
|
240
|
+
* A `:hover` in a compound's `:has()` is the one exception, since what it
|
|
241
|
+
* reaches is known: the compound, without its `:has()`, is an ancestor of
|
|
242
|
+
* the element the pointer is over — or an earlier sibling of it or of an
|
|
243
|
+
* ancestor, where the argument starts at a sibling (Selectors 4, 4.5). So
|
|
244
|
+
* it is given as an anchor (`has`) rather than making the rule `nested`.
|
|
245
|
+
*
|
|
246
|
+
* `:active` is not the pointer's here: nothing sets it (`setPointer` is
|
|
247
|
+
* handed none), so a selector that tests only it never changes as the
|
|
248
|
+
* pointer moves — Wikipedia's buttons' `:focus:not(:active)` among them.
|
|
249
|
+
* A press that sets it would have to be counted here too.
|
|
250
|
+
*/
|
|
251
|
+
export function pointerCompounds(selector: string): {
|
|
252
|
+
compounds: string[];
|
|
253
|
+
siblings: boolean;
|
|
254
|
+
nested: boolean;
|
|
255
|
+
has: { anchor: string; siblings: boolean }[];
|
|
256
|
+
} {
|
|
257
|
+
const out = {
|
|
258
|
+
compounds: [] as string[],
|
|
259
|
+
siblings: false,
|
|
260
|
+
nested: false,
|
|
261
|
+
has: [] as { anchor: string; siblings: boolean }[],
|
|
262
|
+
};
|
|
263
|
+
// the compounds at the top level, each with the combinator after it
|
|
264
|
+
const parts: { text: string; next: string }[] = [];
|
|
265
|
+
let start = 0;
|
|
266
|
+
let depth = 0;
|
|
267
|
+
let quote = '';
|
|
268
|
+
const split = (end: number, next: string): void => {
|
|
269
|
+
const text = selector.slice(start, end).trim();
|
|
270
|
+
if (text) parts.push({ text, next });
|
|
271
|
+
else if (parts.length && next !== ' ') parts[parts.length - 1].next = next;
|
|
272
|
+
};
|
|
273
|
+
for (let i = 0; i < selector.length; i += 1) {
|
|
274
|
+
const c = selector[i];
|
|
275
|
+
if (quote) {
|
|
276
|
+
if (c === quote && selector[i - 1] !== '\\') quote = '';
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (c === '\\') i = escapeEnd(selector, i) - 1;
|
|
280
|
+
else if (c === '"' || c === "'") quote = c;
|
|
281
|
+
else if (c === '(' || c === '[') depth += 1;
|
|
282
|
+
else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
|
|
283
|
+
else if (depth === 0 && /[\s>+~]/.test(c)) {
|
|
284
|
+
split(i, c === '+' || c === '~' || c === '>' ? c : ' ');
|
|
285
|
+
start = i + 1;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
split(selector.length, '');
|
|
289
|
+
for (const { text, next } of parts) {
|
|
290
|
+
let bare = '';
|
|
291
|
+
let pointer = false;
|
|
292
|
+
depth = 0;
|
|
293
|
+
quote = '';
|
|
294
|
+
/** Where a top-level `:has(` starts in `bare`, and its argument in
|
|
295
|
+
* `text`, while one is open. */
|
|
296
|
+
let hasAt = -1;
|
|
297
|
+
let argAt = -1;
|
|
298
|
+
let hasPointer = false;
|
|
299
|
+
let anchored = false;
|
|
300
|
+
const args: string[] = [];
|
|
301
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
302
|
+
const c = text[i];
|
|
303
|
+
if (quote) {
|
|
304
|
+
if (c === quote && text[i - 1] !== '\\') quote = '';
|
|
305
|
+
bare += c;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (c === '\\') {
|
|
309
|
+
const end = escapeEnd(text, i);
|
|
310
|
+
bare += text.slice(i, end);
|
|
311
|
+
i = end - 1;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (c === '"' || c === "'") quote = c;
|
|
315
|
+
else if (c === '(' || c === '[') {
|
|
316
|
+
if (depth === 0 && c === '(' && /:has$/i.test(bare)) {
|
|
317
|
+
hasAt = bare.length - 4;
|
|
318
|
+
argAt = i + 1;
|
|
319
|
+
hasPointer = false;
|
|
320
|
+
}
|
|
321
|
+
depth += 1;
|
|
322
|
+
} else if (c === ')' || c === ']') {
|
|
323
|
+
depth = Math.max(0, depth - 1);
|
|
324
|
+
if (depth === 0 && hasAt >= 0 && c === ')') {
|
|
325
|
+
// the compound is kept without it: its argument is what the
|
|
326
|
+
// pointer changes
|
|
327
|
+
if (hasPointer) {
|
|
328
|
+
anchored = true;
|
|
329
|
+
args.push(text.slice(argAt, i));
|
|
330
|
+
bare = bare.slice(0, hasAt);
|
|
331
|
+
} else bare += c;
|
|
332
|
+
hasAt = -1;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
} else if (c === ':' && text[i + 1] !== ':') {
|
|
336
|
+
const m = POINTER_PSEUDO_AT.exec(text.slice(i));
|
|
337
|
+
if (m) {
|
|
338
|
+
const hover = m[0].length === 6;
|
|
339
|
+
if (depth > 0) {
|
|
340
|
+
// whatever holds it in the argument, the element it tests is
|
|
341
|
+
// in the anchor's subtree or a later sibling's
|
|
342
|
+
if (hover && hasAt >= 0) hasPointer = true;
|
|
343
|
+
else if (hover) out.nested = true;
|
|
344
|
+
} else {
|
|
345
|
+
if (hover) pointer = true;
|
|
346
|
+
i += m[0].length - 1;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
bare += c;
|
|
352
|
+
}
|
|
353
|
+
if (anchored) {
|
|
354
|
+
out.has.push({
|
|
355
|
+
anchor: bare.trim() || '*',
|
|
356
|
+
siblings: args.some((arg) =>
|
|
357
|
+
arg.split(',').some((one) => /^\s*[+~]/.test(one)),
|
|
358
|
+
),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
if (!pointer) continue;
|
|
362
|
+
out.compounds.push(bare.trim() || '*');
|
|
363
|
+
if (next === '+' || next === '~') out.siblings = true;
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** `:hover` or `:active` at the start of a string, and nothing longer. */
|
|
369
|
+
const POINTER_PSEUDO_AT = /^:(?:hover|active)(?![\w-])/i;
|
|
370
|
+
|
|
371
|
+
/** `:hover` or `:active` anywhere in a selector. */
|
|
372
|
+
const POINTER_PSEUDO = /:(?:hover|active)(?![\w-])/i;
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* A selector compiled to a matcher over this adapter. css-select keeps the
|
|
376
|
+
* answers of a `:has()` above the subject, or of the ancestors a
|
|
377
|
+
* descendant combinator tried under one, for as long as the matcher lives
|
|
378
|
+
* (`cacheResults`) — which is the cascade's life, and a pointer move is no
|
|
379
|
+
* new cascade. So a selector that tests the pointer keeps none: with them,
|
|
380
|
+
* `#box:has(a:hover) .m` answered what it did before the first move, for
|
|
381
|
+
* good.
|
|
382
|
+
*/
|
|
383
|
+
function compileSelector(
|
|
384
|
+
selector: string,
|
|
385
|
+
adapter: CssSelectAdapter,
|
|
386
|
+
): (el: Element) => boolean {
|
|
387
|
+
return compile(noEmptyWords(selector), {
|
|
388
|
+
adapter,
|
|
389
|
+
xmlMode: false,
|
|
390
|
+
pseudos: PSEUDOS,
|
|
391
|
+
cacheResults: !POINTER_PSEUDO.test(selector),
|
|
392
|
+
} as unknown as Parameters<typeof compile>[1]) as unknown as (
|
|
393
|
+
node: Element,
|
|
394
|
+
) => boolean;
|
|
395
|
+
}
|
|
396
|
+
|
|
102
397
|
function mapBucket(
|
|
103
398
|
map: Map<string, IndexedRule[]>,
|
|
104
399
|
key: string,
|
|
@@ -132,12 +427,23 @@ function rightmostKey(selector: string): {
|
|
|
132
427
|
if (c === quote && selector[i - 1] !== '\\') quote = '';
|
|
133
428
|
continue;
|
|
134
429
|
}
|
|
135
|
-
|
|
430
|
+
// an escaped character is part of a name, whatever it is — and a hex
|
|
431
|
+
// escape takes the space after it: `.c\6c ass` is the class `class`
|
|
432
|
+
if (c === '\\') i = escapeEnd(selector, i) - 1;
|
|
433
|
+
else if (c === '"' || c === "'") quote = c;
|
|
136
434
|
else if (c === '(' || c === '[') depth += 1;
|
|
137
435
|
else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
|
|
138
436
|
else if (
|
|
139
437
|
depth === 0 &&
|
|
140
|
-
(c === ' ' ||
|
|
438
|
+
(c === ' ' ||
|
|
439
|
+
c === '>' ||
|
|
440
|
+
c === '+' ||
|
|
441
|
+
c === '~' ||
|
|
442
|
+
// CSS's other white space: `div\fp` is a descendant too
|
|
443
|
+
c === '\t' ||
|
|
444
|
+
c === '\n' ||
|
|
445
|
+
c === '\r' ||
|
|
446
|
+
c === '\f')
|
|
141
447
|
) {
|
|
142
448
|
start = i + 1;
|
|
143
449
|
}
|
|
@@ -149,24 +455,23 @@ function rightmostKey(selector: string): {
|
|
|
149
455
|
let i = 0;
|
|
150
456
|
while (i < compound.length) {
|
|
151
457
|
const c = compound[i];
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
i = end;
|
|
458
|
+
// Names as the matcher reads them, escapes resolved: `.md\:flex` is
|
|
459
|
+
// the class `md:flex`, and filed under its first half it never matched.
|
|
460
|
+
if (c === '#' || c === '.') {
|
|
461
|
+
const name = readIdent(compound, i + 1);
|
|
462
|
+
if (c === '#' && !id) id = name.value;
|
|
463
|
+
if (c === '.' && !cls) cls = name.value;
|
|
464
|
+
i = Math.max(name.end, i + 1);
|
|
160
465
|
} else if (c === '[') {
|
|
161
466
|
i = balancedEnd(compound, i, '[', ']');
|
|
162
467
|
} else if (c === ':') {
|
|
163
468
|
const skip = compound[i + 1] === ':' ? 2 : 1;
|
|
164
|
-
const end =
|
|
469
|
+
const end = Math.max(readIdent(compound, i + skip).end, i + skip);
|
|
165
470
|
i = compound[end] === '(' ? balancedEnd(compound, end, '(', ')') : end;
|
|
166
|
-
} else if (
|
|
167
|
-
const
|
|
168
|
-
if (!tag) tag =
|
|
169
|
-
i = end;
|
|
471
|
+
} else if (startsIdent(compound, i)) {
|
|
472
|
+
const name = readIdent(compound, i);
|
|
473
|
+
if (!tag) tag = name.value.toLowerCase();
|
|
474
|
+
i = name.end;
|
|
170
475
|
} else {
|
|
171
476
|
i += 1;
|
|
172
477
|
}
|
|
@@ -177,12 +482,6 @@ function rightmostKey(selector: string): {
|
|
|
177
482
|
return { kind: 'any', name: '' };
|
|
178
483
|
}
|
|
179
484
|
|
|
180
|
-
function identEnd(text: string, from: number): number {
|
|
181
|
-
let i = from;
|
|
182
|
-
while (i < text.length && /[a-zA-Z0-9_\-\\]/.test(text[i])) i += 1;
|
|
183
|
-
return i;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
485
|
function balancedEnd(
|
|
187
486
|
text: string,
|
|
188
487
|
from: number,
|
|
@@ -200,6 +499,12 @@ function balancedEnd(
|
|
|
200
499
|
return text.length;
|
|
201
500
|
}
|
|
202
501
|
|
|
502
|
+
/** What `Cascade.firstLetterRules` found for an element. */
|
|
503
|
+
export interface FirstLetterRules {
|
|
504
|
+
readonly el: Element;
|
|
505
|
+
readonly candidates: readonly unknown[];
|
|
506
|
+
}
|
|
507
|
+
|
|
203
508
|
/** What the pointer is over, for `:hover`. A chain rather than one element,
|
|
204
509
|
* because `li:hover a` has to light up while the pointer is on the `li`. */
|
|
205
510
|
export interface PointerState {
|
|
@@ -209,13 +514,80 @@ export interface PointerState {
|
|
|
209
514
|
|
|
210
515
|
const NO_POINTER: PointerState = { hovered: new Set(), active: new Set() };
|
|
211
516
|
|
|
517
|
+
/**
|
|
518
|
+
* The face a style's font-relative units are measured in: its family list,
|
|
519
|
+
* size, weight and slant, the four that pick one. A family's faces can be
|
|
520
|
+
* different fonts altogether — a page's `@font-face` rules may set its
|
|
521
|
+
* bold in another file — so an `ex` in bold text is the bold face's.
|
|
522
|
+
*/
|
|
523
|
+
export interface MetricFace {
|
|
524
|
+
family: string;
|
|
525
|
+
size: number;
|
|
526
|
+
weight: number;
|
|
527
|
+
style: ComputedStyle['fontStyle'];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** What a face measures, where the fonts can say. */
|
|
531
|
+
export type FaceMetric = (face: MetricFace) => number | null;
|
|
532
|
+
|
|
533
|
+
/** What a style's face is read from: a computed style, or the part of one
|
|
534
|
+
* a declaration's font-relative units are measured against. */
|
|
535
|
+
type FaceSource = Pick<
|
|
536
|
+
ComputedStyle,
|
|
537
|
+
'fontFamily' | 'fontSize' | 'fontWeight' | 'fontStyle'
|
|
538
|
+
>;
|
|
539
|
+
|
|
540
|
+
/** …and its line height, for `lh`. */
|
|
541
|
+
type LineSource = FaceSource &
|
|
542
|
+
Pick<ComputedStyle, 'lineHeight' | 'lineHeightIsLength'>;
|
|
543
|
+
|
|
544
|
+
function faceOf(style: FaceSource): MetricFace {
|
|
545
|
+
return {
|
|
546
|
+
family: style.fontFamily,
|
|
547
|
+
size: style.fontSize,
|
|
548
|
+
weight: style.fontWeight,
|
|
549
|
+
style: style.fontStyle,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function faceKey(style: FaceSource): string {
|
|
554
|
+
return `${style.fontFamily}\u0001${style.fontSize}\u0001${style.fontWeight}\u0001${style.fontStyle}`;
|
|
555
|
+
}
|
|
556
|
+
|
|
212
557
|
/**
|
|
213
558
|
* A cascade over a fixed set of stylesheets. Rebuilt when the sheets change;
|
|
214
559
|
* re-run when the DOM, the viewport band or the pointer state does.
|
|
215
560
|
*/
|
|
216
561
|
export class Cascade {
|
|
217
562
|
private _index = new RuleIndex();
|
|
563
|
+
/** The rules for pseudo-elements, kept apart: they never style the
|
|
564
|
+
* element itself, and a document with none of them asks nothing. */
|
|
565
|
+
private _pseudo: Record<PseudoElement, RuleIndex> = {
|
|
566
|
+
before: new RuleIndex(),
|
|
567
|
+
after: new RuleIndex(),
|
|
568
|
+
'first-letter': new RuleIndex(),
|
|
569
|
+
'first-line': new RuleIndex(),
|
|
570
|
+
marker: new RuleIndex(),
|
|
571
|
+
};
|
|
218
572
|
private _adapter: CssSelectAdapter;
|
|
573
|
+
/** The compounds of the selectors that test the pointer, each without
|
|
574
|
+
* its `:hover` or `:active` (`pointerCompounds`), and their matchers,
|
|
575
|
+
* compiled when a pointer move first asks (`hoverTouches`). */
|
|
576
|
+
private _hoverCompounds = new Set<string>();
|
|
577
|
+
private _hoverMatchers: ((el: Element) => boolean)[] | null = null;
|
|
578
|
+
/** Whether a pointer move can be restyled where it happened
|
|
579
|
+
* (`HtmlViewNode._hoverInPlace`): false where a selector tests the
|
|
580
|
+
* pointer inside a functional pseudo-class, which reaches elements no
|
|
581
|
+
* compound names. */
|
|
582
|
+
hoverLocal = true;
|
|
583
|
+
/** Whether a compound that tests the pointer is followed by a sibling
|
|
584
|
+
* combinator, so an element's later siblings restyle with it. */
|
|
585
|
+
hoverSiblings = false;
|
|
586
|
+
/** The compounds whose `:has()` tests the pointer, each without it, and
|
|
587
|
+
* whether its argument starts at a sibling (`hoverAnchors`). */
|
|
588
|
+
private _hoverAnchors = new Map<string, boolean>();
|
|
589
|
+
private _anchorMatchers:
|
|
590
|
+
{ match: (el: Element) => boolean; siblings: boolean }[] | null = null;
|
|
219
591
|
private _pointer: PointerState = NO_POINTER;
|
|
220
592
|
readonly initial: ComputedStyle;
|
|
221
593
|
readonly look: RootLook;
|
|
@@ -229,6 +601,35 @@ export class Cascade {
|
|
|
229
601
|
/** Every width at which some `@media` rule changes its mind, in CSS
|
|
230
602
|
* pixels — the unit the author wrote them in. */
|
|
231
603
|
readonly breakpoints: number[];
|
|
604
|
+
/** The counter styles the sheets define, over the predefined ones. */
|
|
605
|
+
readonly counterStyles: CounterStyles;
|
|
606
|
+
|
|
607
|
+
/** A face's x-height, for `ex`, where the fonts can say. */
|
|
608
|
+
private _xHeightOf: FaceMetric | null;
|
|
609
|
+
private _xHeights = new Map<string, number>();
|
|
610
|
+
/** The advance of a face's "0", for `ch`, likewise. */
|
|
611
|
+
private _zeroWidthOf: FaceMetric | null;
|
|
612
|
+
private _zeroWidths = new Map<string, number>();
|
|
613
|
+
/** A face's own line height, for `lh` where `line-height` is `normal`,
|
|
614
|
+
* likewise. */
|
|
615
|
+
private _normalLineOf: FaceMetric | null;
|
|
616
|
+
private _normalLines = new Map<string, number>();
|
|
617
|
+
/** Whether any declaration sets a custom property or reads one: without
|
|
618
|
+
* one, no element's style asks about them. */
|
|
619
|
+
private _vars = false;
|
|
620
|
+
/** Whether any declaration is in a unit of the viewport's width — `vw`,
|
|
621
|
+
* `vmin`, `vmax` — or of its height, `vh` and the same two: a style
|
|
622
|
+
* holds such a length as a number, so it is computed again when that
|
|
623
|
+
* side of the viewport moves (`HtmlViewNode._update`). */
|
|
624
|
+
readsViewportWidth = false;
|
|
625
|
+
readsViewportHeight = false;
|
|
626
|
+
/** The families the document loads itself (`fonts.ts`), or null for a
|
|
627
|
+
* document with no `@font-face`. */
|
|
628
|
+
private _families: FontFamilies | null;
|
|
629
|
+
private _mapFamilies: ((list: string) => string) | undefined;
|
|
630
|
+
/** Whether any declaration has a length in `lh` or `rlh`: only then is
|
|
631
|
+
* the line height settled ahead of the declarations that read it. */
|
|
632
|
+
private _lh = false;
|
|
232
633
|
|
|
233
634
|
constructor(
|
|
234
635
|
sheets: Stylesheet[],
|
|
@@ -236,18 +637,44 @@ export class Cascade {
|
|
|
236
637
|
viewportWidth: number,
|
|
237
638
|
viewportHeight: number,
|
|
238
639
|
scale = 1,
|
|
640
|
+
xHeight: FaceMetric | null = null,
|
|
641
|
+
zeroWidth: FaceMetric | null = null,
|
|
642
|
+
normalLine: FaceMetric | null = null,
|
|
643
|
+
families: FontFamilies | null = null,
|
|
239
644
|
) {
|
|
645
|
+
this._xHeightOf = xHeight;
|
|
646
|
+
this._zeroWidthOf = zeroWidth;
|
|
647
|
+
this._normalLineOf = normalLine;
|
|
648
|
+
this._families = families;
|
|
649
|
+
this._mapFamilies = families
|
|
650
|
+
? (list: string) => families.map(list)
|
|
651
|
+
: undefined;
|
|
240
652
|
this.look = look;
|
|
241
|
-
this.initial = initialStyle(look);
|
|
653
|
+
this.initial = initialStyle(look, scale);
|
|
242
654
|
this.viewportWidth = viewportWidth;
|
|
243
655
|
this.viewportHeight = viewportHeight;
|
|
244
656
|
this.scale = scale;
|
|
245
657
|
const breakpoints = new Set<number>();
|
|
658
|
+
const counterStyles = new Map<string, CounterStyleRule>();
|
|
246
659
|
for (const sheet of sheets) {
|
|
247
|
-
|
|
660
|
+
// a counter style's rule whole over any before it of its name
|
|
661
|
+
for (const { prelude, declarations } of sheet.counterStyles ?? []) {
|
|
662
|
+
const style = counterStyleRule(prelude, declarations);
|
|
663
|
+
if (style) counterStyles.set(style.name, style.rule);
|
|
664
|
+
}
|
|
665
|
+
for (const rule of sheet.rules) {
|
|
666
|
+
if (!this._vars && usesVars(rule.declarations)) this._vars = true;
|
|
667
|
+
this._noteViewportUnits(rule.declarations);
|
|
668
|
+
if (!this._lh && usesLh(rule.declarations)) this._lh = true;
|
|
669
|
+
const pseudo = splitPseudoElement(rule);
|
|
670
|
+
if (pseudo) this._pseudo[pseudo.which].add(pseudo.rule);
|
|
671
|
+
else this._index.add(rule);
|
|
672
|
+
this._noteHover((pseudo?.rule ?? rule).selector);
|
|
673
|
+
}
|
|
248
674
|
for (const bp of sheet.breakpoints) breakpoints.add(bp);
|
|
249
675
|
}
|
|
250
676
|
this.breakpoints = [...breakpoints].sort((a, b) => a - b);
|
|
677
|
+
this.counterStyles = new CounterStyles(counterStyles);
|
|
251
678
|
// css-select's default adapter is domutils; `isHovered` and `isActive`
|
|
252
679
|
// are its documented hooks for exactly this, so `:hover` costs an
|
|
253
680
|
// adapter field rather than a fork of the matcher.
|
|
@@ -258,21 +685,127 @@ export class Cascade {
|
|
|
258
685
|
this._adapter = {
|
|
259
686
|
...DomUtils,
|
|
260
687
|
isTag,
|
|
688
|
+
// a type selector sees `tagOf`'s name — XHTML's `<svg:svg>` is an
|
|
689
|
+
// `svg` — asked only of a name that could be one: this runs for every
|
|
690
|
+
// type selector tried, and the parser has lowercased the rest
|
|
691
|
+
getName: (el: Element) =>
|
|
692
|
+
el.name.endsWith(':svg') ? tagOf(el) : el.name,
|
|
261
693
|
isHovered: (el: Element) => this._pointer.hovered.has(el),
|
|
262
694
|
isActive: (el: Element) => this._pointer.active.has(el),
|
|
263
695
|
isVisited: () => false,
|
|
264
696
|
};
|
|
265
697
|
}
|
|
266
698
|
|
|
699
|
+
/** Whether declarations read the viewport's width or height (`vw`,
|
|
700
|
+
* `vh`, `vmin`, `vmax`), noted on the cascade. */
|
|
701
|
+
private _noteViewportUnits(declarations: readonly Declaration[]): void {
|
|
702
|
+
if (this.readsViewportWidth && this.readsViewportHeight) return;
|
|
703
|
+
for (const d of declarations) {
|
|
704
|
+
if (!VIEWPORT_UNIT.test(d.value)) continue;
|
|
705
|
+
for (const m of d.value.matchAll(VIEWPORT_UNITS)) {
|
|
706
|
+
const unit = m[1].toLowerCase();
|
|
707
|
+
if (unit !== 'vh') this.readsViewportWidth = true;
|
|
708
|
+
if (unit !== 'vw') this.readsViewportHeight = true;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
267
713
|
/** Whether a pointer move can change what this cascade produces. */
|
|
268
714
|
get hoverSensitive(): boolean {
|
|
269
|
-
return
|
|
715
|
+
return (
|
|
716
|
+
this._index.hoverSensitive ||
|
|
717
|
+
this._pseudo.before.hoverSensitive ||
|
|
718
|
+
this._pseudo.after.hoverSensitive ||
|
|
719
|
+
this._pseudo['first-letter'].hoverSensitive ||
|
|
720
|
+
this._pseudo['first-line'].hoverSensitive ||
|
|
721
|
+
this._pseudo.marker.hoverSensitive
|
|
722
|
+
);
|
|
270
723
|
}
|
|
271
724
|
|
|
272
725
|
setPointer(pointer: PointerState): void {
|
|
273
726
|
this._pointer = pointer;
|
|
274
727
|
}
|
|
275
728
|
|
|
729
|
+
/**
|
|
730
|
+
* Whether the pointer entering or leaving `el` can change a style: one of
|
|
731
|
+
* the compounds a selector tests the pointer in matches it, the pointer
|
|
732
|
+
* aside. A move between two paragraphs of a page whose only such rule is
|
|
733
|
+
* `a:hover` touches nothing, and restyles nothing.
|
|
734
|
+
*/
|
|
735
|
+
hoverTouches(el: Element): boolean {
|
|
736
|
+
this._hoverMatchers ??= [...this._hoverCompounds].flatMap((c) => {
|
|
737
|
+
const match = this._compile(c);
|
|
738
|
+
return match ? [match] : [];
|
|
739
|
+
});
|
|
740
|
+
for (const match of this._hoverMatchers) if (match(el)) return true;
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* The elements whose `:has()` may have changed its answer as `el` was
|
|
746
|
+
* hovered or left: the ancestors of it a compound whose `:has()` tests
|
|
747
|
+
* the pointer names, and their earlier siblings and its own where the
|
|
748
|
+
* argument starts at a sibling — a superset, which is restyled and found
|
|
749
|
+
* the same where it did not. Into `into`.
|
|
750
|
+
*/
|
|
751
|
+
hoverAnchors(el: Element, into: Element[]): void {
|
|
752
|
+
if (!this._hoverAnchors.size) return;
|
|
753
|
+
this._anchorMatchers ??= [...this._hoverAnchors].flatMap(
|
|
754
|
+
([anchor, siblings]) => {
|
|
755
|
+
const match = this._compile(anchor);
|
|
756
|
+
return match ? [{ match, siblings }] : [];
|
|
757
|
+
},
|
|
758
|
+
);
|
|
759
|
+
const siblings = this._anchorMatchers.some((m) => m.siblings);
|
|
760
|
+
for (let at: Element | null = el; at;) {
|
|
761
|
+
if (at !== el) {
|
|
762
|
+
for (const { match } of this._anchorMatchers) {
|
|
763
|
+
if (match(at)) {
|
|
764
|
+
into.push(at);
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
if (siblings) {
|
|
770
|
+
for (let s = at.prev; s; s = s.prev) {
|
|
771
|
+
if (!isTag(s as Element)) continue;
|
|
772
|
+
for (const { match, siblings: apart } of this._anchorMatchers) {
|
|
773
|
+
if (apart && match(s as Element)) {
|
|
774
|
+
into.push(s as Element);
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
const parent: Element['parent'] = at.parent;
|
|
781
|
+
at = parent && isTag(parent as Element) ? (parent as Element) : null;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
private _noteHover(selector: string): void {
|
|
786
|
+
if (!/:(?:hover|active)/i.test(selector)) return;
|
|
787
|
+
const found = pointerCompounds(selector);
|
|
788
|
+
if (found.nested) this.hoverLocal = false;
|
|
789
|
+
if (found.siblings) this.hoverSiblings = true;
|
|
790
|
+
for (const c of found.compounds) this._hoverCompounds.add(c);
|
|
791
|
+
for (const { anchor, siblings } of found.has) {
|
|
792
|
+
this._hoverAnchors.set(
|
|
793
|
+
anchor,
|
|
794
|
+
siblings || (this._hoverAnchors.get(anchor) ?? false),
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/** A selector compiled as a rule's is, or null where css-select refuses
|
|
800
|
+
* it, as a rule it refuses drops out of the cascade. */
|
|
801
|
+
private _compile(selector: string): ((el: Element) => boolean) | null {
|
|
802
|
+
try {
|
|
803
|
+
return compileSelector(selector, this._adapter);
|
|
804
|
+
} catch {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
276
809
|
/** Which media band a device-pixel width falls in. Two widths in the same
|
|
277
810
|
* band produce identical styles, which is what lets a resize skip
|
|
278
811
|
* restyling. */
|
|
@@ -286,18 +819,361 @@ export class Cascade {
|
|
|
286
819
|
return band;
|
|
287
820
|
}
|
|
288
821
|
|
|
822
|
+
/** Computed styles by sharing key, for one build (`sharedStyleFor`): one
|
|
823
|
+
* map for the elements shared without matching, one for the elements
|
|
824
|
+
* shared by what they matched. */
|
|
825
|
+
private _shared = new Map<string, SharedStyle>();
|
|
826
|
+
private _sharedByMatch = new Map<string, SharedStyle>();
|
|
827
|
+
private _nextShareKey = 1;
|
|
828
|
+
/** Custom property sets by what made them, for one build (`_customFor`). */
|
|
829
|
+
private _customs = new Map<string, CustomProps>();
|
|
830
|
+
private _ids = new WeakMap<object, number>();
|
|
831
|
+
private _nextId = 1;
|
|
832
|
+
|
|
833
|
+
/** A box tree is about to be built: the styles shared in the last build
|
|
834
|
+
* were computed against a pointer and a viewport that may have moved. */
|
|
835
|
+
beginSharing(): void {
|
|
836
|
+
this._shared.clear();
|
|
837
|
+
this._sharedByMatch.clear();
|
|
838
|
+
this._customs.clear();
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* An element's custom properties: its parent's, or its own over them —
|
|
843
|
+
* shared by every element whose parent has the same ones and whose own
|
|
844
|
+
* come from the same declarations. Tailwind 4 sets thirty-five of them on
|
|
845
|
+
* every element (`*, ::before, ::after { --tw-shadow: 0 0 #0000; … }`),
|
|
846
|
+
* and a set per element was a fifth of building the boxes; it also kept
|
|
847
|
+
* each element's `var()`s from being replaced once for its siblings,
|
|
848
|
+
* which `substituteIn` remembers by the set they were read against.
|
|
849
|
+
*/
|
|
850
|
+
private _customFor(
|
|
851
|
+
parentStyle: ComputedStyle,
|
|
852
|
+
candidates: Candidate[],
|
|
853
|
+
): CustomProps | null {
|
|
854
|
+
let key = '';
|
|
855
|
+
for (const c of candidates) {
|
|
856
|
+
if (!this._hasCustom(c)) continue;
|
|
857
|
+
key += `${this._idOf(c.declarations)}:${c.only},`;
|
|
858
|
+
}
|
|
859
|
+
const parent = parentStyle.custom;
|
|
860
|
+
if (!key) return parent;
|
|
861
|
+
key = `${parent ? this._idOf(parent) : 0}|${key}`;
|
|
862
|
+
const hit = this._customs.get(key);
|
|
863
|
+
if (hit) return hit;
|
|
864
|
+
const own = new Map<string, string>();
|
|
865
|
+
for (const c of candidates) {
|
|
866
|
+
for (const d of pick(c)) if (d.custom) own.set(d.prop, d.value);
|
|
867
|
+
}
|
|
868
|
+
const made = customProperties(own, parent);
|
|
869
|
+
this._customs.set(key, made);
|
|
870
|
+
return made;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/** Whether a candidate sets a custom property. */
|
|
874
|
+
private _hasCustom(c: Candidate): boolean {
|
|
875
|
+
if (c.only >= 0) return !!c.declarations[c.only].custom;
|
|
876
|
+
let has = CUSTOM_IN.get(c.declarations);
|
|
877
|
+
if (has === undefined) {
|
|
878
|
+
has = c.declarations.some((d) => !!d.custom);
|
|
879
|
+
CUSTOM_IN.set(c.declarations, has);
|
|
880
|
+
}
|
|
881
|
+
return has;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/** A number for an object, to key a string by. */
|
|
885
|
+
private _idOf(of: object): number {
|
|
886
|
+
let id = this._ids.get(of);
|
|
887
|
+
if (id === undefined) {
|
|
888
|
+
id = this._nextId++;
|
|
889
|
+
this._ids.set(of, id);
|
|
890
|
+
}
|
|
891
|
+
return id;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* `styleFor`, shared between the elements that must compute the same
|
|
896
|
+
* style, with the key this element's children share under.
|
|
897
|
+
*
|
|
898
|
+
* A style is a function of the parent's style, the rules that match, the
|
|
899
|
+
* presentational attributes, the inline style and whether the parent is a
|
|
900
|
+
* flex container. Outside the rules `UNSHAREABLE` names, what matches
|
|
901
|
+
* depends only on the element's tag and attributes and its ancestors', and
|
|
902
|
+
* the hints read nothing else either. So the key is the parent's key, the
|
|
903
|
+
* flex flag, the tag and every attribute — plus the pointer state where a
|
|
904
|
+
* rule asks for it — and an element whose key has been computed in this
|
|
905
|
+
* build takes that style object, matching nothing. A long document is a
|
|
906
|
+
* few kinds of element many times over: the benchmark's 600 KB report is
|
|
907
|
+
* 9,039 elements and 110 keys, and computing every style again was a third
|
|
908
|
+
* of an edit.
|
|
909
|
+
*
|
|
910
|
+
* An element a rule `UNSHAREABLE` could reach is matched, since where it
|
|
911
|
+
* sits decides what matches — and then shared by what matched: the same
|
|
912
|
+
* parent, attributes and rules make the same style. That is what keeps a
|
|
913
|
+
* striped table from being a style per row, and it keeps the key its
|
|
914
|
+
* children share under, which an element-per-key would have taken from the
|
|
915
|
+
* whole subtree.
|
|
916
|
+
*
|
|
917
|
+
* The style is handed out shared, so nothing may write to it — nothing
|
|
918
|
+
* does after the cascade, and `rootStyle` does not go through here.
|
|
919
|
+
*/
|
|
920
|
+
sharedStyleFor(
|
|
921
|
+
el: Element,
|
|
922
|
+
parentStyle: ComputedStyle,
|
|
923
|
+
parentKey: number,
|
|
924
|
+
inFlexContainer: boolean,
|
|
925
|
+
): SharedStyle {
|
|
926
|
+
let key = `${parentKey}\u0001${inFlexContainer ? 1 : 0}`;
|
|
927
|
+
const tag = tagOf(el);
|
|
928
|
+
key += `\u0001${tag.length}:${tag}`;
|
|
929
|
+
if (this._index.hoverSensitive) {
|
|
930
|
+
if (this._pointer.hovered.has(el)) key += '\u0001:hover';
|
|
931
|
+
if (this._pointer.active.has(el)) key += '\u0001:active';
|
|
932
|
+
}
|
|
933
|
+
const attribs = el.attribs;
|
|
934
|
+
for (const name in attribs) {
|
|
935
|
+
const value = attribs[name];
|
|
936
|
+
key += `\u0001${name.length}:${name}=${value.length}:${value}`;
|
|
937
|
+
}
|
|
938
|
+
if (!this._shareable(el)) {
|
|
939
|
+
const matched: number[] = [];
|
|
940
|
+
const candidates = this._candidates(el, matched);
|
|
941
|
+
key += `\u0001${matched.join(',')}`;
|
|
942
|
+
let shared = this._sharedByMatch.get(key);
|
|
943
|
+
if (shared === undefined) {
|
|
944
|
+
shared = {
|
|
945
|
+
style: this._computeStyle(
|
|
946
|
+
el,
|
|
947
|
+
parentStyle,
|
|
948
|
+
inFlexContainer,
|
|
949
|
+
candidates,
|
|
950
|
+
),
|
|
951
|
+
key: this._nextShareKey++,
|
|
952
|
+
};
|
|
953
|
+
this._sharedByMatch.set(key, shared);
|
|
954
|
+
}
|
|
955
|
+
return shared;
|
|
956
|
+
}
|
|
957
|
+
let shared = this._shared.get(key);
|
|
958
|
+
if (shared === undefined) {
|
|
959
|
+
shared = {
|
|
960
|
+
style: this.styleFor(el, parentStyle, inFlexContainer),
|
|
961
|
+
key: this._nextShareKey++,
|
|
962
|
+
};
|
|
963
|
+
this._shared.set(key, shared);
|
|
964
|
+
}
|
|
965
|
+
return shared;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/** Whether no rule `UNSHAREABLE` names could reach this element: the same
|
|
969
|
+
* buckets `_candidates` looks in. */
|
|
970
|
+
private _shareable(el: Element): boolean {
|
|
971
|
+
const index = this._index;
|
|
972
|
+
if (index.ownStyleEverywhere) return false;
|
|
973
|
+
if (index.ownStyleTags.size && index.ownStyleTags.has(tagOf(el))) {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
if (index.ownStyleIds.size) {
|
|
977
|
+
const id = attr(el, 'id');
|
|
978
|
+
if (id && index.ownStyleIds.has(id)) return false;
|
|
979
|
+
}
|
|
980
|
+
if (index.ownStyleClasses.size) {
|
|
981
|
+
const className = attr(el, 'class');
|
|
982
|
+
if (className) {
|
|
983
|
+
for (const name of className.split(/\s+/)) {
|
|
984
|
+
if (name && index.ownStyleClasses.has(name)) return false;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return true;
|
|
989
|
+
}
|
|
990
|
+
|
|
289
991
|
/**
|
|
290
|
-
* The computed style for one element, given its parent's.
|
|
291
|
-
*
|
|
292
|
-
*
|
|
992
|
+
* The computed style for one element, given its parent's. The box builder
|
|
993
|
+
* asks for every element in document order, through `sharedStyleFor`, so
|
|
994
|
+
* a style is computed only for the elements with none to share.
|
|
293
995
|
*/
|
|
294
996
|
styleFor(
|
|
295
997
|
el: Element,
|
|
296
998
|
parentStyle: ComputedStyle,
|
|
297
999
|
inFlexContainer: boolean,
|
|
1000
|
+
): ComputedStyle {
|
|
1001
|
+
return this._computeStyle(
|
|
1002
|
+
el,
|
|
1003
|
+
parentStyle,
|
|
1004
|
+
inFlexContainer,
|
|
1005
|
+
this._candidates(el),
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* The style of an element's `::before` or `::after`, or null when it has
|
|
1011
|
+
* none: no rule reaches it, or the rules that do leave `content` at
|
|
1012
|
+
* `normal` or `none`, which make no box (CSS 2.1 12.2). It inherits from
|
|
1013
|
+
* the element's own style, as a pseudo-element does, and a flex
|
|
1014
|
+
* container's are flex items. Not shared: few elements have one, and where
|
|
1015
|
+
* a rule gives every element one — a clearfix — the style is the cost of
|
|
1016
|
+
* the box it makes.
|
|
1017
|
+
*/
|
|
1018
|
+
pseudoStyleFor(
|
|
1019
|
+
el: Element,
|
|
1020
|
+
which: 'before' | 'after',
|
|
1021
|
+
elementStyle: ComputedStyle,
|
|
1022
|
+
): ComputedStyle | null {
|
|
1023
|
+
const index = this._pseudo[which];
|
|
1024
|
+
if (!index.size || !index.reaches(el)) return null;
|
|
1025
|
+
const candidates: Candidate[] = [];
|
|
1026
|
+
this._matchInto(index, el, candidates);
|
|
1027
|
+
if (!candidates.length) return null;
|
|
1028
|
+
// One that no rule gives a `content` is none, whatever else reaches it —
|
|
1029
|
+
// `content` is not inherited, and its initial `normal` is nothing here.
|
|
1030
|
+
// Tailwind's `*, ::before, ::after` reaches both of every element's.
|
|
1031
|
+
if (!candidates.some(setsContent)) return null;
|
|
1032
|
+
candidates.sort(byCascade);
|
|
1033
|
+
const style = this._computeStyle(
|
|
1034
|
+
el,
|
|
1035
|
+
elementStyle,
|
|
1036
|
+
elementStyle.display === 'flex' || elementStyle.display === 'inline-flex',
|
|
1037
|
+
candidates,
|
|
1038
|
+
);
|
|
1039
|
+
return style.content === 'normal' || style.content === 'none'
|
|
1040
|
+
? null
|
|
1041
|
+
: style;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* The rules for an element's `::first-letter`, or null when none reaches
|
|
1046
|
+
* it. They are matched against the element here, and its style computed
|
|
1047
|
+
* later by `firstLetterStyle`, because the pseudo-element inherits from
|
|
1048
|
+
* the box its letter turns out to be in — a `<span>` the block opens with,
|
|
1049
|
+
* a `::before` — which the builder has not reached yet.
|
|
1050
|
+
*/
|
|
1051
|
+
firstLetterRules(el: Element): FirstLetterRules | null {
|
|
1052
|
+
const index = this._pseudo['first-letter'];
|
|
1053
|
+
if (!index.size || !index.reaches(el)) return null;
|
|
1054
|
+
const candidates: Candidate[] = [];
|
|
1055
|
+
this._matchInto(index, el, candidates);
|
|
1056
|
+
if (!candidates.length) return null;
|
|
1057
|
+
candidates.sort(byCascade);
|
|
1058
|
+
return { el, candidates };
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/** Whether any rule styles a `::first-line`: a document with none asks
|
|
1062
|
+
* nothing more of its blocks. */
|
|
1063
|
+
get hasFirstLine(): boolean {
|
|
1064
|
+
return this._pseudo['first-line'].size > 0;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* A list item's `::marker` style, inheriting from its own, or null when no
|
|
1069
|
+
* rule reaches it (CSS Lists 3, 3.1): the colour and the font a bullet or
|
|
1070
|
+
* a number is set in, and a `content` it is set as instead.
|
|
1071
|
+
*/
|
|
1072
|
+
markerStyle(el: Element, style: ComputedStyle): ComputedStyle | null {
|
|
1073
|
+
const index = this._pseudo.marker;
|
|
1074
|
+
if (!index.size || !index.reaches(el)) return null;
|
|
1075
|
+
const candidates: Candidate[] = [];
|
|
1076
|
+
this._matchInto(index, el, candidates);
|
|
1077
|
+
if (!candidates.length) return null;
|
|
1078
|
+
candidates.sort(byCascade);
|
|
1079
|
+
const out = this._computeStyle(el, style, false, candidates);
|
|
1080
|
+
// a marker's direction is its own unless a rule says otherwise (the
|
|
1081
|
+
// HTML style sheet's `::marker { unicode-bidi: isolate }`)
|
|
1082
|
+
const set = candidates.some((c) =>
|
|
1083
|
+
c.declarations.some((d) => d.prop === 'unicode-bidi'),
|
|
1084
|
+
);
|
|
1085
|
+
if (!set) out.unicodeBidi = 'isolate';
|
|
1086
|
+
return out;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* An element's `::first-line` style, inheriting from its own, or null
|
|
1091
|
+
* when no rule reaches it (CSS 2.1 5.12.1).
|
|
1092
|
+
*/
|
|
1093
|
+
firstLineStyle(el: Element, style: ComputedStyle): ComputedStyle | null {
|
|
1094
|
+
const index = this._pseudo['first-line'];
|
|
1095
|
+
if (!index.size) return null;
|
|
1096
|
+
const candidates: Candidate[] = [];
|
|
1097
|
+
this._matchInto(index, el, candidates);
|
|
1098
|
+
if (!candidates.length) return null;
|
|
1099
|
+
candidates.sort(byCascade);
|
|
1100
|
+
return this._computeStyle(el, style, false, candidates);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* The style of a `::first-letter` inside a box of `parentStyle`. It is an
|
|
1105
|
+
* inline box, or a float where it floats, whatever the rules say of its
|
|
1106
|
+
* `display` or its `position` (CSS 2.1 5.12.2).
|
|
1107
|
+
*/
|
|
1108
|
+
firstLetterStyle(
|
|
1109
|
+
rules: FirstLetterRules,
|
|
1110
|
+
parentStyle: ComputedStyle,
|
|
1111
|
+
): ComputedStyle {
|
|
1112
|
+
const style = this._computeStyle(
|
|
1113
|
+
rules.el,
|
|
1114
|
+
parentStyle,
|
|
1115
|
+
false,
|
|
1116
|
+
rules.candidates as Candidate[],
|
|
1117
|
+
);
|
|
1118
|
+
style.display = style.float === 'none' ? 'inline' : 'block';
|
|
1119
|
+
style.position = 'static';
|
|
1120
|
+
return style;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/** The x-height of a style's font: the font's own, asked once per face,
|
|
1124
|
+
* or half an em. */
|
|
1125
|
+
private _exOf(style: FaceSource): number {
|
|
1126
|
+
const key = faceKey(style);
|
|
1127
|
+
let ex = this._xHeights.get(key);
|
|
1128
|
+
if (ex === undefined) {
|
|
1129
|
+
ex = this._xHeightOf?.(faceOf(style)) ?? NaN;
|
|
1130
|
+
if (!(ex > 0)) ex = style.fontSize * 0.5;
|
|
1131
|
+
this._xHeights.set(key, ex);
|
|
1132
|
+
}
|
|
1133
|
+
return ex;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/** The advance of a style's font's "0": the font's own, asked once per
|
|
1137
|
+
* face, or half an em. */
|
|
1138
|
+
private _chOf(style: FaceSource): number {
|
|
1139
|
+
const key = faceKey(style);
|
|
1140
|
+
let ch = this._zeroWidths.get(key);
|
|
1141
|
+
if (ch === undefined) {
|
|
1142
|
+
ch = this._zeroWidthOf?.(faceOf(style)) ?? NaN;
|
|
1143
|
+
if (!(ch > 0)) ch = style.fontSize * 0.5;
|
|
1144
|
+
this._zeroWidths.set(key, ch);
|
|
1145
|
+
}
|
|
1146
|
+
return ch;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/** A style's computed line height as a length, for `lh`: `normal` as
|
|
1150
|
+
* its font's own, asked once per face, or 1.2em. */
|
|
1151
|
+
private _lineHeightOf(style: LineSource): number {
|
|
1152
|
+
const set = style.lineHeight;
|
|
1153
|
+
if (set !== 'normal') {
|
|
1154
|
+
return style.lineHeightIsLength ? set : set * style.fontSize;
|
|
1155
|
+
}
|
|
1156
|
+
const key = faceKey(style);
|
|
1157
|
+
let line = this._normalLines.get(key);
|
|
1158
|
+
if (line === undefined) {
|
|
1159
|
+
line = this._normalLineOf?.(faceOf(style)) ?? NaN;
|
|
1160
|
+
if (!(line > 0)) line = style.fontSize * 1.2;
|
|
1161
|
+
this._normalLines.set(key, line);
|
|
1162
|
+
}
|
|
1163
|
+
return line;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/** `styleFor`, from the rules and hints already gathered for `el`. */
|
|
1167
|
+
private _computeStyle(
|
|
1168
|
+
el: Element,
|
|
1169
|
+
parentStyle: ComputedStyle,
|
|
1170
|
+
inFlexContainer: boolean,
|
|
1171
|
+
candidates: Candidate[],
|
|
298
1172
|
): ComputedStyle {
|
|
299
1173
|
const style = inherit(parentStyle, this.initial);
|
|
300
|
-
|
|
1174
|
+
// custom properties first, in cascade order, so every `var()` in the
|
|
1175
|
+
// declarations below finds the one that wins
|
|
1176
|
+
if (this._vars) style.custom = this._customFor(parentStyle, candidates);
|
|
301
1177
|
|
|
302
1178
|
// The unit context has to be built twice: once with the parent's font
|
|
303
1179
|
// size, so a `font-size: 1.2em` in the cascade resolves against the
|
|
@@ -306,29 +1182,158 @@ export class Cascade {
|
|
|
306
1182
|
const ctxParent: UnitContext = {
|
|
307
1183
|
em: parentStyle.fontSize,
|
|
308
1184
|
rem: this.initial.fontSize,
|
|
1185
|
+
initial: this.initial,
|
|
309
1186
|
vw: this.viewportWidth,
|
|
310
1187
|
vh: this.viewportHeight,
|
|
311
1188
|
scale: this.scale,
|
|
1189
|
+
ex: () => this._exOf(parentStyle),
|
|
1190
|
+
ch: () => this._chOf(parentStyle),
|
|
1191
|
+
families: this._mapFamilies,
|
|
1192
|
+
lh: () => this._lineHeightOf(parentStyle),
|
|
1193
|
+
rlh: () => this._lineHeightOf(this.initial),
|
|
312
1194
|
};
|
|
1195
|
+
// The family, the weight and the slant go with the size: together they
|
|
1196
|
+
// pick the face an `ex`, a `ch` or an `lh` in any declaration is
|
|
1197
|
+
// measured in, however the declarations are ordered. A `width: 10ex`
|
|
1198
|
+
// in a sheet under an inline `font-weight: 900` was measured in the
|
|
1199
|
+
// family's regular face.
|
|
1200
|
+
let sized = false;
|
|
1201
|
+
let keyword = false;
|
|
313
1202
|
for (const c of candidates) {
|
|
314
1203
|
for (const d of pick(c)) {
|
|
315
|
-
if (
|
|
316
|
-
|
|
1204
|
+
if (
|
|
1205
|
+
d.prop === 'font-family' ||
|
|
1206
|
+
d.prop === 'font-weight' ||
|
|
1207
|
+
d.prop === 'font-style'
|
|
1208
|
+
) {
|
|
1209
|
+
this._apply(style, parentStyle, d, ctxParent);
|
|
1210
|
+
} else if (d.prop === 'font-size' || d.prop === 'font') {
|
|
1211
|
+
// NaN until a size is set, so one the declaration did not take
|
|
1212
|
+
// leaves the size where it was, and says nothing of it
|
|
1213
|
+
const was = style.fontSize;
|
|
1214
|
+
style.fontSize = NaN;
|
|
1215
|
+
this._apply(style, parentStyle, d, ctxParent);
|
|
1216
|
+
if (Number.isNaN(style.fontSize)) {
|
|
1217
|
+
style.fontSize = was;
|
|
1218
|
+
} else {
|
|
1219
|
+
sized = true;
|
|
1220
|
+
keyword = !d.vars && sizeKeyword(d.prop, d.value);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
// The generic `monospace` on its own has a smaller size than the rest,
|
|
1226
|
+
// 13 pixels to their 16 (the "fixed" font size every browser keeps):
|
|
1227
|
+
// an element whose family becomes it, or stops being it, scales the
|
|
1228
|
+
// size it inherits by that, and a keyword size is read from the
|
|
1229
|
+
// smaller scale — the size it sets itself stays its own. Blink's
|
|
1230
|
+
// CheckForGenericFamilyChange, and why a `<pre>` in a browser is 13px.
|
|
1231
|
+
const mono = monospaceOnly(style.fontFamily);
|
|
1232
|
+
if (
|
|
1233
|
+
sized ? keyword && mono : mono !== monospaceOnly(parentStyle.fontFamily)
|
|
1234
|
+
) {
|
|
1235
|
+
style.fontSize *= mono ? FIXED_SIZE : 1 / FIXED_SIZE;
|
|
1236
|
+
}
|
|
1237
|
+
// The face as the first pass settled it. The second applies every
|
|
1238
|
+
// declaration again in cascade order, the font's among them, so the
|
|
1239
|
+
// style passes through the fonts of every rule on the way: read from
|
|
1240
|
+
// it, the `10ex` in `div { font-family: foo; width: 10ex }` was `foo`'s
|
|
1241
|
+
// under an inline `font-family: Ahem` that came after it.
|
|
1242
|
+
const face: FaceSource = {
|
|
1243
|
+
fontFamily: style.fontFamily,
|
|
1244
|
+
fontSize: style.fontSize,
|
|
1245
|
+
fontWeight: style.fontWeight,
|
|
1246
|
+
fontStyle: style.fontStyle,
|
|
1247
|
+
};
|
|
1248
|
+
let line: LineSource | null = null;
|
|
1249
|
+
const ctx: UnitContext = {
|
|
1250
|
+
...ctxParent,
|
|
1251
|
+
em: style.fontSize,
|
|
1252
|
+
ex: () => this._exOf(face),
|
|
1253
|
+
ch: () => this._chOf(face),
|
|
1254
|
+
lh: () => this._lineHeightOf(line ?? style),
|
|
1255
|
+
};
|
|
1256
|
+
// the line height, which an `lh` in any other declaration reads, ahead
|
|
1257
|
+
// of them — a `font` sets one too — and read as it is then, for the
|
|
1258
|
+
// same reason as the face: set again with the rest, in its place among
|
|
1259
|
+
// them. A `font` sets the size as well, which the first pass settled.
|
|
1260
|
+
if (this._lh) {
|
|
1261
|
+
for (const c of candidates) {
|
|
1262
|
+
for (const d of pick(c)) {
|
|
1263
|
+
if (d.prop === 'line-height' || d.prop === 'font') {
|
|
1264
|
+
this._apply(style, parentStyle, d, ctx);
|
|
1265
|
+
}
|
|
317
1266
|
}
|
|
318
1267
|
}
|
|
1268
|
+
style.fontSize = face.fontSize;
|
|
1269
|
+
line = {
|
|
1270
|
+
...face,
|
|
1271
|
+
lineHeight: style.lineHeight,
|
|
1272
|
+
lineHeightIsLength: style.lineHeightIsLength,
|
|
1273
|
+
};
|
|
319
1274
|
}
|
|
320
|
-
const
|
|
1275
|
+
const settled = style.fontSize;
|
|
321
1276
|
for (const c of candidates) {
|
|
322
1277
|
for (const d of pick(c)) {
|
|
323
|
-
if (d.prop === 'font-size') continue;
|
|
324
|
-
|
|
1278
|
+
if (d.prop === 'font-size' || d.custom) continue;
|
|
1279
|
+
this._apply(style, parentStyle, d, ctx);
|
|
325
1280
|
}
|
|
326
1281
|
}
|
|
1282
|
+
// A `font` is applied again, to keep its other longhands in cascade
|
|
1283
|
+
// order, and it sets the size it names as well: the size is the first
|
|
1284
|
+
// pass's, which the declarations that outrank the `font` had their say
|
|
1285
|
+
// in, and the scale after them. `span { font: 15px/1 Ahem }` under
|
|
1286
|
+
// `.b > span { font-size: 3.75em }` was 15px, its `em`s 3.75 of its
|
|
1287
|
+
// parent's.
|
|
1288
|
+
style.fontSize = settled;
|
|
327
1289
|
|
|
1290
|
+
// A table never keeps HTML's alignment, `-webkit-center` and its kin:
|
|
1291
|
+
// the `<td align="center">` every mail centres its body table in
|
|
1292
|
+
// centres the table, and the text in its cells stays at their start —
|
|
1293
|
+
// as Blink resets it ("tables never support the -webkit-* values for
|
|
1294
|
+
// text-align"). An author's own `text-align: center` is not HTML's, and
|
|
1295
|
+
// is inherited into the table as it always was.
|
|
1296
|
+
if (style.alignBlocks !== null && tagOf(el) === 'table') {
|
|
1297
|
+
style.textAlign = 'start';
|
|
1298
|
+
style.alignBlocks = null;
|
|
1299
|
+
}
|
|
1300
|
+
settleClamp(style, parentStyle);
|
|
1301
|
+
settleOverflow(style);
|
|
1302
|
+
settleContentVisibility(style);
|
|
328
1303
|
blockify(style, inFlexContainer);
|
|
1304
|
+
decorate(style);
|
|
1305
|
+
// which faces of the document's own families this family, weight and
|
|
1306
|
+
// slant ask for — known only now, with all three computed
|
|
1307
|
+
this._families?.note(style);
|
|
329
1308
|
return style;
|
|
330
1309
|
}
|
|
331
1310
|
|
|
1311
|
+
/** One declaration, its `var()`s replaced first. One that names a
|
|
1312
|
+
* custom property with no value, and has no fallback, is invalid at
|
|
1313
|
+
* computed-value time, and the property is as though `unset`. */
|
|
1314
|
+
private _apply(
|
|
1315
|
+
style: ComputedStyle,
|
|
1316
|
+
parentStyle: ComputedStyle,
|
|
1317
|
+
d: Declaration,
|
|
1318
|
+
ctx: UnitContext,
|
|
1319
|
+
): void {
|
|
1320
|
+
if (!d.vars) {
|
|
1321
|
+
applyDeclaration(style, parentStyle, d.prop, d.value, ctx);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
// unset first: a value that does not parse once it is substituted is
|
|
1325
|
+
// invalid at computed-value time, and does not leave the property what
|
|
1326
|
+
// an earlier declaration gave it
|
|
1327
|
+
if (isInherited(d.prop)) {
|
|
1328
|
+
applyDeclaration(style, parentStyle, d.prop, 'inherit', ctx);
|
|
1329
|
+
} else {
|
|
1330
|
+
initialOne(style, this.initial, d.prop);
|
|
1331
|
+
}
|
|
1332
|
+
const value = substituteIn(d.value, style.custom);
|
|
1333
|
+
if (value !== null)
|
|
1334
|
+
applyDeclaration(style, parentStyle, d.prop, value, ctx);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
332
1337
|
/**
|
|
333
1338
|
* The root's style — nothing above it to inherit from.
|
|
334
1339
|
*
|
|
@@ -337,24 +1342,51 @@ export class Cascade {
|
|
|
337
1342
|
* a `<body>` would have had: the UA sheet's margin and font, and any
|
|
338
1343
|
* author `body { … }` rule. Without it a fragment renders hard against the
|
|
339
1344
|
* left edge while the same markup inside `<html><body>` does not, which
|
|
340
|
-
* reads as a bug in the renderer rather than as a missing element.
|
|
1345
|
+
* reads as a bug in the renderer rather than as a missing element. With
|
|
1346
|
+
* no `<html>` either, the body inherits from an `<html>` that author
|
|
1347
|
+
* `html { … }` rules have styled, as the one a browser implies would be.
|
|
1348
|
+
*
|
|
1349
|
+
* A document with a `<body>` and no `<html>` — the usual shape of a page
|
|
1350
|
+
* that starts `<!DOCTYPE html><title>` — has the root box standing in for
|
|
1351
|
+
* that implied `<html>` instead, so an `html { font-size }` still reaches
|
|
1352
|
+
* the body. Taken as the initial style, the rule matched nothing and the
|
|
1353
|
+
* whole page came out at the theme's size.
|
|
1354
|
+
*
|
|
1355
|
+
* `html` is the implied `<html>`'s own style where the root box stands in
|
|
1356
|
+
* for the body inside it, and null otherwise: what an `html { … }` or a
|
|
1357
|
+
* `:root { … }` rule gives it that is not inherited has no box to be
|
|
1358
|
+
* drawn on, and its background is the canvas's (`paintDocument`).
|
|
341
1359
|
*/
|
|
342
|
-
rootStyle(
|
|
343
|
-
|
|
1360
|
+
rootStyle(
|
|
1361
|
+
hasBody: boolean,
|
|
1362
|
+
hasHtml = true,
|
|
1363
|
+
): { style: ComputedStyle; html: ComputedStyle | null } {
|
|
1364
|
+
const style = copyStyle(this.initial);
|
|
344
1365
|
style.display = 'block';
|
|
345
|
-
if (hasBody) return style;
|
|
1366
|
+
if (hasBody && hasHtml) return { style, html: null };
|
|
346
1367
|
const synthetic = new DomElement('body', {}, []);
|
|
347
|
-
|
|
1368
|
+
let parent = style;
|
|
1369
|
+
let html: ComputedStyle | null = null;
|
|
1370
|
+
if (!hasHtml) {
|
|
1371
|
+
const element = new DomElement('html', {}, hasBody ? [] : [synthetic]);
|
|
1372
|
+
synthetic.parent = element;
|
|
1373
|
+
parent = this.styleFor(element, style, false);
|
|
1374
|
+
if (hasBody) return { style: asRoot(parent), html: null };
|
|
1375
|
+
html = parent;
|
|
1376
|
+
}
|
|
348
1377
|
// Only the box the body would have drawn is taken, not its layout role:
|
|
349
1378
|
// the root is still the initial containing block.
|
|
350
|
-
|
|
351
|
-
bodyStyle.position = 'static';
|
|
352
|
-
bodyStyle.float = 'none';
|
|
353
|
-
return bodyStyle;
|
|
1379
|
+
return { style: asRoot(this.styleFor(synthetic, parent, false)), html };
|
|
354
1380
|
}
|
|
355
1381
|
|
|
356
|
-
|
|
357
|
-
|
|
1382
|
+
/** The rules of `index` that match `el`, pushed onto `out` as candidates,
|
|
1383
|
+
* and their ids onto `matched` in the order they were tried. */
|
|
1384
|
+
private _matchInto(
|
|
1385
|
+
index: RuleIndex,
|
|
1386
|
+
el: Element,
|
|
1387
|
+
out: Candidate[],
|
|
1388
|
+
matched?: number[],
|
|
1389
|
+
): void {
|
|
358
1390
|
// A media query's width is CSS pixels; the viewport is kept in device.
|
|
359
1391
|
const width = this.viewportWidth / this.scale;
|
|
360
1392
|
|
|
@@ -366,12 +1398,7 @@ export class Cascade {
|
|
|
366
1398
|
if (!indexed.compiled) {
|
|
367
1399
|
indexed.compiled = true;
|
|
368
1400
|
try {
|
|
369
|
-
indexed.match =
|
|
370
|
-
adapter: this._adapter,
|
|
371
|
-
xmlMode: false,
|
|
372
|
-
} as unknown as Parameters<typeof compile>[1]) as unknown as (
|
|
373
|
-
node: Element,
|
|
374
|
-
) => boolean;
|
|
1401
|
+
indexed.match = compileSelector(rule.selector, this._adapter);
|
|
375
1402
|
} catch {
|
|
376
1403
|
// A selector this matcher does not know (`::-moz-…`, a CSS4 form
|
|
377
1404
|
// it has not learnt) drops out of the cascade rather than out of
|
|
@@ -380,26 +1407,36 @@ export class Cascade {
|
|
|
380
1407
|
}
|
|
381
1408
|
}
|
|
382
1409
|
if (!indexed.match || !indexed.match(el)) continue;
|
|
1410
|
+
matched?.push(indexed.id);
|
|
383
1411
|
const origin = rule.order < 0 ? Origin.UserAgent : Origin.Author;
|
|
384
1412
|
pushRule(out, rule, origin);
|
|
385
1413
|
}
|
|
386
1414
|
};
|
|
387
1415
|
|
|
388
1416
|
const id = attr(el, 'id');
|
|
389
|
-
if (id) consider(
|
|
1417
|
+
if (id) consider(index.byId.get(id));
|
|
390
1418
|
const className = attr(el, 'class');
|
|
391
1419
|
if (className) {
|
|
392
1420
|
for (const name of className.split(/\s+/)) {
|
|
393
|
-
if (name) consider(
|
|
1421
|
+
if (name) consider(index.byClass.get(name));
|
|
394
1422
|
}
|
|
395
1423
|
}
|
|
396
|
-
consider(
|
|
397
|
-
consider(
|
|
1424
|
+
consider(index.byTag.get(tagOf(el)));
|
|
1425
|
+
consider(index.universal);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/** Every rule and hint that applies to `el`, in cascade order — and, into
|
|
1429
|
+
* `matched` when it is passed, the ids of the rules, in the order they
|
|
1430
|
+
* were tried. */
|
|
1431
|
+
private _candidates(el: Element, matched?: number[]): Candidate[] {
|
|
1432
|
+
const out: Candidate[] = [];
|
|
1433
|
+
this._matchInto(this._index, el, out, matched);
|
|
398
1434
|
|
|
399
1435
|
const hints = presentationHints(el);
|
|
400
1436
|
if (hints.length) {
|
|
401
1437
|
out.push({
|
|
402
1438
|
origin: Origin.Presentation,
|
|
1439
|
+
layer: null,
|
|
403
1440
|
specificity: 0,
|
|
404
1441
|
order: 0,
|
|
405
1442
|
declarations: hints,
|
|
@@ -410,11 +1447,15 @@ export class Cascade {
|
|
|
410
1447
|
const inline = attr(el, 'style');
|
|
411
1448
|
if (inline) {
|
|
412
1449
|
const declarations = parseDeclarations(inline);
|
|
1450
|
+
if (!this._vars && usesVars(declarations)) this._vars = true;
|
|
1451
|
+
this._noteViewportUnits(declarations);
|
|
1452
|
+
if (!this._lh && usesLh(declarations)) this._lh = true;
|
|
413
1453
|
const normal = declarations.filter((d) => !d.important);
|
|
414
1454
|
const important = declarations.filter((d) => d.important);
|
|
415
1455
|
if (normal.length) {
|
|
416
1456
|
out.push({
|
|
417
1457
|
origin: Origin.Inline,
|
|
1458
|
+
layer: null,
|
|
418
1459
|
specificity: 0,
|
|
419
1460
|
order: 0,
|
|
420
1461
|
declarations: normal,
|
|
@@ -424,6 +1465,7 @@ export class Cascade {
|
|
|
424
1465
|
if (important.length) {
|
|
425
1466
|
out.push({
|
|
426
1467
|
origin: Origin.InlineImportant,
|
|
1468
|
+
layer: null,
|
|
427
1469
|
specificity: 0,
|
|
428
1470
|
order: 0,
|
|
429
1471
|
declarations: important,
|
|
@@ -437,6 +1479,145 @@ export class Cascade {
|
|
|
437
1479
|
}
|
|
438
1480
|
}
|
|
439
1481
|
|
|
1482
|
+
/** An implied element's style as the root box's: its look, not its role —
|
|
1483
|
+
* the root is still a block, in flow, and the initial containing block. */
|
|
1484
|
+
function asRoot(style: ComputedStyle): ComputedStyle {
|
|
1485
|
+
style.display = 'block';
|
|
1486
|
+
style.position = 'static';
|
|
1487
|
+
style.float = 'none';
|
|
1488
|
+
return style;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
/** `:root` is the `<html>` element: the one a browser implies around a
|
|
1492
|
+
* fragment, whose style the root box takes (`rootStyle`), and never a
|
|
1493
|
+
* fragment's top-level elements, which css-select would take for it. */
|
|
1494
|
+
const PSEUDOS = {
|
|
1495
|
+
root: (el: Element) =>
|
|
1496
|
+
el.name === 'html' && !(el.parent && isTag(el.parent as Element)),
|
|
1497
|
+
// css-select's, but its ranges and tags lower-cased as ASCII has it, and
|
|
1498
|
+
// no other script (CSS 2.1 4.1.3): Unicode's took `:lang(\u212Al)`, a
|
|
1499
|
+
// Kelvin sign for the K, for `:lang(kl)`
|
|
1500
|
+
lang: (el: Element, code: string | null): boolean => {
|
|
1501
|
+
const ranges = (code ?? '')
|
|
1502
|
+
.split(',')
|
|
1503
|
+
.map((range) => range.trim().replace(/^['"]|['"]$/g, ''))
|
|
1504
|
+
.filter((range) => range.length > 0)
|
|
1505
|
+
.map((range) => asciiLower(range).split('-'));
|
|
1506
|
+
let root = el;
|
|
1507
|
+
for (let node: Element | null = el; node;) {
|
|
1508
|
+
const value = node.attribs['xml:lang'] ?? node.attribs.lang;
|
|
1509
|
+
if (value != null) {
|
|
1510
|
+
if (!value) return ranges.some((range) => range[0] === '');
|
|
1511
|
+
const tag = asciiLower(value).split('-');
|
|
1512
|
+
return ranges.some((range) => langRangeMatches(tag, range));
|
|
1513
|
+
}
|
|
1514
|
+
root = node;
|
|
1515
|
+
const parent: Element['parent'] = node.parent;
|
|
1516
|
+
node = parent && isTag(parent as Element) ? (parent as Element) : null;
|
|
1517
|
+
}
|
|
1518
|
+
// with no element saying, the document's, as its `<meta>` sets it —
|
|
1519
|
+
// searched from the document, where a fragment's `<meta>` is a
|
|
1520
|
+
// sibling of what it covers
|
|
1521
|
+
const pragma = pragmaLanguage(root.parent ?? root);
|
|
1522
|
+
if (!pragma) return ranges.some((range) => range[0] === '');
|
|
1523
|
+
const tag = asciiLower(pragma).split('-');
|
|
1524
|
+
return ranges.some((range) => langRangeMatches(tag, range));
|
|
1525
|
+
},
|
|
1526
|
+
};
|
|
1527
|
+
|
|
1528
|
+
/** A length in a unit of the viewport: a number, then the unit, and no
|
|
1529
|
+
* more of a name after it. */
|
|
1530
|
+
const VIEWPORT_UNIT = /\d(?:vw|vh|vmin|vmax)(?![\w-])/i;
|
|
1531
|
+
const VIEWPORT_UNITS = /\d(vw|vh|vmin|vmax)(?![\w-])/gi;
|
|
1532
|
+
|
|
1533
|
+
/**
|
|
1534
|
+
* A selector with its `[attr~=""]` made one that matches nothing: an empty
|
|
1535
|
+
* string is no word of a list, and such a selector represents nothing
|
|
1536
|
+
* (Selectors 3, 6.3.1) — which the matcher's `~=` is not told, taking the
|
|
1537
|
+
* empty word for one between two spaces, or at either end of none.
|
|
1538
|
+
*/
|
|
1539
|
+
function noEmptyWords(selector: string): string {
|
|
1540
|
+
if (!selector.includes('~=')) return selector;
|
|
1541
|
+
return selector.replace(
|
|
1542
|
+
/\[[^\]"']*~=\s*(?:""|'')\s*(?:[is]\s*)?\]/gi,
|
|
1543
|
+
':not(*)',
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* A document's language as a `<meta http-equiv="content-language">` sets
|
|
1549
|
+
* it — HTML's pragma-set default language, the last such `<meta>`'s
|
|
1550
|
+
* `content` up to its first white space, and none where it lists more
|
|
1551
|
+
* than one — by the document's root element, found once.
|
|
1552
|
+
*/
|
|
1553
|
+
function pragmaLanguage(root: { children: unknown[] }): string {
|
|
1554
|
+
let lang = PRAGMA_LANGUAGE.get(root);
|
|
1555
|
+
if (lang !== undefined) return lang;
|
|
1556
|
+
lang = '';
|
|
1557
|
+
const stack: { children: unknown[] }[] = [root];
|
|
1558
|
+
while (stack.length) {
|
|
1559
|
+
const node = stack.pop()! as Element;
|
|
1560
|
+
if (
|
|
1561
|
+
isTag(node) &&
|
|
1562
|
+
node.name === 'meta' &&
|
|
1563
|
+
node.attribs['http-equiv']?.trim().toLowerCase() === 'content-language'
|
|
1564
|
+
) {
|
|
1565
|
+
const content = node.attribs.content;
|
|
1566
|
+
if (content !== undefined && !content.includes(',')) {
|
|
1567
|
+
const candidate = content.trim().split(/[\t\n\f\r ]/)[0];
|
|
1568
|
+
if (candidate) lang = candidate;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
// in document order: the last one pushed is taken first
|
|
1572
|
+
for (let i = node.children.length - 1; i >= 0; i -= 1) {
|
|
1573
|
+
const child = node.children[i];
|
|
1574
|
+
if (isTag(child as Element)) stack.push(child as Element);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
PRAGMA_LANGUAGE.set(root, lang);
|
|
1578
|
+
return lang;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
const PRAGMA_LANGUAGE = new WeakMap<object, string>();
|
|
1582
|
+
|
|
1583
|
+
/** RFC 4647's extended filtering, of a tag's subtags by a range's, as
|
|
1584
|
+
* css-select does it. */
|
|
1585
|
+
function langRangeMatches(tag: string[], range: string[]): boolean {
|
|
1586
|
+
if (range[0] !== '*' && range[0] !== tag[0]) return false;
|
|
1587
|
+
let at = 1;
|
|
1588
|
+
for (let r = 1; r < range.length; r += 1) {
|
|
1589
|
+
if (range[r] === '*') continue;
|
|
1590
|
+
while (at < tag.length && tag[at] !== range[r]) {
|
|
1591
|
+
if (tag[at++].length <= 1) return false;
|
|
1592
|
+
}
|
|
1593
|
+
if (at >= tag.length) return false;
|
|
1594
|
+
at += 1;
|
|
1595
|
+
}
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/** Whether any of these declarations sets a custom property or reads one. */
|
|
1600
|
+
function usesVars(declarations: readonly Declaration[]): boolean {
|
|
1601
|
+
for (const d of declarations) if (d.custom || d.vars) return true;
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
/** Whether any of the declarations has a length in `lh` or `rlh`. */
|
|
1606
|
+
function usesLh(declarations: readonly Declaration[]): boolean {
|
|
1607
|
+
for (const d of declarations) if (LH.test(d.value)) return true;
|
|
1608
|
+
return false;
|
|
1609
|
+
}
|
|
1610
|
+
const LH = /\d\.?r?lh\b/i;
|
|
1611
|
+
|
|
1612
|
+
/** Whether a candidate declares `content`. */
|
|
1613
|
+
function setsContent(c: Candidate): boolean {
|
|
1614
|
+
for (const d of pick(c)) if (d.prop === 'content') return true;
|
|
1615
|
+
return false;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/** Whether a rule's declarations set a custom property, by the array. */
|
|
1619
|
+
const CUSTOM_IN = new WeakMap<Declaration[], boolean>();
|
|
1620
|
+
|
|
440
1621
|
function pick(c: Candidate): Declaration[] {
|
|
441
1622
|
return c.only < 0 ? c.declarations : [c.declarations[c.only]];
|
|
442
1623
|
}
|
|
@@ -452,6 +1633,7 @@ function pushRule(out: Candidate[], rule: StyleRule, origin: Origin): void {
|
|
|
452
1633
|
if (!hasImportant) {
|
|
453
1634
|
out.push({
|
|
454
1635
|
origin,
|
|
1636
|
+
layer: rule.layer,
|
|
455
1637
|
specificity: rule.specificity,
|
|
456
1638
|
order: rule.order,
|
|
457
1639
|
declarations: rule.declarations,
|
|
@@ -469,6 +1651,7 @@ function pushRule(out: Candidate[], rule: StyleRule, origin: Origin): void {
|
|
|
469
1651
|
? Origin.UserAgent
|
|
470
1652
|
: Origin.AuthorImportant
|
|
471
1653
|
: origin,
|
|
1654
|
+
layer: rule.layer,
|
|
472
1655
|
specificity: rule.specificity,
|
|
473
1656
|
order: rule.order,
|
|
474
1657
|
declarations: rule.declarations,
|
|
@@ -479,10 +1662,34 @@ function pushRule(out: Candidate[], rule: StyleRule, origin: Origin): void {
|
|
|
479
1662
|
|
|
480
1663
|
function byCascade(a: Candidate, b: Candidate): number {
|
|
481
1664
|
if (a.origin !== b.origin) return a.origin - b.origin;
|
|
1665
|
+
if (a.layer !== b.layer) {
|
|
1666
|
+
// a later layer wins over an earlier one whatever the specificity, and
|
|
1667
|
+
// for `!important` an earlier one does (CSS Cascade 5, 6.4)
|
|
1668
|
+
const by = compareLayers(a.layer, b.layer);
|
|
1669
|
+
if (by !== 0) return a.origin >= Origin.AuthorImportant ? -by : by;
|
|
1670
|
+
}
|
|
482
1671
|
if (a.specificity !== b.specificity) return a.specificity - b.specificity;
|
|
483
1672
|
return a.order - b.order;
|
|
484
1673
|
}
|
|
485
1674
|
|
|
1675
|
+
/**
|
|
1676
|
+
* Which of two layers wins for a normal declaration: a later one over an
|
|
1677
|
+
* earlier one, and a rule in no layer over any, as a rule directly in a
|
|
1678
|
+
* layer is over the layers inside it.
|
|
1679
|
+
*/
|
|
1680
|
+
function compareLayers(
|
|
1681
|
+
a: readonly number[] | null,
|
|
1682
|
+
b: readonly number[] | null,
|
|
1683
|
+
): number {
|
|
1684
|
+
if (a === null) return b === null ? 0 : 1;
|
|
1685
|
+
if (b === null) return -1;
|
|
1686
|
+
const n = Math.min(a.length, b.length);
|
|
1687
|
+
for (let i = 0; i < n; i += 1) {
|
|
1688
|
+
if (a[i] !== b[i]) return a[i] - b[i];
|
|
1689
|
+
}
|
|
1690
|
+
return b.length - a.length;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
486
1693
|
/**
|
|
487
1694
|
* The presentational attributes, as declarations. They sit above the UA
|
|
488
1695
|
* sheet and below every author rule, which is where HTML says they go — and
|
|
@@ -500,16 +1707,79 @@ function presentationHints(el: Element): Declaration[] {
|
|
|
500
1707
|
const align = attr(el, 'align');
|
|
501
1708
|
if (align) {
|
|
502
1709
|
const v = align.toLowerCase();
|
|
503
|
-
|
|
504
|
-
|
|
1710
|
+
const side = v === 'middle' ? 'center' : v;
|
|
1711
|
+
if ((tag === 'img' || tag === 'table') && (v === 'left' || v === 'right')) {
|
|
1712
|
+
push('float', v);
|
|
1713
|
+
} else if (tag === 'img') {
|
|
1714
|
+
// an image's other alignments are its line's
|
|
1715
|
+
const va = IMAGE_ALIGN[v];
|
|
1716
|
+
if (va) push('vertical-align', va);
|
|
1717
|
+
} else if (tag === 'hr') {
|
|
1718
|
+
// a rule narrower than its line stands where it is aligned
|
|
1719
|
+
if (v === 'left') {
|
|
1720
|
+
push('margin-left', '0');
|
|
1721
|
+
push('margin-right', 'auto');
|
|
1722
|
+
} else if (v === 'right') {
|
|
1723
|
+
push('margin-left', 'auto');
|
|
1724
|
+
push('margin-right', '0');
|
|
1725
|
+
}
|
|
1726
|
+
} else if (tag === 'table') {
|
|
1727
|
+
// a table's `align` places the table and leaves its text alone: set
|
|
1728
|
+
// as its text's, `<table align="center">` — the frame of nearly
|
|
1729
|
+
// every mail — stood at the left with its cells' text centred
|
|
1730
|
+
if (side === 'center') {
|
|
1731
|
+
push('margin-left', 'auto');
|
|
1732
|
+
push('margin-right', 'auto');
|
|
1733
|
+
}
|
|
1734
|
+
} else if (
|
|
1735
|
+
ALIGNS_BLOCKS.has(tag) &&
|
|
1736
|
+
(side === 'center' || side === 'left' || side === 'right')
|
|
1737
|
+
) {
|
|
1738
|
+
// these align the blocks in them as well as their text (HTML's
|
|
1739
|
+
// rendering, "align descendants"): the table a `<td align="center">`
|
|
1740
|
+
// or a `<div align="center">` holds is centred in it
|
|
1741
|
+
push('text-align', `-webkit-${side}`);
|
|
1742
|
+
} else if (side === 'center') push('text-align', 'center');
|
|
505
1743
|
else if (v === 'left' || v === 'right' || v === 'justify')
|
|
506
1744
|
push('text-align', v);
|
|
507
1745
|
}
|
|
508
1746
|
const valign = attr(el, 'valign');
|
|
509
1747
|
if (valign) push('vertical-align', valign.toLowerCase());
|
|
1748
|
+
// HTML's `dir` is its element's `direction`: `<table dir="rtl">` starts
|
|
1749
|
+
// at the right, and a Hebrew or an Arabic message is written this way
|
|
1750
|
+
const dir = attr(el, 'dir')?.toLowerCase();
|
|
1751
|
+
if (dir === 'rtl' || dir === 'ltr') push('direction', dir);
|
|
1752
|
+
// and it isolates the element from the text around it, or with `auto`
|
|
1753
|
+
// takes its first strong letter's direction (HTML's UA sheet: `[dir]`,
|
|
1754
|
+
// `[dir=auto i]`, `bdo[dir]`)
|
|
1755
|
+
if (dir !== undefined) {
|
|
1756
|
+
push(
|
|
1757
|
+
'unicode-bidi',
|
|
1758
|
+
tag === 'bdo'
|
|
1759
|
+
? 'isolate-override'
|
|
1760
|
+
: dir === 'auto'
|
|
1761
|
+
? 'plaintext'
|
|
1762
|
+
: 'isolate',
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
510
1765
|
|
|
511
1766
|
const bgcolor = attr(el, 'bgcolor');
|
|
512
1767
|
if (bgcolor) push('background-color', bgcolor);
|
|
1768
|
+
if (BACKGROUNDS.has(tag)) {
|
|
1769
|
+
const background = attr(el, 'background')?.trim();
|
|
1770
|
+
if (background) {
|
|
1771
|
+
push('background-image', `url("${background.replace(/["\\\n]/g, '')}")`);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
if (tag === 'body') {
|
|
1775
|
+
// the text's colour, and the links'
|
|
1776
|
+
const text = attr(el, 'text');
|
|
1777
|
+
if (text) push('color', text);
|
|
1778
|
+
} else if (tag === 'a' && attr(el, 'href') !== undefined) {
|
|
1779
|
+
const body = closestBody(el);
|
|
1780
|
+
const link = body ? attr(body, 'link') : undefined;
|
|
1781
|
+
if (link) push('color', link);
|
|
1782
|
+
}
|
|
513
1783
|
const color = attr(el, 'color');
|
|
514
1784
|
if (color && (tag === 'font' || tag === 'basefont')) push('color', color);
|
|
515
1785
|
const face = attr(el, 'face');
|
|
@@ -529,6 +1799,24 @@ function presentationHints(el: Element): Declaration[] {
|
|
|
529
1799
|
if (width) push('width', lengthAttr(width));
|
|
530
1800
|
const height = attr(el, 'height');
|
|
531
1801
|
if (height) push('height', lengthAttr(height));
|
|
1802
|
+
// and, both numbers, an image's the ratio it has before it loads, or
|
|
1803
|
+
// where it has none of its own (HTML 15.4.3, "map to the aspect-ratio
|
|
1804
|
+
// property")
|
|
1805
|
+
if (width && height && RATIO_SIZED.has(tag)) {
|
|
1806
|
+
const w = parseFloat(width);
|
|
1807
|
+
const h = parseFloat(height);
|
|
1808
|
+
if (w > 0 && h > 0 && !/%/.test(width + height)) {
|
|
1809
|
+
push('aspect-ratio', `auto ${w} / ${h}`);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
} else if (tag === 'svg') {
|
|
1813
|
+
// an SVG's are CSS lengths of their own, units and all (SVG 2, 5.1.1)
|
|
1814
|
+
const width = attr(el, 'width');
|
|
1815
|
+
const w = width ? svgSizeHint(width) : null;
|
|
1816
|
+
if (w) push('width', w);
|
|
1817
|
+
const height = attr(el, 'height');
|
|
1818
|
+
const h = height ? svgSizeHint(height) : null;
|
|
1819
|
+
if (h) push('height', h);
|
|
532
1820
|
}
|
|
533
1821
|
|
|
534
1822
|
if (tag === 'table') {
|
|
@@ -550,13 +1838,50 @@ function presentationHints(el: Element): Declaration[] {
|
|
|
550
1838
|
if (tag === 'hr') {
|
|
551
1839
|
const noshade = attr(el, 'noshade');
|
|
552
1840
|
if (noshade !== undefined) push('border-top-width', '2px');
|
|
1841
|
+
// the rule's colour and its thickness
|
|
1842
|
+
const color = attr(el, 'color');
|
|
1843
|
+
if (color) push('border-top-color', color);
|
|
1844
|
+
const size = attr(el, 'size');
|
|
1845
|
+
if (size && Number(size) > 0) push('border-top-width', `${Number(size)}px`);
|
|
1846
|
+
}
|
|
1847
|
+
if ((tag === 'td' || tag === 'th') && attr(el, 'nowrap') !== undefined) {
|
|
1848
|
+
push('white-space', 'nowrap');
|
|
1849
|
+
}
|
|
1850
|
+
if (tag === 'br') {
|
|
1851
|
+
// below the floats: `<br clear="all">` after a floated image
|
|
1852
|
+
const clear = attr(el, 'clear')?.toLowerCase();
|
|
1853
|
+
if (clear === 'all' || clear === 'both') push('clear', 'both');
|
|
1854
|
+
else if (clear === 'left' || clear === 'right') push('clear', clear);
|
|
553
1855
|
}
|
|
554
1856
|
if (tag === 'ol') {
|
|
555
1857
|
const type = attr(el, 'type');
|
|
556
1858
|
const mapped = OL_TYPES[type ?? ''];
|
|
557
1859
|
if (mapped) push('list-style-type', mapped);
|
|
558
|
-
|
|
559
|
-
|
|
1860
|
+
// the number a list starts at is its `list-item` counter's, one before
|
|
1861
|
+
// it, or counting down one after it (HTML's rendering, "Lists")
|
|
1862
|
+
const start = htmlInteger(attr(el, 'start'));
|
|
1863
|
+
if (start !== null) {
|
|
1864
|
+
push(
|
|
1865
|
+
'counter-reset',
|
|
1866
|
+
attr(el, 'reversed') !== undefined
|
|
1867
|
+
? `reversed(list-item) ${start + 1}`
|
|
1868
|
+
: `list-item ${start - 1}`,
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
if (tag === 'ul') {
|
|
1873
|
+
const type = attr(el, 'type')?.toLowerCase();
|
|
1874
|
+
if (type && BULLETS.has(type)) push('list-style-type', type);
|
|
1875
|
+
}
|
|
1876
|
+
if (tag === 'li') {
|
|
1877
|
+
// an item's `value` is the number it goes on from
|
|
1878
|
+
const value = htmlInteger(attr(el, 'value'));
|
|
1879
|
+
if (value !== null) push('counter-set', `list-item ${value}`);
|
|
1880
|
+
const type = attr(el, 'type');
|
|
1881
|
+
const mapped =
|
|
1882
|
+
OL_TYPES[type ?? ''] ??
|
|
1883
|
+
(type && BULLETS.has(type.toLowerCase()) ? type.toLowerCase() : null);
|
|
1884
|
+
if (mapped) push('list-style-type', mapped);
|
|
560
1885
|
}
|
|
561
1886
|
if (tag === 'img' || tag === 'object') {
|
|
562
1887
|
const hspace = attr(el, 'hspace');
|
|
@@ -573,6 +1898,56 @@ function presentationHints(el: Element): Declaration[] {
|
|
|
573
1898
|
return out;
|
|
574
1899
|
}
|
|
575
1900
|
|
|
1901
|
+
/** An image's `align` other than a side, as its `vertical-align`. */
|
|
1902
|
+
const IMAGE_ALIGN: Record<string, string> = {
|
|
1903
|
+
top: 'top',
|
|
1904
|
+
texttop: 'text-top',
|
|
1905
|
+
middle: 'middle',
|
|
1906
|
+
absmiddle: 'middle',
|
|
1907
|
+
center: 'middle',
|
|
1908
|
+
bottom: 'baseline',
|
|
1909
|
+
baseline: 'baseline',
|
|
1910
|
+
absbottom: 'bottom',
|
|
1911
|
+
};
|
|
1912
|
+
|
|
1913
|
+
/** The elements whose `align` aligns the blocks in them too. */
|
|
1914
|
+
const ALIGNS_BLOCKS = new Set([
|
|
1915
|
+
'div',
|
|
1916
|
+
'caption',
|
|
1917
|
+
'thead',
|
|
1918
|
+
'tbody',
|
|
1919
|
+
'tfoot',
|
|
1920
|
+
'tr',
|
|
1921
|
+
'td',
|
|
1922
|
+
'th',
|
|
1923
|
+
]);
|
|
1924
|
+
|
|
1925
|
+
/** The elements a `background` attribute gives a background image. */
|
|
1926
|
+
const BACKGROUNDS = new Set([
|
|
1927
|
+
'body',
|
|
1928
|
+
'table',
|
|
1929
|
+
'thead',
|
|
1930
|
+
'tbody',
|
|
1931
|
+
'tfoot',
|
|
1932
|
+
'tr',
|
|
1933
|
+
'td',
|
|
1934
|
+
'th',
|
|
1935
|
+
]);
|
|
1936
|
+
|
|
1937
|
+
function closestBody(el: Element): Element | null {
|
|
1938
|
+
let node = el.parent;
|
|
1939
|
+
while (node) {
|
|
1940
|
+
if (node.type === 'tag' && (node as Element).name === 'body') {
|
|
1941
|
+
return node as Element;
|
|
1942
|
+
}
|
|
1943
|
+
node = node.parent;
|
|
1944
|
+
}
|
|
1945
|
+
return null;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
/** What the `width` and `height` attributes give a ratio as well. */
|
|
1949
|
+
const RATIO_SIZED = new Set(['img', 'video']);
|
|
1950
|
+
|
|
576
1951
|
const SIZED = new Set([
|
|
577
1952
|
'img',
|
|
578
1953
|
'table',
|
|
@@ -582,7 +1957,6 @@ const SIZED = new Set([
|
|
|
582
1957
|
'colgroup',
|
|
583
1958
|
'iframe',
|
|
584
1959
|
'video',
|
|
585
|
-
'canvas',
|
|
586
1960
|
'object',
|
|
587
1961
|
'embed',
|
|
588
1962
|
'hr',
|
|
@@ -596,6 +1970,18 @@ const OL_TYPES: Record<string, string> = {
|
|
|
596
1970
|
I: 'upper-roman',
|
|
597
1971
|
};
|
|
598
1972
|
|
|
1973
|
+
/** What a `<ul type>` or an `<li type>` names that is not a number. */
|
|
1974
|
+
const BULLETS = new Set(['disc', 'circle', 'square', 'none']);
|
|
1975
|
+
|
|
1976
|
+
/** An attribute read as HTML reads an integer: its leading digits, signed,
|
|
1977
|
+
* whatever follows them; null where it has none. A counter is a 32-bit
|
|
1978
|
+
* integer in a browser, and one past that is kept to it. */
|
|
1979
|
+
function htmlInteger(value: string | undefined): number | null {
|
|
1980
|
+
const match = value === undefined ? null : /^\s*([+-]?\d+)/.exec(value);
|
|
1981
|
+
if (!match) return null;
|
|
1982
|
+
return Math.max(-2147483648, Math.min(2147483647, Number(match[1])));
|
|
1983
|
+
}
|
|
1984
|
+
|
|
599
1985
|
const FONT_SIZE_STEPS = [1, 0.63, 0.82, 1, 1.13, 1.5, 2, 3];
|
|
600
1986
|
|
|
601
1987
|
/** A presentational length: bare numbers are pixels, `50%` stays a percent. */
|
|
@@ -615,3 +2001,36 @@ function closestTable(el: Element): Element | null {
|
|
|
615
2001
|
}
|
|
616
2002
|
return null;
|
|
617
2003
|
}
|
|
2004
|
+
|
|
2005
|
+
/** The generic fixed-width family's size to everyone else's: 13 to 16. */
|
|
2006
|
+
const FIXED_SIZE = 13 / 16;
|
|
2007
|
+
|
|
2008
|
+
/** Whether a family list is the generic `monospace` and nothing else,
|
|
2009
|
+
* which has the smaller size; a list with a fallback after it does not,
|
|
2010
|
+
* so `monospace, monospace` keeps the size it had. */
|
|
2011
|
+
function monospaceOnly(family: string): boolean {
|
|
2012
|
+
return family.length === 9 && family.toLowerCase() === 'monospace';
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/** Whether a `font-size`, or the size in a `font`, is an absolute-size
|
|
2016
|
+
* keyword, or `initial`, which is `medium`. */
|
|
2017
|
+
function sizeKeyword(prop: string, value: string): boolean {
|
|
2018
|
+
const words = value.trim().toLowerCase();
|
|
2019
|
+
if (prop === 'font-size') return ABSOLUTE_SIZES.has(words);
|
|
2020
|
+
for (const word of words.split(/[\s/]+/)) {
|
|
2021
|
+
if (ABSOLUTE_SIZES.has(word)) return true;
|
|
2022
|
+
}
|
|
2023
|
+
return false;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
const ABSOLUTE_SIZES = new Set([
|
|
2027
|
+
'xx-small',
|
|
2028
|
+
'x-small',
|
|
2029
|
+
'small',
|
|
2030
|
+
'medium',
|
|
2031
|
+
'large',
|
|
2032
|
+
'x-large',
|
|
2033
|
+
'xx-large',
|
|
2034
|
+
'xxx-large',
|
|
2035
|
+
'initial',
|
|
2036
|
+
]);
|