@gyral/core 0.3.1 → 0.3.3

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/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["// Shared state: stores as \"props from the side\" (docs/design-docs/0013-shared-state.md).\n// A store is MVI without a view: init + pure update, commands run by its own interpreter.\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { DEVTOOLS_ENABLED, devCommands, devStore } from '#devtools';\nimport {\n splitNext,\n type AnyDriver,\n type Command,\n type DriverOverrides,\n type Next,\n} from './command.js';\nimport { fail } from './errors.js';\nimport { features, type LocalHost } from './features.js';\nimport { makeInterpreter, type Interpreter } from './internal/interpreter.js';\nimport { noteActivity } from './scheduler.js';\nimport { bindStores } from './store-binding.js';\nimport type { IntentRejected, Tagged } from './types.js';\nimport { message } from './view/index.js';\n\n/** No DOM means a server render: store commands never run there (ADR 0012). */\nconst onServer = typeof document === 'undefined';\n\ntype Variant<M extends Tagged, K extends M['_tag']> = Extract<M, { readonly _tag: K }>;\n\n/** One pure reducer per message tag, like a component's `update` but without context. */\nexport type StoreUpdate<S, M extends Tagged> = {\n readonly [K in M['_tag']]: (state: S, msg: Variant<M, K>) => Next<S, M>;\n};\n\nexport interface StoreSpec<S, M extends Tagged> {\n /** Initial state (and optional commands, e.g. load a saved cart). */\n readonly init: () => Next<S, M>;\n readonly update: StoreUpdate<S, M>;\n /** Driver substitutions by name for this store's commands. */\n readonly drivers?: DriverOverrides;\n /**\n * Validates the server's page seed before the client uses it (any synchronous Standard\n * Schema). An invalid seed is reported with console.error and the store starts from `init`.\n */\n readonly schema?: StandardSchemaV1<unknown, S>;\n}\n\n/** The result of checking a seed against a store's schema. */\nexport type SeedCheck<S> =\n | { readonly ok: true; readonly state: S }\n | { readonly ok: false; readonly issues: readonly string[] };\n\n/** What `ctx.read()` accepts: anything that names a store and carries its state type. */\nexport interface StoreRef<S> {\n readonly name: string;\n /** Type-only marker for the state type. Never set at runtime. */\n readonly stateType?: (state: S) => S;\n}\n\n/** A live store: one per page on the client, one per request on the server, one per test. */\nexport interface StoreInstance<S, M extends Tagged> {\n readonly store: Store<S, M>;\n readonly state: S;\n /** Feeds a message through the store's `update` and notifies subscribers on change. */\n send(msg: M): void;\n /** Called after every state change. Returns an unsubscribe function. */\n subscribe(listener: (state: S, prev: S) => void): () => void;\n /** Per-instance driver substitutions (test fakes). Checked before the spec's. */\n drivers: DriverOverrides;\n /** Stops running commands and drops subscribers. */\n dispose(): void;\n /** Set by the registry that holds the instance (store-to-store `send`). */\n bindScope(resolve: StoreResolver): void;\n}\n\nexport interface Store<S, M extends Tagged> extends StoreRef<S> {\n readonly spec: StoreSpec<S, M>;\n /**\n * A fresh, independent instance. `initial` skips `init`'s state (a server seed, a test\n * fixture); `init`'s commands still run on the client.\n */\n instance(initial?: S): StoreInstance<S, M>;\n /** Checks a seed with `spec.schema` (ok as-is without one). */\n checkSeed(seed: unknown): SeedCheck<S>;\n /** Type-only marker for the message type. Never set at runtime. */\n readonly messageType?: (msg: M) => M;\n}\n\n/** Any store, with its types erased (for declarations and registries). */\nexport interface AnyStore {\n readonly name: string;\n instance(initial?: never): AnyStoreInstance;\n checkSeed(seed: unknown): SeedCheck<unknown>;\n}\n\n/** Any store instance, with its types erased. */\nexport interface AnyStoreInstance {\n readonly store: AnyStore;\n readonly state: unknown;\n send(msg: never): void;\n subscribe(listener: (state: unknown, prev: unknown) => void): () => void;\n dispose(): void;\n /**\n * Called by the registry that holds this instance, so the store's `send(other, msg)`\n * commands reach `other`'s instance in the same scope (gyral-czi.20).\n */\n bindScope?(resolve: StoreResolver): void;\n}\n\n/** Finds a store's instance in a scope (a registry). */\nexport type StoreResolver = (store: AnyStore) => AnyStoreInstance;\n\n/** Store instances substituted by store name (tests, islands). */\nexport type StoreOverrides = Readonly<Record<string, AnyStoreInstance>>;\n\n/** Defines a store. `name` keys it in registries, seeds and overrides, so keep it unique. */\nexport function defineStore<S, M extends Tagged>(name: string, spec: StoreSpec<S, M>): Store<S, M> {\n features.stores = bindStores; // components can use stores now (features.ts)\n const store: Store<S, M> = {\n name,\n spec,\n instance: (initial?: S) => createInstance(store, initial),\n checkSeed: (seed) => checkSeed(spec.schema, seed),\n };\n return store;\n}\n\nconst issuePath = (issue: StandardSchemaV1.Issue): string =>\n (issue.path ?? []).map((s) => String(typeof s === 'object' ? s.key : s)).join('.');\n\nfunction checkSeed<S>(\n schema: StandardSchemaV1<unknown, S> | undefined,\n seed: unknown,\n): SeedCheck<S> {\n // Sound without a schema: the seed is this store's state, serialized by the server.\n if (schema === undefined) return { ok: true, state: seed as S };\n const result = schema['~standard'].validate(seed);\n if (result instanceof Promise) {\n return { ok: false, issues: ['the store schema is async; seeds need a synchronous schema'] };\n }\n if (result.issues === undefined) return { ok: true, state: result.value };\n return {\n ok: false,\n issues: result.issues.map((i) => `${issuePath(i) || '(root)'}: ${i.message}`),\n };\n}\n\nfunction createInstance<S, M extends Tagged>(\n store: Store<S, M>,\n initial: S | undefined,\n): StoreInstance<S, M> {\n // Sound: send() only calls the reducer whose key equals msg._tag.\n const reducers = store.spec.update as unknown as Readonly<\n Record<string, ((state: S, msg: M) => Next<S, M>) | undefined>\n >;\n const listeners = new Set<(state: S, prev: S) => void>();\n let interpreter: Interpreter<M | IntentRejected> | undefined;\n let disposed = false;\n\n const [initState, initCommands] = splitNext(store.spec.init());\n let state = initial === undefined ? initState : initial;\n\n const resolve = (driver: AnyDriver): AnyDriver =>\n instance.drivers[driver.name] ?? store.spec.drivers?.[driver.name] ?? driver;\n\n let scope: StoreResolver | undefined;\n\n // Store-to-store writes: delivered synchronously to the other store's instance in scope.\n const deliver = ({ store: target, msg }: StoreSendInput): void => {\n if (scope === undefined) {\n console.warn(message(54, store.name, target.name));\n return;\n }\n (scope(target) as { send(msg: unknown): void }).send(msg);\n };\n\n const run = (commands: ReadonlyArray<Command<M | IntentRejected>>): void => {\n if (onServer || disposed || commands.length === 0) return; // never on the server (ADR 0012)\n for (const cmd of commands) {\n if (cmd.driver === STORE_SEND) {\n deliver(cmd.input as StoreSendInput);\n continue;\n }\n interpreter ??= makeInterpreter<M | IntentRejected>(\n resolve,\n (msg) => {\n // Stores have no IntentRejected reducer; only their own messages apply.\n if (msg._tag !== 'IntentRejected') instance.send(msg as M);\n },\n DEVTOOLS_ENABLED ? devCommands(() => `store:${store.name}`) : undefined,\n (cause, code, driver) => {\n const owner = `store \"${store.name}\"`;\n const text =\n code === 40\n ? message(40, owner, driver)\n : code === 41\n ? message(41, owner, driver)\n : message(43, owner, driver);\n fail(cause, 'command', text, { msg: driver });\n },\n );\n interpreter.run(cmd);\n }\n };\n\n const instance: StoreInstance<S, M> = {\n store,\n get state() {\n return state;\n },\n drivers: {},\n send(msg) {\n noteActivity(); // settled() keeps waiting while messages arrive (04)\n const reducer = reducers[msg._tag];\n if (reducer === undefined) {\n console.warn(message(55, store.name, msg._tag));\n return;\n }\n const prev = state;\n let result: Next<S, M>;\n try {\n result = reducer(prev, msg);\n } catch (cause) {\n // A store reducer that throws changes nothing (ADR 0024).\n fail(cause, 'store', message(76, store.name, msg._tag), { msg: msg._tag });\n return;\n }\n const [next, commands] = splitNext(result);\n state = next;\n if (DEVTOOLS_ENABLED) devStore(store.name, msg, prev, next);\n if (!Object.is(next, prev)) {\n // Each subscriber on its own: one that throws doesn't keep the others stale (ADR 0024).\n for (const listener of [...listeners]) {\n try {\n listener(next, prev);\n } catch (cause) {\n fail(cause, 'store', message(77, store.name), { msg: msg._tag });\n }\n }\n }\n run(commands);\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n dispose() {\n disposed = true;\n interpreter?.dispose();\n listeners.clear();\n },\n bindScope(resolver) {\n scope = resolver;\n },\n };\n\n // A seeded instance (hydration) starts init's commands after the hydrating renders flushed,\n // so a fast driver can't change state under them (same reason as ADR 0012 for components).\n if (initial === undefined) run(initCommands);\n else if (initCommands.length > 0)\n setTimeout(() => {\n run(initCommands);\n }, 0);\n return instance;\n}\n\n/** Marker driver for `send()`: `define()` delivers these to the resolved store instance. */\nexport const STORE_SEND = {\n name: '@gyral/store-send',\n run: () => undefined,\n /** A component's send: delivered to its instance of the store (features.ts). */\n local: (host: LocalHost, input: unknown) => {\n host.stores().send(input as StoreSendInput);\n },\n} as const;\n\n/** The input of a `send()` command. */\nexport interface StoreSendInput {\n readonly store: AnyStore;\n readonly msg: Tagged;\n}\n\n/** A command that sends `msg` to the component's instance of `store`. Writes are commands. */\n// NoInfer: the store decides the message type, so a literal (`{ _tag: 'Track', … }`) is checked\n// against it instead of widening `_tag` to string (store-to-store sends have no other context).\nexport function send<S, M extends Tagged>(store: Store<S, M>, msg: NoInfer<M>): Command<never> {\n const input: StoreSendInput = { store, msg };\n return { driver: STORE_SEND, input, onSuccess: () => undefined };\n}\n\n/** Framework message: a store this component reads changed (ADR 0013). Optional reducer. */\nexport interface StoreChanged {\n readonly _tag: 'StoreChanged';\n /** The store's name. Narrow with `changed(store, msg)`. */\n readonly store: string;\n readonly state: unknown;\n readonly prev: unknown;\n}\n\n/** Narrows a `StoreChanged` message to one store's typed state, or `undefined`. */\nexport function changed<S>(\n store: StoreRef<S>,\n msg: StoreChanged,\n): { readonly state: S; readonly prev: S } | undefined {\n // Sound: the message was built from this store's instance (matched by unique name).\n return msg.store === store.name ? { state: msg.state as S, prev: msg.prev as S } : undefined;\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["// Shared state: stores as \"props from the side\" (docs/design-docs/0013-shared-state.md).\n// A store is MVI without a view: init + pure update, commands run by its own interpreter.\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { DEVTOOLS_ENABLED, devCommands, devStore } from '#devtools';\nimport {\n splitNext,\n type AnyDriver,\n type Command,\n type DriverOverrides,\n type Next,\n} from './command.js';\nimport { fail } from './errors.js';\nimport { features, type LocalHost } from './features.js';\nimport { makeInterpreter, type Interpreter } from './internal/interpreter.js';\nimport { noteActivity } from './scheduler.js';\nimport { bindStores } from './store-binding.js';\nimport type { IntentRejected, Tagged, Variant } from './types.js';\nimport { message } from './view/index.js';\n\n/** No DOM means a server render: store commands never run there (ADR 0012). */\nconst onServer = typeof document === 'undefined';\n\n/** One pure reducer per message tag, like a component's `update` but without context. */\nexport type StoreUpdate<S, M extends Tagged> = {\n readonly [K in M['_tag']]: (state: S, msg: Variant<M, K>) => Next<S, M>;\n};\n\nexport interface StoreSpec<S, M extends Tagged> {\n /** Initial state (and optional commands, e.g. load a saved cart). */\n readonly init: () => Next<S, M>;\n readonly update: StoreUpdate<S, M>;\n /** Driver substitutions by name for this store's commands. */\n readonly drivers?: DriverOverrides;\n /**\n * Validates the server's page seed before the client uses it (any synchronous Standard\n * Schema). An invalid seed is reported with console.error and the store starts from `init`.\n */\n readonly schema?: StandardSchemaV1<unknown, S>;\n}\n\n/** The result of checking a seed against a store's schema. */\nexport type SeedCheck<S> =\n | { readonly ok: true; readonly state: S }\n | { readonly ok: false; readonly issues: readonly string[] };\n\n/** What `ctx.read()` accepts: anything that names a store and carries its state type. */\nexport interface StoreRef<S> {\n readonly name: string;\n /** Type-only marker for the state type. Never set at runtime. */\n readonly stateType?: (state: S) => S;\n}\n\n/** A live store: one per page on the client, one per request on the server, one per test. */\nexport interface StoreInstance<S, M extends Tagged> {\n readonly store: Store<S, M>;\n readonly state: S;\n /** Feeds a message through the store's `update` and notifies subscribers on change. */\n send(msg: M): void;\n /** Called after every state change. Returns an unsubscribe function. */\n subscribe(listener: (state: S, prev: S) => void): () => void;\n /** Per-instance driver substitutions (test fakes). Checked before the spec's. */\n drivers: DriverOverrides;\n /** Stops running commands and drops subscribers. */\n dispose(): void;\n /** Set by the registry that holds the instance (store-to-store `send`). */\n bindScope(resolve: StoreResolver): void;\n}\n\nexport interface Store<S, M extends Tagged> extends StoreRef<S> {\n readonly spec: StoreSpec<S, M>;\n /**\n * A fresh, independent instance. `initial` skips `init`'s state (a server seed, a test\n * fixture); `init`'s commands still run on the client.\n */\n instance(initial?: S): StoreInstance<S, M>;\n /** Checks a seed with `spec.schema` (ok as-is without one). */\n checkSeed(seed: unknown): SeedCheck<S>;\n /** Type-only marker for the message type. Never set at runtime. */\n readonly messageType?: (msg: M) => M;\n}\n\n/** Any store, with its types erased (for declarations and registries). */\nexport interface AnyStore {\n readonly name: string;\n instance(initial?: never): AnyStoreInstance;\n checkSeed(seed: unknown): SeedCheck<unknown>;\n}\n\n/** Any store instance, with its types erased. */\nexport interface AnyStoreInstance {\n readonly store: AnyStore;\n readonly state: unknown;\n send(msg: never): void;\n subscribe(listener: (state: unknown, prev: unknown) => void): () => void;\n dispose(): void;\n /**\n * Called by the registry that holds this instance, so the store's `send(other, msg)`\n * commands reach `other`'s instance in the same scope.\n */\n bindScope?(resolve: StoreResolver): void;\n}\n\n/** Finds a store's instance in a scope (a registry). */\nexport type StoreResolver = (store: AnyStore) => AnyStoreInstance;\n\n/** Store instances substituted by store name (tests, islands). */\nexport type StoreOverrides = Readonly<Record<string, AnyStoreInstance>>;\n\n/** Defines a store. `name` keys it in registries, seeds and overrides, so keep it unique. */\nexport function defineStore<S, M extends Tagged>(name: string, spec: StoreSpec<S, M>): Store<S, M> {\n features.stores = bindStores; // components can use stores now (features.ts)\n const store: Store<S, M> = {\n name,\n spec,\n instance: (initial?: S) => createInstance(store, initial),\n checkSeed: (seed) => checkSeed(spec.schema, seed),\n };\n return store;\n}\n\nconst issuePath = (issue: StandardSchemaV1.Issue): string =>\n (issue.path ?? []).map((s) => String(typeof s === 'object' ? s.key : s)).join('.');\n\nfunction checkSeed<S>(\n schema: StandardSchemaV1<unknown, S> | undefined,\n seed: unknown,\n): SeedCheck<S> {\n // Sound without a schema: the seed is this store's state, serialized by the server.\n if (schema === undefined) return { ok: true, state: seed as S };\n const result = schema['~standard'].validate(seed);\n if (result instanceof Promise) {\n return { ok: false, issues: ['the store schema is async; seeds need a synchronous schema'] };\n }\n if (result.issues === undefined) return { ok: true, state: result.value };\n return {\n ok: false,\n issues: result.issues.map((i) => `${issuePath(i) || '(root)'}: ${i.message}`),\n };\n}\n\nfunction createInstance<S, M extends Tagged>(\n store: Store<S, M>,\n initial: S | undefined,\n): StoreInstance<S, M> {\n // Sound: send() only calls the reducer whose key equals msg._tag.\n const reducers = store.spec.update as unknown as Readonly<\n Record<string, ((state: S, msg: M) => Next<S, M>) | undefined>\n >;\n const listeners = new Set<(state: S, prev: S) => void>();\n let interpreter: Interpreter<M | IntentRejected> | undefined;\n let disposed = false;\n\n const [initState, initCommands] = splitNext(store.spec.init());\n let state = initial === undefined ? initState : initial;\n\n const resolve = (driver: AnyDriver): AnyDriver =>\n instance.drivers[driver.name] ?? store.spec.drivers?.[driver.name] ?? driver;\n\n let scope: StoreResolver | undefined;\n\n // Store-to-store writes: delivered synchronously to the other store's instance in scope.\n const deliver = ({ store: target, msg }: StoreSendInput): void => {\n if (scope === undefined) {\n console.warn(message(54, store.name, target.name));\n return;\n }\n (scope(target) as { send(msg: unknown): void }).send(msg);\n };\n\n const run = (commands: ReadonlyArray<Command<M | IntentRejected>>): void => {\n if (onServer || disposed || commands.length === 0) return; // never on the server (ADR 0012)\n for (const cmd of commands) {\n if (cmd.driver === STORE_SEND) {\n deliver(cmd.input as StoreSendInput);\n continue;\n }\n interpreter ??= makeInterpreter<M | IntentRejected>(\n resolve,\n (msg) => {\n // Stores have no IntentRejected reducer; only their own messages apply.\n if (msg._tag !== 'IntentRejected') instance.send(msg as M);\n },\n DEVTOOLS_ENABLED ? devCommands(() => `store:${store.name}`) : undefined,\n (cause, code, driver) => {\n const owner = `store \"${store.name}\"`;\n const text =\n code === 40\n ? message(40, owner, driver)\n : code === 41\n ? message(41, owner, driver)\n : message(43, owner, driver);\n fail(cause, 'command', text, { msg: driver });\n },\n );\n interpreter.run(cmd);\n }\n };\n\n const instance: StoreInstance<S, M> = {\n store,\n get state() {\n return state;\n },\n drivers: {},\n send(msg) {\n noteActivity(); // settled() keeps waiting while messages arrive (04)\n const reducer = reducers[msg._tag];\n if (reducer === undefined) {\n console.warn(message(55, store.name, msg._tag));\n return;\n }\n const prev = state;\n let result: Next<S, M>;\n try {\n result = reducer(prev, msg);\n } catch (cause) {\n // A store reducer that throws changes nothing (ADR 0024).\n fail(cause, 'store', message(76, store.name, msg._tag), { msg: msg._tag });\n return;\n }\n const [next, commands] = splitNext(result);\n state = next;\n if (DEVTOOLS_ENABLED) devStore(store.name, msg, prev, next);\n if (!Object.is(next, prev)) {\n // Each subscriber on its own: one that throws doesn't keep the others stale (ADR 0024).\n for (const listener of [...listeners]) {\n try {\n listener(next, prev);\n } catch (cause) {\n fail(cause, 'store', message(77, store.name), { msg: msg._tag });\n }\n }\n }\n run(commands);\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n dispose() {\n disposed = true;\n interpreter?.dispose();\n listeners.clear();\n },\n bindScope(resolver) {\n scope = resolver;\n },\n };\n\n // A seeded instance (hydration) starts init's commands after the hydrating renders flushed,\n // so a fast driver can't change state under them (same reason as ADR 0012 for components).\n if (initial === undefined) run(initCommands);\n else if (initCommands.length > 0)\n setTimeout(() => {\n run(initCommands);\n }, 0);\n return instance;\n}\n\n/** Marker driver for `send()`: `define()` delivers these to the resolved store instance. */\nexport const STORE_SEND = {\n name: '@gyral/store-send',\n run: () => undefined,\n /** A component's send: delivered to its instance of the store (features.ts). */\n local: (host: LocalHost, input: unknown) => {\n host.stores().send(input as StoreSendInput);\n },\n} as const;\n\n/** The input of a `send()` command. */\nexport interface StoreSendInput {\n readonly store: AnyStore;\n readonly msg: Tagged;\n}\n\n/** A command that sends `msg` to the component's instance of `store`. Writes are commands. */\n// NoInfer: the store decides the message type, so a literal (`{ _tag: 'Track', … }`) is checked\n// against it instead of widening `_tag` to string (store-to-store sends have no other context).\nexport function send<S, M extends Tagged>(store: Store<S, M>, msg: NoInfer<M>): Command<never> {\n const input: StoreSendInput = { store, msg };\n return { driver: STORE_SEND, input, onSuccess: () => undefined };\n}\n\n/** Framework message: a store this component reads changed (ADR 0013). Optional reducer. */\nexport interface StoreChanged {\n readonly _tag: 'StoreChanged';\n /** The store's name. Narrow with `changed(store, msg)`. */\n readonly store: string;\n readonly state: unknown;\n readonly prev: unknown;\n}\n\n/** Narrows a `StoreChanged` message to one store's typed state, or `undefined`. */\nexport function changed<S>(\n store: StoreRef<S>,\n msg: StoreChanged,\n): { readonly state: S; readonly prev: S } | undefined {\n // Sound: the message was built from this store's instance (matched by unique name).\n return msg.store === store.name ? { state: msg.state as S, prev: msg.prev as S } : undefined;\n}\n"]}
@@ -30,6 +30,6 @@ export interface SubscriptionOptions<E> {
30
30
  * emit(reduxStore.getState());
31
31
  * return reduxStore.subscribe(() => emit(reduxStore.getState()));
32
32
  * });
33
- * // init: [state, [command(cart, undefined, { onSuccess: (c) => ({ _tag: 'Cart', c }) })]]
33
+ * // subscriptions: () => [command(cart, undefined, { onSuccess: (c) => ({ _tag: 'Cart', c }) })]
34
34
  */
35
35
  export declare function subscription<O, I = undefined, E = unknown>(name: string, subscribe: (emit: (value: O) => void, ctx: SubscriptionContext<I>) => Unsubscribe, options?: SubscriptionOptions<E>): Driver<I, O, E>;
@@ -25,7 +25,7 @@ const asError = (reason) => reason instanceof Error ? reason : new DOMException(
25
25
  * emit(reduxStore.getState());
26
26
  * return reduxStore.subscribe(() => emit(reduxStore.getState()));
27
27
  * });
28
- * // init: [state, [command(cart, undefined, { onSuccess: (c) => ({ _tag: 'Cart', c }) })]]
28
+ * // subscriptions: () => [command(cart, undefined, { onSuccess: (c) => ({ _tag: 'Cart', c }) })]
29
29
  */
30
30
  export function subscription(name, subscribe, options = {}) {
31
31
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"subscription.js","sourceRoot":"","sources":["../src/subscription.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAuB1C,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,IAA6B,EAAQ,EAAE;IACpE,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO;IAC/B,IAAI,CAAC;QACH,IAAI,OAAO,IAAI,KAAK,UAAU;YAAE,IAAI,EAAE,CAAC;;YAClC,IAAI,CAAC,WAAW,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,MAAe,EAAS,EAAE,CACzC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AAE/E;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,SAAiF,EACjF,UAAkC,EAAE;IAEpC,OAAO;QACL,IAAI;QACJ,WAAW,EAAE,QAAQ;QACrB,GAAG,OAAO;QACV,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAC/B,IAAI,OAAO,CAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;YACtC,oFAAoF;YACpF,MAAM,GAAG,GAA2C,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YACrE,qFAAqF;YACrF,uEAAuE;YACvE,MAAM,GAAG,GAAG,CAAC,KAAc,EAAQ,EAAE;gBACnC,IAAI,GAAG,CAAC,KAAK;oBAAE,OAAO;gBACtB,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC;gBACjB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC7C,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;gBACxB,2EAA2E;gBAC3E,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC,CAAC;YACF,MAAM,OAAO,GAAG,GAAS,EAAE;gBACzB,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAC9B,CAAC,CAAC;YACF,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,EAAE,CAAC;gBACV,OAAO;YACT,CAAC;YACD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,SAAS,CACpB,CAAC,KAAK,EAAE,EAAE;oBACR,IAAI,CAAC,GAAG,CAAC,KAAK;wBAAE,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC9B,CAAC,EACD,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAC7B,CAAC;gBACF,yEAAyE;gBACzE,IAAI,GAAG,CAAC,KAAK;oBAAE,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;;oBAC9B,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YACvB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,GAAG,CAAC,KAAK,CAAC,CAAC;YACb,CAAC;QACH,CAAC,CAAC;KACL,CAAC;AACJ,CAAC","sourcesContent":["// Streaming drivers over sources Gyral doesn't own (ADR 0006 \"Outside sources\"): TC39 signals,\n// Redux-style stores, XState actors, WebSocket feeds. `subscription()` turns \"subscribe and\n// return how to stop\" into a driver: values become messages through the command's\n// `onSuccess`, and the source is released when the command is switched away or its component\n// disconnects. A standalone module: apps that don't import it don't bundle it.\nimport type { Concurrency, Driver } from './command.js';\nimport { fail } from './errors.js';\nimport { message } from './view/index.js';\n\n/** How to stop listening: a function, or an object with `unsubscribe()` (RxJS, XState). */\nexport type Unsubscribe = (() => void) | { readonly unsubscribe: () => void };\n\nexport interface SubscriptionContext<I> {\n /** The command's input (a room code, a URL, a store key). */\n readonly input: I;\n /** Aborts when the command is switched away or its component disconnects. */\n readonly signal: AbortSignal;\n /**\n * Ends the subscription with an error: the source is released and the command's\n * `onFailure` gets it. Wrap the driver in `retry()` to subscribe again instead.\n */\n readonly fail: (error: unknown) => void;\n}\n\n/** Driver options a subscription may set. `concurrency` defaults to `'switch'`. */\nexport interface SubscriptionOptions<E> {\n readonly concurrency?: Concurrency;\n readonly toError?: (cause: unknown) => E;\n}\n\nconst release = (name: string, stop: Unsubscribe | undefined): void => {\n if (stop === undefined) return;\n try {\n if (typeof stop === 'function') stop();\n else stop.unsubscribe();\n } catch (error) {\n fail(error, 'subscribe', message(42, name), { msg: name });\n }\n};\n\nconst asError = (reason: unknown): Error =>\n reason instanceof Error ? reason : new DOMException('Aborted', 'AbortError');\n\n/**\n * A streaming driver over a source Gyral doesn't own. `subscribe` starts listening, calls\n * `emit` with each value (it may emit the current value at once) and returns how to stop\n * (a function, or an object with `unsubscribe()`).\n * The subscription lasts while the command runs; aborting it (lane `switch`, disconnect)\n * releases the source, and emits after that are ignored. Substitute it by `name` in tests.\n *\n * const cart = subscription<CartState>('cart', (emit) => {\n * emit(reduxStore.getState());\n * return reduxStore.subscribe(() => emit(reduxStore.getState()));\n * });\n * // init: [state, [command(cart, undefined, { onSuccess: (c) => ({ _tag: 'Cart', c }) })]]\n */\nexport function subscription<O, I = undefined, E = unknown>(\n name: string,\n subscribe: (emit: (value: O) => void, ctx: SubscriptionContext<I>) => Unsubscribe,\n options: SubscriptionOptions<E> = {},\n): Driver<I, O, E> {\n return {\n name,\n concurrency: 'switch',\n ...options,\n run: (input, { signal, emit }) =>\n new Promise<never>((_resolve, reject) => {\n // `ended` may flip inside subscribe (fail() or an abort), so it lives in an object.\n const sub: { ended: boolean; stop?: Unsubscribe } = { ended: false };\n // Deliberately not wrapped in an Error: the driver's toError (or onFailure) sees the\n // source's own error (a WebSocket's error event, a store's rejection).\n const end = (error: unknown): void => {\n if (sub.ended) return;\n sub.ended = true;\n signal.removeEventListener('abort', onAbort);\n release(name, sub.stop);\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n reject(error);\n };\n const onAbort = (): void => {\n end(asError(signal.reason));\n };\n if (signal.aborted) {\n onAbort();\n return;\n }\n signal.addEventListener('abort', onAbort, { once: true });\n try {\n const stop = subscribe(\n (value) => {\n if (!sub.ended) emit(value);\n },\n { input, signal, fail: end },\n );\n // fail() or an abort while subscribing ended it before `stop` was known.\n if (sub.ended) release(name, stop);\n else sub.stop = stop;\n } catch (error) {\n end(error);\n }\n }),\n };\n}\n"]}
1
+ {"version":3,"file":"subscription.js","sourceRoot":"","sources":["../src/subscription.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAuB1C,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,IAA6B,EAAQ,EAAE;IACpE,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO;IAC/B,IAAI,CAAC;QACH,IAAI,OAAO,IAAI,KAAK,UAAU;YAAE,IAAI,EAAE,CAAC;;YAClC,IAAI,CAAC,WAAW,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,MAAe,EAAS,EAAE,CACzC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AAE/E;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,SAAiF,EACjF,UAAkC,EAAE;IAEpC,OAAO;QACL,IAAI;QACJ,WAAW,EAAE,QAAQ;QACrB,GAAG,OAAO;QACV,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAC/B,IAAI,OAAO,CAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;YACtC,oFAAoF;YACpF,MAAM,GAAG,GAA2C,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YACrE,qFAAqF;YACrF,uEAAuE;YACvE,MAAM,GAAG,GAAG,CAAC,KAAc,EAAQ,EAAE;gBACnC,IAAI,GAAG,CAAC,KAAK;oBAAE,OAAO;gBACtB,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC;gBACjB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC7C,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;gBACxB,2EAA2E;gBAC3E,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC,CAAC;YACF,MAAM,OAAO,GAAG,GAAS,EAAE;gBACzB,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAC9B,CAAC,CAAC;YACF,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,EAAE,CAAC;gBACV,OAAO;YACT,CAAC;YACD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,SAAS,CACpB,CAAC,KAAK,EAAE,EAAE;oBACR,IAAI,CAAC,GAAG,CAAC,KAAK;wBAAE,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC9B,CAAC,EACD,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAC7B,CAAC;gBACF,yEAAyE;gBACzE,IAAI,GAAG,CAAC,KAAK;oBAAE,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;;oBAC9B,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YACvB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,GAAG,CAAC,KAAK,CAAC,CAAC;YACb,CAAC;QACH,CAAC,CAAC;KACL,CAAC;AACJ,CAAC","sourcesContent":["// Streaming drivers over sources Gyral doesn't own (ADR 0006 \"Outside sources\"): TC39 signals,\n// Redux-style stores, XState actors, WebSocket feeds. `subscription()` turns \"subscribe and\n// return how to stop\" into a driver: values become messages through the command's\n// `onSuccess`, and the source is released when the command is switched away or its component\n// disconnects. A standalone module: apps that don't import it don't bundle it.\nimport type { Concurrency, Driver } from './command.js';\nimport { fail } from './errors.js';\nimport { message } from './view/index.js';\n\n/** How to stop listening: a function, or an object with `unsubscribe()` (RxJS, XState). */\nexport type Unsubscribe = (() => void) | { readonly unsubscribe: () => void };\n\nexport interface SubscriptionContext<I> {\n /** The command's input (a room code, a URL, a store key). */\n readonly input: I;\n /** Aborts when the command is switched away or its component disconnects. */\n readonly signal: AbortSignal;\n /**\n * Ends the subscription with an error: the source is released and the command's\n * `onFailure` gets it. Wrap the driver in `retry()` to subscribe again instead.\n */\n readonly fail: (error: unknown) => void;\n}\n\n/** Driver options a subscription may set. `concurrency` defaults to `'switch'`. */\nexport interface SubscriptionOptions<E> {\n readonly concurrency?: Concurrency;\n readonly toError?: (cause: unknown) => E;\n}\n\nconst release = (name: string, stop: Unsubscribe | undefined): void => {\n if (stop === undefined) return;\n try {\n if (typeof stop === 'function') stop();\n else stop.unsubscribe();\n } catch (error) {\n fail(error, 'subscribe', message(42, name), { msg: name });\n }\n};\n\nconst asError = (reason: unknown): Error =>\n reason instanceof Error ? reason : new DOMException('Aborted', 'AbortError');\n\n/**\n * A streaming driver over a source Gyral doesn't own. `subscribe` starts listening, calls\n * `emit` with each value (it may emit the current value at once) and returns how to stop\n * (a function, or an object with `unsubscribe()`).\n * The subscription lasts while the command runs; aborting it (lane `switch`, disconnect)\n * releases the source, and emits after that are ignored. Substitute it by `name` in tests.\n *\n * const cart = subscription<CartState>('cart', (emit) => {\n * emit(reduxStore.getState());\n * return reduxStore.subscribe(() => emit(reduxStore.getState()));\n * });\n * // subscriptions: () => [command(cart, undefined, { onSuccess: (c) => ({ _tag: 'Cart', c }) })]\n */\nexport function subscription<O, I = undefined, E = unknown>(\n name: string,\n subscribe: (emit: (value: O) => void, ctx: SubscriptionContext<I>) => Unsubscribe,\n options: SubscriptionOptions<E> = {},\n): Driver<I, O, E> {\n return {\n name,\n concurrency: 'switch',\n ...options,\n run: (input, { signal, emit }) =>\n new Promise<never>((_resolve, reject) => {\n // `ended` may flip inside subscribe (fail() or an abort), so it lives in an object.\n const sub: { ended: boolean; stop?: Unsubscribe } = { ended: false };\n // Deliberately not wrapped in an Error: the driver's toError (or onFailure) sees the\n // source's own error (a WebSocket's error event, a store's rejection).\n const end = (error: unknown): void => {\n if (sub.ended) return;\n sub.ended = true;\n signal.removeEventListener('abort', onAbort);\n release(name, sub.stop);\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n reject(error);\n };\n const onAbort = (): void => {\n end(asError(signal.reason));\n };\n if (signal.aborted) {\n onAbort();\n return;\n }\n signal.addEventListener('abort', onAbort, { once: true });\n try {\n const stop = subscribe(\n (value) => {\n if (!sub.ended) emit(value);\n },\n { input, signal, fail: end },\n );\n // fail() or an abort while subscribing ended it before `stop` was known.\n if (sub.ended) release(name, stop);\n else sub.stop = stop;\n } catch (error) {\n end(error);\n }\n }),\n };\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -32,7 +32,7 @@ export interface IntentInput {
32
32
  readonly key: string | undefined;
33
33
  /** For `toggle` intents (popover, `<details>`): the state it changed to. */
34
34
  readonly newState: 'open' | 'closed' | undefined;
35
- /** For `command` intents: the invoker's command (e.g. `--clear`) and source (gyral-czi.6). */
35
+ /** For `command` intents: the invoker's command (e.g. `--clear`) and source. */
36
36
  readonly command: CommandInfo | undefined;
37
37
  }
38
38
  /** Reads a declared store's current state, typed by the store (ADR 0013). */
@@ -100,17 +100,22 @@ export interface IntentRejected {
100
100
  */
101
101
  readonly values?: FormFields | undefined;
102
102
  }
103
- type ParseResult<M> = M | IntentRejected | undefined;
103
+ /**
104
+ * What an intent parser returns: a message, `IntentRejected` (invalid input), or `undefined` to
105
+ * decline the event.
106
+ */
107
+ export type ParseResult<M> = M | IntentRejected | undefined;
104
108
  /**
105
109
  * Parses a platform event into one message variant, `IntentRejected`, or `undefined` to
106
110
  * decline it: a synchronous `undefined` passes the event to the next intent outward for the
107
- * same event (view/05-element.md "Declining"). May be async because schema validation may be;
111
+ * same event. May be async because schema validation may be;
108
112
  * an async parser keeps the event. `ctx` is the read-only context reducers get (props as
109
113
  * they are when the event fires, `read(store)`) plus `state`, the component's state at that
110
114
  * moment; parsers that don't need it take one parameter.
111
115
  */
112
116
  export type IntentParser<M, P = unknown, S = unknown> = (input: IntentInput, ctx: ParserCtx<P, S>) => ParseResult<M> | Promise<ParseResult<M>>;
113
- type Variant<M extends Tagged, K> = Extract<M, {
117
+ /** The member of message union `M` whose tag is `K`: `Variant<Msg, 'Add'>`. */
118
+ export type Variant<M extends Tagged, K> = Extract<M, {
114
119
  readonly _tag: K;
115
120
  }>;
116
121
  /**
@@ -138,7 +143,14 @@ interface TrueOnlyForFieldlessMessage {
138
143
  export type Intents<M extends Tagged, P = unknown, N extends string = string, S = unknown> = {
139
144
  readonly [K in N]: ParserFor<M, P, K, S>;
140
145
  };
141
- type Reducer<S, M, Msg, P> = (state: S, msg: Msg, ctx: Ctx<P>) => Next<S, M>;
146
+ /**
147
+ * One entry of `update`: takes the state, one message (`Msg`, a variant of `M` or a framework
148
+ * message) and the read-only context, and returns the next state, optionally with commands
149
+ * that produce more `M` messages. To type a reducer written outside the spec:
150
+ *
151
+ * const add: Reducer<State, Msg, Variant<Msg, 'Add'>, object> = (s, m) => …
152
+ */
153
+ export type Reducer<S, M, Msg, P = object> = (state: S, msg: Msg, ctx: Ctx<P>) => Next<S, M>;
142
154
  /**
143
155
  * One pure reducer per message tag (exhaustive by construction), plus optional reducers
144
156
  * for framework messages. Reducers may return commands.
@@ -163,7 +175,7 @@ export type IntentNames<N extends string> = {
163
175
  readonly [K in N]: K;
164
176
  };
165
177
  /**
166
- * The prop builders for props type `P` (view/05-element.md "Props"): one `prop.*` builder per
178
+ * The prop builders for props type `P`: one `prop.*` builder per
167
179
  * key. A builder's output type must fit the prop's type, so a prop whose type excludes
168
180
  * `undefined` needs `required: true` or a `default` (the honesty rule, ADR 0007 addendum).
169
181
  * Without explicit type arguments, `define()` infers `P` from the builders.
@@ -202,7 +214,7 @@ type UpdateField<S, M extends Tagged, P> = [M] extends [never] ? {
202
214
  * `spec.shadow`: `true` (the default) or an object for a shadow root, `false` for light DOM.
203
215
  * `delegatesFocus: true`: focusing the host, or clicking a non-focusable part of it, focuses its
204
216
  * first focusable element, and `:focus` matches the host while focus is inside; the server
205
- * writes `shadowrootdelegatesfocus` on the declarative shadow root (view/05-element.md "Focus").
217
+ * writes `shadowrootdelegatesfocus` on the declarative shadow root.
206
218
  */
207
219
  export type ShadowOption = boolean | {
208
220
  readonly delegatesFocus?: boolean;
@@ -257,7 +269,7 @@ interface SpecBody<S, M extends Tagged, P, N extends string> {
257
269
  * animation frame, so many messages in one frame cost one render: `renderOnFrame: ['Ticked']`.
258
270
  * Reducers still run at once. Any other change to the component renders in the usual
259
271
  * microtask flush, taking pending frame work with it. `'StoreChanged'` covers store changes.
260
- * Pages that get no frames (hidden) render from a 100 ms timer. view/04-scheduler.md.
272
+ * Pages that get no frames (hidden) render from a 100 ms timer.
261
273
  */
262
274
  readonly renderOnFrame?: readonly (M['_tag'] | 'StoreChanged')[];
263
275
  /**
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { GyralError } from './errors.js';\nimport type { Prop } from './prop.js';\nimport type { ChildValue, Styles } from './view/index.js';\nimport type { CommandInfo } from './invokers.js';\nimport type { HydrateStrategy } from './islands.js';\nimport type { Command, Next } from './command.js';\nimport type { AnyStore, StoreChanged, StoreRef } from './store.js';\nimport type {\n FormAssociation,\n FormDisabled,\n FormReset,\n FormStateRestore,\n} from './form-associated.js';\n\n/** Every message is a tagged object; the tag is also the intent name used in markup. */\nexport interface Tagged {\n readonly _tag: string;\n}\n\n/** What the intent layer sees when an element's intent fires. */\nexport interface IntentInput {\n /** The intent's name (a message tag): the `data-intent-<event>` or `data-intent` value. */\n readonly name: string;\n readonly event: Event;\n /** The element carrying the intent attribute (`event.currentTarget` is Gyral's root). */\n readonly target: Element;\n /** `value` of the input, select, textarea or button that carries the intent. */\n readonly value: string | undefined;\n /** `checked` for checkbox and radio inputs. */\n readonly checked: boolean | undefined;\n /** Submitted data (including the submitter button) when the intent is on a `<form>`. */\n readonly formData: FormData | undefined;\n /**\n * `detail` of any `CustomEvent`: a Gyral child's output, or the event of another custom\n * element (a map's `marker-select`). Unknown until the parser checks it (a guard, a schema).\n */\n readonly detail: unknown;\n /** `KeyboardEvent.key` for `keydown`/`keyup` intents. */\n readonly key: string | undefined;\n /** For `toggle` intents (popover, `<details>`): the state it changed to. */\n readonly newState: 'open' | 'closed' | undefined;\n /** For `command` intents: the invoker's command (e.g. `--clear`) and source (gyral-czi.6). */\n readonly command: CommandInfo | undefined;\n}\n\n/** Reads a declared store's current state, typed by the store (ADR 0013). */\nexport type StoreReader = <S>(store: StoreRef<S>) => S;\n\n/** Read-only context handed to every reducer and to the view (ADR 0007, 0013). */\nexport interface Ctx<P> {\n /** Props from above. */\n readonly props: P;\n /** Stores from the side: `read(cart).lines`. The store must be in `spec.stores`. */\n readonly read: StoreReader;\n}\n\n/**\n * What an intent parser gets besides the input: the reducers' context plus the component's\n * current state, read-only. Parsers decide synchronously (`preventDefault()`), and where the\n * user is often decides it: Tab leaves a grid only from its last cell.\n */\nexport interface ParserCtx<P, S = unknown> extends Ctx<P> {\n /** The state when the event fires. */\n readonly state: Readonly<S>;\n}\n\n/** Framework message: declared props changed after the first render (ADR 0007). */\nexport interface PropsChanged<P> {\n readonly _tag: 'PropsChanged';\n readonly props: P;\n readonly prev: P;\n}\n\n/**\n * Framework message: the component is live in the browser (ADR 0012 addendum). Sent once,\n * after the first client render has completed, to every client-side instance. Use it for\n * progressive enhancement: render the no-JS form on the server and in the first client render\n * (so hydration matches), then switch to the enhanced UI in the `Hydrated` reducer.\n */\nexport interface Hydrated {\n readonly _tag: 'Hydrated';\n /** `true` when the instance resumed from server-rendered markup (a hydration seed). */\n readonly serverRendered: boolean;\n}\n\n/**\n * Framework message (ADR 0024): an update, an intent parser or a command of this component\n * failed. The failure was reported (`reportError`, or claimed by an ancestor's boundary) and\n * changed nothing; reduce it to show an error state or to reset. A failure while reducing\n * `Errored` is reported and not sent again.\n */\nexport interface Errored {\n readonly _tag: 'Errored';\n readonly phase: 'update' | 'parse' | 'command';\n readonly error: GyralError;\n}\n\n/** One validation problem. `path` is dot-joined and matches the field's `name`. */\nexport interface FieldIssue {\n readonly path: string;\n readonly message: string;\n}\n\n/** Submitted text values of a form (Files are dropped: they can't be re-filled). */\nexport type FormFields = Readonly<Record<string, string | readonly string[]>>;\n\n/** Framework message: an intent's input failed schema validation (ADR 0008). */\nexport interface IntentRejected {\n readonly _tag: 'IntentRejected';\n /** The `data-intent` name whose input was rejected. */\n readonly intent: string;\n readonly issues: readonly FieldIssue[];\n /**\n * For `form()` rejections: what the user submitted, so the view can re-fill the form. On\n * the no-JS path (ADR 0008 server half) the server re-renders the page from this.\n */\n // `| undefined` so schemas with plain optional fields (valibot `optional`) can produce it.\n readonly values?: FormFields | undefined;\n}\n\ntype ParseResult<M> = M | IntentRejected | undefined;\n\n/**\n * Parses a platform event into one message variant, `IntentRejected`, or `undefined` to\n * decline it: a synchronous `undefined` passes the event to the next intent outward for the\n * same event (view/05-element.md \"Declining\"). May be async because schema validation may be;\n * an async parser keeps the event. `ctx` is the read-only context reducers get (props as\n * they are when the event fires, `read(store)`) plus `state`, the component's state at that\n * moment; parsers that don't need it take one parameter.\n */\nexport type IntentParser<M, P = unknown, S = unknown> = (\n input: IntentInput,\n ctx: ParserCtx<P, S>,\n) => ParseResult<M> | Promise<ParseResult<M>>;\n\ntype Variant<M extends Tagged, K> = Extract<M, { readonly _tag: K }>;\n\n/**\n * The parser for intent name `K` (ADR 0023): a name that is a message tag parses into that\n * variant; any other name is an intent of its own whose parser may produce any message.\n */\nexport type ParserFor<M extends Tagged, P, K, S = unknown> = string extends K\n ? // The wide form (any intent name), which every specific spec is assignable to.\n IntentParser<M, P, S> | true | TrueOnlyForFieldlessMessage\n : K extends M['_tag']\n ? IntentParser<Variant<M, K>, P, S> | TagOnly<Variant<M, K>>\n : IntentParser<M, P, S>;\n\n/**\n * `true` in the intent table: the intent sends its own tag, `{ _tag: K }` (as Elm's `onClick\n * Deal` names the message itself). Only for a variant with no field besides `_tag`; for any\n * other the type names the rule.\n */\ntype TagOnly<V> = [Exclude<keyof V, '_tag'>] extends [never] ? true : TrueOnlyForFieldlessMessage;\n\ninterface TrueOnlyForFieldlessMessage {\n readonly 'true only sends a message with no fields besides _tag; write a parser': never;\n}\n\n/**\n * Intent parsers keyed by intent name (ADR 0001, ADR 0023). The keys are the component's\n * intent names: `define<State, Msg>()(tag, { intent: { Archive: …, Increment: … } })` gives the\n * view `i.Archive` and `i.Increment`. A key that is a message tag must return that variant; any\n * other key may return any message (several controls that change one thing can also share one\n * intent and tell themselves apart by `name`). Messages from drivers need no parser.\n */\nexport type Intents<M extends Tagged, P = unknown, N extends string = string, S = unknown> = {\n readonly [K in N]: ParserFor<M, P, K, S>;\n};\n\ntype Reducer<S, M, Msg, P> = (state: S, msg: Msg, ctx: Ctx<P>) => Next<S, M>;\n\n/**\n * One pure reducer per message tag (exhaustive by construction), plus optional reducers\n * for framework messages. Reducers may return commands.\n */\nexport type Update<S, M extends Tagged, P = object> = {\n readonly [K in M['_tag']]: Reducer<S, M, Variant<M, K>, P>;\n} & {\n readonly PropsChanged?: Reducer<S, M, PropsChanged<P>, P>;\n readonly IntentRejected?: Reducer<S, M, IntentRejected, P>;\n readonly StoreChanged?: Reducer<S, M, StoreChanged, P>;\n readonly Hydrated?: Reducer<S, M, Hydrated, P>;\n readonly Errored?: Reducer<S, M, Errored, P>;\n readonly FormReset?: Reducer<S, M, FormReset, P>;\n readonly FormDisabled?: Reducer<S, M, FormDisabled, P>;\n readonly FormStateRestore?: Reducer<S, M, FormStateRestore, P>;\n};\n\n/**\n * A component's intent names, the keys of its `intent` object, as the view's `i` and as\n * `intentsOf<typeof C>()`, so `data-intent=${i.Increment}` is checked (ADR 0023).\n */\nexport type IntentNames<N extends string> = { readonly [K in N]: K };\n\n/**\n * The prop builders for props type `P` (view/05-element.md \"Props\"): one `prop.*` builder per\n * key. A builder's output type must fit the prop's type, so a prop whose type excludes\n * `undefined` needs `required: true` or a `default` (the honesty rule, ADR 0007 addendum).\n * Without explicit type arguments, `define()` infers `P` from the builders.\n */\nexport type PropDeclarations<P> = { readonly [K in keyof P]-?: Prop<P[K]> };\n\n/** State of a component that keeps none (a pure view of its props). Its `init` is optional. */\nexport type Stateless = Readonly<Record<string, never>>;\n\n/** Initial model state (and optional commands), computed from props on first render. */\ntype Init<S, M, P> = (props: P) => Next<S, M>;\n\n// `init` may be omitted only when an empty object is a valid state (Stateless, all-optional).\ntype InitField<S, M extends Tagged, P> = Stateless extends S\n ? { readonly init?: Init<S, M, P> }\n : { readonly init: Init<S, M, P> };\n\n/**\n * A component spec. `N` is the component's intent names, the keys of `intent` (ADR 0023).\n */\nexport type ComponentSpec<S, M extends Tagged, P, N extends string = string> = SpecBody<\n S,\n M,\n P,\n N\n> &\n IntentField<S, M, P, N> &\n UpdateField<S, M, P> &\n InitField<S, M, P>;\n\ninterface IntentField<S, M extends Tagged, P, N extends string> {\n /**\n * INTENT: platform events to messages, keyed by intent name (ADR 0023). `Key: true` sends\n * `{ _tag: 'Key' }`. Omit it when the component handles no events.\n */\n readonly intent?: Intents<M, P, N, S>;\n}\n\n// `update` is required (and exhaustive) whenever the component has messages.\ntype UpdateField<S, M extends Tagged, P> = [M] extends [never]\n ? {\n /** MODEL: pure state transitions (only framework messages for a component without any). */\n readonly update?: Update<S, M, P>;\n }\n : {\n /** MODEL: pure state transitions, one reducer per message tag. */\n readonly update: Update<S, M, P>;\n };\n\n/**\n * `spec.shadow`: `true` (the default) or an object for a shadow root, `false` for light DOM.\n * `delegatesFocus: true`: focusing the host, or clicking a non-focusable part of it, focuses its\n * first focusable element, and `:focus` matches the host while focus is inside; the server\n * writes `shadowrootdelegatesfocus` on the declarative shadow root (view/05-element.md \"Focus\").\n */\nexport type ShadowOption = boolean | { readonly delegatesFocus?: boolean };\n\ninterface SpecBody<S, M extends Tagged, P, N extends string> {\n /** Native form participation with pure value, restoration-state and validity projections. */\n readonly formAssociated?: FormAssociation<S, P>;\n /** The component's inputs, declared with `prop.*` builders. */\n readonly props?: PropDeclarations<P>;\n /**\n * VIEW: pure function of state and props. Name intents in markup; never attach closures.\n * (A method, so a class typed with fewer intent names, e.g. `GyralElementClass<S, M, P, O>`\n * for a recursive component, still accepts it.)\n */\n view(state: S, intents: IntentNames<N>, ctx: Ctx<P>): ChildValue;\n /**\n * What to show when `init` or the view throws (ADR 0024): `state` is `undefined` when `init`\n * threw. Without it, a failed first render leaves the host empty and a failed later render\n * keeps its previous DOM. A fallback that throws too is reported and the previous DOM stays.\n */\n error?(failure: GyralError, state: S | undefined): ChildValue;\n /**\n * Shadow-root styles: `css` values, plain CSS strings, or arrays of them, nested freely.\n * Each maps to one shared `CSSStyleSheet`. Ignored (with a warning) when `shadow: false`.\n */\n readonly styles?: Styles;\n /**\n * `false` renders the view as the element's own light-DOM children (ADR 0014): document CSS\n * applies, and the server writes plain children instead of a `<template shadowrootmode>`.\n * For page-level components (listings, articles). No `<slot>`s and no `styles`. Default `true`.\n * An object configures the shadow root (`{ delegatesFocus: true }`).\n */\n readonly shadow?: ShadowOption;\n /**\n * When a server-rendered instance hydrates (gyral-4k7.4): `load` (default), `idle`,\n * `visible` (scrolled into view) or `interaction` (first pointer/focus). Client-only\n * renders are unaffected.\n */\n readonly hydrate?: HydrateStrategy;\n /**\n * Stores this component reads with `ctx.read(store)` and writes with `send(store, msg)`.\n * Their changes re-render it (and reach the optional `StoreChanged` reducer). ADR 0013.\n */\n readonly stores?: readonly AnyStore[];\n /**\n * Return `true` to render this state change inside a View Transition (route changes, list\n * reorders). Skipped without browser support or when reduced motion is requested.\n */\n readonly viewTransition?: (prev: S, next: S, msg: Tagged) => boolean;\n /**\n * Messages from bursty sources (a WebSocket feed, a sensor) whose renders wait for the next\n * animation frame, so many messages in one frame cost one render: `renderOnFrame: ['Ticked']`.\n * Reducers still run at once. Any other change to the component renders in the usual\n * microtask flush, taking pending frame work with it. `'StoreChanged'` covers store changes.\n * Pages that get no frames (hidden) render from a 100 ms timer. view/04-scheduler.md.\n */\n readonly renderOnFrame?: readonly (M['_tag'] | 'StoreChanged')[];\n /**\n * Boolean facts about the state, exposed to CSS as custom states:\n * `states: (s) => ({ loading: s._tag === 'Loading' })` enables `:host(:state(loading))` and\n * `gy-x:state(loading)`. Applied after each render; feature-detected; not on the server.\n */\n readonly states?: (state: S) => Readonly<Record<string, boolean>>;\n /**\n * The long-lived commands that should be running now (ADR 0025): polling, clocks, sockets,\n * store or media-query watches. Evaluated after init, every update and every props change;\n * the runtime starts lanes that appear, aborts lanes that disappear, and restarts a lane whose\n * input changed (compared by JSON text). They stop with the host's other commands on removal\n * and start again from the current state when it is attached again:\n * `subscriptions: (s, p) => (s.live ? [every(30_000, (now) => ({ _tag: 'Tick', now }))] : [])`.\n */\n readonly subscriptions?: (state: S, props: P) => readonly Command<M>[];\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { GyralError } from './errors.js';\nimport type { Prop } from './prop.js';\nimport type { ChildValue, Styles } from './view/index.js';\nimport type { CommandInfo } from './invokers.js';\nimport type { HydrateStrategy } from './islands.js';\nimport type { Command, Next } from './command.js';\nimport type { AnyStore, StoreChanged, StoreRef } from './store.js';\nimport type {\n FormAssociation,\n FormDisabled,\n FormReset,\n FormStateRestore,\n} from './form-associated.js';\n\n/** Every message is a tagged object; the tag is also the intent name used in markup. */\nexport interface Tagged {\n readonly _tag: string;\n}\n\n/** What the intent layer sees when an element's intent fires. */\nexport interface IntentInput {\n /** The intent's name (a message tag): the `data-intent-<event>` or `data-intent` value. */\n readonly name: string;\n readonly event: Event;\n /** The element carrying the intent attribute (`event.currentTarget` is Gyral's root). */\n readonly target: Element;\n /** `value` of the input, select, textarea or button that carries the intent. */\n readonly value: string | undefined;\n /** `checked` for checkbox and radio inputs. */\n readonly checked: boolean | undefined;\n /** Submitted data (including the submitter button) when the intent is on a `<form>`. */\n readonly formData: FormData | undefined;\n /**\n * `detail` of any `CustomEvent`: a Gyral child's output, or the event of another custom\n * element (a map's `marker-select`). Unknown until the parser checks it (a guard, a schema).\n */\n readonly detail: unknown;\n /** `KeyboardEvent.key` for `keydown`/`keyup` intents. */\n readonly key: string | undefined;\n /** For `toggle` intents (popover, `<details>`): the state it changed to. */\n readonly newState: 'open' | 'closed' | undefined;\n /** For `command` intents: the invoker's command (e.g. `--clear`) and source. */\n readonly command: CommandInfo | undefined;\n}\n\n/** Reads a declared store's current state, typed by the store (ADR 0013). */\nexport type StoreReader = <S>(store: StoreRef<S>) => S;\n\n/** Read-only context handed to every reducer and to the view (ADR 0007, 0013). */\nexport interface Ctx<P> {\n /** Props from above. */\n readonly props: P;\n /** Stores from the side: `read(cart).lines`. The store must be in `spec.stores`. */\n readonly read: StoreReader;\n}\n\n/**\n * What an intent parser gets besides the input: the reducers' context plus the component's\n * current state, read-only. Parsers decide synchronously (`preventDefault()`), and where the\n * user is often decides it: Tab leaves a grid only from its last cell.\n */\nexport interface ParserCtx<P, S = unknown> extends Ctx<P> {\n /** The state when the event fires. */\n readonly state: Readonly<S>;\n}\n\n/** Framework message: declared props changed after the first render (ADR 0007). */\nexport interface PropsChanged<P> {\n readonly _tag: 'PropsChanged';\n readonly props: P;\n readonly prev: P;\n}\n\n/**\n * Framework message: the component is live in the browser (ADR 0012 addendum). Sent once,\n * after the first client render has completed, to every client-side instance. Use it for\n * progressive enhancement: render the no-JS form on the server and in the first client render\n * (so hydration matches), then switch to the enhanced UI in the `Hydrated` reducer.\n */\nexport interface Hydrated {\n readonly _tag: 'Hydrated';\n /** `true` when the instance resumed from server-rendered markup (a hydration seed). */\n readonly serverRendered: boolean;\n}\n\n/**\n * Framework message (ADR 0024): an update, an intent parser or a command of this component\n * failed. The failure was reported (`reportError`, or claimed by an ancestor's boundary) and\n * changed nothing; reduce it to show an error state or to reset. A failure while reducing\n * `Errored` is reported and not sent again.\n */\nexport interface Errored {\n readonly _tag: 'Errored';\n readonly phase: 'update' | 'parse' | 'command';\n readonly error: GyralError;\n}\n\n/** One validation problem. `path` is dot-joined and matches the field's `name`. */\nexport interface FieldIssue {\n readonly path: string;\n readonly message: string;\n}\n\n/** Submitted text values of a form (Files are dropped: they can't be re-filled). */\nexport type FormFields = Readonly<Record<string, string | readonly string[]>>;\n\n/** Framework message: an intent's input failed schema validation (ADR 0008). */\nexport interface IntentRejected {\n readonly _tag: 'IntentRejected';\n /** The `data-intent` name whose input was rejected. */\n readonly intent: string;\n readonly issues: readonly FieldIssue[];\n /**\n * For `form()` rejections: what the user submitted, so the view can re-fill the form. On\n * the no-JS path (ADR 0008 server half) the server re-renders the page from this.\n */\n // `| undefined` so schemas with plain optional fields (valibot `optional`) can produce it.\n readonly values?: FormFields | undefined;\n}\n\n/**\n * What an intent parser returns: a message, `IntentRejected` (invalid input), or `undefined` to\n * decline the event.\n */\nexport type ParseResult<M> = M | IntentRejected | undefined;\n\n/**\n * Parses a platform event into one message variant, `IntentRejected`, or `undefined` to\n * decline it: a synchronous `undefined` passes the event to the next intent outward for the\n * same event. May be async because schema validation may be;\n * an async parser keeps the event. `ctx` is the read-only context reducers get (props as\n * they are when the event fires, `read(store)`) plus `state`, the component's state at that\n * moment; parsers that don't need it take one parameter.\n */\nexport type IntentParser<M, P = unknown, S = unknown> = (\n input: IntentInput,\n ctx: ParserCtx<P, S>,\n) => ParseResult<M> | Promise<ParseResult<M>>;\n\n/** The member of message union `M` whose tag is `K`: `Variant<Msg, 'Add'>`. */\nexport type Variant<M extends Tagged, K> = Extract<M, { readonly _tag: K }>;\n\n/**\n * The parser for intent name `K` (ADR 0023): a name that is a message tag parses into that\n * variant; any other name is an intent of its own whose parser may produce any message.\n */\nexport type ParserFor<M extends Tagged, P, K, S = unknown> = string extends K\n ? // The wide form (any intent name), which every specific spec is assignable to.\n IntentParser<M, P, S> | true | TrueOnlyForFieldlessMessage\n : K extends M['_tag']\n ? IntentParser<Variant<M, K>, P, S> | TagOnly<Variant<M, K>>\n : IntentParser<M, P, S>;\n\n/**\n * `true` in the intent table: the intent sends its own tag, `{ _tag: K }` (as Elm's `onClick\n * Deal` names the message itself). Only for a variant with no field besides `_tag`; for any\n * other the type names the rule.\n */\ntype TagOnly<V> = [Exclude<keyof V, '_tag'>] extends [never] ? true : TrueOnlyForFieldlessMessage;\n\ninterface TrueOnlyForFieldlessMessage {\n readonly 'true only sends a message with no fields besides _tag; write a parser': never;\n}\n\n/**\n * Intent parsers keyed by intent name (ADR 0001, ADR 0023). The keys are the component's\n * intent names: `define<State, Msg>()(tag, { intent: { Archive: …, Increment: … } })` gives the\n * view `i.Archive` and `i.Increment`. A key that is a message tag must return that variant; any\n * other key may return any message (several controls that change one thing can also share one\n * intent and tell themselves apart by `name`). Messages from drivers need no parser.\n */\nexport type Intents<M extends Tagged, P = unknown, N extends string = string, S = unknown> = {\n readonly [K in N]: ParserFor<M, P, K, S>;\n};\n\n/**\n * One entry of `update`: takes the state, one message (`Msg`, a variant of `M` or a framework\n * message) and the read-only context, and returns the next state, optionally with commands\n * that produce more `M` messages. To type a reducer written outside the spec:\n *\n * const add: Reducer<State, Msg, Variant<Msg, 'Add'>, object> = (s, m) => …\n */\nexport type Reducer<S, M, Msg, P = object> = (state: S, msg: Msg, ctx: Ctx<P>) => Next<S, M>;\n\n/**\n * One pure reducer per message tag (exhaustive by construction), plus optional reducers\n * for framework messages. Reducers may return commands.\n */\nexport type Update<S, M extends Tagged, P = object> = {\n readonly [K in M['_tag']]: Reducer<S, M, Variant<M, K>, P>;\n} & {\n readonly PropsChanged?: Reducer<S, M, PropsChanged<P>, P>;\n readonly IntentRejected?: Reducer<S, M, IntentRejected, P>;\n readonly StoreChanged?: Reducer<S, M, StoreChanged, P>;\n readonly Hydrated?: Reducer<S, M, Hydrated, P>;\n readonly Errored?: Reducer<S, M, Errored, P>;\n readonly FormReset?: Reducer<S, M, FormReset, P>;\n readonly FormDisabled?: Reducer<S, M, FormDisabled, P>;\n readonly FormStateRestore?: Reducer<S, M, FormStateRestore, P>;\n};\n\n/**\n * A component's intent names, the keys of its `intent` object, as the view's `i` and as\n * `intentsOf<typeof C>()`, so `data-intent=${i.Increment}` is checked (ADR 0023).\n */\nexport type IntentNames<N extends string> = { readonly [K in N]: K };\n\n/**\n * The prop builders for props type `P`: one `prop.*` builder per\n * key. A builder's output type must fit the prop's type, so a prop whose type excludes\n * `undefined` needs `required: true` or a `default` (the honesty rule, ADR 0007 addendum).\n * Without explicit type arguments, `define()` infers `P` from the builders.\n */\nexport type PropDeclarations<P> = { readonly [K in keyof P]-?: Prop<P[K]> };\n\n/** State of a component that keeps none (a pure view of its props). Its `init` is optional. */\nexport type Stateless = Readonly<Record<string, never>>;\n\n/** Initial model state (and optional commands), computed from props on first render. */\ntype Init<S, M, P> = (props: P) => Next<S, M>;\n\n// `init` may be omitted only when an empty object is a valid state (Stateless, all-optional).\ntype InitField<S, M extends Tagged, P> = Stateless extends S\n ? { readonly init?: Init<S, M, P> }\n : { readonly init: Init<S, M, P> };\n\n/**\n * A component spec. `N` is the component's intent names, the keys of `intent` (ADR 0023).\n */\nexport type ComponentSpec<S, M extends Tagged, P, N extends string = string> = SpecBody<\n S,\n M,\n P,\n N\n> &\n IntentField<S, M, P, N> &\n UpdateField<S, M, P> &\n InitField<S, M, P>;\n\ninterface IntentField<S, M extends Tagged, P, N extends string> {\n /**\n * INTENT: platform events to messages, keyed by intent name (ADR 0023). `Key: true` sends\n * `{ _tag: 'Key' }`. Omit it when the component handles no events.\n */\n readonly intent?: Intents<M, P, N, S>;\n}\n\n// `update` is required (and exhaustive) whenever the component has messages.\ntype UpdateField<S, M extends Tagged, P> = [M] extends [never]\n ? {\n /** MODEL: pure state transitions (only framework messages for a component without any). */\n readonly update?: Update<S, M, P>;\n }\n : {\n /** MODEL: pure state transitions, one reducer per message tag. */\n readonly update: Update<S, M, P>;\n };\n\n/**\n * `spec.shadow`: `true` (the default) or an object for a shadow root, `false` for light DOM.\n * `delegatesFocus: true`: focusing the host, or clicking a non-focusable part of it, focuses its\n * first focusable element, and `:focus` matches the host while focus is inside; the server\n * writes `shadowrootdelegatesfocus` on the declarative shadow root.\n */\nexport type ShadowOption = boolean | { readonly delegatesFocus?: boolean };\n\ninterface SpecBody<S, M extends Tagged, P, N extends string> {\n /** Native form participation with pure value, restoration-state and validity projections. */\n readonly formAssociated?: FormAssociation<S, P>;\n /** The component's inputs, declared with `prop.*` builders. */\n readonly props?: PropDeclarations<P>;\n /**\n * VIEW: pure function of state and props. Name intents in markup; never attach closures.\n * (A method, so a class typed with fewer intent names, e.g. `GyralElementClass<S, M, P, O>`\n * for a recursive component, still accepts it.)\n */\n view(state: S, intents: IntentNames<N>, ctx: Ctx<P>): ChildValue;\n /**\n * What to show when `init` or the view throws (ADR 0024): `state` is `undefined` when `init`\n * threw. Without it, a failed first render leaves the host empty and a failed later render\n * keeps its previous DOM. A fallback that throws too is reported and the previous DOM stays.\n */\n error?(failure: GyralError, state: S | undefined): ChildValue;\n /**\n * Shadow-root styles: `css` values, plain CSS strings, or arrays of them, nested freely.\n * Each maps to one shared `CSSStyleSheet`. Ignored (with a warning) when `shadow: false`.\n */\n readonly styles?: Styles;\n /**\n * `false` renders the view as the element's own light-DOM children (ADR 0014): document CSS\n * applies, and the server writes plain children instead of a `<template shadowrootmode>`.\n * For page-level components (listings, articles). No `<slot>`s and no `styles`. Default `true`.\n * An object configures the shadow root (`{ delegatesFocus: true }`).\n */\n readonly shadow?: ShadowOption;\n /**\n * When a server-rendered instance hydrates (gyral-4k7.4): `load` (default), `idle`,\n * `visible` (scrolled into view) or `interaction` (first pointer/focus). Client-only\n * renders are unaffected.\n */\n readonly hydrate?: HydrateStrategy;\n /**\n * Stores this component reads with `ctx.read(store)` and writes with `send(store, msg)`.\n * Their changes re-render it (and reach the optional `StoreChanged` reducer). ADR 0013.\n */\n readonly stores?: readonly AnyStore[];\n /**\n * Return `true` to render this state change inside a View Transition (route changes, list\n * reorders). Skipped without browser support or when reduced motion is requested.\n */\n readonly viewTransition?: (prev: S, next: S, msg: Tagged) => boolean;\n /**\n * Messages from bursty sources (a WebSocket feed, a sensor) whose renders wait for the next\n * animation frame, so many messages in one frame cost one render: `renderOnFrame: ['Ticked']`.\n * Reducers still run at once. Any other change to the component renders in the usual\n * microtask flush, taking pending frame work with it. `'StoreChanged'` covers store changes.\n * Pages that get no frames (hidden) render from a 100 ms timer.\n */\n readonly renderOnFrame?: readonly (M['_tag'] | 'StoreChanged')[];\n /**\n * Boolean facts about the state, exposed to CSS as custom states:\n * `states: (s) => ({ loading: s._tag === 'Loading' })` enables `:host(:state(loading))` and\n * `gy-x:state(loading)`. Applied after each render; feature-detected; not on the server.\n */\n readonly states?: (state: S) => Readonly<Record<string, boolean>>;\n /**\n * The long-lived commands that should be running now (ADR 0025): polling, clocks, sockets,\n * store or media-query watches. Evaluated after init, every update and every props change;\n * the runtime starts lanes that appear, aborts lanes that disappear, and restarts a lane whose\n * input changed (compared by JSON text). They stop with the host's other commands on removal\n * and start again from the current state when it is attached again:\n * `subscriptions: (s, p) => (s.live ? [every(30_000, (now) => ({ _tag: 'Tick', now }))] : [])`.\n */\n readonly subscriptions?: (state: S, props: P) => readonly Command<M>[];\n}\n"]}
@@ -4,7 +4,7 @@ export declare const COMMIT_HOOK: unique symbol;
4
4
  /** Attributes a hook's server half adds to the start tag (`true`: present, no value). */
5
5
  export type HookAttributes = Readonly<Record<string, string | true>>;
6
6
  export interface HookSpec<A extends readonly unknown[]> {
7
- /** Attributes for the server-rendered start tag (view/06-server.md). */
7
+ /** Attributes for the server-rendered start tag. */
8
8
  server?(args: A): HookAttributes;
9
9
  /** Runs after the commit when `args` changed; `prev` is undefined the first time. */
10
10
  client(el: Element, args: A, prev: A | undefined): void;
@@ -14,7 +14,7 @@ export interface HookSpec<A extends readonly unknown[]> {
14
14
  */
15
15
  readonly update?: 'args' | 'always';
16
16
  /**
17
- * Teardown, with the last arguments `client` got (view/02 "Widgets with a lifecycle"): runs
17
+ * Teardown, with the last arguments `client` got: runs
18
18
  * when Gyral removes the element, when the position stops holding this hook, or when the host
19
19
  * disconnects (not on moves). After a host reconnects, `client` runs again with `prev`
20
20
  * undefined.
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/view/render/hooks.ts"],"names":[],"mappings":"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","sourcesContent":["// Element hooks (view/02-bindings.md \"Element hooks\", \"Commit order\"): their types and the\n// queue of client calls. A hook position (attr-parts.ts, kind HOOK) queues itself when its\n// arguments change (shallow `Object.is` per argument, hook-part.ts, which also holds\n// `defineHook`), or on every commit with `update: 'always'`. Inside a scheduler flush\n// (`renderBatch`) the queued `client` calls wait until every dirty host has rendered, then run\n// children first, each root's calls in document order (hook-run.ts), so a parent's hook sees its\n// child components rendered (view/02 \"Commit order\"). A `render` call outside a flush runs its\n// own calls when it returns, in document order.\n// A hook whose spec has `dispose` runs it when the element leaves its render root (Gyral removed\n// it: its part cleared, its instance replaced, its row removed), when the position stops holding\n// the hook, or when the host disconnects; not on moves. That machinery (dispose.ts) is reached\n// through `disposableCommit`, filled by core's `#spec-features` (every build without the preset)\n// or, with the preset, only when some module names `dispose` (compiler/features.ts): apps whose\n// hooks have no teardown don't bundle it (gyral-c5d.2, gyral-1zd.24) and pay one empty check per\n// render and per disconnect.\n\nconst HOOK: unique symbol = Symbol('gyral.hook');\n/** Internal: a hook result's commit function (hook-part.ts), so apps without hooks skip it. */\nexport const COMMIT_HOOK: unique symbol = Symbol('gyral.hook.commit');\n\n/** Attributes a hook's server half adds to the start tag (`true`: present, no value). */\nexport type HookAttributes = Readonly<Record<string, string | true>>;\n\nexport interface HookSpec<A extends readonly unknown[]> {\n /** Attributes for the server-rendered start tag (view/06-server.md). */\n server?(args: A): HookAttributes;\n /** Runs after the commit when `args` changed; `prev` is undefined the first time. */\n client(el: Element, args: A, prev: A | undefined): void;\n /**\n * When `client` runs again: `'args'` (the default) when an argument changed (`Object.is`),\n * `'always'` on every render that commits the hook's position (a skipped `each` row doesn't).\n */\n readonly update?: 'args' | 'always';\n /**\n * Teardown, with the last arguments `client` got (view/02 \"Widgets with a lifecycle\"): runs\n * when Gyral removes the element, when the position stops holding this hook, or when the host\n * disconnects (not on moves). After a host reconnects, `client` runs again with `prev`\n * undefined.\n */\n dispose?(el: Element, args: A): void;\n}\n\n/** What a hook returns in a template: `<input ${invalid(errors)}>`. */\nexport interface HookResult<A extends readonly unknown[] = readonly unknown[]> {\n readonly [HOOK]: HookSpec<A>;\n readonly [COMMIT_HOOK]: (part: HookPart, result: HookResult) => void;\n readonly args: A;\n}\n\n/** A hook position as committing sees it (an element part of kind HOOK). */\nexport interface HookPart {\n readonly el: Element;\n spec: HookSpec<readonly unknown[]> | null;\n args: readonly unknown[] | undefined;\n prev: readonly unknown[] | undefined;\n /** dispose.ts: the tracked disposable hook, its last arguments, and whether a disconnect\n * disposed it (`client` runs again after the next render). */\n kept?: HookSpec<readonly unknown[]> | undefined;\n keptArgs?: readonly unknown[] | undefined;\n off?: boolean | undefined;\n /** The render root it was queued for (hook-run.ts: failures, order). */\n owner?: Node | undefined;\n}\n\nexport const isHook = (value: unknown): value is HookResult =>\n typeof value === 'object' && value !== null && HOOK in value;\n\n/** The spec of a hook result, or undefined for anything else. */\nexport const hookSpec = (value: unknown): HookSpec<readonly unknown[]> | undefined =>\n isHook(value) ? value[HOOK] : undefined;\n\n/** Internal (hook-part.ts): a hook result. */\nexport const hookResult = <A extends readonly unknown[]>(\n spec: HookSpec<A>,\n args: A,\n commit: HookResult[typeof COMMIT_HOOK],\n): HookResult<A> => ({ [HOOK]: spec, [COMMIT_HOOK]: commit, args });\n\n/** Shallow comparison of two argument lists, `Object.is` per argument. */\nexport function sameArgs(a: readonly unknown[], b: readonly unknown[] | undefined): boolean {\n if (b === undefined || a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;\n return true;\n}\n\n/**\n * Hook disposal (dispose.ts), set when the first disposable hook commits: `disposal(root)`\n * after a render of `root` commits (before its client calls), `disposal(root, true)` when the\n * host owning `root` disconnects (true when hooks wait to run `client` again after the next\n * render). A plain variable, not an object, to keep apps without it small.\n */\nlet disposal: ((root: Node, disconnect?: boolean) => boolean) | undefined;\n\n/** Internal (dispose.ts): turns hook disposal on. */\nexport function useDisposal(run: (root: Node, disconnect?: boolean) => boolean): void {\n disposal = run;\n}\n\n/** How a hook with `dispose` commits (dispose.ts `commitTracked`), once registered. */\nexport let disposableCommit: ((part: HookPart, result: HookResult) => void) | undefined;\n\n/** Internal (`#spec-features`, use-disposal.ts): registers hook disposal. */\nexport function useDisposableHooks(commit: (part: HookPart, result: HookResult) => void): void {\n disposableCommit = commit;\n}\n\n/** The host owning `root` disconnected (element.ts): disposes its disposable hooks. */\nexport const suspendHooks = (root: Node | undefined): boolean | undefined =>\n disposal?.(root as Node, true);\n\n/** Internal (hook-run.ts): queued client calls. */\nexport const queue: HookPart[] = [];\n/** The root being committed, so `queueHook` knows the owner. */\nlet current: Node | undefined;\n\n/** Queues `part`'s client call for the end of the render, or of the flush. */\nexport function queueHook(part: HookPart): void {\n part.owner = current;\n queue.push(part);\n}\n\n/** The root being committed (dispose.ts tracks disposable hooks per root). */\nexport const committing = (): Node | undefined => current;\n\n/** Internal (hook-run.ts): sets the root being committed (disposal re-queues hooks for it). */\nexport function setCommitting(root: Node | undefined): void {\n current = root;\n}\n\n/** The queue position before a render of `root`, for `runHooks`/`dropHooks`. */\nexport function hookMark(root: Node): number {\n current = root;\n return queue.length;\n}\n\n/** Where a hook's failure goes (core reports it, ADR 0024); unset, `reportError`. */\nlet hookFailed: ((error: unknown, root: Node) => void) | undefined;\n\n/** Internal (core's element.ts): reports hook failures. */\nexport function onHookFailure(report: (error: unknown, root: Node) => void): void {\n hookFailed = report;\n}\n\n/** Internal (hook-run.ts): where failures go, and the disposal pass. */\nexport const hookFailure = (): ((error: unknown, root: Node) => void) => hookFailed ?? reportError;\nexport const disposalPass = (): typeof disposal => disposal;\n\n/**\n * What runs queued client calls (hook-run.ts): ordering, batching across a flush, failures.\n * Set by `defineHook`, so apps without hooks bundle none of it; without\n * hooks nothing is ever queued.\n */\nexport interface HookRunner {\n /** `root` committed: run its calls now, or note it for the end of the flush. */\n run(mark: number, root: Node): void;\n /** A flush starts. */\n start(): void;\n /** Runs the calls of every root rendered in the flush so far. */\n flush(): void;\n /** A flush ends: runs what's left (or drops it when the flush threw). */\n end(ok: boolean): void;\n}\n\nlet runner: HookRunner | undefined;\n\n/** Internal (hook-part.ts): installs the runner. */\nexport function useHookRunner(run: HookRunner): void {\n runner = run;\n}\n\n/** `root` committed: its queued `client` calls run now, or with the flush's (hook-run.ts). */\nexport function runHooks(mark: number, root: Node): void {\n current = undefined;\n runner?.run(mark, root);\n}\n\n/** A flush starts (render.ts `renderBatch`). */\nexport function startHooks(): void {\n runner?.start();\n}\n\n/** Runs the hooks of every root rendered in this flush so far (the scheduler, after each pass). */\nexport function flushHooks(): void {\n runner?.flush();\n}\n\n/** A flush ends. */\nexport function endHooks(ok: boolean): void {\n runner?.end(ok);\n}\n\n/** Forgets the calls queued since `mark` (the render threw). */\nexport function dropHooks(mark: number): void {\n queue.length = mark;\n current = undefined;\n}\n"]}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/view/render/hooks.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,2FAA2F;AAC3F,qFAAqF;AACrF,sFAAsF;AACtF,+FAA+F;AAC/F,iGAAiG;AACjG,+FAA+F;AAC/F,gDAAgD;AAChD,iGAAiG;AACjG,iGAAiG;AACjG,+FAA+F;AAC/F,iGAAiG;AACjG,gGAAgG;AAChG,iGAAiG;AACjG,6BAA6B;AAE7B,MAAM,IAAI,GAAkB,MAAM,CAAC,YAAY,CAAC,CAAC;AACjD,+FAA+F;AAC/F,MAAM,CAAC,MAAM,WAAW,GAAkB,MAAM,CAAC,mBAAmB,CAAC,CAAC;AA8CtE,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAc,EAAuB,EAAE,CAC5D,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC;AAE/D,iEAAiE;AACjE,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAA4C,EAAE,CACnF,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAE1C,8CAA8C;AAC9C,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,IAAiB,EACjB,IAAO,EACP,MAAsC,EACvB,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAEpE,0EAA0E;AAC1E,MAAM,UAAU,QAAQ,CAAC,CAAqB,EAAE,CAAiC;IAC/E,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC3D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;IAC5E,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,IAAI,QAAqE,CAAC;AAE1E,qDAAqD;AACrD,MAAM,UAAU,WAAW,CAAC,GAAkD;IAC5E,QAAQ,GAAG,GAAG,CAAC;AACjB,CAAC;AAED,uFAAuF;AACvF,MAAM,CAAC,IAAI,gBAA4E,CAAC;AAExF,6EAA6E;AAC7E,MAAM,UAAU,kBAAkB,CAAC,MAAoD;IACrF,gBAAgB,GAAG,MAAM,CAAC;AAC5B,CAAC;AAED,uFAAuF;AACvF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAsB,EAAuB,EAAE,CAC1E,QAAQ,EAAE,CAAC,IAAY,EAAE,IAAI,CAAC,CAAC;AAEjC,mDAAmD;AACnD,MAAM,CAAC,MAAM,KAAK,GAAe,EAAE,CAAC;AACpC,gEAAgE;AAChE,IAAI,OAAyB,CAAC;AAE9B,8EAA8E;AAC9E,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;IACrB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnB,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,MAAM,UAAU,GAAG,GAAqB,EAAE,CAAC,OAAO,CAAC;AAE1D,+FAA+F;AAC/F,MAAM,UAAU,aAAa,CAAC,IAAsB;IAClD,OAAO,GAAG,IAAI,CAAC;AACjB,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,QAAQ,CAAC,IAAU;IACjC,OAAO,GAAG,IAAI,CAAC;IACf,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC;AAED,qFAAqF;AACrF,IAAI,UAA8D,CAAC;AAEnE,2DAA2D;AAC3D,MAAM,UAAU,aAAa,CAAC,MAA4C;IACxE,UAAU,GAAG,MAAM,CAAC;AACtB,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,MAAM,WAAW,GAAG,GAA2C,EAAE,CAAC,UAAU,IAAI,WAAW,CAAC;AACnG,MAAM,CAAC,MAAM,YAAY,GAAG,GAAoB,EAAE,CAAC,QAAQ,CAAC;AAkB5D,IAAI,MAA8B,CAAC;AAEnC,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,GAAe;IAC3C,MAAM,GAAG,GAAG,CAAC;AACf,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,IAAU;IAC/C,OAAO,GAAG,SAAS,CAAC;IACpB,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,UAAU;IACxB,MAAM,EAAE,KAAK,EAAE,CAAC;AAClB,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,UAAU;IACxB,MAAM,EAAE,KAAK,EAAE,CAAC;AAClB,CAAC;AAED,oBAAoB;AACpB,MAAM,UAAU,QAAQ,CAAC,EAAW;IAClC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,GAAG,SAAS,CAAC;AACtB,CAAC","sourcesContent":["// Element hooks (view/02-bindings.md \"Element hooks\", \"Commit order\"): their types and the\n// queue of client calls. A hook position (attr-parts.ts, kind HOOK) queues itself when its\n// arguments change (shallow `Object.is` per argument, hook-part.ts, which also holds\n// `defineHook`), or on every commit with `update: 'always'`. Inside a scheduler flush\n// (`renderBatch`) the queued `client` calls wait until every dirty host has rendered, then run\n// children first, each root's calls in document order (hook-run.ts), so a parent's hook sees its\n// child components rendered (view/02 \"Commit order\"). A `render` call outside a flush runs its\n// own calls when it returns, in document order.\n// A hook whose spec has `dispose` runs it when the element leaves its render root (Gyral removed\n// it: its part cleared, its instance replaced, its row removed), when the position stops holding\n// the hook, or when the host disconnects; not on moves. That machinery (dispose.ts) is reached\n// through `disposableCommit`, filled by core's `#spec-features` (every build without the preset)\n// or, with the preset, only when some module names `dispose` (compiler/features.ts): apps whose\n// hooks have no teardown don't bundle it (gyral-c5d.2, gyral-1zd.24) and pay one empty check per\n// render and per disconnect.\n\nconst HOOK: unique symbol = Symbol('gyral.hook');\n/** Internal: a hook result's commit function (hook-part.ts), so apps without hooks skip it. */\nexport const COMMIT_HOOK: unique symbol = Symbol('gyral.hook.commit');\n\n/** Attributes a hook's server half adds to the start tag (`true`: present, no value). */\nexport type HookAttributes = Readonly<Record<string, string | true>>;\n\nexport interface HookSpec<A extends readonly unknown[]> {\n /** Attributes for the server-rendered start tag. */\n server?(args: A): HookAttributes;\n /** Runs after the commit when `args` changed; `prev` is undefined the first time. */\n client(el: Element, args: A, prev: A | undefined): void;\n /**\n * When `client` runs again: `'args'` (the default) when an argument changed (`Object.is`),\n * `'always'` on every render that commits the hook's position (a skipped `each` row doesn't).\n */\n readonly update?: 'args' | 'always';\n /**\n * Teardown, with the last arguments `client` got: runs\n * when Gyral removes the element, when the position stops holding this hook, or when the host\n * disconnects (not on moves). After a host reconnects, `client` runs again with `prev`\n * undefined.\n */\n dispose?(el: Element, args: A): void;\n}\n\n/** What a hook returns in a template: `<input ${invalid(errors)}>`. */\nexport interface HookResult<A extends readonly unknown[] = readonly unknown[]> {\n readonly [HOOK]: HookSpec<A>;\n readonly [COMMIT_HOOK]: (part: HookPart, result: HookResult) => void;\n readonly args: A;\n}\n\n/** A hook position as committing sees it (an element part of kind HOOK). */\nexport interface HookPart {\n readonly el: Element;\n spec: HookSpec<readonly unknown[]> | null;\n args: readonly unknown[] | undefined;\n prev: readonly unknown[] | undefined;\n /** dispose.ts: the tracked disposable hook, its last arguments, and whether a disconnect\n * disposed it (`client` runs again after the next render). */\n kept?: HookSpec<readonly unknown[]> | undefined;\n keptArgs?: readonly unknown[] | undefined;\n off?: boolean | undefined;\n /** The render root it was queued for (hook-run.ts: failures, order). */\n owner?: Node | undefined;\n}\n\nexport const isHook = (value: unknown): value is HookResult =>\n typeof value === 'object' && value !== null && HOOK in value;\n\n/** The spec of a hook result, or undefined for anything else. */\nexport const hookSpec = (value: unknown): HookSpec<readonly unknown[]> | undefined =>\n isHook(value) ? value[HOOK] : undefined;\n\n/** Internal (hook-part.ts): a hook result. */\nexport const hookResult = <A extends readonly unknown[]>(\n spec: HookSpec<A>,\n args: A,\n commit: HookResult[typeof COMMIT_HOOK],\n): HookResult<A> => ({ [HOOK]: spec, [COMMIT_HOOK]: commit, args });\n\n/** Shallow comparison of two argument lists, `Object.is` per argument. */\nexport function sameArgs(a: readonly unknown[], b: readonly unknown[] | undefined): boolean {\n if (b === undefined || a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;\n return true;\n}\n\n/**\n * Hook disposal (dispose.ts), set when the first disposable hook commits: `disposal(root)`\n * after a render of `root` commits (before its client calls), `disposal(root, true)` when the\n * host owning `root` disconnects (true when hooks wait to run `client` again after the next\n * render). A plain variable, not an object, to keep apps without it small.\n */\nlet disposal: ((root: Node, disconnect?: boolean) => boolean) | undefined;\n\n/** Internal (dispose.ts): turns hook disposal on. */\nexport function useDisposal(run: (root: Node, disconnect?: boolean) => boolean): void {\n disposal = run;\n}\n\n/** How a hook with `dispose` commits (dispose.ts `commitTracked`), once registered. */\nexport let disposableCommit: ((part: HookPart, result: HookResult) => void) | undefined;\n\n/** Internal (`#spec-features`, use-disposal.ts): registers hook disposal. */\nexport function useDisposableHooks(commit: (part: HookPart, result: HookResult) => void): void {\n disposableCommit = commit;\n}\n\n/** The host owning `root` disconnected (element.ts): disposes its disposable hooks. */\nexport const suspendHooks = (root: Node | undefined): boolean | undefined =>\n disposal?.(root as Node, true);\n\n/** Internal (hook-run.ts): queued client calls. */\nexport const queue: HookPart[] = [];\n/** The root being committed, so `queueHook` knows the owner. */\nlet current: Node | undefined;\n\n/** Queues `part`'s client call for the end of the render, or of the flush. */\nexport function queueHook(part: HookPart): void {\n part.owner = current;\n queue.push(part);\n}\n\n/** The root being committed (dispose.ts tracks disposable hooks per root). */\nexport const committing = (): Node | undefined => current;\n\n/** Internal (hook-run.ts): sets the root being committed (disposal re-queues hooks for it). */\nexport function setCommitting(root: Node | undefined): void {\n current = root;\n}\n\n/** The queue position before a render of `root`, for `runHooks`/`dropHooks`. */\nexport function hookMark(root: Node): number {\n current = root;\n return queue.length;\n}\n\n/** Where a hook's failure goes (core reports it, ADR 0024); unset, `reportError`. */\nlet hookFailed: ((error: unknown, root: Node) => void) | undefined;\n\n/** Internal (core's element.ts): reports hook failures. */\nexport function onHookFailure(report: (error: unknown, root: Node) => void): void {\n hookFailed = report;\n}\n\n/** Internal (hook-run.ts): where failures go, and the disposal pass. */\nexport const hookFailure = (): ((error: unknown, root: Node) => void) => hookFailed ?? reportError;\nexport const disposalPass = (): typeof disposal => disposal;\n\n/**\n * What runs queued client calls (hook-run.ts): ordering, batching across a flush, failures.\n * Set by `defineHook`, so apps without hooks bundle none of it; without\n * hooks nothing is ever queued.\n */\nexport interface HookRunner {\n /** `root` committed: run its calls now, or note it for the end of the flush. */\n run(mark: number, root: Node): void;\n /** A flush starts. */\n start(): void;\n /** Runs the calls of every root rendered in the flush so far. */\n flush(): void;\n /** A flush ends: runs what's left (or drops it when the flush threw). */\n end(ok: boolean): void;\n}\n\nlet runner: HookRunner | undefined;\n\n/** Internal (hook-part.ts): installs the runner. */\nexport function useHookRunner(run: HookRunner): void {\n runner = run;\n}\n\n/** `root` committed: its queued `client` calls run now, or with the flush's (hook-run.ts). */\nexport function runHooks(mark: number, root: Node): void {\n current = undefined;\n runner?.run(mark, root);\n}\n\n/** A flush starts (render.ts `renderBatch`). */\nexport function startHooks(): void {\n runner?.start();\n}\n\n/** Runs the hooks of every root rendered in this flush so far (the scheduler, after each pass). */\nexport function flushHooks(): void {\n runner?.flush();\n}\n\n/** A flush ends. */\nexport function endHooks(ok: boolean): void {\n runner?.end(ok);\n}\n\n/** Forgets the calls queued since `mark` (the render threw). */\nexport function dropHooks(mark: number): void {\n queue.length = mark;\n current = undefined;\n}\n"]}
@@ -10,7 +10,7 @@ export declare function clearList(part: ChildPart, list: List): void;
10
10
  */
11
11
  export declare function createRows(part: ChildPart, list: List, l: ListResult, rows: ChildPart[], from: number, to: number, end: Node | null, keys?: readonly unknown[]): void;
12
12
  /**
13
- * A keyed list (view/03-lists.md). `key` must give each item a unique string or number; `row`
13
+ * A keyed list. `key` must give each item a unique string or number; `row`
14
14
  * must depend only on its arguments: a row re-renders only when its item object or its `pick`
15
15
  * result changes.
16
16
  */
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/view/render/list.ts"],"names":[],"mappings":"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","sourcesContent":["// Lists in child holes: `each` (view/03-lists.md \"Reconciliation\", \"Fast paths\") and\n// positional arrays (view/02-bindings.md \"Child values\"). Rows are child parts tracked by their\n// node range, with no markers. Fast paths: create into empty (one fragment), clear\n// (replaceChildren when the list is its parent's only content), the common prefix and suffix,\n// insert-only (append, prepend, insert) and remove-only. What remains is reordered by\n// reorder.ts. Rows commit in document order. `each` is here: its result carries `commitList`,\n// so the keyed path (this, rows.ts, reorder.ts, place.ts) is bundled only by apps that call it.\nimport { DEV } from '#view-dev';\nimport { ChildPart, EMPTY, ITEMS, LIST } from './child-part.js';\nimport { checkKeys, recheckRows } from './dev-check.js';\nimport { reorder } from './reorder.js';\nimport { firstFrom, List, newRow, updateRow } from './rows.js';\nimport { EACH, type ChildValue, type ListResult } from './values.js';\n\nexport { List } from './rows.js';\n\nfunction listOf(part: ChildPart, kind: number): List {\n if (part.kind === kind) return part.content as List;\n if (part.kind !== EMPTY) part.clear();\n const list = new List(part);\n part.kind = kind;\n part.content = list;\n return list;\n}\n\n/** Removes every row; the list's part keeps its (now empty) list. */\nexport function clearList(part: ChildPart, list: List): void {\n if (part.sole && part.parentNode !== null) (part.parentNode as Element).replaceChildren();\n else for (const row of list.rows) row.clear();\n list.rows = [];\n}\n\n/**\n * Creates rows for `l.items[from…to]` (keys from `keys[i - from]` when given) into one\n * fragment, pushes them onto `rows` and inserts the fragment before `end`.\n */\nexport function createRows(\n part: ChildPart,\n list: List,\n l: ListResult,\n rows: ChildPart[],\n from: number,\n to: number,\n end: Node | null,\n keys?: readonly unknown[],\n): void {\n const frag = document.createDocumentFragment();\n list.building = frag;\n try {\n for (let i = from; i <= to; i++) {\n const item = l.items[i];\n const key = keys === undefined ? l.key(item) : keys[i - from];\n rows.push(newRow(list, null, l, item, key, i));\n }\n } finally {\n list.building = null;\n }\n part.parent().insertBefore(frag, end);\n}\n\n/**\n * A keyed list (view/03-lists.md). `key` must give each item a unique string or number; `row`\n * must depend only on its arguments: a row re-renders only when its item object or its `pick`\n * result changes.\n */\nexport function each<T>(\n items: readonly T[],\n key: (item: T) => string | number,\n row: (item: T) => ChildValue,\n): ListResult;\nexport function each<T, P>(\n items: readonly T[],\n key: (item: T) => string | number,\n row: (item: T, picked: P) => ChildValue,\n pick: (item: T) => P,\n): ListResult;\nexport function each<T, P>(\n items: readonly T[],\n key: (item: T) => string | number,\n row: (item: T, picked: P) => ChildValue,\n pick?: (item: T) => P,\n): ListResult {\n if (DEV && typeof key !== 'function') {\n throw new TypeError(\n 'gyral: each() needs a key function as its second argument: ' +\n 'each(items, (x) => x.id, Row) (docs/design-docs/view/03-lists.md \"Keys\", rule 9).',\n );\n }\n return {\n [EACH]: commitList,\n items,\n key: key as (item: unknown) => unknown,\n row: row as (item: unknown, picked: unknown) => unknown,\n pick: pick as ((item: unknown) => unknown) | undefined,\n };\n}\n\nfunction commitList(part: ChildPart, l: ListResult): void {\n const list = listOf(part, LIST);\n const n = l.items.length;\n if (DEV) checkKeys(l.items, l.key);\n if (list.rows.length === 0) {\n if (n > 0) {\n const rows: ChildPart[] = [];\n createRows(part, list, l, rows, 0, n - 1, part.end());\n list.rows = rows;\n }\n } else if (n === 0) clearList(part, list);\n else reconcile(part, list, l);\n if (DEV) recheckRows(list, l);\n}\n\nfunction reconcile(part: ChildPart, list: List, l: ListResult): void {\n const { items, key } = l;\n const old = list.rows;\n const n = items.length;\n const on = old.length;\n const m = n < on ? n : on;\n let s = 0;\n for (; s < m; s++) {\n const item = items[s];\n const row = old[s] as ChildPart;\n if (key(item) !== row.key) break;\n updateRow(row, item, l);\n }\n if (s === n && s === on) return;\n let oe = on - 1;\n let ne = n - 1;\n while (oe >= s && ne >= s && key(items[ne]) === (old[oe] as ChildPart).key) {\n oe--;\n ne--;\n }\n const rows = old.slice(0, s);\n if (s > oe) createRows(part, list, l, rows, s, ne, firstFrom(old, oe + 1, part));\n else if (s > ne) for (let j = s; j <= oe; j++) (old[j] as ChildPart).clear();\n else reorder(part, list, l, rows, s, oe, ne);\n for (let j = oe + 1; j < on; j++) rows.push(old[j] as ChildPart);\n list.rows = rows;\n for (let i = s; i < n; i++) (rows[i] as ChildPart).index = i;\n for (let i = ne + 1; i < n; i++) updateRow(rows[i] as ChildPart, items[i], l);\n}\n\n/** A positional array: items reused by index (view/02-bindings.md \"Child values\"). */\nexport function commitItems(part: ChildPart, values: readonly unknown[]): void {\n const list = listOf(part, ITEMS);\n const rows = list.rows;\n const n = values.length;\n if (n === 0) {\n if (rows.length > 0) clearList(part, list);\n return;\n }\n const common = n < rows.length ? n : rows.length;\n for (let i = 0; i < common; i++) (rows[i] as ChildPart).commit(values[i]);\n if (n > rows.length) {\n const end = part.end();\n const frag = document.createDocumentFragment();\n list.building = frag;\n try {\n for (let i = rows.length; i < n; i++) {\n const row = new ChildPart(null, null, list, i, false, -1);\n rows.push(row);\n row.commit(values[i]);\n }\n } finally {\n list.building = null;\n }\n part.parent().insertBefore(frag, end);\n } else while (rows.length > n) (rows.pop() as ChildPart).clear();\n}\n"]}
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/view/render/list.ts"],"names":[],"mappings":"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","sourcesContent":["// Lists in child holes: `each` (view/03-lists.md \"Reconciliation\", \"Fast paths\") and\n// positional arrays (view/02-bindings.md \"Child values\"). Rows are child parts tracked by their\n// node range, with no markers. Fast paths: create into empty (one fragment), clear\n// (replaceChildren when the list is its parent's only content), the common prefix and suffix,\n// insert-only (append, prepend, insert) and remove-only. What remains is reordered by\n// reorder.ts. Rows commit in document order. `each` is here: its result carries `commitList`,\n// so the keyed path (this, rows.ts, reorder.ts, place.ts) is bundled only by apps that call it.\nimport { DEV } from '#view-dev';\nimport { ChildPart, EMPTY, ITEMS, LIST } from './child-part.js';\nimport { checkKeys, recheckRows } from './dev-check.js';\nimport { reorder } from './reorder.js';\nimport { firstFrom, List, newRow, updateRow } from './rows.js';\nimport { EACH, type ChildValue, type ListResult } from './values.js';\n\nexport { List } from './rows.js';\n\nfunction listOf(part: ChildPart, kind: number): List {\n if (part.kind === kind) return part.content as List;\n if (part.kind !== EMPTY) part.clear();\n const list = new List(part);\n part.kind = kind;\n part.content = list;\n return list;\n}\n\n/** Removes every row; the list's part keeps its (now empty) list. */\nexport function clearList(part: ChildPart, list: List): void {\n if (part.sole && part.parentNode !== null) (part.parentNode as Element).replaceChildren();\n else for (const row of list.rows) row.clear();\n list.rows = [];\n}\n\n/**\n * Creates rows for `l.items[from…to]` (keys from `keys[i - from]` when given) into one\n * fragment, pushes them onto `rows` and inserts the fragment before `end`.\n */\nexport function createRows(\n part: ChildPart,\n list: List,\n l: ListResult,\n rows: ChildPart[],\n from: number,\n to: number,\n end: Node | null,\n keys?: readonly unknown[],\n): void {\n const frag = document.createDocumentFragment();\n list.building = frag;\n try {\n for (let i = from; i <= to; i++) {\n const item = l.items[i];\n const key = keys === undefined ? l.key(item) : keys[i - from];\n rows.push(newRow(list, null, l, item, key, i));\n }\n } finally {\n list.building = null;\n }\n part.parent().insertBefore(frag, end);\n}\n\n/**\n * A keyed list. `key` must give each item a unique string or number; `row`\n * must depend only on its arguments: a row re-renders only when its item object or its `pick`\n * result changes.\n */\nexport function each<T>(\n items: readonly T[],\n key: (item: T) => string | number,\n row: (item: T) => ChildValue,\n): ListResult;\nexport function each<T, P>(\n items: readonly T[],\n key: (item: T) => string | number,\n row: (item: T, picked: P) => ChildValue,\n pick: (item: T) => P,\n): ListResult;\nexport function each<T, P>(\n items: readonly T[],\n key: (item: T) => string | number,\n row: (item: T, picked: P) => ChildValue,\n pick?: (item: T) => P,\n): ListResult {\n if (DEV && typeof key !== 'function') {\n throw new TypeError(\n 'gyral: each() needs a key function as its second argument: ' +\n 'each(items, (x) => x.id, Row) (docs/design-docs/view/03-lists.md \"Keys\", rule 9).',\n );\n }\n return {\n [EACH]: commitList,\n items,\n key: key as (item: unknown) => unknown,\n row: row as (item: unknown, picked: unknown) => unknown,\n pick: pick as ((item: unknown) => unknown) | undefined,\n };\n}\n\nfunction commitList(part: ChildPart, l: ListResult): void {\n const list = listOf(part, LIST);\n const n = l.items.length;\n if (DEV) checkKeys(l.items, l.key);\n if (list.rows.length === 0) {\n if (n > 0) {\n const rows: ChildPart[] = [];\n createRows(part, list, l, rows, 0, n - 1, part.end());\n list.rows = rows;\n }\n } else if (n === 0) clearList(part, list);\n else reconcile(part, list, l);\n if (DEV) recheckRows(list, l);\n}\n\nfunction reconcile(part: ChildPart, list: List, l: ListResult): void {\n const { items, key } = l;\n const old = list.rows;\n const n = items.length;\n const on = old.length;\n const m = n < on ? n : on;\n let s = 0;\n for (; s < m; s++) {\n const item = items[s];\n const row = old[s] as ChildPart;\n if (key(item) !== row.key) break;\n updateRow(row, item, l);\n }\n if (s === n && s === on) return;\n let oe = on - 1;\n let ne = n - 1;\n while (oe >= s && ne >= s && key(items[ne]) === (old[oe] as ChildPart).key) {\n oe--;\n ne--;\n }\n const rows = old.slice(0, s);\n if (s > oe) createRows(part, list, l, rows, s, ne, firstFrom(old, oe + 1, part));\n else if (s > ne) for (let j = s; j <= oe; j++) (old[j] as ChildPart).clear();\n else reorder(part, list, l, rows, s, oe, ne);\n for (let j = oe + 1; j < on; j++) rows.push(old[j] as ChildPart);\n list.rows = rows;\n for (let i = s; i < n; i++) (rows[i] as ChildPart).index = i;\n for (let i = ne + 1; i < n; i++) updateRow(rows[i] as ChildPart, items[i], l);\n}\n\n/** A positional array: items reused by index (view/02-bindings.md \"Child values\"). */\nexport function commitItems(part: ChildPart, values: readonly unknown[]): void {\n const list = listOf(part, ITEMS);\n const rows = list.rows;\n const n = values.length;\n if (n === 0) {\n if (rows.length > 0) clearList(part, list);\n return;\n }\n const common = n < rows.length ? n : rows.length;\n for (let i = 0; i < common; i++) (rows[i] as ChildPart).commit(values[i]);\n if (n > rows.length) {\n const end = part.end();\n const frag = document.createDocumentFragment();\n list.building = frag;\n try {\n for (let i = rows.length; i < n; i++) {\n const row = new ChildPart(null, null, list, i, false, -1);\n rows.push(row);\n row.commit(values[i]);\n }\n } finally {\n list.building = null;\n }\n part.parent().insertBefore(frag, end);\n } else while (rows.length > n) (rows.pop() as ChildPart).clear();\n}\n"]}
@@ -21,7 +21,7 @@ export interface ListResult {
21
21
  readonly row: (item: unknown, picked: unknown) => unknown;
22
22
  readonly pick: ((item: unknown) => unknown) | undefined;
23
23
  }
24
- /** Everything a child hole accepts (view/02-bindings.md "Child values"). */
24
+ /** Everything a child hole accepts. */
25
25
  export type ChildValue = string | number | boolean | null | undefined | typeof nothing | TemplateResult | ListResult | RawResult | readonly ChildValue[];
26
26
  export declare const isRaw: (value: object) => value is RawResult;
27
27
  /** The markup of a `raw()` value, or undefined for anything else. */
@@ -1 +1 @@
1
- {"version":3,"file":"values.js","sourceRoot":"","sources":["../../../src/view/render/values.ts"],"names":[],"mappings":"AAQA,mFAAmF;AACnF,MAAM,CAAC,MAAM,OAAO,GAAkB,MAAM,CAAC,eAAe,CAAC,CAAC;AAE9D,2EAA2E;AAC3E,MAAM,CAAC,MAAM,KAAK,GAAkB,MAAM,CAAC,aAAa,CAAC,CAAC;AAE1D,8FAA8F;AAC9F,MAAM,CAAC,MAAM,MAAM,GAAkB,MAAM,CAAC,WAAW,CAAC,CAAC;AACzD,6FAA6F;AAC7F,MAAM,CAAC,MAAM,IAAI,GAAkB,MAAM,CAAC,YAAY,CAAC,CAAC;AA8BxD,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,KAAa,EAAsB,EAAE,CAAC,MAAM,IAAI,KAAK,CAAC;AAE5E,qEAAqE;AACrE,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAa,EAAsB,EAAE,CAC3D,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAExC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAuB,EAAE,CAAC,IAAI,IAAI,KAAK,CAAC","sourcesContent":["// Child values besides text and templates (view/02-bindings.md \"Child values\", \"`raw(html)`\",\n// \"`nothing`\") and the result of `each` (view/03-lists.md \"The API\"). Each is recognised by a\n// symbol, so data parsed from JSON can never pass for one. `each` lives in list.ts and `raw` in\n// raw.ts: their results carry the code that commits them, so an app that never calls one\n// doesn't bundle that code.\nimport type { TemplateResult } from '../template.js';\nimport type { ChildPart } from './child-part.js';\n\n/** Renders nothing in a child hole; removes the attribute in an attribute hole. */\nexport const nothing: unique symbol = Symbol('gyral.nothing');\n\n/** Internal: \"never committed\", so a part's first commit always writes. */\nexport const UNSET: unique symbol = Symbol('gyral.unset');\n\n/** Internal: the key of `raw`'s result; its value is the function that commits the markup. */\nexport const MARKUP: unique symbol = Symbol('gyral.raw');\n/** Internal: the key of `each`'s result; its value is the function that commits the list. */\nexport const EACH: unique symbol = Symbol('gyral.each');\n\n/** Trusted markup for a child hole: what `raw(html)` returns. */\nexport interface RawResult {\n readonly [MARKUP]: (part: ChildPart, html: string) => void;\n readonly html: string;\n}\n\n/** A keyed list for a child hole: what `each(…)` returns. */\nexport interface ListResult {\n readonly [EACH]: (part: ChildPart, list: ListResult) => void;\n readonly items: readonly unknown[];\n readonly key: (item: unknown) => unknown;\n readonly row: (item: unknown, picked: unknown) => unknown;\n readonly pick: ((item: unknown) => unknown) | undefined;\n}\n\n/** Everything a child hole accepts (view/02-bindings.md \"Child values\"). */\nexport type ChildValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | typeof nothing\n | TemplateResult\n | ListResult\n | RawResult\n | readonly ChildValue[];\n\nexport const isRaw = (value: object): value is RawResult => MARKUP in value;\n\n/** The markup of a `raw()` value, or undefined for anything else. */\nexport const rawHtml = (value: object): string | undefined =>\n isRaw(value) ? value.html : undefined;\n\nexport const isList = (value: object): value is ListResult => EACH in value;\n"]}
1
+ {"version":3,"file":"values.js","sourceRoot":"","sources":["../../../src/view/render/values.ts"],"names":[],"mappings":"AAQA,mFAAmF;AACnF,MAAM,CAAC,MAAM,OAAO,GAAkB,MAAM,CAAC,eAAe,CAAC,CAAC;AAE9D,2EAA2E;AAC3E,MAAM,CAAC,MAAM,KAAK,GAAkB,MAAM,CAAC,aAAa,CAAC,CAAC;AAE1D,8FAA8F;AAC9F,MAAM,CAAC,MAAM,MAAM,GAAkB,MAAM,CAAC,WAAW,CAAC,CAAC;AACzD,6FAA6F;AAC7F,MAAM,CAAC,MAAM,IAAI,GAAkB,MAAM,CAAC,YAAY,CAAC,CAAC;AA8BxD,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,KAAa,EAAsB,EAAE,CAAC,MAAM,IAAI,KAAK,CAAC;AAE5E,qEAAqE;AACrE,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAa,EAAsB,EAAE,CAC3D,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAExC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAuB,EAAE,CAAC,IAAI,IAAI,KAAK,CAAC","sourcesContent":["// Child values besides text and templates (view/02-bindings.md \"Child values\", \"`raw(html)`\",\n// \"`nothing`\") and the result of `each` (view/03-lists.md \"The API\"). Each is recognised by a\n// symbol, so data parsed from JSON can never pass for one. `each` lives in list.ts and `raw` in\n// raw.ts: their results carry the code that commits them, so an app that never calls one\n// doesn't bundle that code.\nimport type { TemplateResult } from '../template.js';\nimport type { ChildPart } from './child-part.js';\n\n/** Renders nothing in a child hole; removes the attribute in an attribute hole. */\nexport const nothing: unique symbol = Symbol('gyral.nothing');\n\n/** Internal: \"never committed\", so a part's first commit always writes. */\nexport const UNSET: unique symbol = Symbol('gyral.unset');\n\n/** Internal: the key of `raw`'s result; its value is the function that commits the markup. */\nexport const MARKUP: unique symbol = Symbol('gyral.raw');\n/** Internal: the key of `each`'s result; its value is the function that commits the list. */\nexport const EACH: unique symbol = Symbol('gyral.each');\n\n/** Trusted markup for a child hole: what `raw(html)` returns. */\nexport interface RawResult {\n readonly [MARKUP]: (part: ChildPart, html: string) => void;\n readonly html: string;\n}\n\n/** A keyed list for a child hole: what `each(…)` returns. */\nexport interface ListResult {\n readonly [EACH]: (part: ChildPart, list: ListResult) => void;\n readonly items: readonly unknown[];\n readonly key: (item: unknown) => unknown;\n readonly row: (item: unknown, picked: unknown) => unknown;\n readonly pick: ((item: unknown) => unknown) | undefined;\n}\n\n/** Everything a child hole accepts. */\nexport type ChildValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | typeof nothing\n | TemplateResult\n | ListResult\n | RawResult\n | readonly ChildValue[];\n\nexport const isRaw = (value: object): value is RawResult => MARKUP in value;\n\n/** The markup of a `raw()` value, or undefined for anything else. */\nexport const rawHtml = (value: object): string | undefined =>\n isRaw(value) ? value.html : undefined;\n\nexport const isList = (value: object): value is ListResult => EACH in value;\n"]}
@@ -9,8 +9,7 @@ export interface TemplateResult {
9
9
  export declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult;
10
10
  /**
11
11
  * The SVG fragment tag: `` svg`<path d=${s.d} />` ``. Its top level is SVG content, so it
12
- * renders only inside an `<svg>` (or another SVG element) of an html template
13
- * (view/01-templates.md "svg templates"). A whole `<svg>` graphic is written with html.
12
+ * renders only inside an `<svg>` (or another SVG element) of an html template. A whole `<svg>` graphic is written with html.
14
13
  */
15
14
  export declare const svg: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult;
16
15
  /** Internal: what the template compiler (`@gyral/core/vite`) rewrites `html` calls into. */
@@ -52,8 +52,7 @@ export const html = DEV
52
52
  : tag;
53
53
  /**
54
54
  * The SVG fragment tag: `` svg`<path d=${s.d} />` ``. Its top level is SVG content, so it
55
- * renders only inside an `<svg>` (or another SVG element) of an html template
56
- * (view/01-templates.md "svg templates"). A whole `<svg>` graphic is written with html.
55
+ * renders only inside an `<svg>` (or another SVG element) of an html template. A whole `<svg>` graphic is written with html.
57
56
  */
58
57
  export const svg = DEV
59
58
  ? withAt(svgTag)
@@ -1 +1 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/view/template.ts"],"names":[],"mappings":"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","sourcesContent":["// Template results and the `html` and `svg` tags (view/01-templates.md \"Authoring\", \"Template\n// results\", \"svg templates\"). A result is a template source (the call site's strings, or a\n// template object) plus this call's values, recognised by a module-private symbol so data from\n// JSON can never pass for one. `templateOf` turns the source into the template object: compiled\n// and svg results carry it, html results are normalized once per strings array through\n// `#prepare` (prepare.ts). Development records each call site's source location (loc.ts) for\n// error messages.\nimport { prepare } from '#prepare';\nimport { DEV } from '#view-dev';\nimport { callSite, locOf, setLoc } from './loc.js';\nimport type { TemplateObject } from './normalize/types.js';\nimport { svgTemplate } from './template-element.js';\n\nconst SOURCE: unique symbol = Symbol('gyral.template');\n\n/** What `html` returns: a template plus this render's values. */\nexport interface TemplateResult {\n readonly [SOURCE]: readonly string[] | TemplateObject;\n readonly values: readonly unknown[];\n}\n\n/** The template tag, as `html` is in production. */\nfunction tag(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult {\n // Development: the call site, from the stack, the first time (unless `html.at` gave it).\n if (DEV && locOf(strings) === undefined) setLoc(strings, callSite(new Error().stack));\n return { [SOURCE]: strings, values };\n}\n\n/** The svg tag, as `svg` is in production: svg results carry their (prepared) template. */\nfunction svgTag(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult {\n // Development: the call site, from the stack, the first time (unless `svg.at` gave it), so\n // the normalizer's rule errors name it.\n if (DEV && locOf(strings) === undefined) setLoc(strings, callSite(new Error().stack));\n return compiledSvg(prepare(strings, true), values);\n}\n\ntype Tag = typeof tag;\n\n/**\n * Development: `html.at(loc)` (and `svg.at(loc)`) is the tag with a known call site. The Vite\n * preset's dev transform (compiler/locate.ts) writes it at every call site, since stack\n * positions in transformed modules aren't the author's lines. Attached here, not by a statement\n * that mentions the tag, so production builds don't keep it alive (the compiler checks that).\n */\nfunction withAt(html: Tag): Tag {\n const tags = new Map<string, Tag>();\n const at = (loc: string): Tag => {\n let located = tags.get(loc);\n if (located === undefined) {\n located = (strings, ...values) => {\n setLoc(strings, loc);\n return html(strings, ...values);\n };\n tags.set(loc, located);\n }\n return located;\n };\n return Object.assign(html, { at });\n}\n\n/** The template tag: `` html`<p>${s.text}</p>` ``. */\nexport const html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult = DEV\n ? withAt(tag)\n : tag;\n\n/**\n * The SVG fragment tag: `` svg`<path d=${s.d} />` ``. Its top level is SVG content, so it\n * renders only inside an `<svg>` (or another SVG element) of an html template\n * (view/01-templates.md \"svg templates\"). A whole `<svg>` graphic is written with html.\n */\nexport const svg: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult = DEV\n ? withAt(svgTag)\n : svgTag;\n\n/** Internal: what the template compiler (`@gyral/core/vite`) rewrites `html` calls into. */\nexport function compiled(template: TemplateObject, values: readonly unknown[]): TemplateResult {\n return { [SOURCE]: template, values };\n}\n\n/** Internal: what the template compiler rewrites `svg` calls into. */\nexport function compiledSvg(template: TemplateObject, values: readonly unknown[]): TemplateResult {\n svgTemplate(template);\n return { [SOURCE]: template, values };\n}\n\nexport function isTemplateResult(value: unknown): value is TemplateResult {\n return typeof value === 'object' && value !== null && SOURCE in value;\n}\n\n/** Internal: a result's source, compared by identity to skip `templateOf` on updates. */\nexport const sourceOf = (result: TemplateResult): readonly string[] | TemplateObject =>\n result[SOURCE];\n\n/**\n * The template object of a result: compiled results carry it; a runtime result's call site is\n * normalized once by `#prepare` (cached there, so compiled builds carry neither the preparer\n * nor its cache).\n */\nexport function templateOf(result: TemplateResult): TemplateObject {\n const source = result[SOURCE];\n return Array.isArray(source) ? prepare(source) : (source as TemplateObject);\n}\n"]}
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/view/template.ts"],"names":[],"mappings":"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","sourcesContent":["// Template results and the `html` and `svg` tags (view/01-templates.md \"Authoring\", \"Template\n// results\", \"svg templates\"). A result is a template source (the call site's strings, or a\n// template object) plus this call's values, recognised by a module-private symbol so data from\n// JSON can never pass for one. `templateOf` turns the source into the template object: compiled\n// and svg results carry it, html results are normalized once per strings array through\n// `#prepare` (prepare.ts). Development records each call site's source location (loc.ts) for\n// error messages.\nimport { prepare } from '#prepare';\nimport { DEV } from '#view-dev';\nimport { callSite, locOf, setLoc } from './loc.js';\nimport type { TemplateObject } from './normalize/types.js';\nimport { svgTemplate } from './template-element.js';\n\nconst SOURCE: unique symbol = Symbol('gyral.template');\n\n/** What `html` returns: a template plus this render's values. */\nexport interface TemplateResult {\n readonly [SOURCE]: readonly string[] | TemplateObject;\n readonly values: readonly unknown[];\n}\n\n/** The template tag, as `html` is in production. */\nfunction tag(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult {\n // Development: the call site, from the stack, the first time (unless `html.at` gave it).\n if (DEV && locOf(strings) === undefined) setLoc(strings, callSite(new Error().stack));\n return { [SOURCE]: strings, values };\n}\n\n/** The svg tag, as `svg` is in production: svg results carry their (prepared) template. */\nfunction svgTag(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult {\n // Development: the call site, from the stack, the first time (unless `svg.at` gave it), so\n // the normalizer's rule errors name it.\n if (DEV && locOf(strings) === undefined) setLoc(strings, callSite(new Error().stack));\n return compiledSvg(prepare(strings, true), values);\n}\n\ntype Tag = typeof tag;\n\n/**\n * Development: `html.at(loc)` (and `svg.at(loc)`) is the tag with a known call site. The Vite\n * preset's dev transform (compiler/locate.ts) writes it at every call site, since stack\n * positions in transformed modules aren't the author's lines. Attached here, not by a statement\n * that mentions the tag, so production builds don't keep it alive (the compiler checks that).\n */\nfunction withAt(html: Tag): Tag {\n const tags = new Map<string, Tag>();\n const at = (loc: string): Tag => {\n let located = tags.get(loc);\n if (located === undefined) {\n located = (strings, ...values) => {\n setLoc(strings, loc);\n return html(strings, ...values);\n };\n tags.set(loc, located);\n }\n return located;\n };\n return Object.assign(html, { at });\n}\n\n/** The template tag: `` html`<p>${s.text}</p>` ``. */\nexport const html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult = DEV\n ? withAt(tag)\n : tag;\n\n/**\n * The SVG fragment tag: `` svg`<path d=${s.d} />` ``. Its top level is SVG content, so it\n * renders only inside an `<svg>` (or another SVG element) of an html template. A whole `<svg>` graphic is written with html.\n */\nexport const svg: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult = DEV\n ? withAt(svgTag)\n : svgTag;\n\n/** Internal: what the template compiler (`@gyral/core/vite`) rewrites `html` calls into. */\nexport function compiled(template: TemplateObject, values: readonly unknown[]): TemplateResult {\n return { [SOURCE]: template, values };\n}\n\n/** Internal: what the template compiler rewrites `svg` calls into. */\nexport function compiledSvg(template: TemplateObject, values: readonly unknown[]): TemplateResult {\n svgTemplate(template);\n return { [SOURCE]: template, values };\n}\n\nexport function isTemplateResult(value: unknown): value is TemplateResult {\n return typeof value === 'object' && value !== null && SOURCE in value;\n}\n\n/** Internal: a result's source, compared by identity to skip `templateOf` on updates. */\nexport const sourceOf = (result: TemplateResult): readonly string[] | TemplateObject =>\n result[SOURCE];\n\n/**\n * The template object of a result: compiled results carry it; a runtime result's call site is\n * normalized once by `#prepare` (cached there, so compiled builds carry neither the preparer\n * nor its cache).\n */\nexport function templateOf(result: TemplateResult): TemplateObject {\n const source = result[SOURCE];\n return Array.isArray(source) ? prepare(source) : (source as TemplateObject);\n}\n"]}
package/dist/vite.d.ts CHANGED
@@ -14,7 +14,7 @@ export interface TemplateCompilerOptions {
14
14
  */
15
15
  readonly parse5?: boolean | string;
16
16
  }
17
- /** Where the preset looks for components and writes their typings (gyral-1zd.25). */
17
+ /** Where the preset looks for components and writes their typings. */
18
18
  export interface ElementTypesOptions {
19
19
  /** Source directories to scan, relative to the Vite root. Default `['src']`. */
20
20
  readonly dirs?: readonly string[];
@@ -33,7 +33,7 @@ export interface GyralViteOptions {
33
33
  readonly compiler?: TemplateCompilerOptions;
34
34
  /**
35
35
  * The app is never server-rendered: leave the hydration code out of the browser bundle
36
- * (`gyralClientOnly()`, view/07-hydration.md "Client-only builds"). Default false.
36
+ * (`gyralClientOnly()`). Default false.
37
37
  */
38
38
  readonly clientOnly?: boolean;
39
39
  /**
package/dist/vite.js CHANGED
@@ -134,6 +134,14 @@ function gyralTemplateLocations(options = {}) {
134
134
  name: 'gyral:template-locations',
135
135
  enforce: 'pre',
136
136
  apply: 'serve',
137
+ resolveId(source) {
138
+ return source === 'virtual:gyral/template-locations' ? '\0gyral:template-locations' : null;
139
+ },
140
+ load(id) {
141
+ return id === '\0gyral:template-locations'
142
+ ? 'export const locate = (tag, loc) => tag.at?.(loc) ?? tag;'
143
+ : null;
144
+ },
137
145
  async configResolved(config) {
138
146
  const locate = await loadCompiler('locate');
139
147
  hooks = locate.createLocator(settingsOf(options).sources);