face-up 0.0.0 → 0.0.2

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 (46) hide show
  1. package/.gitmodules +3 -0
  2. package/.kiro/steering/project-context.md +17 -0
  3. package/.vscode/settings.json +3 -0
  4. package/FaceUp.js +305 -0
  5. package/README.md +162 -2
  6. package/imports.html +8 -0
  7. package/package.json +30 -20
  8. package/tests/test1.html +115 -0
  9. package/types/.kiro/specs/conversion-template/README.md +128 -0
  10. package/types/.kiro/specs/conversion-template/design.md +360 -0
  11. package/types/.kiro/specs/conversion-template/requirements.md +191 -0
  12. package/types/.kiro/specs/conversion-template/tasks.md +174 -0
  13. package/types/.kiro/steering/coding-standards.md +17 -0
  14. package/types/.kiro/steering/conversion-guide.md +103 -0
  15. package/types/.kiro/steering/declarative-configuration.md +108 -0
  16. package/types/.kiro/steering/emc-json-serializability.md +306 -0
  17. package/types/EnhancementConversionInstructions.md +1626 -0
  18. package/types/LICENSE +21 -0
  19. package/types/NewCustomElementFeature.md +673 -0
  20. package/types/NewEnhancementInstructions.md +395 -0
  21. package/types/README.md +2 -0
  22. package/types/agrace/types.d.ts +11 -0
  23. package/types/assign-gingerly/types.d.ts +328 -0
  24. package/types/be-a-beacon/types.d.ts +17 -0
  25. package/types/be-bound/types.d.ts +61 -0
  26. package/types/be-buttoned-up/types.d.ts +19 -0
  27. package/types/be-clonable/types.d.ts +36 -0
  28. package/types/be-committed/types.d.ts +22 -0
  29. package/types/be-decked-with/types.d.ts +26 -0
  30. package/types/be-delible/types.d.ts +25 -0
  31. package/types/be-reflective/types.d.ts +80 -0
  32. package/types/be-render-neutral/types.d.ts +29 -0
  33. package/types/be-typed/types.d.ts +31 -0
  34. package/types/do-inc/types.d.ts +56 -0
  35. package/types/do-invoke/types.d.ts +38 -0
  36. package/types/do-merge/types.d.ts +28 -0
  37. package/types/do-toggle/types.d.ts +31 -0
  38. package/types/face-up/types.d.ts +104 -0
  39. package/types/global.d.ts +29 -0
  40. package/types/id-generation/types.d.ts +26 -0
  41. package/types/inferencer/types.d.ts +46 -0
  42. package/types/mount-observer/types.d.ts +363 -0
  43. package/types/nested-regex-groups/types.d.ts +101 -0
  44. package/types/roundabout/types.d.ts +255 -0
  45. package/types/time-ticker/types.d.ts +66 -0
  46. package/types/truth-sourcer/types.d.ts +46 -0
@@ -0,0 +1,1626 @@
1
+ # Enhancement Conversion Instructions
2
+
3
+ > **Scope:** This document applies to **enhancements** (declarative behaviors added to existing HTML elements via attributes using `be-hive` and `mount-observer`). It does NOT apply to custom elements. If you are creating a brand new enhancement (not converting a legacy one), see [NewEnhancementInstructions.md](./NewEnhancementInstructions.md) instead.
4
+
5
+ ## Introduction
6
+
7
+ This document provides step-by-step instructions for converting legacy "be-*" enhancement projects to the modern architecture. The conversion process has been successfully applied to several projects including:
8
+
9
+ - **[be-clonable](https://github.com/bahrus/be-clonable)** ⭐ **RECOMMENDED REFERENCE** - The most up-to-date implementation with the latest architectural improvements. Use this as your primary reference.
10
+ - **[do-invoke](https://github.com/bahrus/do-invoke)** ⭐ **CUSTOM PARSER REFERENCE** - The first conversion using custom parser integration with nested paths and default values. Use this as your reference for complex attribute parsing.
11
+ - [be-committed](https://github.com/bahrus/be-committed)
12
+ - [be-decked-with](https://github.com/bahrus/be-decked-with)
13
+
14
+ Each of these repositories contains a "legacy" folder showing the original implementation for reference. When in doubt about implementation details, refer to be-clonable first as it demonstrates the cleanest, most refined patterns. For custom parser implementations, refer to do-invoke.
15
+
16
+ **Note:** be-a-beacon is intentionally not listed as a reference because its requirements are too simple - it just fires an event on construction without needing reactive properties or actions, making it an outlier that doesn't benefit from the roundabout architecture.
17
+
18
+ ## What This Conversion Achieves
19
+
20
+ The conversion modernizes HTML element enhancement libraries that follow the "be-enhanced" pattern. These enhancements add declarative behaviors to HTML elements through attributes, enabling progressive enhancement without requiring custom elements.
21
+
22
+ The new architecture provides:
23
+
24
+ - **Simplified configuration**: Streamlined setup using be-hive's mount observer pattern
25
+ - **Better type safety**: Centralized type definitions in a shared types submodule
26
+ - **Cleaner separation**: Distinct interfaces for end-user props, internal props, and actions
27
+ - **Improved maintainability**: Consistent structure across all enhancement projects
28
+ - **Enhanced DX**: Better IDE support through TypeScript definitions
29
+
30
+ ## High-Level Conversion Overview
31
+
32
+ The conversion transforms projects from a legacy architecture to a modern one that:
33
+
34
+ 1. Uses `be-hive` for enhancement registration and lifecycle management
35
+ 2. Centralizes type definitions in a shared `types` submodule
36
+ 3. Separates concerns between end-user properties, internal state, and actions
37
+ 4. Adopts a declarative configuration approach for defining enhancement behavior
38
+ 5. Leverages modern ES modules and import patterns
39
+
40
+ The process involves updating:
41
+ - Project structure and file organization
42
+ - Type definitions and interfaces
43
+ - Enhancement registration and initialization
44
+ - Configuration and property handling
45
+ - Import/export patterns
46
+
47
+ ## Conversion Steps
48
+
49
+ ### Step 1: Migrate Type Definitions to New Submodule
50
+
51
+ The first step is to move your project's type definitions from the `ts-refs` submodule to the `types` submodule.
52
+
53
+ **Why this step?** The legacy architecture used a git submodule called `ts-refs` to share type definitions across all be-* projects. The modern approach uses a renamed submodule called `types` with a clearer, more intuitive name that better communicates its purpose.
54
+
55
+ **Instructions:**
56
+
57
+ 1. Check if a `ts-refs` folder exists in your project root (it's a git submodule)
58
+ 2. If it exists, locate the subfolder matching your project name (e.g., `ts-refs/be-clonable` for the be-clonable project)
59
+ 3. Copy that folder and its contents into the `types` folder (e.g., copy `ts-refs/be-clonable/` to `types/be-clonable/`)
60
+ 4. Delete the entire `ts-refs` folder using `Remove-Item -Recurse -Force ts-refs` (or `rm -rf ts-refs` on Unix-like systems)
61
+
62
+ **Result:** You should now have your type definitions at `types/[project-name]/types.d.ts` and the `ts-refs` submodule should be removed.
63
+
64
+ ### Step 2: Archive Legacy Implementation
65
+
66
+ Before converting to the new architecture, preserve the existing implementation in a `legacy` folder for reference.
67
+
68
+ **Why this step?** Keeping the original implementation allows you to compare the old and new approaches, verify behavior during conversion, and provides a fallback if needed. It also serves as documentation for others learning about the architectural changes.
69
+
70
+ **Instructions:**
71
+
72
+ 1. Create a `legacy` folder in your project root if it doesn't exist
73
+ 2. If the `legacy` folder already exists, empty its contents: `Remove-Item -Path legacy/* -Force` (or `rm -rf legacy/*` on Unix-like systems)
74
+ 3. Move all `.js`, `.mjs`, and `.json` files from the root directory to the `legacy` folder, excluding `package.json` and `package-lock.json`
75
+ - Example: `Move-Item -Path *.js -Destination legacy/`
76
+ - For .json files: `Get-ChildItem -Filter *.json | Where-Object { $_.Name -notlike 'package*.json' } | Move-Item -Destination legacy/`
77
+
78
+ **Result:** Your `legacy` folder should now contain copies of all implementation files that will be converted in subsequent steps.
79
+
80
+ ### Step 3: Update package.json Dependencies and Scripts
81
+
82
+ Update the package.json to use the modern architecture's dependencies and build scripts.
83
+
84
+ **Why this step?** The new architecture uses a simplified dependency set with be-hive for enhancement management and roundabout-lib for utilities. The build process also changes to generate configuration files for both the full name and emoji shorthand versions.
85
+
86
+ **Instructions:**
87
+
88
+ 1. Update the `scripts` section:
89
+ ```json
90
+ "scripts": {
91
+ "build": "node emc.mjs > emc.json && node [emoji].mjs > [emoji].json",
92
+ "serve": "node ./node_modules/spa-ssi/serve.js",
93
+ "test": "playwright test",
94
+ "safari": "npx playwright wk http://localhost:8000",
95
+ "update": "ncu -u && npm install"
96
+ }
97
+ ```
98
+ - Replace `[emoji]` with the emoji from your README.md title (e.g., `⿻` for be-clonable)
99
+ - If there's no emoji in the README title, omit the `&& node [emoji].mjs > [emoji].json` part
100
+
101
+ 2. Update the `dependencies` section:
102
+ ```json
103
+ "dependencies": {
104
+ "be-hive": "0.1.9",
105
+ "mount-observer": "0.0.16",
106
+ "roundabout-lib": "0.0.2",
107
+ "nested-regex-groups": "0.0.1"
108
+ }
109
+ ```
110
+
111
+ **IMPORTANT - Use Specific Versions:** Always use specific point versions (e.g., `"0.1.9"`) rather than version ranges (e.g., `"^0.1.9"` or `"~0.1.9"`). This ensures:
112
+ - Reproducible builds across environments
113
+ - No unexpected breaking changes from automatic updates
114
+ - Explicit control over when dependencies are updated
115
+ - Easier debugging when issues arise
116
+
117
+ **Note:** Including `mount-observer` as a direct dependency ensures it's installed at the root `node_modules/` level, making it accessible via the import map and available for direct use in your code. The `nested-regex-groups` package is optional but recommended if your enhancement requires complex attribute parsing.
118
+
119
+ 3. **DO NOT modify the `devDependencies` section** - leave it as-is. The conversion only updates runtime dependencies, not development/testing dependencies.
120
+
121
+ 4. Verify the `update` script exists in the `scripts` section:
122
+ ```json
123
+ "update": "ncu -u && npm install"
124
+ ```
125
+ This script uses npm-check-updates (ncu) to update all dependencies to their latest versions. If it's missing, add it.
126
+
127
+ 5. Run `npm run update` to fetch and install the latest versions of all dependencies
128
+
129
+ **IMPORTANT:** After updating package.json, you MUST run `npm run update` to install the new dependencies before proceeding with the conversion. The subsequent steps require these packages to be installed. Use `npm run update` (not `npm install`) to ensure you get the latest compatible versions.
130
+
131
+ **Result:** Your package.json should now use the modern dependency set, and running the update script will ensure you have the latest compatible versions.
132
+
133
+ ### Step 4: Update imports.html
134
+
135
+ Create or update the imports.html file to use the modern import map pattern.
136
+
137
+ **Why this step?** The import map provides a clean way to map bare module specifiers to their locations, enabling browser-native ES module imports without bundling. This approach follows web standards and improves development experience.
138
+
139
+ **Instructions:**
140
+
141
+ 1. Create or replace the `imports.html` file in your project root with the following structure:
142
+ ```html
143
+ <script type=importmap >
144
+ {
145
+ "imports": {
146
+ "assign-gingerly/": "/node_modules/assign-gingerly/",
147
+ "[project-name]/": "/",
148
+ "be-hive/": "/node_modules/be-hive/",
149
+ "inferencer/": "/node_modules/inferencer/",
150
+ "mount-observer/": "/node_modules/mount-observer/",
151
+ "roundabout-lib/": "/node_modules/roundabout-lib/",
152
+ "id-generation/": "/node_modules/id-generation/",
153
+ "nested-regex-groups/": "/node_modules/nested-regex-groups/"
154
+ }
155
+ }
156
+ </script>
157
+ ```
158
+ 2. Replace `[project-name]` with your actual project name (e.g., `be-clonable`)
159
+ 3. The key difference: the project itself maps to `"/"` (root), while dependencies map to `/node_modules/[package]/`
160
+ 4. **Note:** The `id-generation` and `nested-regex-groups` entries are required if your enhancement uses custom parsers (Step 7a). If you're not using custom parsers, these can be omitted.
161
+
162
+ **Result:** Your imports.html file should now provide proper import mappings for the browser.
163
+
164
+ ### Step 5: Establish Coding Standards
165
+
166
+ Create a coding standards steering document to guide development practices.
167
+
168
+ **Why this step?** Establishing clear conventions ensures consistency across the codebase and helps maintain the architectural patterns of the modern approach.
169
+
170
+ **Instructions:**
171
+
172
+ 1. Create the directory structure: `.kiro/steering/`
173
+ 2. Create a file `.kiro/steering/coding-standards.md` with the following content:
174
+
175
+ ```markdown
176
+ # Coding Standards
177
+
178
+ ## JavaScript Module Conventions
179
+
180
+ ### Import Maps
181
+ - Use import maps with explicit, bare specifiers ending with `*.js` for all JavaScript references that run in the browser
182
+ - Example: `"be-hive/": "/node_modules/be-hive/"`
183
+
184
+ ### File Extensions
185
+ - Use `*.mjs` files exclusively for npm build scripts, not for browser code
186
+ - Use `*.js` files (not `*.ts`) for all browser-executable code
187
+ - Enable TypeScript support in `*.js` files via `@ts-check` directive at the top of files
188
+
189
+ ### TypeScript Support
190
+ - Add `// @ts-check` at the beginning of JavaScript files to enable TypeScript checking
191
+ - Use JSDoc comments for type annotations when needed
192
+ - Leverage type definitions from the `types` submodule
193
+ ```
194
+
195
+ **Result:** Your project now has documented coding standards that will be automatically included in Kiro's context when working on the codebase.
196
+
197
+ ### Step 6: Update Type Definitions to be Standalone
198
+
199
+ Modernize the type definitions to be self-contained without external dependencies.
200
+
201
+ **Why this step?** The legacy types relied on imports from trans-render and be-enhanced packages. The modern approach makes type files standalone, improving portability and reducing coupling between packages.
202
+
203
+ **Instructions:**
204
+
205
+ 1. Open `types/[project-name]/types.d.ts`
206
+ 2. Remove the import statement: `import { IEnhancement, BEAllProps } from '../trans-render/be/types';`
207
+ 3. Remove `extends IEnhancement` from the `EndUserProps` interface
208
+ 4. Add `enhancedElement: Element;` to the `AllProps` interface (this is the key property that was previously inherited)
209
+ 5. Remove the `BAP` type alias entirely
210
+ 6. Replace all occurrences of `BAP` with `AP` in the `Actions` interface method signatures
211
+
212
+ **Example transformation:**
213
+
214
+ Before:
215
+ ```typescript
216
+ import { IEnhancement, BEAllProps } from '../trans-render/be/types';
217
+
218
+ export interface EndUserProps extends IEnhancement{
219
+ triggerInsertPosition: InsertPosition;
220
+ }
221
+
222
+ export interface AllProps extends EndUserProps{
223
+ byob?: boolean;
224
+ }
225
+
226
+ export type BAP = AP & BEAllProps;
227
+
228
+ export interface Actions{
229
+ addCloneBtn(self: BAP): ProPAP;
230
+ }
231
+ ```
232
+
233
+ After:
234
+ ```typescript
235
+ export interface EndUserProps{
236
+ triggerInsertPosition: InsertPosition;
237
+ }
238
+
239
+ export interface AllProps extends EndUserProps{
240
+ enhancedElement: Element;
241
+ byob?: boolean;
242
+ }
243
+
244
+ export interface Actions{
245
+ addCloneBtn(self: AP): ProPAP;
246
+ }
247
+ ```
248
+
249
+ **Result:** Your type definitions are now standalone and don't depend on external type packages.
250
+
251
+ ### Step 7: Create emc.mjs Build Configuration
252
+
253
+ Transform the legacy browser-based emc.js into a build-time emc.mjs configuration file that generates emc.json.
254
+
255
+ **Why this step?** The legacy architecture used emc.js as a browser module that imported be-hive and registered enhancements at runtime. The modern approach uses emc.mjs as a build script that generates a static JSON configuration file, improving performance and separating build-time concerns from runtime code.
256
+
257
+ **Key Changes:**
258
+
259
+ 1. **withAttrs replaces base/branches/map**: The new assign-gingerly withAttrs pattern provides a cleaner, more intuitive way to map HTML attributes to properties using template syntax
260
+ 2. **Static config moves to customData**: Configuration from the legacy be-*.js static config section (actions, handlers, compacts) moves into the customData section of emc.mjs
261
+ 3. **Property inference**: propDefaults and propInfo are no longer needed - the roundabout library automatically infers property names from actions, handlers, and compacts
262
+ 4. **Build-time only**: emc.mjs is used only for generating JSON, not loaded in the browser
263
+
264
+ **Instructions:**
265
+
266
+ 1. Create `emc.mjs` in your project root
267
+ 2. Start with the basic structure:
268
+
269
+ ```javascript
270
+ //@ts-check
271
+
272
+ /** @import {EMC} from './types/mount-observer/types' */;
273
+ /** @import {AllProps, Actions} from './types/[project-name]/types' */
274
+ /** @import {RAConfig} from './types/roundabout/types' */
275
+
276
+ /**
277
+ * @type {EMC<any, AllProps, Element, RAConfig<AllProps, Actions> >}
278
+ */
279
+ export const emc = {
280
+ enhConfig: {
281
+ enhKey: '[EnhancementKey]',
282
+ spawn: '[project-name]/[project-name].js',
283
+ withAttrs: {
284
+ base: '[project-name]',
285
+ // Map each property to an attribute using ${base} template
286
+ propertyName: '${base}-property-name',
287
+ // For boolean properties, add instanceOf
288
+ _booleanProp: {
289
+ instanceOf: 'Boolean'
290
+ }
291
+ }
292
+ },
293
+ customData: {
294
+ weakRef: {
295
+ properties: ['enhancedElement']
296
+ },
297
+ actions: {
298
+ // Copy from legacy static config
299
+ },
300
+ handlers: {
301
+ // Copy from legacy static config
302
+ },
303
+ compacts: {
304
+ // Copy from legacy static config
305
+ },
306
+ defaultPropVals: {
307
+ // Copy from legacy static config.propDefaults
308
+ // Note: propDefaults → defaultPropVals
309
+ }
310
+ }
311
+ }
312
+
313
+ export function render(){
314
+ return JSON.stringify(emc, null, 4);
315
+ }
316
+ ```
317
+
318
+ 3. **Configure withAttrs**: For each EndUserProps property in your types file:
319
+ - Add `propertyName: '${base}-property-name'` for string/number properties
320
+ - For boolean properties, use the underscore prefix and instanceOf pattern:
321
+ ```javascript
322
+ _nudge: {
323
+ instanceOf: 'Boolean'
324
+ }
325
+ ```
326
+ - The `${base}` template variable references the base attribute name
327
+
328
+ 4. **Configure weakRef**:
329
+ - Add `weakRef: { properties: ['enhancedElement'] }` at the start of customData
330
+ - If any action methods reference other element properties (like `trigger`, `button`, etc.), add those to the properties array as well
331
+ - This tells roundabout to automatically create property getters/setters that store weak references
332
+
333
+ 5. **Migrate static config to customData**:
334
+ - Copy `actions`, `handlers`, and `compacts` from the legacy be-*.js static config
335
+ - For any action methods that have `ifAllOf` and reference `enhancedElement` in their code, add `'enhancedElement'` to the `ifAllOf` array
336
+ - Copy `propDefaults` to `defaultPropVals` in customData (note the name change from propDefaults to defaultPropVals)
337
+ - Do NOT copy `propInfo` - property names are automatically inferred by roundabout from actions, handlers, and compacts
338
+ - Remove any `positractions` - these are handled differently in the new architecture
339
+
340
+ **Example Transformation:**
341
+
342
+ Legacy emc.js:
343
+ ```javascript
344
+ export const emc = {
345
+ base: 'be-committed',
346
+ branches: ['', 'to', 'nudges'],
347
+ map: {
348
+ '0.0': { instanceOf: 'Object', mapsTo: '.' },
349
+ '1.0': { instanceOf: 'String', mapsTo: 'to' },
350
+ '2.0': { instanceOf: 'Boolean', mapsTo: 'nudges' }
351
+ },
352
+ enhPropKey: 'beCommitted',
353
+ importEnh: async () => {
354
+ const {BeCommitted} = await import('./be-committed.js');
355
+ return BeCommitted;
356
+ },
357
+ };
358
+ ```
359
+
360
+ Legacy be-committed.js static config:
361
+ ```javascript
362
+ static config = {
363
+ propDefaults: { on: 'keyup' },
364
+ propInfo: { to: {}, nudges: {} },
365
+ compacts: { when_on_changes_call_hydrate: 0 },
366
+ positractions: [resolved, rejected]
367
+ }
368
+ ```
369
+
370
+ Modern emc.mjs:
371
+ ```javascript
372
+ export const emc = {
373
+ enhConfig: {
374
+ enhKey: 'BeCommitted',
375
+ spawn: 'be-committed/be-committed.js',
376
+ withAttrs: {
377
+ base: 'be-committed',
378
+ to: '${base}-to',
379
+ nudge: '${base}-nudge',
380
+ _nudge: { instanceOf: 'Boolean' }
381
+ }
382
+ },
383
+ customData: {
384
+ weakRef: {
385
+ properties: ['enhancedElement']
386
+ },
387
+ actions: {
388
+ hydrate: { ifAllOf: ['on', 'to', 'enhancedElement'] }
389
+ },
390
+ compacts: {
391
+ when_resolved_changes_dispatch: 'resolved',
392
+ },
393
+ defaultPropVals: {
394
+ on: 'keyup'
395
+ }
396
+ }
397
+ }
398
+
399
+ export function render(){
400
+ return JSON.stringify(emc, null, 4);
401
+ }
402
+
403
+ console.log(render());
404
+ ```
405
+
406
+ **Result:** You now have a build-time configuration file that will generate emc.json when you run the build script.
407
+
408
+ ### Step 7a: Custom Parser Integration (Optional)
409
+
410
+ #### Overview
411
+
412
+ Some be-* enhancements require **custom attribute parsers** to transform complex attribute syntax into structured data. The legacy architecture used `regExpExts` patterns in emc.js, while the modern architecture uses **built-in parsers from nested-regex-groups** integrated through the `parserConfig` property.
413
+
414
+ **When do you need a custom parser?**
415
+
416
+ - Your enhancement accepts complex attribute syntax (e.g., `do-invoke="methodName on click"` or `be-switched="case1: /pattern1/ | case2: /pattern2/"`)
417
+ - You need to parse multiple patterns or conditional logic from a single attribute
418
+ - Simple string-to-property mapping isn't sufficient
419
+
420
+ **References:**
421
+ - [nested-regex-groups](https://github.com/bahrus/nested-regex-groups) - Documentation for the parsing library
422
+ - [nested-regex-groups npm](https://www.npmjs.com/package/nested-regex-groups) - Install with `npm install nested-regex-groups`
423
+ - **[do-invoke](https://github.com/bahrus/do-invoke)** ⭐ - Complete working example demonstrating custom parser with nested paths and default values
424
+
425
+ **Key Insight:** You don't need to write custom parser functions! The `nested-regex-groups` package provides built-in parsers that you configure with pattern definitions in your `emc.mjs` file.
426
+
427
+ #### Built-in Parsers Available
428
+
429
+ The modern architecture provides these built-in parsers (no custom code needed):
430
+
431
+ 1. **`parse-pattern-statements`** - For flat objects (no nested properties)
432
+ - Use when your target type has no nested properties
433
+ - Example: `InvokingParameters { targetPart: string, localEventType?: string }`
434
+ - Parses multiple statements separated by periods
435
+ - **Use this for do-invoke style patterns**
436
+
437
+ 2. **`parse-grouped-captures`** - For flat objects, single statement
438
+ - Use when parsing a single statement into a flat object
439
+ - No statement splitting
440
+
441
+ 3. **`parse-grouped-capture-statements`** - For flat objects, multiple statements
442
+ - Similar to `parse-pattern-statements` but different API
443
+
444
+ **CRITICAL:** All content in `emc.mjs` must be JSON-serializable. The `render()` function exports the configuration as JSON, which means:
445
+ - No function references (including parser functions)
446
+ - No class constructors
447
+ - No symbols or other non-JSON types
448
+ - Parser names must be strings
449
+ - Pattern configurations must use `String.raw` for regex patterns
450
+
451
+ #### Step-by-Step Implementation
452
+
453
+ **Step 1: Define Pattern Configurations in emc.mjs**
454
+
455
+ Create pattern configurations using `PatternConfig` objects with `String.raw` for regex patterns:
456
+
457
+ ```javascript
458
+ // @ts-check
459
+
460
+ /** @import {EMC} from './types/mount-observer/types' */;
461
+ /** @import {AllProps, Actions} from './types/do-invoke/types' */
462
+ /** @import {RAConfig} from './types/roundabout/types' */
463
+ /** @import {PatternConfig} from './types/nested-regex-groups/types' */
464
+
465
+ /** @type {PatternConfig[]} */
466
+ const parsePatterns = [
467
+ {
468
+ name: 'targetsPartOnEventType',
469
+ pattern: String.raw `^(?<targetPart>.*) on (?<localEventType>.*)`,
470
+ description: 'Method/selector with explicit event type'
471
+ },
472
+ {
473
+ name: 'targetsPart',
474
+ pattern: String.raw `^(?<targetPart>.*)`,
475
+ description: 'Method/selector with default event type'
476
+ }
477
+ ];
478
+ ```
479
+
480
+ **Why `String.raw`?** Template literals with `String.raw` prevent JavaScript from interpreting backslashes as escape sequences, which is essential for regex patterns containing `\w`, `\s`, `\.`, etc.
481
+
482
+ **Pattern Priority:** Patterns are tried in order - most specific first. The first matching pattern wins.
483
+
484
+ **Step 2: Configure withAttrs to Use Built-in Parser**
485
+
486
+ Reference the built-in parser by name and pass your pattern configuration:
487
+
488
+ ```javascript
489
+ export const emc = {
490
+ enhConfig: {
491
+ enhKey: 'DoInvoke',
492
+ spawn: 'do-invoke/do-invoke.js',
493
+ withAttrs: {
494
+ base: 'do-invoke',
495
+ _base: {
496
+ mapsTo: 'invokeParamSets',
497
+ parser: 'parse-pattern-statements', // Built-in parser name
498
+ instanceOf: 'Array', // Result is an array
499
+ parserConfig: parsePatterns // Your pattern configs
500
+ }
501
+ }
502
+ },
503
+ customData: {
504
+ weakRef: {
505
+ properties: ['enhancedElement']
506
+ },
507
+ actions: {
508
+ hydrate: {
509
+ ifAllOf: ['invokeParamSets', 'enhancedElement']
510
+ }
511
+ }
512
+ }
513
+ };
514
+
515
+ export function render() {
516
+ return JSON.stringify(emc, null, 4);
517
+ }
518
+
519
+ console.log(render());
520
+ ```
521
+
522
+ **Key Configuration Properties:**
523
+
524
+ - **`parser`**: String name of built-in parser (`'parse-pattern-statements'`)
525
+ - **`parserConfig`**: Array of `PatternConfig` objects defining your patterns
526
+ - **`instanceOf`**: `'Array'` since the parser returns an array of parsed statements
527
+ - **`mapsTo`**: Property name where parsed result is stored (`'invokeParamSets'`)
528
+
529
+ **Step 3: Ensure Type Definitions Match**
530
+
531
+ Your type definitions should match the parser output structure:
532
+
533
+ ```typescript
534
+ // types/do-invoke/types.d.ts
535
+
536
+ export interface InvokingParameters {
537
+ targetPart: string; // Captured from pattern
538
+ localEventType?: string; // Optional, captured from pattern
539
+ }
540
+
541
+ export interface EndUserProps {
542
+ invokeParamSets: Array<InvokingParameters>; // Array of parsed statements
543
+ }
544
+
545
+ export interface AllProps extends EndUserProps {
546
+ enhancedElement: Element;
547
+ }
548
+ ```
549
+
550
+ **Important:**
551
+ - The property names in your type (`targetPart`, `localEventType`) must match the capture group names in your regex patterns (`(?<targetPart>...)`, `(?<localEventType>...)`).
552
+ - When using custom parsers, wrap your parsed property type with `StatementsResult<T>` from `nested-regex-groups`:
553
+ ```typescript
554
+ import { StatementsResult } from "../nested-regex-groups/types";
555
+
556
+ export interface EndUserProps {
557
+ parsedStatements: StatementsResult<IncParameters>;
558
+ }
559
+ ```
560
+ - The `StatementsResult<T>` type includes a `success` flag and a `statements` array containing your parsed data.
561
+
562
+ #### Complete Working Example: do-invoke
563
+
564
+ Here's the complete, tested implementation from do-invoke that demonstrates nested paths and default values:
565
+
566
+ **emc.mjs:**
567
+ ```javascript
568
+ // @ts-check
569
+
570
+ /** @import {EMC} from './types/mount-observer/types' */;
571
+ /** @import {AllProps, Actions} from './types/do-invoke/types' */
572
+ /** @import {RAConfig} from './types/roundabout/types' */
573
+ /** @import {PatternConfig} from './types/nested-regex-groups/types' */
574
+
575
+ const defaultVals = {
576
+ localEventType: 'click'
577
+ };
578
+
579
+ /** @type {PatternConfig[]} */
580
+ const parsePatterns = [
581
+ {
582
+ name: 'idWithMethodAndEvent',
583
+ pattern: String.raw `^#(?<targetSpecifier.targetElementId>[^?]+)\?\.(?<targetSpecifier.hostOrPeerMethodName>\w+) on (?<localEventType>\w+)$`,
584
+ description: 'Element ID with method and explicit event type: #{{id}}?.method on event',
585
+ defaultVals,
586
+ },
587
+ {
588
+ name: 'idWithMethod',
589
+ pattern: String.raw `^#(?<targetSpecifier.targetElementId>[^?]+)\?\.(?<targetSpecifier.hostOrPeerMethodName>\w+)$`,
590
+ description: 'Element ID with method, default event: #{{id}}?.method',
591
+ defaultVals,
592
+ },
593
+ {
594
+ name: 'methodWithEvent',
595
+ pattern: String.raw `^(?<targetSpecifier.hostOrPeerMethodName>\w+) on (?<localEventType>\w+)$`,
596
+ description: 'Method name with explicit event type: method on event',
597
+ defaultVals,
598
+ },
599
+ {
600
+ name: 'methodOnly',
601
+ pattern: String.raw `^(?<targetSpecifier.hostOrPeerMethodName>\w+)$`,
602
+ description: 'Method name only, default event: method',
603
+ defaultVals,
604
+ }
605
+ ];
606
+
607
+ /**
608
+ * @type {EMC<any, AllProps, Element, RAConfig<AllProps, Actions> >}
609
+ */
610
+ export const emc = {
611
+ enhConfig: {
612
+ enhKey: 'DoInvoke',
613
+ spawn: 'do-invoke/do-invoke.js',
614
+ withAttrs: {
615
+ base: 'do-invoke',
616
+ _base: {
617
+ mapsTo: 'invokeParamSet',
618
+ parser: 'parse-pattern-statements',
619
+ instanceOf: 'Array',
620
+ parserConfig: parsePatterns
621
+ }
622
+ }
623
+ },
624
+ customData: {
625
+ weakRef: {
626
+ properties: ['enhancedElement']
627
+ },
628
+ actions: {
629
+ hydrate: {
630
+ ifAllOf: ['invokeParamSet', 'enhancedElement']
631
+ }
632
+ }
633
+ }
634
+ };
635
+
636
+ export function render() {
637
+ return JSON.stringify(emc, null, 4);
638
+ }
639
+
640
+ console.log(render());
641
+ ```
642
+
643
+ **types/do-invoke/types.d.ts:**
644
+ ```typescript
645
+ import { StatementsResult } from "../nested-regex-groups/types";
646
+
647
+ export interface InvokingParameters {
648
+ targetSpecifier: {
649
+ hostOrPeerMethodName: string,
650
+ targetElementId?: string,
651
+ },
652
+ // defaults to "click" if not specified
653
+ localEventType: string,
654
+ }
655
+
656
+ export interface EndUserProps {
657
+ invokeParamSet: StatementsResult<InvokingParameters>;
658
+ }
659
+
660
+ export interface AllProps extends EndUserProps {
661
+ enhancedElement: Element;
662
+ resolved: boolean;
663
+ }
664
+ ```
665
+
666
+ **Key Features Demonstrated:**
667
+
668
+ 1. **Nested paths**: Using dot notation like `targetSpecifier.hostOrPeerMethodName` creates nested object structures
669
+ 2. **Default values**: The `defaultVals` object provides defaults for `localEventType: 'click'`
670
+ 3. **Multiple patterns**: Four patterns handle different syntax variations, ordered from most specific to least specific
671
+ 4. **StatementsResult type**: The parser returns a `StatementsResult<InvokingParameters>` which includes `success` flag and `statements` array
672
+
673
+ **Usage Examples:**
674
+ ```html
675
+ <!-- Pattern 1: Method only (uses default click event) -->
676
+ <button do-invoke="handleClick">Click me</button>
677
+ <!-- Parsed: { targetSpecifier: { hostOrPeerMethodName: "handleClick" }, localEventType: "click" } -->
678
+
679
+ <!-- Pattern 2: Method with explicit event -->
680
+ <input do-invoke="handleInput on input" />
681
+ <!-- Parsed: { targetSpecifier: { hostOrPeerMethodName: "handleInput" }, localEventType: "input" } -->
682
+
683
+ <!-- Pattern 3: Element ID with method (uses default click event) -->
684
+ <button do-invoke="#{{soul-searching}}?.engage">What have I done?</button>
685
+ <!-- Parsed: { targetSpecifier: { targetElementId: "soul-searching", hostOrPeerMethodName: "engage" }, localEventType: "click" } -->
686
+
687
+ <!-- Pattern 4: Element ID with method and explicit event -->
688
+ <button do-invoke="#{{soul-searching}}?.engage on mouseover">Hover me</button>
689
+ <!-- Parsed: { targetSpecifier: { targetElementId: "soul-searching", hostOrPeerMethodName: "engage" }, localEventType: "mouseover" } -->
690
+ ```
691
+
692
+ #### Choosing the Right Parser
693
+
694
+ The `nested-regex-groups` package provides two built-in parsers with different capabilities:
695
+
696
+ **Use `parse-pattern-statements` when:**
697
+ - You need to support **nested object structures** using dot notation in capture groups
698
+ - Example: `(?<lhs.id>#\\w+)` creates nested structure `{ lhs: { id: '#foo' } }`
699
+ - This is a heavier-footprint parser based on the API documented [here](https://github.com/bahrus/nested-regex-groups#parsepatternstatementsinput-patternconfigs-options)
700
+ - Example usage:
701
+ ```javascript
702
+ import { parsePatternStatements } from 'nested-regex-groups';
703
+
704
+ const patterns = [
705
+ { name: 'comparison', pattern: '^(?<trigger>on|off)\\s+when\\s+(?<lhs.id>#\\w+)\\s+eq\\s+(?<rhs.id>#\\w+)$' }
706
+ ];
707
+
708
+ const result = parsePatternStatements('on when #foo eq #bar. off when #baz eq #qux.', patterns);
709
+ // {
710
+ // success: true,
711
+ // statements: [
712
+ // { pattern: 'comparison', value: { trigger: 'on', lhs: { id: '#foo' }, rhs: { id: '#bar' } } },
713
+ // { pattern: 'comparison', value: { trigger: 'off', lhs: { id: '#baz' }, rhs: { id: '#qux' } } }
714
+ // ]
715
+ // }
716
+ ```
717
+
718
+ **Use `parse-grouped-capture-statements` when:**
719
+ - Your target object is **flat** (no nested properties)
720
+ - You want a lighter-weight parser that relies almost exclusively on built-in regex capabilities
721
+ - Example: `InvokingParameters { targetPart: string, localEventType?: string }`
722
+ - This parser can only result in a flat document structure
723
+ - Based on the API documented [here](https://github.com/bahrus/nested-regex-groups#parsegroupedcapturestatementsinput-patternconfigs-options)
724
+ - Example usage:
725
+ ```javascript
726
+ import { parseGroupedCaptureStatements } from 'nested-regex-groups';
727
+
728
+ const patterns = [
729
+ { name: 'withEvent', pattern: '^(?<methodName>\\w+)\\s+on\\s+(?<eventType>\\w+)$' },
730
+ { name: 'methodOnly', pattern: '^(?<methodName>\\w+)$' }
731
+ ];
732
+
733
+ const result = parseGroupedCaptureStatements('handleClick on click. handleInput on input.', patterns);
734
+ // {
735
+ // success: true,
736
+ // statements: [
737
+ // { pattern: 'withEvent', value: { methodName: 'handleClick', eventType: 'click' } },
738
+ // { pattern: 'withEvent', value: { methodName: 'handleInput', eventType: 'input' } }
739
+ // ]
740
+ // }
741
+ ```
742
+
743
+ **Use custom parser (advanced) when:**
744
+ - You need complex post-processing logic beyond pattern matching
745
+ - Built-in parsers don't meet your needs
746
+ - See [Scoped Parser Registry](https://github.com/bahrus/mount-observer#scoped-parser-registry-for-emc-scripts) for custom parser documentation
747
+
748
+ #### Troubleshooting
749
+
750
+ **Parser not working:**
751
+ - Verify `nested-regex-groups` is in dependencies
752
+ - Check that capture group names match your type properties
753
+ - Ensure `String.raw` is used for patterns with backslashes
754
+ - Run `node emc.mjs` to verify JSON output includes `parserConfig`
755
+
756
+ **Type mismatches:**
757
+ - Capture group names must exactly match type property names
758
+ - Use optional properties (`?`) for optional capture groups
759
+ - Ensure `instanceOf: 'Array'` when parser returns an array
760
+
761
+ **Pattern not matching:**
762
+ - Test patterns in order - first match wins
763
+ - Use more specific patterns first
764
+ - Check regex syntax with online regex testers
765
+ - Remember `.*` is greedy - use `.*?` for non-greedy matching
766
+
767
+ ---
768
+
769
+ **Result:** If your enhancement requires custom parsing, you now have pattern configurations in `emc.mjs` using built-in parsers. If not needed, skip this step and proceed to Step 8.
770
+
771
+ ### Step 8: Configure VS Code File Nesting
772
+
773
+ Set up VS Code to nest generated .json files under their corresponding .mjs source files for better project organization.
774
+
775
+ **Why this step?** The build process generates .json files from .mjs files (e.g., emc.mjs → emc.json). File nesting in VS Code's explorer keeps these related files grouped together, reducing visual clutter and making it clear which JSON files are generated artifacts.
776
+
777
+ **Instructions:**
778
+
779
+ 1. Create a `.vscode` folder in your project root if it doesn't exist
780
+ 2. Create or open `.vscode/settings.json`
781
+ 3. Add the following settings (merge with existing settings if the file already has content):
782
+
783
+ ```json
784
+ {
785
+ "explorer.fileNesting.patterns": {
786
+ "*.mjs": "${capture}.json"
787
+ },
788
+ "explorer.fileNesting.enabled": true
789
+ }
790
+ ```
791
+
792
+ **What this does:**
793
+ - `"*.mjs": "${capture}.json"` - Nests any .json file under its matching .mjs file (e.g., emc.json nests under emc.mjs)
794
+ - `"explorer.fileNesting.enabled": true` - Enables the file nesting feature in VS Code's explorer
795
+
796
+ **Result:** In VS Code's file explorer, generated JSON files will appear nested under their source .mjs files, making the project structure cleaner and more intuitive.
797
+
798
+ ### Step 8a: Configure Auto-Build Hook (Optional but Recommended)
799
+
800
+ Set up a Kiro hook to automatically rebuild JSON configuration files when .mjs files are saved.
801
+
802
+ **Why this step?** Manually running `npm run build` after every change to emc.mjs or [emoji].mjs is tedious and easy to forget. A Kiro hook automates this, ensuring your JSON files are always up-to-date.
803
+
804
+ **Instructions:**
805
+
806
+ 1. Use Kiro to create the hook by saying: "Create a hook that runs npm run build when emc.mjs or [emoji].mjs is saved"
807
+
808
+ Or manually create `.kiro/hooks/auto-build-config.kiro.hook` with:
809
+
810
+ ```json
811
+ {
812
+ "name": "Auto-build Configuration",
813
+ "version": "1.0.0",
814
+ "description": "Automatically runs npm run build when emc.mjs or emoji .mjs files are saved, regenerating the JSON configuration files",
815
+ "when": {
816
+ "type": "fileEdited",
817
+ "patterns": ["emc.mjs", "⿻.mjs"]
818
+ },
819
+ "then": {
820
+ "type": "runCommand",
821
+ "command": "npm run build",
822
+ "timeout": 10000
823
+ }
824
+ }
825
+ ```
826
+
827
+ 2. Adjust the patterns array to match your emoji filename if different from `⿻.mjs`
828
+ 3. If you don't have an emoji variant, use: `"patterns": ["emc.mjs"]`
829
+
830
+ **What this does:**
831
+ - Watches for saves to emc.mjs and emoji .mjs files
832
+ - Automatically runs `npm run build` to regenerate JSON files
833
+ - Completes within 10 seconds (configurable via timeout)
834
+ - Keeps your runtime configuration in sync with source files
835
+
836
+ **Result:** Your JSON configuration files will automatically regenerate whenever you save the source .mjs files, eliminating manual build steps during development.
837
+
838
+ ### Step 9: Create Modern Enhancement Class
839
+
840
+ Transform the legacy enhancement class to use the modern architecture with roundabout and assign-gingerly.
841
+
842
+ **Why this step?** The legacy class extended BE (be-enhanced) and used a static config object. The modern approach uses a standalone class with constructor-based initialization, leveraging roundabout for reactive property management and assign-gingerly for property assignment.
843
+
844
+ **Key Changes:**
845
+
846
+ 1. **No base class**: Class doesn't extend anything - it's a plain JavaScript class
847
+ 2. **No static config**: Configuration is now in emc.mjs, not in the class
848
+ 3. **Constructor pattern**: Uses constructor with enhancedElement, ctx, and initVals parameters
849
+ 4. **No WeakRef boilerplate**: The roundabout library automatically handles weak references for properties listed in `customData.weakRef.properties`
850
+ 5. **Configuration from ctx, not JSON import**: The `ctx` parameter (typed as `SpawnContext`) carries the full EMC configuration via `ctx.emc`, so the enhancement class does NOT need to import `emc.json`. This avoids duplicate JSON parsing when both the canonical name and emoji shorthand are used.
851
+ 6. **Single library call**: Only roundabout is called - no separate assignGingerly import needed
852
+ 7. **No bootUp/export boilerplate**: Simply export the class, no await bootUp() needed
853
+ 8. **BAP → AP**: Replace all BAP type references with AP
854
+
855
+ **Instructions:**
856
+
857
+ 1. Create `be-[project-name].js` (or `do-[project-name].js`) in your project root
858
+ 2. Start with the required imports — note there is **no `emc.json` import**:
859
+
860
+ ```javascript
861
+ // @ts-check
862
+ /** @import {Actions, PAP, AllProps, AP} from './types/[project-name]/types' */;
863
+ /** @import {RoundaboutOptions} from './types/roundabout/types' */;
864
+ /** @import {ElementEnhancementGateway, SpawnContext} from './types/assign-gingerly/types' */;
865
+ /** @import {EMC} from './types/mount-observer/types' */;
866
+ /** @import {RAConfig} from './types/roundabout/types' */;
867
+ ```
868
+
869
+ **Important:** The class does NOT import `emc.json`. Instead, the full EMC configuration (including `customData`) is passed through the `ctx` parameter by the mount-observer/assign-gingerly infrastructure. This means:
870
+ - Only one JSON file is parsed by the browser (whichever EMC script triggered the spawn)
871
+ - The enhancement class is name-agnostic — it works identically whether spawned by `emc.json` or an emoji variant JSON
872
+ - No duplicate JSON imports when using emoji shorthand aliases
873
+
874
+ 3. Add the class with the standard boilerplate:
875
+
876
+ ```javascript
877
+ /**
878
+ * @implements {Actions}
879
+ */
880
+ class Be[ClassName] {
881
+
882
+ /**
883
+ * @this {AllProps & Actions}
884
+ * @param {Element & ElementEnhancementGateway} enhancedElement
885
+ * @param {SpawnContext} ctx
886
+ * @param {PAP} initVals
887
+ */
888
+ constructor(enhancedElement, ctx, initVals){
889
+ this.init(this, enhancedElement, ctx, initVals);
890
+ }
891
+
892
+ /**
893
+ * @param {AllProps} self
894
+ * @param {Element & ElementEnhancementGateway} enhancedElement
895
+ * @param {SpawnContext} ctx
896
+ * @param {PAP} initVals
897
+ */
898
+ async init(self, enhancedElement, ctx, initVals){
899
+ const {customData} = /** @type {EMC<any, AllProps, Element, RAConfig<AllProps, Actions>>} */ (ctx.emc);
900
+ /**
901
+ * @type {RoundaboutOptions}
902
+ */
903
+ const raOptions = {
904
+ ...customData,
905
+ vm: self,
906
+ initialPropVals: {
907
+ enhancedElement,
908
+ ...customData?.defaultPropVals,
909
+ ...initVals
910
+ }
911
+ };
912
+ (await import('roundabout-lib/roundabout.js')).roundabout(raOptions);
913
+ }
914
+
915
+ // Copy your action methods here, replacing BAP with AP
916
+ }
917
+
918
+ export { Be[ClassName] }
919
+ ```
920
+
921
+ **Key pattern — extracting customData from ctx.emc:**
922
+ ```javascript
923
+ const {customData} = /** @type {EMC<any, AllProps, Element, RAConfig<AllProps, Actions>>} */ (ctx.emc);
924
+ ```
925
+ The `ctx.emc` property is typed as `any` on `SpawnContext`, so you cast it to your specific `EMC` parameterization to get full type safety on `customData`.
926
+
927
+ **IMPORTANT — Update the `init` signature in types.d.ts:** The `Actions` interface in `types/[project-name]/types.d.ts` must be updated to match the new 4-parameter `init` method. The legacy signature has 3 parameters (no `ctx`), but the modern architecture passes `ctx` through so the class can access `ctx.emc`. If you skip this, `@ts-check` will report "Expected 3 arguments, but got 4" in the constructor and a signature mismatch on `init`.
928
+
929
+ Add `SpawnContext` to the import and update the `init` signature:
930
+
931
+ Before:
932
+ ```typescript
933
+ import { ElementEnhancementGateway } from "../assign-gingerly/types";
934
+
935
+ export interface Actions{
936
+ init(self: AP, enhancedElement: Element, initVals: PAP): Promise<void>;
937
+ }
938
+ ```
939
+
940
+ After:
941
+ ```typescript
942
+ import { ElementEnhancementGateway, SpawnContext } from "../assign-gingerly/types";
943
+
944
+ export interface Actions{
945
+ init(self: AP, enhancedElement: Element, ctx: SpawnContext, initVals: PAP): Promise<void>;
946
+ }
947
+ ```
948
+
949
+ 4. **Copy action methods** from the legacy class:
950
+ - Remove the static config section entirely
951
+ - Copy all action methods (like addCloneBtn, setBtnContent, etc.)
952
+ - Replace all `BAP` type annotations with `AP`
953
+ - Keep the method implementations the same
954
+ - **Update import paths**: Replace `'mount-observer/refid/nudge.js'` with `'mount-observer/nudge.js'` (whether dynamic import or top-level import)
955
+
956
+ 5. **Apply default values** in the init method:
957
+ - Extract `customData` from `ctx.emc` with a cast to your EMC type
958
+ - Pass all initial values through the `initialPropVals` property in roundabout options
959
+ - Spread values in order: `enhancedElement`, then `defaultPropVals`, then `initVals`
960
+ - This ensures defaults are applied, but can be overridden by `initVals`
961
+
962
+ 6. **Remove legacy code**:
963
+ - Remove `await BeClonable.bootUp();` at the bottom
964
+ - Remove imports from be-enhanced (BE, resolved, rejected, propInfo)
965
+ - Remove imports from trans-render that were only used in static config
966
+ - Remove any separate assignGingerly imports - roundabout handles initialization
967
+
968
+ 7. **Update utility imports**:
969
+ - Replace `trans-render/lib/findAdjacentElement.js` with `be-hive/findAdjacentElement.js`
970
+ - The be-hive package provides common utilities that were previously in trans-render
971
+
972
+ **CRITICAL - Avoid Compact/Action Conflicts:**
973
+
974
+ When migrating the static config to emc.mjs, be careful not to define the same method in both `actions` and `compacts`:
975
+
976
+ - **Compacts** automatically call methods when properties change (e.g., `when_triggerInsertPosition_changes_call_addDeleteBtn`)
977
+ - **Actions** define when methods should be called based on property availability (e.g., `ifAllOf: ['prop1', 'prop2']`)
978
+ - If a method is already invoked by a compact, DO NOT add it to actions - this will cause a "Conflict detected" error
979
+ - Example: If you have `when_triggerInsertPosition_changes_call_addDeleteBtn: 0` in compacts, do NOT add `addDeleteBtn` to actions
980
+
981
+ **Example Transformation:**
982
+
983
+ Legacy class:
984
+ ```javascript
985
+ import { BE } from 'be-enhanced/BE.js';
986
+
987
+ class BeClonable extends BE {
988
+ static config = {
989
+ propDefaults: { byob: true },
990
+ actions: { addCloneBtn: { ifAllOf: ['triggerInsertPosition'] } }
991
+ };
992
+
993
+ async addCloneBtn(self) {
994
+ // method implementation
995
+ }
996
+ }
997
+
998
+ await BeClonable.bootUp();
999
+ export { BeClonable }
1000
+ ```
1001
+
1002
+ Modern class:
1003
+ ```javascript
1004
+ // @ts-check
1005
+ /** @import {Actions, PAP, AllProps, AP} from './types/be-clonable/types' */;
1006
+ /** @import {RoundaboutOptions} from './types/roundabout/types' */;
1007
+ /** @import {ElementEnhancementGateway, SpawnContext} from './types/assign-gingerly/types' */;
1008
+ /** @import {EMC} from './types/mount-observer/types' */;
1009
+ /** @import {RAConfig} from './types/roundabout/types' */;
1010
+
1011
+ /**
1012
+ * @implements {Actions}
1013
+ */
1014
+ class BeClonable {
1015
+
1016
+ /**
1017
+ * @this {AllProps & Actions}
1018
+ * @param {Element & ElementEnhancementGateway} enhancedElement
1019
+ * @param {SpawnContext} ctx
1020
+ * @param {PAP} initVals
1021
+ */
1022
+ constructor(enhancedElement, ctx, initVals){
1023
+ this.init(this, enhancedElement, ctx, initVals);
1024
+ }
1025
+
1026
+ /**
1027
+ * @param {AllProps} self
1028
+ * @param {Element & ElementEnhancementGateway} enhancedElement
1029
+ * @param {SpawnContext} ctx
1030
+ * @param {PAP} initVals
1031
+ */
1032
+ async init(self, enhancedElement, ctx, initVals){
1033
+ const {customData} = /** @type {EMC<any, AllProps, Element, RAConfig<AllProps, Actions>>} */ (ctx.emc);
1034
+ const raOptions = {
1035
+ ...customData,
1036
+ vm: self,
1037
+ initialPropVals: {
1038
+ enhancedElement,
1039
+ ...customData?.defaultPropVals,
1040
+ ...initVals
1041
+ }
1042
+ };
1043
+ (await import('roundabout-lib/roundabout.js')).roundabout(raOptions);
1044
+ }
1045
+
1046
+ async addCloneBtn(self) {
1047
+ // same method implementation
1048
+ }
1049
+ }
1050
+
1051
+ export { BeClonable }
1052
+ ```
1053
+
1054
+ **Result:** You now have a modern enhancement class that uses roundabout for reactive properties and integrates with the emc.mjs configuration.
1055
+
1056
+ ### Step 10: Create Emoji Shorthand Configuration (Optional)
1057
+
1058
+ If your project has an emoji shorthand in the README title, create a corresponding .mjs file that generates a variant configuration using the emoji as the base attribute.
1059
+
1060
+ **Why this step?** Many be-* projects support both a full name (e.g., `be-clonable`) and an emoji shorthand (e.g., `⿻`) for brevity. This step creates a separate JSON configuration that uses the emoji as the base attribute name, allowing users to write `<div ⿻>` instead of `<div be-clonable>`.
1061
+
1062
+ **When to do this:** Only if your README.md title includes an emoji in parentheses, like `# be-clonable (⿻)`.
1063
+
1064
+ **Instructions:**
1065
+
1066
+ 1. Identify the emoji from your README.md title (the character in parentheses)
1067
+ 2. Create `[emoji].mjs` in your project root (e.g., `⿻.mjs`)
1068
+ 3. Use this template:
1069
+
1070
+ ```javascript
1071
+ import myJSON from './emc.json' with {type: 'json'};
1072
+
1073
+ /** @import {EMC} from './types/mount-observer/types' */;
1074
+ /** @import {AllProps} from './types/[project-name]/types' */
1075
+
1076
+ /**
1077
+ * @type {EMC<any, AllProps> }
1078
+ */
1079
+ const emc = {
1080
+ ...myJSON,
1081
+ enhConfig: {
1082
+ ...myJSON.enhConfig,
1083
+ enhKey: '[emoji]',
1084
+ withAttrs: {
1085
+ ...myJSON.enhConfig.withAttrs,
1086
+ base: '[emoji]'
1087
+ }
1088
+ }
1089
+ }
1090
+
1091
+ export function render(){
1092
+ return JSON.stringify(emc, null, 4);
1093
+ }
1094
+
1095
+ console.log(render());
1096
+ ```
1097
+
1098
+ 4. Replace `[emoji]` with your actual emoji character
1099
+ 5. Replace `[project-name]` with your project name in the import
1100
+
1101
+ **What this does:**
1102
+ - Imports the base emc.json configuration
1103
+ - Creates a variant that overrides the `enhKey` and `base` attribute to use the emoji
1104
+ - Generates a separate JSON file (e.g., `⿻.json`) when the build script runs
1105
+
1106
+ **Example:**
1107
+
1108
+ For be-clonable with emoji `⿻`:
1109
+
1110
+ ```javascript
1111
+ import myJSON from './emc.json' with {type: 'json'};
1112
+
1113
+ /** @import {EMC} from './types/mount-observer/types' */;
1114
+ /** @import {AllProps} from './types/be-clonable/types' */
1115
+
1116
+ /**
1117
+ * @type {EMC<any, AllProps> }
1118
+ */
1119
+ const emc = {
1120
+ ...myJSON,
1121
+ enhConfig: {
1122
+ ...myJSON.enhConfig,
1123
+ enhKey: '⿻',
1124
+ withAttrs: {
1125
+ ...myJSON.enhConfig.withAttrs,
1126
+ base: '⿻'
1127
+ }
1128
+ }
1129
+ }
1130
+
1131
+ export function render(){
1132
+ return JSON.stringify(emc, null, 4);
1133
+ }
1134
+
1135
+ console.log(render());
1136
+ ```
1137
+
1138
+ **IMPORTANT — Spread `...myJSON` at the top level:** The `...myJSON` spread ensures that all top-level properties from `emc.json` (including `customData`) are carried over into the emoji variant. Without this, the emoji JSON would only contain `enhConfig` and the enhancement class would not have access to its configuration (actions, weakRef, defaultPropVals, etc.) when spawned via the emoji attribute.
1139
+
1140
+ **Result:** When you run `npm run build`, both `emc.json` and `⿻.json` will be generated, allowing users to use either the full name or emoji shorthand.
1141
+
1142
+ ### Step 11: Update Tests and Demo Files
1143
+
1144
+ Update test and demo HTML files to use the modern be-hive registration pattern.
1145
+
1146
+ **Why this step?** The legacy architecture used direct imports of emc.js files. The modern approach uses be-hive's declarative `<be-hive>` element with `<script type=emc>` tags to load enhancement configurations.
1147
+
1148
+ **Instructions:**
1149
+
1150
+ 1. **Update test HTML files** (e.g., `tests/test1.html`):
1151
+ - Replace the legacy import pattern:
1152
+ ```html
1153
+ <script type=module>
1154
+ import '/emc.js';
1155
+ </script>
1156
+ ```
1157
+ - With the modern be-hive pattern:
1158
+ ```html
1159
+ <be-hive>
1160
+ <script type=emc src="[project-name]/emc.json"></script>
1161
+ </be-hive>
1162
+ <script type=module>
1163
+ import 'be-hive/be-hive.js';
1164
+ </script>
1165
+ ```
1166
+ - Replace `[project-name]` with your actual project name (e.g., `be-delible`)
1167
+ - **Important**: Reference `emc.json` (not `emc.mjs`) - this reduces dependency on special web server behavior and makes the markup portable across different hosting environments without modification
1168
+
1169
+ 2. **If using custom parsers (Step 7a), register the parser in HTML:**
1170
+ - When your enhancement uses a custom parser like `parse-pattern-statements`, you must register it in the HTML before loading the EMC configuration
1171
+ - Add a `<script type=emc-parser>` tag that loads the parser and assigns it a name
1172
+ - Use `wait-for-parsers` attribute on the EMC script to ensure the parser is loaded first
1173
+ - Example for enhancements using `parse-pattern-statements`:
1174
+ ```html
1175
+ <be-hive>
1176
+ <script type=emc-parser
1177
+ src="be-hive/parsers/parse-pattern-statements.js"
1178
+ parser-name=parse-pattern-statements></script>
1179
+ <script type=emc
1180
+ src="do-invoke/🕹️.json"
1181
+ wait-for-parsers=parse-pattern-statements></script>
1182
+ </be-hive>
1183
+ <script type=module>
1184
+ import 'be-hive/be-hive.js';
1185
+ </script>
1186
+ ```
1187
+ - **Key attributes:**
1188
+ - `type=emc-parser` - Identifies this as a parser registration script
1189
+ - `src` - Path to the parser module (e.g., `be-hive/parsers/parse-pattern-statements.js`)
1190
+ - `parser-name` - The name you used in `emc.mjs` for the `parser` property
1191
+ - `wait-for-parsers` - Comma-separated list of parser names to wait for before processing the EMC
1192
+
1193
+ 3. **Update demo files** (e.g., `demo/dev.html`) with the same pattern
1194
+
1195
+ 4. **Update test selectors** in test files:
1196
+ - Change button selectors from generic `button` to the specific class (e.g., `.be-delible-trigger`)
1197
+ - Verify test logic matches the enhancement behavior
1198
+
1199
+ 5. **Update playwright.config.ts** to only run Chrome tests:
1200
+ - Comment out firefox and webkit projects
1201
+ - Add a comment explaining that Chrome 146+ features are required (JSON imports with type assertion)
1202
+ - Example:
1203
+ ```typescript
1204
+ projects: [
1205
+ {
1206
+ name: 'chromium',
1207
+ use: { ...devices['Desktop Chrome'] },
1208
+ },
1209
+ // Commented out - requires Chrome 146+ features (JSON imports with type assertion)
1210
+ // {
1211
+ // name: 'firefox',
1212
+ // use: { ...devices['Desktop Firefox'] },
1213
+ // },
1214
+ // {
1215
+ // name: 'webkit',
1216
+ // use: { ...devices['Desktop Safari'] },
1217
+ // },
1218
+ ],
1219
+ ```
1220
+
1221
+ **Complete Working Example (do-invoke):**
1222
+
1223
+ ```html
1224
+ <!DOCTYPE html>
1225
+ <html lang="en">
1226
+ <head>
1227
+ <meta charset="UTF-8">
1228
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1229
+ <title>Example 1a</title>
1230
+ <!-- #include virtual="/imports.html" -->
1231
+ <be-hive>
1232
+ <script type=emc-parser
1233
+ src="be-hive/parsers/parse-pattern-statements.js"
1234
+ parser-name=parse-pattern-statements></script>
1235
+ <script type=emc
1236
+ src="do-invoke/🕹️.json"
1237
+ wait-for-parsers=parse-pattern-statements></script>
1238
+ </be-hive>
1239
+ <script type=module>
1240
+ import 'be-hive/be-hive.js';
1241
+ class MoodStone extends HTMLElement{
1242
+ howAmIFeelingAboutToday(targetElement, event){
1243
+ console.log({targetElement, event});
1244
+ }
1245
+ }
1246
+ customElements.define('mood-stone', MoodStone);
1247
+ </script>
1248
+ </head>
1249
+ <body>
1250
+ <mood-stone itemscope>
1251
+ <button 🕹️=howAmIFeelingAboutToday>Feeling great</button>
1252
+ </mood-stone>
1253
+ </body>
1254
+ </html>
1255
+ ```
1256
+
1257
+ **Common Issues:**
1258
+
1259
+ - **Module resolution errors**: If you see "Failed to resolve module specifier" errors for utilities like `findAdjacentElement`, ensure you're importing from `be-hive/findAdjacentElement.js` (not `trans-render/lib/findAdjacentElement.js`)
1260
+ - **Compact/Action conflicts**: If roundabout reports "Method X is invoked by both a compact and an action", remove the method from the `actions` section in emc.mjs - compacts already handle the invocation
1261
+ - **WeakRef properties**: Ensure any properties that store element references (like `trigger`, `button`, etc.) are listed in `customData.weakRef.properties` in emc.mjs
1262
+ - **Parser not found**: If you get errors about parser not being found, ensure:
1263
+ - The `parser-name` attribute matches the `parser` value in your `emc.mjs`
1264
+ - The `wait-for-parsers` attribute lists all required parsers
1265
+ - The parser script loads before the EMC script
1266
+
1267
+ **Result:** Tests and demos should now work with the modern architecture. Run `npm test` to verify.
1268
+ ---
1269
+
1270
+ *This document is a living guide that will be expanded with detailed instructions for each conversion step.*
1271
+
1272
+
1273
+ ## Lessons Learned from Recent Conversions
1274
+
1275
+ ### Emoji Shorthand Configuration (⏻.mjs)
1276
+
1277
+ **Issue:** When creating emoji shorthand files, the generated JSON was missing the `customData` section, causing the enhancement to fail silently.
1278
+
1279
+ **Solution:** Always include `customData` when spreading the base configuration:
1280
+
1281
+ ```javascript
1282
+ const emc = {
1283
+ enhConfig: {
1284
+ ...myJSON.enhConfig,
1285
+ enhKey: '⏻',
1286
+ withAttrs: {
1287
+ ...myJSON.enhConfig.withAttrs,
1288
+ base: '⏻'
1289
+ }
1290
+ },
1291
+ customData: myJSON.customData // ← CRITICAL: Don't forget this!
1292
+ }
1293
+ ```
1294
+
1295
+ **Why:** The `customData` section contains essential configuration for roundabout:
1296
+ - `actions` - Defines when methods should be called
1297
+ - `weakRef` - Specifies which properties should use weak references
1298
+ - `compacts` - Defines property change handlers
1299
+ - `defaultPropVals` - Default property values
1300
+
1301
+ Without `customData`, roundabout won't know how to initialize the enhancement, and the enhancement will appear to load but won't respond to events.
1302
+
1303
+ ### Property Inference from Name Attribute
1304
+
1305
+ **Issue:** When implementing inference from the `name` attribute (for empty attribute values like `⏻` with no value), the inferred statement structure must match the parser's output structure.
1306
+
1307
+ **Solution:** Ensure the inferred statement matches your type definitions:
1308
+
1309
+ ```javascript
1310
+ // If using simple structure with just 'prop':
1311
+ if(statements.length === 0){
1312
+ const name = enhancedElement.getAttribute('name');
1313
+ if(name){
1314
+ statements.push({
1315
+ value: {
1316
+ prop: name, // ← Match your TogglingParameters type
1317
+ localEventType: 'click'
1318
+ }
1319
+ });
1320
+ }
1321
+ }
1322
+ ```
1323
+
1324
+ **Don't use nested structures** like `remoteSpecifier: { targetPart: name }` unless your types actually define that structure.
1325
+
1326
+ ### Chained Accessor Syntax for Selectors
1327
+
1328
+ **Issue:** When using selectors with properties like `[#myLight].isOn`, the simple period `.` conflicts with statement splitting (periods are used to separate multiple statements in a single attribute).
1329
+
1330
+ **Solution:** Use the chained accessor `?.` syntax instead:
1331
+
1332
+ ```html
1333
+ <!-- ❌ Wrong - period conflicts with statement splitting -->
1334
+ <button ⏻="[#myLight].isOn">Toggle</button>
1335
+
1336
+ <!-- ✅ Correct - chained accessor avoids conflict -->
1337
+ <button ⏻="[#myLight]?.isOn">Toggle</button>
1338
+ ```
1339
+
1340
+ **Implementation:** Update your regex patterns to match `?.` instead of `.`:
1341
+
1342
+ ```javascript
1343
+ // Match [selector]?.property instead of [selector].property
1344
+ const selectorMatch = prop.match(/^\[(.+?)\](?:\?\.(.+))?$/);
1345
+ ```
1346
+
1347
+ **Why:** The `parse-grouped-capture-statements` parser splits on periods to handle multiple statements like `prop1. prop2. prop3`. Using `?.` avoids this conflict while maintaining readable syntax.
1348
+
1349
+ ### Parser Selection: parse-grouped-capture-statements vs parse-pattern-statements
1350
+
1351
+ **When to use `parse-grouped-capture-statements`:**
1352
+ - Your target object is **flat** (no nested properties)
1353
+ - Example: `{ prop: "isHappy", localEventType: "click" }`
1354
+ - Lighter weight, simpler patterns
1355
+ - Good for most basic enhancements
1356
+
1357
+ **When to use `parse-pattern-statements`:**
1358
+ - You need **nested object structures** using dot notation in capture groups
1359
+ - Example: `{ targetSpecifier: { hostOrPeerMethodName: "method" }, localEventType: "click" }`
1360
+ - Required when using `dssKeys` for DSS (DOM Selector Syntax) parsing
1361
+ - Heavier footprint but more powerful
1362
+
1363
+ **Key difference:** The parser choice affects your type definitions and how you structure parsed data. Choose based on your data structure needs, not syntax complexity.
1364
+
1365
+ ### Testing Strategy
1366
+
1367
+ **Start simple, then expand:**
1368
+
1369
+ 1. **Get the basic case working first** (Example1a - simple property toggle on host)
1370
+ 2. **Add inference** (Example1aInfer - infer from name attribute)
1371
+ 3. **Add event customization** (Example1b - custom event types)
1372
+ 4. **Add selector support** (Example1c - toggle peer elements)
1373
+
1374
+ **Don't try to implement all features at once.** Each step builds on the previous one and helps identify issues early.
1375
+
1376
+ ### Common Pitfalls
1377
+
1378
+ 1. **Missing customData in emoji JSON** - Enhancement loads but doesn't work
1379
+ 2. **Type mismatch in inference** - Using nested structure when types expect flat structure
1380
+ 3. **Wrong parser in HTML** - HTML references `parse-pattern-statements` but emc.mjs uses `parse-grouped-capture-statements`
1381
+ 4. **Period vs chained accessor** - Using `.` instead of `?.` for selector properties
1382
+ 5. **Forgetting to rebuild** - After changing emc.mjs or emoji.mjs, always run `npm run build`
1383
+
1384
+ ### Debugging Tips
1385
+
1386
+ 1. **Check the generated JSON** - Verify emc.json and emoji.json have all expected sections
1387
+ 2. **Console log parsedStatements** - Add `console.log({parsedStatements})` in hydrate to see what the parser produced
1388
+ 3. **Verify parser loading** - Check browser console for "Parser not found" errors
1389
+ 4. **Test incrementally** - Get one example working before moving to the next
1390
+ 5. **Compare with working examples** - Look at do-inc and do-invoke for reference patterns
1391
+
1392
+ ---
1393
+
1394
+ *Last updated: April 2026 - Based on do-toggle conversion experience*
1395
+
1396
+
1397
+ ## Using the Infer Pattern for Element Conventions
1398
+
1399
+ ### Overview
1400
+
1401
+ The modern architecture provides a standardized way to infer element properties and event types through the **`infer` pattern** from `assign-gingerly`. This eliminates the need for hardcoded element type checks and provides a consistent, extensible approach to element conventions.
1402
+
1403
+ ### The Infer Function
1404
+
1405
+ Add this helper function at the bottom of your enhancement class file:
1406
+
1407
+ ```javascript
1408
+ /**
1409
+ * @param {Element & ElementEnhancementGateway} from
1410
+ */
1411
+ async function infer(from){
1412
+ return /** @type {ElementInfer} */ (
1413
+ /** @type {any} */ (
1414
+ from.enh.get((await import('assign-gingerly/Infer.js')).registryItem)
1415
+ )
1416
+ );
1417
+ }
1418
+ ```
1419
+
1420
+ ### What It Provides
1421
+
1422
+ The `infer` function returns an `ElementInfer` object with:
1423
+ - **`eventType`**: The most appropriate event type for the element (e.g., 'click', 'input', 'change')
1424
+ - **`propName`**: The most appropriate property name for the element (e.g., 'checked', 'value', 'textContent')
1425
+ - **`value`**: The current value of the inferred property (getter/setter)
1426
+
1427
+ ### Type Import
1428
+
1429
+ Add `ElementInfer` to your imports:
1430
+
1431
+ ```javascript
1432
+ /** @import {ElementEnhancementGateway, ElementInfer} from './types/assign-gingerly/types' */;
1433
+ ```
1434
+
1435
+ ### Usage Examples
1436
+
1437
+ #### Example 1: Inferring Event Type
1438
+
1439
+ Replace hardcoded event type logic:
1440
+
1441
+ ```javascript
1442
+ // ❌ Old approach - hardcoded logic
1443
+ if (localEventType === undefined) {
1444
+ const tagName = enhancedElement.tagName.toLowerCase();
1445
+ if(tagName === 'input' || tagName === 'textarea' || tagName === 'select'){
1446
+ localEventType = 'input';
1447
+ } else {
1448
+ localEventType = 'click';
1449
+ }
1450
+ }
1451
+
1452
+ // ✅ New approach - use infer
1453
+ if (localEventType === undefined) {
1454
+ localEventType = (await infer(enhancedElement)).eventType;
1455
+ }
1456
+ ```
1457
+
1458
+ #### Example 2: Inferring Property Name
1459
+
1460
+ Replace hardcoded property inference:
1461
+
1462
+ ```javascript
1463
+ // ❌ Old approach - hardcoded logic
1464
+ if(!propertyName){
1465
+ const tagName = target.tagName.toLowerCase();
1466
+ if(tagName === 'input'){
1467
+ const inputType = target.getAttribute('type');
1468
+ propertyName = (inputType === 'checkbox' || inputType === 'radio') ? 'checked' : 'value';
1469
+ } else {
1470
+ propertyName = 'textContent';
1471
+ }
1472
+ }
1473
+
1474
+ // ✅ New approach - use infer
1475
+ if(!propertyName){
1476
+ propertyName = (await infer(target)).propName;
1477
+ }
1478
+ ```
1479
+
1480
+ #### Example 3: Inferring from Name Attribute
1481
+
1482
+ When the attribute value is empty, infer both event type and property name:
1483
+
1484
+ ```javascript
1485
+ // When statements.length === 0, infer from element
1486
+ if(statements.length === 0){
1487
+ const inference = await infer(enhancedElement);
1488
+ statements.push({
1489
+ value: {
1490
+ prop: inference.propName,
1491
+ localEventType: inference.eventType
1492
+ }
1493
+ });
1494
+ }
1495
+ ```
1496
+
1497
+ #### Example 4: Getting/Setting Inferred Property Value
1498
+
1499
+ The `infer` object provides direct access to the property value:
1500
+
1501
+ ```javascript
1502
+ // Get the inferred property value
1503
+ const inference = await infer(target);
1504
+ const currentValue = inference.value;
1505
+
1506
+ // Set the inferred property value
1507
+ inference.value = !inference.value; // Toggle
1508
+ ```
1509
+
1510
+ This is particularly useful in do-toggle when no property is specified:
1511
+
1512
+ ```javascript
1513
+ if(propertyName){
1514
+ target[propertyName] = !target[propertyName];
1515
+ } else {
1516
+ const inference = await infer(target);
1517
+ inference.value = !inference.value;
1518
+ }
1519
+ ```
1520
+
1521
+ ### Complete Example: do-toggle
1522
+
1523
+ Here's how do-toggle uses the infer pattern throughout:
1524
+
1525
+ ```javascript
1526
+ class DoToggle {
1527
+ async hydrate(self){
1528
+ const { parsedStatements, enhancedElement } = self;
1529
+ const {success, statements} = parsedStatements;
1530
+ if(!success) throw 400;
1531
+
1532
+ // Infer when no statements provided
1533
+ if(statements.length === 0){
1534
+ const name = enhancedElement.getAttribute('name');
1535
+ statements.push({
1536
+ value: {
1537
+ prop: name,
1538
+ localEventType: (await infer(enhancedElement)).eventType,
1539
+ }
1540
+ });
1541
+ }
1542
+
1543
+ for (const statement of statements) {
1544
+ const {value} = statement;
1545
+ if(!value) continue;
1546
+
1547
+ // Infer event type if not specified
1548
+ let { localEventType } = value;
1549
+ if (localEventType === undefined) {
1550
+ localEventType = (await infer(enhancedElement)).eventType;
1551
+ }
1552
+
1553
+ enhancedElement.addEventListener(localEventType, e => {
1554
+ self.handleEvent(self, e, value);
1555
+ });
1556
+ }
1557
+ // ...
1558
+ }
1559
+
1560
+ async handleEvent(self, e, parsedStatement){
1561
+ // ... find target element ...
1562
+
1563
+ // Use inferred value when no property specified
1564
+ if(propertyName){
1565
+ target[propertyName] = !target[propertyName];
1566
+ } else {
1567
+ const inference = await infer(target);
1568
+ inference.value = !inference.value;
1569
+ }
1570
+ }
1571
+ }
1572
+
1573
+ // Helper type at top of file
1574
+ /** @import {Infer} from './types/inferencer/types' */
1575
+
1576
+ // Helper function at bottom of file
1577
+ /**
1578
+ *
1579
+ * @param {Element & ElementEnhancementGateway} from
1580
+ */
1581
+ async function infer(from){return /** @type {Infer} */ (/** @type {any} */ (from.enh.get((await import('inferencer/inferencer.js')).registryItem)));}
1582
+ ```
1583
+
1584
+ ### Benefits
1585
+
1586
+ 1. **Consistency**: All enhancements use the same inference logic
1587
+ 2. **Extensibility**: New element types can be supported by updating assign-gingerly, not each enhancement
1588
+ 3. **Maintainability**: No duplicated element type checking code
1589
+ 4. **Type Safety**: TypeScript definitions ensure correct usage
1590
+ 5. **Future-proof**: Custom elements can provide their own inference hints
1591
+
1592
+ ### How It Works
1593
+
1594
+ The `infer` function:
1595
+ 1. Accesses the enhancement gateway on the element (`from.enh`)
1596
+ 2. Gets the `Infer` enhancement instance from the registry
1597
+ 3. Returns an object with inferred `eventType`, `propName`, and `value` getter/setter
1598
+
1599
+ The `Infer` enhancement (from assign-gingerly) analyzes the element and provides sensible defaults based on:
1600
+ - Element tag name (input, button, textarea, etc.)
1601
+ - Input type attribute (checkbox, radio, text, etc.)
1602
+ - Name attribute (as fallback for property name)
1603
+ - Itemprop attribute (for microdata-aware properties)
1604
+ - Custom element conventions
1605
+
1606
+ ### Migration Checklist
1607
+
1608
+ When converting an enhancement to use the infer pattern:
1609
+
1610
+ - [ ] Add `ElementInfer` to type imports
1611
+ - [ ] Add the `infer` helper function at the bottom of the file
1612
+ - [ ] Replace hardcoded event type inference with `(await infer(element)).eventType`
1613
+ - [ ] Replace hardcoded property name inference with `(await infer(element)).propName`
1614
+ - [ ] Use `inference.value` for getting/setting inferred properties
1615
+ - [ ] Update empty statement handling to use infer
1616
+ - [ ] Test with various element types (button, input, checkbox, etc.)
1617
+
1618
+ ### Reference
1619
+
1620
+ For more details on the inference system, see:
1621
+ - [assign-gingerly documentation](https://github.com/bahrus/assign-gingerly#accessing-the-enhancement-instance)
1622
+ - Working examples: do-toggle, do-inc, do-invoke
1623
+
1624
+ ---
1625
+
1626
+ *Added: April 2026 - Standardized inference pattern*