@statorjs/stator 1.8.0 → 2.0.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/package.json +2 -2
- package/src/client/bind.ts +34 -0
- package/src/client/client-id.ts +17 -2
- package/src/client/index.ts +3 -2
- package/src/client/machine.ts +61 -46
- package/src/client/use.ts +28 -9
- package/src/compiler/client-emit.ts +21 -44
- package/src/compiler/client-script.ts +6 -2
- package/src/compiler/compile.ts +22 -2
- package/src/compiler/dts.ts +35 -13
- package/src/compiler/lower.ts +0 -0
- package/src/compiler/virtual-code.ts +13 -4
- package/src/engine/actor.ts +3 -22
- package/src/engine/index.ts +1 -0
- package/src/engine/types.ts +7 -0
- package/src/server/http.ts +6 -6
- package/src/server/index.ts +10 -0
- package/src/server/recompute.ts +5 -25
- package/src/template/directives/on.ts +9 -2
- package/src/template/directives/spread.ts +88 -0
- package/src/template/html-attributes.ts +210 -0
- package/src/template/html.ts +19 -34
- package/src/template/index.ts +17 -11
- package/src/template/types.ts +17 -5
- package/src/wire/apply.ts +2 -2
- package/src/wire/attr-value.ts +55 -0
- package/src/wire/island-markers.ts +22 -0
package/src/template/types.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AnyMachineDef,
|
|
3
|
+
EventOf,
|
|
4
|
+
InstanceOf as SelectorsOf,
|
|
5
|
+
StateNameOf,
|
|
6
|
+
} from '../engine/index.ts'
|
|
2
7
|
import type { EventDescriptor } from '../server/render-context.ts'
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* The template-facing shape of a machine instance: the engine's selector view
|
|
6
11
|
* (each selector as a property carrying its return type, callable if it returns
|
|
7
|
-
* a function) plus the framework-provided `send` / `state` / `snapshot`.
|
|
12
|
+
* a function) plus the framework-provided `send` / `state` / `snapshot`. Both
|
|
13
|
+
* `state` (the state-name union) and `send` (the event union) are typed to the
|
|
14
|
+
* machine's own def, so `s.state === 'ready'` and `m.send({ type: 'SAVE', … })`
|
|
15
|
+
* autocomplete and a typo — in the name or the payload — is a compile error.
|
|
8
16
|
*/
|
|
9
|
-
export type InstanceOf<TDef extends AnyMachineDef> = SelectorsOf<TDef> &
|
|
17
|
+
export type InstanceOf<TDef extends AnyMachineDef> = SelectorsOf<TDef> &
|
|
18
|
+
InstanceCommon<StateNameOf<TDef>, EventOf<TDef>>
|
|
10
19
|
|
|
11
|
-
export interface InstanceCommon<
|
|
12
|
-
|
|
20
|
+
export interface InstanceCommon<
|
|
21
|
+
TStateKey extends string = string,
|
|
22
|
+
TEvent = { type: string; [k: string]: unknown },
|
|
23
|
+
> {
|
|
24
|
+
send(event: TEvent): EventDescriptor | undefined
|
|
13
25
|
readonly state: TStateKey
|
|
14
26
|
readonly snapshot: unknown
|
|
15
27
|
}
|
package/src/wire/apply.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* CustomEvent on `window` (the inspector's contract), regardless of which
|
|
9
9
|
* path applied it.
|
|
10
10
|
*/
|
|
11
|
+
import { setAttr } from './attr-value.ts'
|
|
11
12
|
import type { Directive, Patch } from './index.ts'
|
|
12
13
|
import {
|
|
13
14
|
elementAt,
|
|
@@ -74,8 +75,7 @@ function applyPatch(
|
|
|
74
75
|
return null
|
|
75
76
|
}
|
|
76
77
|
if (patch.op === 'text') element.textContent = patch.value
|
|
77
|
-
else
|
|
78
|
-
else element.setAttribute(patch.name, patch.value)
|
|
78
|
+
else setAttr(element, patch.name, patch.value)
|
|
79
79
|
return element
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { isUrlAttribute, safeAttrUrl } from './safe-url.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* THE attribute-value contract — one implementation for every tier that
|
|
5
|
+
* writes an attribute: static render (template/html.ts), the live diff
|
|
6
|
+
* (server/recompute.ts), the island writer codegen (compiler/client-emit.ts,
|
|
7
|
+
* via the client re-export), and the wire applier (wire/apply.ts).
|
|
8
|
+
*
|
|
9
|
+
* Boolean-absent semantics: `false`/`null`/`undefined` → `null`, meaning the
|
|
10
|
+
* attribute is ABSENT (`checked={expr}` must be able to render unchecked, and
|
|
11
|
+
* a static render must agree with what a live patch of the same attribute
|
|
12
|
+
* does). `true` → present-and-empty. Arrays join with no separator (matching
|
|
13
|
+
* text interpolation); everything else stringifies.
|
|
14
|
+
*
|
|
15
|
+
* History: this contract used to live in four drifting copies, and two of
|
|
16
|
+
* them disagreed in ways users hit — `checked={false}` rendered
|
|
17
|
+
* `checked="false"` on the static path, and array values patched as "a,b"
|
|
18
|
+
* but rendered as "ab". One implementation, so the seams can't disagree.
|
|
19
|
+
*/
|
|
20
|
+
export function attrValue(v: unknown): string | null {
|
|
21
|
+
if (v === false || v === null || v === undefined) return null
|
|
22
|
+
if (v === true) return ''
|
|
23
|
+
return stringifyAttr(v)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function stringifyAttr(v: unknown): string {
|
|
27
|
+
if (v == null) return ''
|
|
28
|
+
if (typeof v === 'string') return v
|
|
29
|
+
if (Array.isArray(v)) return v.map(stringifyAttr).join('')
|
|
30
|
+
return String(v)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The text-position value contract, shared by static render and text
|
|
34
|
+
* patches: null/undefined → empty, arrays join with no separator (an
|
|
35
|
+
* interpolated list of fragments/strings renders concatenated, not
|
|
36
|
+
* comma-joined), everything else stringifies. */
|
|
37
|
+
export function textValue(v: unknown): string {
|
|
38
|
+
return stringifyAttr(v)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Scheme guard for url-bearing attributes (href/src/…): strip a
|
|
42
|
+
* javascript:/vbscript: value whether it arrives at first render or through
|
|
43
|
+
* a later diff. `null` (attribute-absent) passes through. */
|
|
44
|
+
export function sanitizeAttr(name: string, value: string): string
|
|
45
|
+
export function sanitizeAttr(name: string, value: string | null): string | null
|
|
46
|
+
export function sanitizeAttr(name: string, value: string | null): string | null {
|
|
47
|
+
if (value === null) return null
|
|
48
|
+
return isUrlAttribute(name) ? safeAttrUrl(value) : value
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The DOM half of the contract: `null` removes, a string sets. */
|
|
52
|
+
export function setAttr(el: Element, name: string, value: string | null): void {
|
|
53
|
+
if (value === null) el.removeAttribute(name)
|
|
54
|
+
else el.setAttribute(name, value)
|
|
55
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Island marker formats — the compiler↔island-runtime contract, in one place
|
|
3
|
+
* so codegen (compiler/lower.ts, compiler/client-emit.ts) and the runtime
|
|
4
|
+
* (client/bind.ts's bindSlot, the generated querySelector calls) can't drift.
|
|
5
|
+
*
|
|
6
|
+
* - ELEMENT markers: `data-b="b<N>"` on an element whose on:/bind:/read()
|
|
7
|
+
* attribute wiring the island's setup() attaches. Every occurrence of a
|
|
8
|
+
* marker is wired (a marked element inside a `.map()` repeats per row).
|
|
9
|
+
* - TEXT SLOT markers: an `<!--s<N>-->` comment where a client-machine
|
|
10
|
+
* `read()` sat in text position; the runtime materializes one text node
|
|
11
|
+
* after each occurrence. The comment's data IS the marker string.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const ISLAND_MARKER_ATTR = 'data-b'
|
|
15
|
+
|
|
16
|
+
export const elementMarker = (i: number): string => `b${i}`
|
|
17
|
+
|
|
18
|
+
export const slotMarker = (i: number): string => `s${i}`
|
|
19
|
+
|
|
20
|
+
/** CSS selector for every occurrence of an element marker. */
|
|
21
|
+
export const elementMarkerSelector = (marker: string): string =>
|
|
22
|
+
`[${ISLAND_MARKER_ATTR}="${marker}"]`
|