@askrjs/askr 0.3.0 → 0.4.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/CHANGELOG.md +874 -0
- package/README.md +61 -29
- package/capabilities.json +19 -9
- package/dist/actions/index.js +56 -12
- package/dist/benchmark.js +10 -15
- package/dist/boot/index.d.ts +3 -2
- package/dist/boot/index.js +1 -1
- package/dist/boot-DaoTYoCw.js +3989 -0
- package/dist/browser-activity-CB8e-V4f.js +220 -0
- package/dist/components/index.d.ts +8 -1
- package/dist/components/index.js +1 -43
- package/dist/{aria-hKjThHcp.js → compose-ref-DWzdbxsD.js} +41 -4
- package/dist/control/index.d.ts +2 -0
- package/dist/control/index.js +1 -1
- package/dist/control-nfpT2fbK.js +209 -0
- package/dist/core.d.ts +5 -9
- package/dist/csp-nonce-DwGw1A6Q.js +35 -0
- package/dist/data/index.js +3 -3
- package/dist/{data-2edW0jhF.js → data-BCwClJiF.js} +283 -94
- package/dist/data-runtime-C1bFAlba.js +242 -0
- package/dist/{deferred-CbgNhf67.js → deferred-bEEHHvic.js} +42 -20
- package/dist/domains/actions.d.ts +8 -3
- package/dist/domains/component.d.ts +1 -107
- package/dist/domains/context.d.ts +0 -15
- package/dist/domains/control.d.ts +36 -249
- package/dist/domains/data.d.ts +31 -13
- package/dist/domains/diagnostics.d.ts +1 -2
- package/dist/domains/lifecycle.d.ts +2 -5
- package/dist/domains/refs.d.ts +0 -2
- package/dist/domains/routing.d.ts +43 -8
- package/dist/domains/scheduler.d.ts +1 -76
- package/dist/domains/server.d.ts +5 -2
- package/dist/domains/state.d.ts +2 -5
- package/dist/domains/telemetry.d.ts +0 -2
- package/dist/elements.d.ts +214 -51
- package/dist/error-boundary-Bpwofwps.js +293 -0
- package/dist/foundations/icon/index.d.ts +0 -58
- package/dist/foundations/icon/index.js +2 -2
- package/dist/foundations/index.js +2 -1
- package/dist/foundations/interactions/index.js +1 -1
- package/dist/foundations/state/index.d.ts +3 -3
- package/dist/foundations/state/index.js +5 -3
- package/dist/foundations/structures/index.js +2 -1
- package/dist/foundations/utilities/index.d.ts +2 -0
- package/dist/foundations/utilities/index.js +1 -2
- package/dist/fx/index.d.ts +31 -58
- package/dist/fx/index.js +313 -195
- package/dist/hooks-BimKStr4.js +202 -0
- package/dist/index.d.ts +11 -74
- package/dist/index.js +8 -447
- package/dist/instance-BAnfmXOR.js +988 -0
- package/dist/{interactions-CTeACdT_.js → interactions-D2BoREb-.js} +3 -2
- package/dist/jsx-dev-runtime.d.ts +41 -18
- package/dist/jsx-dev-runtime.js +1 -6
- package/dist/{jsx-runtime-CPJXAOBF.js → jsx-runtime-C4idqHY0.js} +18 -7
- package/dist/jsx-runtime.js +1 -1
- package/dist/jsx.d.ts +61 -37
- package/dist/{manifest-D1P3UNWV.js → manifest-CW9gyNBY.js} +3 -3
- package/dist/{navigate-prC5V8XV.js → navigate-CP_0Zy5U.js} +454 -87
- package/dist/navigation.d.ts +12 -3
- package/dist/peer-types.d.ts +8 -0
- package/dist/portal-DuOKfnLa.js +349 -0
- package/dist/promise-Dz8iIHAm.js +16 -0
- package/dist/query-registry-WMCN0BJL.js +320 -0
- package/dist/readable-DFWOsTac.js +21 -0
- package/dist/{render-context-B7ohlh6t.js → render-context-DGHnEveC.js} +27 -11
- package/dist/render-sync-IC5lMFpq.js +1240 -0
- package/dist/resolution-Cx82-xjW.js +1244 -0
- package/dist/resource-CLjB7RBQ.js +377 -0
- package/dist/resources/index.d.ts +21 -11
- package/dist/resources/index.js +45 -244
- package/dist/router/index.d.ts +14 -5
- package/dist/router/index.js +22 -53
- package/dist/scope-CwpVSHVf.js +55 -0
- package/dist/{shared-kU8yvryp.js → shared-O2ZnmYAf.js} +1 -7
- package/dist/{snapshot-source-An4iKajG.js → snapshot-Cjx4N988.js} +6 -1
- package/dist/ssg/index.d.ts +1 -2
- package/dist/ssg/index.js +5 -13
- package/dist/ssr/index.d.ts +26 -28
- package/dist/ssr/index.js +4 -4
- package/dist/{ssr-CiNm6Vzl.js → ssr-BJ8ug7zH.js} +133 -122
- package/dist/ssr-DoV-vJ-5.js +229 -0
- package/dist/state-DWO1KzkX.js +232 -0
- package/dist/structures-D4dDMqHX.js +231 -0
- package/dist/testing/index.d.ts +8 -4
- package/dist/testing/index.js +29 -34
- package/dist/url-CnbfpX0K.js +185 -0
- package/dist/utilities.d.ts +48 -2
- package/dist/verify-hydration-DpVaDnqz.js +152 -0
- package/package.json +19 -9
- package/dist/access-CEA2pOry.js +0 -1046
- package/dist/activity-oy2YFTFF.js +0 -201
- package/dist/boot-BMBMWUGI.js +0 -1084
- package/dist/capabilities-Bbf0mlp9.js +0 -42
- package/dist/compose-ref-v0qGlHoN.js +0 -26
- package/dist/control-BR_mrX__.js +0 -173
- package/dist/control-C-ApnFs1.js +0 -20
- package/dist/csp-nonce-BfFWcd0w.js +0 -173
- package/dist/data-runtime-CxoY98lX.js +0 -149
- package/dist/default-portal-runtime-DvMvWJVp.js +0 -20
- package/dist/dom-renderer.d.ts +0 -77
- package/dist/domains/renderer.d.ts +0 -127
- package/dist/eager-control.d.ts +0 -8
- package/dist/effect-C3Zsc1k6.js +0 -292
- package/dist/for-state-BDykeOJL.js +0 -1753
- package/dist/instance-VAFYVJOa.js +0 -1027
- package/dist/notify-DxxuDPeY.js +0 -266
- package/dist/portal-w3oQ6oQU.js +0 -29
- package/dist/query-registry-LrYpQsDJ.js +0 -83
- package/dist/render-resolved-BC0W7_5b.js +0 -1123
- package/dist/renderer-CT0HeY58.js +0 -8623
- package/dist/resolution-3DskUkjG.js +0 -577
- package/dist/resource-18-sFRl9.js +0 -344
- package/dist/route-matching-CUzZ31gT.js +0 -551
- package/dist/router-internal-7nuNEHfG.js +0 -36
- package/dist/scope-Cq_iYbTR.js +0 -403
- package/dist/selector-BSPVkjKP.js +0 -311
- package/dist/ssr-EPjwJH5R.js +0 -29
- package/dist/state-BXJfudq1.js +0 -91
- package/dist/structures-CjEzfj4h.js +0 -654
- package/dist/timer-C7uqnJVt.js +0 -85
- package/dist/verify-hydration-ikaxotf7.js +0 -27
- package/dist/vnode-D24p6IW2.js +0 -7
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { n as isPromiseLike } from "./promise-Dz8iIHAm.js";
|
|
2
|
+
import { d as isCustomElementName } from "./url-CnbfpX0K.js";
|
|
3
|
+
//#region src/common/prop-classification.ts
|
|
4
|
+
/**
|
|
5
|
+
* The prop vocabulary shared by the DOM renderer and the SSR renderer.
|
|
6
|
+
*
|
|
7
|
+
* These questions — is this prop framework-internal, is it an ARIA attribute,
|
|
8
|
+
* how is a style property named, does a `true` value render bare — were
|
|
9
|
+
* previously answered separately on each side, and the answers had drifted.
|
|
10
|
+
* Both renderers must agree for hydration to match, so the answers live here.
|
|
11
|
+
*/
|
|
12
|
+
/** Props that are never rendered as attributes by either renderer. */
|
|
13
|
+
function isSkippedProp(key) {
|
|
14
|
+
return key === "children" || key === "imperativeChildren" || key === "key" || key === "ref";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* ARIA state attributes take the strings `"true"`/`"false"`, so unlike HTML
|
|
18
|
+
* boolean attributes a `false` value is meaningful and must still be rendered.
|
|
19
|
+
*/
|
|
20
|
+
function isAriaAttribute(key) {
|
|
21
|
+
return key.length > 5 && key.slice(0, 5).toLowerCase() === "aria-";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Enumerated attributes whose `"false"` state differs from being absent: an
|
|
25
|
+
* `<img>` or link is draggable by default, and `spellcheck`/`contenteditable`
|
|
26
|
+
* inherit when missing. `false` must render the literal string for these.
|
|
27
|
+
*/
|
|
28
|
+
const ENUMERATED_FALSE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
29
|
+
"contenteditable",
|
|
30
|
+
"draggable",
|
|
31
|
+
"spellcheck",
|
|
32
|
+
"writingsuggestions"
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* Whether a `false` prop renders as the string `"false"` rather than removing
|
|
36
|
+
* the attribute: true for ARIA state and the enumerated attributes above.
|
|
37
|
+
* Accepts either the JSX prop name or the rendered attribute name.
|
|
38
|
+
*/
|
|
39
|
+
function keepsFalseValue(key) {
|
|
40
|
+
return isAriaAttribute(key) || ENUMERATED_FALSE_ATTRIBUTES.has(key.toLowerCase());
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* CSS custom properties are case-sensitive and must survive verbatim; every
|
|
44
|
+
* other style property is camelCase in JSX and kebab-case in CSS.
|
|
45
|
+
*/
|
|
46
|
+
function normalizeStylePropertyName(propertyName) {
|
|
47
|
+
if (propertyName.startsWith("--")) return propertyName;
|
|
48
|
+
const kebab = propertyName.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
|
|
49
|
+
return kebab.startsWith("ms-") ? `-${kebab}` : kebab;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* CSS properties (kebab-case, vendor prefix stripped) that accept a bare
|
|
53
|
+
* number. This follows React's unitless list, plus `font-size-adjust`,
|
|
54
|
+
* `initial-letter` and `math-depth`. Numeric values for every other property get a `px` unit, matching
|
|
55
|
+
* the conventional JSX style contract.
|
|
56
|
+
*/
|
|
57
|
+
const UNITLESS_STYLE_PROPERTIES = new Set("animation-iteration-count aspect-ratio border-image-outset border-image-slice border-image-width box-flex box-flex-group box-ordinal-group column-count columns fill-opacity flex flex-grow flex-negative flex-order flex-positive flex-shrink flood-opacity font-size-adjust font-weight grid-area grid-column grid-column-end grid-column-span grid-column-start grid-row grid-row-end grid-row-span grid-row-start initial-letter line-clamp line-height mask-border-outset mask-border-slice mask-border-width math-depth opacity order orphans scale shape-image-threshold stop-opacity stroke-dasharray stroke-dashoffset stroke-miterlimit stroke-opacity stroke-width tab-size widows z-index zoom".split(" "));
|
|
58
|
+
const VENDOR_PREFIX_RE = /^-(?:webkit|moz|ms|o)-/;
|
|
59
|
+
/**
|
|
60
|
+
* The CSS text for one style entry, given its normalized property name.
|
|
61
|
+
*
|
|
62
|
+
* A non-zero number on a dimensional property gets `px` (`width: 10` is
|
|
63
|
+
* `10px`); custom properties and unitless properties keep the bare number.
|
|
64
|
+
*/
|
|
65
|
+
function styleValueText(propertyName, value) {
|
|
66
|
+
if (typeof value !== "number" || value === 0 || !Number.isFinite(value) || propertyName.startsWith("--") || UNITLESS_STYLE_PROPERTIES.has(propertyName.replace(VENDOR_PREFIX_RE, ""))) return String(value);
|
|
67
|
+
return `${value}px`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* HTML boolean content attributes: present means true, and the value is
|
|
71
|
+
* ignored. A `true` prop renders these bare on both sides. Anything else —
|
|
72
|
+
* `aria-*`, `data-*`, arbitrary attributes — renders the literal `"true"`,
|
|
73
|
+
* because for those the value carries the meaning.
|
|
74
|
+
*/
|
|
75
|
+
const BOOLEAN_HTML_ATTRIBUTES = new Set("allowfullscreen async autofocus autoplay checked controls default defer disabled formnovalidate inert ismap itemscope loop multiple muted nomodule novalidate open playsinline readonly required reversed selected".split(" "));
|
|
76
|
+
/**
|
|
77
|
+
* The attribute text for a prop value that is exactly `true`.
|
|
78
|
+
*
|
|
79
|
+
* Boolean attributes render bare (an empty value); everything else renders
|
|
80
|
+
* `"true"` so ARIA state and `data-` payloads survive a round trip.
|
|
81
|
+
*/
|
|
82
|
+
function booleanAttributeValue(attributeName) {
|
|
83
|
+
return BOOLEAN_HTML_ATTRIBUTES.has(attributeName.toLowerCase()) ? "" : "true";
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/common/dom-properties.ts
|
|
87
|
+
/**
|
|
88
|
+
* Which JSX props are written to DOM properties instead of attributes.
|
|
89
|
+
*
|
|
90
|
+
* Attributes are the default because SSR can serialize them and hydration can
|
|
91
|
+
* compare them. Some element state only lives in a property, though: the
|
|
92
|
+
* `muted` attribute is only the initial value of `media.muted`, `indeterminate`
|
|
93
|
+
* has no attribute at all, and custom elements take objects and arrays as
|
|
94
|
+
* properties. Both renderers classify props here so the DOM renderer writes
|
|
95
|
+
* the property and SSR leaves out whatever it cannot serialize.
|
|
96
|
+
*
|
|
97
|
+
* - `prop:name` always assigns `el.name`, verbatim, and never renders.
|
|
98
|
+
* - `attr:name` always renders the `name` attribute (see attr-names.ts).
|
|
99
|
+
*/
|
|
100
|
+
const PROPERTY_PROP_PREFIX = "prop:";
|
|
101
|
+
const ATTRIBUTE_PROP_PREFIX = "attr:";
|
|
102
|
+
/**
|
|
103
|
+
* Known properties the attribute does not keep in sync, on the elements that
|
|
104
|
+
* have them. Their value is coerced to a boolean and an absent prop resets
|
|
105
|
+
* them to `false`. `muted` also keeps its attribute, which SSR renders and
|
|
106
|
+
* which sets the initial state on parse; `indeterminate` has no attribute and
|
|
107
|
+
* never renders.
|
|
108
|
+
*/
|
|
109
|
+
function isKnownPropertyOf(tagName, key) {
|
|
110
|
+
if (key === "muted") {
|
|
111
|
+
const tag = tagName.toLowerCase();
|
|
112
|
+
return tag === "video" || tag === "audio";
|
|
113
|
+
}
|
|
114
|
+
return key === "indeterminate" && tagName.toLowerCase() === "input";
|
|
115
|
+
}
|
|
116
|
+
function isKnownBooleanProperty(key) {
|
|
117
|
+
return key === "muted" || key === "indeterminate";
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The DOM property a prop is written to, or `null` when it is an attribute.
|
|
121
|
+
*
|
|
122
|
+
* Custom elements (a tag name containing `-`) receive object and array values
|
|
123
|
+
* as properties; their primitive values stay attributes.
|
|
124
|
+
*/
|
|
125
|
+
function getDomPropertyName(tagName, key, value) {
|
|
126
|
+
if (key.startsWith("prop:")) return key.slice(5);
|
|
127
|
+
if (isKnownPropertyOf(tagName, key)) return key;
|
|
128
|
+
if (value !== null && typeof value === "object" && key !== "style" && key !== "dangerouslySetInnerHTML" && isCustomElementName(tagName)) return key;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
/** Whether a property-routed prop also renders its attribute (`muted`). */
|
|
132
|
+
function propertyReflectsAttribute(key) {
|
|
133
|
+
return key === "muted";
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Whether SSR leaves a prop out entirely because it is written only as a
|
|
137
|
+
* property on the client.
|
|
138
|
+
*/
|
|
139
|
+
function isPropertyOnlyProp(tagName, key, value) {
|
|
140
|
+
return getDomPropertyName(tagName, key, value) !== null && !propertyReflectsAttribute(key);
|
|
141
|
+
}
|
|
142
|
+
/** The value a property prop assigns: `muted`/`indeterminate` are booleans. */
|
|
143
|
+
function domPropertyValue(key, value) {
|
|
144
|
+
return key === "muted" || key === "indeterminate" ? Boolean(value) : value;
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/common/css.ts
|
|
148
|
+
const CSS_UNSAFE_RE = /[{}<>\\]/g;
|
|
149
|
+
const CSS_URI_SCHEME_RE = /(?:^|[\s(,])([a-z][a-z0-9+.-]*):/i;
|
|
150
|
+
const CSS_FUNCTION_NAME_RE = /([a-z-][a-z0-9-]*)\s*\(/gi;
|
|
151
|
+
const CSS_ALLOWED_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
152
|
+
"var",
|
|
153
|
+
"calc",
|
|
154
|
+
"min",
|
|
155
|
+
"max",
|
|
156
|
+
"clamp",
|
|
157
|
+
"rgb",
|
|
158
|
+
"rgba",
|
|
159
|
+
"hsl",
|
|
160
|
+
"hsla",
|
|
161
|
+
"lab",
|
|
162
|
+
"lch",
|
|
163
|
+
"oklab",
|
|
164
|
+
"oklch",
|
|
165
|
+
"color",
|
|
166
|
+
"color-mix",
|
|
167
|
+
"translate",
|
|
168
|
+
"translatex",
|
|
169
|
+
"translatey",
|
|
170
|
+
"translatez",
|
|
171
|
+
"scale",
|
|
172
|
+
"scalex",
|
|
173
|
+
"scaley",
|
|
174
|
+
"scalez",
|
|
175
|
+
"rotate",
|
|
176
|
+
"rotatex",
|
|
177
|
+
"rotatey",
|
|
178
|
+
"rotatez",
|
|
179
|
+
"skew",
|
|
180
|
+
"skewx",
|
|
181
|
+
"skewy",
|
|
182
|
+
"matrix",
|
|
183
|
+
"matrix3d",
|
|
184
|
+
"linear-gradient",
|
|
185
|
+
"radial-gradient",
|
|
186
|
+
"conic-gradient",
|
|
187
|
+
"repeating-linear-gradient",
|
|
188
|
+
"repeating-radial-gradient",
|
|
189
|
+
"repeating-conic-gradient",
|
|
190
|
+
"cubic-bezier",
|
|
191
|
+
"steps"
|
|
192
|
+
]);
|
|
193
|
+
function sanitizeCssValue(value) {
|
|
194
|
+
const str = String(value);
|
|
195
|
+
if (CSS_URI_SCHEME_RE.test(str)) return "";
|
|
196
|
+
if (str.includes("(")) {
|
|
197
|
+
for (const match of str.matchAll(CSS_FUNCTION_NAME_RE)) if (!CSS_ALLOWED_FUNCTIONS.has(match[1].toLowerCase())) return "";
|
|
198
|
+
}
|
|
199
|
+
return str.replace(CSS_UNSAFE_RE, "");
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/common/ssr.ts
|
|
203
|
+
/**
|
|
204
|
+
* Common call contracts: SSR types
|
|
205
|
+
*/
|
|
206
|
+
function escapedStyleRawText(value) {
|
|
207
|
+
return value.replace(/<\//gi, "<\\/");
|
|
208
|
+
}
|
|
209
|
+
function validateDocumentStyleRegistrations(html, styles, apiName, validation) {
|
|
210
|
+
if (validation === "off" || !styles || styles.length === 0) return;
|
|
211
|
+
const dropped = styles.filter(({ cssText }) => !html.includes(cssText) && !html.includes(escapedStyleRawText(cssText)));
|
|
212
|
+
if (dropped.length === 0) return;
|
|
213
|
+
const registrations = dropped.map(({ id }) => JSON.stringify(id)).join(", ");
|
|
214
|
+
const message = `${apiName} document() dropped ${dropped.length} registered SSR style${dropped.length === 1 ? "" : "s"}: ${registrations}. Serialize context.styles into the returned document, or set styleRegistrationValidation to "off" when omission is intentional.`;
|
|
215
|
+
if (validation === "error") throw new Error(message);
|
|
216
|
+
console.warn(message);
|
|
217
|
+
}
|
|
218
|
+
function renderDocument(document, args, apiName, styleRegistrationValidation = "warn") {
|
|
219
|
+
const html = document(args);
|
|
220
|
+
if (typeof html !== "string") {
|
|
221
|
+
const received = isPromiseLike(html) ? "a Promise-like value" : html === null ? "null" : Array.isArray(html) ? "an array" : typeof html;
|
|
222
|
+
throw new Error(`${apiName} document() must synchronously return a string; received ${received}.`);
|
|
223
|
+
}
|
|
224
|
+
validateDocumentStyleRegistrations(html, args.context.styles, apiName, styleRegistrationValidation);
|
|
225
|
+
return html;
|
|
226
|
+
}
|
|
227
|
+
const SSR_RENDER_DATA_ATTR = "data-askr-render-data";
|
|
228
|
+
//#endregion
|
|
229
|
+
export { PROPERTY_PROP_PREFIX as a, isKnownBooleanProperty as c, booleanAttributeValue as d, isSkippedProp as f, styleValueText as h, ATTRIBUTE_PROP_PREFIX as i, isPropertyOnlyProp as l, normalizeStylePropertyName as m, renderDocument as n, domPropertyValue as o, keepsFalseValue as p, sanitizeCssValue as r, getDomPropertyName as s, SSR_RENDER_DATA_ATTR as t, propertyReflectsAttribute as u };
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { D as isTracking, E as getTrackingComputation, F as logger, L as isProductionEnvironment, O as notifySource, S as queueTask, T as Signal, a as getCurrentInstance, b as getFlushVersion, h as isRendering, k as trackSource, r as claimHook, s as requireInstance, v as effectScheduler, w as Computation } from "./instance-BAnfmXOR.js";
|
|
2
|
+
import { t as markReadable } from "./readable-DFWOsTac.js";
|
|
3
|
+
import { n as isSnapshotSource } from "./snapshot-Cjx4N988.js";
|
|
4
|
+
//#region src/core/api/state.ts
|
|
5
|
+
/**
|
|
6
|
+
* Public reactive primitives: state(), derive(), selector().
|
|
7
|
+
*/
|
|
8
|
+
/** A derived computation is running: writes would loop. */
|
|
9
|
+
function isDerivedComputationActive() {
|
|
10
|
+
const running = getTrackingComputation();
|
|
11
|
+
return running !== null && running._derived === true;
|
|
12
|
+
}
|
|
13
|
+
/** Create a callable state cell backed by `signal`. */
|
|
14
|
+
function createStateCell(signal, trackReads = false) {
|
|
15
|
+
const read = trackReads ? () => {
|
|
16
|
+
read._everRead = true;
|
|
17
|
+
return signal.read();
|
|
18
|
+
} : () => signal.read();
|
|
19
|
+
read._signal = signal;
|
|
20
|
+
const set = (valueOrUpdater) => {
|
|
21
|
+
if (isRendering() && !isDerivedComputationActive()) throw new Error("[Askr] state.set() cannot be called during component render. A write during render would schedule another render of the same component and could loop forever. Move state updates to event handlers or use conditional rendering instead.");
|
|
22
|
+
const next = typeof valueOrUpdater === "function" ? valueOrUpdater(signal.peek()) : valueOrUpdater;
|
|
23
|
+
if (Object.is(signal.peek(), next)) return;
|
|
24
|
+
if (isDerivedComputationActive()) throw new Error("[Askr] state.set() cannot be called inside a derive() or selector() computation. Derived computations must be pure; a write from one can re-trigger it in an update loop. Move the state update to an event handler.");
|
|
25
|
+
signal.write(next);
|
|
26
|
+
};
|
|
27
|
+
markReadable(read);
|
|
28
|
+
read.set = set;
|
|
29
|
+
return makeDestructurable(read);
|
|
30
|
+
}
|
|
31
|
+
/** Let a getter with a `set` method destructure into `[getter, setter]`. */
|
|
32
|
+
function makeDestructurable(read) {
|
|
33
|
+
read[Symbol.iterator] = function* () {
|
|
34
|
+
yield read;
|
|
35
|
+
yield read.set;
|
|
36
|
+
};
|
|
37
|
+
return read;
|
|
38
|
+
}
|
|
39
|
+
function state(initialValue) {
|
|
40
|
+
const instance = requireInstance("state()");
|
|
41
|
+
const index = claimHook(instance, "state");
|
|
42
|
+
const existing = instance.hooks[index];
|
|
43
|
+
if (existing) return existing;
|
|
44
|
+
const development = !isProductionEnvironment();
|
|
45
|
+
const cell = createStateCell(new Signal(initialValue), development);
|
|
46
|
+
instance.hooks[index] = cell;
|
|
47
|
+
if (development) warnUnusedStateOnUnmount(instance);
|
|
48
|
+
return cell;
|
|
49
|
+
}
|
|
50
|
+
const unusedStateChecks = /* @__PURE__ */ new WeakSet();
|
|
51
|
+
/**
|
|
52
|
+
* Warn, when a committed component unmounts, about state it never read. A
|
|
53
|
+
* branch that has not rendered is no evidence that its state is unused, so
|
|
54
|
+
* the check waits for the end of the component's lifetime.
|
|
55
|
+
*/
|
|
56
|
+
function warnUnusedStateOnUnmount(instance) {
|
|
57
|
+
if (unusedStateChecks.has(instance)) return;
|
|
58
|
+
unusedStateChecks.add(instance);
|
|
59
|
+
instance.onCleanup(() => {
|
|
60
|
+
if (!instance.mounted) return;
|
|
61
|
+
const name = instance.displayName || "<anonymous>";
|
|
62
|
+
for (let index = 0; index < instance.hookKinds.length; index++) {
|
|
63
|
+
if (instance.hookKinds[index] !== "state") continue;
|
|
64
|
+
if (instance.hooks[index]._everRead) continue;
|
|
65
|
+
logger.warn(`[askr] Unused state variable detected in ${name} at index ${index}. State should be read during render or removed.`);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const MAX_DERIVED_RUNS = 50;
|
|
70
|
+
function mapped(source, map) {
|
|
71
|
+
return () => {
|
|
72
|
+
let value;
|
|
73
|
+
if (typeof source === "function") value = source();
|
|
74
|
+
else if (isSnapshotSource(source)) {
|
|
75
|
+
const snapshot = source;
|
|
76
|
+
if (snapshot.pending) return null;
|
|
77
|
+
value = snapshot.value;
|
|
78
|
+
} else value = source;
|
|
79
|
+
if (value == null) return null;
|
|
80
|
+
return map(value);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function derive(source, map) {
|
|
84
|
+
const instance = requireInstance("derive()");
|
|
85
|
+
const index = claimHook(instance, "derive");
|
|
86
|
+
const compute = map === void 0 ? source : mapped(source, map);
|
|
87
|
+
let slot = instance.hooks[index];
|
|
88
|
+
if (!slot) {
|
|
89
|
+
slot = createDerived(instance, compute, "derive()");
|
|
90
|
+
instance.hooks[index] = slot;
|
|
91
|
+
} else slot.setCompute(compute);
|
|
92
|
+
return slot.read;
|
|
93
|
+
}
|
|
94
|
+
function createDerived(owner, compute, label) {
|
|
95
|
+
let current = compute;
|
|
96
|
+
let runs = 0;
|
|
97
|
+
let runsFlush = -1;
|
|
98
|
+
const computation = new Computation(owner, () => {
|
|
99
|
+
const flush = getFlushVersion();
|
|
100
|
+
if (flush !== runsFlush) {
|
|
101
|
+
runsFlush = flush;
|
|
102
|
+
runs = 0;
|
|
103
|
+
}
|
|
104
|
+
if (++runs > MAX_DERIVED_RUNS) throw new Error(`[Askr] ${label} exceeded ${MAX_DERIVED_RUNS} runs`);
|
|
105
|
+
return current();
|
|
106
|
+
}, null);
|
|
107
|
+
computation._derived = true;
|
|
108
|
+
return {
|
|
109
|
+
computation,
|
|
110
|
+
read: markReadable((() => {
|
|
111
|
+
if (computation.disposed) throw new Error(`${label} owner has been disposed`);
|
|
112
|
+
if (computation._running) throw new Error(`${label} cannot read itself recursively`);
|
|
113
|
+
return computation.read();
|
|
114
|
+
})),
|
|
115
|
+
setCompute(fn) {
|
|
116
|
+
if (fn === current) return;
|
|
117
|
+
current = fn;
|
|
118
|
+
computation.invalidate();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function selector(source, equals = Object.is) {
|
|
123
|
+
const instance = requireInstance("selector()");
|
|
124
|
+
const index = claimHook(instance, "selector");
|
|
125
|
+
let slot = instance.hooks[index];
|
|
126
|
+
if (!slot) {
|
|
127
|
+
slot = createSelector(instance, source, equals);
|
|
128
|
+
instance.hooks[index] = slot;
|
|
129
|
+
} else slot.setSource(source, equals);
|
|
130
|
+
return slot.predicate;
|
|
131
|
+
}
|
|
132
|
+
const candidateCounts = /* @__PURE__ */ new WeakMap();
|
|
133
|
+
function createSelector(owner, initialSource, initialEquals) {
|
|
134
|
+
let source = initialSource;
|
|
135
|
+
let equals = initialEquals;
|
|
136
|
+
let value;
|
|
137
|
+
let hasValue = false;
|
|
138
|
+
const primitives = /* @__PURE__ */ new Map();
|
|
139
|
+
const objects = /* @__PURE__ */ new WeakMap();
|
|
140
|
+
const sweepCandidates = /* @__PURE__ */ new Set();
|
|
141
|
+
let sweepQueued = false;
|
|
142
|
+
const sweep = () => {
|
|
143
|
+
sweepQueued = false;
|
|
144
|
+
for (const entry of sweepCandidates) {
|
|
145
|
+
if (entry._observers?.size) continue;
|
|
146
|
+
const candidate = entry.candidate;
|
|
147
|
+
if (typeof candidate === "object" && candidate !== null || typeof candidate === "function") {
|
|
148
|
+
if (objects.get(candidate) === entry) objects.delete(candidate);
|
|
149
|
+
} else if (primitives.get(candidate) === entry) primitives.delete(candidate);
|
|
150
|
+
}
|
|
151
|
+
sweepCandidates.clear();
|
|
152
|
+
};
|
|
153
|
+
const queueSweep = (entry) => {
|
|
154
|
+
sweepCandidates.add(entry);
|
|
155
|
+
if (sweepQueued) return;
|
|
156
|
+
sweepQueued = true;
|
|
157
|
+
queueTask(sweep);
|
|
158
|
+
};
|
|
159
|
+
const candidateSource = (candidate, create) => {
|
|
160
|
+
const isObject = typeof candidate === "object" && candidate !== null || typeof candidate === "function";
|
|
161
|
+
let entry = isObject ? objects.get(candidate) : primitives.get(candidate);
|
|
162
|
+
if (!entry && create) {
|
|
163
|
+
const created = {
|
|
164
|
+
_observers: null,
|
|
165
|
+
candidate
|
|
166
|
+
};
|
|
167
|
+
created._unobserved = () => queueSweep(created);
|
|
168
|
+
queueSweep(created);
|
|
169
|
+
entry = created;
|
|
170
|
+
if (isObject) objects.set(candidate, entry);
|
|
171
|
+
else primitives.set(candidate, entry);
|
|
172
|
+
}
|
|
173
|
+
return entry;
|
|
174
|
+
};
|
|
175
|
+
const notifyChange = (previous, next) => {
|
|
176
|
+
if (equals !== Object.is) {
|
|
177
|
+
if (equals(previous, next)) return;
|
|
178
|
+
for (const entry of primitives.values()) notifySource(entry);
|
|
179
|
+
notifySource(allObjects);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const a = candidateSource(previous, false);
|
|
183
|
+
if (a) notifySource(a);
|
|
184
|
+
const b = candidateSource(next, false);
|
|
185
|
+
if (b) notifySource(b);
|
|
186
|
+
};
|
|
187
|
+
const allObjects = { _observers: null };
|
|
188
|
+
const watcher = new Computation(owner, () => {
|
|
189
|
+
const next = source();
|
|
190
|
+
if (hasValue && !Object.is(value, next)) {
|
|
191
|
+
const previous = value;
|
|
192
|
+
value = next;
|
|
193
|
+
notifyChange(previous, next);
|
|
194
|
+
} else {
|
|
195
|
+
value = next;
|
|
196
|
+
hasValue = true;
|
|
197
|
+
}
|
|
198
|
+
}, effectScheduler("render", owner.depth), null);
|
|
199
|
+
watcher._derived = true;
|
|
200
|
+
watcher.run();
|
|
201
|
+
const predicate = ((candidate) => {
|
|
202
|
+
if (watcher.disposed) throw new Error("selector() owner has been disposed");
|
|
203
|
+
if (watcher._running) throw new Error("selector() cannot read itself recursively");
|
|
204
|
+
if (watcher.stale) watcher.update();
|
|
205
|
+
if (isTracking()) {
|
|
206
|
+
trackSource(candidateSource(candidate, true));
|
|
207
|
+
trackSource(allObjects);
|
|
208
|
+
}
|
|
209
|
+
return equals(value, candidate);
|
|
210
|
+
});
|
|
211
|
+
candidateCounts.set(predicate, () => primitives.size);
|
|
212
|
+
return {
|
|
213
|
+
predicate,
|
|
214
|
+
setSource(nextSource, nextEquals) {
|
|
215
|
+
const comparatorChanged = equals !== nextEquals;
|
|
216
|
+
equals = nextEquals;
|
|
217
|
+
if (comparatorChanged) notifySource(allObjects, getTrackingComputation() ?? void 0);
|
|
218
|
+
if (nextSource !== source) {
|
|
219
|
+
source = nextSource;
|
|
220
|
+
watcher.invalidate();
|
|
221
|
+
watcher.update();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function getSignal() {
|
|
227
|
+
const instance = getCurrentInstance();
|
|
228
|
+
if (!instance) throw new Error("getSignal() can only be called during component render execution.");
|
|
229
|
+
return instance.signal;
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
export { state as a, selector as i, getSignal as n, makeDestructurable as r, derive as t };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { n as Fragment, t as ELEMENT_TYPE } from "./jsx-DRS-qjiN.js";
|
|
2
|
+
//#region src/jsx/utils.ts
|
|
3
|
+
/** Check whether `value` is a JSX element vnode. */
|
|
4
|
+
function isElement(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && value.$$typeof === ELEMENT_TYPE;
|
|
6
|
+
}
|
|
7
|
+
/** Clone a JSX element, shallow-merging `props` over its existing props. */
|
|
8
|
+
function cloneElement(element, props) {
|
|
9
|
+
return {
|
|
10
|
+
$$typeof: element.$$typeof,
|
|
11
|
+
type: element.type,
|
|
12
|
+
key: element.key,
|
|
13
|
+
props: {
|
|
14
|
+
...element.props,
|
|
15
|
+
...props
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/foundations/structures/layout.tsx
|
|
21
|
+
/**
|
|
22
|
+
* Wrap a {@link LayoutComponent} so it can be invoked as `(children, props)`,
|
|
23
|
+
* matching route layout conventions.
|
|
24
|
+
*/
|
|
25
|
+
function layout(Layout) {
|
|
26
|
+
return (children, props) => {
|
|
27
|
+
return Layout({
|
|
28
|
+
...props,
|
|
29
|
+
children
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/foundations/structures/slot.tsx
|
|
35
|
+
/**
|
|
36
|
+
* Slot
|
|
37
|
+
*
|
|
38
|
+
* Structural primitive for prop forwarding patterns.
|
|
39
|
+
*
|
|
40
|
+
* POLICY DECISIONS (LOCKED):
|
|
41
|
+
*
|
|
42
|
+
* 1. asChild Pattern
|
|
43
|
+
* When asChild=true, merges props into the single child element.
|
|
44
|
+
* Child must be a valid JSXElement; non-element children return null.
|
|
45
|
+
* **Slot props override child props** (injection pattern).
|
|
46
|
+
*
|
|
47
|
+
* 2. Fallback Behavior
|
|
48
|
+
* When asChild=false, returns a Fragment (structural no-op).
|
|
49
|
+
* No DOM element is introduced.
|
|
50
|
+
*
|
|
51
|
+
* 3. Type Safety
|
|
52
|
+
* asChild=true requires exactly one JSXElement child (enforced by type).
|
|
53
|
+
* Runtime validates with isElement() check.
|
|
54
|
+
*/
|
|
55
|
+
function Slot(props) {
|
|
56
|
+
if (props.asChild) {
|
|
57
|
+
const { children, asChild: _asChild, ...rest } = props;
|
|
58
|
+
if (isElement(children)) return cloneElement(children, rest);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
$$typeof: ELEMENT_TYPE,
|
|
63
|
+
type: Fragment,
|
|
64
|
+
props: { children: props.children },
|
|
65
|
+
key: null
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/foundations/structures/presence.ts
|
|
70
|
+
/**
|
|
71
|
+
* Presence
|
|
72
|
+
*
|
|
73
|
+
* Structural policy primitive for conditional mount/unmount.
|
|
74
|
+
* - No timers
|
|
75
|
+
* - No animation coupling
|
|
76
|
+
* - No DOM side-effects
|
|
77
|
+
*
|
|
78
|
+
* POLICY DECISIONS (LOCKED):
|
|
79
|
+
*
|
|
80
|
+
* 1. Present as Function
|
|
81
|
+
* Accepts boolean OR function to support lazy evaluation patterns.
|
|
82
|
+
* Function is called once per render. Use boolean form for static values.
|
|
83
|
+
*
|
|
84
|
+
* 2. Children Type
|
|
85
|
+
* Presence forwards normal renderable child content only.
|
|
86
|
+
* Imperative DOM nodes are not part of the public contract.
|
|
87
|
+
*
|
|
88
|
+
* 3. Immediate Mount/Unmount
|
|
89
|
+
* No exit animations or transitions. When `present` becomes false,
|
|
90
|
+
* children are removed immediately. Animation must be layered above
|
|
91
|
+
* this primitive.
|
|
92
|
+
*/
|
|
93
|
+
function Presence({ present, children }) {
|
|
94
|
+
if (!(typeof present === "function" ? present() : Boolean(present))) return null;
|
|
95
|
+
return {
|
|
96
|
+
$$typeof: ELEMENT_TYPE,
|
|
97
|
+
type: Fragment,
|
|
98
|
+
props: { children },
|
|
99
|
+
key: null
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/foundations/structures/collection.ts
|
|
104
|
+
/** Create an empty, insertion-ordered {@link Collection} registry. */
|
|
105
|
+
function createCollection() {
|
|
106
|
+
const registry = /* @__PURE__ */ new Map();
|
|
107
|
+
function register(node, metadata) {
|
|
108
|
+
const item = {
|
|
109
|
+
node,
|
|
110
|
+
metadata
|
|
111
|
+
};
|
|
112
|
+
registry.set(node, item);
|
|
113
|
+
return () => {
|
|
114
|
+
registry.delete(node);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function items() {
|
|
118
|
+
return Array.from(registry.values());
|
|
119
|
+
}
|
|
120
|
+
function clear() {
|
|
121
|
+
registry.clear();
|
|
122
|
+
}
|
|
123
|
+
function size() {
|
|
124
|
+
return registry.size;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
register,
|
|
128
|
+
items,
|
|
129
|
+
clear,
|
|
130
|
+
size
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* USAGE EXAMPLE:
|
|
135
|
+
*
|
|
136
|
+
* // Create a collection for menu items
|
|
137
|
+
* const menuItems = createCollection<HTMLElement, { disabled: boolean }>();
|
|
138
|
+
*
|
|
139
|
+
* // Register items
|
|
140
|
+
* const unregister1 = menuItems.register(element1, { disabled: false });
|
|
141
|
+
* const unregister2 = menuItems.register(element2, { disabled: true });
|
|
142
|
+
*
|
|
143
|
+
* // Query items
|
|
144
|
+
* const allItems = menuItems.items();
|
|
145
|
+
* const enabledItems = allItems.filter(item => !item.metadata.disabled);
|
|
146
|
+
*
|
|
147
|
+
* // Cleanup
|
|
148
|
+
* unregister1();
|
|
149
|
+
* unregister2();
|
|
150
|
+
*/
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/foundations/structures/layer.ts
|
|
153
|
+
/** Create a new, empty {@link LayerManager} for coordinating overlay stacking. */
|
|
154
|
+
function createLayer() {
|
|
155
|
+
const stack = [];
|
|
156
|
+
let nextId = 1;
|
|
157
|
+
function register(options) {
|
|
158
|
+
const id = nextId++;
|
|
159
|
+
const entry = {
|
|
160
|
+
id,
|
|
161
|
+
options
|
|
162
|
+
};
|
|
163
|
+
stack.push(entry);
|
|
164
|
+
function isTop() {
|
|
165
|
+
return stack[stack.length - 1]?.id === id;
|
|
166
|
+
}
|
|
167
|
+
function unregister() {
|
|
168
|
+
const index = stack.findIndex((e) => e.id === id);
|
|
169
|
+
if (index !== -1) stack.splice(index, 1);
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
id,
|
|
173
|
+
isTop,
|
|
174
|
+
unregister
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function layers() {
|
|
178
|
+
return stack.map((entry) => ({
|
|
179
|
+
id: entry.id,
|
|
180
|
+
isTop: () => stack[stack.length - 1]?.id === entry.id,
|
|
181
|
+
unregister: () => {
|
|
182
|
+
const index = stack.findIndex((e) => e.id === entry.id);
|
|
183
|
+
if (index !== -1) stack.splice(index, 1);
|
|
184
|
+
}
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
function handleEscape() {
|
|
188
|
+
const top = stack[stack.length - 1];
|
|
189
|
+
if (top) top.options.onEscape?.();
|
|
190
|
+
}
|
|
191
|
+
function handleOutsidePointer(e) {
|
|
192
|
+
const top = stack[stack.length - 1];
|
|
193
|
+
if (!top) return;
|
|
194
|
+
const node = top.options.node;
|
|
195
|
+
if (node && e.target instanceof Node) {
|
|
196
|
+
if (!node.contains(e.target)) top.options.onOutsidePointer?.(e);
|
|
197
|
+
} else top.options.onOutsidePointer?.(e);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
register,
|
|
201
|
+
layers,
|
|
202
|
+
handleEscape,
|
|
203
|
+
handleOutsidePointer
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* USAGE EXAMPLE:
|
|
208
|
+
*
|
|
209
|
+
* const layerManager = createLayer();
|
|
210
|
+
*
|
|
211
|
+
* function Modal({ onClose }) {
|
|
212
|
+
* const modalRef = ref<HTMLDivElement>();
|
|
213
|
+
*
|
|
214
|
+
* const layer = layerManager.register({
|
|
215
|
+
* node: modalRef.current,
|
|
216
|
+
* onEscape: onClose,
|
|
217
|
+
* onOutsidePointer: onClose,
|
|
218
|
+
* });
|
|
219
|
+
*
|
|
220
|
+
* onUnmount(() => layer.unregister());
|
|
221
|
+
*
|
|
222
|
+
* return (
|
|
223
|
+
* <div ref={modalRef}>
|
|
224
|
+
* <h1>Modal</h1>
|
|
225
|
+
* {layer.isTop() && <p>I am on top!</p>}
|
|
226
|
+
* </div>
|
|
227
|
+
* );
|
|
228
|
+
* }
|
|
229
|
+
*/
|
|
230
|
+
//#endregion
|
|
231
|
+
export { layout as a, Slot as i, createCollection as n, cloneElement as o, Presence as r, isElement as s, createLayer as t };
|