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.
- package/{emojis.js → DX/emojis.js} +2 -1
- package/{emojis.ts → DX/emojis.ts} +2 -1
- package/inferencer/AGENTS.md +10 -0
- package/inferencer/types/EnhancementConversionInstructions.md +21 -0
- package/inferencer/types/NewEnhancementInstructions.md +47 -1
- package/inferencer/types/three-peat/types.d.ts +44 -4
- package/package.json +4 -4
|
@@ -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:
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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.
|
|
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",
|