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.
- package/.gitmodules +3 -0
- package/.kiro/steering/project-context.md +17 -0
- package/.vscode/settings.json +3 -0
- package/FaceUp.js +305 -0
- package/README.md +162 -2
- package/imports.html +8 -0
- package/package.json +30 -20
- package/tests/test1.html +115 -0
- package/types/.kiro/specs/conversion-template/README.md +128 -0
- package/types/.kiro/specs/conversion-template/design.md +360 -0
- package/types/.kiro/specs/conversion-template/requirements.md +191 -0
- package/types/.kiro/specs/conversion-template/tasks.md +174 -0
- package/types/.kiro/steering/coding-standards.md +17 -0
- package/types/.kiro/steering/conversion-guide.md +103 -0
- package/types/.kiro/steering/declarative-configuration.md +108 -0
- package/types/.kiro/steering/emc-json-serializability.md +306 -0
- package/types/EnhancementConversionInstructions.md +1626 -0
- package/types/LICENSE +21 -0
- package/types/NewCustomElementFeature.md +673 -0
- package/types/NewEnhancementInstructions.md +395 -0
- package/types/README.md +2 -0
- package/types/agrace/types.d.ts +11 -0
- package/types/assign-gingerly/types.d.ts +328 -0
- package/types/be-a-beacon/types.d.ts +17 -0
- package/types/be-bound/types.d.ts +61 -0
- package/types/be-buttoned-up/types.d.ts +19 -0
- package/types/be-clonable/types.d.ts +36 -0
- package/types/be-committed/types.d.ts +22 -0
- package/types/be-decked-with/types.d.ts +26 -0
- package/types/be-delible/types.d.ts +25 -0
- package/types/be-reflective/types.d.ts +80 -0
- package/types/be-render-neutral/types.d.ts +29 -0
- package/types/be-typed/types.d.ts +31 -0
- package/types/do-inc/types.d.ts +56 -0
- package/types/do-invoke/types.d.ts +38 -0
- package/types/do-merge/types.d.ts +28 -0
- package/types/do-toggle/types.d.ts +31 -0
- package/types/face-up/types.d.ts +104 -0
- package/types/global.d.ts +29 -0
- package/types/id-generation/types.d.ts +26 -0
- package/types/inferencer/types.d.ts +46 -0
- package/types/mount-observer/types.d.ts +363 -0
- package/types/nested-regex-groups/types.d.ts +101 -0
- package/types/roundabout/types.d.ts +255 -0
- package/types/time-ticker/types.d.ts +66 -0
- 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*
|