assign-gingerly 0.0.53 → 0.0.55

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 (114) hide show
  1. package/README.md +847 -5
  2. package/assignFrom.js +229 -9
  3. package/assignFrom.ts +338 -9
  4. package/assignGingerly.js +34 -1
  5. package/assignGingerly.ts +54 -1
  6. package/beVigilant.js +73 -0
  7. package/beVigilant.ts +85 -0
  8. package/enhanceAll.js +106 -0
  9. package/enhanceAll.ts +138 -0
  10. package/handlers/join.js +74 -0
  11. package/handlers/join.ts +80 -0
  12. package/handlers/lazyLoad.js +212 -0
  13. package/handlers/lazyLoad.ts +307 -0
  14. package/handlers/lazyLoadSwitch.js +58 -0
  15. package/handlers/lazyLoadSwitch.ts +63 -0
  16. package/handlers/microDataJoin.js +184 -0
  17. package/handlers/microDataJoin.ts +270 -0
  18. package/inferencer/.gitmodules +3 -0
  19. package/inferencer/.vscode/settings.json +2 -0
  20. package/inferencer/InferencedPropagator.js +230 -0
  21. package/inferencer/InferencedPropagator.ts +269 -0
  22. package/inferencer/LICENSE +21 -0
  23. package/inferencer/README.md +524 -0
  24. package/inferencer/Requirements/SupportForPropagator.md +368 -0
  25. package/inferencer/imports.html +7 -0
  26. package/inferencer/inferencer.js +254 -0
  27. package/inferencer/inferencer.ts +292 -0
  28. package/inferencer/package-lock.json +129 -0
  29. package/inferencer/package.json +60 -0
  30. package/inferencer/playwright-report/data/507ad515125e13390ea07de92f22331c913fa068.md +55 -0
  31. package/inferencer/playwright-report/index.html +90 -0
  32. package/inferencer/playwright.config.ts +54 -0
  33. package/inferencer/test-results/.last-run.json +6 -0
  34. package/inferencer/test-results/inferencer-Inferencer-Enha-535bc-inferencer-tests-in-browser-chromium/error-context.md +55 -0
  35. package/inferencer/tests/inferencedPropagator.html +428 -0
  36. package/inferencer/tests/inferencedPropagator.spec.ts +18 -0
  37. package/inferencer/tests/inferencer.html +355 -0
  38. package/inferencer/tests/inferencer.spec.ts +19 -0
  39. package/inferencer/tsconfig.json +19 -0
  40. package/inferencer/types/.kiro/specs/conversion-template/README.md +128 -0
  41. package/inferencer/types/.kiro/specs/conversion-template/design.md +360 -0
  42. package/inferencer/types/.kiro/specs/conversion-template/requirements.md +191 -0
  43. package/inferencer/types/.kiro/specs/conversion-template/tasks.md +174 -0
  44. package/inferencer/types/.kiro/steering/coding-standards.md +53 -0
  45. package/inferencer/types/.kiro/steering/conversion-guide.md +108 -0
  46. package/inferencer/types/.kiro/steering/declarative-configuration.md +108 -0
  47. package/inferencer/types/.kiro/steering/emc-json-serializability.md +306 -0
  48. package/inferencer/types/EnhancementConversionInstructions.md +1854 -0
  49. package/inferencer/types/LICENSE +21 -0
  50. package/inferencer/types/NewCustomElement.md +388 -0
  51. package/inferencer/types/NewCustomElementFeature.md +683 -0
  52. package/inferencer/types/NewEnhancementInstructions.md +705 -0
  53. package/inferencer/types/README.md +2 -0
  54. package/inferencer/types/agrace/types.d.ts +11 -0
  55. package/inferencer/types/assign-gingerly/types.d.ts +572 -0
  56. package/inferencer/types/be-a-beacon/types.d.ts +17 -0
  57. package/inferencer/types/be-bound/types.d.ts +66 -0
  58. package/inferencer/types/be-buttoned-up/types.d.ts +19 -0
  59. package/inferencer/types/be-calculating/types.d.ts +54 -0
  60. package/inferencer/types/be-clonable/types.d.ts +38 -0
  61. package/inferencer/types/be-committed/types.d.ts +22 -0
  62. package/inferencer/types/be-consoling/types.d.ts +24 -0
  63. package/inferencer/types/be-decked-with/types.d.ts +26 -0
  64. package/inferencer/types/be-delible/types.d.ts +27 -0
  65. package/inferencer/types/be-dispatching/types.d.ts +34 -0
  66. package/inferencer/types/be-evanescent/types.d.ts +20 -0
  67. package/inferencer/types/be-flashy/types.d.ts +21 -0
  68. package/inferencer/types/be-gone/types.d.ts +25 -0
  69. package/inferencer/types/be-observing/types.d.ts +55 -0
  70. package/inferencer/types/be-reflective/types.d.ts +78 -0
  71. package/inferencer/types/be-reformable/types.d.ts +49 -0
  72. package/inferencer/types/be-render-neutral/types.d.ts +32 -0
  73. package/inferencer/types/be-switched/types.d.ts +146 -0
  74. package/inferencer/types/be-typed/types.d.ts +32 -0
  75. package/inferencer/types/be-valued/types.d.ts +22 -0
  76. package/inferencer/types/data-props/types.d.ts +34 -0
  77. package/inferencer/types/do-inc/types.d.ts +56 -0
  78. package/inferencer/types/do-invoke/types.d.ts +38 -0
  79. package/inferencer/types/do-merge/types.d.ts +28 -0
  80. package/inferencer/types/do-toggle/types.d.ts +31 -0
  81. package/inferencer/types/face-up/types.d.ts +100 -0
  82. package/inferencer/types/fetch-for/types.d.ts +36 -0
  83. package/inferencer/types/folder-picker/types.d.ts +21 -0
  84. package/inferencer/types/global.d.ts +29 -0
  85. package/inferencer/types/id-generation/types.d.ts +26 -0
  86. package/inferencer/types/inferencer/types.d.ts +46 -0
  87. package/inferencer/types/mount-observer/types.d.ts +363 -0
  88. package/inferencer/types/nested-regex-groups/types.d.ts +107 -0
  89. package/inferencer/types/pipe-in/types.d.ts +52 -0
  90. package/inferencer/types/roundabout/types.d.ts +268 -0
  91. package/inferencer/types/soak-up/types.d.ts +40 -0
  92. package/inferencer/types/templ-maker/types.d.ts +43 -0
  93. package/inferencer/types/time-ticker/types.d.ts +62 -0
  94. package/inferencer/types/truth-sourcer/types.d.ts +44 -0
  95. package/inferencer/upSearch.js +27 -0
  96. package/inferencer/upSearch.ts +26 -0
  97. package/inferencer/withScopePerimeter.js +27 -0
  98. package/inferencer/withScopePerimeter.ts +33 -0
  99. package/inferredAssignments.js +38 -0
  100. package/inferredAssignments.ts +65 -0
  101. package/isAllowedImportPath.js +42 -0
  102. package/isAllowedImportPath.ts +53 -0
  103. package/package.json +57 -3
  104. package/paths.js +231 -0
  105. package/paths.ts +413 -0
  106. package/processHandlerCommands.js +188 -0
  107. package/processHandlerCommands.ts +217 -0
  108. package/resolveIdRef.js +144 -0
  109. package/resolveIdRef.ts +170 -0
  110. package/resolveValues.js +41 -2
  111. package/resolveValues.ts +41 -1
  112. package/transitionHelper.js +109 -0
  113. package/transitionHelper.ts +132 -0
  114. package/types/assign-gingerly/types.d.ts +89 -0
@@ -0,0 +1,368 @@
1
+ # Support for Propagator
2
+
3
+ A common requirement for working with multiple DOM elements, built in and custom, is being able to subscribe to changes of a property.
4
+
5
+ <!-- IMPLEMENTATION THOUGHTS (Kiro):
6
+
7
+ The core challenge here is creating a unified event-based interface for property changes across heterogeneous element types. The PropagatorInferencer needs to act as an adapter/facade that normalizes different change detection mechanisms into a single EventTarget interface.
8
+
9
+ Key architectural insight: We're building a "virtual propagator" - an EventTarget that emits property change events by intelligently hooking into whatever change detection mechanism is available for that specific element type.
10
+
11
+ -->
12
+
13
+ One major obstacle to doing that is there is no standard metadata that elements support in order to indicate how to do that. So we try our best to "infer" how to do this to our best ability, hence it seems that this functionality fits in a package that focusing on inferring.
14
+
15
+ There are a number of scenarios to consider:
16
+
17
+ 1. Mostly built-in elements that have "source of truth" attributes that reflect the value of the property.
18
+
19
+ Examples are the aria attributes, the output's value property, the anchor tag href property.
20
+
21
+ Setting the property value updates the attributes, and vice versa.
22
+
23
+ 2. Properties of built-in elements with corresponding events, but the events are only user driven. An example would be the input's value property. If the property is set programmatically, no attribute changes, nor any event fires. How can we listen for changes made regardless of how? We could theoretically override the value setter, but if memory serves, that not be reliable either (We need to do some experiments on this).
24
+
25
+ 3. Other even more challenging scenarios are the iframe's src property.
26
+
27
+ 4. For custom elements, because standard setters and getters are typically generated on the prototype, overriding the setter may often be a viable solution. Perhaps the only solution, since there is no currently available way to determine any metadata between properties and attributes, and we currently don't have a way to subscribe to custom css state changes.
28
+
29
+ 5. For custom elements that adopt a [roundabout property](https://github.com/bahrus/roundabout), such custom elements will have a propagator property that emits events with the name of the property when that property changes. That's really what we need.
30
+
31
+ So this addendum to the Infer class should have a new method:
32
+
33
+ ```TypeScript
34
+ export class Infer<TValue = any, TDisplay = any> {
35
+ async getPropagator(){
36
+ const {elementEnhancement} = this;
37
+ const {localName} = elementEnhancement;
38
+ if(localName.includes('-')){
39
+ await (enhancedElement.customElementRegistry || customElements).whenDefined(localName);
40
+ const {propagator} = elementEnhancement;
41
+ if(propagator instanceof EventTarget) return propagator;
42
+ }
43
+ //no built in propagator. Implement a virtual propagator as best we can using our
44
+ //best inferring abilities
45
+ const {PropagatorInferencer} = await import('./PropagatorInferencer.js');
46
+ await propagatorInference = new PropagatorInferencer(elementEnhancement);
47
+ return await propagatorInference.getPropagator();
48
+ }
49
+ }
50
+ ```
51
+
52
+
53
+ ---
54
+
55
+ ## Implementation Thoughts (Kiro)
56
+
57
+ ### Architectural Approach
58
+
59
+ The PropagatorInferencer should create a **virtual propagator** - an EventTarget that emits property change events by intelligently detecting changes through whatever mechanism is available for each element type.
60
+
61
+ ### Core Design Principles
62
+
63
+ 1. **Lazy Initialization**: Don't set up watchers until `getPropagator()` is called
64
+ 2. **Property-Specific Watching**: Each property being watched may need different strategies
65
+ 3. **Memory Safety**: Use WeakRefs and proper cleanup to avoid memory leaks
66
+ 4. **Fallback Chain**: Try the most reliable method first, fall back to less ideal approaches
67
+
68
+ ### Strategy Pattern by Element Type
69
+
70
+ #### Strategy 1: Attribute-Reflected Properties (aria-*, href, etc.)
71
+ - **Detection**: Check if setting property updates corresponding attribute
72
+ - **Implementation**: Use MutationObserver on attributes
73
+ - **Pros**: Reliable, no prototype pollution
74
+ - **Cons**: Only works for reflected properties
75
+ - **Example**: `aria-valuenow`, `href`, `src` (for some elements)
76
+
77
+ #### Strategy 2: Native Event-Driven Properties (input.value, etc.)
78
+ - **Detection**: Check if element fires native events (input, change)
79
+ - **Implementation**: Listen to native events, but this only catches user-driven changes
80
+ - **Challenge**: Programmatic changes don't fire events
81
+ - **Hybrid Solution**: Combine event listening with setter interception
82
+
83
+ #### Strategy 3: Setter Interception (Custom Elements)
84
+ - **Detection**: Check if property descriptor exists on prototype
85
+ - **Implementation**: Create a proxy or override setter on the instance
86
+ - **Pros**: Catches all changes (user and programmatic)
87
+ - **Cons**: Can be fragile, may conflict with frameworks
88
+ - **Best for**: Custom elements with standard getters/setters
89
+
90
+ #### Strategy 4: Polling (Last Resort)
91
+ - **Detection**: When no other method works
92
+ - **Implementation**: requestAnimationFrame-based polling with dirty checking
93
+ - **Pros**: Always works
94
+ - **Cons**: Performance overhead, delayed detection
95
+ - **Use case**: iframe.src and other problematic properties
96
+
97
+ ### Proposed Class Structure
98
+
99
+ ```typescript
100
+ export class PropagatorInferencer {
101
+ #element: Element;
102
+ #propagator: EventTarget;
103
+ #watchers: Map<string, WatcherStrategy>;
104
+ #abortController: AbortController;
105
+
106
+ constructor(element: Element) {
107
+ this.#element = element;
108
+ this.#propagator = new EventTarget();
109
+ this.#watchers = new Map();
110
+ this.#abortController = new AbortController();
111
+ }
112
+
113
+ async getPropagator(): Promise<EventTarget> {
114
+ // Return the virtual propagator that will emit events
115
+ // when properties change
116
+ return this.#propagator;
117
+ }
118
+
119
+ async watchProperty(propName: string): Promise<void> {
120
+ // Determine best strategy for this property
121
+ // Set up the appropriate watcher
122
+ // Store cleanup logic in #watchers
123
+ }
124
+
125
+ #selectStrategy(propName: string): WatcherStrategy {
126
+ // 1. Check if attribute-reflected
127
+ // 2. Check if has native events
128
+ // 3. Check if has setter on prototype
129
+ // 4. Fall back to polling
130
+ }
131
+
132
+ destroy(): void {
133
+ // Clean up all watchers
134
+ this.#abortController.abort();
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### Strategy Detection Algorithm
140
+
141
+ ```typescript
142
+ async #selectStrategy(propName: string): Promise<WatcherStrategy> {
143
+ const element = this.#element;
144
+
145
+ // Strategy 1: Check for attribute reflection
146
+ const attrName = this.#getAttributeName(propName);
147
+ if (attrName && this.#isAttributeReflected(propName, attrName)) {
148
+ return new AttributeWatcher(element, propName, attrName);
149
+ }
150
+
151
+ // Strategy 2: Check for native events
152
+ const eventType = this.#getNativeEventType(propName);
153
+ if (eventType) {
154
+ // Hybrid: event + setter interception for programmatic changes
155
+ return new HybridEventWatcher(element, propName, eventType);
156
+ }
157
+
158
+ // Strategy 3: Setter interception (custom elements)
159
+ if (element.localName.includes('-')) {
160
+ const descriptor = this.#getPropertyDescriptor(propName);
161
+ if (descriptor?.set) {
162
+ return new SetterInterceptor(element, propName, descriptor);
163
+ }
164
+ }
165
+
166
+ // Strategy 4: Polling fallback
167
+ return new PollingWatcher(element, propName);
168
+ }
169
+ ```
170
+
171
+ ### Key Implementation Challenges
172
+
173
+ 1. **Setter Interception Reliability**:
174
+ - Need to test if we can reliably override setters on instances
175
+ - May need to use Proxy instead of direct override
176
+ - Must preserve original setter behavior
177
+
178
+ 2. **Attribute Name Mapping**:
179
+ - `aria-valuenow` ↔ `ariaValueNow`
180
+ - Need bidirectional mapping logic
181
+ - Handle edge cases (data-*, custom attributes)
182
+
183
+ 3. **Memory Leaks**:
184
+ - MutationObservers must be disconnected
185
+ - Event listeners must be removed
186
+ - Polling must be stopped
187
+ - Use AbortController for cleanup
188
+
189
+ 4. **Race Conditions**:
190
+ - Custom element may not be defined yet
191
+ - Properties may change during setup
192
+ - Need to handle async initialization
193
+
194
+ ### Testing Strategy
195
+
196
+ 1. Test each strategy independently with known element types
197
+ 2. Test fallback chain works correctly
198
+ 3. Test memory cleanup (no leaks)
199
+ 4. Test with custom elements that have propagator property
200
+ 5. Test edge cases (iframe.src, programmatic input.value changes)
201
+
202
+ ### Integration with Existing Infer Class
203
+
204
+ The `getPropagator()` method should:
205
+ 1. First check if element has native `propagator` property (roundabout elements)
206
+ 2. If not, create PropagatorInferencer instance
207
+ 3. Cache the inferencer to avoid recreating
208
+ 4. Return the EventTarget that emits property change events
209
+
210
+ ### Open Questions
211
+
212
+ 1. Should we auto-detect which properties to watch, or require explicit registration?
213
+ 2. How do we handle properties that don't exist yet (lazy initialization)?
214
+ 3. Should we support watching nested properties (e.g., `style.color`)?
215
+ 4. What's the event detail format? `{property: string, oldValue: any, newValue: any}`?
216
+
217
+ ### Recommended First Implementation
218
+
219
+ Start with the simplest cases and expand:
220
+ 1. Attribute-reflected properties (MutationObserver)
221
+ 2. Custom elements with roundabout propagator (already works)
222
+ 3. Input elements with hybrid event + setter approach
223
+ 4. Then tackle harder cases (iframe.src, etc.)
224
+
225
+ ---
226
+
227
+ ## Human Response I
228
+
229
+ What are your thoughts on making class PropagatorInferencer the inferenced propagator? First, maybe it should be renamed InferencedPropagator. Second, it should extend EventTarget, and dispatch the events. I think the constructor should be passed in the Infer class, so that it already has access the existing infer methods, like default event type, etc.
230
+
231
+ It is true that some of the logic depends on which property is being observed. I'm thinking the InferencedPropagor could override the addEventListener instead of the watchProperty method. There is a tricky aspect as far as asynchronous logic in that method. I'm thinking make the addEventListenr get a snapshot of the initial value, then do whatever asynchronous wiring is needed, then if the value changed, already dispatch an event with the property name. Would this work?
232
+
233
+ ---
234
+
235
+ ## Kiro Response I
236
+
237
+ Both suggestions are strong improvements over the original design.
238
+
239
+ ### Renaming to InferencedPropagator + Extending EventTarget
240
+
241
+ This is cleaner. The class *is* the propagator—it shouldn't contain one. Extending EventTarget directly means consumers interact with it exactly like they would with a native propagator from a roundabout element. No wrapper indirection, no `.getPropagator()` method on the inferencer itself. The `Infer.getPropagator()` just returns `this` (the InferencedPropagator) or the native propagator—same interface either way.
242
+
243
+ Passing the `Infer` instance into the constructor is the right call. The `Infer` class already knows the element, has `inferEventType`, `inferValueProperty`, etc. No need to duplicate that logic or re-derive it.
244
+
245
+ ### Overriding `addEventListener` as the Wiring Entry Point
246
+
247
+ This is elegant and solves the "which properties to watch" open question naturally—you only wire up observation for properties that someone actually subscribes to. Lazy by design.
248
+
249
+ The async concern is real though, and your proposed solution handles it well. Here's how I'd refine the flow:
250
+
251
+ ```typescript
252
+ export class InferencedPropagator extends EventTarget {
253
+ #infer: Infer;
254
+ #watchedProperties: Set<string> = new Set();
255
+ #abortController: AbortController = new AbortController();
256
+
257
+ constructor(infer: Infer) {
258
+ super();
259
+ this.#infer = infer;
260
+ }
261
+
262
+ override addEventListener(
263
+ type: string,
264
+ callback: EventListenerOrEventListenerObject | null,
265
+ options?: AddEventListenerOptions | boolean
266
+ ): void {
267
+ // Register the listener immediately so it's ready to receive
268
+ super.addEventListener(type, callback, options);
269
+
270
+ // Only wire up observation once per property
271
+ if (!this.#watchedProperties.has(type)) {
272
+ this.#watchedProperties.add(type);
273
+ this.#wireProperty(type);
274
+ }
275
+ }
276
+
277
+ async #wireProperty(propName: string): Promise<void> {
278
+ const element = this.#infer.enhancedElement;
279
+
280
+ // Snapshot current value before async work
281
+ const initialValue = (element as any)[propName];
282
+
283
+ // Do async wiring (MutationObserver setup, setter interception, etc.)
284
+ await this.#setupStrategy(propName);
285
+
286
+ // Check if value changed during setup—if so, dispatch immediately
287
+ const currentValue = (element as any)[propName];
288
+ if (currentValue !== initialValue) {
289
+ this.dispatchEvent(new Event(propName));
290
+ }
291
+ }
292
+
293
+ async #setupStrategy(propName: string): Promise<void> {
294
+ const element = this.#infer.enhancedElement;
295
+ const signal = this.#abortController.signal;
296
+
297
+ // Strategy 1: Attribute-reflected properties
298
+ // Use Infer's knowledge to check reflection...
299
+
300
+ // Strategy 2: Native events (use infer.eventType)
301
+ // For input/textarea/select, the Infer class already
302
+ // knows the relevant event type
303
+
304
+ // Strategy 3: Setter interception for custom elements
305
+
306
+ // Strategy 4: Polling fallback
307
+ }
308
+ }
309
+ ```
310
+
311
+ ### Why This Works
312
+
313
+ 1. **`addEventListener` is synchronous** — the listener is registered immediately via `super.addEventListener()`, so it won't miss events.
314
+ 2. **Wiring is async but safe** — the snapshot/compare pattern catches changes that happen during setup. The only gap is a very brief window where a change might happen *and then revert* during async wiring, but that's an extreme edge case.
315
+ 3. **Event name = property name** — this mirrors the roundabout propagator convention. Consumers do `propagator.addEventListener('value', handler)` and it just works regardless of whether it's native or inferred.
316
+
317
+ ### One Consideration: Multiple `addEventListener` Calls for Same Property
318
+
319
+ The `#watchedProperties` set prevents re-wiring, which is correct. But we should think about `removeEventListener` — if *all* listeners for a property are removed, should we tear down the watcher? This could matter for memory/performance. An option:
320
+
321
+ - Track listener count per property
322
+ - On `removeEventListener`, decrement and tear down if zero
323
+ - Or: just keep watchers alive until `destroy()` is called (simpler, probably fine for most use cases)
324
+
325
+ ### Revised `Infer.getPropagator()`
326
+
327
+ With this design, the integration simplifies to:
328
+
329
+ ```typescript
330
+ export class Infer<TValue = any, TDisplay = any> {
331
+ #propagator: EventTarget | undefined;
332
+
333
+ async getPropagator(): Promise<EventTarget> {
334
+ if (this.#propagator) return this.#propagator;
335
+
336
+ const {enhancedElement} = this;
337
+ const {localName} = enhancedElement;
338
+
339
+ if (localName.includes('-')) {
340
+ await (
341
+ (enhancedElement as any).customElementRegistry || customElements
342
+ ).whenDefined(localName);
343
+ const {propagator} = enhancedElement as any;
344
+ if (propagator instanceof EventTarget) {
345
+ this.#propagator = propagator;
346
+ return propagator;
347
+ }
348
+ }
349
+
350
+ // No native propagator — create an inferred one
351
+ const {InferencedPropagator} = await import('./InferencedPropagator.js');
352
+ this.#propagator = new InferencedPropagator(this);
353
+ return this.#propagator;
354
+ }
355
+ }
356
+ ```
357
+
358
+ Caching via `#propagator` ensures we don't create multiple instances for the same element.
359
+
360
+ ### Strategy Selection Leveraging Infer
361
+
362
+ Since the constructor receives the `Infer` instance, the strategy selection can lean on existing inference logic:
363
+
364
+ - `inferEventType(element)` tells us if there's a native event to listen to (Strategy 2)
365
+ - `inferValueProperty(element)` tells us what property the element considers its "value" — useful for knowing which properties are likely attribute-reflected
366
+ - The `localName` check for `-` (custom element detection) is already a pattern in the codebase
367
+
368
+ This avoids duplicating element-type knowledge across two classes.
@@ -0,0 +1,7 @@
1
+ <script type=importmap >
2
+ {
3
+ "imports": {
4
+ "assign-gingerly/": "/node_modules/assign-gingerly/"
5
+ }
6
+ }
7
+ </script>
@@ -0,0 +1,254 @@
1
+ import { withScopePerimeter } from "./withScopePerimeter.js";
2
+ /**
3
+ * Symbol for smart value assignment
4
+ * When used with element.set[value], it infers and sets the appropriate value property
5
+ */
6
+ export const value = Symbol.for('inferencer:value');
7
+ /**
8
+ * Symbol for smart display assignment
9
+ * When used with element.set[display], it infers and sets the appropriate display property
10
+ */
11
+ export const display = Symbol.for('inferencer:display');
12
+ /**
13
+ * Enhancement class that provides smart value and display property inference
14
+ * Automatically determines the correct property to set based on element type
15
+ */
16
+ export class Infer {
17
+ #weakRef;
18
+ #propName;
19
+ #propagator;
20
+ get enhancedElement() {
21
+ return this.#weakRef.deref();
22
+ }
23
+ constructor(enhancedElement, propName) {
24
+ this.#weakRef = new WeakRef(enhancedElement);
25
+ this.#propName = propName;
26
+ }
27
+ /**
28
+ * Get a propagator (EventTarget) that emits events when properties change.
29
+ * For custom elements with a native propagator (roundabout), returns that directly.
30
+ * Otherwise, creates an InferencedPropagator that uses best-effort strategies
31
+ * to detect property changes.
32
+ */
33
+ async getPropagator() {
34
+ if (this.#propagator)
35
+ return this.#propagator;
36
+ const { enhancedElement } = this;
37
+ const { localName } = enhancedElement;
38
+ if (localName.includes('-')) {
39
+ await (enhancedElement.customElementRegistry || customElements).whenDefined(localName);
40
+ const { propagator } = enhancedElement;
41
+ if (propagator instanceof EventTarget) {
42
+ this.#propagator = propagator;
43
+ return propagator;
44
+ }
45
+ }
46
+ // No native propagator — create an inferred one
47
+ const { InferencedPropagator } = await import('./InferencedPropagator.js');
48
+ this.#propagator = new InferencedPropagator(this);
49
+ return this.#propagator;
50
+ }
51
+ #value;
52
+ get value() {
53
+ return this.#value;
54
+ }
55
+ set value(nv) {
56
+ this.#value = nv;
57
+ const { enhancedElement } = this;
58
+ enhancedElement[inferValueProperty(enhancedElement)] = nv;
59
+ }
60
+ #display;
61
+ get display() {
62
+ return this.#display;
63
+ }
64
+ set display(nv) {
65
+ this.#display = nv;
66
+ const { enhancedElement } = this;
67
+ enhancedElement[inferDisplayProperty(enhancedElement)] = nv;
68
+ }
69
+ /**
70
+ * Get the inferred event type for the element
71
+ * @returns The most appropriate event type for this element
72
+ */
73
+ get eventType() {
74
+ return inferEventType(this.enhancedElement);
75
+ }
76
+ /**
77
+ * Get the inferred value property name for the element
78
+ * @returns The property name used for value assignment (e.g. 'value', 'checked', 'dateTime')
79
+ */
80
+ get valueProperty() {
81
+ return inferValueProperty(this.enhancedElement);
82
+ }
83
+ ['|'](itempropAttr, scopeBoundary = '[itemscope]') {
84
+ const candidates = this.enhancedElement.querySelectorAll(`[itemprop="${itempropAttr}"]`);
85
+ return Array.from(candidates)
86
+ .filter(el => withScopePerimeter(this.enhancedElement, el, scopeBoundary))
87
+ .map(x => new Infer(x, itempropAttr));
88
+ }
89
+ ['@'](nameAttr, scopeBoundary) {
90
+ const candidates = this.enhancedElement.querySelectorAll(`[name="${nameAttr}"]`);
91
+ const filtered = scopeBoundary
92
+ ? Array.from(candidates).filter(el => withScopePerimeter(this.enhancedElement, el, scopeBoundary))
93
+ : Array.from(candidates);
94
+ return filtered.map(x => new Infer(x, nameAttr));
95
+ }
96
+ ['%'](partAttr, scopeBoundary) {
97
+ const candidates = this.enhancedElement.querySelectorAll(`[part~="${partAttr}"]`);
98
+ const filtered = scopeBoundary
99
+ ? Array.from(candidates).filter(el => withScopePerimeter(this.enhancedElement, el, scopeBoundary))
100
+ : Array.from(candidates);
101
+ return filtered.map(x => new Infer(x, partAttr));
102
+ }
103
+ ['#'](id, scopeBoundary) {
104
+ const candidates = this.enhancedElement.querySelectorAll(`#${id}`);
105
+ const filtered = scopeBoundary
106
+ ? Array.from(candidates).filter(el => withScopePerimeter(this.enhancedElement, el, scopeBoundary))
107
+ : Array.from(candidates);
108
+ return filtered.map(x => new Infer(x, id));
109
+ }
110
+ ['.'](className, scopeBoundary) {
111
+ const candidates = this.enhancedElement.querySelectorAll(`.${className}`);
112
+ const filtered = scopeBoundary
113
+ ? Array.from(candidates).filter(el => withScopePerimeter(this.enhancedElement, el, scopeBoundary))
114
+ : Array.from(candidates);
115
+ return filtered.map(x => new Infer(x, className));
116
+ }
117
+ setDisplay(vm) {
118
+ const val = this.#propName ? vm[this.#propName] : inferBindingProperty(this.enhancedElement);
119
+ this.display = val;
120
+ }
121
+ get defaultRemoteBindingPropName() {
122
+ const { enhancedElement } = this;
123
+ return enhancedElement.getAttribute('itemprop') || enhancedElement.getAttribute('name') || enhancedElement.getAttribute('id') || 'value';
124
+ }
125
+ }
126
+ /**
127
+ * Registry item for the Infer enhancement
128
+ * Register this with customElements.enhancementRegistry to enable smart value/display assignment
129
+ */
130
+ export const registryItem = {
131
+ spawn: Infer,
132
+ enhKey: 'inferencer',
133
+ symlinks: {
134
+ [value]: 'value',
135
+ [display]: 'display'
136
+ }
137
+ };
138
+ /**
139
+ * Infer the most appropriate value property for an element
140
+ * @param element - The element to infer the property for
141
+ * @returns The property name to use for value assignment
142
+ */
143
+ export function inferValueProperty(element) {
144
+ // Non-empty itemscope → route through ish (itemscope manager)
145
+ const itemscope = element.getAttribute('itemscope');
146
+ if (itemscope !== null && itemscope !== '') {
147
+ return 'ish';
148
+ }
149
+ const { localName } = element;
150
+ switch (localName) {
151
+ case 'input': {
152
+ const type = element.getAttribute('type')?.toLowerCase();
153
+ switch (type) {
154
+ case 'checkbox':
155
+ case 'radio':
156
+ return 'checked';
157
+ case 'number':
158
+ case 'range':
159
+ return 'valueAsNumber';
160
+ default:
161
+ return 'value';
162
+ }
163
+ }
164
+ case 'textarea':
165
+ case 'select':
166
+ case 'data':
167
+ case 'meter':
168
+ case 'progress':
169
+ case 'output':
170
+ return 'value';
171
+ case 'time':
172
+ return 'dateTime';
173
+ case 'a':
174
+ case 'area':
175
+ return 'href';
176
+ default: {
177
+ return 'textContent';
178
+ }
179
+ }
180
+ }
181
+ /**
182
+ * Infer the most appropriate display property for an element
183
+ * @param element - The element to infer the property for
184
+ * @returns The property name to use for display assignment
185
+ */
186
+ export function inferDisplayProperty(element) {
187
+ const { localName } = element;
188
+ switch (localName) {
189
+ case 'input':
190
+ case 'textarea':
191
+ case 'select':
192
+ return 'value';
193
+ case 'meter':
194
+ case 'progress':
195
+ return 'ariaValueText';
196
+ case 'time':
197
+ case 'data':
198
+ default:
199
+ return 'textContent';
200
+ }
201
+ }
202
+ /**
203
+ * Infer the most appropriate event type for an element
204
+ * Used when no explicit event type is provided
205
+ * @param element - The element to infer the event type for
206
+ * @returns The event type name like 'input', 'change', 'click', 'submit'
207
+ */
208
+ export function inferEventType(element) {
209
+ const { localName } = element;
210
+ switch (localName) {
211
+ case 'input':
212
+ case 'textarea':
213
+ case 'select':
214
+ return 'input';
215
+ case 'form':
216
+ return 'submit';
217
+ case 'details':
218
+ return 'toggle';
219
+ case 'dialog':
220
+ return 'close';
221
+ default:
222
+ return 'click';
223
+ }
224
+ }
225
+ /**
226
+ * Check if an element requires propagator-based observation for value changes.
227
+ * Elements like <data>, <meter>, <output>, <time> have no meaningful user-driven
228
+ * event for value changes — their values change programmatically and reflect to attributes.
229
+ * Custom elements (names containing '-') should also use the propagator path, since they
230
+ * may expose a native propagator (EventTarget) for property change notification.
231
+ * For these elements, consumers should use InferencedPropagator rather than raw addEventListener.
232
+ * @param element - The element to check
233
+ * @returns true if the element needs propagator-based observation
234
+ */
235
+ export function needsPropagator(element) {
236
+ const { localName } = element;
237
+ switch (localName) {
238
+ case 'data':
239
+ case 'meter':
240
+ case 'output':
241
+ case 'time':
242
+ return true;
243
+ default:
244
+ // Custom elements should use propagator path — Infer.getPropagator()
245
+ // will check for a native propagator or fall back to InferencedPropagator
246
+ if (localName.includes('-'))
247
+ return true;
248
+ return inferEventType(element) === 'click';
249
+ }
250
+ }
251
+ export function inferBindingProperty(element) {
252
+ return element.getAttribute('itemprop') || element.getAttribute('name') || element.getAttribute('id') || 'value';
253
+ }
254
+ export default registryItem;