@vielzeug/ore 2.2.0 → 3.0.1

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.
Files changed (89) hide show
  1. package/README.md +2 -53
  2. package/dist/context.cjs +1 -1
  3. package/dist/context.cjs.map +1 -1
  4. package/dist/context.d.ts +4 -13
  5. package/dist/context.d.ts.map +1 -1
  6. package/dist/context.js +1 -1
  7. package/dist/context.js.map +1 -1
  8. package/dist/define.cjs.map +1 -1
  9. package/dist/define.d.ts +1 -1
  10. package/dist/define.d.ts.map +1 -1
  11. package/dist/define.js.map +1 -1
  12. package/dist/directives/each.cjs +1 -1
  13. package/dist/directives/each.cjs.map +1 -1
  14. package/dist/directives/each.d.ts +2 -2
  15. package/dist/directives/each.d.ts.map +1 -1
  16. package/dist/directives/each.js +1 -1
  17. package/dist/directives/each.js.map +1 -1
  18. package/dist/directives/when.cjs +1 -1
  19. package/dist/directives/when.cjs.map +1 -1
  20. package/dist/directives/when.js +1 -1
  21. package/dist/directives/when.js.map +1 -1
  22. package/dist/forms/field.cjs +1 -1
  23. package/dist/forms/field.cjs.map +1 -1
  24. package/dist/forms/field.d.ts +0 -7
  25. package/dist/forms/field.d.ts.map +1 -1
  26. package/dist/forms/field.js +1 -1
  27. package/dist/forms/field.js.map +1 -1
  28. package/dist/host-bind.cjs +1 -1
  29. package/dist/host-bind.cjs.map +1 -1
  30. package/dist/host-bind.d.ts +3 -10
  31. package/dist/host-bind.d.ts.map +1 -1
  32. package/dist/host-bind.js +1 -1
  33. package/dist/host-bind.js.map +1 -1
  34. package/dist/index.cjs +1 -1
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/ore.cjs +1 -1
  39. package/dist/ore.cjs.map +1 -1
  40. package/dist/ore.iife.js +1 -1
  41. package/dist/ore.iife.js.map +1 -1
  42. package/dist/ore.js +1 -1
  43. package/dist/ore.js.map +1 -1
  44. package/dist/runtime.cjs.map +1 -1
  45. package/dist/runtime.d.ts +1 -1
  46. package/dist/runtime.d.ts.map +1 -1
  47. package/dist/runtime.js.map +1 -1
  48. package/dist/template/bindings.cjs +1 -1
  49. package/dist/template/bindings.cjs.map +1 -1
  50. package/dist/template/bindings.js +1 -1
  51. package/dist/template/bindings.js.map +1 -1
  52. package/dist/testing/flush.cjs +1 -1
  53. package/dist/testing/flush.cjs.map +1 -1
  54. package/dist/testing/flush.d.ts +0 -18
  55. package/dist/testing/flush.d.ts.map +1 -1
  56. package/dist/testing/flush.js +1 -1
  57. package/dist/testing/flush.js.map +1 -1
  58. package/dist/testing/form-internals-polyfill.cjs +1 -1
  59. package/dist/testing/form-internals-polyfill.cjs.map +1 -1
  60. package/dist/testing/form-internals-polyfill.js +1 -1
  61. package/dist/testing/form-internals-polyfill.js.map +1 -1
  62. package/dist/testing/index.d.ts +3 -6
  63. package/dist/testing/index.d.ts.map +1 -1
  64. package/dist/testing/mount.cjs +1 -1
  65. package/dist/testing/mount.cjs.map +1 -1
  66. package/dist/testing/mount.d.ts +0 -20
  67. package/dist/testing/mount.d.ts.map +1 -1
  68. package/dist/testing/mount.js +1 -1
  69. package/dist/testing/mount.js.map +1 -1
  70. package/dist/testing/reset.cjs.map +1 -1
  71. package/dist/testing/reset.d.ts +1 -1
  72. package/dist/testing/reset.js.map +1 -1
  73. package/dist/testing.cjs +1 -1
  74. package/dist/testing.cjs.map +1 -1
  75. package/dist/testing.js +1 -1
  76. package/dist/testing.js.map +1 -1
  77. package/dist/utils/aria.cjs +1 -1
  78. package/dist/utils/aria.cjs.map +1 -1
  79. package/dist/utils/aria.d.ts +1 -13
  80. package/dist/utils/aria.d.ts.map +1 -1
  81. package/dist/utils/aria.js +1 -1
  82. package/dist/utils/aria.js.map +1 -1
  83. package/dist/utils/id.cjs +1 -1
  84. package/dist/utils/id.cjs.map +1 -1
  85. package/dist/utils/id.d.ts +2 -25
  86. package/dist/utils/id.d.ts.map +1 -1
  87. package/dist/utils/id.js +1 -1
  88. package/dist/utils/id.js.map +1 -1
  89. package/package.json +11 -11
package/README.md CHANGED
@@ -1,24 +1,6 @@
1
1
  # @vielzeug/ore
2
2
 
3
- > Functional custom-element authoring with typed props, reactive templates, lifecycle helpers, observers, and testing utilities.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@vielzeug/ore)](https://www.npmjs.com/package/@vielzeug/ore) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
-
7
- <details>
8
- <summary>Quick Reference</summary>
9
-
10
- **Package:** `@vielzeug/ore` &nbsp;·&nbsp; **Category:** UI Primitives
11
-
12
- **Key exports:** `define`, `html`, `css`, `prop`, `ref`, `onMounted`, `onCleanup`, `onEvent`, `onElement`, `watchEffect`, `bind`, `provide`, `inject`, `useEmit`, `useSlots`, `getHost`, `each`, `when`, `live`, `unsafeHtml`, `useField`
13
-
14
- **When to use:** Functional custom-element authoring with typed props, reactive templates, lifecycle helpers, observers, and testing utilities.
15
-
16
- **Related:** [@vielzeug/ripple](https://vielzeug.dev/ripple/) · [@vielzeug/refine](https://vielzeug.dev/refine/) · [@vielzeug/orbit](https://vielzeug.dev/orbit/)
17
-
18
- </details>
19
-
20
- `@vielzeug/ore` is part of Vielzeug and ships as a zero-dependency TypeScript package with ESM+CJS output. Install
21
- `@vielzeug/ripple` alongside it: Ore uses the application's Ripple runtime as a peer dependency.
3
+ > Functional web-component authoring on top of ripple
22
4
 
23
5
  ## Installation
24
6
 
@@ -63,46 +45,13 @@ define('my-counter', {
63
45
  });
64
46
  ```
65
47
 
66
- `setup()` takes only `props`. Everything else — lifecycle hooks (`onMounted`, `onCleanup`, `onEvent`, `onElement`, `watchEffect`), host bindings (`bind`), context (`inject`/`injectStrict`/`provide`), and per-instance factories (`useEmit<Emits>()`, `useSlots<SlotNames>()`) — is imported from `@vielzeug/ore` and resolved while `setup()` (or a composable it calls) is running. Setup state is connection-scoped: disconnect disposes it and reconnect runs setup again.
67
-
68
- ## Testing
69
-
70
- `@vielzeug/ore/testing` mounts real custom elements into jsdom/happy-dom and gives you `act`/query/dispose in one fixture — no separate render + flush + query steps to remember:
71
-
72
- ```ts
73
- import { mount } from '@vielzeug/ore/testing';
74
-
75
- const { element, query, act, dispose } = await mount('my-counter');
76
-
77
- expect(query('button')?.textContent).toContain('Count: 0');
78
-
79
- await act(() => query('button')?.click());
80
-
81
- expect(query('button')?.textContent).toContain('Count: 1');
82
-
83
- dispose(); // or: use the fixture as `using fixture = await mount(...)`
84
- ```
85
-
86
- Register `cleanup` once so mounted elements don't leak between tests:
87
-
88
- ```ts
89
- // vitest.setup.ts
90
- import { afterEach } from 'vitest';
91
- import { install } from '@vielzeug/ore/testing';
92
-
93
- install(afterEach); // add { formInternals: true } when testing form-associated (useField) components
94
- ```
95
-
96
- Also available from `@vielzeug/ore/testing`: `renderHook` (test a composable in isolation, without a full template),
97
- `flush`, and form-internals support. Import generic DOM interactions, queries, and waiting helpers from
98
- `@vielzeug/assay`. See the [Usage Guide](https://vielzeug.dev/ore/usage) for the full API.
99
-
100
48
  ## Documentation
101
49
 
102
50
  - [Overview](https://vielzeug.dev/ore/)
103
51
  - [Usage Guide](https://vielzeug.dev/ore/usage)
104
52
  - [API Reference](https://vielzeug.dev/ore/api)
105
53
  - [Examples](https://vielzeug.dev/ore/examples)
54
+ - [Migration Guide](https://vielzeug.dev/ore/migration)
106
55
 
107
56
  ## License
108
57
 
package/dist/context.cjs CHANGED
@@ -1,2 +1,2 @@
1
- require("./_dev.cjs");const e=require("./errors.cjs"),t=require("./runtime.cjs");var n=new WeakMap,r=e=>{let t=[],n=e;for(;n;)n instanceof HTMLElement&&t.push(n),n=n.parentNode??(n instanceof ShadowRoot?n.host:null);return t},i=(e,t,r)=>{let i=n.get(e)??new Map;i.has(t)&&`${e.localName}`,i.set(t,r),n.set(e,i)},a=(e,r)=>{let a=t.requireSetupContext(`provide`).element;i(a,e,r),t.onCleanup(()=>{let t=n.get(a);t&&(t.delete(e),t.size===0&&n.delete(a))})},o=Symbol(`inject.not_found`),s=new WeakMap,c=(e,t)=>{let i=r(e);for(let e of i){let r=n.get(e);if(r?.has(t))return r.get(t)}return o},l=(e,t)=>{let n=s.get(e);n||(n=new Map,s.set(e,n));let r=t;return n.has(r)||n.set(r,c(e.element,t)),n.get(r)};function u(e,...n){let r=l(t.requireSetupContext(`inject`),e);return r===o?n.length>0?n[0]:void 0:r}var d=n=>{let r=t.requireSetupContext(`injectStrict`),i=l(r,n);if(i!==o)return i;throw new e.OreApiError(e.ORE_ERRORS.injectStrictFailed(String(n),r.element.localName))},f=0;function p(e){return Symbol.for(`ore:context:${e??`anonymous-${++f}`}`)}exports.createContext=p,exports.inject=u,exports.injectStrict=d,exports.provide=a;
1
+ require("./_dev.cjs");const e=require("./errors.cjs"),t=require("./runtime.cjs");var n=new WeakMap,r=e=>{let t=[],n=e;for(;n;)n instanceof HTMLElement&&t.push(n),n=n.parentNode??(n instanceof ShadowRoot?n.host:null);return t},i=(e,t,r)=>{let i=n.get(e)??new Map;i.has(t)&&`${e.localName}`,i.set(t,r),n.set(e,i)},a=(e,r)=>{let a=t.requireSetupContext(`provide`).element;i(a,e,r),t.onCleanup(()=>{let t=n.get(a);t&&(t.delete(e),t.size===0&&n.delete(a))})},o=Symbol(`inject.not_found`),s=new WeakMap,c=(e,t)=>{let i=r(e);for(let e of i){let r=n.get(e);if(r?.has(t))return r.get(t)}return o},l=(e,t)=>{let n=s.get(e);n||(n=new Map,s.set(e,n));let r=t;return n.has(r)||n.set(r,c(e.element,t)),n.get(r)};function u(e,...n){let r=l(t.requireSetupContext(`inject`),e);return r===o?n.length>0?n[0]:void 0:r}var d=n=>{let r=t.requireSetupContext(`injectStrict`),i=l(r,n);if(i!==o)return i;throw new e.OreApiError(e.ORE_ERRORS.injectStrictFailed(String(n),r.element.localName))};function f(e){return Symbol(e)}exports.createContext=f,exports.inject=u,exports.injectStrict=d,exports.provide=a;
2
2
  //# sourceMappingURL=context.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.cjs","names":[],"sources":["../src/context.ts"],"sourcesContent":["/**\n * Component context injection API — `inject` / `injectStrict` / `provide` / `createContext`.\n *\n * Context values are stored on the providing element via a WeakMap registry and\n * resolved by walking up the DOM tree (including through shadow boundaries).\n * Providing is done via `provide(key, value)` inside `setup()`.\n *\n * Keys are `Symbol.for`-based (see `createContext`) so provide/inject still match\n * across a duplicated module graph — the same cross-copy survival rule as the\n * object brands in `utils/brand.ts`.\n */\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { onCleanup, type RuntimeContext, requireSetupContext } from './runtime';\n\nconst contextRegistry = new WeakMap<HTMLElement, Map<InjectionKey<unknown>, unknown>>();\n\nexport type InjectionKey<T> = symbol & {\n readonly __ore_injection_key?: T;\n};\n\n/**\n * Build a linear ancestor chain (including shadow host boundaries).\n * Walks `parentNode` (not `parentElement`) so non-HTML intermediate parents\n * (e.g. an `SVGElement` between child and provider) don't break the chain.\n */\nconst buildAncestorChain = (start: HTMLElement): HTMLElement[] => {\n const chain: HTMLElement[] = [];\n let node: Node | null = start;\n\n while (node) {\n if (node instanceof HTMLElement) chain.push(node);\n\n // A ShadowRoot's parentNode is null — hop to its host to keep walking.\n node = node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);\n }\n\n return chain;\n};\n\n/**\n * Register a context value on a specific element.\n * @internal Backs the public `provide()` — do not call directly.\n */\nconst provideOnElement = <T>(el: HTMLElement, key: InjectionKey<T>, value: T): void => {\n const map = contextRegistry.get(el) ?? new Map<InjectionKey<unknown>, unknown>();\n\n // `inject()` memoizes its result per consumer (see resolvedCache below), so a\n // provider swapping the raw value after a descendant already read it would be\n // silently ignored downstream. Provide a `Readable` (signal/computed) instead\n // of a raw value so descendants observe updates through the value itself.\n if (map.has(key)) {\n warn(\n `provide(): key already provided on <${el.localName}> — overwriting. Provide a Readable to update it instead.`,\n );\n }\n\n map.set(key, value);\n contextRegistry.set(el, map);\n};\n\n/**\n * Register a context value on the current component's host element, making it\n * available to descendant components via `inject(key)`.\n *\n * Provide a `Readable` (signal/computed) rather than a raw value if descendants\n * need to observe later changes — `inject()` resolves and caches the value once\n * per consumer, so re-calling `provide()` with a new raw value later is not seen.\n */\nexport const provide = <T>(key: InjectionKey<T>, value: T): void => {\n const el = requireSetupContext('provide').element;\n\n provideOnElement(el, key, value);\n\n onCleanup(() => {\n const map = contextRegistry.get(el);\n\n if (!map) return;\n\n map.delete(key);\n\n if (map.size === 0) contextRegistry.delete(el);\n });\n};\n\nconst NOT_FOUND_SENTINEL = Symbol('inject.not_found');\n\n/** Per-setup-context cache: avoids repeated ancestor walks for the same key. */\nconst resolvedCache = new WeakMap<object, Map<InjectionKey<unknown>, unknown>>();\n\nconst walkAndFind = <T>(element: HTMLElement, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n const chain = buildAncestorChain(element);\n\n for (const node of chain) {\n const map = contextRegistry.get(node);\n\n if (map?.has(key)) return map.get(key) as T;\n }\n\n return NOT_FOUND_SENTINEL;\n};\n\n/** Cached ancestor-walk lookup shared by `inject()` and `injectStrict()`. */\nconst lookup = <T>(ctx: RuntimeContext, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n let cache = resolvedCache.get(ctx);\n\n if (!cache) {\n cache = new Map();\n resolvedCache.set(ctx, cache);\n }\n\n const cacheKey = key as InjectionKey<unknown>;\n\n if (!cache.has(cacheKey)) cache.set(cacheKey, walkAndFind(ctx.element, key));\n\n return cache.get(cacheKey) as T | typeof NOT_FOUND_SENTINEL;\n};\n\nexport function inject<T>(key: InjectionKey<T>): T | undefined;\nexport function inject<T>(key: InjectionKey<T>, fallback: T): T;\nexport function inject<T>(key: InjectionKey<T>, ...rest: [T?]): T | undefined {\n const found = lookup(requireSetupContext('inject'), key);\n\n if (found === NOT_FOUND_SENTINEL) return rest.length > 0 ? rest[0] : undefined;\n\n return found;\n}\n\nexport const injectStrict = <T>(key: InjectionKey<T>): T => {\n const ctx = requireSetupContext('injectStrict');\n const found = lookup(ctx, key);\n\n if (found !== NOT_FOUND_SENTINEL) return found;\n\n throw new OreApiError(ORE_ERRORS.injectStrictFailed(String(key), ctx.element.localName));\n};\n\nlet anonymousKeyCounter = 0;\n\n/**\n * Create a typed context key. `Symbol.for`-keyed (`ore:context:<description>`) so\n * a provider and an injector loaded from two bundled copies of ore still match\n * (see module header). Two `createContext('theme')` calls intentionally produce\n * the same key — use distinct descriptions for distinct contexts.\n *\n * Always pass a description: anonymous keys are minted from a per-graph counter,\n * so they do NOT survive duplicated module graphs (each copy numbers its own).\n */\nexport function createContext<T>(description?: string): InjectionKey<T> {\n if (description === undefined) {\n warn(\n 'createContext() called without a description — anonymous context keys do not survive duplicated module graphs.',\n );\n }\n\n return Symbol.for(`ore:context:${description ?? `anonymous-${++anonymousKeyCounter}`}`) as InjectionKey<T>;\n}\n"],"mappings":"iFAgBA,IAAM,EAAkB,IAAI,QAWtB,EAAsB,GAAsC,CAChE,IAAM,EAAuB,CAAC,EAC1B,EAAoB,EAExB,KAAO,GACD,aAAgB,aAAa,EAAM,KAAK,CAAI,EAGhD,EAAO,EAAK,aAAe,aAAgB,WAAa,EAAK,KAAO,MAGtE,OAAO,CACT,EAMM,GAAuB,EAAiB,EAAsB,IAAmB,CACrF,IAAM,EAAM,EAAgB,IAAI,CAAE,GAAK,IAAI,IAMvC,EAAI,IAAI,CAAG,GAEX,GAAuC,EAAG,UAA1C,EAIJ,EAAI,IAAI,EAAK,CAAK,EAClB,EAAgB,IAAI,EAAI,CAAG,CAC7B,EAUa,GAAc,EAAsB,IAAmB,CAClE,IAAM,EAAK,EAAA,oBAAoB,SAAS,CAAC,CAAC,QAE1C,EAAiB,EAAI,EAAK,CAAK,EAE/B,EAAA,cAAgB,CACd,IAAM,EAAM,EAAgB,IAAI,CAAE,EAE7B,IAEL,EAAI,OAAO,CAAG,EAEV,EAAI,OAAS,GAAG,EAAgB,OAAO,CAAE,EAC/C,CAAC,CACH,EAEM,EAAqB,OAAO,kBAAkB,EAG9C,EAAgB,IAAI,QAEpB,GAAkB,EAAsB,IAAwD,CACpG,IAAM,EAAQ,EAAmB,CAAO,EAExC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAM,EAAgB,IAAI,CAAI,EAEpC,GAAI,GAAK,IAAI,CAAG,EAAG,OAAO,EAAI,IAAI,CAAG,CACvC,CAEA,OAAO,CACT,EAGM,GAAa,EAAqB,IAAwD,CAC9F,IAAI,EAAQ,EAAc,IAAI,CAAG,EAE5B,IACH,EAAQ,IAAI,IACZ,EAAc,IAAI,EAAK,CAAK,GAG9B,IAAM,EAAW,EAIjB,OAFK,EAAM,IAAI,CAAQ,GAAG,EAAM,IAAI,EAAU,EAAY,EAAI,QAAS,CAAG,CAAC,EAEpE,EAAM,IAAI,CAAQ,CAC3B,EAIA,SAAgB,EAAU,EAAsB,GAAG,EAA2B,CAC5E,IAAM,EAAQ,EAAO,EAAA,oBAAoB,QAAQ,EAAG,CAAG,EAIvD,OAFI,IAAU,EAA2B,EAAK,OAAS,EAAI,EAAK,GAAK,IAAA,GAE9D,CACT,CAEA,IAAa,EAAmB,GAA4B,CAC1D,IAAM,EAAM,EAAA,oBAAoB,cAAc,EACxC,EAAQ,EAAO,EAAK,CAAG,EAE7B,GAAI,IAAU,EAAoB,OAAO,EAEzC,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,mBAAmB,OAAO,CAAG,EAAG,EAAI,QAAQ,SAAS,CAAC,CACzF,EAEI,EAAsB,EAW1B,SAAgB,EAAiB,EAAuC,CAOtE,OAAO,OAAO,IAAI,eAAe,GAAe,aAAa,EAAE,KAAuB,CACxF"}
1
+ {"version":3,"file":"context.cjs","names":[],"sources":["../src/context.ts"],"sourcesContent":["/**\n * Component context injection API — `inject` / `injectStrict` / `provide` / `createContext`.\n *\n * Context values are stored on the providing element via a WeakMap registry and\n * resolved by walking up the DOM tree (including through shadow boundaries).\n * Providing is done via `provide(key, value)` inside `setup()`.\n *\n * Each `createContext()` call creates a unique key. Descriptions are diagnostic\n * labels only and never determine identity.\n */\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { onCleanup, type RuntimeContext, requireSetupContext } from './runtime';\n\nconst contextRegistry = new WeakMap<HTMLElement, Map<InjectionKey<unknown>, unknown>>();\n\nexport type InjectionKey<T> = symbol & {\n readonly __ore_injection_key?: T;\n};\n\n/**\n * Build a linear ancestor chain (including shadow host boundaries).\n * Walks `parentNode` (not `parentElement`) so non-HTML intermediate parents\n * (e.g. an `SVGElement` between child and provider) don't break the chain.\n */\nconst buildAncestorChain = (start: HTMLElement): HTMLElement[] => {\n const chain: HTMLElement[] = [];\n let node: Node | null = start;\n\n while (node) {\n if (node instanceof HTMLElement) chain.push(node);\n\n // A ShadowRoot's parentNode is null — hop to its host to keep walking.\n node = node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);\n }\n\n return chain;\n};\n\n/**\n * Register a context value on a specific element.\n * @internal Backs the public `provide()` — do not call directly.\n */\nconst provideOnElement = <T>(el: HTMLElement, key: InjectionKey<T>, value: T): void => {\n const map = contextRegistry.get(el) ?? new Map<InjectionKey<unknown>, unknown>();\n\n // `inject()` memoizes its result per consumer (see resolvedCache below), so a\n // provider swapping the raw value after a descendant already read it would be\n // silently ignored downstream. Provide a `Readable` (signal/computed) instead\n // of a raw value so descendants observe updates through the value itself.\n if (map.has(key)) {\n warn(\n `provide(): key already provided on <${el.localName}> — overwriting. Provide a Readable to update it instead.`,\n );\n }\n\n map.set(key, value);\n contextRegistry.set(el, map);\n};\n\n/**\n * Register a context value on the current component's host element, making it\n * available to descendant components via `inject(key)`.\n *\n * Provide a `Readable` (signal/computed) rather than a raw value if descendants\n * need to observe later changes — `inject()` resolves and caches the value once\n * per consumer, so re-calling `provide()` with a new raw value later is not seen.\n */\nexport const provide = <T>(key: InjectionKey<T>, value: T): void => {\n const el = requireSetupContext('provide').element;\n\n provideOnElement(el, key, value);\n\n onCleanup(() => {\n const map = contextRegistry.get(el);\n\n if (!map) return;\n\n map.delete(key);\n\n if (map.size === 0) contextRegistry.delete(el);\n });\n};\n\nconst NOT_FOUND_SENTINEL = Symbol('inject.not_found');\n\n/** Per-setup-context cache: avoids repeated ancestor walks for the same key. */\nconst resolvedCache = new WeakMap<object, Map<InjectionKey<unknown>, unknown>>();\n\nconst walkAndFind = <T>(element: HTMLElement, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n const chain = buildAncestorChain(element);\n\n for (const node of chain) {\n const map = contextRegistry.get(node);\n\n if (map?.has(key)) return map.get(key) as T;\n }\n\n return NOT_FOUND_SENTINEL;\n};\n\n/** Cached ancestor-walk lookup shared by `inject()` and `injectStrict()`. */\nconst lookup = <T>(ctx: RuntimeContext, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n let cache = resolvedCache.get(ctx);\n\n if (!cache) {\n cache = new Map();\n resolvedCache.set(ctx, cache);\n }\n\n const cacheKey = key as InjectionKey<unknown>;\n\n if (!cache.has(cacheKey)) cache.set(cacheKey, walkAndFind(ctx.element, key));\n\n return cache.get(cacheKey) as T | typeof NOT_FOUND_SENTINEL;\n};\n\nexport function inject<T>(key: InjectionKey<T>): T | undefined;\nexport function inject<T>(key: InjectionKey<T>, fallback: T): T;\nexport function inject<T>(key: InjectionKey<T>, ...rest: [T?]): T | undefined {\n const found = lookup(requireSetupContext('inject'), key);\n\n if (found === NOT_FOUND_SENTINEL) return rest.length > 0 ? rest[0] : undefined;\n\n return found;\n}\n\nexport const injectStrict = <T>(key: InjectionKey<T>): T => {\n const ctx = requireSetupContext('injectStrict');\n const found = lookup(ctx, key);\n\n if (found !== NOT_FOUND_SENTINEL) return found;\n\n throw new OreApiError(ORE_ERRORS.injectStrictFailed(String(key), ctx.element.localName));\n};\n\n/** Create a unique typed context key with a diagnostic description. */\nexport function createContext<T>(description: string): InjectionKey<T> {\n return Symbol(description) as InjectionKey<T>;\n}\n"],"mappings":"iFAeA,IAAM,EAAkB,IAAI,QAWtB,EAAsB,GAAsC,CAChE,IAAM,EAAuB,CAAC,EAC1B,EAAoB,EAExB,KAAO,GACD,aAAgB,aAAa,EAAM,KAAK,CAAI,EAGhD,EAAO,EAAK,aAAe,aAAgB,WAAa,EAAK,KAAO,MAGtE,OAAO,CACT,EAMM,GAAuB,EAAiB,EAAsB,IAAmB,CACrF,IAAM,EAAM,EAAgB,IAAI,CAAE,GAAK,IAAI,IAMvC,EAAI,IAAI,CAAG,GAEX,GAAuC,EAAG,UAA1C,EAIJ,EAAI,IAAI,EAAK,CAAK,EAClB,EAAgB,IAAI,EAAI,CAAG,CAC7B,EAUa,GAAc,EAAsB,IAAmB,CAClE,IAAM,EAAK,EAAA,oBAAoB,SAAS,CAAC,CAAC,QAE1C,EAAiB,EAAI,EAAK,CAAK,EAE/B,EAAA,cAAgB,CACd,IAAM,EAAM,EAAgB,IAAI,CAAE,EAE7B,IAEL,EAAI,OAAO,CAAG,EAEV,EAAI,OAAS,GAAG,EAAgB,OAAO,CAAE,EAC/C,CAAC,CACH,EAEM,EAAqB,OAAO,kBAAkB,EAG9C,EAAgB,IAAI,QAEpB,GAAkB,EAAsB,IAAwD,CACpG,IAAM,EAAQ,EAAmB,CAAO,EAExC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAM,EAAgB,IAAI,CAAI,EAEpC,GAAI,GAAK,IAAI,CAAG,EAAG,OAAO,EAAI,IAAI,CAAG,CACvC,CAEA,OAAO,CACT,EAGM,GAAa,EAAqB,IAAwD,CAC9F,IAAI,EAAQ,EAAc,IAAI,CAAG,EAE5B,IACH,EAAQ,IAAI,IACZ,EAAc,IAAI,EAAK,CAAK,GAG9B,IAAM,EAAW,EAIjB,OAFK,EAAM,IAAI,CAAQ,GAAG,EAAM,IAAI,EAAU,EAAY,EAAI,QAAS,CAAG,CAAC,EAEpE,EAAM,IAAI,CAAQ,CAC3B,EAIA,SAAgB,EAAU,EAAsB,GAAG,EAA2B,CAC5E,IAAM,EAAQ,EAAO,EAAA,oBAAoB,QAAQ,EAAG,CAAG,EAIvD,OAFI,IAAU,EAA2B,EAAK,OAAS,EAAI,EAAK,GAAK,IAAA,GAE9D,CACT,CAEA,IAAa,EAAmB,GAA4B,CAC1D,IAAM,EAAM,EAAA,oBAAoB,cAAc,EACxC,EAAQ,EAAO,EAAK,CAAG,EAE7B,GAAI,IAAU,EAAoB,OAAO,EAEzC,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,mBAAmB,OAAO,CAAG,EAAG,EAAI,QAAQ,SAAS,CAAC,CACzF,EAGA,SAAgB,EAAiB,EAAsC,CACrE,OAAO,OAAO,CAAW,CAC3B"}
package/dist/context.d.ts CHANGED
@@ -5,9 +5,8 @@
5
5
  * resolved by walking up the DOM tree (including through shadow boundaries).
6
6
  * Providing is done via `provide(key, value)` inside `setup()`.
7
7
  *
8
- * Keys are `Symbol.for`-based (see `createContext`) so provide/inject still match
9
- * across a duplicated module graph — the same cross-copy survival rule as the
10
- * object brands in `utils/brand.ts`.
8
+ * Each `createContext()` call creates a unique key. Descriptions are diagnostic
9
+ * labels only and never determine identity.
11
10
  */
12
11
  export type InjectionKey<T> = symbol & {
13
12
  readonly __ore_injection_key?: T;
@@ -24,14 +23,6 @@ export declare const provide: <T>(key: InjectionKey<T>, value: T) => void;
24
23
  export declare function inject<T>(key: InjectionKey<T>): T | undefined;
25
24
  export declare function inject<T>(key: InjectionKey<T>, fallback: T): T;
26
25
  export declare const injectStrict: <T>(key: InjectionKey<T>) => T;
27
- /**
28
- * Create a typed context key. `Symbol.for`-keyed (`ore:context:<description>`) so
29
- * a provider and an injector loaded from two bundled copies of ore still match
30
- * (see module header). Two `createContext('theme')` calls intentionally produce
31
- * the same key — use distinct descriptions for distinct contexts.
32
- *
33
- * Always pass a description: anonymous keys are minted from a per-graph counter,
34
- * so they do NOT survive duplicated module graphs (each copy numbers its own).
35
- */
36
- export declare function createContext<T>(description?: string): InjectionKey<T>;
26
+ /** Create a unique typed context key with a diagnostic description. */
27
+ export declare function createContext<T>(description: string): InjectionKey<T>;
37
28
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,MAAM,GAAG;IACrC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC;CAClC,CAAC;AA0CF;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,KAAG,IAc3D,CAAC;AAmCF,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;AAC/D,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;AAShE,eAAO,MAAM,YAAY,GAAI,CAAC,EAAE,KAAK,YAAY,CAAC,CAAC,CAAC,KAAG,CAOtD,CAAC;AAIF;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,MAAM,GAAG;IACrC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC;CAClC,CAAC;AA0CF;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,KAAG,IAc3D,CAAC;AAmCF,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;AAC/D,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;AAShE,eAAO,MAAM,YAAY,GAAI,CAAC,EAAE,KAAK,YAAY,CAAC,CAAC,CAAC,KAAG,CAOtD,CAAC;AAEF,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAErE"}
package/dist/context.js CHANGED
@@ -1,2 +1,2 @@
1
- import"./_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{onCleanup as n,requireSetupContext as r}from"./runtime.js";var i=new WeakMap,a=e=>{let t=[],n=e;for(;n;)n instanceof HTMLElement&&t.push(n),n=n.parentNode??(n instanceof ShadowRoot?n.host:null);return t},o=(e,t,n)=>{let r=i.get(e)??new Map;r.has(t)&&`${e.localName}`,r.set(t,n),i.set(e,r)},s=(e,t)=>{let a=r(`provide`).element;o(a,e,t),n(()=>{let t=i.get(a);t&&(t.delete(e),t.size===0&&i.delete(a))})},c=Symbol(`inject.not_found`),l=new WeakMap,u=(e,t)=>{let n=a(e);for(let e of n){let n=i.get(e);if(n?.has(t))return n.get(t)}return c},d=(e,t)=>{let n=l.get(e);n||(n=new Map,l.set(e,n));let r=t;return n.has(r)||n.set(r,u(e.element,t)),n.get(r)};function f(e,...t){let n=d(r(`inject`),e);return n===c?t.length>0?t[0]:void 0:n}var p=n=>{let i=r(`injectStrict`),a=d(i,n);if(a!==c)return a;throw new t(e.injectStrictFailed(String(n),i.element.localName))},m=0;function h(e){return Symbol.for(`ore:context:${e??`anonymous-${++m}`}`)}export{h as createContext,f as inject,p as injectStrict,s as provide};
1
+ import"./_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{onCleanup as n,requireSetupContext as r}from"./runtime.js";var i=new WeakMap,a=e=>{let t=[],n=e;for(;n;)n instanceof HTMLElement&&t.push(n),n=n.parentNode??(n instanceof ShadowRoot?n.host:null);return t},o=(e,t,n)=>{let r=i.get(e)??new Map;r.has(t)&&`${e.localName}`,r.set(t,n),i.set(e,r)},s=(e,t)=>{let a=r(`provide`).element;o(a,e,t),n(()=>{let t=i.get(a);t&&(t.delete(e),t.size===0&&i.delete(a))})},c=Symbol(`inject.not_found`),l=new WeakMap,u=(e,t)=>{let n=a(e);for(let e of n){let n=i.get(e);if(n?.has(t))return n.get(t)}return c},d=(e,t)=>{let n=l.get(e);n||(n=new Map,l.set(e,n));let r=t;return n.has(r)||n.set(r,u(e.element,t)),n.get(r)};function f(e,...t){let n=d(r(`inject`),e);return n===c?t.length>0?t[0]:void 0:n}var p=n=>{let i=r(`injectStrict`),a=d(i,n);if(a!==c)return a;throw new t(e.injectStrictFailed(String(n),i.element.localName))};function m(e){return Symbol(e)}export{m as createContext,f as inject,p as injectStrict,s as provide};
2
2
  //# sourceMappingURL=context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["/**\n * Component context injection API — `inject` / `injectStrict` / `provide` / `createContext`.\n *\n * Context values are stored on the providing element via a WeakMap registry and\n * resolved by walking up the DOM tree (including through shadow boundaries).\n * Providing is done via `provide(key, value)` inside `setup()`.\n *\n * Keys are `Symbol.for`-based (see `createContext`) so provide/inject still match\n * across a duplicated module graph — the same cross-copy survival rule as the\n * object brands in `utils/brand.ts`.\n */\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { onCleanup, type RuntimeContext, requireSetupContext } from './runtime';\n\nconst contextRegistry = new WeakMap<HTMLElement, Map<InjectionKey<unknown>, unknown>>();\n\nexport type InjectionKey<T> = symbol & {\n readonly __ore_injection_key?: T;\n};\n\n/**\n * Build a linear ancestor chain (including shadow host boundaries).\n * Walks `parentNode` (not `parentElement`) so non-HTML intermediate parents\n * (e.g. an `SVGElement` between child and provider) don't break the chain.\n */\nconst buildAncestorChain = (start: HTMLElement): HTMLElement[] => {\n const chain: HTMLElement[] = [];\n let node: Node | null = start;\n\n while (node) {\n if (node instanceof HTMLElement) chain.push(node);\n\n // A ShadowRoot's parentNode is null — hop to its host to keep walking.\n node = node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);\n }\n\n return chain;\n};\n\n/**\n * Register a context value on a specific element.\n * @internal Backs the public `provide()` — do not call directly.\n */\nconst provideOnElement = <T>(el: HTMLElement, key: InjectionKey<T>, value: T): void => {\n const map = contextRegistry.get(el) ?? new Map<InjectionKey<unknown>, unknown>();\n\n // `inject()` memoizes its result per consumer (see resolvedCache below), so a\n // provider swapping the raw value after a descendant already read it would be\n // silently ignored downstream. Provide a `Readable` (signal/computed) instead\n // of a raw value so descendants observe updates through the value itself.\n if (map.has(key)) {\n warn(\n `provide(): key already provided on <${el.localName}> — overwriting. Provide a Readable to update it instead.`,\n );\n }\n\n map.set(key, value);\n contextRegistry.set(el, map);\n};\n\n/**\n * Register a context value on the current component's host element, making it\n * available to descendant components via `inject(key)`.\n *\n * Provide a `Readable` (signal/computed) rather than a raw value if descendants\n * need to observe later changes — `inject()` resolves and caches the value once\n * per consumer, so re-calling `provide()` with a new raw value later is not seen.\n */\nexport const provide = <T>(key: InjectionKey<T>, value: T): void => {\n const el = requireSetupContext('provide').element;\n\n provideOnElement(el, key, value);\n\n onCleanup(() => {\n const map = contextRegistry.get(el);\n\n if (!map) return;\n\n map.delete(key);\n\n if (map.size === 0) contextRegistry.delete(el);\n });\n};\n\nconst NOT_FOUND_SENTINEL = Symbol('inject.not_found');\n\n/** Per-setup-context cache: avoids repeated ancestor walks for the same key. */\nconst resolvedCache = new WeakMap<object, Map<InjectionKey<unknown>, unknown>>();\n\nconst walkAndFind = <T>(element: HTMLElement, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n const chain = buildAncestorChain(element);\n\n for (const node of chain) {\n const map = contextRegistry.get(node);\n\n if (map?.has(key)) return map.get(key) as T;\n }\n\n return NOT_FOUND_SENTINEL;\n};\n\n/** Cached ancestor-walk lookup shared by `inject()` and `injectStrict()`. */\nconst lookup = <T>(ctx: RuntimeContext, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n let cache = resolvedCache.get(ctx);\n\n if (!cache) {\n cache = new Map();\n resolvedCache.set(ctx, cache);\n }\n\n const cacheKey = key as InjectionKey<unknown>;\n\n if (!cache.has(cacheKey)) cache.set(cacheKey, walkAndFind(ctx.element, key));\n\n return cache.get(cacheKey) as T | typeof NOT_FOUND_SENTINEL;\n};\n\nexport function inject<T>(key: InjectionKey<T>): T | undefined;\nexport function inject<T>(key: InjectionKey<T>, fallback: T): T;\nexport function inject<T>(key: InjectionKey<T>, ...rest: [T?]): T | undefined {\n const found = lookup(requireSetupContext('inject'), key);\n\n if (found === NOT_FOUND_SENTINEL) return rest.length > 0 ? rest[0] : undefined;\n\n return found;\n}\n\nexport const injectStrict = <T>(key: InjectionKey<T>): T => {\n const ctx = requireSetupContext('injectStrict');\n const found = lookup(ctx, key);\n\n if (found !== NOT_FOUND_SENTINEL) return found;\n\n throw new OreApiError(ORE_ERRORS.injectStrictFailed(String(key), ctx.element.localName));\n};\n\nlet anonymousKeyCounter = 0;\n\n/**\n * Create a typed context key. `Symbol.for`-keyed (`ore:context:<description>`) so\n * a provider and an injector loaded from two bundled copies of ore still match\n * (see module header). Two `createContext('theme')` calls intentionally produce\n * the same key — use distinct descriptions for distinct contexts.\n *\n * Always pass a description: anonymous keys are minted from a per-graph counter,\n * so they do NOT survive duplicated module graphs (each copy numbers its own).\n */\nexport function createContext<T>(description?: string): InjectionKey<T> {\n if (description === undefined) {\n warn(\n 'createContext() called without a description — anonymous context keys do not survive duplicated module graphs.',\n );\n }\n\n return Symbol.for(`ore:context:${description ?? `anonymous-${++anonymousKeyCounter}`}`) as InjectionKey<T>;\n}\n"],"mappings":"8IAgBA,IAAM,EAAkB,IAAI,QAWtB,EAAsB,GAAsC,CAChE,IAAM,EAAuB,CAAC,EAC1B,EAAoB,EAExB,KAAO,GACD,aAAgB,aAAa,EAAM,KAAK,CAAI,EAGhD,EAAO,EAAK,aAAe,aAAgB,WAAa,EAAK,KAAO,MAGtE,OAAO,CACT,EAMM,GAAuB,EAAiB,EAAsB,IAAmB,CACrF,IAAM,EAAM,EAAgB,IAAI,CAAE,GAAK,IAAI,IAMvC,EAAI,IAAI,CAAG,GAEX,GAAuC,EAAG,UAA1C,EAIJ,EAAI,IAAI,EAAK,CAAK,EAClB,EAAgB,IAAI,EAAI,CAAG,CAC7B,EAUa,GAAc,EAAsB,IAAmB,CAClE,IAAM,EAAK,EAAoB,SAAS,CAAC,CAAC,QAE1C,EAAiB,EAAI,EAAK,CAAK,EAE/B,MAAgB,CACd,IAAM,EAAM,EAAgB,IAAI,CAAE,EAE7B,IAEL,EAAI,OAAO,CAAG,EAEV,EAAI,OAAS,GAAG,EAAgB,OAAO,CAAE,EAC/C,CAAC,CACH,EAEM,EAAqB,OAAO,kBAAkB,EAG9C,EAAgB,IAAI,QAEpB,GAAkB,EAAsB,IAAwD,CACpG,IAAM,EAAQ,EAAmB,CAAO,EAExC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAM,EAAgB,IAAI,CAAI,EAEpC,GAAI,GAAK,IAAI,CAAG,EAAG,OAAO,EAAI,IAAI,CAAG,CACvC,CAEA,OAAO,CACT,EAGM,GAAa,EAAqB,IAAwD,CAC9F,IAAI,EAAQ,EAAc,IAAI,CAAG,EAE5B,IACH,EAAQ,IAAI,IACZ,EAAc,IAAI,EAAK,CAAK,GAG9B,IAAM,EAAW,EAIjB,OAFK,EAAM,IAAI,CAAQ,GAAG,EAAM,IAAI,EAAU,EAAY,EAAI,QAAS,CAAG,CAAC,EAEpE,EAAM,IAAI,CAAQ,CAC3B,EAIA,SAAgB,EAAU,EAAsB,GAAG,EAA2B,CAC5E,IAAM,EAAQ,EAAO,EAAoB,QAAQ,EAAG,CAAG,EAIvD,OAFI,IAAU,EAA2B,EAAK,OAAS,EAAI,EAAK,GAAK,IAAA,GAE9D,CACT,CAEA,IAAa,EAAmB,GAA4B,CAC1D,IAAM,EAAM,EAAoB,cAAc,EACxC,EAAQ,EAAO,EAAK,CAAG,EAE7B,GAAI,IAAU,EAAoB,OAAO,EAEzC,MAAM,IAAI,EAAY,EAAW,mBAAmB,OAAO,CAAG,EAAG,EAAI,QAAQ,SAAS,CAAC,CACzF,EAEI,EAAsB,EAW1B,SAAgB,EAAiB,EAAuC,CAOtE,OAAO,OAAO,IAAI,eAAe,GAAe,aAAa,EAAE,KAAuB,CACxF"}
1
+ {"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["/**\n * Component context injection API — `inject` / `injectStrict` / `provide` / `createContext`.\n *\n * Context values are stored on the providing element via a WeakMap registry and\n * resolved by walking up the DOM tree (including through shadow boundaries).\n * Providing is done via `provide(key, value)` inside `setup()`.\n *\n * Each `createContext()` call creates a unique key. Descriptions are diagnostic\n * labels only and never determine identity.\n */\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { onCleanup, type RuntimeContext, requireSetupContext } from './runtime';\n\nconst contextRegistry = new WeakMap<HTMLElement, Map<InjectionKey<unknown>, unknown>>();\n\nexport type InjectionKey<T> = symbol & {\n readonly __ore_injection_key?: T;\n};\n\n/**\n * Build a linear ancestor chain (including shadow host boundaries).\n * Walks `parentNode` (not `parentElement`) so non-HTML intermediate parents\n * (e.g. an `SVGElement` between child and provider) don't break the chain.\n */\nconst buildAncestorChain = (start: HTMLElement): HTMLElement[] => {\n const chain: HTMLElement[] = [];\n let node: Node | null = start;\n\n while (node) {\n if (node instanceof HTMLElement) chain.push(node);\n\n // A ShadowRoot's parentNode is null — hop to its host to keep walking.\n node = node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);\n }\n\n return chain;\n};\n\n/**\n * Register a context value on a specific element.\n * @internal Backs the public `provide()` — do not call directly.\n */\nconst provideOnElement = <T>(el: HTMLElement, key: InjectionKey<T>, value: T): void => {\n const map = contextRegistry.get(el) ?? new Map<InjectionKey<unknown>, unknown>();\n\n // `inject()` memoizes its result per consumer (see resolvedCache below), so a\n // provider swapping the raw value after a descendant already read it would be\n // silently ignored downstream. Provide a `Readable` (signal/computed) instead\n // of a raw value so descendants observe updates through the value itself.\n if (map.has(key)) {\n warn(\n `provide(): key already provided on <${el.localName}> — overwriting. Provide a Readable to update it instead.`,\n );\n }\n\n map.set(key, value);\n contextRegistry.set(el, map);\n};\n\n/**\n * Register a context value on the current component's host element, making it\n * available to descendant components via `inject(key)`.\n *\n * Provide a `Readable` (signal/computed) rather than a raw value if descendants\n * need to observe later changes — `inject()` resolves and caches the value once\n * per consumer, so re-calling `provide()` with a new raw value later is not seen.\n */\nexport const provide = <T>(key: InjectionKey<T>, value: T): void => {\n const el = requireSetupContext('provide').element;\n\n provideOnElement(el, key, value);\n\n onCleanup(() => {\n const map = contextRegistry.get(el);\n\n if (!map) return;\n\n map.delete(key);\n\n if (map.size === 0) contextRegistry.delete(el);\n });\n};\n\nconst NOT_FOUND_SENTINEL = Symbol('inject.not_found');\n\n/** Per-setup-context cache: avoids repeated ancestor walks for the same key. */\nconst resolvedCache = new WeakMap<object, Map<InjectionKey<unknown>, unknown>>();\n\nconst walkAndFind = <T>(element: HTMLElement, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n const chain = buildAncestorChain(element);\n\n for (const node of chain) {\n const map = contextRegistry.get(node);\n\n if (map?.has(key)) return map.get(key) as T;\n }\n\n return NOT_FOUND_SENTINEL;\n};\n\n/** Cached ancestor-walk lookup shared by `inject()` and `injectStrict()`. */\nconst lookup = <T>(ctx: RuntimeContext, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n let cache = resolvedCache.get(ctx);\n\n if (!cache) {\n cache = new Map();\n resolvedCache.set(ctx, cache);\n }\n\n const cacheKey = key as InjectionKey<unknown>;\n\n if (!cache.has(cacheKey)) cache.set(cacheKey, walkAndFind(ctx.element, key));\n\n return cache.get(cacheKey) as T | typeof NOT_FOUND_SENTINEL;\n};\n\nexport function inject<T>(key: InjectionKey<T>): T | undefined;\nexport function inject<T>(key: InjectionKey<T>, fallback: T): T;\nexport function inject<T>(key: InjectionKey<T>, ...rest: [T?]): T | undefined {\n const found = lookup(requireSetupContext('inject'), key);\n\n if (found === NOT_FOUND_SENTINEL) return rest.length > 0 ? rest[0] : undefined;\n\n return found;\n}\n\nexport const injectStrict = <T>(key: InjectionKey<T>): T => {\n const ctx = requireSetupContext('injectStrict');\n const found = lookup(ctx, key);\n\n if (found !== NOT_FOUND_SENTINEL) return found;\n\n throw new OreApiError(ORE_ERRORS.injectStrictFailed(String(key), ctx.element.localName));\n};\n\n/** Create a unique typed context key with a diagnostic description. */\nexport function createContext<T>(description: string): InjectionKey<T> {\n return Symbol(description) as InjectionKey<T>;\n}\n"],"mappings":"8IAeA,IAAM,EAAkB,IAAI,QAWtB,EAAsB,GAAsC,CAChE,IAAM,EAAuB,CAAC,EAC1B,EAAoB,EAExB,KAAO,GACD,aAAgB,aAAa,EAAM,KAAK,CAAI,EAGhD,EAAO,EAAK,aAAe,aAAgB,WAAa,EAAK,KAAO,MAGtE,OAAO,CACT,EAMM,GAAuB,EAAiB,EAAsB,IAAmB,CACrF,IAAM,EAAM,EAAgB,IAAI,CAAE,GAAK,IAAI,IAMvC,EAAI,IAAI,CAAG,GAEX,GAAuC,EAAG,UAA1C,EAIJ,EAAI,IAAI,EAAK,CAAK,EAClB,EAAgB,IAAI,EAAI,CAAG,CAC7B,EAUa,GAAc,EAAsB,IAAmB,CAClE,IAAM,EAAK,EAAoB,SAAS,CAAC,CAAC,QAE1C,EAAiB,EAAI,EAAK,CAAK,EAE/B,MAAgB,CACd,IAAM,EAAM,EAAgB,IAAI,CAAE,EAE7B,IAEL,EAAI,OAAO,CAAG,EAEV,EAAI,OAAS,GAAG,EAAgB,OAAO,CAAE,EAC/C,CAAC,CACH,EAEM,EAAqB,OAAO,kBAAkB,EAG9C,EAAgB,IAAI,QAEpB,GAAkB,EAAsB,IAAwD,CACpG,IAAM,EAAQ,EAAmB,CAAO,EAExC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAM,EAAgB,IAAI,CAAI,EAEpC,GAAI,GAAK,IAAI,CAAG,EAAG,OAAO,EAAI,IAAI,CAAG,CACvC,CAEA,OAAO,CACT,EAGM,GAAa,EAAqB,IAAwD,CAC9F,IAAI,EAAQ,EAAc,IAAI,CAAG,EAE5B,IACH,EAAQ,IAAI,IACZ,EAAc,IAAI,EAAK,CAAK,GAG9B,IAAM,EAAW,EAIjB,OAFK,EAAM,IAAI,CAAQ,GAAG,EAAM,IAAI,EAAU,EAAY,EAAI,QAAS,CAAG,CAAC,EAEpE,EAAM,IAAI,CAAQ,CAC3B,EAIA,SAAgB,EAAU,EAAsB,GAAG,EAA2B,CAC5E,IAAM,EAAQ,EAAO,EAAoB,QAAQ,EAAG,CAAG,EAIvD,OAFI,IAAU,EAA2B,EAAK,OAAS,EAAI,EAAK,GAAK,IAAA,GAE9D,CACT,CAEA,IAAa,EAAmB,GAA4B,CAC1D,IAAM,EAAM,EAAoB,cAAc,EACxC,EAAQ,EAAO,EAAK,CAAG,EAE7B,GAAI,IAAU,EAAoB,OAAO,EAEzC,MAAM,IAAI,EAAY,EAAW,mBAAmB,OAAO,CAAG,EAAG,EAAI,QAAQ,SAAS,CAAC,CACzF,EAGA,SAAgB,EAAiB,EAAsC,CACrE,OAAO,OAAO,CAAW,CAC3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"define.cjs","names":[],"sources":["../src/define.ts"],"sourcesContent":["import { createComponentClass } from './_component-class';\nimport type { ComponentDefinition } from './component-types';\nimport { ORE_ERRORS, OreApiError } from './errors';\n\n/**\n * Define and register a web component.\n *\n * The `setup` function runs for each connection and returns an `HTMLResult`.\n * Disconnecting disposes its state; reconnecting rebuilds it. All reactive\n * behaviour within a connection is expressed through directives inside the\n * template — not by re-evaluating setup itself.\n *\n * Everything besides `props` — lifecycle hooks, host bindings, context, slots,\n * emit — is a free function imported from `@vielzeug/ore` and called directly\n * from inside `setup()` (or from a composable it calls):\n *\n * ```ts\n * import { define, html, prop, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n *\n * define<{ count?: number }>('my-counter', {\n * props: { count: prop.number(0) },\n * setup(props) {\n * const emit = useEmit<{ increment: number }>();\n * const slots = useSlots<'header' | 'footer'>();\n *\n * onMounted(() => console.log('mounted'));\n *\n * return html`<button @click=${() => emit('increment', props.count.value + 1)}>${props.count}</button>`;\n * },\n * });\n * ```\n */\nexport function define<Props extends Record<string, unknown> = Record<never, never>>(\n tag: string,\n definition: ComponentDefinition<Props>,\n): void {\n if (!tag) throw new OreApiError(ORE_ERRORS.defineRequiresTag);\n\n if (customElements.get(tag)) throw new OreApiError(ORE_ERRORS.defineDuplicate(tag));\n\n const ComponentClass = createComponentClass(tag, definition);\n\n // Registration is intentionally the sole global side effect of define().\n Object.defineProperty(ComponentClass, 'name', { value: tag });\n customElements.define(tag, ComponentClass);\n}\n"],"mappings":"oEAgCA,SAAgB,EACd,EACA,EACM,CACN,GAAI,CAAC,EAAK,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,iBAAiB,EAE5D,GAAI,eAAe,IAAI,CAAG,EAAG,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,gBAAgB,CAAG,CAAC,EAElF,IAAM,EAAiB,EAAA,qBAAqB,EAAK,CAAU,EAG3D,OAAO,eAAe,EAAgB,OAAQ,CAAE,MAAO,CAAI,CAAC,EAC5D,eAAe,OAAO,EAAK,CAAc,CAC3C"}
1
+ {"version":3,"file":"define.cjs","names":[],"sources":["../src/define.ts"],"sourcesContent":["import { createComponentClass } from './_component-class';\nimport type { ComponentDefinition } from './component-types';\nimport { ORE_ERRORS, OreApiError } from './errors';\n\n/**\n * Create and register a web component.\n *\n * The `setup` function runs for each connection and returns an `HTMLResult`.\n * Disconnecting disposes its state; reconnecting rebuilds it. All reactive\n * behaviour within a connection is expressed through directives inside the\n * template — not by re-evaluating setup itself.\n *\n * Everything besides `props` — lifecycle hooks, host bindings, context, slots,\n * emit — is a free function imported from `@vielzeug/ore` and called directly\n * from inside `setup()` (or from a composable it calls):\n *\n * ```ts\n * import { define, html, prop, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n *\n * define<{ count?: number }>('my-counter', {\n * props: { count: prop.number(0) },\n * setup(props) {\n * const emit = useEmit<{ increment: number }>();\n * const slots = useSlots<'header' | 'footer'>();\n *\n * onMounted(() => console.log('mounted'));\n *\n * return html`<button @click=${() => emit('increment', props.count.value + 1)}>${props.count}</button>`;\n * },\n * });\n * ```\n */\nexport function define<Props extends Record<string, unknown> = Record<never, never>>(\n tag: string,\n definition: ComponentDefinition<Props>,\n): void {\n if (!tag) throw new OreApiError(ORE_ERRORS.defineRequiresTag);\n if (customElements.get(tag)) throw new OreApiError(ORE_ERRORS.defineDuplicate(tag));\n\n const ComponentClass = createComponentClass(tag, definition);\n\n // Registration is intentionally the sole global side effect of define().\n Object.defineProperty(ComponentClass, 'name', { value: tag });\n customElements.define(tag, ComponentClass);\n}\n"],"mappings":"oEAgCA,SAAgB,EACd,EACA,EACM,CACN,GAAI,CAAC,EAAK,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,iBAAiB,EAC5D,GAAI,eAAe,IAAI,CAAG,EAAG,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,gBAAgB,CAAG,CAAC,EAElF,IAAM,EAAiB,EAAA,qBAAqB,EAAK,CAAU,EAG3D,OAAO,eAAe,EAAgB,OAAQ,CAAE,MAAO,CAAI,CAAC,EAC5D,eAAe,OAAO,EAAK,CAAc,CAC3C"}
package/dist/define.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ComponentDefinition } from './component-types';
2
2
  /**
3
- * Define and register a web component.
3
+ * Create and register a web component.
4
4
  *
5
5
  * The `setup` function runs for each connection and returns an `HTMLResult`.
6
6
  * Disconnecting disposes its state; reconnecting rebuilds it. All reactive
@@ -1 +1 @@
1
- {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAG7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,MAAM,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EACjF,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,mBAAmB,CAAC,KAAK,CAAC,GACrC,IAAI,CAUN"}
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAG7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,MAAM,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EACjF,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,mBAAmB,CAAC,KAAK,CAAC,GACrC,IAAI,CASN"}
@@ -1 +1 @@
1
- {"version":3,"file":"define.js","names":[],"sources":["../src/define.ts"],"sourcesContent":["import { createComponentClass } from './_component-class';\nimport type { ComponentDefinition } from './component-types';\nimport { ORE_ERRORS, OreApiError } from './errors';\n\n/**\n * Define and register a web component.\n *\n * The `setup` function runs for each connection and returns an `HTMLResult`.\n * Disconnecting disposes its state; reconnecting rebuilds it. All reactive\n * behaviour within a connection is expressed through directives inside the\n * template — not by re-evaluating setup itself.\n *\n * Everything besides `props` — lifecycle hooks, host bindings, context, slots,\n * emit — is a free function imported from `@vielzeug/ore` and called directly\n * from inside `setup()` (or from a composable it calls):\n *\n * ```ts\n * import { define, html, prop, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n *\n * define<{ count?: number }>('my-counter', {\n * props: { count: prop.number(0) },\n * setup(props) {\n * const emit = useEmit<{ increment: number }>();\n * const slots = useSlots<'header' | 'footer'>();\n *\n * onMounted(() => console.log('mounted'));\n *\n * return html`<button @click=${() => emit('increment', props.count.value + 1)}>${props.count}</button>`;\n * },\n * });\n * ```\n */\nexport function define<Props extends Record<string, unknown> = Record<never, never>>(\n tag: string,\n definition: ComponentDefinition<Props>,\n): void {\n if (!tag) throw new OreApiError(ORE_ERRORS.defineRequiresTag);\n\n if (customElements.get(tag)) throw new OreApiError(ORE_ERRORS.defineDuplicate(tag));\n\n const ComponentClass = createComponentClass(tag, definition);\n\n // Registration is intentionally the sole global side effect of define().\n Object.defineProperty(ComponentClass, 'name', { value: tag });\n customElements.define(tag, ComponentClass);\n}\n"],"mappings":"uHAgCA,SAAgB,EACd,EACA,EACM,CACN,GAAI,CAAC,EAAK,MAAM,IAAI,EAAY,EAAW,iBAAiB,EAE5D,GAAI,eAAe,IAAI,CAAG,EAAG,MAAM,IAAI,EAAY,EAAW,gBAAgB,CAAG,CAAC,EAElF,IAAM,EAAiB,EAAqB,EAAK,CAAU,EAG3D,OAAO,eAAe,EAAgB,OAAQ,CAAE,MAAO,CAAI,CAAC,EAC5D,eAAe,OAAO,EAAK,CAAc,CAC3C"}
1
+ {"version":3,"file":"define.js","names":[],"sources":["../src/define.ts"],"sourcesContent":["import { createComponentClass } from './_component-class';\nimport type { ComponentDefinition } from './component-types';\nimport { ORE_ERRORS, OreApiError } from './errors';\n\n/**\n * Create and register a web component.\n *\n * The `setup` function runs for each connection and returns an `HTMLResult`.\n * Disconnecting disposes its state; reconnecting rebuilds it. All reactive\n * behaviour within a connection is expressed through directives inside the\n * template — not by re-evaluating setup itself.\n *\n * Everything besides `props` — lifecycle hooks, host bindings, context, slots,\n * emit — is a free function imported from `@vielzeug/ore` and called directly\n * from inside `setup()` (or from a composable it calls):\n *\n * ```ts\n * import { define, html, prop, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n *\n * define<{ count?: number }>('my-counter', {\n * props: { count: prop.number(0) },\n * setup(props) {\n * const emit = useEmit<{ increment: number }>();\n * const slots = useSlots<'header' | 'footer'>();\n *\n * onMounted(() => console.log('mounted'));\n *\n * return html`<button @click=${() => emit('increment', props.count.value + 1)}>${props.count}</button>`;\n * },\n * });\n * ```\n */\nexport function define<Props extends Record<string, unknown> = Record<never, never>>(\n tag: string,\n definition: ComponentDefinition<Props>,\n): void {\n if (!tag) throw new OreApiError(ORE_ERRORS.defineRequiresTag);\n if (customElements.get(tag)) throw new OreApiError(ORE_ERRORS.defineDuplicate(tag));\n\n const ComponentClass = createComponentClass(tag, definition);\n\n // Registration is intentionally the sole global side effect of define().\n Object.defineProperty(ComponentClass, 'name', { value: tag });\n customElements.define(tag, ComponentClass);\n}\n"],"mappings":"uHAgCA,SAAgB,EACd,EACA,EACM,CACN,GAAI,CAAC,EAAK,MAAM,IAAI,EAAY,EAAW,iBAAiB,EAC5D,GAAI,eAAe,IAAI,CAAG,EAAG,MAAM,IAAI,EAAY,EAAW,gBAAgB,CAAG,CAAC,EAElF,IAAM,EAAiB,EAAqB,EAAK,CAAU,EAG3D,OAAO,eAAe,EAAgB,OAAQ,CAAE,MAAO,CAAI,CAAC,EAC5D,eAAe,OAAO,EAAK,CAAc,CAC3C"}
@@ -1,2 +1,2 @@
1
- const e=require("../errors.cjs"),t=require("../utils/dom.cjs"),n=require("../template/result.cjs");let r=require("@vielzeug/ripple");var i=(e,t,n,i,a)=>{let o=(0,r.signal)(e),s=(0,r.signal)(t),c=(0,r.createScope)(),l=[],u=[];return c.run(()=>{u=n(o,s).mount(i,a,e=>l.push(e))}),{cleanups:l,data:o,index:s,key:``,nodes:u,scope:c}},a=e=>{e.scope.dispose(),t.runAll(e.cleanups),t.removeNodes(e.nodes)},o=(t,n,o,s,c,l)=>{let u=[],d=new Set;for(let t=0;t<n.length;t++){let r=String(o(n[t],t));if(d.has(r))throw new e.OreApiError(e.ORE_ERRORS.eachDuplicateKey(r,t));d.add(r),u.push(r)}for(let[e,n]of t)d.has(e)||(a(n),t.delete(e));let f=[];for(let e=0;e<n.length;e++){let a=u[e],o=t.get(a);if(o)(0,r.batch)(()=>{o.data.value=n[e],o.index.value=e}),f.push(o);else{let o=(0,r.untrack)(()=>i(n[e],e,s,c,l));o.key=a,t.set(a,o),f.push(o)}}let p=l;for(let e=f.length-1;e>=0;e--){let t=f[e],n=t.nodes[0];if(n&&n!==p.previousSibling)for(let e of t.nodes)c.insertBefore(e,p);p=n??p}return f};function s(i,s,c,l){let u=Array.isArray(i)?(0,r.signal)(i):typeof i==`function`?(0,r.computed)(i):i;return n.createDirectiveResult((n,i)=>{let d=n.parentNode;e.invariant(d,`each() anchor comment has no parent node`);let f=document.createComment(`each/end`);d.insertBefore(f,n.nextSibling);let p=new Map,m=[],h=null,g=[],_=()=>{l&&(h=l().mount(d,f,e=>g.push(e)))},v=()=>{h&&(t.runAll(g),t.removeNodes(h),h=null,g=[])},y=(0,r.effect)(()=>{let t=u.value??[];if(t.length===0){for(let e of(0,r.untrack)(()=>m))a(e);p=new Map,m=[],h||(0,r.untrack)(_);return}v();try{m=(0,r.untrack)(()=>o(p,t,s,c,d,f))}catch(t){let r=t instanceof Error?t:Error(String(t));e.reportRuntimeError(new e.OreLifecycleError(`each() failed to reconcile a list update: ${r.message}`,{cause:r,component:`each()`,phase:`each-reconcile`}),n);for(let e of p.values())a(e);p=new Map,m=[]}});i(()=>y.dispose()),i(()=>{v();for(let e of m)a(e);f.remove()})})}exports.each=s;
1
+ const e=require("../errors.cjs"),t=require("../utils/dom.cjs"),n=require("../template/result.cjs");let r=require("@vielzeug/ripple");var i=(e,t,n,i,a)=>{let o=(0,r.signal)(e),s=(0,r.signal)(t),c=(0,r.createScope)(),l=[],u=[];return c.run(()=>{u=n(o,s).mount(i,a,e=>l.push(e))}),{cleanups:l,data:o,index:s,nodes:u,scope:c}},a=e=>{e.scope.dispose(),t.runAll(e.cleanups),t.removeNodes(e.nodes)},o=(e,t,n,o,s,c)=>{let l=new Set(n);for(let[t,n]of e)l.has(t)||(a(n),e.delete(t));let u=[];for(let a=0;a<t.length;a++){let l=n[a],d=e.get(l);if(d)(0,r.batch)(()=>{d.data.value=t[a],d.index.value=a}),u.push(d);else{let n=(0,r.untrack)(()=>i(t[a],a,o,s,c));e.set(l,n),u.push(n)}}let d=c;for(let e=u.length-1;e>=0;e--){let t=u[e],n=t.nodes[0],r=t.nodes[t.nodes.length-1];if(n&&r!==d.previousSibling)for(let e of t.nodes)s.insertBefore(e,d);d=n??d}return u},s=(t,n)=>{let r=[],i=new Set;for(let a=0;a<t.length;a++){let o=n(t[a],a);if(i.has(o))throw new e.OreApiError(e.ORE_ERRORS.eachDuplicateKey(String(o),a));i.add(o),r.push(o)}return r},c=(t,n)=>{let r=t instanceof Error?t:Error(String(t));e.reportRuntimeError(new e.OreLifecycleError(`each() failed to reconcile a list update: ${r.message}`,{cause:r,component:`each()`,phase:`each-reconcile`}),n)};function l(i,l,u,d){let f=Array.isArray(i)?(0,r.signal)(i):typeof i==`function`?(0,r.computed)(i):i;return n.createDirectiveResult((n,i)=>{let p=n.parentNode;e.invariant(p,`each() anchor comment has no parent node`);let m=document.createComment(`each/end`);p.insertBefore(m,n.nextSibling);let h=new Map,g=[],_=null,v=[],y=()=>{d&&(_=d().mount(p,m,e=>v.push(e)))},b=()=>{_&&(t.runAll(v),t.removeNodes(_),_=null,v=[])},x=(0,r.effect)(()=>{let e=f.value??[];if(e.length===0){for(let e of(0,r.untrack)(()=>g))a(e);h=new Map,g=[],_||(0,r.untrack)(y);return}let t;try{t=(0,r.untrack)(()=>s(e,l))}catch(e){c(e,n);return}b();try{g=(0,r.untrack)(()=>o(h,e,t,u,p,m))}catch(e){c(e,n);for(let e of h.values())a(e);h=new Map,g=[]}});i(()=>x.dispose()),i(()=>{b();for(let e of g)a(e);m.remove()})})}exports.each=l;
2
2
  //# sourceMappingURL=each.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"each.cjs","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import {\n batch,\n computed,\n createScope,\n type Readable,\n effect as rawEffect,\n type Scope,\n type Signal,\n signal,\n untrack,\n} from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError, OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../template/result';\nimport { removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = Readable<T[]> | (() => T[]) | T[];\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n /** The key used to identify this entry. */\n key: string;\n nodes: Node[];\n scope: Scope;\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n const scope = createScope();\n const cleanups: (() => void)[] = [];\n let nodes: Node[] = [];\n\n scope.run(() => {\n const result = render(dataSignal, indexSignal);\n\n nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n });\n\n return { cleanups, data: dataSignal, index: indexSignal, key: '', nodes, scope };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n entry.scope.dispose();\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map (mutated in-place) against the next array.\n * Stale entries are removed and destroyed; new entries are created and inserted.\n * Existing entries are updated in-place via signal writes.\n * Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<string, ItemEntry<T>>,\n next: T[],\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n): ItemEntry<T>[] => {\n const nextKeys: string[] = [];\n const nextKeySet = new Set<string>();\n\n for (let i = 0; i < next.length; i++) {\n const key = String(keyFn(next[i], i));\n\n if (nextKeySet.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(key, i));\n\n nextKeySet.add(key);\n nextKeys.push(key);\n }\n\n // Remove stale entries from the map\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n // Build the next ordered list: update existing items, create new ones\n const nextOrdered: ItemEntry<T>[] = [];\n\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n const existing = itemsMap.get(key);\n\n if (existing) {\n batch(() => {\n existing.data.value = next[i];\n existing.index.value = i;\n });\n nextOrdered.push(existing);\n } else {\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n entry.key = key;\n itemsMap.set(key, entry);\n nextOrdered.push(entry);\n }\n }\n\n // DOM ordering: right-to-left pass — move any item not already adjacent to cursor.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n\n if (firstNode && firstNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n return nextOrdered;\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index — an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for.\n *\n * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)\n * does not throw past this function — an uncaught exception inside the reactive effect that\n * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.\n * Instead the list is cleared and the failure is reported via the `ore:error` DOM event (see\n * `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log — listen for\n * `ore:error` on `document`/`window` to observe this in every build, including production.\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<string, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n clearFallback();\n\n try {\n itemsOrdered = untrack(() => reconcileItems(itemsMap, nextList, keyFn, render, parent, endMarker));\n } catch (err) {\n const cause = err instanceof Error ? err : new Error(String(err));\n\n // Dispatched on the anchor comment (always a live DOM node) rather than the enclosing\n // component's host element, which each() has no direct reference to — the event still\n // bubbles/composes up to any ancestor listener, including a global one on document.\n reportRuntimeError(\n new OreLifecycleError(`each() failed to reconcile a list update: ${cause.message}`, {\n cause,\n component: 'each()',\n phase: 'each-reconcile',\n }),\n anchor,\n );\n\n for (const entry of itemsMap.values()) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"each.cjs","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import {\n batch,\n computed,\n createScope,\n type Readable,\n effect as rawEffect,\n type Scope,\n type Signal,\n signal,\n untrack,\n} from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError, OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../template/result';\nimport { removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = Readable<T[]> | (() => T[]) | T[];\ntype ItemKey = number | string;\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n nodes: Node[];\n scope: Scope;\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n const scope = createScope();\n const cleanups: (() => void)[] = [];\n let nodes: Node[] = [];\n\n scope.run(() => {\n const result = render(dataSignal, indexSignal);\n\n nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n });\n\n return { cleanups, data: dataSignal, index: indexSignal, nodes, scope };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n entry.scope.dispose();\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map (mutated in-place) against the next array.\n * Stale entries are removed and destroyed; new entries are created and inserted.\n * Existing entries are updated in-place via signal writes.\n * Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<ItemKey, ItemEntry<T>>,\n next: T[],\n nextKeys: ItemKey[],\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n): ItemEntry<T>[] => {\n const nextKeySet = new Set(nextKeys);\n\n // Remove stale entries from the map\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n // Build the next ordered list: update existing items, create new ones\n const nextOrdered: ItemEntry<T>[] = [];\n\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n const existing = itemsMap.get(key);\n\n if (existing) {\n batch(() => {\n existing.data.value = next[i];\n existing.index.value = i;\n });\n nextOrdered.push(existing);\n } else {\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n itemsMap.set(key, entry);\n nextOrdered.push(entry);\n }\n }\n\n // DOM ordering: right-to-left pass — move any item not already adjacent to cursor.\n // An item is in place when its last node directly precedes the cursor; items may\n // span several nodes (whitespace text around an element), so compare the last one.\n // Moving an already-placed item would disconnect and reconnect its custom elements.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n const lastNode = entry.nodes[entry.nodes.length - 1];\n\n if (firstNode && lastNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n return nextOrdered;\n};\n\nconst collectKeys = <T>(next: T[], keyFn: (item: T, index: number) => ItemKey): ItemKey[] => {\n const keys: ItemKey[] = [];\n const seen = new Set<ItemKey>();\n\n for (let i = 0; i < next.length; i++) {\n const key = keyFn(next[i], i);\n\n if (seen.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(String(key), i));\n\n seen.add(key);\n keys.push(key);\n }\n\n return keys;\n};\n\nconst reportEachError = (error: unknown, anchor: Comment): void => {\n const cause = error instanceof Error ? error : new Error(String(error));\n\n reportRuntimeError(\n new OreLifecycleError(`each() failed to reconcile a list update: ${cause.message}`, {\n cause,\n component: 'each()',\n phase: 'each-reconcile',\n }),\n anchor,\n );\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index — an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for.\n *\n * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)\n * does not throw past this function — an uncaught exception inside the reactive effect that\n * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.\n * Instead the last valid list is preserved and the failure is reported via the `ore:error` DOM event\n * (see `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log — listen for\n * `ore:error` on `document`/`window` to observe this in every build, including production.\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<ItemKey, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n let nextKeys: ItemKey[];\n\n try {\n nextKeys = untrack(() => collectKeys(nextList, keyFn));\n } catch (error) {\n reportEachError(error, anchor);\n return;\n }\n\n clearFallback();\n\n try {\n itemsOrdered = untrack(() => reconcileItems(itemsMap, nextList, nextKeys, render, parent, endMarker));\n } catch (error) {\n // Rendering may already have changed the live map, so clear it to avoid retaining partial DOM.\n reportEachError(error, anchor);\n for (const entry of itemsMap.values()) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
@@ -29,8 +29,8 @@ type MaybeReactiveArray<T> = Readable<T[]> | (() => T[]) | T[];
29
29
  * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)
30
30
  * does not throw past this function — an uncaught exception inside the reactive effect that
31
31
  * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.
32
- * Instead the list is cleared and the failure is reported via the `ore:error` DOM event (see
33
- * `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log — listen for
32
+ * Instead the last valid list is preserved and the failure is reported via the `ore:error` DOM event
33
+ * (see `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log — listen for
34
34
  * `ore:error` on `document`/`window` to observe this in every build, including production.
35
35
  */
36
36
  export declare function each<T>(list: MaybeReactiveArray<T>, keyFn: (item: T, index: number) => string | number, render: (item: Readable<T>, index: Readable<number>) => HTMLResult, fallback?: () => HTMLResult): DirectiveResult;
@@ -1 +1 @@
1
- {"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/directives/each.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAMd,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAyB,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAKlG,KAAK,kBAAkB,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;AAyH/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,IAAI,CAAC,CAAC,EACpB,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAC3B,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,EAClD,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,UAAU,EAClE,QAAQ,CAAC,EAAE,MAAM,UAAU,GAC1B,eAAe,CAmFjB"}
1
+ {"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/directives/each.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAMd,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAyB,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAKlG,KAAK,kBAAkB,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;AA8I/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,IAAI,CAAC,CAAC,EACpB,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAC3B,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,EAClD,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,UAAU,EAClE,QAAQ,CAAC,EAAE,MAAM,UAAU,GAC1B,eAAe,CAgFjB"}
@@ -1,2 +1,2 @@
1
- import{ORE_ERRORS as e,OreApiError as t,OreLifecycleError as n,invariant as r,reportRuntimeError as i}from"../errors.js";import{removeNodes as a,runAll as o}from"../utils/dom.js";import{createDirectiveResult as s}from"../template/result.js";import{batch as c,computed as l,createScope as u,effect as d,signal as f,untrack as p}from"@vielzeug/ripple";var m=(e,t,n,r,i)=>{let a=f(e),o=f(t),s=u(),c=[],l=[];return s.run(()=>{l=n(a,o).mount(r,i,e=>c.push(e))}),{cleanups:c,data:a,index:o,key:``,nodes:l,scope:s}},h=e=>{e.scope.dispose(),o(e.cleanups),a(e.nodes)},g=(n,r,i,a,o,s)=>{let l=[],u=new Set;for(let n=0;n<r.length;n++){let a=String(i(r[n],n));if(u.has(a))throw new t(e.eachDuplicateKey(a,n));u.add(a),l.push(a)}for(let[e,t]of n)u.has(e)||(h(t),n.delete(e));let d=[];for(let e=0;e<r.length;e++){let t=l[e],i=n.get(t);if(i)c(()=>{i.data.value=r[e],i.index.value=e}),d.push(i);else{let i=p(()=>m(r[e],e,a,o,s));i.key=t,n.set(t,i),d.push(i)}}let f=s;for(let e=d.length-1;e>=0;e--){let t=d[e],n=t.nodes[0];if(n&&n!==f.previousSibling)for(let e of t.nodes)o.insertBefore(e,f);f=n??f}return d};function _(e,t,c,u){let m=Array.isArray(e)?f(e):typeof e==`function`?l(e):e;return s((e,s)=>{let l=e.parentNode;r(l,`each() anchor comment has no parent node`);let f=document.createComment(`each/end`);l.insertBefore(f,e.nextSibling);let _=new Map,v=[],y=null,b=[],x=()=>{u&&(y=u().mount(l,f,e=>b.push(e)))},S=()=>{y&&(o(b),a(y),y=null,b=[])},C=d(()=>{let r=m.value??[];if(r.length===0){for(let e of p(()=>v))h(e);_=new Map,v=[],y||p(x);return}S();try{v=p(()=>g(_,r,t,c,l,f))}catch(t){let r=t instanceof Error?t:Error(String(t));i(new n(`each() failed to reconcile a list update: ${r.message}`,{cause:r,component:`each()`,phase:`each-reconcile`}),e);for(let e of _.values())h(e);_=new Map,v=[]}});s(()=>C.dispose()),s(()=>{S();for(let e of v)h(e);f.remove()})})}export{_ as each};
1
+ import{ORE_ERRORS as e,OreApiError as t,OreLifecycleError as n,invariant as r,reportRuntimeError as i}from"../errors.js";import{removeNodes as a,runAll as o}from"../utils/dom.js";import{createDirectiveResult as s}from"../template/result.js";import{batch as c,computed as l,createScope as u,effect as d,signal as f,untrack as p}from"@vielzeug/ripple";var m=(e,t,n,r,i)=>{let a=f(e),o=f(t),s=u(),c=[],l=[];return s.run(()=>{l=n(a,o).mount(r,i,e=>c.push(e))}),{cleanups:c,data:a,index:o,nodes:l,scope:s}},h=e=>{e.scope.dispose(),o(e.cleanups),a(e.nodes)},g=(e,t,n,r,i,a)=>{let o=new Set(n);for(let[t,n]of e)o.has(t)||(h(n),e.delete(t));let s=[];for(let o=0;o<t.length;o++){let l=n[o],u=e.get(l);if(u)c(()=>{u.data.value=t[o],u.index.value=o}),s.push(u);else{let n=p(()=>m(t[o],o,r,i,a));e.set(l,n),s.push(n)}}let l=a;for(let e=s.length-1;e>=0;e--){let t=s[e],n=t.nodes[0],r=t.nodes[t.nodes.length-1];if(n&&r!==l.previousSibling)for(let e of t.nodes)i.insertBefore(e,l);l=n??l}return s},_=(n,r)=>{let i=[],a=new Set;for(let o=0;o<n.length;o++){let s=r(n[o],o);if(a.has(s))throw new t(e.eachDuplicateKey(String(s),o));a.add(s),i.push(s)}return i},v=(e,t)=>{let r=e instanceof Error?e:Error(String(e));i(new n(`each() failed to reconcile a list update: ${r.message}`,{cause:r,component:`each()`,phase:`each-reconcile`}),t)};function y(e,t,n,i){let c=Array.isArray(e)?f(e):typeof e==`function`?l(e):e;return s((e,s)=>{let l=e.parentNode;r(l,`each() anchor comment has no parent node`);let u=document.createComment(`each/end`);l.insertBefore(u,e.nextSibling);let f=new Map,m=[],y=null,b=[],x=()=>{i&&(y=i().mount(l,u,e=>b.push(e)))},S=()=>{y&&(o(b),a(y),y=null,b=[])},C=d(()=>{let r=c.value??[];if(r.length===0){for(let e of p(()=>m))h(e);f=new Map,m=[],y||p(x);return}let i;try{i=p(()=>_(r,t))}catch(t){v(t,e);return}S();try{m=p(()=>g(f,r,i,n,l,u))}catch(t){v(t,e);for(let e of f.values())h(e);f=new Map,m=[]}});s(()=>C.dispose()),s(()=>{S();for(let e of m)h(e);u.remove()})})}export{y as each};
2
2
  //# sourceMappingURL=each.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"each.js","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import {\n batch,\n computed,\n createScope,\n type Readable,\n effect as rawEffect,\n type Scope,\n type Signal,\n signal,\n untrack,\n} from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError, OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../template/result';\nimport { removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = Readable<T[]> | (() => T[]) | T[];\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n /** The key used to identify this entry. */\n key: string;\n nodes: Node[];\n scope: Scope;\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n const scope = createScope();\n const cleanups: (() => void)[] = [];\n let nodes: Node[] = [];\n\n scope.run(() => {\n const result = render(dataSignal, indexSignal);\n\n nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n });\n\n return { cleanups, data: dataSignal, index: indexSignal, key: '', nodes, scope };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n entry.scope.dispose();\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map (mutated in-place) against the next array.\n * Stale entries are removed and destroyed; new entries are created and inserted.\n * Existing entries are updated in-place via signal writes.\n * Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<string, ItemEntry<T>>,\n next: T[],\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n): ItemEntry<T>[] => {\n const nextKeys: string[] = [];\n const nextKeySet = new Set<string>();\n\n for (let i = 0; i < next.length; i++) {\n const key = String(keyFn(next[i], i));\n\n if (nextKeySet.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(key, i));\n\n nextKeySet.add(key);\n nextKeys.push(key);\n }\n\n // Remove stale entries from the map\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n // Build the next ordered list: update existing items, create new ones\n const nextOrdered: ItemEntry<T>[] = [];\n\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n const existing = itemsMap.get(key);\n\n if (existing) {\n batch(() => {\n existing.data.value = next[i];\n existing.index.value = i;\n });\n nextOrdered.push(existing);\n } else {\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n entry.key = key;\n itemsMap.set(key, entry);\n nextOrdered.push(entry);\n }\n }\n\n // DOM ordering: right-to-left pass — move any item not already adjacent to cursor.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n\n if (firstNode && firstNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n return nextOrdered;\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index — an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for.\n *\n * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)\n * does not throw past this function — an uncaught exception inside the reactive effect that\n * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.\n * Instead the list is cleared and the failure is reported via the `ore:error` DOM event (see\n * `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log — listen for\n * `ore:error` on `document`/`window` to observe this in every build, including production.\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<string, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n clearFallback();\n\n try {\n itemsOrdered = untrack(() => reconcileItems(itemsMap, nextList, keyFn, render, parent, endMarker));\n } catch (err) {\n const cause = err instanceof Error ? err : new Error(String(err));\n\n // Dispatched on the anchor comment (always a live DOM node) rather than the enclosing\n // component's host element, which each() has no direct reference to — the event still\n // bubbles/composes up to any ancestor listener, including a global one on document.\n reportRuntimeError(\n new OreLifecycleError(`each() failed to reconcile a list update: ${cause.message}`, {\n cause,\n component: 'each()',\n phase: 'each-reconcile',\n }),\n anchor,\n );\n\n for (const entry of itemsMap.values()) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"each.js","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import {\n batch,\n computed,\n createScope,\n type Readable,\n effect as rawEffect,\n type Scope,\n type Signal,\n signal,\n untrack,\n} from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError, OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../template/result';\nimport { removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = Readable<T[]> | (() => T[]) | T[];\ntype ItemKey = number | string;\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n nodes: Node[];\n scope: Scope;\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n const scope = createScope();\n const cleanups: (() => void)[] = [];\n let nodes: Node[] = [];\n\n scope.run(() => {\n const result = render(dataSignal, indexSignal);\n\n nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n });\n\n return { cleanups, data: dataSignal, index: indexSignal, nodes, scope };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n entry.scope.dispose();\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map (mutated in-place) against the next array.\n * Stale entries are removed and destroyed; new entries are created and inserted.\n * Existing entries are updated in-place via signal writes.\n * Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<ItemKey, ItemEntry<T>>,\n next: T[],\n nextKeys: ItemKey[],\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n): ItemEntry<T>[] => {\n const nextKeySet = new Set(nextKeys);\n\n // Remove stale entries from the map\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n // Build the next ordered list: update existing items, create new ones\n const nextOrdered: ItemEntry<T>[] = [];\n\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n const existing = itemsMap.get(key);\n\n if (existing) {\n batch(() => {\n existing.data.value = next[i];\n existing.index.value = i;\n });\n nextOrdered.push(existing);\n } else {\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n itemsMap.set(key, entry);\n nextOrdered.push(entry);\n }\n }\n\n // DOM ordering: right-to-left pass — move any item not already adjacent to cursor.\n // An item is in place when its last node directly precedes the cursor; items may\n // span several nodes (whitespace text around an element), so compare the last one.\n // Moving an already-placed item would disconnect and reconnect its custom elements.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n const lastNode = entry.nodes[entry.nodes.length - 1];\n\n if (firstNode && lastNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n return nextOrdered;\n};\n\nconst collectKeys = <T>(next: T[], keyFn: (item: T, index: number) => ItemKey): ItemKey[] => {\n const keys: ItemKey[] = [];\n const seen = new Set<ItemKey>();\n\n for (let i = 0; i < next.length; i++) {\n const key = keyFn(next[i], i);\n\n if (seen.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(String(key), i));\n\n seen.add(key);\n keys.push(key);\n }\n\n return keys;\n};\n\nconst reportEachError = (error: unknown, anchor: Comment): void => {\n const cause = error instanceof Error ? error : new Error(String(error));\n\n reportRuntimeError(\n new OreLifecycleError(`each() failed to reconcile a list update: ${cause.message}`, {\n cause,\n component: 'each()',\n phase: 'each-reconcile',\n }),\n anchor,\n );\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index — an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for.\n *\n * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)\n * does not throw past this function — an uncaught exception inside the reactive effect that\n * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.\n * Instead the last valid list is preserved and the failure is reported via the `ore:error` DOM event\n * (see `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log — listen for\n * `ore:error` on `document`/`window` to observe this in every build, including production.\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<ItemKey, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n let nextKeys: ItemKey[];\n\n try {\n nextKeys = untrack(() => collectKeys(nextList, keyFn));\n } catch (error) {\n reportEachError(error, anchor);\n return;\n }\n\n clearFallback();\n\n try {\n itemsOrdered = untrack(() => reconcileItems(itemsMap, nextList, nextKeys, render, parent, endMarker));\n } catch (error) {\n // Rendering may already have changed the live map, so clear it to avoid retaining partial DOM.\n reportEachError(error, anchor);\n for (const entry of itemsMap.values()) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"8VA+BA,IAAM,GACJ,EACA,EACA,EACA,EACA,IACiB,CACjB,IAAM,EAAwB,EAAO,CAAI,EACnC,EAA8B,EAAO,CAAK,EAC1C,EAAQ,EAAY,EACpB,EAA2B,CAAC,EAC9B,EAAgB,CAAC,EAQrB,OANA,EAAM,QAAU,CAGd,EAFe,EAAO,EAAY,CAE1B,CAAA,CAAO,MAAM,EAAQ,EAAe,GAAO,EAAS,KAAK,CAAE,CAAC,CACtE,CAAC,EAEM,CAAE,WAAU,KAAM,EAAY,MAAO,EAAa,QAAO,OAAM,CACxE,EAEM,EAAiB,GAA8B,CACnD,EAAM,MAAM,QAAQ,EACpB,EAAO,EAAM,QAAQ,EACrB,EAAY,EAAM,KAAK,CACzB,EAUM,GACJ,EACA,EACA,EACA,EACA,EACA,IACmB,CACnB,IAAM,EAAa,IAAI,IAAI,CAAQ,EAGnC,IAAK,GAAM,CAAC,EAAK,KAAU,EACpB,EAAW,IAAI,CAAG,IACrB,EAAW,CAAK,EAChB,EAAS,OAAO,CAAG,GAKvB,IAAM,EAA8B,CAAC,EAErC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,IAAK,CACpC,IAAM,EAAM,EAAS,GACf,EAAW,EAAS,IAAI,CAAG,EAEjC,GAAI,EACF,MAAY,CACV,EAAS,KAAK,MAAQ,EAAK,GAC3B,EAAS,MAAM,MAAQ,CACzB,CAAC,EACD,EAAY,KAAK,CAAQ,MACpB,CACL,IAAM,EAAQ,MAAc,EAAW,EAAK,GAAI,EAAG,EAAQ,EAAQ,CAAS,CAAC,EAE7E,EAAS,IAAI,EAAK,CAAK,EACvB,EAAY,KAAK,CAAK,CACxB,CACF,CAQA,IAAI,EAAe,EAEnB,IAAK,IAAI,EAAI,EAAY,OAAS,EAAG,GAAK,EAAG,IAAK,CAChD,IAAM,EAAQ,EAAY,GACpB,EAAY,EAAM,MAAM,GACxB,EAAW,EAAM,MAAM,EAAM,MAAM,OAAS,GAElD,GAAI,GAAa,IAAa,EAAO,gBACnC,IAAK,IAAM,KAAQ,EAAM,MAAO,EAAO,aAAa,EAAM,CAAM,EAGlE,EAAS,GAAa,CACxB,CAEA,OAAO,CACT,EAEM,GAAkB,EAAW,IAA0D,CAC3F,IAAM,EAAkB,CAAC,EACnB,EAAO,IAAI,IAEjB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,IAAK,CACpC,IAAM,EAAM,EAAM,EAAK,GAAI,CAAC,EAE5B,GAAI,EAAK,IAAI,CAAG,EAAG,MAAM,IAAI,EAAY,EAAW,iBAAiB,OAAO,CAAG,EAAG,CAAC,CAAC,EAEpF,EAAK,IAAI,CAAG,EACZ,EAAK,KAAK,CAAG,CACf,CAEA,OAAO,CACT,EAEM,GAAmB,EAAgB,IAA0B,CACjE,IAAM,EAAQ,aAAiB,MAAQ,EAAY,MAAM,OAAO,CAAK,CAAC,EAEtE,EACE,IAAI,EAAkB,6CAA6C,EAAM,UAAW,CAClF,QACA,UAAW,SACX,MAAO,gBACT,CAAC,EACD,CACF,CACF,EAoCA,SAAgB,EACd,EACA,EACA,EACA,EACiB,CACjB,IAAM,EAAa,MAAM,QAAQ,CAAI,EACjC,EAAO,CAAW,EAClB,OAAO,GAAS,WACd,EAAS,CAAiB,EAC1B,EAEN,OAAO,GAAuB,EAAQ,IAAoB,CACxD,IAAM,EAAS,EAAO,WAEtB,EAAU,EAAQ,0CAA0C,EAE5D,IAAM,EAAY,SAAS,cAAc,UAAU,EAEnD,EAAO,aAAa,EAAW,EAAO,WAAW,EAEjD,IAAI,EAAW,IAAI,IACf,EAA+B,CAAC,EAChC,EAA+B,KAC/B,EAAmC,CAAC,EAElC,MAA4B,CAC3B,IAIL,EAFe,EAEC,CAAA,CAAO,MAAM,EAAQ,EAAY,GAAO,EAAiB,KAAK,CAAE,CAAC,EACnF,EAEM,MAA4B,CAC5B,IACF,EAAO,CAAgB,EACvB,EAAY,CAAa,EACzB,EAAgB,KAChB,EAAmB,CAAC,EAExB,EAEM,EAAM,MAAgB,CAC1B,IAAM,EAAW,EAAW,OAAS,CAAC,EAEtC,GAAI,EAAS,SAAW,EAAG,CACzB,IAAK,IAAM,KAAS,MAAc,CAAY,EAAG,EAAW,CAAK,EACjE,EAAW,IAAI,IACf,EAAe,CAAC,EAEX,GAAe,EAAQ,CAAa,EAEzC,MACF,CAEA,IAAI,EAEJ,GAAI,CACF,EAAW,MAAc,EAAY,EAAU,CAAK,CAAC,CACvD,OAAS,EAAO,CACd,EAAgB,EAAO,CAAM,EAC7B,MACF,CAEA,EAAc,EAEd,GAAI,CACF,EAAe,MAAc,EAAe,EAAU,EAAU,EAAU,EAAQ,EAAQ,CAAS,CAAC,CACtG,OAAS,EAAO,CAEd,EAAgB,EAAO,CAAM,EAC7B,IAAK,IAAM,KAAS,EAAS,OAAO,EAAG,EAAW,CAAK,EACvD,EAAW,IAAI,IACf,EAAe,CAAC,CAClB,CACF,CAAC,EAED,MAAsB,EAAI,QAAQ,CAAC,EACnC,MAAsB,CACpB,EAAc,EACd,IAAK,IAAM,KAAS,EAAc,EAAW,CAAK,EAClD,EAAU,OAAO,CACnB,CAAC,CACH,CAAC,CACH"}
@@ -1,2 +1,2 @@
1
- const e=require("../errors.cjs"),t=require("../utils/dom.cjs"),n=require("../template/result.cjs");let r=require("@vielzeug/ripple");var i=`when() anchor comment has no parent node`;function a(a,o,s){return typeof a!=`function`&&!(0,r.isReactive)(a)?n.createDirectiveResult((r,c)=>{let l=a?o():s?s():null;if(!l||!n.isHtmlResult(l))return;let u=r.parentNode;e.invariant(u,i);let d=l.mount(u,r,c);c(()=>t.removeNodes(d))}):n.createDirectiveResult((c,l)=>{let u=(typeof a==`function`?(0,r.computed)(a):null)??a,d=c.parentNode;e.invariant(d,i);let f=document.createComment(`when/end`);d.insertBefore(f,c.nextSibling);let p=t.createReplaceableSlot(),m=(0,r.effect)(()=>{let e=u.value;p.clear();let t=e?o():s?s():null;!t||!n.isHtmlResult(t)||p.setNodes((0,r.untrack)(()=>t.mount(d,f,p.registerCleanup)))});l(()=>m.dispose()),l(()=>{p.clear(),f.remove()})})}exports.when=a;
1
+ const e=require("../errors.cjs"),t=require("../utils/dom.cjs"),n=require("../template/result.cjs");let r=require("@vielzeug/ripple");var i=`when() anchor comment has no parent node`;function a(a,o,s){return typeof a!=`function`&&!(0,r.isReactive)(a)?n.createDirectiveResult((r,c)=>{let l=a?o():s?s():null;if(!l||!n.isHtmlResult(l))return;let u=r.parentNode;e.invariant(u,i);let d=l.mount(u,r,c);c(()=>t.removeNodes(d))}):n.createDirectiveResult((c,l)=>{let u=(typeof a==`function`?(0,r.computed)(a):null)??a,d=c.parentNode;e.invariant(d,i);let f=document.createComment(`when/end`);d.insertBefore(f,c.nextSibling);let p=t.createReplaceableSlot(),m=(0,r.effect)(()=>{let e=u.value;p.clear();let t=e?o():s?s():null;t&&n.isHtmlResult(t)&&p.setNodes((0,r.untrack)(()=>t.mount(d,f,p.registerCleanup)))});l(()=>m.dispose()),l(()=>{p.clear(),f.remove()})})}exports.when=a;
2
2
  //# sourceMappingURL=when.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"when.cjs","names":[],"sources":["../../src/directives/when.ts"],"sourcesContent":["import { computed, isReactive, type Readable, effect as rawEffect, untrack } from '@vielzeug/ripple';\n\nimport { invariant } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult, isHtmlResult } from '../template/result';\nimport { createReplaceableSlot, removeNodes } from '../utils/dom';\n\ntype MaybeReactive<T> = T | (() => T) | Readable<T>;\n\ntype WhenRenderable = HTMLResult | null | undefined | false;\n\nconst NO_PARENT_MSG = 'when() anchor comment has no parent node';\n\n/**\n * Conditionally renders one of two branches as a `DirectiveResult`.\n *\n * The condition may be a boolean, signal, or getter function.\n * When the condition changes, the current branch is cleaned up and\n * the new branch is mounted in its place.\n *\n * @example\n * ```ts\n * html`${when(isLoggedIn, () => html`<p>Welcome</p>`, () => html`<p>Log in</p>`)}`\n * ```\n */\nexport function when(\n condition: MaybeReactive<boolean>,\n truthy: () => WhenRenderable,\n falsy?: () => WhenRenderable,\n): DirectiveResult {\n if (typeof condition !== 'function' && !isReactive(condition)) {\n return createDirectiveResult((anchor, registerCleanup) => {\n const branch = condition ? truthy() : falsy ? falsy() : null;\n\n if (!branch || !isHtmlResult(branch)) return;\n\n const parent = anchor.parentNode;\n\n invariant(parent, NO_PARENT_MSG);\n\n const nodes = branch.mount(parent, anchor, registerCleanup);\n\n registerCleanup(() => removeNodes(nodes));\n });\n }\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const ownedComputed = typeof condition === 'function' ? computed(condition as () => boolean) : null;\n const conditionSignal = ownedComputed ?? (condition as Readable<boolean>);\n\n const parent = anchor.parentNode;\n\n invariant(parent, NO_PARENT_MSG);\n\n const endMarker = document.createComment('when/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n const slot = createReplaceableSlot();\n\n const sub = rawEffect(() => {\n const next = conditionSignal.value;\n\n slot.clear();\n\n const branch = next ? truthy() : falsy ? falsy() : null;\n\n if (!branch || !isHtmlResult(branch)) return;\n\n slot.setNodes(untrack(() => branch.mount(parent, endMarker, slot.registerCleanup)));\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n slot.clear();\n endMarker.remove();\n });\n });\n}\n"],"mappings":"qIAUA,IAAM,EAAgB,2CActB,SAAgB,EACd,EACA,EACA,EACiB,CAiBjB,OAhBI,OAAO,GAAc,YAAc,EAAA,EAAC,EAAA,WAAA,CAAW,CAAS,EACnD,EAAA,uBAAuB,EAAQ,IAAoB,CACxD,IAAM,EAAS,EAAY,EAAO,EAAI,EAAQ,EAAM,EAAI,KAExD,GAAI,CAAC,GAAU,CAAC,EAAA,aAAa,CAAM,EAAG,OAEtC,IAAM,EAAS,EAAO,WAEtB,EAAA,UAAU,EAAQ,CAAa,EAE/B,IAAM,EAAQ,EAAO,MAAM,EAAQ,EAAQ,CAAe,EAE1D,MAAsB,EAAA,YAAY,CAAK,CAAC,CAC1C,CAAC,EAGI,EAAA,uBAAuB,EAAQ,IAAoB,CAExD,IAAM,GADgB,OAAO,GAAc,YAAA,EAAa,EAAA,SAAA,CAAS,CAA0B,EAAI,OACrD,EAEpC,EAAS,EAAO,WAEtB,EAAA,UAAU,EAAQ,CAAa,EAE/B,IAAM,EAAY,SAAS,cAAc,UAAU,EAEnD,EAAO,aAAa,EAAW,EAAO,WAAW,EAEjD,IAAM,EAAO,EAAA,sBAAsB,EAE7B,GAAA,EAAM,EAAA,OAAA,KAAgB,CAC1B,IAAM,EAAO,EAAgB,MAE7B,EAAK,MAAM,EAEX,IAAM,EAAS,EAAO,EAAO,EAAI,EAAQ,EAAM,EAAI,KAE/C,CAAC,GAAU,CAAC,EAAA,aAAa,CAAM,GAEnC,EAAK,UAAA,EAAS,EAAA,QAAA,KAAc,EAAO,MAAM,EAAQ,EAAW,EAAK,eAAe,CAAC,CAAC,CACpF,CAAC,EAED,MAAsB,EAAI,QAAQ,CAAC,EACnC,MAAsB,CACpB,EAAK,MAAM,EACX,EAAU,OAAO,CACnB,CAAC,CACH,CAAC,CACH"}
1
+ {"version":3,"file":"when.cjs","names":[],"sources":["../../src/directives/when.ts"],"sourcesContent":["import { computed, isReactive, type Readable, effect as rawEffect, untrack } from '@vielzeug/ripple';\n\nimport { invariant } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult, isHtmlResult } from '../template/result';\nimport { createReplaceableSlot, removeNodes } from '../utils/dom';\n\ntype MaybeReactive<T> = T | (() => T) | Readable<T>;\n\ntype WhenRenderable = HTMLResult | null | undefined | false;\n\nconst NO_PARENT_MSG = 'when() anchor comment has no parent node';\n\n/**\n * Conditionally renders one of two branches as a `DirectiveResult`.\n *\n * The condition may be a boolean, signal, or getter function.\n * When the condition changes, the current branch is cleaned up and\n * the new branch is mounted in its place.\n *\n * @example\n * ```ts\n * html`${when(isLoggedIn, () => html`<p>Welcome</p>`, () => html`<p>Log in</p>`)}`\n * ```\n */\nexport function when(\n condition: MaybeReactive<boolean>,\n truthy: () => WhenRenderable,\n falsy?: () => WhenRenderable,\n): DirectiveResult {\n if (typeof condition !== 'function' && !isReactive(condition)) {\n return createDirectiveResult((anchor, registerCleanup) => {\n const branch = condition ? truthy() : falsy ? falsy() : null;\n\n if (!branch || !isHtmlResult(branch)) return;\n\n const parent = anchor.parentNode;\n\n invariant(parent, NO_PARENT_MSG);\n\n const nodes = branch.mount(parent, anchor, registerCleanup);\n\n registerCleanup(() => removeNodes(nodes));\n });\n }\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const ownedComputed = typeof condition === 'function' ? computed(condition as () => boolean) : null;\n const conditionSignal = ownedComputed ?? (condition as Readable<boolean>);\n\n const parent = anchor.parentNode;\n\n invariant(parent, NO_PARENT_MSG);\n\n const endMarker = document.createComment('when/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n const slot = createReplaceableSlot();\n\n const sub = rawEffect(() => {\n const next = conditionSignal.value;\n\n slot.clear();\n\n const branch = next ? truthy() : falsy ? falsy() : null;\n\n if (!branch || !isHtmlResult(branch)) return;\n\n slot.setNodes(untrack(() => branch.mount(parent, endMarker, slot.registerCleanup)));\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n slot.clear();\n endMarker.remove();\n });\n });\n}\n"],"mappings":"qIAUA,IAAM,EAAgB,2CActB,SAAgB,EACd,EACA,EACA,EACiB,CAiBjB,OAhBI,OAAO,GAAc,YAAc,EAAA,EAAC,EAAA,WAAA,CAAW,CAAS,EACnD,EAAA,uBAAuB,EAAQ,IAAoB,CACxD,IAAM,EAAS,EAAY,EAAO,EAAI,EAAQ,EAAM,EAAI,KAExD,GAAI,CAAC,GAAU,CAAC,EAAA,aAAa,CAAM,EAAG,OAEtC,IAAM,EAAS,EAAO,WAEtB,EAAA,UAAU,EAAQ,CAAa,EAE/B,IAAM,EAAQ,EAAO,MAAM,EAAQ,EAAQ,CAAe,EAE1D,MAAsB,EAAA,YAAY,CAAK,CAAC,CAC1C,CAAC,EAGI,EAAA,uBAAuB,EAAQ,IAAoB,CAExD,IAAM,GADgB,OAAO,GAAc,YAAA,EAAa,EAAA,SAAA,CAAS,CAA0B,EAAI,OACrD,EAEpC,EAAS,EAAO,WAEtB,EAAA,UAAU,EAAQ,CAAa,EAE/B,IAAM,EAAY,SAAS,cAAc,UAAU,EAEnD,EAAO,aAAa,EAAW,EAAO,WAAW,EAEjD,IAAM,EAAO,EAAA,sBAAsB,EAE7B,GAAA,EAAM,EAAA,OAAA,KAAgB,CAC1B,IAAM,EAAO,EAAgB,MAE7B,EAAK,MAAM,EAEX,IAAM,EAAS,EAAO,EAAO,EAAI,EAAQ,EAAM,EAAI,KAE9C,GAAW,EAAA,aAAa,CAAM,GAEnC,EAAK,UAAA,EAAS,EAAA,QAAA,KAAc,EAAO,MAAM,EAAQ,EAAW,EAAK,eAAe,CAAC,CAAC,CACpF,CAAC,EAED,MAAsB,EAAI,QAAQ,CAAC,EACnC,MAAsB,CACpB,EAAK,MAAM,EACX,EAAU,OAAO,CACnB,CAAC,CACH,CAAC,CACH"}
@@ -1,2 +1,2 @@
1
- import{invariant as e}from"../errors.js";import{createReplaceableSlot as t,removeNodes as n}from"../utils/dom.js";import{createDirectiveResult as r,isHtmlResult as i}from"../template/result.js";import{computed as a,effect as o,isReactive as s,untrack as c}from"@vielzeug/ripple";var l=`when() anchor comment has no parent node`;function u(u,d,f){return typeof u!=`function`&&!s(u)?r((t,r)=>{let a=u?d():f?f():null;if(!a||!i(a))return;let o=t.parentNode;e(o,l);let s=a.mount(o,t,r);r(()=>n(s))}):r((n,r)=>{let s=(typeof u==`function`?a(u):null)??u,p=n.parentNode;e(p,l);let m=document.createComment(`when/end`);p.insertBefore(m,n.nextSibling);let h=t(),g=o(()=>{let e=s.value;h.clear();let t=e?d():f?f():null;!t||!i(t)||h.setNodes(c(()=>t.mount(p,m,h.registerCleanup)))});r(()=>g.dispose()),r(()=>{h.clear(),m.remove()})})}export{u as when};
1
+ import{invariant as e}from"../errors.js";import{createReplaceableSlot as t,removeNodes as n}from"../utils/dom.js";import{createDirectiveResult as r,isHtmlResult as i}from"../template/result.js";import{computed as a,effect as o,isReactive as s,untrack as c}from"@vielzeug/ripple";var l=`when() anchor comment has no parent node`;function u(u,d,f){return typeof u!=`function`&&!s(u)?r((t,r)=>{let a=u?d():f?f():null;if(!a||!i(a))return;let o=t.parentNode;e(o,l);let s=a.mount(o,t,r);r(()=>n(s))}):r((n,r)=>{let s=(typeof u==`function`?a(u):null)??u,p=n.parentNode;e(p,l);let m=document.createComment(`when/end`);p.insertBefore(m,n.nextSibling);let h=t(),g=o(()=>{let e=s.value;h.clear();let t=e?d():f?f():null;t&&i(t)&&h.setNodes(c(()=>t.mount(p,m,h.registerCleanup)))});r(()=>g.dispose()),r(()=>{h.clear(),m.remove()})})}export{u as when};
2
2
  //# sourceMappingURL=when.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"when.js","names":[],"sources":["../../src/directives/when.ts"],"sourcesContent":["import { computed, isReactive, type Readable, effect as rawEffect, untrack } from '@vielzeug/ripple';\n\nimport { invariant } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult, isHtmlResult } from '../template/result';\nimport { createReplaceableSlot, removeNodes } from '../utils/dom';\n\ntype MaybeReactive<T> = T | (() => T) | Readable<T>;\n\ntype WhenRenderable = HTMLResult | null | undefined | false;\n\nconst NO_PARENT_MSG = 'when() anchor comment has no parent node';\n\n/**\n * Conditionally renders one of two branches as a `DirectiveResult`.\n *\n * The condition may be a boolean, signal, or getter function.\n * When the condition changes, the current branch is cleaned up and\n * the new branch is mounted in its place.\n *\n * @example\n * ```ts\n * html`${when(isLoggedIn, () => html`<p>Welcome</p>`, () => html`<p>Log in</p>`)}`\n * ```\n */\nexport function when(\n condition: MaybeReactive<boolean>,\n truthy: () => WhenRenderable,\n falsy?: () => WhenRenderable,\n): DirectiveResult {\n if (typeof condition !== 'function' && !isReactive(condition)) {\n return createDirectiveResult((anchor, registerCleanup) => {\n const branch = condition ? truthy() : falsy ? falsy() : null;\n\n if (!branch || !isHtmlResult(branch)) return;\n\n const parent = anchor.parentNode;\n\n invariant(parent, NO_PARENT_MSG);\n\n const nodes = branch.mount(parent, anchor, registerCleanup);\n\n registerCleanup(() => removeNodes(nodes));\n });\n }\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const ownedComputed = typeof condition === 'function' ? computed(condition as () => boolean) : null;\n const conditionSignal = ownedComputed ?? (condition as Readable<boolean>);\n\n const parent = anchor.parentNode;\n\n invariant(parent, NO_PARENT_MSG);\n\n const endMarker = document.createComment('when/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n const slot = createReplaceableSlot();\n\n const sub = rawEffect(() => {\n const next = conditionSignal.value;\n\n slot.clear();\n\n const branch = next ? truthy() : falsy ? falsy() : null;\n\n if (!branch || !isHtmlResult(branch)) return;\n\n slot.setNodes(untrack(() => branch.mount(parent, endMarker, slot.registerCleanup)));\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n slot.clear();\n endMarker.remove();\n });\n });\n}\n"],"mappings":"uRAUA,IAAM,EAAgB,2CActB,SAAgB,EACd,EACA,EACA,EACiB,CAiBjB,OAhBI,OAAO,GAAc,YAAc,CAAC,EAAW,CAAS,EACnD,GAAuB,EAAQ,IAAoB,CACxD,IAAM,EAAS,EAAY,EAAO,EAAI,EAAQ,EAAM,EAAI,KAExD,GAAI,CAAC,GAAU,CAAC,EAAa,CAAM,EAAG,OAEtC,IAAM,EAAS,EAAO,WAEtB,EAAU,EAAQ,CAAa,EAE/B,IAAM,EAAQ,EAAO,MAAM,EAAQ,EAAQ,CAAe,EAE1D,MAAsB,EAAY,CAAK,CAAC,CAC1C,CAAC,EAGI,GAAuB,EAAQ,IAAoB,CAExD,IAAM,GADgB,OAAO,GAAc,WAAa,EAAS,CAA0B,EAAI,OACrD,EAEpC,EAAS,EAAO,WAEtB,EAAU,EAAQ,CAAa,EAE/B,IAAM,EAAY,SAAS,cAAc,UAAU,EAEnD,EAAO,aAAa,EAAW,EAAO,WAAW,EAEjD,IAAM,EAAO,EAAsB,EAE7B,EAAM,MAAgB,CAC1B,IAAM,EAAO,EAAgB,MAE7B,EAAK,MAAM,EAEX,IAAM,EAAS,EAAO,EAAO,EAAI,EAAQ,EAAM,EAAI,KAE/C,CAAC,GAAU,CAAC,EAAa,CAAM,GAEnC,EAAK,SAAS,MAAc,EAAO,MAAM,EAAQ,EAAW,EAAK,eAAe,CAAC,CAAC,CACpF,CAAC,EAED,MAAsB,EAAI,QAAQ,CAAC,EACnC,MAAsB,CACpB,EAAK,MAAM,EACX,EAAU,OAAO,CACnB,CAAC,CACH,CAAC,CACH"}
1
+ {"version":3,"file":"when.js","names":[],"sources":["../../src/directives/when.ts"],"sourcesContent":["import { computed, isReactive, type Readable, effect as rawEffect, untrack } from '@vielzeug/ripple';\n\nimport { invariant } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult, isHtmlResult } from '../template/result';\nimport { createReplaceableSlot, removeNodes } from '../utils/dom';\n\ntype MaybeReactive<T> = T | (() => T) | Readable<T>;\n\ntype WhenRenderable = HTMLResult | null | undefined | false;\n\nconst NO_PARENT_MSG = 'when() anchor comment has no parent node';\n\n/**\n * Conditionally renders one of two branches as a `DirectiveResult`.\n *\n * The condition may be a boolean, signal, or getter function.\n * When the condition changes, the current branch is cleaned up and\n * the new branch is mounted in its place.\n *\n * @example\n * ```ts\n * html`${when(isLoggedIn, () => html`<p>Welcome</p>`, () => html`<p>Log in</p>`)}`\n * ```\n */\nexport function when(\n condition: MaybeReactive<boolean>,\n truthy: () => WhenRenderable,\n falsy?: () => WhenRenderable,\n): DirectiveResult {\n if (typeof condition !== 'function' && !isReactive(condition)) {\n return createDirectiveResult((anchor, registerCleanup) => {\n const branch = condition ? truthy() : falsy ? falsy() : null;\n\n if (!branch || !isHtmlResult(branch)) return;\n\n const parent = anchor.parentNode;\n\n invariant(parent, NO_PARENT_MSG);\n\n const nodes = branch.mount(parent, anchor, registerCleanup);\n\n registerCleanup(() => removeNodes(nodes));\n });\n }\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const ownedComputed = typeof condition === 'function' ? computed(condition as () => boolean) : null;\n const conditionSignal = ownedComputed ?? (condition as Readable<boolean>);\n\n const parent = anchor.parentNode;\n\n invariant(parent, NO_PARENT_MSG);\n\n const endMarker = document.createComment('when/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n const slot = createReplaceableSlot();\n\n const sub = rawEffect(() => {\n const next = conditionSignal.value;\n\n slot.clear();\n\n const branch = next ? truthy() : falsy ? falsy() : null;\n\n if (!branch || !isHtmlResult(branch)) return;\n\n slot.setNodes(untrack(() => branch.mount(parent, endMarker, slot.registerCleanup)));\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n slot.clear();\n endMarker.remove();\n });\n });\n}\n"],"mappings":"uRAUA,IAAM,EAAgB,2CActB,SAAgB,EACd,EACA,EACA,EACiB,CAiBjB,OAhBI,OAAO,GAAc,YAAc,CAAC,EAAW,CAAS,EACnD,GAAuB,EAAQ,IAAoB,CACxD,IAAM,EAAS,EAAY,EAAO,EAAI,EAAQ,EAAM,EAAI,KAExD,GAAI,CAAC,GAAU,CAAC,EAAa,CAAM,EAAG,OAEtC,IAAM,EAAS,EAAO,WAEtB,EAAU,EAAQ,CAAa,EAE/B,IAAM,EAAQ,EAAO,MAAM,EAAQ,EAAQ,CAAe,EAE1D,MAAsB,EAAY,CAAK,CAAC,CAC1C,CAAC,EAGI,GAAuB,EAAQ,IAAoB,CAExD,IAAM,GADgB,OAAO,GAAc,WAAa,EAAS,CAA0B,EAAI,OACrD,EAEpC,EAAS,EAAO,WAEtB,EAAU,EAAQ,CAAa,EAE/B,IAAM,EAAY,SAAS,cAAc,UAAU,EAEnD,EAAO,aAAa,EAAW,EAAO,WAAW,EAEjD,IAAM,EAAO,EAAsB,EAE7B,EAAM,MAAgB,CAC1B,IAAM,EAAO,EAAgB,MAE7B,EAAK,MAAM,EAEX,IAAM,EAAS,EAAO,EAAO,EAAI,EAAQ,EAAM,EAAI,KAE9C,GAAW,EAAa,CAAM,GAEnC,EAAK,SAAS,MAAc,EAAO,MAAM,EAAQ,EAAW,EAAK,eAAe,CAAC,CAAC,CACpF,CAAC,EAED,MAAsB,EAAI,QAAQ,CAAC,EACnC,MAAsB,CACpB,EAAK,MAAM,EACX,EAAU,OAAO,CACnB,CAAC,CACH,CAAC,CACH"}
@@ -1,2 +1,2 @@
1
- require("../_dev.cjs");const e=require("../errors.cjs"),t=require("../runtime.cjs");var n=new WeakMap,r=new WeakSet,i=i=>{let a=i.el??t.getHost();if(!a.constructor.formAssociated)throw new e.OreApiError(e.ORE_ERRORS.defineFieldRequiresFormAssociated(a.localName));if(r.has(a))throw new e.OreApiError(e.ORE_ERRORS.useFieldAlreadyCalled(a.localName));let o=n.get(a)??a.attachInternals();n.set(a,o),r.add(a),t.onCleanup(()=>r.delete(a));let s=i.toFormValue??(e=>e==null?i.emptyStringForNull?``:null:e instanceof File||e instanceof FormData?e:String(e));t.watchEffect(()=>{o.setFormValue(s(i.value.value))});let c=i.disabled;if(c&&`states`in o){let e=o.states;t.watchEffect(()=>{c.value?e.add(`disabled`):e.delete(`disabled`)})}return i.validity&&t.watchEffect(()=>{let e=i.validity?.value??{},t=Object.values(e).some(Boolean),n=i.validationMessage?.value??``;if(t&&!n){o.setValidity(e,`Invalid value.`);return}o.setValidity(e,n)}),i.onReset&&t.onFormReset(i.onReset),{checkValidity:()=>o.checkValidity(),internals:o,reportValidity:()=>o.reportValidity(),setCustomValidity:e=>e?o.setValidity({customError:!0},e):o.setValidity({})}};exports.useField=i;
1
+ require("../_dev.cjs");const e=require("../errors.cjs"),t=require("../runtime.cjs");var n=new WeakMap,r=new WeakSet,i=i=>{let a=t.getHost();if(!a.constructor.formAssociated)throw new e.OreApiError(e.ORE_ERRORS.defineFieldRequiresFormAssociated(a.localName));if(r.has(a))throw new e.OreApiError(e.ORE_ERRORS.useFieldAlreadyCalled(a.localName));let o=n.get(a)??a.attachInternals();n.set(a,o),r.add(a),t.onCleanup(()=>r.delete(a));let s=i.toFormValue??(e=>e==null?i.emptyStringForNull?``:null:e instanceof File||e instanceof FormData?e:String(e));t.watchEffect(()=>{o.setFormValue(s(i.value.value))});let c=i.disabled;if(c&&`states`in o){let e=o.states;t.watchEffect(()=>{c.value?e.add(`disabled`):e.delete(`disabled`)})}return i.validity&&t.watchEffect(()=>{let e=i.validity?.value??{},t=Object.values(e).some(Boolean),n=i.validationMessage?.value??``;if(t&&!n){o.setValidity(e,`Invalid value.`);return}o.setValidity(e,n)}),i.onReset&&t.onFormReset(i.onReset),{checkValidity:()=>o.checkValidity(),internals:o,reportValidity:()=>o.reportValidity(),setCustomValidity:e=>e?o.setValidity({customError:!0},e):o.setValidity({})}};exports.useField=i;
2
2
  //# sourceMappingURL=field.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"field.cjs","names":[],"sources":["../../src/forms/field.ts"],"sourcesContent":["import type { Readable, Signal } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\nimport { ORE_ERRORS, OreApiError } from '../errors';\nimport { getHost, onCleanup, onFormReset, watchEffect } from '../runtime';\n\n/** @internal */\nconst internalsRegistry = new WeakMap<HTMLElement, ElementInternals>();\n/** @internal */\nconst activeFieldRegistry = new WeakSet<HTMLElement>();\n\nexport type FormFieldOptions<T = unknown> = {\n disabled?: Readable<boolean>;\n /**\n * The host element to attach `ElementInternals` to.\n * Defaults to the element currently being set up via `getHost()`.\n * Pass this explicitly when calling `useField` from a composable that is\n * not called directly during `setup()` (e.g. a helper factory function).\n */\n el?: HTMLElement;\n /**\n * When `true`, a `null` or `undefined` value is submitted as an empty string\n * (`''`) instead of `null`. This keeps the field's key present in `FormData`\n * even when the value is absent — useful when the server expects the field to\n * always be included.\n *\n * @default false\n */\n emptyStringForNull?: boolean;\n /**\n * Called when the ancestor `<form>` is reset (see `onFormReset`). Use to restore\n * whatever local state backs `value` — `useField` itself owns no field state to reset.\n */\n onReset?: () => void;\n toFormValue?: (value: T) => File | FormData | string | null;\n /**\n * Recomputed reactively and passed straight to `internals.setValidity()` — `null`\n * (or omitting `validity` entirely) means always valid. Pair with `validationMessage`.\n *\n * @example\n * ```ts\n * validity: computed(() => (required.value && isBlank(value.value)) ? { valueMissing: true } : null),\n * validationMessage: computed(() => (required.value && isBlank(value.value)) ? 'Required.' : ''),\n * ```\n */\n validationMessage?: Readable<string>;\n validity?: Readable<ValidityStateFlags | null>;\n value: Signal<T> | Readable<T>;\n};\n\nexport type FormFieldHandle = {\n checkValidity: () => boolean;\n readonly internals: ElementInternals;\n reportValidity: () => boolean;\n /**\n * Set (non-empty message) or clear (empty string) a custom validity error.\n * Guards the platform's throw-on-empty-message contract — prefer this over\n * calling `internals.setValidity` directly.\n */\n setCustomValidity: (message: string) => void;\n};\n\nexport const useField = <T = unknown>(options: FormFieldOptions<T>): FormFieldHandle => {\n const host = options.el ?? getHost();\n const ctor = host.constructor as typeof HTMLElement & { formAssociated?: boolean };\n\n if (!ctor.formAssociated) {\n throw new OreApiError(ORE_ERRORS.defineFieldRequiresFormAssociated(host.localName));\n }\n\n if (activeFieldRegistry.has(host)) {\n throw new OreApiError(ORE_ERRORS.useFieldAlreadyCalled(host.localName));\n }\n\n // ElementInternals may only be attached once for an element's lifetime, while setup\n // legitimately runs again when a custom element is reconnected.\n const internals = internalsRegistry.get(host) ?? host.attachInternals();\n\n internalsRegistry.set(host, internals);\n activeFieldRegistry.add(host);\n onCleanup(() => activeFieldRegistry.delete(host));\n\n const toFormValue =\n options.toFormValue ??\n ((v: T): File | FormData | string | null => {\n if (v == null) return options.emptyStringForNull ? '' : null;\n\n if (v instanceof File || v instanceof FormData) return v;\n\n return String(v);\n });\n\n watchEffect(() => {\n internals.setFormValue(toFormValue(options.value.value));\n });\n\n const disabled = options.disabled;\n\n if (disabled) {\n if (!('states' in internals)) {\n warn(\n 'useField(): ElementInternals.states (CustomStateSet) is not available in this environment — disabled state tracking skipped.',\n );\n } else {\n const states = internals.states as CustomStateSet;\n\n watchEffect(() => {\n if (disabled.value) states.add('disabled');\n else states.delete('disabled');\n });\n }\n }\n\n if (options.validity) {\n watchEffect(() => {\n const flags = options.validity?.value ?? {};\n // Per spec, ElementInternals.setValidity() throws if any flag is true and message is\n // empty — a caller-supplied `validity` with no matching `validationMessage` would crash\n // this effect in a real browser. Fail safe with a generic message instead of propagating\n // that crash, and say so loudly in dev so the real fix (pass validationMessage) gets made.\n const hasFlag = Object.values(flags).some(Boolean);\n const message = options.validationMessage?.value ?? '';\n\n if (hasFlag && !message) {\n warn(\n 'useField(): `validity` has a truthy flag but `validationMessage` is empty — internals.setValidity() ' +\n 'requires a non-empty message whenever any flag is true. Falling back to a generic message; pass ' +\n '`validationMessage` to customize it.',\n );\n internals.setValidity(flags, 'Invalid value.');\n\n return;\n }\n\n internals.setValidity(flags, message);\n });\n }\n\n if (options.onReset) onFormReset(options.onReset);\n\n const checkValidity = () => internals.checkValidity();\n const reportValidity = () => internals.reportValidity();\n const setCustomValidity = (message: string) =>\n message ? internals.setValidity({ customError: true }, message) : internals.setValidity({});\n\n return {\n checkValidity,\n internals,\n reportValidity,\n setCustomValidity,\n };\n};\n"],"mappings":"oFAOA,IAAM,EAAoB,IAAI,QAExB,EAAsB,IAAI,QAqDnB,EAAyB,GAAkD,CACtF,IAAM,EAAO,EAAQ,IAAM,EAAA,QAAQ,EAGnC,GAAI,CAFS,EAAK,YAER,eACR,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,kCAAkC,EAAK,SAAS,CAAC,EAGpF,GAAI,EAAoB,IAAI,CAAI,EAC9B,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,sBAAsB,EAAK,SAAS,CAAC,EAKxE,IAAM,EAAY,EAAkB,IAAI,CAAI,GAAK,EAAK,gBAAgB,EAEtE,EAAkB,IAAI,EAAM,CAAS,EACrC,EAAoB,IAAI,CAAI,EAC5B,EAAA,cAAgB,EAAoB,OAAO,CAAI,CAAC,EAEhD,IAAM,EACJ,EAAQ,cACN,GACI,GAAK,KAAa,EAAQ,mBAAqB,GAAK,KAEpD,aAAa,MAAQ,aAAa,SAAiB,EAEhD,OAAO,CAAC,GAGnB,EAAA,gBAAkB,CAChB,EAAU,aAAa,EAAY,EAAQ,MAAM,KAAK,CAAC,CACzD,CAAC,EAED,IAAM,EAAW,EAAQ,SAEzB,GAAI,GACI,WAAY,EAIX,CACL,IAAM,EAAS,EAAU,OAEzB,EAAA,gBAAkB,CACZ,EAAS,MAAO,EAAO,IAAI,UAAU,EACpC,EAAO,OAAO,UAAU,CAC/B,CAAC,CACH,CAmCF,OAhCI,EAAQ,UACV,EAAA,gBAAkB,CAChB,IAAM,EAAQ,EAAQ,UAAU,OAAS,CAAC,EAKpC,EAAU,OAAO,OAAO,CAAK,CAAC,CAAC,KAAK,OAAO,EAC3C,EAAU,EAAQ,mBAAmB,OAAS,GAEpD,GAAI,GAAW,CAAC,EAAS,CAMvB,EAAU,YAAY,EAAO,gBAAgB,EAE7C,MACF,CAEA,EAAU,YAAY,EAAO,CAAO,CACtC,CAAC,EAGC,EAAQ,SAAS,EAAA,YAAY,EAAQ,OAAO,EAOzC,CACL,kBAN0B,EAAU,cAAc,EAOlD,YACA,mBAP2B,EAAU,eAAe,EAQpD,kBAPyB,GACzB,EAAU,EAAU,YAAY,CAAE,YAAa,EAAK,EAAG,CAAO,EAAI,EAAU,YAAY,CAAC,CAAC,CAO5F,CACF"}
1
+ {"version":3,"file":"field.cjs","names":[],"sources":["../../src/forms/field.ts"],"sourcesContent":["import type { Readable, Signal } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\nimport { ORE_ERRORS, OreApiError } from '../errors';\nimport { getHost, onCleanup, onFormReset, watchEffect } from '../runtime';\n\n/** @internal */\nconst internalsRegistry = new WeakMap<HTMLElement, ElementInternals>();\n/** @internal */\nconst activeFieldRegistry = new WeakSet<HTMLElement>();\n\nexport type FormFieldOptions<T = unknown> = {\n disabled?: Readable<boolean>;\n /**\n * When `true`, a `null` or `undefined` value is submitted as an empty string\n * (`''`) instead of `null`. This keeps the field's key present in `FormData`\n * even when the value is absent — useful when the server expects the field to\n * always be included.\n *\n * @default false\n */\n emptyStringForNull?: boolean;\n /**\n * Called when the ancestor `<form>` is reset (see `onFormReset`). Use to restore\n * whatever local state backs `value` — `useField` itself owns no field state to reset.\n */\n onReset?: () => void;\n toFormValue?: (value: T) => File | FormData | string | null;\n /**\n * Recomputed reactively and passed straight to `internals.setValidity()` — `null`\n * (or omitting `validity` entirely) means always valid. Pair with `validationMessage`.\n *\n * @example\n * ```ts\n * validity: computed(() => (required.value && isBlank(value.value)) ? { valueMissing: true } : null),\n * validationMessage: computed(() => (required.value && isBlank(value.value)) ? 'Required.' : ''),\n * ```\n */\n validationMessage?: Readable<string>;\n validity?: Readable<ValidityStateFlags | null>;\n value: Signal<T> | Readable<T>;\n};\n\nexport type FormFieldHandle = {\n checkValidity: () => boolean;\n readonly internals: ElementInternals;\n reportValidity: () => boolean;\n /**\n * Set (non-empty message) or clear (empty string) a custom validity error.\n * Guards the platform's throw-on-empty-message contract — prefer this over\n * calling `internals.setValidity` directly.\n */\n setCustomValidity: (message: string) => void;\n};\n\nexport const useField = <T = unknown>(options: FormFieldOptions<T>): FormFieldHandle => {\n const host = getHost();\n const ctor = host.constructor as typeof HTMLElement & { formAssociated?: boolean };\n\n if (!ctor.formAssociated) {\n throw new OreApiError(ORE_ERRORS.defineFieldRequiresFormAssociated(host.localName));\n }\n\n if (activeFieldRegistry.has(host)) {\n throw new OreApiError(ORE_ERRORS.useFieldAlreadyCalled(host.localName));\n }\n\n // ElementInternals may only be attached once for an element's lifetime, while setup\n // legitimately runs again when a custom element is reconnected.\n const internals = internalsRegistry.get(host) ?? host.attachInternals();\n\n internalsRegistry.set(host, internals);\n activeFieldRegistry.add(host);\n onCleanup(() => activeFieldRegistry.delete(host));\n\n const toFormValue =\n options.toFormValue ??\n ((v: T): File | FormData | string | null => {\n if (v == null) return options.emptyStringForNull ? '' : null;\n\n if (v instanceof File || v instanceof FormData) return v;\n\n return String(v);\n });\n\n watchEffect(() => {\n internals.setFormValue(toFormValue(options.value.value));\n });\n\n const disabled = options.disabled;\n\n if (disabled) {\n if (!('states' in internals)) {\n warn(\n 'useField(): ElementInternals.states (CustomStateSet) is not available in this environment — disabled state tracking skipped.',\n );\n } else {\n const states = internals.states as CustomStateSet;\n\n watchEffect(() => {\n if (disabled.value) states.add('disabled');\n else states.delete('disabled');\n });\n }\n }\n\n if (options.validity) {\n watchEffect(() => {\n const flags = options.validity?.value ?? {};\n // Per spec, ElementInternals.setValidity() throws if any flag is true and message is\n // empty — a caller-supplied `validity` with no matching `validationMessage` would crash\n // this effect in a real browser. Fail safe with a generic message instead of propagating\n // that crash, and say so loudly in dev so the real fix (pass validationMessage) gets made.\n const hasFlag = Object.values(flags).some(Boolean);\n const message = options.validationMessage?.value ?? '';\n\n if (hasFlag && !message) {\n warn(\n 'useField(): `validity` has a truthy flag but `validationMessage` is empty — internals.setValidity() ' +\n 'requires a non-empty message whenever any flag is true. Falling back to a generic message; pass ' +\n '`validationMessage` to customize it.',\n );\n internals.setValidity(flags, 'Invalid value.');\n\n return;\n }\n\n internals.setValidity(flags, message);\n });\n }\n\n if (options.onReset) onFormReset(options.onReset);\n\n const checkValidity = () => internals.checkValidity();\n const reportValidity = () => internals.reportValidity();\n const setCustomValidity = (message: string) =>\n message ? internals.setValidity({ customError: true }, message) : internals.setValidity({});\n\n return {\n checkValidity,\n internals,\n reportValidity,\n setCustomValidity,\n };\n};\n"],"mappings":"oFAOA,IAAM,EAAoB,IAAI,QAExB,EAAsB,IAAI,QA8CnB,EAAyB,GAAkD,CACtF,IAAM,EAAO,EAAA,QAAQ,EAGrB,GAAI,CAFS,EAAK,YAER,eACR,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,kCAAkC,EAAK,SAAS,CAAC,EAGpF,GAAI,EAAoB,IAAI,CAAI,EAC9B,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,sBAAsB,EAAK,SAAS,CAAC,EAKxE,IAAM,EAAY,EAAkB,IAAI,CAAI,GAAK,EAAK,gBAAgB,EAEtE,EAAkB,IAAI,EAAM,CAAS,EACrC,EAAoB,IAAI,CAAI,EAC5B,EAAA,cAAgB,EAAoB,OAAO,CAAI,CAAC,EAEhD,IAAM,EACJ,EAAQ,cACN,GACI,GAAK,KAAa,EAAQ,mBAAqB,GAAK,KAEpD,aAAa,MAAQ,aAAa,SAAiB,EAEhD,OAAO,CAAC,GAGnB,EAAA,gBAAkB,CAChB,EAAU,aAAa,EAAY,EAAQ,MAAM,KAAK,CAAC,CACzD,CAAC,EAED,IAAM,EAAW,EAAQ,SAEzB,GAAI,GACI,WAAY,EAIX,CACL,IAAM,EAAS,EAAU,OAEzB,EAAA,gBAAkB,CACZ,EAAS,MAAO,EAAO,IAAI,UAAU,EACpC,EAAO,OAAO,UAAU,CAC/B,CAAC,CACH,CAmCF,OAhCI,EAAQ,UACV,EAAA,gBAAkB,CAChB,IAAM,EAAQ,EAAQ,UAAU,OAAS,CAAC,EAKpC,EAAU,OAAO,OAAO,CAAK,CAAC,CAAC,KAAK,OAAO,EAC3C,EAAU,EAAQ,mBAAmB,OAAS,GAEpD,GAAI,GAAW,CAAC,EAAS,CAMvB,EAAU,YAAY,EAAO,gBAAgB,EAE7C,MACF,CAEA,EAAU,YAAY,EAAO,CAAO,CACtC,CAAC,EAGC,EAAQ,SAAS,EAAA,YAAY,EAAQ,OAAO,EAOzC,CACL,kBAN0B,EAAU,cAAc,EAOlD,YACA,mBAP2B,EAAU,eAAe,EAQpD,kBAPyB,GACzB,EAAU,EAAU,YAAY,CAAE,YAAa,EAAK,EAAG,CAAO,EAAI,EAAU,YAAY,CAAC,CAAC,CAO5F,CACF"}
@@ -1,13 +1,6 @@
1
1
  import type { Readable, Signal } from '@vielzeug/ripple';
2
2
  export type FormFieldOptions<T = unknown> = {
3
3
  disabled?: Readable<boolean>;
4
- /**
5
- * The host element to attach `ElementInternals` to.
6
- * Defaults to the element currently being set up via `getHost()`.
7
- * Pass this explicitly when calling `useField` from a composable that is
8
- * not called directly during `setup()` (e.g. a helper factory function).
9
- */
10
- el?: HTMLElement;
11
4
  /**
12
5
  * When `true`, a `null` or `undefined` value is submitted as an empty string
13
6
  * (`''`) instead of `null`. This keeps the field's key present in `FormData`
@@ -1 +1 @@
1
- {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/forms/field.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAWzD,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG,OAAO,IAAI;IAC1C,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,EAAE,CAAC,EAAE,WAAW,CAAC;IACjB;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5D;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,QAAQ,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAC/C,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC;IAC9B;;;;OAIG;IACH,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,CAAC,GAAG,OAAO,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,KAAG,eAyFpE,CAAC"}
1
+ {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/forms/field.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAWzD,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG,OAAO,IAAI;IAC1C,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC7B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5D;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,QAAQ,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAC/C,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC;IAC9B;;;;OAIG;IACH,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,CAAC,GAAG,OAAO,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,KAAG,eAyFpE,CAAC"}
@@ -1,2 +1,2 @@
1
- import"../_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"../errors.js";import{getHost as n,onCleanup as r,onFormReset as i,watchEffect as a}from"../runtime.js";var o=new WeakMap,s=new WeakSet,c=c=>{let l=c.el??n();if(!l.constructor.formAssociated)throw new t(e.defineFieldRequiresFormAssociated(l.localName));if(s.has(l))throw new t(e.useFieldAlreadyCalled(l.localName));let u=o.get(l)??l.attachInternals();o.set(l,u),s.add(l),r(()=>s.delete(l));let d=c.toFormValue??(e=>e==null?c.emptyStringForNull?``:null:e instanceof File||e instanceof FormData?e:String(e));a(()=>{u.setFormValue(d(c.value.value))});let f=c.disabled;if(f&&`states`in u){let e=u.states;a(()=>{f.value?e.add(`disabled`):e.delete(`disabled`)})}return c.validity&&a(()=>{let e=c.validity?.value??{},t=Object.values(e).some(Boolean),n=c.validationMessage?.value??``;if(t&&!n){u.setValidity(e,`Invalid value.`);return}u.setValidity(e,n)}),c.onReset&&i(c.onReset),{checkValidity:()=>u.checkValidity(),internals:u,reportValidity:()=>u.reportValidity(),setCustomValidity:e=>e?u.setValidity({customError:!0},e):u.setValidity({})}};export{c as useField};
1
+ import"../_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"../errors.js";import{getHost as n,onCleanup as r,onFormReset as i,watchEffect as a}from"../runtime.js";var o=new WeakMap,s=new WeakSet,c=c=>{let l=n();if(!l.constructor.formAssociated)throw new t(e.defineFieldRequiresFormAssociated(l.localName));if(s.has(l))throw new t(e.useFieldAlreadyCalled(l.localName));let u=o.get(l)??l.attachInternals();o.set(l,u),s.add(l),r(()=>s.delete(l));let d=c.toFormValue??(e=>e==null?c.emptyStringForNull?``:null:e instanceof File||e instanceof FormData?e:String(e));a(()=>{u.setFormValue(d(c.value.value))});let f=c.disabled;if(f&&`states`in u){let e=u.states;a(()=>{f.value?e.add(`disabled`):e.delete(`disabled`)})}return c.validity&&a(()=>{let e=c.validity?.value??{},t=Object.values(e).some(Boolean),n=c.validationMessage?.value??``;if(t&&!n){u.setValidity(e,`Invalid value.`);return}u.setValidity(e,n)}),c.onReset&&i(c.onReset),{checkValidity:()=>u.checkValidity(),internals:u,reportValidity:()=>u.reportValidity(),setCustomValidity:e=>e?u.setValidity({customError:!0},e):u.setValidity({})}};export{c as useField};
2
2
  //# sourceMappingURL=field.js.map