@jxsuite/runtime 3.0.0 → 3.1.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/dist/runtime.js +35 -33
- package/dist/runtime.js.map +8 -7
- package/package.json +5 -4
- package/src/css.ts +119 -0
- package/src/runtime.ts +13 -99
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "JSON-native reactive web component runtime",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": "./src/runtime.ts",
|
|
18
|
+
"./css": "./src/css.ts",
|
|
18
19
|
"./expression": "./src/expression.ts",
|
|
19
20
|
"./pointer": "./src/pointer.ts",
|
|
20
21
|
"./statements": "./src/statements.ts",
|
|
@@ -32,10 +33,10 @@
|
|
|
32
33
|
"upgrade": "bun update --latest && bun install"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
|
-
"@jxsuite/schema": "^2.
|
|
36
|
-
"@vue/reactivity": "3.5.
|
|
36
|
+
"@jxsuite/schema": "^2.1.0",
|
|
37
|
+
"@vue/reactivity": "3.5.42"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@happy-dom/global-registrator": "^20.
|
|
40
|
+
"@happy-dom/global-registrator": "^20.12.0"
|
|
40
41
|
}
|
|
41
42
|
}
|
package/src/css.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CSS-authoring rules that are not the DOM.
|
|
3
|
+
*
|
|
4
|
+
* `@jxsuite/runtime` renders a document in a browser, and every consumer of its root export pays
|
|
5
|
+
* for that: the element registry, the reactive scope, `@vue/reactivity`. These six exports need
|
|
6
|
+
* none of it. They are pure string and regex math over what a `style` block MEANS — how a property
|
|
7
|
+
* name is spelled in CSS, which media query an `@`-key resolves to, and which selector pair a
|
|
8
|
+
* scheme-conditional rule dual-emits as.
|
|
9
|
+
*
|
|
10
|
+
* They are a subpath of their own because of who else needs them. `@jxsuite/site/site-style` turns
|
|
11
|
+
* `project.json`'s `style` into a stylesheet, and it runs in places the DOM runtime cannot go — a
|
|
12
|
+
* static build, and a Cloudflare Worker serving a live preview. Importing the root export for
|
|
13
|
+
* `camelToKebab` would put the whole renderer plus `@vue/reactivity` inside a Worker script for
|
|
14
|
+
* four pure functions.
|
|
15
|
+
*
|
|
16
|
+
* `./runtime` re-exports all of these, so nothing that already imported them from the root export
|
|
17
|
+
* has to change. This is a narrow door beside the wide one, not a replacement for it.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert camelCase to kebab-case.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} s
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function camelToKebab(s: string) {
|
|
27
|
+
return s.replaceAll(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── Style At-Rules ───────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The four CSS media types. A media _type_ is bare — `@media print` — while a media _feature_ is
|
|
34
|
+
* parenthesised, so `@media (print)` reads as a boolean feature named `print`, which does not exist
|
|
35
|
+
* and evaluates false.
|
|
36
|
+
*/
|
|
37
|
+
const MEDIA_TYPES = new Set(["all", "print", "screen", "speech"]);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The media query an `@`-prefixed style key names, or null when the key is some other at-rule
|
|
41
|
+
* (`@supports`, `@starting-style`) that is emitted verbatim.
|
|
42
|
+
*
|
|
43
|
+
* `@--name` resolves through the project's `$media` map. `@(…)` carries its own parentheses, so
|
|
44
|
+
* they are stripped only when what they wrap is a bare media type: `@(print)` must become `@media
|
|
45
|
+
* print`, while `@(min-width: 40rem)` keeps them.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} atKey
|
|
48
|
+
* @param {Record<string, string>} mediaQueries
|
|
49
|
+
* @returns {string | null}
|
|
50
|
+
* @docs framework/concepts/styling
|
|
51
|
+
*/
|
|
52
|
+
export function resolveAtQuery(atKey: string, mediaQueries: Record<string, string>): string | null {
|
|
53
|
+
if (atKey.startsWith("@--")) {
|
|
54
|
+
return mediaQueries[atKey.slice(1)] ?? atKey.slice(1);
|
|
55
|
+
}
|
|
56
|
+
if (atKey.startsWith("@(")) {
|
|
57
|
+
const inner = atKey.slice(2, -1).trim();
|
|
58
|
+
return MEDIA_TYPES.has(inner.toLowerCase()) ? inner : atKey.slice(1);
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Color Schemes ────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Attribute on the root element (`<html>`) that forces a color scheme. Absent = auto (follow the OS
|
|
67
|
+
* `prefers-color-scheme`). Values: "light" | "dark".
|
|
68
|
+
*
|
|
69
|
+
* @docs framework/concepts/color-schemes
|
|
70
|
+
*/
|
|
71
|
+
export const COLOR_SCHEME_ATTR = "data-color-scheme";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The localStorage key a site switcher persists the visitor's forced scheme under; read by the
|
|
75
|
+
* compiler-injected pre-paint script. Values: "light" | "dark" (absent = auto).
|
|
76
|
+
*
|
|
77
|
+
* @docs framework/concepts/color-schemes
|
|
78
|
+
*/
|
|
79
|
+
export const COLOR_SCHEME_STORAGE_KEY = "jx-color-scheme";
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The scheme a _pure_ `prefers-color-scheme` media query targets. Compound queries (any other
|
|
83
|
+
* conditions attached) return null — they are not eligible for forced-scheme dual emission.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} query
|
|
86
|
+
* @returns {"light" | "dark" | null}
|
|
87
|
+
* @docs framework/concepts/color-schemes
|
|
88
|
+
*/
|
|
89
|
+
export function pureSchemeOf(query: string): "light" | "dark" | null {
|
|
90
|
+
const m = /^\(\s*prefers-color-scheme\s*:\s*(light|dark)\s*\)$/.exec(query.trim());
|
|
91
|
+
return (m?.[1] as "light" | "dark") ?? null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Selector pair for a scheme-conditional rule: `auto` applies inside the scheme's media query only
|
|
96
|
+
* while no scheme is forced; `forced` applies unconditionally when the root attribute forces the
|
|
97
|
+
* scheme. Guards are wrapped in `:where()` so specificity matches the unguarded selector and source
|
|
98
|
+
* order decides the cascade.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} selector
|
|
101
|
+
* @param {"light" | "dark"} scheme
|
|
102
|
+
* @returns {{ auto: string; forced: string }}
|
|
103
|
+
* @docs framework/concepts/color-schemes
|
|
104
|
+
*/
|
|
105
|
+
export function schemeSelectors(
|
|
106
|
+
selector: string,
|
|
107
|
+
scheme: "light" | "dark",
|
|
108
|
+
): { auto: string; forced: string } {
|
|
109
|
+
if (selector === ":root" || selector === "html") {
|
|
110
|
+
return {
|
|
111
|
+
auto: `${selector}:where(:not([${COLOR_SCHEME_ATTR}]))`,
|
|
112
|
+
forced: `${selector}:where([${COLOR_SCHEME_ATTR}="${scheme}"])`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
auto: `:where(:root:not([${COLOR_SCHEME_ATTR}])) ${selector}`,
|
|
117
|
+
forced: `:where(:root[${COLOR_SCHEME_ATTR}="${scheme}"]) ${selector}`,
|
|
118
|
+
};
|
|
119
|
+
}
|
package/src/runtime.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
ref,
|
|
24
24
|
toRaw,
|
|
25
25
|
} from "@vue/reactivity";
|
|
26
|
+
import { camelToKebab, pureSchemeOf, resolveAtQuery, schemeSelectors } from "./css.ts";
|
|
26
27
|
import { evaluateExpression, evaluateOperand, isMutating } from "./expression.ts";
|
|
27
28
|
import { readPath } from "./pointer.ts";
|
|
28
29
|
import type { DynamicClass, JxEventHandler, JxPath, JxRenderOptions, JxScope } from "./types.ts";
|
|
@@ -2559,15 +2560,19 @@ function mergeProps(def: JxElement, parentState: JxScope): JxScope {
|
|
|
2559
2560
|
return child;
|
|
2560
2561
|
}
|
|
2561
2562
|
|
|
2562
|
-
|
|
2563
|
-
*
|
|
2564
|
-
*
|
|
2565
|
-
*
|
|
2566
|
-
* @returns {string}
|
|
2563
|
+
/*
|
|
2564
|
+
* The CSS-authoring rules live in `./css.ts`, which imports nothing at all. They are re-exported
|
|
2565
|
+
* here because they always were part of this module's surface and moving them must not break a
|
|
2566
|
+
* consumer; a host that cannot afford the DOM runtime imports `@jxsuite/runtime/css` instead.
|
|
2567
2567
|
*/
|
|
2568
|
-
export
|
|
2569
|
-
|
|
2570
|
-
|
|
2568
|
+
export {
|
|
2569
|
+
camelToKebab,
|
|
2570
|
+
COLOR_SCHEME_ATTR,
|
|
2571
|
+
COLOR_SCHEME_STORAGE_KEY,
|
|
2572
|
+
pureSchemeOf,
|
|
2573
|
+
resolveAtQuery,
|
|
2574
|
+
schemeSelectors,
|
|
2575
|
+
} from "./css.ts";
|
|
2571
2576
|
|
|
2572
2577
|
/**
|
|
2573
2578
|
* Convert a style rules object to a CSS text string (skipping nested selectors).
|
|
@@ -2582,97 +2587,6 @@ export function toCSSText(rules: Record<string, unknown> | object) {
|
|
|
2582
2587
|
.join("; ");
|
|
2583
2588
|
}
|
|
2584
2589
|
|
|
2585
|
-
// ─── Style At-Rules ───────────────────────────────────────────────────────────
|
|
2586
|
-
|
|
2587
|
-
/**
|
|
2588
|
-
* The four CSS media types. A media _type_ is bare — `@media print` — while a media _feature_ is
|
|
2589
|
-
* parenthesised, so `@media (print)` reads as a boolean feature named `print`, which does not exist
|
|
2590
|
-
* and evaluates false.
|
|
2591
|
-
*/
|
|
2592
|
-
const MEDIA_TYPES = new Set(["all", "print", "screen", "speech"]);
|
|
2593
|
-
|
|
2594
|
-
/**
|
|
2595
|
-
* The media query an `@`-prefixed style key names, or null when the key is some other at-rule
|
|
2596
|
-
* (`@supports`, `@starting-style`) that is emitted verbatim.
|
|
2597
|
-
*
|
|
2598
|
-
* `@--name` resolves through the project's `$media` map. `@(…)` carries its own parentheses, so
|
|
2599
|
-
* they are stripped only when what they wrap is a bare media type: `@(print)` must become `@media
|
|
2600
|
-
* print`, while `@(min-width: 40rem)` keeps them.
|
|
2601
|
-
*
|
|
2602
|
-
* @param {string} atKey
|
|
2603
|
-
* @param {Record<string, string>} mediaQueries
|
|
2604
|
-
* @returns {string | null}
|
|
2605
|
-
* @docs framework/concepts/styling
|
|
2606
|
-
*/
|
|
2607
|
-
export function resolveAtQuery(atKey: string, mediaQueries: Record<string, string>): string | null {
|
|
2608
|
-
if (atKey.startsWith("@--")) {
|
|
2609
|
-
return mediaQueries[atKey.slice(1)] ?? atKey.slice(1);
|
|
2610
|
-
}
|
|
2611
|
-
if (atKey.startsWith("@(")) {
|
|
2612
|
-
const inner = atKey.slice(2, -1).trim();
|
|
2613
|
-
return MEDIA_TYPES.has(inner.toLowerCase()) ? inner : atKey.slice(1);
|
|
2614
|
-
}
|
|
2615
|
-
return null;
|
|
2616
|
-
}
|
|
2617
|
-
|
|
2618
|
-
// ─── Color Schemes ────────────────────────────────────────────────────────────
|
|
2619
|
-
|
|
2620
|
-
/**
|
|
2621
|
-
* Attribute on the root element (`<html>`) that forces a color scheme. Absent = auto (follow the OS
|
|
2622
|
-
* `prefers-color-scheme`). Values: "light" | "dark".
|
|
2623
|
-
*
|
|
2624
|
-
* @docs framework/concepts/color-schemes
|
|
2625
|
-
*/
|
|
2626
|
-
export const COLOR_SCHEME_ATTR = "data-color-scheme";
|
|
2627
|
-
|
|
2628
|
-
/**
|
|
2629
|
-
* The localStorage key a site switcher persists the visitor's forced scheme under; read by the
|
|
2630
|
-
* compiler-injected pre-paint script. Values: "light" | "dark" (absent = auto).
|
|
2631
|
-
*
|
|
2632
|
-
* @docs framework/concepts/color-schemes
|
|
2633
|
-
*/
|
|
2634
|
-
export const COLOR_SCHEME_STORAGE_KEY = "jx-color-scheme";
|
|
2635
|
-
|
|
2636
|
-
/**
|
|
2637
|
-
* The scheme a _pure_ `prefers-color-scheme` media query targets. Compound queries (any other
|
|
2638
|
-
* conditions attached) return null — they are not eligible for forced-scheme dual emission.
|
|
2639
|
-
*
|
|
2640
|
-
* @param {string} query
|
|
2641
|
-
* @returns {"light" | "dark" | null}
|
|
2642
|
-
* @docs framework/concepts/color-schemes
|
|
2643
|
-
*/
|
|
2644
|
-
export function pureSchemeOf(query: string): "light" | "dark" | null {
|
|
2645
|
-
const m = /^\(\s*prefers-color-scheme\s*:\s*(light|dark)\s*\)$/.exec(query.trim());
|
|
2646
|
-
return (m?.[1] as "light" | "dark") ?? null;
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
/**
|
|
2650
|
-
* Selector pair for a scheme-conditional rule: `auto` applies inside the scheme's media query only
|
|
2651
|
-
* while no scheme is forced; `forced` applies unconditionally when the root attribute forces the
|
|
2652
|
-
* scheme. Guards are wrapped in `:where()` so specificity matches the unguarded selector and source
|
|
2653
|
-
* order decides the cascade.
|
|
2654
|
-
*
|
|
2655
|
-
* @param {string} selector
|
|
2656
|
-
* @param {"light" | "dark"} scheme
|
|
2657
|
-
* @returns {{ auto: string; forced: string }}
|
|
2658
|
-
* @docs framework/concepts/color-schemes
|
|
2659
|
-
*/
|
|
2660
|
-
export function schemeSelectors(
|
|
2661
|
-
selector: string,
|
|
2662
|
-
scheme: "light" | "dark",
|
|
2663
|
-
): { auto: string; forced: string } {
|
|
2664
|
-
if (selector === ":root" || selector === "html") {
|
|
2665
|
-
return {
|
|
2666
|
-
auto: `${selector}:where(:not([${COLOR_SCHEME_ATTR}]))`,
|
|
2667
|
-
forced: `${selector}:where([${COLOR_SCHEME_ATTR}="${scheme}"])`,
|
|
2668
|
-
};
|
|
2669
|
-
}
|
|
2670
|
-
return {
|
|
2671
|
-
auto: `:where(:root:not([${COLOR_SCHEME_ATTR}])) ${selector}`,
|
|
2672
|
-
forced: `:where(:root[${COLOR_SCHEME_ATTR}="${scheme}"]) ${selector}`,
|
|
2673
|
-
};
|
|
2674
|
-
}
|
|
2675
|
-
|
|
2676
2590
|
// ─── Custom Element Registration ──────────────────────────────────────────────
|
|
2677
2591
|
|
|
2678
2592
|
let _rootMedia: Record<string, string> = {};
|