assign-gingerly 0.0.67 → 0.0.68

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.
@@ -33,7 +33,8 @@ export const akaMethods = {
33
33
  '🔍': 'querySelector',
34
34
  '🧺': 'querySelectorAll',
35
35
  '+': 'add',
36
- '🧬': 'cloneNode'
36
+ '🧬': 'cloneNode',
37
+ '🔤': 'textContent',
37
38
  };
38
39
  export const aka = {
39
40
  '©️': 'content?.cloneNode?.true',
@@ -35,7 +35,8 @@ export const akaMethods: Record<string, string> = {
35
35
  '🔍': 'querySelector',
36
36
  '🧺': 'querySelectorAll',
37
37
  '+': 'add',
38
- '🧬': 'cloneNode'
38
+ '🧬': 'cloneNode',
39
+ '🔤': 'textContent',
39
40
  };
40
41
 
41
42
  export const aka: Record<string, string> = {
@@ -0,0 +1,10 @@
1
+ # Inferencer Submodule Isolation
2
+
3
+ The `inferencer/` folder is a git submodule that must remain self-contained and independently publishable.
4
+
5
+ ## Rules
6
+
7
+ - **No imports from outside the folder.** Code in `inferencer/` must NOT import from any file in the parent project (no `../assignGingerly.js`, no `../paths.js`, etc.).
8
+ - **No references to assign-gingerly internals.** The inferencer module must work as a standalone package with zero dependencies on assign-gingerly runtime code.
9
+ - **Types only exception:** Type-only imports from `../types/` are acceptable IF they are also published as part of the inferencer package's own type declarations. Prefer duplicating small type definitions over creating a dependency.
10
+ - **Test exceptions:** Test files within `inferencer/` MAY reference the parent project for integration testing, but runtime source files must not.
@@ -1383,6 +1383,27 @@ const selectorMatch = prop.match(/^\[(.+?)\](?:\?\.(.+))?$/);
1383
1383
  3. **Wrong parser in HTML** - HTML references `parse-pattern-statements` but emc.mjs uses `parse-grouped-capture-statements`
1384
1384
  4. **Period vs chained accessor** - Using `.` instead of `?.` for selector properties
1385
1385
  5. **Forgetting to rebuild** - After changing emc.mjs or emoji.mjs, always run `npm run build`
1386
+ 6. **Hydrate fires before all attributes are read** - Attribute props are assigned one at a time during initialization; use the `initialized` flag pattern (see below) when an action must wait for all of them
1387
+
1388
+ ### Blocking an Action Until All Attributes Are Read
1389
+
1390
+ When converting an enhancement whose `hydrate` (or other action) depends on multiple attribute-derived props, it's often predictable that nothing should happen until all relevant attributes have been read. Gating on the props alone doesn't work:
1391
+
1392
+ - `ifKeyIn` alone means **at least one** of the listed props is defined, so the action can fire after the first attribute is read with the rest still `undefined`.
1393
+ - With `ifKeyIn` and `ifAllOf` combined, roundabout only runs the action when the *changed* property is in `ifKeyIn` — a prop listed only in `ifAllOf` never triggers it.
1394
+
1395
+ The proven fix (from three-peat): add `initialized?: boolean` to `AllProps`, set `self.initialized = true` in `init` immediately after `await roundabout(...)`, and gate the action on it in both lists:
1396
+
1397
+ ```javascript
1398
+ actions: {
1399
+ hydrate: {
1400
+ ifKeyIn: ['src', 'listProp', 'initialized'],
1401
+ ifAllOf: ['enhancedElement', 'initialized']
1402
+ }
1403
+ }
1404
+ ```
1405
+
1406
+ `initialized` flips only after `roundabout()` returns (all attribute reads complete), and including it in `ifKeyIn` makes its change the trigger. Keep the attribute props in `ifKeyIn` too, so later attribute changes still re-trigger the action. See `NewEnhancementInstructions.md` ("Blocking an Action Until All Attributes Are Read") for the full step-by-step recipe.
1386
1407
 
1387
1408
  ### Debugging Tips
1388
1409
 
@@ -681,6 +681,52 @@ import { findAdjacentElement } from 'be-hive/findAdjacentElement.js';
681
681
  import { findAdjacentElement } from 'trans-render/lib/findAdjacentElement.js';
682
682
  ```
683
683
 
684
+ ### Blocking an Action Until All Attributes Are Read
685
+
686
+ **Use this pattern only when it's predictable that an action (typically `hydrate`) must not run until all relevant attributes have been read.** Attribute-derived props are assigned one at a time during `roundabout()`'s initial pass, so gating on the props themselves isn't enough:
687
+
688
+ - `ifKeyIn` alone means **at least one** of the listed props is defined — the action fires as soon as the first attribute is read, while later ones are still `undefined`.
689
+ - When `ifKeyIn` is combined with `ifAllOf`, the action only executes when the property that *changed* is in `ifKeyIn` (`shouldExecute = conditionsMet && changedIsInKeyIn` in roundabout). A prop listed only in `ifAllOf` can never trigger the action.
690
+
691
+ The proven solution (from three-peat) is an `initialized` flag set after `roundabout()` returns:
692
+
693
+ **1. Add `initialized` to `AllProps` in `types/<name>/types.d.ts`:**
694
+
695
+ ```typescript
696
+ export interface AllProps extends EndUserProps{
697
+ enhancedElement: Element & ElementEnhancementGateway;
698
+ resolved?: boolean;
699
+ initialized?: boolean;
700
+ }
701
+ ```
702
+
703
+ **2. Set it at the end of `init`, after the `await roundabout(...)`:**
704
+
705
+ ```javascript
706
+ async init(self, enhancedElement, ctx, initVals){
707
+ // ...build raOptions...
708
+ await (await import('roundabout-lib/roundabout.js')).roundabout(raOptions);
709
+ self.initialized = true; // all attribute reads are done at this point
710
+ }
711
+ ```
712
+
713
+ **3. Gate the action on `initialized` in `emc.mjs` — in *both* lists:**
714
+
715
+ ```javascript
716
+ actions: {
717
+ hydrate: {
718
+ ifKeyIn: ['src', 'listProp', 'initialized'],
719
+ ifAllOf: ['enhancedElement', 'initialized']
720
+ }
721
+ }
722
+ ```
723
+
724
+ - `initialized` is the last prop to change, and it only flips after every attribute has been read, so it is the reliable trigger.
725
+ - It must be in `ifKeyIn`, not just `ifAllOf` — otherwise its change event doesn't satisfy `changedIsInKeyIn` and the action never fires (the other props were already assigned during the initial pass and never change again).
726
+ - Keeping the attribute props (`src`, `listProp`) in `ifKeyIn` preserves re-hydration when those attributes change later at runtime.
727
+
728
+ Reference implementation: three-peat (`emc.mjs`, `three-peat.js`, `types/three-peat/types.d.ts`).
729
+
684
730
  ### Debugging Tips
685
731
 
686
732
  1. **Check the generated JSON** — Run `node emc.mjs` and verify all sections (especially `customData`) are present
@@ -702,4 +748,4 @@ Don't try to implement all features at once.
702
748
 
703
749
  ---
704
750
 
705
- *Last updated: June 2026*
751
+ *Last updated: July 2026*
@@ -10,9 +10,49 @@ export interface EndUserProps{
10
10
 
11
11
  /**
12
12
  * Specify id of peer element to pull list from.
13
+ * If not provided, the host is found by searching upwards
14
+ * for an itemscope-managed element, falling back to the
15
+ * shadow root host.
13
16
  */
14
17
  src?: string;
15
- each: FromEachItemConfig,
16
- target: string,
17
- updateOn: string,
18
- }
18
+
19
+ /**
20
+ * Specifies how each item's values are distributed into the
21
+ * cloned document fragment. Parsed from JSON.
22
+ * If not provided, each item's properties are inferred into
23
+ * the clone's [itemprop] descendants.
24
+ */
25
+ each?: FromEachItemConfig,
26
+
27
+ /**
28
+ * id of an element (within the same root node) into which the
29
+ * repeating cloned fragments should be placed.
30
+ * If not provided, the fragments are appended to the children
31
+ * of the adorned element (or, if the adorned element is a
32
+ * template, to the template's parent element).
33
+ */
34
+ target?: string,
35
+
36
+ /**
37
+ * Name of the event the host dispatches when the list has changed.
38
+ * If not provided, but listProp is, the host is assumed to have
39
+ * a propagator (one is created if it doesn't exist), which is
40
+ * listened to for an event named after listProp.
41
+ */
42
+ updateOn?: string,
43
+ }
44
+
45
+ export interface AllProps extends EndUserProps{
46
+ enhancedElement: Element & ElementEnhancementGateway;
47
+ resolved?: boolean;
48
+ initialized?: boolean;
49
+ }
50
+
51
+ export type AP = AllProps;
52
+ export type PAP = Partial<AP>;
53
+ export type ProPAP = Promise<PAP>;
54
+
55
+ export interface Actions {
56
+ init(self: AP, enhancedElement: Element & ElementEnhancementGateway, ctx: SpawnContext, initVals: PAP): Promise<void>;
57
+ hydrate(self: AP): ProPAP;
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.67",
3
+ "version": "0.0.68",
4
4
  "description": "This package provides a utility function for carefully merging one object into another.",
5
5
  "homepage": "https://github.com/bahrus/assign-gingerly#readme",
6
6
  "bugs": {
@@ -163,9 +163,9 @@
163
163
  "default": "./assignFromAsync-extension.js",
164
164
  "types": "./assignFromAsync-extension.ts"
165
165
  },
166
- "./emojis.js": {
167
- "default": "./emojis.js",
168
- "types": "./emojis.ts"
166
+ "./DX/emojis.js": {
167
+ "default": "./DX/emojis.js",
168
+ "types": "./DX/emojis.ts"
169
169
  },
170
170
  "./assignFeatures.js": {
171
171
  "default": "./assignFeatures.js",