@vielzeug/ore 26.10.14 → 26.10.16
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/_dev.cjs.map +1 -1
- package/dist/_dev.js.map +1 -1
- package/dist/base-element.cjs.map +1 -1
- package/dist/base-element.js.map +1 -1
- package/dist/context.cjs.map +1 -1
- package/dist/context.js +1 -1
- package/dist/context.js.map +1 -1
- package/dist/define.cjs.map +1 -1
- package/dist/define.js.map +1 -1
- package/dist/directives/classMap.cjs.map +1 -1
- package/dist/directives/classMap.js.map +1 -1
- package/dist/directives/each.cjs.map +1 -1
- package/dist/directives/each.js +1 -1
- package/dist/directives/each.js.map +1 -1
- package/dist/directives/live.cjs.map +1 -1
- package/dist/directives/live.js.map +1 -1
- package/dist/directives/model.cjs.map +1 -1
- package/dist/directives/model.js.map +1 -1
- package/dist/directives/styleMap.cjs.map +1 -1
- package/dist/directives/styleMap.js.map +1 -1
- package/dist/directives/unsafe-html.cjs.map +1 -1
- package/dist/directives/unsafe-html.js.map +1 -1
- package/dist/directives/when.cjs.map +1 -1
- package/dist/directives/when.js.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/forms/field.cjs.map +1 -1
- package/dist/forms/field.js +1 -1
- package/dist/forms/field.js.map +1 -1
- package/dist/host-bind.cjs.map +1 -1
- package/dist/host-bind.js +1 -1
- package/dist/host-bind.js.map +1 -1
- package/dist/ore.cjs +1 -1
- package/dist/ore.cjs.map +1 -1
- package/dist/ore.iife.js +1 -1
- package/dist/ore.iife.js.map +1 -1
- package/dist/ore.js +1180 -2
- package/dist/ore.js.map +1 -1
- package/dist/props.cjs.map +1 -1
- package/dist/props.js +1 -1
- package/dist/props.js.map +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/slots.cjs.map +1 -1
- package/dist/slots.js +1 -1
- package/dist/slots.js.map +1 -1
- package/dist/template/bindings.cjs +1 -1
- package/dist/template/bindings.cjs.map +1 -1
- package/dist/template/bindings.js +1 -1
- package/dist/template/bindings.js.map +1 -1
- package/dist/template/compiler.cjs.map +1 -1
- package/dist/template/compiler.js +1 -1
- package/dist/template/compiler.js.map +1 -1
- package/dist/template/instantiator.cjs +1 -1
- package/dist/template/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.js +1 -1
- package/dist/template/instantiator.js.map +1 -1
- package/dist/template/result.cjs.map +1 -1
- package/dist/template/result.js.map +1 -1
- package/dist/testing/flush.cjs.map +1 -1
- package/dist/testing/flush.js.map +1 -1
- package/dist/testing/mount.cjs.map +1 -1
- package/dist/testing/mount.js.map +1 -1
- package/dist/testing/render-hook.cjs.map +1 -1
- package/dist/testing/render-hook.js +1 -1
- package/dist/testing/render-hook.js.map +1 -1
- package/dist/testing/reset.cjs.map +1 -1
- package/dist/testing/reset.js.map +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/utils/aria.cjs.map +1 -1
- package/dist/utils/aria.js.map +1 -1
- package/dist/utils/brand.cjs.map +1 -1
- package/dist/utils/brand.js.map +1 -1
- package/dist/utils/css.cjs.map +1 -1
- package/dist/utils/css.js +1 -1
- package/dist/utils/css.js.map +1 -1
- package/dist/utils/dom.cjs +1 -1
- package/dist/utils/dom.cjs.map +1 -1
- package/dist/utils/dom.js +1 -1
- package/dist/utils/dom.js.map +1 -1
- package/dist/utils/emit.cjs.map +1 -1
- package/dist/utils/emit.js.map +1 -1
- package/dist/utils/id.cjs.map +1 -1
- package/dist/utils/id.js.map +1 -1
- package/package.json +10 -10
package/dist/props.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"props.cjs","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory: use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property: never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number(): PropDef<number | undefined>;\n number(defaultValue: number): PropDef<number>;\n /**\n * Prop restricted to a fixed set of allowed values: the only honest way to\n * type a union-valued prop, because the restriction is enforced at runtime\n * (`parse` falls back to the default for anything outside `allowed`), not just\n * in a type parameter a raw attribute value could violate. Numeric members are\n * matched against the attribute value parsed as a number. Omit `defaultValue`\n * for an optional prop (`undefined` when the attribute is absent).\n */\n oneOf<T extends number>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends number>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n oneOf<T extends string>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends string>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n string(): PropDef<string | undefined>;\n string(defaultValue: string): PropDef<string>;\n};\n\n/** @internal JS-only prop implementation: never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\n/** @internal Implementation behind the overloaded `prop.number` signature. */\nfunction _numberProp(defaultValue?: number): PropDef<number | undefined> {\n const def = defaultValue;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n;\n },\n reflect: true,\n };\n}\n\n/** @internal Implementation behind the overloaded `prop.string` signature. */\nfunction _stringProp(defaultValue?: string): PropDef<string | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n return {\n default: defaultValue,\n parse: (value: string | null) => (value == null ? defaultValue : value),\n reflect: true,\n };\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop: reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop: the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number: _numberProp as PropFactory['number'],\n oneOf<T extends string | number>(allowed: readonly T[], defaultValue?: T): PropDef<T | undefined> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null) return defaultValue;\n\n // Return the allowed member itself (correct literal type). Numeric unions\n // arrive as attribute strings, so also try the value parsed as a number.\n const members = allowed as readonly unknown[];\n const direct = members.indexOf(value);\n\n if (direct !== -1) return (allowed as readonly T[])[direct];\n\n const asNumber = Number(value);\n\n if (!Number.isNaN(asNumber)) {\n const byNumber = members.indexOf(asNumber);\n\n if (byNumber !== -1) return (allowed as readonly T[])[byNumber];\n }\n\n return defaultValue;\n },\n reflect: true,\n };\n },\n string: _stringProp as PropFactory['string'],\n};\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n *\n * Accepts any object shaped like `PropDef`: not just `prop.*` helper output, so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded: e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n\n // On reconnection, the prop registry still holds the previous connection's\n // PropMeta. Its signal carries the correct prop value (updated by\n // attributeChangedCallback between connections). We must read from it rather\n // than from `el[propName]`, because setup() may have overwritten the host\n // property with a different getter/setter (e.g. `defineFieldValue()` in\n // refine's form-field components redefines `el.value` to expose the internal\n // field string). Reading that getter would yield the wrong value and lose\n // the prop's actual state across reconnections.\n const existingMeta = registry.get(attrName);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (existingMeta) {\n s.value = existingMeta.signal.peek() as T;\n } else if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = coerceIncoming(preUpgradeValue, parse);\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n registry.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n s.value = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"gJAuDA,SAAS,EAAe,EAAuD,CAC7E,MAAO,CAAE,QAAS,EAAc,UAAa,EAAc,QAAS,EAAM,CAC5E,CAGA,SAAS,EAAY,EAAoD,CACvE,IAAM,EAAM,EAEZ,MAAO,CACL,QAAS,EACT,MAAQ,GAAU,CAChB,GAAI,GAAS,KAAM,OAAO,EAE1B,IAAM,EAAI,OAAO,CAAK,EAQtB,OANI,OAAO,MAAM,CAAC,GACX,GAAmC,EAAnC,EAAmF,OAAO,CAAG,EAA7F,EAEE,GAGF,CACT,EACA,QAAS,EACX,CACF,CAGA,SAAS,EAAY,EAAoD,CAEvE,MAAO,CACL,QAAS,EACT,MAAQ,GAA0B,GAAgB,EAClD,QAAS,EACX,CACF,CAEA,IAAa,EAAoB,CAM/B,KAAK,EAA0C,CAG7C,MAAO,CACL,QAHU,GAAgB,GAI1B,MAAQ,GAAU,IAAU,MAAQ,IAAU,QAC9C,QAAS,EACX,CACF,EACA,KAAQ,EAAuD,CAC7D,OAAO,EAAY,CAAY,CACjC,EAcA,KAAQ,EAA6B,CACnC,MAAO,CACL,QAAS,EACT,MAAQ,GAAU,CAChB,GAAI,GAAS,MAAQ,IAAU,GAAI,OAAO,EAE1C,GAAI,CACF,OAAO,KAAK,MAAM,CAAK,CACzB,MAAQ,CACN,OAAO,CACT,CACF,EACA,QAAS,EACX,CACF,EACA,OAAQ,EACR,MAAiC,EAAuB,EAA0C,CAChG,MAAO,CACL,QAAS,EACT,MAAQ,GAAU,CAChB,GAAI,GAAS,KAAM,OAAO,EAI1B,IAAM,EAAU,EACV,EAAS,EAAQ,QAAQ,CAAK,EAEpC,GAAI,IAAW,GAAI,OAAQ,EAAyB,GAEpD,IAAM,EAAW,OAAO,CAAK,EAE7B,GAAI,CAAC,OAAO,MAAM,CAAQ,EAAG,CAC3B,IAAM,EAAW,EAAQ,QAAQ,CAAQ,EAEzC,GAAI,IAAa,GAAI,OAAQ,EAAyB,EACxD,CAEA,OAAO,CACT,EACA,QAAS,EACX,CACF,EACA,OAAQ,CACV,EAEM,EAAa,GACjB,OAAO,GAAU,YAAY,GAAkB,YAAa,GAAS,UAAW,EAkBlF,SAAgB,EAA2B,EAAgB,EAA8B,CACvF,GAAI,CAAC,EAAU,CAAK,EAClB,MAAM,IAAI,EAAA,YACR,SAAS,EAAS,wEAAwE,OAAO,GACnG,EAGF,IAAM,EAAa,EAEnB,GAAI,CAAC,EAAW,MACd,MAAM,IAAI,EAAA,YAAY,SAAS,EAAS,kDAAkD,EAG5F,IAAM,EAAU,EAAW,SAAW,GAGtC,GAAI,GAAW,EAAA,kBAAkB,EAAW,OAAO,EACjD,MAAM,IAAI,EAAA,YAAY,SAAS,EAAS,KAAK,EAAA,WAAW,oBAAoB,EAG9E,MAAO,CACL,GAAG,EACH,SACF,CACF,CAMA,SAAgB,EAAiB,EAAyC,CACxE,IAAM,EAAmB,CAAC,EAE1B,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAI,EAC5C,GAAI,CACF,EAAwB,EAAO,CAAG,CACpC,OAAS,EAAO,CACd,EAAO,KAAK,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAAC,CACpE,CAGF,OAAO,CACT,CASA,IAAM,EAAe,IAAI,QAMZ,GAAe,EAAiB,IAC3C,EAAa,IAAI,CAAE,CAAC,EAAE,IAAI,CAAQ,EAY9B,GAAqB,EAAgB,IACxC,OAAO,GAAU,SAAW,EAAM,CAAK,EAAI,EAGxC,GAAmB,EAAiB,EAAkB,EAAkB,IAAmC,CAC/G,IAAI,EAAW,EAAa,IAAI,CAAE,EAE7B,IACH,EAAW,IAAI,IACf,EAAa,IAAI,EAAI,CAAQ,GAG/B,GAAM,CAAE,QAAS,EAAc,QAAO,UAAU,IAAU,EACpD,GAAA,EAAI,EAAA,OAAA,CAAU,CAAY,EAU1B,EAAe,EAAS,IAAI,CAAQ,EACpC,EAAwB,OAAO,OAAO,EAA0C,CAAQ,EACxF,EAAkB,EAAyB,EAA0C,GAAY,IAAA,GAEjG,EAA0B,CAC9B,QACA,UACA,OAAQ,CACV,EAoCA,OAlCI,EACF,EAAE,MAAQ,EAAa,OAAO,KAAK,EAC1B,GACT,OAAQ,EAA0C,GAClD,EAAE,MAAQ,EAAe,EAAiB,CAAK,GACtC,EAAG,aAAa,CAAQ,IACjC,EAAE,MAAQ,EAAM,EAAG,aAAa,CAAQ,CAAC,GAG3C,EAAS,IAAI,EAAU,CAAI,EAE3B,OAAO,eAAe,EAAI,EAAU,CAClC,aAAc,GACd,WAAY,GACZ,QAAW,EAAE,MACb,IAAM,GAAa,CACjB,EAAE,MAAQ,EAAe,EAAO,CAAK,CACvC,CACF,CAAC,EAEG,GACF,EAAA,gBAAkB,CAChB,IAAM,EAAI,EAAE,MAER,GAAK,KACP,EAAG,gBAAgB,CAAQ,EAClB,OAAO,GAAM,UACtB,EAAG,gBAAgB,EAAU,CAAC,EAE9B,EAAA,QAAQ,EAAI,EAAU,CAAC,CAE3B,CAAC,EAGI,CACT,EAmBA,SAAgB,EAAqC,EAAiB,EAAwB,CAC5F,IAAM,EAAQ,CAAC,EAEf,IAAK,GAAM,CAAC,EAAM,KAAQ,OAAO,QAAQ,CAAI,EAI3C,EAAM,GAAQ,EAAa,EAAI,EAFd,EAAA,QAAQ,CAEY,EAAU,CAAuB,EAGxE,OAAO,CACT"}
|
|
1
|
+
{"version":3,"file":"props.cjs","names":["OreApiError","isStructuredValue","ORE_ERRORS","signal","toKebab","require_errors","require_dom","require_runtime","_vielzeug_ripple","_jsOnlyProp","defaultValue","_numberProp","def","value","_stringProp","prop","members","allowed","direct","asNumber","byNumber","isPropDef","normalizePropDefinition","propName","descriptor","reflect","validatePropDefs","errors","key","defs","error","propRegistry","getPropMeta","el","attrName","coerceIncoming","parse","registerProp","registry","propDef","s","existingMeta","hasPreUpgradeProperty","preUpgradeValue","meta","v","createProps","props","name"],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory: use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property: never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number(): PropDef<number | undefined>;\n number(defaultValue: number): PropDef<number>;\n /**\n * Prop restricted to a fixed set of allowed values: the only honest way to\n * type a union-valued prop, because the restriction is enforced at runtime\n * (`parse` falls back to the default for anything outside `allowed`), not just\n * in a type parameter a raw attribute value could violate. Numeric members are\n * matched against the attribute value parsed as a number. Omit `defaultValue`\n * for an optional prop (`undefined` when the attribute is absent).\n */\n oneOf<T extends number>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends number>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n oneOf<T extends string>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends string>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n string(): PropDef<string | undefined>;\n string(defaultValue: string): PropDef<string>;\n};\n\n/** @internal JS-only prop implementation: never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\n/** @internal Implementation behind the overloaded `prop.number` signature. */\nfunction _numberProp(defaultValue?: number): PropDef<number | undefined> {\n const def = defaultValue;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n;\n },\n reflect: true,\n };\n}\n\n/** @internal Implementation behind the overloaded `prop.string` signature. */\nfunction _stringProp(defaultValue?: string): PropDef<string | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n return {\n default: defaultValue,\n parse: (value: string | null) => (value == null ? defaultValue : value),\n reflect: true,\n };\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop: reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop: the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number: _numberProp as PropFactory['number'],\n oneOf<T extends string | number>(allowed: readonly T[], defaultValue?: T): PropDef<T | undefined> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null) return defaultValue;\n\n // Return the allowed member itself (correct literal type). Numeric unions\n // arrive as attribute strings, so also try the value parsed as a number.\n const members = allowed as readonly unknown[];\n const direct = members.indexOf(value);\n\n if (direct !== -1) return (allowed as readonly T[])[direct];\n\n const asNumber = Number(value);\n\n if (!Number.isNaN(asNumber)) {\n const byNumber = members.indexOf(asNumber);\n\n if (byNumber !== -1) return (allowed as readonly T[])[byNumber];\n }\n\n return defaultValue;\n },\n reflect: true,\n };\n },\n string: _stringProp as PropFactory['string'],\n};\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n *\n * Accepts any object shaped like `PropDef`: not just `prop.*` helper output, so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded: e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n\n // On reconnection, the prop registry still holds the previous connection's\n // PropMeta. Its signal carries the correct prop value (updated by\n // attributeChangedCallback between connections). We must read from it rather\n // than from `el[propName]`, because setup() may have overwritten the host\n // property with a different getter/setter (e.g. `defineFieldValue()` in\n // refine's form-field components redefines `el.value` to expose the internal\n // field string). Reading that getter would yield the wrong value and lose\n // the prop's actual state across reconnections.\n const existingMeta = registry.get(attrName);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (existingMeta) {\n s.value = existingMeta.signal.peek() as T;\n } else if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = coerceIncoming(preUpgradeValue, parse);\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n registry.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n s.value = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"A,gJAuDA,SAASS,EAAe,EAAuD,CAC7E,MAAO,CAAE,QAASC,EAAc,UAAaA,EAAc,QAAS,EAAM,CAC5E,CAGA,SAASC,EAAY,EAAoD,CACvE,IAAMC,EAAMF,EAEZ,MAAO,CACL,QAASE,EACT,MAAQ,GAAU,CAChB,GAAIC,GAAS,KAAM,OAAOD,EAE1B,IAAM,EAAI,OAAOC,CAAK,EAQtB,OANI,OAAO,MAAM,CAAC,GACX,GAAmCA,EAAnC,EAAmF,OAAOD,CAAG,EAA7F,EAEEA,GAGF,CACT,EACA,QAAS,EACX,CACF,CAGA,SAASE,EAAY,EAAoD,CAEvE,MAAO,CACL,QAASJ,EACT,MAAQ,GAA0BG,GAAgBH,EAClD,QAAS,EACX,CACF,CAEA,IAAaK,EAAoB,CAM/B,KAAK,EAA0C,CAG7C,MAAO,CACL,QAHUL,GAAgB,GAI1B,MAAQ,GAAUG,IAAU,MAAQA,IAAU,QAC9C,QAAS,EACX,CACF,EACA,KAAQ,EAAuD,CAC7D,OAAOJ,EAAYC,CAAY,CACjC,EAcA,KAAQ,EAA6B,CACnC,MAAO,CACL,QAASA,EACT,MAAQ,GAAU,CAChB,GAAIG,GAAS,MAAQA,IAAU,GAAI,OAAOH,EAE1C,GAAI,CACF,OAAO,KAAK,MAAMG,CAAK,CACzB,MAAQ,CACN,OAAOH,CACT,CACF,EACA,QAAS,EACX,CACF,EACA,OAAQC,EACR,MAAiC,EAAuB,EAA0C,CAChG,MAAO,CACL,QAASD,EACT,MAAQ,GAAU,CAChB,GAAIG,GAAS,KAAM,OAAOH,EAI1B,IAAMM,EAAUC,EACVC,EAASF,EAAQ,QAAQH,CAAK,EAEpC,GAAIK,IAAW,GAAI,OAAQD,EAAyBC,GAEpD,IAAMC,EAAW,OAAON,CAAK,EAE7B,GAAI,CAAC,OAAO,MAAMM,CAAQ,EAAG,CAC3B,IAAMC,EAAWJ,EAAQ,QAAQG,CAAQ,EAEzC,GAAIC,IAAa,GAAI,OAAQH,EAAyBG,EACxD,CAEA,OAAOV,CACT,EACA,QAAS,EACX,CACF,EACA,OAAQI,CACV,EAEMO,EAAa,GACjB,OAAOR,GAAU,YAAYA,GAAkB,YAAaA,GAAS,UAAWA,EAkBlF,SAAgBS,EAA2B,EAAgB,EAA8B,CACvF,GAAI,CAACD,EAAUR,CAAK,EAClB,MAAM,IAAIb,EAAAA,YACR,SAASuB,EAAS,wEAAwE,OAAOV,GACnG,EAGF,IAAMW,EAAaX,EAEnB,GAAI,CAACW,EAAW,MACd,MAAM,IAAIxB,EAAAA,YAAY,SAASuB,EAAS,kDAAkD,EAG5F,IAAME,EAAUD,EAAW,SAAW,GAGtC,GAAIC,GAAWxB,EAAAA,kBAAkBuB,EAAW,OAAO,EACjD,MAAM,IAAIxB,EAAAA,YAAY,SAASuB,EAAS,KAAKrB,EAAAA,WAAW,oBAAoB,EAG9E,MAAO,CACL,GAAGsB,EACH,SACF,CACF,CAMA,SAAgBE,EAAiB,EAAyC,CACxE,IAAMC,EAAmB,CAAC,EAE1B,IAAK,GAAM,CAACC,EAAKf,KAAU,OAAO,QAAQgB,CAAI,EAC5C,GAAI,CACFP,EAAwBT,EAAOe,CAAG,CACpC,OAASE,EAAO,CACdH,EAAO,KAAKG,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CAAC,CACpE,CAGF,OAAOH,CACT,CASA,IAAMI,EAAe,IAAI,QAMZC,GAAe,EAAiB,IAC3CD,EAAa,IAAIE,CAAE,CAAC,EAAE,IAAIC,CAAQ,EAY9BC,GAAqB,EAAgB,IACxC,OAAOtB,GAAU,SAAWuB,EAAMvB,CAAK,EAAIA,EAGxCwB,GAAmB,EAAiB,EAAkB,EAAkB,IAAmC,CAC/G,IAAIC,EAAWP,EAAa,IAAIE,CAAE,EAE7BK,IACH,EAAW,IAAI,IACfP,EAAa,IAAIE,EAAIK,CAAQ,GAG/B,GAAM,CAAE,QAAS5B,EAAc,QAAO,UAAU,IAAU6B,EACpDC,GAAA,EAAIrC,EAAAA,OAAAA,CAAUO,CAAY,EAU1B+B,EAAeH,EAAS,IAAIJ,CAAQ,EACpCQ,EAAwB,OAAO,OAAOT,EAA0CV,CAAQ,EACxFoB,EAAkBD,EAAyBT,EAA0CV,GAAY,IAAA,GAEjGqB,EAA0B,CAC9B,QACA,UACA,OAAQJ,CACV,EAoCA,OAlCIC,EACF,EAAE,MAAQA,EAAa,OAAO,KAAK,EAC1BC,GACT,OAAQT,EAA0CV,GAClD,EAAE,MAAQY,EAAeQ,EAAiBP,CAAK,GACtCH,EAAG,aAAaC,CAAQ,IACjC,EAAE,MAAQE,EAAMH,EAAG,aAAaC,CAAQ,CAAC,GAG3CI,EAAS,IAAIJ,EAAUU,CAAI,EAE3B,OAAO,eAAeX,EAAIV,EAAU,CAClC,aAAc,GACd,WAAY,GACZ,QAAWiB,EAAE,MACb,IAAM,GAAa,CACjB,EAAE,MAAQL,EAAetB,EAAOuB,CAAK,CACvC,CACF,CAAC,EAEGX,GACFlB,EAAA,gBAAkB,CAChB,IAAMsC,EAAIL,EAAE,MAERK,GAAK,KACPZ,EAAG,gBAAgBC,CAAQ,EAClB,OAAOW,GAAM,UACtBZ,EAAG,gBAAgBC,EAAUW,CAAC,EAE9BvC,EAAA,QAAQ2B,EAAIC,EAAUW,CAAC,CAE3B,CAAC,EAGIL,CACT,EAmBA,SAAgBM,EAAqC,EAAiB,EAAwB,CAC5F,IAAMC,EAAQ,CAAC,EAEf,IAAK,GAAM,CAACC,EAAMpC,KAAQ,OAAO,QAAQiB,CAAI,EAI3C,EAAMmB,GAAQX,EAAaJ,EAAIe,EAFd5C,EAAAA,QAAQ4C,CAEY,EAAUpC,CAAuB,EAGxE,OAAOmC,CACT,C"}
|
package/dist/props.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{isStructuredValue as n,setAttr as r,toKebab as i}from"./utils/dom.js";import{watchEffect as a}from"./runtime.js";import{signal as o}from"@vielzeug/ripple";function s(e){return{default:e,parse:()=>e,reflect:!1}}function c(e){let t=e;return{default:t,parse:e=>{if(e==null)return t;let n=Number(e);return Number.isNaN(n)?(`${e}${String(t)}`,t):n},reflect:!0}}function l(e){return{default:e,parse:t=>t??e,reflect:!0}}var u={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return s(e)},json(e){return{default:e,parse:t=>{if(t==null||t===``)return e;try{return JSON.parse(t)}catch{return e}},reflect:!1}},number:c,oneOf(e,t){return{default:t,parse:n=>{if(n==null)return t;let r=e,i=r.indexOf(n);if(i!==-1)return e[i];let a=Number(n);if(!Number.isNaN(a)){let t=r.indexOf(a);if(t!==-1)return e[t]}return t},reflect:!0}},string:l},d=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function f(r,i){if(!d(r))throw new t(`Prop "${i}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof r}`);let a=r;if(!a.parse)throw new t(`Prop "${i}" must have a parse function. Use prop.* helpers.`);let o=a.reflect??!1;if(o&&n(a.default))throw new t(`Prop "${i}": ${e.propInvalidReflect}`);return{...a,reflect:o}}function p(e){let t=[];for(let[n,r]of Object.entries(e))try{f(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var m
|
|
1
|
+
import"./_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{isStructuredValue as n,setAttr as r,toKebab as i}from"./utils/dom.js";import{watchEffect as a}from"./runtime.js";import{signal as o}from"@vielzeug/ripple";function s(e){return{default:e,parse:()=>e,reflect:!1}}function c(e){let t=e;return{default:t,parse:e=>{if(e==null)return t;let n=Number(e);return Number.isNaN(n)?(`${e}${String(t)}`,t):n},reflect:!0}}function l(e){return{default:e,parse:t=>t??e,reflect:!0}}var u={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return s(e)},json(e){return{default:e,parse:t=>{if(t==null||t===``)return e;try{return JSON.parse(t)}catch{return e}},reflect:!1}},number:c,oneOf(e,t){return{default:t,parse:n=>{if(n==null)return t;let r=e,i=r.indexOf(n);if(i!==-1)return e[i];let a=Number(n);if(!Number.isNaN(a)){let t=r.indexOf(a);if(t!==-1)return e[t]}return t},reflect:!0}},string:l},d=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function f(r,i){if(!d(r))throw new t(`Prop "${i}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof r}`);let a=r;if(!a.parse)throw new t(`Prop "${i}" must have a parse function. Use prop.* helpers.`);let o=a.reflect??!1;if(o&&n(a.default))throw new t(`Prop "${i}": ${e.propInvalidReflect}`);return{...a,reflect:o}}function p(e){let t=[];for(let[n,r]of Object.entries(e))try{f(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var m=/* @__PURE__ */ new WeakMap,h=(e,t)=>m.get(e)?.get(t),g=(e,t)=>typeof e==`string`?t(e):e,_=(e,t,n,i)=>{let s=m.get(e);s||(s=/* @__PURE__ */ new Map,m.set(e,s));let{default:c,parse:l,reflect:u=!1}=i,d=o(c),f=s.get(n),p=Object.hasOwn(e,t),h=p?e[t]:void 0,_={parse:l,reflect:u,signal:d};return f?d.value=f.signal.peek():p?(delete e[t],d.value=g(h,l)):e.hasAttribute(n)&&(d.value=l(e.getAttribute(n))),s.set(n,_),Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:()=>d.value,set:e=>{d.value=g(e,l)}}),u&&a(()=>{let t=d.value;t==null?e.removeAttribute(n):typeof t==`boolean`?e.toggleAttribute(n,t):r(e,n,t)}),d};function v(e,t){let n={};for(let[r,a]of Object.entries(t))n[r]=_(e,r,i(r),a);return n}export{v as createProps,h as getPropMeta,f as normalizePropDefinition,u as prop,p as validatePropDefs};
|
|
2
2
|
//# sourceMappingURL=props.js.map
|
package/dist/props.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"props.js","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory: use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property: never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number(): PropDef<number | undefined>;\n number(defaultValue: number): PropDef<number>;\n /**\n * Prop restricted to a fixed set of allowed values: the only honest way to\n * type a union-valued prop, because the restriction is enforced at runtime\n * (`parse` falls back to the default for anything outside `allowed`), not just\n * in a type parameter a raw attribute value could violate. Numeric members are\n * matched against the attribute value parsed as a number. Omit `defaultValue`\n * for an optional prop (`undefined` when the attribute is absent).\n */\n oneOf<T extends number>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends number>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n oneOf<T extends string>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends string>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n string(): PropDef<string | undefined>;\n string(defaultValue: string): PropDef<string>;\n};\n\n/** @internal JS-only prop implementation: never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\n/** @internal Implementation behind the overloaded `prop.number` signature. */\nfunction _numberProp(defaultValue?: number): PropDef<number | undefined> {\n const def = defaultValue;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n;\n },\n reflect: true,\n };\n}\n\n/** @internal Implementation behind the overloaded `prop.string` signature. */\nfunction _stringProp(defaultValue?: string): PropDef<string | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n return {\n default: defaultValue,\n parse: (value: string | null) => (value == null ? defaultValue : value),\n reflect: true,\n };\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop: reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop: the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number: _numberProp as PropFactory['number'],\n oneOf<T extends string | number>(allowed: readonly T[], defaultValue?: T): PropDef<T | undefined> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null) return defaultValue;\n\n // Return the allowed member itself (correct literal type). Numeric unions\n // arrive as attribute strings, so also try the value parsed as a number.\n const members = allowed as readonly unknown[];\n const direct = members.indexOf(value);\n\n if (direct !== -1) return (allowed as readonly T[])[direct];\n\n const asNumber = Number(value);\n\n if (!Number.isNaN(asNumber)) {\n const byNumber = members.indexOf(asNumber);\n\n if (byNumber !== -1) return (allowed as readonly T[])[byNumber];\n }\n\n return defaultValue;\n },\n reflect: true,\n };\n },\n string: _stringProp as PropFactory['string'],\n};\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n *\n * Accepts any object shaped like `PropDef`: not just `prop.*` helper output, so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded: e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n\n // On reconnection, the prop registry still holds the previous connection's\n // PropMeta. Its signal carries the correct prop value (updated by\n // attributeChangedCallback between connections). We must read from it rather\n // than from `el[propName]`, because setup() may have overwritten the host\n // property with a different getter/setter (e.g. `defineFieldValue()` in\n // refine's form-field components redefines `el.value` to expose the internal\n // field string). Reading that getter would yield the wrong value and lose\n // the prop's actual state across reconnections.\n const existingMeta = registry.get(attrName);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (existingMeta) {\n s.value = existingMeta.signal.peek() as T;\n } else if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = coerceIncoming(preUpgradeValue, parse);\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n registry.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n s.value = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"props.js","names":["_jsOnlyProp","defaultValue","_numberProp","def","value","_stringProp","prop","members","allowed","direct","asNumber","byNumber","isPropDef","normalizePropDefinition","OreApiError","propName","descriptor","reflect","isStructuredValue","ORE_ERRORS","validatePropDefs","errors","key","defs","error","propRegistry","getPropMeta","el","attrName","coerceIncoming","parse","registerProp","registry","propDef","s","signal","existingMeta","hasPreUpgradeProperty","preUpgradeValue","meta","watchEffect","v","setAttr","createProps","props","name","toKebab"],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory: use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property: never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number(): PropDef<number | undefined>;\n number(defaultValue: number): PropDef<number>;\n /**\n * Prop restricted to a fixed set of allowed values: the only honest way to\n * type a union-valued prop, because the restriction is enforced at runtime\n * (`parse` falls back to the default for anything outside `allowed`), not just\n * in a type parameter a raw attribute value could violate. Numeric members are\n * matched against the attribute value parsed as a number. Omit `defaultValue`\n * for an optional prop (`undefined` when the attribute is absent).\n */\n oneOf<T extends number>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends number>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n oneOf<T extends string>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends string>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n string(): PropDef<string | undefined>;\n string(defaultValue: string): PropDef<string>;\n};\n\n/** @internal JS-only prop implementation: never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\n/** @internal Implementation behind the overloaded `prop.number` signature. */\nfunction _numberProp(defaultValue?: number): PropDef<number | undefined> {\n const def = defaultValue;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n;\n },\n reflect: true,\n };\n}\n\n/** @internal Implementation behind the overloaded `prop.string` signature. */\nfunction _stringProp(defaultValue?: string): PropDef<string | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n return {\n default: defaultValue,\n parse: (value: string | null) => (value == null ? defaultValue : value),\n reflect: true,\n };\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop: reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop: the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number: _numberProp as PropFactory['number'],\n oneOf<T extends string | number>(allowed: readonly T[], defaultValue?: T): PropDef<T | undefined> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null) return defaultValue;\n\n // Return the allowed member itself (correct literal type). Numeric unions\n // arrive as attribute strings, so also try the value parsed as a number.\n const members = allowed as readonly unknown[];\n const direct = members.indexOf(value);\n\n if (direct !== -1) return (allowed as readonly T[])[direct];\n\n const asNumber = Number(value);\n\n if (!Number.isNaN(asNumber)) {\n const byNumber = members.indexOf(asNumber);\n\n if (byNumber !== -1) return (allowed as readonly T[])[byNumber];\n }\n\n return defaultValue;\n },\n reflect: true,\n };\n },\n string: _stringProp as PropFactory['string'],\n};\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n *\n * Accepts any object shaped like `PropDef`: not just `prop.*` helper output, so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded: e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n\n // On reconnection, the prop registry still holds the previous connection's\n // PropMeta. Its signal carries the correct prop value (updated by\n // attributeChangedCallback between connections). We must read from it rather\n // than from `el[propName]`, because setup() may have overwritten the host\n // property with a different getter/setter (e.g. `defineFieldValue()` in\n // refine's form-field components redefines `el.value` to expose the internal\n // field string). Reading that getter would yield the wrong value and lose\n // the prop's actual state across reconnections.\n const existingMeta = registry.get(attrName);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (existingMeta) {\n s.value = existingMeta.signal.peek() as T;\n } else if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = coerceIncoming(preUpgradeValue, parse);\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n registry.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n s.value = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
|
package/dist/runtime.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.cjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Component runtime: implicit \"current component\" context plus every lifecycle\n * hook a `setup()` function can call.\n *\n * Design note: hooks are plain module-level functions (not a bag/context object\n * passed into `setup`). They resolve the active component through a single\n * module-level pointer (`currentContext`), set for the duration of `setup()` and\n * of each queued `onMounted` callback. This is the same mechanism React/Vue/Solid\n * use for their composable hooks: it lets any helper function (not just the\n * top-level `setup()` body) call `onMounted`/`onCleanup`/`bind`/... directly,\n * with no context object to thread through every layer of a composable.\n */\nimport { effect as _effect, type Cleanup, type Readable } from '@vielzeug/ripple';\n\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { listen as listenInternal } from './utils/dom';\n\n// ─── Runtime context ──────────────────────────────────────────────────────────\n// A single context object carries both the host element and mount callbacks,\n// eliminating two parallel globals that were always set together.\n\nexport type OnMountedCallback = () => Cleanup | undefined;\nexport type OnFormResetCallback = () => void;\n\nexport type RuntimeContext = {\n element: HTMLElement;\n formResetCallbacks: OnFormResetCallback[];\n mountCallbacks: OnMountedCallback[];\n};\n\nlet currentContext: RuntimeContext | null = null;\n\n/**\n * @internal Create a fresh runtime context for a component element. The single\n * construction site for `RuntimeContext`: used by `BaseElement` (setup and\n * per-callback mount contexts) and by `testing/render-hook.ts`, so the test\n * harness can never silently desync from a new required field.\n */\nexport const createRuntimeContext = (element: HTMLElement): RuntimeContext => ({\n element,\n formResetCallbacks: [],\n mountCallbacks: [],\n});\n\n// ─── Pending work tracking ──────────────────────────────────────────────────\n// A single counter of \"in-flight scheduled work\" across every live component\n// instance on the page: incremented when a mount-callback microtask is scheduled\n// (base-element.ts's _scheduleMountCallbacks()), decremented when it completes.\n//\n// Why this exists: `@vielzeug/ripple`'s reactive graph settles fully synchronously on\n// every signal write (see ripple's scheduling.ts): there is no async flush queue to wait\n// for there. The only genuinely async work `testing/flush()` needs to wait for is ore's own\n// bounded, internal scheduling: `queueMicrotask`-scheduled onMounted callbacks.\n// `testing/flush()` polls `hasPendingWork()` to know precisely when that work has\n// settled, instead of draining a fixed, guessed number of microtask turns.\nlet pendingWork = 0;\n\n/**\n * @internal Mark one scheduled mount-callback microtask as started. Call the\n * returned function exactly once when it completes.\n */\nexport const beginPendingWork = (): (() => void) => {\n pendingWork++;\n\n let ended = false;\n\n return () => {\n if (ended) return;\n\n ended = true;\n pendingWork--;\n };\n};\n\n/** @internal True while any tracked component work is in flight. Polled by `testing/flush()`. */\nexport const hasPendingWork = (): boolean => pendingWork > 0;\n\n/** @internal Execute fn with a given runtime context active. */\nexport const runWithContext = <T>(ctx: RuntimeContext, fn: () => T): T => {\n const prev = currentContext;\n\n currentContext = ctx;\n\n try {\n return fn();\n } finally {\n currentContext = prev;\n }\n};\n\n/**\n * Returns the current runtime context, throwing a consistently-worded error\n * (naming the calling API) if called outside `setup()`. Every lifecycle/context\n * hook below routes through this: it's the single place that decides both\n * \"are we inside setup?\" and what the resulting error looks like, so the error\n * message is never worse for one hook than another.\n * @internal\n */\nexport const requireSetupContext = (api: string): RuntimeContext => {\n if (currentContext) return currentContext;\n\n throw new OreApiError(`${api}: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\n\n/**\n * Returns the current component's host element.\n * Only valid synchronously during component `setup()` (or inside a composable\n * called from it): throws otherwise.\n */\nexport const getHost = (): HTMLElement => requireSetupContext('getHost').element;\n\nexport const tryRegisterCleanup = (fn: Cleanup): boolean => {\n if (!currentContext) return false;\n\n _effect(() => fn);\n\n return true;\n};\n\n/** Registers cleanup work for component disconnect. */\nexport const onCleanup = (fn: Cleanup): void => {\n if (!tryRegisterCleanup(fn)) throw new OreApiError(`onCleanup: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\n\n/**\n * Register work to run after the component template mounts to the DOM.\n * Multiple callbacks run in registration order.\n */\nexport const onMounted = (fn: OnMountedCallback): void => {\n requireSetupContext('onMounted').mountCallbacks.push(fn);\n};\n\n/**\n * Register work to run when the ancestor `<form>` is reset (native `formResetCallback`,\n * only fires for `formAssociated: true` components). Multiple callbacks run in\n * registration order, every time the form resets: unlike `onMounted`, this isn't a\n * one-shot hook.\n */\nexport const onFormReset = (fn: OnFormResetCallback): void => {\n requireSetupContext('onFormReset').formResetCallbacks.push(fn);\n};\n\n/**\n * Create a reactive effect scoped to the component lifecycle.\n * Automatically cleaned up on component disconnect.\n * Returns a stop function that disposes the effect immediately.\n *\n * Named `watchEffect` (not `watch`) to avoid shadowing `@vielzeug/ripple`'s\n * `watch(source, callback)`, which has different semantics (explicit source,\n * old/new value pair): the two are commonly imported in the same file.\n */\nexport const watchEffect = (fn: () => Cleanup | undefined): (() => void) => {\n const sub = _effect(fn);\n const stop = (): void => sub.dispose();\n\n tryRegisterCleanup(stop);\n\n return stop;\n};\n\n/**\n * Attach a scoped event listener that is automatically removed on component disconnect.\n * Silently no-ops when `target` is `null` or `undefined` (safe for reactive targets).\n */\nexport function onEvent<K extends keyof HTMLElementEventMap>(\n target: EventTarget | null | undefined,\n event: K,\n listener: (e: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions,\n): void;\nexport function onEvent(\n target: EventTarget | null | undefined,\n event: string,\n listener: EventListener,\n options?: AddEventListenerOptions,\n): void {\n requireSetupContext('onEvent');\n\n if (!target) return;\n\n const cleanup = listenInternal(target, event, listener, options);\n\n if (!tryRegisterCleanup(cleanup)) cleanup();\n}\n\n/**\n * Watch a ref signal and run a callback when it resolves to a non-null element.\n * The callback's return value is used as a cleanup function.\n */\nexport const onElement = <T extends HTMLElement>(\n ref: Readable<T | null>,\n callback: (el: T) => Cleanup | undefined,\n): (() => void) => {\n return watchEffect(() => {\n const el = ref.value;\n\n if (el) return callback(el);\n });\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"runtime.cjs","names":["OreApiError","ORE_ERRORS","_effect","listenInternal","require_errors","require_dom","_vielzeug_ripple","currentContext","createRuntimeContext","pendingWork","beginPendingWork","ended","hasPendingWork","runWithContext","prev","ctx","fn","requireSetupContext","api","getHost","tryRegisterCleanup","onCleanup","onMounted","onFormReset","watchEffect","sub","stop","onEvent","target","cleanup","event","listener","options","onElement","el","ref","callback"],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Component runtime: implicit \"current component\" context plus every lifecycle\n * hook a `setup()` function can call.\n *\n * Design note: hooks are plain module-level functions (not a bag/context object\n * passed into `setup`). They resolve the active component through a single\n * module-level pointer (`currentContext`), set for the duration of `setup()` and\n * of each queued `onMounted` callback. This is the same mechanism React/Vue/Solid\n * use for their composable hooks: it lets any helper function (not just the\n * top-level `setup()` body) call `onMounted`/`onCleanup`/`bind`/... directly,\n * with no context object to thread through every layer of a composable.\n */\nimport { effect as _effect, type Cleanup, type Readable } from '@vielzeug/ripple';\n\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { listen as listenInternal } from './utils/dom';\n\n// ─── Runtime context ──────────────────────────────────────────────────────────\n// A single context object carries both the host element and mount callbacks,\n// eliminating two parallel globals that were always set together.\n\nexport type OnMountedCallback = () => Cleanup | undefined;\nexport type OnFormResetCallback = () => void;\n\nexport type RuntimeContext = {\n element: HTMLElement;\n formResetCallbacks: OnFormResetCallback[];\n mountCallbacks: OnMountedCallback[];\n};\n\nlet currentContext: RuntimeContext | null = null;\n\n/**\n * @internal Create a fresh runtime context for a component element. The single\n * construction site for `RuntimeContext`: used by `BaseElement` (setup and\n * per-callback mount contexts) and by `testing/render-hook.ts`, so the test\n * harness can never silently desync from a new required field.\n */\nexport const createRuntimeContext = (element: HTMLElement): RuntimeContext => ({\n element,\n formResetCallbacks: [],\n mountCallbacks: [],\n});\n\n// ─── Pending work tracking ──────────────────────────────────────────────────\n// A single counter of \"in-flight scheduled work\" across every live component\n// instance on the page: incremented when a mount-callback microtask is scheduled\n// (base-element.ts's _scheduleMountCallbacks()), decremented when it completes.\n//\n// Why this exists: `@vielzeug/ripple`'s reactive graph settles fully synchronously on\n// every signal write (see ripple's scheduling.ts): there is no async flush queue to wait\n// for there. The only genuinely async work `testing/flush()` needs to wait for is ore's own\n// bounded, internal scheduling: `queueMicrotask`-scheduled onMounted callbacks.\n// `testing/flush()` polls `hasPendingWork()` to know precisely when that work has\n// settled, instead of draining a fixed, guessed number of microtask turns.\nlet pendingWork = 0;\n\n/**\n * @internal Mark one scheduled mount-callback microtask as started. Call the\n * returned function exactly once when it completes.\n */\nexport const beginPendingWork = (): (() => void) => {\n pendingWork++;\n\n let ended = false;\n\n return () => {\n if (ended) return;\n\n ended = true;\n pendingWork--;\n };\n};\n\n/** @internal True while any tracked component work is in flight. Polled by `testing/flush()`. */\nexport const hasPendingWork = (): boolean => pendingWork > 0;\n\n/** @internal Execute fn with a given runtime context active. */\nexport const runWithContext = <T>(ctx: RuntimeContext, fn: () => T): T => {\n const prev = currentContext;\n\n currentContext = ctx;\n\n try {\n return fn();\n } finally {\n currentContext = prev;\n }\n};\n\n/**\n * Returns the current runtime context, throwing a consistently-worded error\n * (naming the calling API) if called outside `setup()`. Every lifecycle/context\n * hook below routes through this: it's the single place that decides both\n * \"are we inside setup?\" and what the resulting error looks like, so the error\n * message is never worse for one hook than another.\n * @internal\n */\nexport const requireSetupContext = (api: string): RuntimeContext => {\n if (currentContext) return currentContext;\n\n throw new OreApiError(`${api}: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\n\n/**\n * Returns the current component's host element.\n * Only valid synchronously during component `setup()` (or inside a composable\n * called from it): throws otherwise.\n */\nexport const getHost = (): HTMLElement => requireSetupContext('getHost').element;\n\nexport const tryRegisterCleanup = (fn: Cleanup): boolean => {\n if (!currentContext) return false;\n\n _effect(() => fn);\n\n return true;\n};\n\n/** Registers cleanup work for component disconnect. */\nexport const onCleanup = (fn: Cleanup): void => {\n if (!tryRegisterCleanup(fn)) throw new OreApiError(`onCleanup: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\n\n/**\n * Register work to run after the component template mounts to the DOM.\n * Multiple callbacks run in registration order.\n */\nexport const onMounted = (fn: OnMountedCallback): void => {\n requireSetupContext('onMounted').mountCallbacks.push(fn);\n};\n\n/**\n * Register work to run when the ancestor `<form>` is reset (native `formResetCallback`,\n * only fires for `formAssociated: true` components). Multiple callbacks run in\n * registration order, every time the form resets: unlike `onMounted`, this isn't a\n * one-shot hook.\n */\nexport const onFormReset = (fn: OnFormResetCallback): void => {\n requireSetupContext('onFormReset').formResetCallbacks.push(fn);\n};\n\n/**\n * Create a reactive effect scoped to the component lifecycle.\n * Automatically cleaned up on component disconnect.\n * Returns a stop function that disposes the effect immediately.\n *\n * Named `watchEffect` (not `watch`) to avoid shadowing `@vielzeug/ripple`'s\n * `watch(source, callback)`, which has different semantics (explicit source,\n * old/new value pair): the two are commonly imported in the same file.\n */\nexport const watchEffect = (fn: () => Cleanup | undefined): (() => void) => {\n const sub = _effect(fn);\n const stop = (): void => sub.dispose();\n\n tryRegisterCleanup(stop);\n\n return stop;\n};\n\n/**\n * Attach a scoped event listener that is automatically removed on component disconnect.\n * Silently no-ops when `target` is `null` or `undefined` (safe for reactive targets).\n */\nexport function onEvent<K extends keyof HTMLElementEventMap>(\n target: EventTarget | null | undefined,\n event: K,\n listener: (e: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions,\n): void;\nexport function onEvent(\n target: EventTarget | null | undefined,\n event: string,\n listener: EventListener,\n options?: AddEventListenerOptions,\n): void {\n requireSetupContext('onEvent');\n\n if (!target) return;\n\n const cleanup = listenInternal(target, event, listener, options);\n\n if (!tryRegisterCleanup(cleanup)) cleanup();\n}\n\n/**\n * Watch a ref signal and run a callback when it resolves to a non-null element.\n * The callback's return value is used as a cleanup function.\n */\nexport const onElement = <T extends HTMLElement>(\n ref: Readable<T | null>,\n callback: (el: T) => Cleanup | undefined,\n): (() => void) => {\n return watchEffect(() => {\n const el = ref.value;\n\n if (el) return callback(el);\n });\n};\n"],"mappings":"A,+FA8BA,IAAIO,EAAwC,KAQ/BC,EAAwB,IAA0C,CAC7E,UACA,mBAAoB,CAAC,EACrB,eAAgB,CAAC,CACnB,GAaIC,EAAc,EAMLC,MAAuC,CAClD,IAEA,IAAIC,EAAQ,GAEZ,UAAa,CACPA,IAEJ,EAAQ,GACR,IACF,CACF,EAGaC,MAAgCH,EAAc,EAG9CI,GAAqB,EAAqB,IAAmB,CACxE,IAAMC,EAAOP,EAEb,EAAiBQ,EAEjB,GAAI,CACF,OAAOC,EAAG,CACZ,QAAU,CACR,EAAiBF,CACnB,CACF,EAUaG,EAAuB,GAAgC,CAClE,GAAIV,EAAgB,OAAOA,EAE3B,MAAM,IAAIP,EAAAA,YAAY,GAAGkB,EAAI,IAAIjB,EAAAA,WAAW,uBAAuB,CACrE,EAOakB,MAA6BF,EAAoB,SAAS,CAAC,CAAC,QAE5DG,EAAsB,GAC5Bb,IAEL,EAAAD,EAAA,OAAA,KAAcU,CAAE,EAET,IAJqB,GAQjBK,EAAa,GAAsB,CAC9C,GAAI,CAACD,EAAmBJ,CAAE,EAAG,MAAM,IAAIhB,EAAAA,YAAY,cAAcC,EAAAA,WAAW,uBAAuB,CACrG,EAMaqB,EAAa,GAAgC,CACxDL,EAAoB,WAAW,CAAC,CAAC,eAAe,KAAKD,CAAE,CACzD,EAQaO,EAAe,GAAkC,CAC5DN,EAAoB,aAAa,CAAC,CAAC,mBAAmB,KAAKD,CAAE,CAC/D,EAWaQ,EAAe,GAAgD,CAC1E,IAAMC,GAAA,EAAMvB,EAAAA,OAAAA,CAAQc,CAAE,EAChBU,MAAmBD,EAAI,QAAQ,EAIrC,OAFAL,EAAmBM,CAAI,EAEhBA,CACT,EAYA,SAAgBC,EACd,EACA,EACA,EACA,EACM,CAGN,GAFAV,EAAoB,SAAS,EAEzB,CAACW,EAAQ,OAEb,IAAMC,EAAU1B,EAAAA,OAAeyB,EAAQE,EAAOC,EAAUC,CAAO,EAE1DZ,EAAmBS,CAAO,GAAGA,EAAQ,CAC5C,CAMA,IAAaI,GACX,EACA,IAEOT,MAAkB,CACvB,IAAMU,EAAKC,EAAI,MAEf,GAAID,EAAI,OAAOE,EAASF,CAAE,CAC5B,CAAC,E"}
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Component runtime: implicit \"current component\" context plus every lifecycle\n * hook a `setup()` function can call.\n *\n * Design note: hooks are plain module-level functions (not a bag/context object\n * passed into `setup`). They resolve the active component through a single\n * module-level pointer (`currentContext`), set for the duration of `setup()` and\n * of each queued `onMounted` callback. This is the same mechanism React/Vue/Solid\n * use for their composable hooks: it lets any helper function (not just the\n * top-level `setup()` body) call `onMounted`/`onCleanup`/`bind`/... directly,\n * with no context object to thread through every layer of a composable.\n */\nimport { effect as _effect, type Cleanup, type Readable } from '@vielzeug/ripple';\n\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { listen as listenInternal } from './utils/dom';\n\n// ─── Runtime context ──────────────────────────────────────────────────────────\n// A single context object carries both the host element and mount callbacks,\n// eliminating two parallel globals that were always set together.\n\nexport type OnMountedCallback = () => Cleanup | undefined;\nexport type OnFormResetCallback = () => void;\n\nexport type RuntimeContext = {\n element: HTMLElement;\n formResetCallbacks: OnFormResetCallback[];\n mountCallbacks: OnMountedCallback[];\n};\n\nlet currentContext: RuntimeContext | null = null;\n\n/**\n * @internal Create a fresh runtime context for a component element. The single\n * construction site for `RuntimeContext`: used by `BaseElement` (setup and\n * per-callback mount contexts) and by `testing/render-hook.ts`, so the test\n * harness can never silently desync from a new required field.\n */\nexport const createRuntimeContext = (element: HTMLElement): RuntimeContext => ({\n element,\n formResetCallbacks: [],\n mountCallbacks: [],\n});\n\n// ─── Pending work tracking ──────────────────────────────────────────────────\n// A single counter of \"in-flight scheduled work\" across every live component\n// instance on the page: incremented when a mount-callback microtask is scheduled\n// (base-element.ts's _scheduleMountCallbacks()), decremented when it completes.\n//\n// Why this exists: `@vielzeug/ripple`'s reactive graph settles fully synchronously on\n// every signal write (see ripple's scheduling.ts): there is no async flush queue to wait\n// for there. The only genuinely async work `testing/flush()` needs to wait for is ore's own\n// bounded, internal scheduling: `queueMicrotask`-scheduled onMounted callbacks.\n// `testing/flush()` polls `hasPendingWork()` to know precisely when that work has\n// settled, instead of draining a fixed, guessed number of microtask turns.\nlet pendingWork = 0;\n\n/**\n * @internal Mark one scheduled mount-callback microtask as started. Call the\n * returned function exactly once when it completes.\n */\nexport const beginPendingWork = (): (() => void) => {\n pendingWork++;\n\n let ended = false;\n\n return () => {\n if (ended) return;\n\n ended = true;\n pendingWork--;\n };\n};\n\n/** @internal True while any tracked component work is in flight. Polled by `testing/flush()`. */\nexport const hasPendingWork = (): boolean => pendingWork > 0;\n\n/** @internal Execute fn with a given runtime context active. */\nexport const runWithContext = <T>(ctx: RuntimeContext, fn: () => T): T => {\n const prev = currentContext;\n\n currentContext = ctx;\n\n try {\n return fn();\n } finally {\n currentContext = prev;\n }\n};\n\n/**\n * Returns the current runtime context, throwing a consistently-worded error\n * (naming the calling API) if called outside `setup()`. Every lifecycle/context\n * hook below routes through this: it's the single place that decides both\n * \"are we inside setup?\" and what the resulting error looks like, so the error\n * message is never worse for one hook than another.\n * @internal\n */\nexport const requireSetupContext = (api: string): RuntimeContext => {\n if (currentContext) return currentContext;\n\n throw new OreApiError(`${api}: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\n\n/**\n * Returns the current component's host element.\n * Only valid synchronously during component `setup()` (or inside a composable\n * called from it): throws otherwise.\n */\nexport const getHost = (): HTMLElement => requireSetupContext('getHost').element;\n\nexport const tryRegisterCleanup = (fn: Cleanup): boolean => {\n if (!currentContext) return false;\n\n _effect(() => fn);\n\n return true;\n};\n\n/** Registers cleanup work for component disconnect. */\nexport const onCleanup = (fn: Cleanup): void => {\n if (!tryRegisterCleanup(fn)) throw new OreApiError(`onCleanup: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\n\n/**\n * Register work to run after the component template mounts to the DOM.\n * Multiple callbacks run in registration order.\n */\nexport const onMounted = (fn: OnMountedCallback): void => {\n requireSetupContext('onMounted').mountCallbacks.push(fn);\n};\n\n/**\n * Register work to run when the ancestor `<form>` is reset (native `formResetCallback`,\n * only fires for `formAssociated: true` components). Multiple callbacks run in\n * registration order, every time the form resets: unlike `onMounted`, this isn't a\n * one-shot hook.\n */\nexport const onFormReset = (fn: OnFormResetCallback): void => {\n requireSetupContext('onFormReset').formResetCallbacks.push(fn);\n};\n\n/**\n * Create a reactive effect scoped to the component lifecycle.\n * Automatically cleaned up on component disconnect.\n * Returns a stop function that disposes the effect immediately.\n *\n * Named `watchEffect` (not `watch`) to avoid shadowing `@vielzeug/ripple`'s\n * `watch(source, callback)`, which has different semantics (explicit source,\n * old/new value pair): the two are commonly imported in the same file.\n */\nexport const watchEffect = (fn: () => Cleanup | undefined): (() => void) => {\n const sub = _effect(fn);\n const stop = (): void => sub.dispose();\n\n tryRegisterCleanup(stop);\n\n return stop;\n};\n\n/**\n * Attach a scoped event listener that is automatically removed on component disconnect.\n * Silently no-ops when `target` is `null` or `undefined` (safe for reactive targets).\n */\nexport function onEvent<K extends keyof HTMLElementEventMap>(\n target: EventTarget | null | undefined,\n event: K,\n listener: (e: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions,\n): void;\nexport function onEvent(\n target: EventTarget | null | undefined,\n event: string,\n listener: EventListener,\n options?: AddEventListenerOptions,\n): void {\n requireSetupContext('onEvent');\n\n if (!target) return;\n\n const cleanup = listenInternal(target, event, listener, options);\n\n if (!tryRegisterCleanup(cleanup)) cleanup();\n}\n\n/**\n * Watch a ref signal and run a callback when it resolves to a non-null element.\n * The callback's return value is used as a cleanup function.\n */\nexport const onElement = <T extends HTMLElement>(\n ref: Readable<T | null>,\n callback: (el: T) => Cleanup | undefined,\n): (() => void) => {\n return watchEffect(() => {\n const el = ref.value;\n\n if (el) return callback(el);\n });\n};\n"],"mappings":"4IA8BA,
|
|
1
|
+
{"version":3,"file":"runtime.js","names":["_effect","listenInternal","currentContext","createRuntimeContext","pendingWork","beginPendingWork","ended","hasPendingWork","runWithContext","prev","ctx","fn","requireSetupContext","OreApiError","api","ORE_ERRORS","getHost","tryRegisterCleanup","effect","onCleanup","onMounted","onFormReset","watchEffect","sub","stop","onEvent","target","cleanup","listen","event","listener","options","onElement","el","ref","callback"],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Component runtime: implicit \"current component\" context plus every lifecycle\n * hook a `setup()` function can call.\n *\n * Design note: hooks are plain module-level functions (not a bag/context object\n * passed into `setup`). They resolve the active component through a single\n * module-level pointer (`currentContext`), set for the duration of `setup()` and\n * of each queued `onMounted` callback. This is the same mechanism React/Vue/Solid\n * use for their composable hooks: it lets any helper function (not just the\n * top-level `setup()` body) call `onMounted`/`onCleanup`/`bind`/... directly,\n * with no context object to thread through every layer of a composable.\n */\nimport { effect as _effect, type Cleanup, type Readable } from '@vielzeug/ripple';\n\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { listen as listenInternal } from './utils/dom';\n\n// ─── Runtime context ──────────────────────────────────────────────────────────\n// A single context object carries both the host element and mount callbacks,\n// eliminating two parallel globals that were always set together.\n\nexport type OnMountedCallback = () => Cleanup | undefined;\nexport type OnFormResetCallback = () => void;\n\nexport type RuntimeContext = {\n element: HTMLElement;\n formResetCallbacks: OnFormResetCallback[];\n mountCallbacks: OnMountedCallback[];\n};\n\nlet currentContext: RuntimeContext | null = null;\n\n/**\n * @internal Create a fresh runtime context for a component element. The single\n * construction site for `RuntimeContext`: used by `BaseElement` (setup and\n * per-callback mount contexts) and by `testing/render-hook.ts`, so the test\n * harness can never silently desync from a new required field.\n */\nexport const createRuntimeContext = (element: HTMLElement): RuntimeContext => ({\n element,\n formResetCallbacks: [],\n mountCallbacks: [],\n});\n\n// ─── Pending work tracking ──────────────────────────────────────────────────\n// A single counter of \"in-flight scheduled work\" across every live component\n// instance on the page: incremented when a mount-callback microtask is scheduled\n// (base-element.ts's _scheduleMountCallbacks()), decremented when it completes.\n//\n// Why this exists: `@vielzeug/ripple`'s reactive graph settles fully synchronously on\n// every signal write (see ripple's scheduling.ts): there is no async flush queue to wait\n// for there. The only genuinely async work `testing/flush()` needs to wait for is ore's own\n// bounded, internal scheduling: `queueMicrotask`-scheduled onMounted callbacks.\n// `testing/flush()` polls `hasPendingWork()` to know precisely when that work has\n// settled, instead of draining a fixed, guessed number of microtask turns.\nlet pendingWork = 0;\n\n/**\n * @internal Mark one scheduled mount-callback microtask as started. Call the\n * returned function exactly once when it completes.\n */\nexport const beginPendingWork = (): (() => void) => {\n pendingWork++;\n\n let ended = false;\n\n return () => {\n if (ended) return;\n\n ended = true;\n pendingWork--;\n };\n};\n\n/** @internal True while any tracked component work is in flight. Polled by `testing/flush()`. */\nexport const hasPendingWork = (): boolean => pendingWork > 0;\n\n/** @internal Execute fn with a given runtime context active. */\nexport const runWithContext = <T>(ctx: RuntimeContext, fn: () => T): T => {\n const prev = currentContext;\n\n currentContext = ctx;\n\n try {\n return fn();\n } finally {\n currentContext = prev;\n }\n};\n\n/**\n * Returns the current runtime context, throwing a consistently-worded error\n * (naming the calling API) if called outside `setup()`. Every lifecycle/context\n * hook below routes through this: it's the single place that decides both\n * \"are we inside setup?\" and what the resulting error looks like, so the error\n * message is never worse for one hook than another.\n * @internal\n */\nexport const requireSetupContext = (api: string): RuntimeContext => {\n if (currentContext) return currentContext;\n\n throw new OreApiError(`${api}: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\n\n/**\n * Returns the current component's host element.\n * Only valid synchronously during component `setup()` (or inside a composable\n * called from it): throws otherwise.\n */\nexport const getHost = (): HTMLElement => requireSetupContext('getHost').element;\n\nexport const tryRegisterCleanup = (fn: Cleanup): boolean => {\n if (!currentContext) return false;\n\n _effect(() => fn);\n\n return true;\n};\n\n/** Registers cleanup work for component disconnect. */\nexport const onCleanup = (fn: Cleanup): void => {\n if (!tryRegisterCleanup(fn)) throw new OreApiError(`onCleanup: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\n\n/**\n * Register work to run after the component template mounts to the DOM.\n * Multiple callbacks run in registration order.\n */\nexport const onMounted = (fn: OnMountedCallback): void => {\n requireSetupContext('onMounted').mountCallbacks.push(fn);\n};\n\n/**\n * Register work to run when the ancestor `<form>` is reset (native `formResetCallback`,\n * only fires for `formAssociated: true` components). Multiple callbacks run in\n * registration order, every time the form resets: unlike `onMounted`, this isn't a\n * one-shot hook.\n */\nexport const onFormReset = (fn: OnFormResetCallback): void => {\n requireSetupContext('onFormReset').formResetCallbacks.push(fn);\n};\n\n/**\n * Create a reactive effect scoped to the component lifecycle.\n * Automatically cleaned up on component disconnect.\n * Returns a stop function that disposes the effect immediately.\n *\n * Named `watchEffect` (not `watch`) to avoid shadowing `@vielzeug/ripple`'s\n * `watch(source, callback)`, which has different semantics (explicit source,\n * old/new value pair): the two are commonly imported in the same file.\n */\nexport const watchEffect = (fn: () => Cleanup | undefined): (() => void) => {\n const sub = _effect(fn);\n const stop = (): void => sub.dispose();\n\n tryRegisterCleanup(stop);\n\n return stop;\n};\n\n/**\n * Attach a scoped event listener that is automatically removed on component disconnect.\n * Silently no-ops when `target` is `null` or `undefined` (safe for reactive targets).\n */\nexport function onEvent<K extends keyof HTMLElementEventMap>(\n target: EventTarget | null | undefined,\n event: K,\n listener: (e: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions,\n): void;\nexport function onEvent(\n target: EventTarget | null | undefined,\n event: string,\n listener: EventListener,\n options?: AddEventListenerOptions,\n): void {\n requireSetupContext('onEvent');\n\n if (!target) return;\n\n const cleanup = listenInternal(target, event, listener, options);\n\n if (!tryRegisterCleanup(cleanup)) cleanup();\n}\n\n/**\n * Watch a ref signal and run a callback when it resolves to a non-null element.\n * The callback's return value is used as a cleanup function.\n */\nexport const onElement = <T extends HTMLElement>(\n ref: Readable<T | null>,\n callback: (el: T) => Cleanup | undefined,\n): (() => void) => {\n return watchEffect(() => {\n const el = ref.value;\n\n if (el) return callback(el);\n });\n};\n"],"mappings":"A,4IA8BA,IAAIE,EAAwC,KAQ/BC,EAAwB,IAA0C,CAC7E,UACA,mBAAoB,CAAC,EACrB,eAAgB,CAAC,CACnB,GAaIC,EAAc,EAMLC,MAAuC,CAClD,IAEA,IAAIC,EAAQ,GAEZ,UAAa,CACPA,IAEJ,EAAQ,GACR,IACF,CACF,EAGaC,MAAgCH,EAAc,EAG9CI,GAAqB,EAAqB,IAAmB,CACxE,IAAMC,EAAOP,EAEb,EAAiBQ,EAEjB,GAAI,CACF,OAAOC,EAAG,CACZ,QAAU,CACR,EAAiBF,CACnB,CACF,EAUaG,EAAuB,GAAgC,CAClE,GAAIV,EAAgB,OAAOA,EAE3B,MAAM,IAAIW,EAAY,GAAGC,EAAI,IAAIC,EAAW,uBAAuB,CACrE,EAOaC,MAA6BJ,EAAoB,SAAS,CAAC,CAAC,QAE5DK,EAAsB,GAC5Bf,GAELgB,MAAcP,CAAE,EAET,IAJqB,GAQjBQ,EAAa,GAAsB,CAC9C,GAAI,CAACF,EAAmBN,CAAE,EAAG,MAAM,IAAIE,EAAY,cAAcE,EAAW,uBAAuB,CACrG,EAMaK,EAAa,GAAgC,CACxDR,EAAoB,WAAW,CAAC,CAAC,eAAe,KAAKD,CAAE,CACzD,EAQaU,EAAe,GAAkC,CAC5DT,EAAoB,aAAa,CAAC,CAAC,mBAAmB,KAAKD,CAAE,CAC/D,EAWaW,EAAe,GAAgD,CAC1E,IAAMC,EAAMvB,EAAQW,CAAE,EAChBa,MAAmBD,EAAI,QAAQ,EAIrC,OAFAN,EAAmBO,CAAI,EAEhBA,CACT,EAYA,SAAgBC,EACd,EACA,EACA,EACA,EACM,CAGN,GAFAb,EAAoB,SAAS,EAEzB,CAACc,EAAQ,OAEb,IAAMC,EAAU1B,EAAeyB,EAAQG,EAAOC,EAAUC,CAAO,EAE1Dd,EAAmBU,CAAO,GAAGA,EAAQ,CAC5C,CAMA,IAAaK,GACX,EACA,IAEOV,MAAkB,CACvB,IAAMW,EAAKC,EAAI,MAEf,GAAID,EAAI,OAAOE,EAASF,CAAE,CAC5B,CAAC,E"}
|
package/dist/slots.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slots.cjs","names":[],"sources":["../src/slots.ts"],"sourcesContent":["/**\n * Slot observation and reactive slot signals.\n *\n * `slots.has(name?)`: Signal<boolean>: whether a named slot has assigned elements.\n * `slots.elements(name?)`: Signal<Element[]>: assigned elements for a slot (flattened).\n */\n\nimport { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted, requireSetupContext } from './runtime';\n\nexport type ComponentSlots<SlotNames extends string = string> = {\n elements: (name?: SlotNames) => Readable<Element[]>;\n has: (name?: SlotNames) => Readable<boolean>;\n};\n\nconst SLOT_DEFAULT = 'default';\nconst normalizeSlotName = (slotName: string | null | undefined): string => slotName || SLOT_DEFAULT;\n\nconst createSlots = (host: HTMLElement): ComponentSlots<string> => {\n type SlotEntry = {\n elements: Signal<Element[]>;\n presence: Signal<boolean>;\n };\n\n const slotSignals = new Map<string, SlotEntry>();\n const slotNodesByName = new Map<string, Set<HTMLSlotElement>>();\n const slotCleanupMap = new Map<HTMLSlotElement, () => void>();\n\n const ensureSlotEntry = (normalizedName: string): SlotEntry => {\n let entry = slotSignals.get(normalizedName);\n\n if (!entry) {\n entry = {\n elements: signal<Element[]>([]),\n presence: signal(false),\n };\n slotSignals.set(normalizedName, entry);\n }\n\n return entry;\n };\n\n const areElementsEqual = (prev: Element[], next: Element[]): boolean => {\n if (prev.length !== next.length) return false;\n\n for (let i = 0; i < prev.length; i++) {\n if (prev[i] !== next[i]) return false;\n }\n\n return true;\n };\n\n const recomputeSlot = (name: string): void => {\n const normalized = normalizeSlotName(name);\n const slotsForName = slotNodesByName.get(normalized);\n const assigned: Element[] = [];\n\n if (slotsForName) {\n for (const slotEl of slotsForName) {\n assigned.push(...slotEl.assignedElements({ flatten: true }));\n }\n }\n\n const entry = ensureSlotEntry(normalized);\n\n if (!areElementsEqual(entry.elements.value, assigned)) entry.elements.value = assigned;\n\n const hasElements = assigned.length > 0;\n\n if (entry.presence.value !== hasElements) entry.presence.value = hasElements;\n };\n\n const bindSlot = (slotEl: HTMLSlotElement): void => {\n if (slotCleanupMap.has(slotEl)) return;\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name) ?? new Set<HTMLSlotElement>();\n\n setForName.add(slotEl);\n slotNodesByName.set(name, setForName);\n\n const onChange = () => recomputeSlot(name);\n\n slotEl.addEventListener('slotchange', onChange);\n\n slotCleanupMap.set(slotEl, () => {\n slotEl.removeEventListener('slotchange', onChange);\n });\n\n recomputeSlot(name);\n };\n\n const unbindSlot = (slotEl: HTMLSlotElement): void => {\n const cleanup = slotCleanupMap.get(slotEl);\n\n if (!cleanup) return;\n\n cleanup();\n slotCleanupMap.delete(slotEl);\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name);\n\n if (setForName) {\n setForName.delete(slotEl);\n\n if (setForName.size === 0) slotNodesByName.delete(name);\n }\n\n recomputeSlot(name);\n };\n\n const bindAllSlots = (): void => {\n host.shadowRoot?.querySelectorAll('slot').forEach((slotEl) => {\n bindSlot(slotEl);\n });\n };\n\n const recomputeAllSlots = (): void => {\n for (const name of slotNodesByName.keys()) {\n recomputeSlot(name);\n }\n };\n\n // Watch for dynamically-inserted <slot> elements (e.g. inside when(), each()).\n let observer: MutationObserver | null = null;\n\n // Single init pass, run after the first render: binds slots already present\n // (pre-upgrade markup and template-rendered ones alike) and starts observation\n // for slots inserted later. The observer must stay connected for the component's\n // lifetime: it is the only way to detect a *first* <slot> appearing dynamically\n // (e.g. a when() branch that renders a slot), so it cannot be disconnected when\n // the bound-slot count drops to zero.\n const initSlots = (): undefined => {\n bindAllSlots();\n recomputeAllSlots();\n\n if (!observer && host.shadowRoot) {\n observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.removedNodes) {\n if (node instanceof HTMLSlotElement) unbindSlot(node);\n }\n }\n\n bindAllSlots();\n\n if (slotCleanupMap.size > 0) recomputeAllSlots();\n });\n observer.observe(host.shadowRoot, { childList: true, subtree: true });\n }\n\n return undefined;\n };\n\n onMounted(initSlots);\n\n onCleanup(() => {\n observer?.disconnect();\n observer = null;\n\n for (const cleanup of slotCleanupMap.values()) cleanup();\n\n slotCleanupMap.clear();\n slotNodesByName.clear();\n slotSignals.clear();\n\n // The element instance survives disconnect/reconnect (custom elements aren't recreated),\n // but this registry's observer/listeners are torn down above: drop the cache entry so a\n // subsequent reconnect's setup() rebuilds a live registry instead of reusing a dead one.\n slotsByElement.delete(host);\n });\n\n return {\n elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n };\n};\n\n// Keyed by the host element, not the ephemeral `RuntimeContext`: `onMounted()` callbacks each\n// run with their own freshly-created context object (see base-element.ts's\n// `_scheduleMountCallbacks`), so keying this on `RuntimeContext` would silently create a second,\n// independent registry (a second `MutationObserver`, a second signal set) every time `useSlots()`\n// was called from inside `onMounted()` rather than directly in `setup()`: a real bug the\n// \"one registry per instance\" doc comment below never actually held for that (common) case.\n/** One slot registry per component instance: reused across repeated `useSlots()` calls. */\nconst slotsByElement = new WeakMap<HTMLElement, ComponentSlots<string>>();\n\n/**\n * Returns reactive slot presence / element signals for the current component.\n * Safe to call multiple times during `setup()`: the underlying slot registry\n * (MutationObserver + `slotchange` listeners) is created once per instance.\n *\n * Pass a `SlotNames` type parameter for typed slot names:\n * ```ts\n * const slots = useSlots<'header' | 'footer'>();\n * slots.has('header'); // typed ✓\n * ```\n */\nexport const useSlots = <SlotNames extends string = string>(): ComponentSlots<SlotNames> => {\n const ctx = requireSetupContext('useSlots');\n let entry = slotsByElement.get(ctx.element);\n\n if (!entry) {\n entry = createSlots(ctx.element);\n slotsByElement.set(ctx.element, entry);\n }\n\n return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slots.cjs","names":["signal","requireSetupContext","require_runtime","_vielzeug_ripple","SLOT_DEFAULT","normalizeSlotName","slotName","createSlots","slotSignals","slotNodesByName","slotCleanupMap","ensureSlotEntry","entry","normalizedName","areElementsEqual","prev","next","i","recomputeSlot","normalized","name","slotsForName","assigned","slotEl","hasElements","bindSlot","setForName","onChange","unbindSlot","cleanup","bindAllSlots","host","recomputeAllSlots","observer","mutation","mutations","node","slotsByElement","useSlots","ctx"],"sources":["../src/slots.ts"],"sourcesContent":["/**\n * Slot observation and reactive slot signals.\n *\n * `slots.has(name?)`: Signal<boolean>: whether a named slot has assigned elements.\n * `slots.elements(name?)`: Signal<Element[]>: assigned elements for a slot (flattened).\n */\n\nimport { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted, requireSetupContext } from './runtime';\n\nexport type ComponentSlots<SlotNames extends string = string> = {\n elements: (name?: SlotNames) => Readable<Element[]>;\n has: (name?: SlotNames) => Readable<boolean>;\n};\n\nconst SLOT_DEFAULT = 'default';\nconst normalizeSlotName = (slotName: string | null | undefined): string => slotName || SLOT_DEFAULT;\n\nconst createSlots = (host: HTMLElement): ComponentSlots<string> => {\n type SlotEntry = {\n elements: Signal<Element[]>;\n presence: Signal<boolean>;\n };\n\n const slotSignals = new Map<string, SlotEntry>();\n const slotNodesByName = new Map<string, Set<HTMLSlotElement>>();\n const slotCleanupMap = new Map<HTMLSlotElement, () => void>();\n\n const ensureSlotEntry = (normalizedName: string): SlotEntry => {\n let entry = slotSignals.get(normalizedName);\n\n if (!entry) {\n entry = {\n elements: signal<Element[]>([]),\n presence: signal(false),\n };\n slotSignals.set(normalizedName, entry);\n }\n\n return entry;\n };\n\n const areElementsEqual = (prev: Element[], next: Element[]): boolean => {\n if (prev.length !== next.length) return false;\n\n for (let i = 0; i < prev.length; i++) {\n if (prev[i] !== next[i]) return false;\n }\n\n return true;\n };\n\n const recomputeSlot = (name: string): void => {\n const normalized = normalizeSlotName(name);\n const slotsForName = slotNodesByName.get(normalized);\n const assigned: Element[] = [];\n\n if (slotsForName) {\n for (const slotEl of slotsForName) {\n assigned.push(...slotEl.assignedElements({ flatten: true }));\n }\n }\n\n const entry = ensureSlotEntry(normalized);\n\n if (!areElementsEqual(entry.elements.value, assigned)) entry.elements.value = assigned;\n\n const hasElements = assigned.length > 0;\n\n if (entry.presence.value !== hasElements) entry.presence.value = hasElements;\n };\n\n const bindSlot = (slotEl: HTMLSlotElement): void => {\n if (slotCleanupMap.has(slotEl)) return;\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name) ?? new Set<HTMLSlotElement>();\n\n setForName.add(slotEl);\n slotNodesByName.set(name, setForName);\n\n const onChange = () => recomputeSlot(name);\n\n slotEl.addEventListener('slotchange', onChange);\n\n slotCleanupMap.set(slotEl, () => {\n slotEl.removeEventListener('slotchange', onChange);\n });\n\n recomputeSlot(name);\n };\n\n const unbindSlot = (slotEl: HTMLSlotElement): void => {\n const cleanup = slotCleanupMap.get(slotEl);\n\n if (!cleanup) return;\n\n cleanup();\n slotCleanupMap.delete(slotEl);\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name);\n\n if (setForName) {\n setForName.delete(slotEl);\n\n if (setForName.size === 0) slotNodesByName.delete(name);\n }\n\n recomputeSlot(name);\n };\n\n const bindAllSlots = (): void => {\n host.shadowRoot?.querySelectorAll('slot').forEach((slotEl) => {\n bindSlot(slotEl);\n });\n };\n\n const recomputeAllSlots = (): void => {\n for (const name of slotNodesByName.keys()) {\n recomputeSlot(name);\n }\n };\n\n // Watch for dynamically-inserted <slot> elements (e.g. inside when(), each()).\n let observer: MutationObserver | null = null;\n\n // Single init pass, run after the first render: binds slots already present\n // (pre-upgrade markup and template-rendered ones alike) and starts observation\n // for slots inserted later. The observer must stay connected for the component's\n // lifetime: it is the only way to detect a *first* <slot> appearing dynamically\n // (e.g. a when() branch that renders a slot), so it cannot be disconnected when\n // the bound-slot count drops to zero.\n const initSlots = (): undefined => {\n bindAllSlots();\n recomputeAllSlots();\n\n if (!observer && host.shadowRoot) {\n observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.removedNodes) {\n if (node instanceof HTMLSlotElement) unbindSlot(node);\n }\n }\n\n bindAllSlots();\n\n if (slotCleanupMap.size > 0) recomputeAllSlots();\n });\n observer.observe(host.shadowRoot, { childList: true, subtree: true });\n }\n\n return undefined;\n };\n\n onMounted(initSlots);\n\n onCleanup(() => {\n observer?.disconnect();\n observer = null;\n\n for (const cleanup of slotCleanupMap.values()) cleanup();\n\n slotCleanupMap.clear();\n slotNodesByName.clear();\n slotSignals.clear();\n\n // The element instance survives disconnect/reconnect (custom elements aren't recreated),\n // but this registry's observer/listeners are torn down above: drop the cache entry so a\n // subsequent reconnect's setup() rebuilds a live registry instead of reusing a dead one.\n slotsByElement.delete(host);\n });\n\n return {\n elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n };\n};\n\n// Keyed by the host element, not the ephemeral `RuntimeContext`: `onMounted()` callbacks each\n// run with their own freshly-created context object (see base-element.ts's\n// `_scheduleMountCallbacks`), so keying this on `RuntimeContext` would silently create a second,\n// independent registry (a second `MutationObserver`, a second signal set) every time `useSlots()`\n// was called from inside `onMounted()` rather than directly in `setup()`: a real bug the\n// \"one registry per instance\" doc comment below never actually held for that (common) case.\n/** One slot registry per component instance: reused across repeated `useSlots()` calls. */\nconst slotsByElement = new WeakMap<HTMLElement, ComponentSlots<string>>();\n\n/**\n * Returns reactive slot presence / element signals for the current component.\n * Safe to call multiple times during `setup()`: the underlying slot registry\n * (MutationObserver + `slotchange` listeners) is created once per instance.\n *\n * Pass a `SlotNames` type parameter for typed slot names:\n * ```ts\n * const slots = useSlots<'header' | 'footer'>();\n * slots.has('header'); // typed ✓\n * ```\n */\nexport const useSlots = <SlotNames extends string = string>(): ComponentSlots<SlotNames> => {\n const ctx = requireSetupContext('useSlots');\n let entry = slotsByElement.get(ctx.element);\n\n if (!entry) {\n entry = createSlots(ctx.element);\n slotsByElement.set(ctx.element, entry);\n }\n\n return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}
|
package/dist/slots.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{onCleanup as e,onMounted as t,requireSetupContext as n}from"./runtime.js";import{signal as r}from"@vielzeug/ripple";var i=`default`,a=e=>e||i,o=n=>{let i
|
|
1
|
+
import{onCleanup as e,onMounted as t,requireSetupContext as n}from"./runtime.js";import{signal as r}from"@vielzeug/ripple";var i=`default`,a=e=>e||i,o=n=>{let i=/* @__PURE__ */ new Map,o=/* @__PURE__ */ new Map,c=/* @__PURE__ */ new Map,l=e=>{let t=i.get(e);return t||(t={elements:r([]),presence:r(!1)},i.set(e,t)),t},u=(e,t)=>{if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0},d=e=>{let t=a(e),n=o.get(t),r=[];if(n)for(let e of n)r.push(...e.assignedElements({flatten:!0}));let i=l(t);u(i.elements.value,r)||(i.elements.value=r);let s=r.length>0;i.presence.value!==s&&(i.presence.value=s)},f=e=>{if(c.has(e))return;let t=a(e.getAttribute(`name`)),n=o.get(t)??/* @__PURE__ */ new Set;n.add(e),o.set(t,n);let r=()=>d(t);e.addEventListener(`slotchange`,r),c.set(e,()=>{e.removeEventListener(`slotchange`,r)}),d(t)},p=e=>{let t=c.get(e);if(!t)return;t(),c.delete(e);let n=a(e.getAttribute(`name`)),r=o.get(n);r&&(r.delete(e),r.size===0&&o.delete(n)),d(n)},m=()=>{n.shadowRoot?.querySelectorAll(`slot`).forEach(e=>{f(e)})},h=()=>{for(let e of o.keys())d(e)},g=null;return t(()=>{m(),h(),!g&&n.shadowRoot&&(g=new MutationObserver(e=>{for(let t of e)for(let e of t.removedNodes)e instanceof HTMLSlotElement&&p(e);m(),c.size>0&&h()}),g.observe(n.shadowRoot,{childList:!0,subtree:!0}))}),e(()=>{g?.disconnect(),g=null;for(let e of c.values())e();c.clear(),o.clear(),i.clear(),s.delete(n)}),{elements:e=>l(a(e)).elements,has:e=>l(a(e)).presence}},s=/* @__PURE__ */ new WeakMap,c=()=>{let e=n(`useSlots`),t=s.get(e.element);return t||(t=o(e.element),s.set(e.element,t)),t};export{c as useSlots};
|
|
2
2
|
//# sourceMappingURL=slots.js.map
|
package/dist/slots.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slots.js","names":[],"sources":["../src/slots.ts"],"sourcesContent":["/**\n * Slot observation and reactive slot signals.\n *\n * `slots.has(name?)`: Signal<boolean>: whether a named slot has assigned elements.\n * `slots.elements(name?)`: Signal<Element[]>: assigned elements for a slot (flattened).\n */\n\nimport { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted, requireSetupContext } from './runtime';\n\nexport type ComponentSlots<SlotNames extends string = string> = {\n elements: (name?: SlotNames) => Readable<Element[]>;\n has: (name?: SlotNames) => Readable<boolean>;\n};\n\nconst SLOT_DEFAULT = 'default';\nconst normalizeSlotName = (slotName: string | null | undefined): string => slotName || SLOT_DEFAULT;\n\nconst createSlots = (host: HTMLElement): ComponentSlots<string> => {\n type SlotEntry = {\n elements: Signal<Element[]>;\n presence: Signal<boolean>;\n };\n\n const slotSignals = new Map<string, SlotEntry>();\n const slotNodesByName = new Map<string, Set<HTMLSlotElement>>();\n const slotCleanupMap = new Map<HTMLSlotElement, () => void>();\n\n const ensureSlotEntry = (normalizedName: string): SlotEntry => {\n let entry = slotSignals.get(normalizedName);\n\n if (!entry) {\n entry = {\n elements: signal<Element[]>([]),\n presence: signal(false),\n };\n slotSignals.set(normalizedName, entry);\n }\n\n return entry;\n };\n\n const areElementsEqual = (prev: Element[], next: Element[]): boolean => {\n if (prev.length !== next.length) return false;\n\n for (let i = 0; i < prev.length; i++) {\n if (prev[i] !== next[i]) return false;\n }\n\n return true;\n };\n\n const recomputeSlot = (name: string): void => {\n const normalized = normalizeSlotName(name);\n const slotsForName = slotNodesByName.get(normalized);\n const assigned: Element[] = [];\n\n if (slotsForName) {\n for (const slotEl of slotsForName) {\n assigned.push(...slotEl.assignedElements({ flatten: true }));\n }\n }\n\n const entry = ensureSlotEntry(normalized);\n\n if (!areElementsEqual(entry.elements.value, assigned)) entry.elements.value = assigned;\n\n const hasElements = assigned.length > 0;\n\n if (entry.presence.value !== hasElements) entry.presence.value = hasElements;\n };\n\n const bindSlot = (slotEl: HTMLSlotElement): void => {\n if (slotCleanupMap.has(slotEl)) return;\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name) ?? new Set<HTMLSlotElement>();\n\n setForName.add(slotEl);\n slotNodesByName.set(name, setForName);\n\n const onChange = () => recomputeSlot(name);\n\n slotEl.addEventListener('slotchange', onChange);\n\n slotCleanupMap.set(slotEl, () => {\n slotEl.removeEventListener('slotchange', onChange);\n });\n\n recomputeSlot(name);\n };\n\n const unbindSlot = (slotEl: HTMLSlotElement): void => {\n const cleanup = slotCleanupMap.get(slotEl);\n\n if (!cleanup) return;\n\n cleanup();\n slotCleanupMap.delete(slotEl);\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name);\n\n if (setForName) {\n setForName.delete(slotEl);\n\n if (setForName.size === 0) slotNodesByName.delete(name);\n }\n\n recomputeSlot(name);\n };\n\n const bindAllSlots = (): void => {\n host.shadowRoot?.querySelectorAll('slot').forEach((slotEl) => {\n bindSlot(slotEl);\n });\n };\n\n const recomputeAllSlots = (): void => {\n for (const name of slotNodesByName.keys()) {\n recomputeSlot(name);\n }\n };\n\n // Watch for dynamically-inserted <slot> elements (e.g. inside when(), each()).\n let observer: MutationObserver | null = null;\n\n // Single init pass, run after the first render: binds slots already present\n // (pre-upgrade markup and template-rendered ones alike) and starts observation\n // for slots inserted later. The observer must stay connected for the component's\n // lifetime: it is the only way to detect a *first* <slot> appearing dynamically\n // (e.g. a when() branch that renders a slot), so it cannot be disconnected when\n // the bound-slot count drops to zero.\n const initSlots = (): undefined => {\n bindAllSlots();\n recomputeAllSlots();\n\n if (!observer && host.shadowRoot) {\n observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.removedNodes) {\n if (node instanceof HTMLSlotElement) unbindSlot(node);\n }\n }\n\n bindAllSlots();\n\n if (slotCleanupMap.size > 0) recomputeAllSlots();\n });\n observer.observe(host.shadowRoot, { childList: true, subtree: true });\n }\n\n return undefined;\n };\n\n onMounted(initSlots);\n\n onCleanup(() => {\n observer?.disconnect();\n observer = null;\n\n for (const cleanup of slotCleanupMap.values()) cleanup();\n\n slotCleanupMap.clear();\n slotNodesByName.clear();\n slotSignals.clear();\n\n // The element instance survives disconnect/reconnect (custom elements aren't recreated),\n // but this registry's observer/listeners are torn down above: drop the cache entry so a\n // subsequent reconnect's setup() rebuilds a live registry instead of reusing a dead one.\n slotsByElement.delete(host);\n });\n\n return {\n elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n };\n};\n\n// Keyed by the host element, not the ephemeral `RuntimeContext`: `onMounted()` callbacks each\n// run with their own freshly-created context object (see base-element.ts's\n// `_scheduleMountCallbacks`), so keying this on `RuntimeContext` would silently create a second,\n// independent registry (a second `MutationObserver`, a second signal set) every time `useSlots()`\n// was called from inside `onMounted()` rather than directly in `setup()`: a real bug the\n// \"one registry per instance\" doc comment below never actually held for that (common) case.\n/** One slot registry per component instance: reused across repeated `useSlots()` calls. */\nconst slotsByElement = new WeakMap<HTMLElement, ComponentSlots<string>>();\n\n/**\n * Returns reactive slot presence / element signals for the current component.\n * Safe to call multiple times during `setup()`: the underlying slot registry\n * (MutationObserver + `slotchange` listeners) is created once per instance.\n *\n * Pass a `SlotNames` type parameter for typed slot names:\n * ```ts\n * const slots = useSlots<'header' | 'footer'>();\n * slots.has('header'); // typed ✓\n * ```\n */\nexport const useSlots = <SlotNames extends string = string>(): ComponentSlots<SlotNames> => {\n const ctx = requireSetupContext('useSlots');\n let entry = slotsByElement.get(ctx.element);\n\n if (!entry) {\n entry = createSlots(ctx.element);\n slotsByElement.set(ctx.element, entry);\n }\n\n return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slots.js","names":["SLOT_DEFAULT","normalizeSlotName","slotName","createSlots","slotSignals","slotNodesByName","slotCleanupMap","ensureSlotEntry","entry","normalizedName","signal","areElementsEqual","prev","next","i","recomputeSlot","normalized","name","slotsForName","assigned","slotEl","hasElements","bindSlot","setForName","onChange","unbindSlot","cleanup","bindAllSlots","host","recomputeAllSlots","observer","onMounted","mutation","mutations","node","onCleanup","slotsByElement","useSlots","ctx","requireSetupContext"],"sources":["../src/slots.ts"],"sourcesContent":["/**\n * Slot observation and reactive slot signals.\n *\n * `slots.has(name?)`: Signal<boolean>: whether a named slot has assigned elements.\n * `slots.elements(name?)`: Signal<Element[]>: assigned elements for a slot (flattened).\n */\n\nimport { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted, requireSetupContext } from './runtime';\n\nexport type ComponentSlots<SlotNames extends string = string> = {\n elements: (name?: SlotNames) => Readable<Element[]>;\n has: (name?: SlotNames) => Readable<boolean>;\n};\n\nconst SLOT_DEFAULT = 'default';\nconst normalizeSlotName = (slotName: string | null | undefined): string => slotName || SLOT_DEFAULT;\n\nconst createSlots = (host: HTMLElement): ComponentSlots<string> => {\n type SlotEntry = {\n elements: Signal<Element[]>;\n presence: Signal<boolean>;\n };\n\n const slotSignals = new Map<string, SlotEntry>();\n const slotNodesByName = new Map<string, Set<HTMLSlotElement>>();\n const slotCleanupMap = new Map<HTMLSlotElement, () => void>();\n\n const ensureSlotEntry = (normalizedName: string): SlotEntry => {\n let entry = slotSignals.get(normalizedName);\n\n if (!entry) {\n entry = {\n elements: signal<Element[]>([]),\n presence: signal(false),\n };\n slotSignals.set(normalizedName, entry);\n }\n\n return entry;\n };\n\n const areElementsEqual = (prev: Element[], next: Element[]): boolean => {\n if (prev.length !== next.length) return false;\n\n for (let i = 0; i < prev.length; i++) {\n if (prev[i] !== next[i]) return false;\n }\n\n return true;\n };\n\n const recomputeSlot = (name: string): void => {\n const normalized = normalizeSlotName(name);\n const slotsForName = slotNodesByName.get(normalized);\n const assigned: Element[] = [];\n\n if (slotsForName) {\n for (const slotEl of slotsForName) {\n assigned.push(...slotEl.assignedElements({ flatten: true }));\n }\n }\n\n const entry = ensureSlotEntry(normalized);\n\n if (!areElementsEqual(entry.elements.value, assigned)) entry.elements.value = assigned;\n\n const hasElements = assigned.length > 0;\n\n if (entry.presence.value !== hasElements) entry.presence.value = hasElements;\n };\n\n const bindSlot = (slotEl: HTMLSlotElement): void => {\n if (slotCleanupMap.has(slotEl)) return;\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name) ?? new Set<HTMLSlotElement>();\n\n setForName.add(slotEl);\n slotNodesByName.set(name, setForName);\n\n const onChange = () => recomputeSlot(name);\n\n slotEl.addEventListener('slotchange', onChange);\n\n slotCleanupMap.set(slotEl, () => {\n slotEl.removeEventListener('slotchange', onChange);\n });\n\n recomputeSlot(name);\n };\n\n const unbindSlot = (slotEl: HTMLSlotElement): void => {\n const cleanup = slotCleanupMap.get(slotEl);\n\n if (!cleanup) return;\n\n cleanup();\n slotCleanupMap.delete(slotEl);\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name);\n\n if (setForName) {\n setForName.delete(slotEl);\n\n if (setForName.size === 0) slotNodesByName.delete(name);\n }\n\n recomputeSlot(name);\n };\n\n const bindAllSlots = (): void => {\n host.shadowRoot?.querySelectorAll('slot').forEach((slotEl) => {\n bindSlot(slotEl);\n });\n };\n\n const recomputeAllSlots = (): void => {\n for (const name of slotNodesByName.keys()) {\n recomputeSlot(name);\n }\n };\n\n // Watch for dynamically-inserted <slot> elements (e.g. inside when(), each()).\n let observer: MutationObserver | null = null;\n\n // Single init pass, run after the first render: binds slots already present\n // (pre-upgrade markup and template-rendered ones alike) and starts observation\n // for slots inserted later. The observer must stay connected for the component's\n // lifetime: it is the only way to detect a *first* <slot> appearing dynamically\n // (e.g. a when() branch that renders a slot), so it cannot be disconnected when\n // the bound-slot count drops to zero.\n const initSlots = (): undefined => {\n bindAllSlots();\n recomputeAllSlots();\n\n if (!observer && host.shadowRoot) {\n observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.removedNodes) {\n if (node instanceof HTMLSlotElement) unbindSlot(node);\n }\n }\n\n bindAllSlots();\n\n if (slotCleanupMap.size > 0) recomputeAllSlots();\n });\n observer.observe(host.shadowRoot, { childList: true, subtree: true });\n }\n\n return undefined;\n };\n\n onMounted(initSlots);\n\n onCleanup(() => {\n observer?.disconnect();\n observer = null;\n\n for (const cleanup of slotCleanupMap.values()) cleanup();\n\n slotCleanupMap.clear();\n slotNodesByName.clear();\n slotSignals.clear();\n\n // The element instance survives disconnect/reconnect (custom elements aren't recreated),\n // but this registry's observer/listeners are torn down above: drop the cache entry so a\n // subsequent reconnect's setup() rebuilds a live registry instead of reusing a dead one.\n slotsByElement.delete(host);\n });\n\n return {\n elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n };\n};\n\n// Keyed by the host element, not the ephemeral `RuntimeContext`: `onMounted()` callbacks each\n// run with their own freshly-created context object (see base-element.ts's\n// `_scheduleMountCallbacks`), so keying this on `RuntimeContext` would silently create a second,\n// independent registry (a second `MutationObserver`, a second signal set) every time `useSlots()`\n// was called from inside `onMounted()` rather than directly in `setup()`: a real bug the\n// \"one registry per instance\" doc comment below never actually held for that (common) case.\n/** One slot registry per component instance: reused across repeated `useSlots()` calls. */\nconst slotsByElement = new WeakMap<HTMLElement, ComponentSlots<string>>();\n\n/**\n * Returns reactive slot presence / element signals for the current component.\n * Safe to call multiple times during `setup()`: the underlying slot registry\n * (MutationObserver + `slotchange` listeners) is created once per instance.\n *\n * Pass a `SlotNames` type parameter for typed slot names:\n * ```ts\n * const slots = useSlots<'header' | 'footer'>();\n * slots.has('header'); // typed ✓\n * ```\n */\nexport const useSlots = <SlotNames extends string = string>(): ComponentSlots<SlotNames> => {\n const ctx = requireSetupContext('useSlots');\n let entry = slotsByElement.get(ctx.element);\n\n if (!entry) {\n entry = createSlots(ctx.element);\n slotsByElement.set(ctx.element, entry);\n }\n\n return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../errors.cjs"),t=require("../utils/dom.cjs"),n=require("../props.cjs"),r=require("./result.cjs"),i=require("../directives/live.cjs");let a=require("@vielzeug/ripple");var o=(e,t,n)=>{let r=(0,a.effect)(()=>{t(e.value)});n(()=>r.dispose())},s=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,c=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),l=(e,t,n,r={last:void 0})=>{let i=c(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},u=(e,n,r,i)=>{let o=t.isStructuredValue(i)?i:n.parse(r.mode===`bool`?i?``:null:i==null||i===!1?null:String(i));if(Object.is((0,a.untrack)(()=>n.signal.value),o)||(n.signal.value=o),!n.reflect){if(t.isStructuredValue(i))return;r.mode===`bool`?e.toggleAttribute(r.name,!!i):t.setAttr(e,r.name,i)}},d=(e,r)=>{let{el:i,mode:a,name:c}=e,d={last:void 0},f=r=>{let o=n.getPropMeta(i,c);
|
|
1
|
+
const e=require("../errors.cjs"),t=require("../utils/dom.cjs"),n=require("../props.cjs"),r=require("./result.cjs"),i=require("../directives/live.cjs");let a=require("@vielzeug/ripple");var o=(e,t,n)=>{let r=(0,a.effect)(()=>{t(e.value)});n(()=>r.dispose())},s=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,c=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),l=(e,t,n,r={last:void 0})=>{let i=c(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},u=(e,n,r,i)=>{let o=t.isStructuredValue(i)?i:n.parse(r.mode===`bool`?i?``:null:i==null||i===!1?null:String(i));if(Object.is((0,a.untrack)(()=>n.signal.value),o)||(n.signal.value=o),!n.reflect){if(t.isStructuredValue(i))return;r.mode===`bool`?e.toggleAttribute(r.name,!!i):t.setAttr(e,r.name,i)}},d=(e,r)=>{let{el:i,mode:a,name:c}=e,d={last:void 0},f=r=>{let o=n.getPropMeta(i,c);o?u(i,o,e,r):c===`value`&&s(i)||c===`checked`&&i instanceof HTMLInputElement?l(i,r,e.live,d):c===`indeterminate`&&i instanceof HTMLInputElement?i.indeterminate=!!r:a===`bool`?i.toggleAttribute(c,!!r):t.setAttr(i,c,r)};`signal`in e?o(e.signal,f,r):f(e.value)},f=(e,n)=>{let{modifiers:r}=e,i=r?.stop||r?.prevent?t=>{r.stop&&t.stopPropagation(),r.prevent&&t.preventDefault(),e.handler(t)}:e.handler,a=r?.once||r?.capture?{capture:r.capture,once:r.once}:void 0;n(t.listen(e.el,e.name,i,a))},p=(e,t)=>{let n=t=>{e.el[e.name]=t};`signal`in e?o(e.signal,n,t):n(e.value)},m=(e,n)=>{let{el:r,get:i,signal:o}=e,c={last:void 0},u=e=>{s(r)?l(r,e,!0,c):r.value=e},d=(0,a.effect)(()=>{u(o.value)});n(()=>d.dispose()),n(t.listen(r,e.event,()=>{let e=i(r);Object.is((0,a.untrack)(()=>o.value),e)||(o.value=e)}))},h=(e,t)=>{let{el:n,ref:r}=e;typeof r==`function`?(r(n),t(()=>r(null))):(r.value=n,t(()=>{r.value=null}))},g=(t,n,i)=>{let a=[],o=n.parentNode;e.invariant(o,`html binding anchor has no parent node`);for(let e of t)if(r.isHtmlResult(e)){let t=Array.from(e.fragment.childNodes);o.insertBefore(e.fragment,n),e.apply(i),a.push(...t)}else if(e!=null&&e!==!1){let t=document.createTextNode(String(e));o.insertBefore(t,n),a.push(t)}return a},_=(e,n)=>{let{anchor:r,signal:i}=e,o=t.createReplaceableSlot(),s=(0,a.effect)(()=>{let e=i.value;o.clear(),e!=null&&e.length!==0&&(0,a.untrack)(()=>{o.setNodes(g(e,r,o.registerCleanup))})});n(()=>{s.dispose(),o.clear()})},v=(e,t)=>{e.directive.mount(e.anchor,t)},y=(e,t)=>{switch(e.type){case`attr`:d(e,t);break;case`directive`:v(e,t);break;case`event`:f(e,t);break;case`html`:_(e,t);break;case`model`:m(e,t);break;case`prop`:p(e,t);break;case`ref`:h(e,t)}},b=(e,t,n,r)=>i.isLiveBinding(r)?{el:e,live:!0,mode:t,name:n,signal:r.source,type:`attr`}:typeof r==`function`?{el:e,mode:t,name:n,signal:(0,a.computed)(r),type:`attr`}:(0,a.isReactive)(r)?{el:e,mode:t,name:n,signal:r,type:`attr`}:{el:e,mode:t,name:n,type:`attr`,value:r},x=e=>e==null?``:String(e);exports.applyAttrBinding=d,exports.applyBinding=y,exports.applyHtmlBinding=_,exports.createAttrBindingFromValue=b,exports.resolveStaticText=x,exports.syncFormControl=l;
|
|
2
2
|
//# sourceMappingURL=bindings.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.cjs","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, model,\n * prop, ref, directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings, and the two-way `model()` write-back loop.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n ModelBinding,\n PropBinding,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n // Resolved per update, not at instantiation: a nested custom element only\n // registers its props when it is connected, which happens when this result\n // mounts: later than binding construction. By update time the registry entry\n // exists, so a structured value (array/object/callback) reaches a registered\n // prop through its signal instead of an attribute round-trip that could not\n // represent it. Unregistered targets get no such pass: structured data to\n // them is an explicit `.prop=` binding.\n const propMeta = getPropMeta(el, name);\n\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n // `indeterminate` is a property-only concept on checkboxes (no attribute exists):\n // bind it like any other element property so reactive indeterminate state works\n // through the same `indeterminate=\"${sig}\"` syntax as `checked`.\n if (name === 'indeterminate' && el instanceof HTMLInputElement) {\n el.indeterminate = Boolean(value);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n const { modifiers } = binding;\n\n const handler =\n modifiers?.stop || modifiers?.prevent\n ? (e: Event): void => {\n if (modifiers.stop) e.stopPropagation();\n if (modifiers.prevent) e.preventDefault();\n binding.handler(e);\n }\n : binding.handler;\n\n const options =\n modifiers?.once || modifiers?.capture ? { capture: modifiers.capture, once: modifiers.once } : undefined;\n\n registerCleanup(listen(binding.el, binding.name, handler, options));\n};\n\n// ─── Explicit property bindings ───────────────────────────────────────────────\n\n/**\n * `.prop=${value}` binding: always writes the element property (never an\n * attribute). This is the explicit channel for structured values (arrays,\n * objects, callbacks): the old implicit \"value happens to be an object, so set\n * a property\" fallback in the attr path is gone, so intent lives in the markup.\n * Prototype-polluting names are rejected at compile time (see compiler.ts\n * PROP_RE), not silently dropped here.\n */\nconst applyPropBinding = (binding: PropBinding, registerCleanup: RegisterCleanup): void => {\n const update = (value: unknown): void => {\n (binding.el as unknown as Record<string, unknown>)[binding.name] = value;\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Two-way model bindings ──────────────────────────────────────────────────\n\n/**\n * `model()` binding: signal → control property (through the same live-aware\n * `syncFormControl` write path as `live()`, so an in-progress user edit is never\n * clobbered) and control event → signal (the write-back that makes it two-way).\n */\nconst applyModelBinding = (binding: ModelBinding, registerCleanup: RegisterCleanup): void => {\n const { el, get, signal } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const write = (value: unknown): void => {\n if (isNativeFormInput(el)) {\n syncFormControl(el, value, true, liveState);\n } else {\n // Non-native target: plain property write (custom controls expose `value`).\n (el as unknown as { value: unknown }).value = value;\n }\n };\n\n const sub = rawEffect(() => {\n write(signal.value);\n return undefined;\n });\n\n const onEvent: EventListener = () => {\n const next = get(el);\n\n if (\n !Object.is(\n untrack(() => signal.value),\n next,\n )\n ) {\n (signal as Signal<unknown>).value = next;\n }\n };\n\n registerCleanup(() => sub.dispose());\n registerCleanup(listen(el, binding.event, onEvent));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'model':\n applyModelBinding(binding, registerCleanup);\n break;\n case 'prop':\n applyPropBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"yLAqCA,IAAM,GACJ,EACA,EACA,IACS,CACT,IAAM,GAAA,EAAM,EAAA,OAAA,KAAgB,CAC1B,EAAO,EAAO,KAAK,CAErB,CAAC,EAED,MAAsB,EAAI,QAAQ,CAAC,CACrC,EAIM,EAAqB,GACzB,aAAc,kBAAoB,aAAc,qBAAuB,aAAc,kBAEjF,EAAoB,GACxB,aAAc,mBAAqB,EAAG,OAAS,YAAc,EAAG,OAAS,SAY9D,GACX,EACA,EACA,EACA,EAAwB,CAAE,KAAM,IAAA,EAAU,IACjC,CACT,IAAM,EAAY,EAAiB,CAAE,EAC/B,EAAyB,EAAY,EAAQ,EAAS,GAAS,KAAO,GAAK,OAAO,CAAK,EACvF,EAA4B,EAAa,EAAwB,QAAU,EAAG,OAEhF,IAAU,EAAM,OAAS,IAAA,IAAc,OAAO,GAAG,EAAS,EAAM,IAAI,GAAM,OAAO,GAAG,EAAS,CAAI,KAEjG,EAAW,EAAyB,QAAU,EAC7C,EAAG,MAAQ,EAEZ,IAAQ,EAAM,KAAO,GAC3B,EAIM,GAAsB,EAAiB,EAAgB,EAAsB,IAAyB,CAC1G,IAAM,EAAS,EAAA,kBAAkB,CAAK,EAClC,EACA,EAAK,MACH,EAAQ,OAAS,OAAU,EAAQ,GAAK,KAAQ,GAAS,MAAQ,IAAU,GAAQ,KAAO,OAAO,CAAK,CACxG,EAWJ,GARG,OAAO,IAAA,EACN,EAAA,QAAA,KAAc,EAAK,OAAO,KAAK,EAC/B,CACF,IAEA,EAAK,OAAO,MAAQ,GAGlB,CAAC,EAAK,QAAS,CACjB,GAAI,EAAA,kBAAkB,CAAK,EAAG,OAE1B,EAAQ,OAAS,OAAQ,EAAG,gBAAgB,EAAQ,KAAM,EAAQ,CAAM,EACvE,EAAA,QAAQ,EAAI,EAAQ,KAAM,CAAK,CACtC,CACF,EAEa,GAAoB,EAAsB,IAA2C,CAChG,GAAM,CAAE,KAAI,OAAM,QAAS,EACrB,EAA4B,CAAE,KAAM,IAAA,EAAU,EAE9C,EAAU,GAAyB,CAQvC,IAAM,EAAW,EAAA,YAAY,EAAI,CAAI,EAErC,GAAI,EAAU,CACZ,EAAmB,EAAI,EAAU,EAAS,CAAK,EAE/C,MACF,CAEA,GAAK,IAAS,SAAW,EAAkB,CAAE,GAAO,IAAS,WAAa,aAAc,iBAAmB,CACzG,EAAgB,EAAkE,EAAO,EAAQ,KAAM,CAAS,EAEhH,MACF,CAKA,GAAI,IAAS,iBAAmB,aAAc,iBAAkB,CAC9D,EAAG,cAAgB,EAAQ,EAE3B,MACF,CAEI,IAAS,OAAQ,EAAG,gBAAgB,EAAM,EAAQ,CAAM,EACvD,EAAA,QAAQ,EAAI,EAAM,CAAK,CAC9B,EAEI,WAAY,EACd,EAAa,EAAQ,OAAQ,EAAQ,CAAe,EAEpD,EAAO,EAAQ,KAAK,CAExB,EAIM,GAAqB,EAAuB,IAA2C,CAC3F,GAAM,CAAE,aAAc,EAEhB,EACJ,GAAW,MAAQ,GAAW,QACzB,GAAmB,CACd,EAAU,MAAM,EAAE,gBAAgB,EAClC,EAAU,SAAS,EAAE,eAAe,EACxC,EAAQ,QAAQ,CAAC,CACnB,EACA,EAAQ,QAER,EACJ,GAAW,MAAQ,GAAW,QAAU,CAAE,QAAS,EAAU,QAAS,KAAM,EAAU,IAAK,EAAI,IAAA,GAEjG,EAAgB,EAAA,OAAO,EAAQ,GAAI,EAAQ,KAAM,EAAS,CAAO,CAAC,CACpE,EAYM,GAAoB,EAAsB,IAA2C,CACzF,IAAM,EAAU,GAAyB,CACvC,EAAS,GAA0C,EAAQ,MAAQ,CACrE,EAEI,WAAY,EACd,EAAa,EAAQ,OAAQ,EAAQ,CAAe,EAEpD,EAAO,EAAQ,KAAK,CAExB,EASM,GAAqB,EAAuB,IAA2C,CAC3F,GAAM,CAAE,KAAI,MAAK,UAAW,EACtB,EAA4B,CAAE,KAAM,IAAA,EAAU,EAE9C,EAAS,GAAyB,CAClC,EAAkB,CAAE,EACtB,EAAgB,EAAI,EAAO,GAAM,CAAS,EAG1C,EAAsC,MAAQ,CAElD,EAEM,GAAA,EAAM,EAAA,OAAA,KAAgB,CAC1B,EAAM,EAAO,KAAK,CAEpB,CAAC,EAeD,MAAsB,EAAI,QAAQ,CAAC,EACnC,EAAgB,EAAA,OAAO,EAAI,EAAQ,UAdE,CACnC,IAAM,EAAO,EAAI,CAAE,EAGhB,OAAO,IAAA,EACN,EAAA,QAAA,KAAc,EAAO,KAAK,EAC1B,CACF,IAEA,EAA4B,MAAQ,EAExC,CAGiD,CAAC,CACpD,EAIM,GAAmB,EAAqB,IAA2C,CACvF,GAAM,CAAE,KAAI,OAAQ,EAEpB,GAAI,OAAO,GAAQ,WAAY,CAC7B,EAAI,CAAW,EACf,MAAsB,EAAI,IAAa,CAAC,EAExC,MACF,CAEA,EAAI,MAAQ,EACZ,MAAsB,CACpB,EAAI,MAAQ,IACd,CAAC,CACH,EAIM,GACJ,EACA,EACA,IACW,CACX,IAAM,EAAgB,CAAC,EACjB,EAAS,EAAa,WAE5B,EAAA,UAAU,EAAQ,wCAAwC,EAE1D,IAAK,IAAM,KAAK,EACd,GAAI,EAAA,aAAa,CAAC,EAAG,CACnB,IAAM,EAAW,MAAM,KAAK,EAAE,SAAS,UAAU,EAEjD,EAAO,aAAa,EAAE,SAAU,CAAY,EAC5C,EAAE,MAAM,CAAe,EACvB,EAAM,KAAK,GAAG,CAAQ,CACxB,MAAO,GAAI,GAAK,MAAQ,IAAM,GAAO,CACnC,IAAM,EAAO,SAAS,eAAe,OAAO,CAAC,CAAC,EAE9C,EAAO,aAAa,EAAM,CAAY,EACtC,EAAM,KAAK,CAAI,CACjB,CAGF,OAAO,CACT,EAEa,GAAoB,EAAsB,IAA2C,CAChG,GAAM,CAAE,SAAQ,UAAW,EAErB,EAAO,EAAA,sBAAsB,EAE7B,GAAA,EAAO,EAAA,OAAA,KAAgB,CAC3B,IAAM,EAAM,EAAO,MAEnB,EAAK,MAAM,EAEP,GAAO,MAAQ,EAAI,SAAW,IAElC,EAAA,EAAA,QAAA,KAAc,CACZ,EAAK,SAAS,EAAiB,EAAK,EAAQ,EAAK,eAAe,CAAC,CACnE,CAAC,CACH,CAAC,EAED,MAAsB,CACpB,EAAK,QAAQ,EACb,EAAK,MAAM,CACb,CAAC,CACH,EAIM,GAAyB,EAA2B,IAA2C,CACnG,EAAQ,UAAU,MAAM,EAAQ,OAAQ,CAAe,CACzD,EAIa,GAAgB,EAAkB,IAA2C,CACxF,OAAQ,EAAQ,KAAhB,CACE,IAAK,OACH,EAAiB,EAAS,CAAe,EACzC,MACF,IAAK,YACH,EAAsB,EAAS,CAAe,EAC9C,MACF,IAAK,QACH,EAAkB,EAAS,CAAe,EAC1C,MACF,IAAK,OACH,EAAiB,EAAS,CAAe,EACzC,MACF,IAAK,QACH,EAAkB,EAAS,CAAe,EAC1C,MACF,IAAK,OACH,EAAiB,EAAS,CAAe,EACzC,MACF,IAAK,MACH,EAAgB,EAAS,CAAe,CAE5C,CACF,EAIa,GACX,EACA,EACA,EACA,IAEI,EAAA,cAAc,CAAK,EACd,CAAE,KAAI,KAAM,GAAM,OAAM,OAAM,OAAQ,EAAM,OAAQ,KAAM,MAAO,EAGtE,OAAO,GAAU,WACZ,CAAE,KAAI,OAAM,OAAM,QAAA,EAAQ,EAAA,SAAA,CAAS,CAAsB,EAAG,KAAM,MAAO,GAGlF,EAAI,EAAA,WAAA,CAAW,CAAK,EACX,CAAE,KAAI,OAAM,OAAM,OAAQ,EAA4B,KAAM,MAAO,EAGrE,CAAE,KAAI,OAAM,OAAM,KAAM,OAAQ,OAAM,EAGlC,EAAqB,GAC5B,GAAS,KAAa,GAEnB,OAAO,CAAK"}
|
|
1
|
+
{"version":3,"file":"bindings.cjs","names":["rawEffect","isStructuredValue","untrack","getPropMeta","listen","isHtmlResult","createReplaceableSlot","isLiveBinding","computed","isReactive","require_errors","require_dom","require_props","require_result","require_live","_vielzeug_ripple","signalEffect","sub","update","signal","registerCleanup","isNativeFormInput","el","isCheckableInput","syncFormControl","checkable","next","value","current","state","isLive","syncRegisteredProp","parsed","meta","binding","applyAttrBinding","liveState","propMeta","name","mode","applyEventBinding","handler","modifiers","e","options","applyPropBinding","applyModelBinding","write","get","applyRefBinding","ref","insertHtmlValues","nodes","parent","insertBefore","v","values","captured","text","applyHtmlBinding","slot","stop","raw","anchor","applyDirectiveBinding","applyBinding","createAttrBindingFromValue","resolveStaticText"],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, model,\n * prop, ref, directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings, and the two-way `model()` write-back loop.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n ModelBinding,\n PropBinding,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n // Resolved per update, not at instantiation: a nested custom element only\n // registers its props when it is connected, which happens when this result\n // mounts: later than binding construction. By update time the registry entry\n // exists, so a structured value (array/object/callback) reaches a registered\n // prop through its signal instead of an attribute round-trip that could not\n // represent it. Unregistered targets get no such pass: structured data to\n // them is an explicit `.prop=` binding.\n const propMeta = getPropMeta(el, name);\n\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n // `indeterminate` is a property-only concept on checkboxes (no attribute exists):\n // bind it like any other element property so reactive indeterminate state works\n // through the same `indeterminate=\"${sig}\"` syntax as `checked`.\n if (name === 'indeterminate' && el instanceof HTMLInputElement) {\n el.indeterminate = Boolean(value);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n const { modifiers } = binding;\n\n const handler =\n modifiers?.stop || modifiers?.prevent\n ? (e: Event): void => {\n if (modifiers.stop) e.stopPropagation();\n if (modifiers.prevent) e.preventDefault();\n binding.handler(e);\n }\n : binding.handler;\n\n const options =\n modifiers?.once || modifiers?.capture ? { capture: modifiers.capture, once: modifiers.once } : undefined;\n\n registerCleanup(listen(binding.el, binding.name, handler, options));\n};\n\n// ─── Explicit property bindings ───────────────────────────────────────────────\n\n/**\n * `.prop=${value}` binding: always writes the element property (never an\n * attribute). This is the explicit channel for structured values (arrays,\n * objects, callbacks): the old implicit \"value happens to be an object, so set\n * a property\" fallback in the attr path is gone, so intent lives in the markup.\n * Prototype-polluting names are rejected at compile time (see compiler.ts\n * PROP_RE), not silently dropped here.\n */\nconst applyPropBinding = (binding: PropBinding, registerCleanup: RegisterCleanup): void => {\n const update = (value: unknown): void => {\n (binding.el as unknown as Record<string, unknown>)[binding.name] = value;\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Two-way model bindings ──────────────────────────────────────────────────\n\n/**\n * `model()` binding: signal → control property (through the same live-aware\n * `syncFormControl` write path as `live()`, so an in-progress user edit is never\n * clobbered) and control event → signal (the write-back that makes it two-way).\n */\nconst applyModelBinding = (binding: ModelBinding, registerCleanup: RegisterCleanup): void => {\n const { el, get, signal } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const write = (value: unknown): void => {\n if (isNativeFormInput(el)) {\n syncFormControl(el, value, true, liveState);\n } else {\n // Non-native target: plain property write (custom controls expose `value`).\n (el as unknown as { value: unknown }).value = value;\n }\n };\n\n const sub = rawEffect(() => {\n write(signal.value);\n return undefined;\n });\n\n const onEvent: EventListener = () => {\n const next = get(el);\n\n if (\n !Object.is(\n untrack(() => signal.value),\n next,\n )\n ) {\n (signal as Signal<unknown>).value = next;\n }\n };\n\n registerCleanup(() => sub.dispose());\n registerCleanup(listen(el, binding.event, onEvent));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'model':\n applyModelBinding(binding, registerCleanup);\n break;\n case 'prop':\n applyPropBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{invariant as e}from"../errors.js";import{createReplaceableSlot as t,isStructuredValue as n,listen as r,setAttr as i}from"../utils/dom.js";import{getPropMeta as a}from"../props.js";import{isHtmlResult as o}from"./result.js";import{isLiveBinding as s}from"../directives/live.js";import{computed as c,effect as l,isReactive as u,untrack as d}from"@vielzeug/ripple";var f=(e,t,n)=>{let r=l(()=>{t(e.value)});n(()=>r.dispose())},p=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,m=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),h=(e,t,n,r={last:void 0})=>{let i=m(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},g=(e,t,r,a)=>{let o=n(a)?a:t.parse(r.mode===`bool`?a?``:null:a==null||a===!1?null:String(a));if(Object.is(d(()=>t.signal.value),o)||(t.signal.value=o),!t.reflect){if(n(a))return;r.mode===`bool`?e.toggleAttribute(r.name,!!a):i(e,r.name,a)}},_=(e,t)=>{let{el:n,mode:r,name:o}=e,s={last:void 0},c=t=>{let c=a(n,o);
|
|
1
|
+
import{invariant as e}from"../errors.js";import{createReplaceableSlot as t,isStructuredValue as n,listen as r,setAttr as i}from"../utils/dom.js";import{getPropMeta as a}from"../props.js";import{isHtmlResult as o}from"./result.js";import{isLiveBinding as s}from"../directives/live.js";import{computed as c,effect as l,isReactive as u,untrack as d}from"@vielzeug/ripple";var f=(e,t,n)=>{let r=l(()=>{t(e.value)});n(()=>r.dispose())},p=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,m=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),h=(e,t,n,r={last:void 0})=>{let i=m(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},g=(e,t,r,a)=>{let o=n(a)?a:t.parse(r.mode===`bool`?a?``:null:a==null||a===!1?null:String(a));if(Object.is(d(()=>t.signal.value),o)||(t.signal.value=o),!t.reflect){if(n(a))return;r.mode===`bool`?e.toggleAttribute(r.name,!!a):i(e,r.name,a)}},_=(e,t)=>{let{el:n,mode:r,name:o}=e,s={last:void 0},c=t=>{let c=a(n,o);c?g(n,c,e,t):o===`value`&&p(n)||o===`checked`&&n instanceof HTMLInputElement?h(n,t,e.live,s):o===`indeterminate`&&n instanceof HTMLInputElement?n.indeterminate=!!t:r===`bool`?n.toggleAttribute(o,!!t):i(n,o,t)};`signal`in e?f(e.signal,c,t):c(e.value)},v=(e,t)=>{let{modifiers:n}=e,i=n?.stop||n?.prevent?t=>{n.stop&&t.stopPropagation(),n.prevent&&t.preventDefault(),e.handler(t)}:e.handler,a=n?.once||n?.capture?{capture:n.capture,once:n.once}:void 0;t(r(e.el,e.name,i,a))},y=(e,t)=>{let n=t=>{e.el[e.name]=t};`signal`in e?f(e.signal,n,t):n(e.value)},b=(e,t)=>{let{el:n,get:i,signal:a}=e,o={last:void 0},s=e=>{p(n)?h(n,e,!0,o):n.value=e},c=l(()=>{s(a.value)});t(()=>c.dispose()),t(r(n,e.event,()=>{let e=i(n);Object.is(d(()=>a.value),e)||(a.value=e)}))},x=(e,t)=>{let{el:n,ref:r}=e;typeof r==`function`?(r(n),t(()=>r(null))):(r.value=n,t(()=>{r.value=null}))},S=(t,n,r)=>{let i=[],a=n.parentNode;e(a,`html binding anchor has no parent node`);for(let e of t)if(o(e)){let t=Array.from(e.fragment.childNodes);a.insertBefore(e.fragment,n),e.apply(r),i.push(...t)}else if(e!=null&&e!==!1){let t=document.createTextNode(String(e));a.insertBefore(t,n),i.push(t)}return i},C=(e,n)=>{let{anchor:r,signal:i}=e,a=t(),o=l(()=>{let e=i.value;a.clear(),e!=null&&e.length!==0&&d(()=>{a.setNodes(S(e,r,a.registerCleanup))})});n(()=>{o.dispose(),a.clear()})},w=(e,t)=>{e.directive.mount(e.anchor,t)},T=(e,t)=>{switch(e.type){case`attr`:_(e,t);break;case`directive`:w(e,t);break;case`event`:v(e,t);break;case`html`:C(e,t);break;case`model`:b(e,t);break;case`prop`:y(e,t);break;case`ref`:x(e,t)}},E=(e,t,n,r)=>s(r)?{el:e,live:!0,mode:t,name:n,signal:r.source,type:`attr`}:typeof r==`function`?{el:e,mode:t,name:n,signal:c(r),type:`attr`}:u(r)?{el:e,mode:t,name:n,signal:r,type:`attr`}:{el:e,mode:t,name:n,type:`attr`,value:r},D=e=>e==null?``:String(e);export{_ as applyAttrBinding,T as applyBinding,C as applyHtmlBinding,E as createAttrBindingFromValue,D as resolveStaticText,h as syncFormControl};
|
|
2
2
|
//# sourceMappingURL=bindings.js.map
|