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.
- package/README.md +847 -5
- package/assignFrom.js +229 -9
- package/assignFrom.ts +338 -9
- package/assignGingerly.js +34 -1
- package/assignGingerly.ts +54 -1
- package/beVigilant.js +73 -0
- package/beVigilant.ts +85 -0
- package/enhanceAll.js +106 -0
- package/enhanceAll.ts +138 -0
- package/handlers/join.js +74 -0
- package/handlers/join.ts +80 -0
- package/handlers/lazyLoad.js +212 -0
- package/handlers/lazyLoad.ts +307 -0
- package/handlers/lazyLoadSwitch.js +58 -0
- package/handlers/lazyLoadSwitch.ts +63 -0
- package/handlers/microDataJoin.js +184 -0
- package/handlers/microDataJoin.ts +270 -0
- package/inferencer/.gitmodules +3 -0
- package/inferencer/.vscode/settings.json +2 -0
- package/inferencer/InferencedPropagator.js +230 -0
- package/inferencer/InferencedPropagator.ts +269 -0
- package/inferencer/LICENSE +21 -0
- package/inferencer/README.md +524 -0
- package/inferencer/Requirements/SupportForPropagator.md +368 -0
- package/inferencer/imports.html +7 -0
- package/inferencer/inferencer.js +254 -0
- package/inferencer/inferencer.ts +292 -0
- package/inferencer/package-lock.json +129 -0
- package/inferencer/package.json +60 -0
- package/inferencer/playwright-report/data/507ad515125e13390ea07de92f22331c913fa068.md +55 -0
- package/inferencer/playwright-report/index.html +90 -0
- package/inferencer/playwright.config.ts +54 -0
- package/inferencer/test-results/.last-run.json +6 -0
- package/inferencer/test-results/inferencer-Inferencer-Enha-535bc-inferencer-tests-in-browser-chromium/error-context.md +55 -0
- package/inferencer/tests/inferencedPropagator.html +428 -0
- package/inferencer/tests/inferencedPropagator.spec.ts +18 -0
- package/inferencer/tests/inferencer.html +355 -0
- package/inferencer/tests/inferencer.spec.ts +19 -0
- package/inferencer/tsconfig.json +19 -0
- package/inferencer/types/.kiro/specs/conversion-template/README.md +128 -0
- package/inferencer/types/.kiro/specs/conversion-template/design.md +360 -0
- package/inferencer/types/.kiro/specs/conversion-template/requirements.md +191 -0
- package/inferencer/types/.kiro/specs/conversion-template/tasks.md +174 -0
- package/inferencer/types/.kiro/steering/coding-standards.md +53 -0
- package/inferencer/types/.kiro/steering/conversion-guide.md +108 -0
- package/inferencer/types/.kiro/steering/declarative-configuration.md +108 -0
- package/inferencer/types/.kiro/steering/emc-json-serializability.md +306 -0
- package/inferencer/types/EnhancementConversionInstructions.md +1854 -0
- package/inferencer/types/LICENSE +21 -0
- package/inferencer/types/NewCustomElement.md +388 -0
- package/inferencer/types/NewCustomElementFeature.md +683 -0
- package/inferencer/types/NewEnhancementInstructions.md +705 -0
- package/inferencer/types/README.md +2 -0
- package/inferencer/types/agrace/types.d.ts +11 -0
- package/inferencer/types/assign-gingerly/types.d.ts +572 -0
- package/inferencer/types/be-a-beacon/types.d.ts +17 -0
- package/inferencer/types/be-bound/types.d.ts +66 -0
- package/inferencer/types/be-buttoned-up/types.d.ts +19 -0
- package/inferencer/types/be-calculating/types.d.ts +54 -0
- package/inferencer/types/be-clonable/types.d.ts +38 -0
- package/inferencer/types/be-committed/types.d.ts +22 -0
- package/inferencer/types/be-consoling/types.d.ts +24 -0
- package/inferencer/types/be-decked-with/types.d.ts +26 -0
- package/inferencer/types/be-delible/types.d.ts +27 -0
- package/inferencer/types/be-dispatching/types.d.ts +34 -0
- package/inferencer/types/be-evanescent/types.d.ts +20 -0
- package/inferencer/types/be-flashy/types.d.ts +21 -0
- package/inferencer/types/be-gone/types.d.ts +25 -0
- package/inferencer/types/be-observing/types.d.ts +55 -0
- package/inferencer/types/be-reflective/types.d.ts +78 -0
- package/inferencer/types/be-reformable/types.d.ts +49 -0
- package/inferencer/types/be-render-neutral/types.d.ts +32 -0
- package/inferencer/types/be-switched/types.d.ts +146 -0
- package/inferencer/types/be-typed/types.d.ts +32 -0
- package/inferencer/types/be-valued/types.d.ts +22 -0
- package/inferencer/types/data-props/types.d.ts +34 -0
- package/inferencer/types/do-inc/types.d.ts +56 -0
- package/inferencer/types/do-invoke/types.d.ts +38 -0
- package/inferencer/types/do-merge/types.d.ts +28 -0
- package/inferencer/types/do-toggle/types.d.ts +31 -0
- package/inferencer/types/face-up/types.d.ts +100 -0
- package/inferencer/types/fetch-for/types.d.ts +36 -0
- package/inferencer/types/folder-picker/types.d.ts +21 -0
- package/inferencer/types/global.d.ts +29 -0
- package/inferencer/types/id-generation/types.d.ts +26 -0
- package/inferencer/types/inferencer/types.d.ts +46 -0
- package/inferencer/types/mount-observer/types.d.ts +363 -0
- package/inferencer/types/nested-regex-groups/types.d.ts +107 -0
- package/inferencer/types/pipe-in/types.d.ts +52 -0
- package/inferencer/types/roundabout/types.d.ts +268 -0
- package/inferencer/types/soak-up/types.d.ts +40 -0
- package/inferencer/types/templ-maker/types.d.ts +43 -0
- package/inferencer/types/time-ticker/types.d.ts +62 -0
- package/inferencer/types/truth-sourcer/types.d.ts +44 -0
- package/inferencer/upSearch.js +27 -0
- package/inferencer/upSearch.ts +26 -0
- package/inferencer/withScopePerimeter.js +27 -0
- package/inferencer/withScopePerimeter.ts +33 -0
- package/inferredAssignments.js +38 -0
- package/inferredAssignments.ts +65 -0
- package/isAllowedImportPath.js +42 -0
- package/isAllowedImportPath.ts +53 -0
- package/package.json +57 -3
- package/paths.js +231 -0
- package/paths.ts +413 -0
- package/processHandlerCommands.js +188 -0
- package/processHandlerCommands.ts +217 -0
- package/resolveIdRef.js +144 -0
- package/resolveIdRef.ts +170 -0
- package/resolveValues.js +41 -2
- package/resolveValues.ts +41 -1
- package/transitionHelper.js +109 -0
- package/transitionHelper.ts +132 -0
- package/types/assign-gingerly/types.d.ts +89 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
# New Enhancement Instructions
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
This document provides step-by-step instructions for creating a **brand new** "be-*" or "do-*" enhancement project using the modern architecture. Unlike the [Enhancement Conversion Instructions](./EnhancementConversionInstructions.md) (which covers migrating legacy projects), this guide starts from scratch.
|
|
6
|
+
|
|
7
|
+
**Note:** This guide is specifically for **enhancements** (declarative behaviors added to existing HTML elements via attributes). It does NOT apply to custom elements. Enhancements use `be-hive` and `mount-observer` to attach behavior to elements without requiring custom element registration.
|
|
8
|
+
|
|
9
|
+
## Reference Implementations
|
|
10
|
+
|
|
11
|
+
- **[do-invoke](https://github.com/bahrus/do-invoke)** — Custom parser with nested paths and default values
|
|
12
|
+
- **[do-toggle](https://github.com/bahrus/do-toggle)** — Parser with infer pattern
|
|
13
|
+
- **[do-inc](https://github.com/bahrus/do-inc)** — Increment enhancement with parser
|
|
14
|
+
- **[be-clonable](https://github.com/bahrus/be-clonable)** — Most up-to-date conversion example (useful for architecture patterns)
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
- Node.js installed
|
|
19
|
+
- npm installed
|
|
20
|
+
- `ncu` (npm-check-updates) installed globally: `npm install -g npm-check-updates`
|
|
21
|
+
|
|
22
|
+
## Step 1: Initialize the Project
|
|
23
|
+
|
|
24
|
+
1. Create a new repository (e.g., `do-merge` or `be-fancy`)
|
|
25
|
+
2. Run `npm init` or create a `package.json` manually
|
|
26
|
+
3. Add the `types` submodule:
|
|
27
|
+
```bash
|
|
28
|
+
git submodule add https://github.com/bahrus/types.git types
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Step 2: Configure package.json
|
|
32
|
+
|
|
33
|
+
Set up the standard scripts and dependencies:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"name": "do-my-enhancement",
|
|
38
|
+
"version": "0.0.0",
|
|
39
|
+
"description": "Description of what the enhancement does",
|
|
40
|
+
"type": "module",
|
|
41
|
+
"main": "do-my-enhancement.js",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "node emc.mjs > emc.json && node [emoji].mjs > [emoji].json",
|
|
44
|
+
"serve": "node ./node_modules/spa-ssi/serve.js",
|
|
45
|
+
"test": "playwright test",
|
|
46
|
+
"safari": "npx playwright wk http://localhost:8000",
|
|
47
|
+
"update": "ncu -u && npm install"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"assign-gingerly": "0.0.5",
|
|
51
|
+
"be-hive": "0.1.9",
|
|
52
|
+
"inferencer": "0.0.1",
|
|
53
|
+
"mount-observer": "0.0.16",
|
|
54
|
+
"roundabout-lib": "0.0.2"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"spa-ssi": "0.0.27",
|
|
58
|
+
"@playwright/test": "1.59.1"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Notes:**
|
|
64
|
+
- Replace `[emoji]` with your emoji shorthand (or remove that part of the build script if no emoji)
|
|
65
|
+
- Use exact versions, not ranges (no `^` or `~`)
|
|
66
|
+
- Add `nested-regex-groups` if you need custom attribute parsing
|
|
67
|
+
- Run `npm run update` after creating package.json to install dependencies
|
|
68
|
+
|
|
69
|
+
## Step 3: Create Type Definitions
|
|
70
|
+
|
|
71
|
+
Create `types/[project-name]/types.d.ts` with the standard structure:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { ElementEnhancementGateway, SpawnContext } from "../assign-gingerly/types";
|
|
75
|
+
|
|
76
|
+
export interface EndUserProps {
|
|
77
|
+
// Properties that end users configure via attributes
|
|
78
|
+
myProp: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface AllProps extends EndUserProps {
|
|
82
|
+
enhancedElement: Element & ElementEnhancementGateway;
|
|
83
|
+
resolved: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type AP = AllProps;
|
|
87
|
+
export type PAP = Partial<AP>;
|
|
88
|
+
export type ProPAP = Promise<PAP>;
|
|
89
|
+
|
|
90
|
+
export interface Actions {
|
|
91
|
+
init(self: AP, enhancedElement: Element, ctx: SpawnContext, initVals: PAP): Promise<void>;
|
|
92
|
+
hydrate(self: AP): ProPAP;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Key points:**
|
|
97
|
+
- `EndUserProps` — what the user configures via HTML attributes
|
|
98
|
+
- `AllProps` — includes `enhancedElement` and any internal state like `resolved`
|
|
99
|
+
- `Actions` — methods the enhancement exposes; `init` always has the 4-parameter signature
|
|
100
|
+
- If using a custom parser, import `StatementsResult` from `../nested-regex-groups/types`
|
|
101
|
+
|
|
102
|
+
## Step 4: Create emc.mjs (Build Configuration)
|
|
103
|
+
|
|
104
|
+
Create `emc.mjs` in the project root:
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
//@ts-check
|
|
108
|
+
|
|
109
|
+
/** @import {EMC} from './types/mount-observer/types' */;
|
|
110
|
+
/** @import {AllProps, Actions} from './types/[project-name]/types' */
|
|
111
|
+
/** @import {RAConfig} from './types/roundabout/types' */
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @type {EMC<any, AllProps, Element, RAConfig<AllProps, Actions> >}
|
|
115
|
+
*/
|
|
116
|
+
export const emc = {
|
|
117
|
+
enhConfig: {
|
|
118
|
+
enhKey: 'DoMyEnhancement',
|
|
119
|
+
spawn: '[project-name]/[project-name].js',
|
|
120
|
+
withAttrs: {
|
|
121
|
+
base: '[project-name]',
|
|
122
|
+
// For simple string/number properties:
|
|
123
|
+
// myProp: '${base}-my-prop',
|
|
124
|
+
// For the base attribute itself (JSON object):
|
|
125
|
+
// _base: { mapsTo: 'myProp', instanceOf: 'Object' }
|
|
126
|
+
// For boolean properties:
|
|
127
|
+
// _nudge: { instanceOf: 'Boolean' }
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
customData: {
|
|
131
|
+
weakRef: {
|
|
132
|
+
properties: ['enhancedElement']
|
|
133
|
+
},
|
|
134
|
+
actions: {
|
|
135
|
+
hydrate: {
|
|
136
|
+
ifAllOf: ['myProp', 'enhancedElement']
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export function render(){
|
|
143
|
+
return JSON.stringify(emc, null, 4);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log(render());
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Attribute Parsing Options
|
|
150
|
+
|
|
151
|
+
**JSON attribute (parsed by browser):**
|
|
152
|
+
```javascript
|
|
153
|
+
_base: { mapsTo: 'mergeParamSets', instanceOf: 'Object' }
|
|
154
|
+
```
|
|
155
|
+
Use when the attribute value is valid JSON. The browser's JSON parser handles it.
|
|
156
|
+
|
|
157
|
+
**Custom parser (for string DSL syntax):**
|
|
158
|
+
```javascript
|
|
159
|
+
_base: {
|
|
160
|
+
mapsTo: 'invokeParamSet',
|
|
161
|
+
parser: 'parse-pattern-statements',
|
|
162
|
+
instanceOf: 'Array',
|
|
163
|
+
parserConfig: parsePatterns
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
Use when you need to parse a custom string syntax. See [Enhancement Conversion Instructions](./EnhancementConversionInstructions.md) Step 7a for details.
|
|
167
|
+
|
|
168
|
+
## Step 5: Create Emoji Shorthand (Optional)
|
|
169
|
+
|
|
170
|
+
If your enhancement has an emoji shorthand, create `[emoji].mjs`:
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
import myJSON from './emc.json' with {type: 'json'};
|
|
174
|
+
|
|
175
|
+
/** @import {EMC} from './types/mount-observer/types' */;
|
|
176
|
+
/** @import {AllProps} from './types/[project-name]/types' */
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @type {EMC<any, AllProps> }
|
|
180
|
+
*/
|
|
181
|
+
const emc = {
|
|
182
|
+
...myJSON,
|
|
183
|
+
enhConfig: {
|
|
184
|
+
...myJSON.enhConfig,
|
|
185
|
+
enhKey: '[emoji]',
|
|
186
|
+
withAttrs: {
|
|
187
|
+
...myJSON.enhConfig.withAttrs,
|
|
188
|
+
base: '[emoji]'
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export function render(){
|
|
194
|
+
return JSON.stringify(emc, null, 4);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(render());
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Critical:** The `...myJSON` spread at the top level ensures `customData` is carried over.
|
|
201
|
+
|
|
202
|
+
## Step 6: Create the Enhancement Class
|
|
203
|
+
|
|
204
|
+
Create `[project-name].js`:
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
// @ts-check
|
|
208
|
+
/** @import {Actions, PAP, AllProps, AP} from './types/[project-name]/types' */;
|
|
209
|
+
/** @import {RoundaboutOptions} from './types/roundabout/types' */;
|
|
210
|
+
/** @import {ElementEnhancementGateway, SpawnContext} from './types/assign-gingerly/types' */;
|
|
211
|
+
/** @import {EMC} from './types/mount-observer/types' */;
|
|
212
|
+
/** @import {RAConfig} from './types/roundabout/types' */;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @implements {Actions}
|
|
216
|
+
*/
|
|
217
|
+
class DoMyEnhancement {
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @this {AllProps & Actions}
|
|
221
|
+
* @param {Element & ElementEnhancementGateway} enhancedElement
|
|
222
|
+
* @param {SpawnContext} ctx
|
|
223
|
+
* @param {PAP} initVals
|
|
224
|
+
*/
|
|
225
|
+
constructor(enhancedElement, ctx, initVals){
|
|
226
|
+
this.init(this, enhancedElement, ctx, initVals);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param {AllProps} self
|
|
231
|
+
* @param {Element & ElementEnhancementGateway} enhancedElement
|
|
232
|
+
* @param {SpawnContext} ctx
|
|
233
|
+
* @param {PAP} initVals
|
|
234
|
+
*/
|
|
235
|
+
async init(self, enhancedElement, ctx, initVals){
|
|
236
|
+
const {customData} = /** @type {EMC<any, AllProps, Element, RAConfig<AllProps, Actions>>} */ (ctx.emc);
|
|
237
|
+
/**
|
|
238
|
+
* @type {RoundaboutOptions}
|
|
239
|
+
*/
|
|
240
|
+
const raOptions = {
|
|
241
|
+
...customData,
|
|
242
|
+
vm: self,
|
|
243
|
+
initialPropVals: {
|
|
244
|
+
enhancedElement,
|
|
245
|
+
...customData?.defaultPropVals,
|
|
246
|
+
...initVals
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
(await import('roundabout-lib/roundabout.js')).roundabout(raOptions);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @param {AP} self
|
|
254
|
+
*/
|
|
255
|
+
async hydrate(self){
|
|
256
|
+
// Your enhancement logic here
|
|
257
|
+
return /** @type {PAP} */ ({resolved: true});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export {DoMyEnhancement};
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Key patterns:**
|
|
265
|
+
- No base class — plain JavaScript class
|
|
266
|
+
- Constructor delegates to `init` with 4 parameters
|
|
267
|
+
- `init` extracts `customData` from `ctx.emc` (no JSON import needed)
|
|
268
|
+
- Action methods receive `self` (the reactive proxy) as first parameter
|
|
269
|
+
- Return partial props from actions to trigger reactive updates
|
|
270
|
+
|
|
271
|
+
## Step 7: Create imports.html
|
|
272
|
+
|
|
273
|
+
```html
|
|
274
|
+
<script type=importmap >
|
|
275
|
+
{
|
|
276
|
+
"imports": {
|
|
277
|
+
"assign-gingerly/": "/node_modules/assign-gingerly/",
|
|
278
|
+
"[project-name]/": "/",
|
|
279
|
+
"be-hive/": "/node_modules/be-hive/",
|
|
280
|
+
"inferencer/": "/node_modules/inferencer/",
|
|
281
|
+
"mount-observer/": "/node_modules/mount-observer/",
|
|
282
|
+
"roundabout-lib/": "/node_modules/roundabout-lib/",
|
|
283
|
+
"id-generation/": "/node_modules/id-generation/"
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
</script>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Step 8: Configure VS Code
|
|
290
|
+
|
|
291
|
+
Create `.vscode/settings.json`:
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"explorer.fileNesting.patterns": {
|
|
296
|
+
"*.mjs": "${capture}.json"
|
|
297
|
+
},
|
|
298
|
+
"explorer.fileNesting.enabled": true
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Step 9: Set Up Auto-Build Hook
|
|
303
|
+
|
|
304
|
+
Create `.kiro/hooks/auto-build-config.kiro.hook`:
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"name": "Auto-build Configuration",
|
|
309
|
+
"version": "1.0.0",
|
|
310
|
+
"description": "Automatically runs npm run build when emc.mjs or emoji .mjs files are saved",
|
|
311
|
+
"when": {
|
|
312
|
+
"type": "fileEdited",
|
|
313
|
+
"patterns": ["emc.mjs", "[emoji].mjs"]
|
|
314
|
+
},
|
|
315
|
+
"then": {
|
|
316
|
+
"type": "runCommand",
|
|
317
|
+
"command": "npm run build",
|
|
318
|
+
"timeout": 10000
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Step 10: Build and Verify
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
npm run update # Install/update dependencies
|
|
327
|
+
npm run build # Generate emc.json and emoji.json
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Verify that `emc.json` (and `[emoji].json`) are generated with the expected structure.
|
|
331
|
+
|
|
332
|
+
## Step 11: Create Test HTML
|
|
333
|
+
|
|
334
|
+
Create a test file (e.g., `tests/test1.html`):
|
|
335
|
+
|
|
336
|
+
```html
|
|
337
|
+
<!DOCTYPE html>
|
|
338
|
+
<html lang="en">
|
|
339
|
+
<head>
|
|
340
|
+
<meta charset="UTF-8">
|
|
341
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
342
|
+
<title>Test</title>
|
|
343
|
+
<!-- #include virtual="/imports.html" -->
|
|
344
|
+
<be-hive>
|
|
345
|
+
<script type=emc src="[project-name]/emc.json"></script>
|
|
346
|
+
</be-hive>
|
|
347
|
+
<script type=module>
|
|
348
|
+
import 'be-hive/be-hive.js';
|
|
349
|
+
</script>
|
|
350
|
+
</head>
|
|
351
|
+
<body>
|
|
352
|
+
<!-- Your test markup here -->
|
|
353
|
+
</body>
|
|
354
|
+
</html>
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Architecture Overview
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
[project-name]/
|
|
361
|
+
├── .kiro/
|
|
362
|
+
│ └── hooks/
|
|
363
|
+
│ └── auto-build-config.kiro.hook
|
|
364
|
+
├── .vscode/
|
|
365
|
+
│ └── settings.json
|
|
366
|
+
├── types/ (git submodule)
|
|
367
|
+
│ └── [project-name]/
|
|
368
|
+
│ └── types.d.ts
|
|
369
|
+
├── [project-name].js (enhancement class - browser code)
|
|
370
|
+
├── emc.mjs (build script → emc.json)
|
|
371
|
+
├── emc.json (generated - runtime config)
|
|
372
|
+
├── [emoji].mjs (build script → [emoji].json)
|
|
373
|
+
├── [emoji].json (generated - emoji variant config)
|
|
374
|
+
├── imports.html (import map for browser)
|
|
375
|
+
├── package.json
|
|
376
|
+
└── README.md
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Key Differences from Conversion
|
|
380
|
+
|
|
381
|
+
| Aspect | Conversion | New Enhancement |
|
|
382
|
+
|--------|-----------|-----------------|
|
|
383
|
+
| Legacy code | Move to `legacy/` folder | N/A — no legacy code |
|
|
384
|
+
| ts-refs | Remove and migrate to `types/` | Start directly in `types/` |
|
|
385
|
+
| Type cleanup | Remove IEnhancement extends, BAP | Write clean types from scratch |
|
|
386
|
+
| Static config | Migrate to emc.mjs customData | Write customData directly |
|
|
387
|
+
| Class refactor | Remove BE extends, bootUp | Write plain class from start |
|
|
388
|
+
|
|
389
|
+
## Tips
|
|
390
|
+
|
|
391
|
+
- **Start simple**: Get the basic case working before adding complexity
|
|
392
|
+
- **Test incrementally**: Build one example at a time
|
|
393
|
+
- **Check generated JSON**: Run `node emc.mjs` to see the output
|
|
394
|
+
- **Use `@ts-check`**: Catches type errors early in `.js` files
|
|
395
|
+
- **Don't import emc.json in the class**: The config comes through `ctx.emc`
|
|
396
|
+
|
|
397
|
+
## Parser Registration in HTML
|
|
398
|
+
|
|
399
|
+
When your enhancement uses a custom parser (see Step 4's custom parser section), you must also register the parser in your test/demo HTML files. The EMC script needs to wait for the parser to be available before processing attributes.
|
|
400
|
+
|
|
401
|
+
```html
|
|
402
|
+
<be-hive>
|
|
403
|
+
<script type=emc-parser
|
|
404
|
+
src="be-hive/parsers/parse-pattern-statements.js"
|
|
405
|
+
parser-name=parse-pattern-statements></script>
|
|
406
|
+
<script type=emc
|
|
407
|
+
src="[project-name]/emc.json"
|
|
408
|
+
wait-for-parsers=parse-pattern-statements></script>
|
|
409
|
+
</be-hive>
|
|
410
|
+
<script type=module>
|
|
411
|
+
import 'be-hive/be-hive.js';
|
|
412
|
+
</script>
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Key attributes:**
|
|
416
|
+
- `type=emc-parser` — Identifies this as a parser registration script
|
|
417
|
+
- `src` — Path to the parser module (e.g., `be-hive/parsers/parse-pattern-statements.js`)
|
|
418
|
+
- `parser-name` — Must match the `parser` string value in your `emc.mjs` `withAttrs` config
|
|
419
|
+
- `wait-for-parsers` — Comma-separated list of parser names to wait for before processing the EMC
|
|
420
|
+
|
|
421
|
+
If you omit the parser registration, you'll get "Parser not found" errors in the browser console.
|
|
422
|
+
|
|
423
|
+
## Playwright Configuration
|
|
424
|
+
|
|
425
|
+
The modern architecture uses JSON imports with `with {type: 'json'}` syntax, which requires Chrome 146+. Update `playwright.config.ts` to only run Chromium tests:
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
projects: [
|
|
429
|
+
{
|
|
430
|
+
name: 'chromium',
|
|
431
|
+
use: { ...devices['Desktop Chrome'] },
|
|
432
|
+
},
|
|
433
|
+
// Commented out - requires Chrome 146+ features (JSON imports with type assertion)
|
|
434
|
+
// {
|
|
435
|
+
// name: 'firefox',
|
|
436
|
+
// use: { ...devices['Desktop Firefox'] },
|
|
437
|
+
// },
|
|
438
|
+
// {
|
|
439
|
+
// name: 'webkit',
|
|
440
|
+
// use: { ...devices['Desktop Safari'] },
|
|
441
|
+
// },
|
|
442
|
+
],
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Using the Infer Pattern for Element Conventions
|
|
446
|
+
|
|
447
|
+
### Overview
|
|
448
|
+
|
|
449
|
+
The modern architecture provides a standardized way to infer element properties and event types through the **`infer` pattern** from `inferencer`. This eliminates the need for hardcoded element type checks and provides a consistent, extensible approach to element conventions.
|
|
450
|
+
|
|
451
|
+
### The Infer Function
|
|
452
|
+
|
|
453
|
+
Add this helper function at the bottom of your enhancement class file:
|
|
454
|
+
|
|
455
|
+
```javascript
|
|
456
|
+
/** @import {Infer} from './types/inferencer/types' */
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* @param {Element & ElementEnhancementGateway} from
|
|
460
|
+
*/
|
|
461
|
+
async function infer(from){
|
|
462
|
+
return /** @type {Infer} */ (
|
|
463
|
+
/** @type {any} */ (
|
|
464
|
+
from.enh.get((await import('inferencer/inferencer.js')).registryItem)
|
|
465
|
+
)
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### What It Provides
|
|
471
|
+
|
|
472
|
+
The `infer` function returns an object with:
|
|
473
|
+
- **`eventType`**: The most appropriate event type for the element (e.g., 'click', 'input', 'change')
|
|
474
|
+
- **`propName`**: The most appropriate property name for the element (e.g., 'checked', 'value', 'textContent')
|
|
475
|
+
- **`value`**: The current value of the inferred property (getter/setter)
|
|
476
|
+
- **`getPropagator()`**: Returns a propagator EventTarget for observing property changes
|
|
477
|
+
|
|
478
|
+
### Usage Examples
|
|
479
|
+
|
|
480
|
+
#### Inferring Event Type
|
|
481
|
+
|
|
482
|
+
```javascript
|
|
483
|
+
// ❌ Hardcoded logic
|
|
484
|
+
if (localEventType === undefined) {
|
|
485
|
+
const tagName = enhancedElement.tagName.toLowerCase();
|
|
486
|
+
if(tagName === 'input' || tagName === 'textarea' || tagName === 'select'){
|
|
487
|
+
localEventType = 'input';
|
|
488
|
+
} else {
|
|
489
|
+
localEventType = 'click';
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ✅ Use infer
|
|
494
|
+
if (localEventType === undefined) {
|
|
495
|
+
localEventType = (await infer(enhancedElement)).eventType;
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
#### Inferring Property Name
|
|
500
|
+
|
|
501
|
+
```javascript
|
|
502
|
+
// ❌ Hardcoded logic
|
|
503
|
+
if(!propertyName){
|
|
504
|
+
const tagName = target.tagName.toLowerCase();
|
|
505
|
+
if(tagName === 'input'){
|
|
506
|
+
const inputType = target.getAttribute('type');
|
|
507
|
+
propertyName = (inputType === 'checkbox' || inputType === 'radio') ? 'checked' : 'value';
|
|
508
|
+
} else {
|
|
509
|
+
propertyName = 'textContent';
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ✅ Use infer
|
|
514
|
+
if(!propertyName){
|
|
515
|
+
propertyName = (await infer(target)).propName;
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
#### Getting/Setting Inferred Property Value
|
|
520
|
+
|
|
521
|
+
```javascript
|
|
522
|
+
const inference = await infer(target);
|
|
523
|
+
const currentValue = inference.value;
|
|
524
|
+
inference.value = !inference.value; // Toggle
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### Inferring from Name Attribute (Empty Attribute Value)
|
|
528
|
+
|
|
529
|
+
When the attribute value is empty, infer both event type and property name:
|
|
530
|
+
|
|
531
|
+
```javascript
|
|
532
|
+
if(statements.length === 0){
|
|
533
|
+
const name = enhancedElement.getAttribute('name');
|
|
534
|
+
if(name){
|
|
535
|
+
statements.push({
|
|
536
|
+
value: {
|
|
537
|
+
prop: name,
|
|
538
|
+
localEventType: (await infer(enhancedElement)).eventType,
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Benefits
|
|
546
|
+
|
|
547
|
+
1. **Consistency**: All enhancements use the same inference logic
|
|
548
|
+
2. **Extensibility**: New element types can be supported by updating inferencer, not each enhancement
|
|
549
|
+
3. **Maintainability**: No duplicated element type checking code
|
|
550
|
+
4. **Type Safety**: TypeScript definitions ensure correct usage
|
|
551
|
+
5. **Future-proof**: Custom elements can provide their own inference hints
|
|
552
|
+
|
|
553
|
+
## Binding Enhancement Patterns
|
|
554
|
+
|
|
555
|
+
If your enhancement needs to synchronize property values between elements (two-way data binding), the following patterns from be-bound apply.
|
|
556
|
+
|
|
557
|
+
### The InferencedPropagator Pattern
|
|
558
|
+
|
|
559
|
+
Binding enhancements need to detect property changes on arbitrary elements — both custom elements (which may have roundabout propagators) and native elements (which don't). The `inferencer` package provides `InferencedPropagator` which handles this transparently:
|
|
560
|
+
|
|
561
|
+
```javascript
|
|
562
|
+
const localInference = await infer(enhancedElement);
|
|
563
|
+
const localPropagator = await localInference.getPropagator();
|
|
564
|
+
|
|
565
|
+
// Works for both custom elements and built-in elements
|
|
566
|
+
localPropagator.addEventListener('value', () => {
|
|
567
|
+
// property changed
|
|
568
|
+
});
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
**How `getPropagator()` works:**
|
|
572
|
+
- For **custom elements** with roundabout: returns the element's native `propagator` EventTarget
|
|
573
|
+
- For **built-in elements**: creates an `InferencedPropagator` that uses best-effort detection (native events, setter interception, attribute observation, polling fallback)
|
|
574
|
+
|
|
575
|
+
The propagator's `addEventListener` uses the **property name** as the event type (not a DOM event name).
|
|
576
|
+
|
|
577
|
+
### contentEditable Elements
|
|
578
|
+
|
|
579
|
+
The `InferencedPropagator` supports contentEditable elements by detecting `element.isContentEditable` and listening for the native `input` event. The browser does NOT fire property setters when users type in a contentEditable element — it mutates the DOM directly. The `input` event is the only reliable signal.
|
|
580
|
+
|
|
581
|
+
### upSearch: Resolving Remote Targets
|
|
582
|
+
|
|
583
|
+
The `inferencer/upSearch.js` function resolves remote binding targets:
|
|
584
|
+
|
|
585
|
+
```javascript
|
|
586
|
+
const target = await upSearch(enhancedElement, remoteId);
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
- If `remoteId` is truthy: calls `getRootNode().getElementById(remoteId)`
|
|
590
|
+
- If `remoteId` is falsy: traverses up to the nearest `[itemscope]` ancestor, or the shadow root's host
|
|
591
|
+
|
|
592
|
+
### Path-Based Property Access
|
|
593
|
+
|
|
594
|
+
When binding to nested properties, use the `?.` path syntax to avoid conflicts with period-based statement separation:
|
|
595
|
+
|
|
596
|
+
```html
|
|
597
|
+
<form 🪢="between ?.rating?.value@change and #alternativeRating.">
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Explicit Event Listening with `@` Syntax
|
|
601
|
+
|
|
602
|
+
When the inferred event isn't appropriate, allow users to specify the event by appending `@eventName`:
|
|
603
|
+
|
|
604
|
+
```html
|
|
605
|
+
<form 🪢="between ?.rating?.value@change and #alternativeRating.">
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
In `hydrate`, check for `localEvent` and use it instead of the inferred propagator when present.
|
|
609
|
+
|
|
610
|
+
### Tie-Breaking for Initial Reconciliation
|
|
611
|
+
|
|
612
|
+
When two-way binding is first established, a `breakTie` function determines which value wins based on type specificity:
|
|
613
|
+
|
|
614
|
+
```
|
|
615
|
+
object > function > symbol > bigint > number > boolean > string > null > undefined
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Within the same type, longer string representations win. Equal values result in no action.
|
|
619
|
+
|
|
620
|
+
## Lessons Learned / Common Pitfalls
|
|
621
|
+
|
|
622
|
+
### Compact/Action Conflicts
|
|
623
|
+
|
|
624
|
+
Do NOT define the same method in both `actions` and `compacts` in your `emc.mjs` `customData`:
|
|
625
|
+
|
|
626
|
+
- **Compacts** automatically call methods when properties change (e.g., `when_triggerInsertPosition_changes_call_addDeleteBtn`)
|
|
627
|
+
- **Actions** define when methods should be called based on property availability (e.g., `ifAllOf: ['prop1', 'prop2']`)
|
|
628
|
+
|
|
629
|
+
If a method is already invoked by a compact, adding it to `actions` will cause a "Conflict detected" error from roundabout.
|
|
630
|
+
|
|
631
|
+
### Emoji Shorthand: Always Spread `...myJSON`
|
|
632
|
+
|
|
633
|
+
When creating emoji `.mjs` files, always spread `...myJSON` at the top level:
|
|
634
|
+
|
|
635
|
+
```javascript
|
|
636
|
+
const emc = {
|
|
637
|
+
...myJSON, // ← CRITICAL: carries over customData
|
|
638
|
+
enhConfig: {
|
|
639
|
+
...myJSON.enhConfig,
|
|
640
|
+
enhKey: '⿻',
|
|
641
|
+
withAttrs: {
|
|
642
|
+
...myJSON.enhConfig.withAttrs,
|
|
643
|
+
base: '⿻'
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Without `...myJSON`, the emoji JSON will be missing `customData` (actions, weakRef, defaultPropVals), and the enhancement will appear to load but won't respond to events.
|
|
650
|
+
|
|
651
|
+
### Chained Accessor `?.` vs Period `.`
|
|
652
|
+
|
|
653
|
+
The parsers (`parse-pattern-statements`, `parse-grouped-capture-statements`) split statements on periods. If you need to reference properties using dot notation in attribute values, use `?.` instead:
|
|
654
|
+
|
|
655
|
+
```html
|
|
656
|
+
<!-- ❌ Period conflicts with statement splitting -->
|
|
657
|
+
<button ⏻="[#myLight].isOn">Toggle</button>
|
|
658
|
+
|
|
659
|
+
<!-- ✅ Chained accessor avoids conflict -->
|
|
660
|
+
<button ⏻="[#myLight]?.isOn">Toggle</button>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Parser Selection
|
|
664
|
+
|
|
665
|
+
| Use case | Parser |
|
|
666
|
+
|----------|--------|
|
|
667
|
+
| Flat target objects, no nesting | `parse-grouped-capture-statements` |
|
|
668
|
+
| Nested object structures via dot notation in capture groups | `parse-pattern-statements` |
|
|
669
|
+
|
|
670
|
+
Choose based on your data structure needs, not syntax complexity. The parser choice directly affects your type definitions.
|
|
671
|
+
|
|
672
|
+
### Utility Import Paths
|
|
673
|
+
|
|
674
|
+
Common utilities live in `be-hive`, not `trans-render`:
|
|
675
|
+
|
|
676
|
+
```javascript
|
|
677
|
+
// ✅ Correct
|
|
678
|
+
import { findAdjacentElement } from 'be-hive/findAdjacentElement.js';
|
|
679
|
+
|
|
680
|
+
// ❌ Wrong (legacy path)
|
|
681
|
+
import { findAdjacentElement } from 'trans-render/lib/findAdjacentElement.js';
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### Debugging Tips
|
|
685
|
+
|
|
686
|
+
1. **Check the generated JSON** — Run `node emc.mjs` and verify all sections (especially `customData`) are present
|
|
687
|
+
2. **Console log parsedStatements** — Add `console.log({parsedStatements})` in `hydrate` to see parser output
|
|
688
|
+
3. **Verify parser loading** — Check browser console for "Parser not found" errors
|
|
689
|
+
4. **Test incrementally** — Get one example working before moving to the next
|
|
690
|
+
5. **Compare with working examples** — Use be-clonable (architecture), do-invoke (custom parser), do-toggle (infer pattern) as references
|
|
691
|
+
|
|
692
|
+
### Testing Strategy
|
|
693
|
+
|
|
694
|
+
Build incrementally:
|
|
695
|
+
|
|
696
|
+
1. **Get the basic case working first** (simple property/action trigger)
|
|
697
|
+
2. **Add inference** (infer from name attribute when attribute value is empty)
|
|
698
|
+
3. **Add event customization** (custom event types)
|
|
699
|
+
4. **Add selector/remote support** (target peer elements)
|
|
700
|
+
|
|
701
|
+
Don't try to implement all features at once.
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
*Last updated: June 2026*
|