@nisli/core 0.54.0 → 0.55.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 +93 -3
- package/dist/component.js +146 -5
- package/dist/component.js.map +1 -1
- package/dist/diagnostics.d.ts +48 -0
- package/dist/diagnostics.js +118 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/disposable.d.ts +14 -0
- package/dist/disposable.js +8 -0
- package/dist/disposable.js.map +1 -0
- package/dist/emitter.d.ts +2 -1
- package/dist/emitter.js +2 -1
- package/dist/emitter.js.map +1 -1
- package/dist/hmr/registry.js +8 -1
- package/dist/hmr/registry.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/injector.d.ts +9 -2
- package/dist/injector.js +18 -0
- package/dist/injector.js.map +1 -1
- package/dist/lifecycle.d.ts +9 -0
- package/dist/lifecycle.js +17 -7
- package/dist/lifecycle.js.map +1 -1
- package/dist/projection.js +27 -10
- package/dist/projection.js.map +1 -1
- package/dist/query.d.ts +134 -55
- package/dist/query.js +379 -188
- package/dist/query.js.map +1 -1
- package/dist/resource.d.ts +2 -1
- package/dist/resource.js +21 -4
- package/dist/resource.js.map +1 -1
- package/dist/settle.d.ts +61 -0
- package/dist/settle.js +106 -0
- package/dist/settle.js.map +1 -0
- package/dist/signal.d.ts +42 -7
- package/dist/signal.js +248 -68
- package/dist/signal.js.map +1 -1
- package/dist/template-audit.d.ts +64 -0
- package/dist/template-audit.js +246 -0
- package/dist/template-audit.js.map +1 -0
- package/dist/template.d.ts +70 -1
- package/dist/template.js +477 -218
- package/dist/template.js.map +1 -1
- package/dist/view-transition.d.ts +70 -0
- package/dist/view-transition.js +98 -0
- package/dist/view-transition.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* template-audit.ts — First-parse static template audit (ADR 0030.2 T4).
|
|
3
|
+
*
|
|
4
|
+
* Attached to the parse-once cache in template.ts: runs ONCE per `html`
|
|
5
|
+
* callsite (keyed by TemplateStringsArray identity) at the first parse, and
|
|
6
|
+
* walks the parsed-but-never-mounted template element checking its STATIC
|
|
7
|
+
* parts. The silent-static-typo class — the no-build equivalent of a compile
|
|
8
|
+
* error — dies in the engine, amortized free.
|
|
9
|
+
*
|
|
10
|
+
* Checks (per ADR 0030.2 §2 T4 as amended by §8 "T4 (template)"):
|
|
11
|
+
* - N101 — a dash-tag with no customElements definition. Races
|
|
12
|
+
* `customElements.whenDefined` against a microtask + macrotask grace so
|
|
13
|
+
* ordinary lazy definition (module import order) never warns.
|
|
14
|
+
* - N102 — an `@event` name that is neither a known native event nor an
|
|
15
|
+
* allowlisted component-dispatched event (`ui-*`, `nisli-*`).
|
|
16
|
+
* - N103 — an unknown `@event.modifier`.
|
|
17
|
+
* - N104 — an attribute on a component tag that declares attributes
|
|
18
|
+
* (non-empty `static observedAttributes`) which is neither declared nor a
|
|
19
|
+
* global HTML attribute. Components WITHOUT declared attributes get NO
|
|
20
|
+
* attribute audit: pre-schema (T3) there is no truth source — auto-resolved
|
|
21
|
+
* props are deliberately not attributes (§8).
|
|
22
|
+
*
|
|
23
|
+
* Suppression escape (designed for lazy islands, §8): a comment directive as
|
|
24
|
+
* the template's FIRST node (leading whitespace allowed):
|
|
25
|
+
*
|
|
26
|
+
* html`<!-- nisli-audit off --><my-island>…</my-island>`
|
|
27
|
+
* — disables the whole audit for this template.
|
|
28
|
+
*
|
|
29
|
+
* html`<!-- nisli-audit allow-undefined: my-island other-tag --><…>`
|
|
30
|
+
* — suppresses only N101 for the named tags (other checks stay live).
|
|
31
|
+
*
|
|
32
|
+
* The directive comment is static content: it survives into the mounted DOM
|
|
33
|
+
* (and SSG output) as an inert HTML comment. Documented, accepted for the
|
|
34
|
+
* prototype; the design gate may choose to strip it at parse.
|
|
35
|
+
*
|
|
36
|
+
* Dev gating: core has no build-time dev/prod define today — that mechanism
|
|
37
|
+
* is a named Wave-1 deliverable (§8). Until it exists this module is gated on
|
|
38
|
+
* the `devMode` flag below ONLY. SSG/happy-dom detection is deliberately NOT
|
|
39
|
+
* attempted (unreliable); an SSG build that wants silence must call
|
|
40
|
+
* `setTemplateAuditEnabled(false)` before rendering.
|
|
41
|
+
*/
|
|
42
|
+
// Reporting goes through the diagnostics leaf. The audit keeps its own
|
|
43
|
+
// enable override (tests, SSG environments) and otherwise follows the
|
|
44
|
+
// leaf's dev gate — under NODE_ENV=production builds it is silent.
|
|
45
|
+
import { diag as emitDiag, isDev } from './diagnostics.js';
|
|
46
|
+
let enabledOverride = null;
|
|
47
|
+
/**
|
|
48
|
+
* Enable/disable the first-parse audit (and its callsite capture);
|
|
49
|
+
* `null` restores the dev-gate default.
|
|
50
|
+
*/
|
|
51
|
+
export function setTemplateAuditEnabled(enabled) {
|
|
52
|
+
enabledOverride = enabled;
|
|
53
|
+
}
|
|
54
|
+
/** Current audit gate state (exposed for template.ts and tests). */
|
|
55
|
+
export function isTemplateAuditEnabled() {
|
|
56
|
+
return enabledOverride ?? isDev();
|
|
57
|
+
}
|
|
58
|
+
/** Emit a coded audit diagnostic through the diagnostics leaf (audit-gated). */
|
|
59
|
+
export function diag(code, message, detail) {
|
|
60
|
+
if (!isTemplateAuditEnabled())
|
|
61
|
+
return;
|
|
62
|
+
emitDiag(code, message, detail === undefined ? undefined : { detail });
|
|
63
|
+
}
|
|
64
|
+
// ── Callsite capture ────────────────────────────────────────────────
|
|
65
|
+
/** First-seen authoring callsite per template identity (dev only). */
|
|
66
|
+
const callsites = new WeakMap();
|
|
67
|
+
/**
|
|
68
|
+
* Remember the authoring callsite of an `html` template the first time its
|
|
69
|
+
* strings identity is seen. Must be called from `html()` itself — by the
|
|
70
|
+
* time the first parse happens (first mount), the authoring frame is gone
|
|
71
|
+
* from the stack. No-op when the audit is disabled or already captured.
|
|
72
|
+
*/
|
|
73
|
+
export function captureCallsite(strings) {
|
|
74
|
+
if (!isTemplateAuditEnabled() || callsites.has(strings))
|
|
75
|
+
return;
|
|
76
|
+
const stack = new Error().stack ?? '';
|
|
77
|
+
const frame = stack
|
|
78
|
+
.split('\n')
|
|
79
|
+
.slice(1)
|
|
80
|
+
.find(line => line.includes('at ') && !/template(-audit)?\.(ts|js|mjs|cjs)/.test(line));
|
|
81
|
+
callsites.set(strings, frame ? frame.trim() : '(unknown callsite)');
|
|
82
|
+
}
|
|
83
|
+
// ── Audit ───────────────────────────────────────────────────────────
|
|
84
|
+
const audited = new WeakSet();
|
|
85
|
+
/** Modifiers understood by bindEvent (template.ts). */
|
|
86
|
+
const KNOWN_MODIFIERS = new Set(['stop', 'prevent', 'once', 'enter', 'escape', 'space', 'tab']);
|
|
87
|
+
/** Component-dispatched event prefixes exempt from the native-event check (§8). */
|
|
88
|
+
const COMPONENT_EVENT_PREFIXES = ['ui-', 'nisli-'];
|
|
89
|
+
/** Real native events with no `on*` IDL attribute (the `in`-check misses them). */
|
|
90
|
+
const NATIVE_EVENTS_WITHOUT_IDL = new Set(['focusin', 'focusout']);
|
|
91
|
+
/**
|
|
92
|
+
* Global HTML attributes legal on ANY component tag. Mirrors (and must stay a
|
|
93
|
+
* superset of) template.ts's auto-resolution exclusion list — these reach the
|
|
94
|
+
* element as plain attributes, never `_setProp`.
|
|
95
|
+
*/
|
|
96
|
+
const GLOBAL_ATTRS = new Set([
|
|
97
|
+
'id', 'class', 'style', 'slot', 'title', 'role', 'tabindex', 'name',
|
|
98
|
+
'part', 'hidden', 'dir', 'lang', 'is',
|
|
99
|
+
]);
|
|
100
|
+
/** Parse the leading `<!-- nisli-audit … -->` directive comment, if any. */
|
|
101
|
+
function parseDirectives(content) {
|
|
102
|
+
const directives = { off: false, allowUndefined: new Set() };
|
|
103
|
+
for (const node of content.childNodes) {
|
|
104
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
105
|
+
if ((node.textContent ?? '').trim() === '')
|
|
106
|
+
continue; // leading whitespace
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
110
|
+
const body = (node.textContent ?? '').trim();
|
|
111
|
+
if (!body.startsWith('nisli-audit'))
|
|
112
|
+
break;
|
|
113
|
+
const rest = body.slice('nisli-audit'.length).trim();
|
|
114
|
+
if (rest === 'off') {
|
|
115
|
+
directives.off = true;
|
|
116
|
+
}
|
|
117
|
+
else if (rest.startsWith('allow-undefined')) {
|
|
118
|
+
const tags = rest
|
|
119
|
+
.slice('allow-undefined'.length)
|
|
120
|
+
.replace(/^[:\s]+/, '')
|
|
121
|
+
.split(/[\s,]+/)
|
|
122
|
+
.filter(Boolean);
|
|
123
|
+
for (const tag of tags)
|
|
124
|
+
directives.allowUndefined.add(tag.toLowerCase());
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
break; // only the FIRST non-whitespace node may carry the directive
|
|
128
|
+
}
|
|
129
|
+
return directives;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* N101 — dash-tag with no definition. Rather than warning synchronously (the
|
|
133
|
+
* defining module may simply import later in the same task), race
|
|
134
|
+
* `customElements.whenDefined(tag)` against a microtask + macrotask grace;
|
|
135
|
+
* warn only if the tag is still undefined after the grace elapses.
|
|
136
|
+
*/
|
|
137
|
+
function checkDashTag(tag, site) {
|
|
138
|
+
let registry;
|
|
139
|
+
try {
|
|
140
|
+
registry = customElements;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return; // no registry in this environment — nothing to check
|
|
144
|
+
}
|
|
145
|
+
if (!registry || registry.get(tag))
|
|
146
|
+
return;
|
|
147
|
+
let defined = false;
|
|
148
|
+
try {
|
|
149
|
+
registry.whenDefined(tag).then(() => { defined = true; }, () => { });
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
diag('N101', `<${tag}> is not a valid custom element name — at ${site}`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Grace: one microtask hop (whenDefined settlements for definitions made
|
|
156
|
+
// earlier in this task) + one macrotask hop (definitions made later in the
|
|
157
|
+
// same task, e.g. module evaluation order). Under fake timers the macrotask
|
|
158
|
+
// may never fire — the warning is simply skipped, never wrong.
|
|
159
|
+
queueMicrotask(() => {
|
|
160
|
+
if (defined)
|
|
161
|
+
return;
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
try {
|
|
164
|
+
if (!isTemplateAuditEnabled() || defined || registry.get(tag))
|
|
165
|
+
return;
|
|
166
|
+
diag('N101', `<${tag}> is used in a template but never defined (customElements.get returned ` +
|
|
167
|
+
`undefined after grace) — at ${site}. If this tag is a lazily-loaded island, ` +
|
|
168
|
+
`suppress with a leading <!-- nisli-audit allow-undefined: ${tag} --> comment.`);
|
|
169
|
+
}
|
|
170
|
+
catch { /* environment torn down during grace — nothing to report */ }
|
|
171
|
+
}, 0);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** N102/N103 — event-name and modifier checks for one `@event.mods` attribute. */
|
|
175
|
+
function checkEvent(el, attrName, site) {
|
|
176
|
+
const parts = attrName.slice(1).split('.');
|
|
177
|
+
const eventName = parts[0] ?? '';
|
|
178
|
+
const modifiers = parts.slice(1);
|
|
179
|
+
const isNative = `on${eventName}` in el || NATIVE_EVENTS_WITHOUT_IDL.has(eventName);
|
|
180
|
+
const isComponentEvent = COMPONENT_EVENT_PREFIXES.some(p => eventName.startsWith(p));
|
|
181
|
+
if (eventName && !isNative && !isComponentEvent) {
|
|
182
|
+
diag('N102', `@${eventName} on <${el.tagName.toLowerCase()}> is not a known native event ` +
|
|
183
|
+
`(and not an allowlisted ${COMPONENT_EVENT_PREFIXES.join('/')} component event) — at ${site}`);
|
|
184
|
+
}
|
|
185
|
+
for (const mod of modifiers) {
|
|
186
|
+
if (!KNOWN_MODIFIERS.has(mod)) {
|
|
187
|
+
diag('N103', `unknown event modifier ".${mod}" in "${attrName}" — known: ` +
|
|
188
|
+
`${[...KNOWN_MODIFIERS].join(', ')} — at ${site}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** N104 — undeclared attribute on a component that DECLARES attributes. */
|
|
193
|
+
function checkComponentAttr(tag, name, observed, site) {
|
|
194
|
+
if (name.startsWith('data-') || name.startsWith('aria-'))
|
|
195
|
+
return;
|
|
196
|
+
if (GLOBAL_ATTRS.has(name) || observed.includes(name))
|
|
197
|
+
return;
|
|
198
|
+
diag('N104', `<${tag}> declares attributes [${observed.join(', ')}] but the template sets ` +
|
|
199
|
+
`unknown "${name}" — at ${site}. Undeclared names route through _setProp and are ` +
|
|
200
|
+
`silently dead as attributes.`);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Run the one-time static audit over a freshly parsed template. Called by
|
|
204
|
+
* template.ts's parse-once cache on cache miss; idempotent per template
|
|
205
|
+
* identity, no-op when the audit is disabled. Read-only over the cached
|
|
206
|
+
* template element — mounting state is never touched.
|
|
207
|
+
*/
|
|
208
|
+
export function auditTemplate(strings, template) {
|
|
209
|
+
if (!isTemplateAuditEnabled() || audited.has(strings))
|
|
210
|
+
return;
|
|
211
|
+
audited.add(strings);
|
|
212
|
+
const site = callsites.get(strings) ?? '(unknown callsite)';
|
|
213
|
+
const content = template.content;
|
|
214
|
+
const directives = parseDirectives(content);
|
|
215
|
+
if (directives.off)
|
|
216
|
+
return;
|
|
217
|
+
for (const el of content.querySelectorAll('*')) {
|
|
218
|
+
const tag = el.tagName.toLowerCase();
|
|
219
|
+
const isDashTag = tag.includes('-');
|
|
220
|
+
if (isDashTag && !directives.allowUndefined.has(tag)) {
|
|
221
|
+
checkDashTag(tag, site);
|
|
222
|
+
}
|
|
223
|
+
// Attribute audit ONLY for components with declared attrs (§8): a
|
|
224
|
+
// non-empty static observedAttributes is the single pre-T3 truth source.
|
|
225
|
+
let observed;
|
|
226
|
+
if (isDashTag) {
|
|
227
|
+
const ctor = customElements.get(tag);
|
|
228
|
+
const list = ctor?.observedAttributes;
|
|
229
|
+
if (Array.isArray(list) && list.length > 0)
|
|
230
|
+
observed = list;
|
|
231
|
+
}
|
|
232
|
+
for (const attr of [...el.attributes]) {
|
|
233
|
+
const name = attr.name;
|
|
234
|
+
if (name.startsWith('@')) {
|
|
235
|
+
checkEvent(el, name, site);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
// Directive positions with their own semantics — never attributes.
|
|
239
|
+
if (name.startsWith('class:') || name === 'ref' || name === 'html:inner')
|
|
240
|
+
continue;
|
|
241
|
+
if (observed)
|
|
242
|
+
checkComponentAttr(tag, name, observed, site);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=template-audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-audit.js","sourceRoot":"","sources":["../src/template-audit.ts"],"names":[],"mappings":"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"}
|
package/dist/template.d.ts
CHANGED
|
@@ -21,6 +21,59 @@ export interface TemplateResult {
|
|
|
21
21
|
}
|
|
22
22
|
/** A DOM event handler whose event type is inferred from its native event name. */
|
|
23
23
|
export type TypedEventHandler<K extends keyof HTMLElementEventMap> = (event: HTMLElementEventMap[K]) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Branded trusted-HTML value accepted by `html:inner` (and by @nisli/ssg's
|
|
26
|
+
* static renderer, which re-exports this brand — core owns it, ADR 0030.2 §8).
|
|
27
|
+
* Structural, not nominal: cross-package/cross-realm values with the same
|
|
28
|
+
* shape stay valid.
|
|
29
|
+
*/
|
|
30
|
+
export interface RawHtml {
|
|
31
|
+
value: string;
|
|
32
|
+
__raw: true;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Mark a string as trusted raw HTML. `html:inner` accepts ONLY branded
|
|
36
|
+
* values — a bare string throws N106 (the accidental-XSS-sink class closes,
|
|
37
|
+
* ADR 0030.2 §3). NEVER wrap user-generated input.
|
|
38
|
+
*/
|
|
39
|
+
export declare function raw(value: string): RawHtml;
|
|
40
|
+
/**
|
|
41
|
+
* Branded UNTRUSTED-HTML value accepted by `html:inner`. The mirror image of
|
|
42
|
+
* {@link RawHtml}: where `raw()` is the author asserting markup is already
|
|
43
|
+
* trustworthy, this brand asserts the opposite — the string is untrusted and
|
|
44
|
+
* MUST pass a sanitizer before it reaches the DOM (ADR 0019:124–128, executed
|
|
45
|
+
* by bet-09(c)). Structural, not nominal, on the same rule as RawHtml.
|
|
46
|
+
*/
|
|
47
|
+
export interface SanitizedHtml {
|
|
48
|
+
value: string;
|
|
49
|
+
__sanitize: true;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Mark a string as untrusted markup to be sanitized at the sink — the brand
|
|
53
|
+
* for user-generated HTML, which must NEVER go through `raw()`.
|
|
54
|
+
*
|
|
55
|
+
* `html:inner` writes it with the platform's `Element.setHTML()` (the spec's
|
|
56
|
+
* XSS-safe default sanitizer) when the engine has one, otherwise with the hook
|
|
57
|
+
* registered by {@link setSanitizerFallback}. With NEITHER available the
|
|
58
|
+
* binding throws N107: it never falls back to `innerHTML`. nisli bundles no
|
|
59
|
+
* sanitizer — the trust decision, and its bytes, stay in the app.
|
|
60
|
+
*/
|
|
61
|
+
export declare function sanitized(value: string): SanitizedHtml;
|
|
62
|
+
/** A sanitizing writer: sanitize `html`, then put the result inside `el`. */
|
|
63
|
+
export type SanitizerFallback = (el: Element, html: string) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Register the sanitizing writer used for `sanitized()` values on engines
|
|
66
|
+
* WITHOUT native `Element.setHTML()` (Safari today, older Chromium/Firefox),
|
|
67
|
+
* or `null` to unregister. Native `setHTML()` always wins where it exists, so
|
|
68
|
+
* modern engines never pay the hook. nisli ships no sanitizer; wire one:
|
|
69
|
+
*
|
|
70
|
+
* setSanitizerFallback((el, html) => {
|
|
71
|
+
* el.innerHTML = DOMPurify.sanitize(html);
|
|
72
|
+
* });
|
|
73
|
+
*/
|
|
74
|
+
export declare function setSanitizerFallback(fn: SanitizerFallback | null): void;
|
|
75
|
+
/** @internal Test-only: number of actual `<template>` parses performed. */
|
|
76
|
+
export declare function __templateParseCount(): number;
|
|
24
77
|
/**
|
|
25
78
|
* Tagged template literal for creating reactive DOM templates.
|
|
26
79
|
*
|
|
@@ -49,7 +102,23 @@ export declare function html(strings: TemplateStringsArray, ...values: unknown[]
|
|
|
49
102
|
* ```
|
|
50
103
|
*/
|
|
51
104
|
export declare function each<T>(items: ReadonlySignal<T[]>, keyFn: (item: T, index: number) => string | number, templateFn: (item: ReadonlySignal<T>, index: ReadonlySignal<number>) => TemplateResult): TemplateResult;
|
|
52
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Conditional rendering helper (ADR 0030.2 §3, boolean-gated with `else`).
|
|
107
|
+
*
|
|
108
|
+
* Shows `template` when the condition is truthy, `elseTemplate` (optional)
|
|
109
|
+
* when falsy. Both arms accept a TemplateResult or a lazy callback
|
|
110
|
+
* `() => TemplateResult`; callbacks are only evaluated for the active arm.
|
|
111
|
+
*
|
|
112
|
+
* Semantics for signal conditions:
|
|
113
|
+
* - The gate is `!!condition.value` — a truthy→truthy (or falsy→falsy)
|
|
114
|
+
* transition returns the memoized previous result, so the live branch is
|
|
115
|
+
* NEVER rebuilt (no state/scroll/focus loss) and callbacks never re-run.
|
|
116
|
+
* - Branch callbacks evaluate UNTRACKED: a signal read during branch
|
|
117
|
+
* construction does not subscribe the when() computed, so it cannot
|
|
118
|
+
* trigger a rebuild. Reactive parts inside a branch must be interpolated
|
|
119
|
+
* as signals/computeds (values), not read raw at construction time.
|
|
120
|
+
*/
|
|
121
|
+
export declare function when(condition: unknown, template: TemplateResult | (() => TemplateResult), elseTemplate?: TemplateResult | (() => TemplateResult)): TemplateResult | ReadonlySignal<TemplateResult | null> | null;
|
|
53
122
|
/** A value bindable to an `el()` attribute — static or reactive. */
|
|
54
123
|
type ElAttrValue = string | number | boolean | null | undefined | ReadonlySignal<unknown>;
|
|
55
124
|
export interface ElProps {
|