assign-gingerly 0.0.80 → 0.0.82
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/DX/emojis.js +2 -1
- package/DX/emojis.ts +2 -1
- package/DX/paths.js +61 -17
- package/DX/paths.ts +179 -116
- package/README.md +9 -8
- package/inferencer/types/NewCustomElement.md +28 -445
- package/inferencer/types/NewHTMLFirstCustomElement.md +466 -0
- package/inferencer/types/NewJSFirstCustomElement.md +278 -0
- package/inferencer/types/roundabout/types.d.ts +2 -1
- package/package.json +1 -1
|
@@ -8,11 +8,6 @@ This document provides step-by-step instructions for creating a **brand new** cu
|
|
|
8
8
|
- Custom element features (composable behavior classes injected into elements) — see [NewCustomElementFeature.md](./NewCustomElementFeature.md)
|
|
9
9
|
- Enhancements (declarative behaviors attached to existing elements via attributes) — see [NewEnhancementInstructions.md](./NewEnhancementInstructions.md)
|
|
10
10
|
|
|
11
|
-
## Reference Implementations
|
|
12
|
-
|
|
13
|
-
- **[time-ticker](https://github.com/bahrus/time-ticker)** — A non-visual custom element that fires events periodically. Demonstrates extending `ElementMaker`, a custom feature (`TimeTicker`), roundabout wiring via `defRef.json`, and the `def.js` / `wireFeatures.js` pattern.
|
|
14
|
-
- **[scratch-box](https://github.com/bahrus/scratch-box)** — A visual, form-associated custom element with a declarative shadow DOM template and zero custom element JavaScript. Demonstrates `cede` script definition from a static `root.html` and JSON feature configuration.
|
|
15
|
-
|
|
16
11
|
## Prerequisites
|
|
17
12
|
|
|
18
13
|
- Node.js installed
|
|
@@ -20,6 +15,7 @@ This document provides step-by-step instructions for creating a **brand new** cu
|
|
|
20
15
|
- `ncu` (npm-check-updates) installed globally: `npm install -g npm-check-updates`
|
|
21
16
|
- Chrome 146+ for testing (scoped custom element registry support required)
|
|
22
17
|
|
|
18
|
+
|
|
23
19
|
## Step 1: Initialize the Project
|
|
24
20
|
|
|
25
21
|
1. Create a new repository (e.g., `my-element`)
|
|
@@ -39,7 +35,6 @@ This document provides step-by-step instructions for creating a **brand new** cu
|
|
|
39
35
|
"type": "module",
|
|
40
36
|
"main": "def.js",
|
|
41
37
|
"scripts": {
|
|
42
|
-
"build": "node defRef.mjs > defRef.json",
|
|
43
38
|
"serve": "node ./node_modules/spa-ssi/serve.js",
|
|
44
39
|
"test": "playwright test",
|
|
45
40
|
"update": "ncu -u && npm install",
|
|
@@ -52,19 +47,11 @@ This document provides step-by-step instructions for creating a **brand new** cu
|
|
|
52
47
|
},
|
|
53
48
|
"dependencies": {
|
|
54
49
|
"assign-gingerly": "0.0.48",
|
|
55
|
-
"el-maker": "0.0.0"
|
|
56
|
-
"imp-h": "0.0.5",
|
|
57
|
-
"mount-observer": "0.1.50"
|
|
50
|
+
"el-maker": "0.0.0"
|
|
58
51
|
}
|
|
59
52
|
}
|
|
60
53
|
```
|
|
61
54
|
|
|
62
|
-
**Notes:**
|
|
63
|
-
- Use exact versions, not ranges (no `^` or `~`)
|
|
64
|
-
- `el-maker` brings in `roundabout-lib`, `truth-sourcer`, `face-up`, and `be-reflective` transitively
|
|
65
|
-
- Only add direct dependencies for features unique to your element
|
|
66
|
-
- Run `npm run update` after creating package.json to install dependencies
|
|
67
|
-
|
|
68
55
|
## Step 3: Create Type Definitions
|
|
69
56
|
|
|
70
57
|
Create `types/[project-name]/types.d.ts` with the element's property interface:
|
|
@@ -73,7 +60,7 @@ Create `types/[project-name]/types.d.ts` with the element's property interface:
|
|
|
73
60
|
/**
|
|
74
61
|
* Properties specific to this custom element
|
|
75
62
|
*/
|
|
76
|
-
export interface
|
|
63
|
+
export interface EndUserProps {
|
|
77
64
|
// Properties unique to this element
|
|
78
65
|
myProp: string;
|
|
79
66
|
disabled: boolean;
|
|
@@ -82,179 +69,24 @@ export interface ElementProps {
|
|
|
82
69
|
/**
|
|
83
70
|
* Full property set including internal state
|
|
84
71
|
*/
|
|
85
|
-
export interface AllProps extends
|
|
72
|
+
export interface AllProps extends EndUserProps {
|
|
86
73
|
idx: number;
|
|
87
74
|
item: any;
|
|
88
75
|
}
|
|
89
76
|
|
|
90
|
-
export type
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**Key points:**
|
|
94
|
-
- `ElementProps` — the public API specific to this element
|
|
95
|
-
- `AllProps` — includes internal/computed state managed by roundabout
|
|
96
|
-
- Export `T` as a convenience alias for use in `defRef.mjs` type annotations
|
|
97
|
-
|
|
98
|
-
## Step 4: Create the Element Class, if the complexity is too much for a "code-free" solution.
|
|
77
|
+
export type AP = AllProps;
|
|
99
78
|
|
|
100
|
-
|
|
79
|
+
export interface RunTimeProps extends AllProps, HTMLElement
|
|
101
80
|
|
|
102
|
-
1. The power of assign-gingerly/assignFrom/RoundaboutLib configuration (JSON)
|
|
103
|
-
2. Defining a new reusable custom element feature to include with el-maker's package (Step 5).
|
|
104
|
-
|
|
105
|
-
Create `[element-name]-element.js` (e.g., `my-element-element.js`):
|
|
106
|
-
|
|
107
|
-
```javascript
|
|
108
|
-
import { ElementMaker } from 'el-maker/ElementMaker.js';
|
|
109
|
-
|
|
110
|
-
export class MyElementElement extends ElementMaker {
|
|
111
|
-
static supportedFeatures = {
|
|
112
|
-
...ElementMaker.supportedFeatures,
|
|
113
|
-
myFeature: {},
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
81
|
```
|
|
117
82
|
|
|
118
|
-
**Key
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
- No need for `static formAssociated = true` — `FaceUp.onAssigned` sets it automatically
|
|
123
|
-
- No constructor needed unless you have element-specific initialization
|
|
124
|
-
|
|
125
|
-
## Step 5: Create the Element-Specific Feature (if any)
|
|
126
|
-
|
|
127
|
-
If your element has unique behavior beyond what the inherited features provide, create a custom element feature following [NewCustomElementFeature.md](./NewCustomElementFeature.md).
|
|
128
|
-
|
|
129
|
-
For example, `time-ticker` has a `TimeTicker.js` feature that provides precise drift-correcting ticking.
|
|
130
|
-
|
|
131
|
-
## Step 6: Create defRef.mjs (Roundabout Configuration)
|
|
132
|
-
|
|
133
|
-
Create `defRef.mjs` — this generates the JSON configuration that drives the roundabout reactive wiring:
|
|
134
|
-
|
|
135
|
-
```javascript
|
|
136
|
-
//@ts-check
|
|
137
|
-
|
|
138
|
-
/** @import {RAConfig} from './types/roundabout/types' */
|
|
139
|
-
/** @import {T} from './types/[project-name]/types' */
|
|
140
|
-
/** @import {AttrPatterns} from './types/assign-gingerly/types' */
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* @type {{ [K in keyof T]: K }}
|
|
144
|
-
*/
|
|
145
|
-
const props = {
|
|
146
|
-
myProp: 'myProp',
|
|
147
|
-
disabled: 'disabled',
|
|
148
|
-
// ... all properties that roundabout manages
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* @type {RAConfig<T,T,T>}
|
|
153
|
-
*/
|
|
154
|
-
export const raConfig = {
|
|
155
|
-
propagate: /** @type {Array<keyof T>} */ (Object.keys(props)),
|
|
156
|
-
compacts: {
|
|
157
|
-
// Reactive shorthand rules
|
|
158
|
-
},
|
|
159
|
-
merges: [
|
|
160
|
-
// Reactive assignment rules
|
|
161
|
-
],
|
|
162
|
-
yields: {
|
|
163
|
-
// Derived property rules
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* @type {AttrPatterns<T>}
|
|
169
|
-
*/
|
|
170
|
-
const withAttrs = {
|
|
171
|
-
// Attribute-to-property mappings for truthSourcer
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
export const cef = {
|
|
175
|
-
features: {
|
|
176
|
-
roundabout: {
|
|
177
|
-
customData: {
|
|
178
|
-
raConfig
|
|
179
|
-
},
|
|
180
|
-
withAttrs
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
export function render() {
|
|
186
|
-
return JSON.stringify(cef, null, 4);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
console.log(render());
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
**Key patterns:**
|
|
193
|
-
- The `props` object provides type-safe property name references (keys must be in `T`, values must equal the key)
|
|
194
|
-
- `raConfig` defines the reactive wiring: compacts (shorthand rules), merges (assignment rules), yields (derived values)
|
|
195
|
-
- `withAttrs` maps HTML attributes to properties (used by `truthSourcer`)
|
|
196
|
-
- The `render()` function outputs JSON for the build step
|
|
197
|
-
|
|
198
|
-
Run `npm run build` to generate `defRef.json`.
|
|
199
|
-
|
|
200
|
-
## Step 7: Create wireFeatures.js
|
|
201
|
-
|
|
202
|
-
This module resolves async fallback spawns and calls `assignFeatures` with the element-specific configuration:
|
|
203
|
-
|
|
204
|
-
```javascript
|
|
205
|
-
import { MyFeature } from './MyFeature.js';
|
|
206
|
-
import { resolveAndAssignFeatures } from 'assign-gingerly/resolveAndAssignFeatures.js';
|
|
207
|
-
|
|
208
|
-
export async function wireFeatures(ElementClass, cfg) {
|
|
209
|
-
const { roundabout } = cfg.features;
|
|
210
|
-
const { customData, withAttrs } = roundabout;
|
|
211
|
-
|
|
212
|
-
await resolveAndAssignFeatures(ElementClass, {
|
|
213
|
-
myFeature: { spawn: MyFeature },
|
|
214
|
-
truthSourcer: {
|
|
215
|
-
callbackForwarding: ['connectedCallback', 'attributeChangedCallback'],
|
|
216
|
-
},
|
|
217
|
-
faceUp: {
|
|
218
|
-
customData: { integrateWithRoundabout: true },
|
|
219
|
-
callbackForwarding: [
|
|
220
|
-
'connectedCallback', 'disconnectedCallback',
|
|
221
|
-
'formDisabledCallback', 'formResetCallback', 'formStateRestoreCallback',
|
|
222
|
-
],
|
|
223
|
-
},
|
|
224
|
-
roundabout: {
|
|
225
|
-
customData,
|
|
226
|
-
withAttrs,
|
|
227
|
-
callbackForwarding: ['connectedCallback'],
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
**Key patterns:**
|
|
234
|
-
- Only eagerly imports the feature(s) unique to this element
|
|
235
|
-
- Inherited features (`truthSourcer`, `faceUp`, `roundabout`, `reflector`) use their async `fallbackSpawn` from `ElementMaker` — no explicit `spawn` needed
|
|
236
|
-
- `resolveAndAssignFeatures` resolves async fallback spawns before calling `assignFeatures`, ensuring `onAssigned` hooks (like `FaceUp.onAssigned` setting `static formAssociated = true`) run before `define()`
|
|
237
|
-
- `callbackForwarding` and `customData` are per-element configuration that gets unioned with the author defaults from `supportedFeatures`
|
|
238
|
-
|
|
239
|
-
## Step 8: Create def.js
|
|
240
|
-
|
|
241
|
-
The side-effect module that registers the custom element with its canonical tag name and default feature wiring:
|
|
242
|
-
|
|
243
|
-
```javascript
|
|
244
|
-
import { MyElementElement } from './my-element-element.js';
|
|
245
|
-
import { wireFeatures } from './wireFeatures.js';
|
|
246
|
-
import defRef from './defRef.json' with { type: 'json' };
|
|
247
|
-
|
|
248
|
-
await wireFeatures(MyElementElement, defRef);
|
|
249
|
-
customElements.define('my-element', MyElementElement);
|
|
250
|
-
```
|
|
83
|
+
**Key points:**
|
|
84
|
+
- `EndUserProps` — the public API specific to this element
|
|
85
|
+
- `AllProps` — includes internal/computed state managed by roundabout
|
|
86
|
+
- Export `AP` as a convenience alias
|
|
251
87
|
|
|
252
|
-
**Key patterns:**
|
|
253
|
-
- `def.js` = "default define" — centralizes all side effects
|
|
254
|
-
- Imports the JSON config and passes it to `wireFeatures`
|
|
255
|
-
- Consumers who want a different tag name, scoped registry, or DI overrides write their own version of this file
|
|
256
88
|
|
|
257
|
-
## Step
|
|
89
|
+
## Step 4: Create imports.html
|
|
258
90
|
|
|
259
91
|
```html
|
|
260
92
|
<script type=importmap>
|
|
@@ -262,11 +94,8 @@ customElements.define('my-element', MyElementElement);
|
|
|
262
94
|
"imports": {
|
|
263
95
|
"assign-gingerly/": "/node_modules/assign-gingerly/",
|
|
264
96
|
"el-maker/": "/node_modules/el-maker/",
|
|
265
|
-
"face-up/": "/node_modules/face-up/",
|
|
266
|
-
"on-to-me/": "/node_modules/on-to-me/",
|
|
267
97
|
"roundabout-lib/": "/node_modules/roundabout-lib/",
|
|
268
98
|
"[project-name]/": "/",
|
|
269
|
-
"truth-sourcer/": "/node_modules/truth-sourcer/"
|
|
270
99
|
}
|
|
271
100
|
}
|
|
272
101
|
</script>
|
|
@@ -276,102 +105,33 @@ customElements.define('my-element', MyElementElement);
|
|
|
276
105
|
- Include all transitive dependencies that are loaded in the browser
|
|
277
106
|
- The project itself maps to `/` for local development
|
|
278
107
|
|
|
279
|
-
##
|
|
108
|
+
## Fork in the road -- HTML first vs JS First
|
|
280
109
|
|
|
281
|
-
|
|
110
|
+
When developing such a web component, a fundamental question must be asked -- is the web component heavy on HTML / CSS, or is the web component a (usually non visual) component that is heavy on non-reusable JavaScript - JS First?
|
|
282
111
|
|
|
283
|
-
|
|
284
|
-
{
|
|
285
|
-
"name": "Auto-build Configuration",
|
|
286
|
-
"version": "1.0.0",
|
|
287
|
-
"description": "Automatically runs npm run build when defRef.mjs is saved",
|
|
288
|
-
"when": {
|
|
289
|
-
"type": "fileEdited",
|
|
290
|
-
"patterns": ["**/*.mjs"]
|
|
291
|
-
},
|
|
292
|
-
"then": {
|
|
293
|
-
"type": "askAgent",
|
|
294
|
-
"prompt": "A .mjs file was changed. Run npm run build to regenerate the output."
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
```
|
|
112
|
+
If the decision is JS-first, follow the directions of [New JS First Custom Element](./NewJSFirstCustomElement.md).
|
|
298
113
|
|
|
299
|
-
|
|
114
|
+
If the decision is mostly code free, HTML-first, follow the directions of [New HTML First Custom Element](./NewHTMLFirstCustomElement.md).
|
|
300
115
|
|
|
301
|
-
Create `tests/test1.html`:
|
|
302
116
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<head>
|
|
307
|
-
<meta charset="UTF-8">
|
|
308
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
309
|
-
<title>Test - my-element</title>
|
|
310
|
-
<!-- #include virtual="/imports.html" -->
|
|
311
|
-
<script type=module>
|
|
312
|
-
import '[project-name]/def.js';
|
|
313
|
-
</script>
|
|
314
|
-
</head>
|
|
315
|
-
<body>
|
|
316
|
-
<my-element></my-element>
|
|
317
|
-
</body>
|
|
318
|
-
</html>
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
## Architecture Overview
|
|
322
|
-
|
|
323
|
-
```
|
|
324
|
-
[project-name]/
|
|
325
|
-
├── .kiro/
|
|
326
|
-
│ ├── hooks/
|
|
327
|
-
│ │ └── auto-build-config.kiro.hook
|
|
328
|
-
│ └── steering/
|
|
329
|
-
│ └── project-context.md
|
|
330
|
-
├── .vscode/
|
|
331
|
-
│ └── settings.json
|
|
332
|
-
├── types/ (git submodule)
|
|
333
|
-
│ └── [project-name]/
|
|
334
|
-
│ └── types.d.ts
|
|
335
|
-
├── [element-name]-element.js (element class — extends ElementMaker)
|
|
336
|
-
├── [FeatureName].js (element-specific feature, if any)
|
|
337
|
-
├── wireFeatures.js (resolves + assigns features)
|
|
338
|
-
├── def.js (side-effect: wire + define)
|
|
339
|
-
├── defRef.mjs (build script → defRef.json)
|
|
340
|
-
├── defRef.json (generated — roundabout config)
|
|
341
|
-
├── imports.html (import map for browser)
|
|
342
|
-
├── package.json
|
|
343
|
-
├── tests/
|
|
344
|
-
│ └── test1.html
|
|
345
|
-
└── README.md
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
## The Three-File Pattern
|
|
349
|
-
|
|
350
|
-
Every custom element package exports three key modules:
|
|
117
|
+
**Notes:**
|
|
118
|
+
- Use exact versions, not ranges (no `^` or `~`)
|
|
119
|
+
- Run `npm run update` after creating package.json to install dependencies
|
|
351
120
|
|
|
352
|
-
| File | Role | Side effects? |
|
|
353
|
-
|------|------|---------------|
|
|
354
|
-
| `[element-name]-element.js` | Class definition + `supportedFeatures` declaration | No |
|
|
355
|
-
| `wireFeatures.js` | Resolves spawns + calls `assignFeatures` with config | No |
|
|
356
|
-
| `def.js` | Imports config, wires features, calls `define()` | Yes |
|
|
357
121
|
|
|
358
|
-
This separation enables:
|
|
359
|
-
- **Different tag names** — write your own `def.js` with a different `define()` call
|
|
360
|
-
- **Scoped registries** — call `scopedRegistry.define()` instead of `customElements.define()`
|
|
361
|
-
- **DI / testing** — call `resolveAndAssignFeatures` directly with mock spawns
|
|
362
|
-
- **Declarative definition** — use `defineWithFeatures` from a cede script without any JS class code
|
|
363
122
|
|
|
364
|
-
## What ElementMaker Provides
|
|
123
|
+
## What [ElementMaker](https://github.com/bahrus/el-maker) Provides
|
|
365
124
|
|
|
366
|
-
By extending `ElementMaker`, your element inherits:
|
|
125
|
+
By extending `ElementMaker`, your element inherits these loaded on-demand features:
|
|
367
126
|
|
|
368
|
-
| Feature |
|
|
369
|
-
|
|
370
|
-
|
|
|
371
|
-
|
|
|
372
|
-
|
|
|
373
|
-
|
|
|
374
|
-
|
|
|
127
|
+
| Key/Feature | Package | Description | Source |
|
|
128
|
+
|--------------|-------------|-------------|--------|
|
|
129
|
+
| truthSourcer | [truth-sourcer](https://www.npmjs.com/package/truth-sourcer) | Attribute/property binding and truth-sourcing for custom elements | [GitHub](https://github.com/bahrus/truth-sourcer) |
|
|
130
|
+
| reflector | [be-reflective](https://www.npmjs.com/package/be-reflective) | CSS custom state reflection from computed styles | [GitHub](https://github.com/bahrus/be-reflective) |
|
|
131
|
+
| faceUp | [face-up](https://www.npmjs.com/package/face-up) | Form Associated Custom Element behavior via ElementInternals | [GitHub](https://github.com/bahrus/face-up) |
|
|
132
|
+
| roundabout | [roundabout-lib](https://www.npmjs.com/package/roundabout-lib) | Reactive view-model binding with template rendering and computed property orchestration | [GitHub](https://github.com/bahrus/roundabout-lib) |
|
|
133
|
+
| templateMaker | [templ-maker](https://www.npmjs.com/package/templ-maker) | Extracts a DOM fragment into a reusable template and clones it per instance (works with cede scripts) | [GitHub](https://github.com/bahrus/templ-maker) |
|
|
134
|
+
| fontMgr | [font-face-feature](https://www.npmjs.com/package/font-face-feature) | Installs global fonts | [GitHub](https://github.com/bahrus/font-face-feature)
|
|
375
135
|
|
|
376
136
|
Plus infrastructure:
|
|
377
137
|
- `propagator` (EventTarget) for inter-feature communication
|
|
@@ -379,185 +139,8 @@ Plus infrastructure:
|
|
|
379
139
|
- `attachInternals()` called in the constructor
|
|
380
140
|
- Async `fallbackSpawn` for lazy-loading all feature implementations
|
|
381
141
|
|
|
382
|
-
## Elements Without HTML (Non-Visual)
|
|
383
|
-
|
|
384
|
-
For elements like `time-ticker` that have no HTML template or shadow DOM, simply don't activate the `templateMaker` feature in `wireFeatures.js`. The feature remains declared in `supportedFeatures` (inherited from `ElementMaker`) but is never instantiated because no `assignFeatures` call references it.
|
|
385
|
-
|
|
386
|
-
## Elements With HTML: Declarative Shadow DOM Without a JS Class
|
|
387
|
-
|
|
388
|
-
For visual elements you can skip the `def.js` / `wireFeatures.js` / custom element class entirely and register the element with a `cede` script that extends `el-maker`. The scratch-box checkbox is the reference implementation of this pattern: it is built from a single static HTML file (`root.html`) and a JSON feature configuration (`el-maker.json`).
|
|
389
|
-
|
|
390
|
-
### How scratch-box is structured
|
|
391
|
-
|
|
392
|
-
| File | Role |
|
|
393
|
-
|------|------|
|
|
394
|
-
| `root.html` | Declarative shadow DOM template, styles, inner form, and enhancement metadata. |
|
|
395
|
-
| `el-maker.mjs` | Type-checked configuration generator for the ElementMaker features. |
|
|
396
|
-
| `el-maker.json` | Generated JSON consumed by the `cede` script. |
|
|
397
|
-
|
|
398
|
-
### The template file (`root.html`)
|
|
399
|
-
|
|
400
|
-
The host element declares its shadow root declaratively, then contains everything needed inside the shadow DOM, including styles, a form element, and a `<be-hive>` block that wires up declarative enhancements:
|
|
401
|
-
|
|
402
|
-
```html
|
|
403
|
-
<scratch-box>
|
|
404
|
-
<template shadowrootmode=open>
|
|
405
|
-
<style adopt>
|
|
406
|
-
:host[hidden] { display:none; }
|
|
407
|
-
:host { display:block; background-color: HSL(250, 22%, 41%); padding: 1vw; }
|
|
408
|
-
/* ... remaining styles ... */
|
|
409
|
-
</style>
|
|
410
|
-
<form class="checkbox-wrapper">
|
|
411
|
-
<input 🪢 name=value type="checkbox" id="option"/>
|
|
412
|
-
<link itemprop=value>
|
|
413
|
-
<label for="option">
|
|
414
|
-
<slot name="labelTxt">scratch-box</slot>
|
|
415
|
-
<svg viewBox="0 0 60 40" aria-hidden="true" focusable="false">
|
|
416
|
-
<path d="M21,2 ..." stroke-width="4" fill="none" stroke-dasharray="270" stroke-dashoffset="270"></path>
|
|
417
|
-
</svg>
|
|
418
|
-
</label>
|
|
419
|
-
</form>
|
|
420
|
-
|
|
421
|
-
<be-hive>
|
|
422
|
-
<script type=emc-parser
|
|
423
|
-
src="be-hive/parsers/parse-grouped-capture-statements.js"
|
|
424
|
-
parser-name=parse-grouped-capture-statements></script>
|
|
425
|
-
<script type=emc
|
|
426
|
-
src="be-bound/🪢.json"
|
|
427
|
-
wait-for-parsers=parse-grouped-capture-statements></script>
|
|
428
|
-
</be-hive>
|
|
429
|
-
</template>
|
|
430
|
-
</scratch-box>
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
Key details:
|
|
434
|
-
|
|
435
|
-
- `shadowrootmode=open` gives the element a declarative shadow DOM that the browser attaches before any script runs.
|
|
436
|
-
- The internal checkbox is named `value` and carries the `🪢` emoji attribute. That marks it for the `be-bound` enhancement so the host `value` property and the inner checkbox `checked` state stay in sync.
|
|
437
|
-
- `<link itemprop=value>` lets the `faceUp` feature expose the element as a form-associated value without any JS wiring.
|
|
438
|
-
- The `<slot name="labelTxt">` lets users provide the label from light DOM via `<span slot="labelTxt">...</span>`.
|
|
439
|
-
- `<style adopt>` with `adopt` ensures the styles are adopted into the shadow root instead of a separate `<style>` element.
|
|
440
|
-
|
|
441
|
-
### The feature configuration (`el-maker.mjs` → `el-maker.json`)
|
|
442
|
-
|
|
443
|
-
Instead of `wireFeatures.js`, the features are declared in JSON and consumed by the `cede` script. The source file is type-checked TypeScript via JSDoc comments and outputs `el-maker.json`:
|
|
444
|
-
|
|
445
|
-
```javascript
|
|
446
|
-
//@ts-check
|
|
447
|
-
|
|
448
|
-
import { writeFileSync } from 'fs';
|
|
449
|
-
import { fileURLToPath } from 'url';
|
|
450
|
-
import {akaMethods as m, aka, builtInEmoji} from 'assign-gingerly/DX/emojis.js';
|
|
451
|
-
|
|
452
|
-
/** @import {FontFaceFeatureConfig} from './types/font-face-feature/types'; */
|
|
453
|
-
/** @import {EndUserProps} from './types'; */
|
|
454
|
-
/** @import {RoundaboutOptions} from './types/roundabout/types' */
|
|
455
|
-
/** @import {ElMakerConfig} from './types/el-maker/types' */
|
|
456
|
-
|
|
457
|
-
const props = {
|
|
458
|
-
value: 'value',
|
|
459
|
-
name: 'name',
|
|
460
|
-
disabled: 'disabled',
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
const fontFaceFeatureConfig = {
|
|
464
|
-
fontFamilies: [
|
|
465
|
-
{
|
|
466
|
-
name: 'Indie Flower',
|
|
467
|
-
url: 'https://fonts.gstatic.com/s/indieflower/v24/m8JVjfNVeKWVnh3QMuKkFcZVZ0uH5dI.woff2',
|
|
468
|
-
descriptors: {
|
|
469
|
-
style: 'normal',
|
|
470
|
-
weight: '400',
|
|
471
|
-
unicodeRange: '...',
|
|
472
|
-
},
|
|
473
|
-
},
|
|
474
|
-
// additional font-face descriptors...
|
|
475
|
-
],
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
const raConfig = {
|
|
479
|
-
assignOptions: {
|
|
480
|
-
akaMethods: {
|
|
481
|
-
'🔍': m['🔍']
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
|
-
merges: [
|
|
485
|
-
{
|
|
486
|
-
ifKeyIn: ['disabled'],
|
|
487
|
-
assign: {
|
|
488
|
-
'?.shadowRoot?.🔍?.input?.disabled': '?.disabled',
|
|
489
|
-
}
|
|
490
|
-
},
|
|
491
|
-
],
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
/** @type {ElMakerConfig<EndUserProps>} */
|
|
495
|
-
const features = {
|
|
496
|
-
assignFeatures: {
|
|
497
|
-
faceUp: { customData: { integrateWithRoundabout: true } },
|
|
498
|
-
truthSourcer: {},
|
|
499
|
-
roundabout: { customData: { raConfig } },
|
|
500
|
-
fontMgr: { customData: { fontFaceFeatureConfig } },
|
|
501
|
-
templateMaker: {},
|
|
502
|
-
},
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
export function render() {
|
|
506
|
-
return JSON.stringify(features, null, 4);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
510
|
-
const outputFile = __filename.replace(/\.mjs$/, '.json');
|
|
511
|
-
writeFileSync(outputFile, render(), 'utf8');
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
Run `node el-maker.mjs` (or `npm run build-el-maker` if your `package.json` includes a watch script) to regenerate `el-maker.json`.
|
|
515
|
-
|
|
516
|
-
### Registering the element in a page
|
|
517
|
-
|
|
518
|
-
The element is defined by a `cede` script. Include `imp-h` (or another template importer) to fetch `root.html`, and `el-maker/def.js` to provide the base class machinery:
|
|
519
|
-
|
|
520
|
-
```html
|
|
521
|
-
<!DOCTYPE html>
|
|
522
|
-
<html lang="en">
|
|
523
|
-
<head>
|
|
524
|
-
<meta charset="UTF-8">
|
|
525
|
-
<title>scratch-box demo</title>
|
|
526
|
-
<script type=module>
|
|
527
|
-
import 'be-hive/be-hive.js';
|
|
528
|
-
import 'imp-h/imp-h.js';
|
|
529
|
-
import 'el-maker/def.js';
|
|
530
|
-
</script>
|
|
531
|
-
</head>
|
|
532
|
-
<body>
|
|
533
|
-
<scratch-box imp-h="scratch-box/root.html">
|
|
534
|
-
<span slot=labelTxt>Create demo</span>
|
|
535
|
-
<script type=cede data-extends=el-maker src="scratch-box/el-maker.json"></script>
|
|
536
|
-
</scratch-box>
|
|
537
|
-
</body>
|
|
538
|
-
</html>
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
Notes:
|
|
542
|
-
|
|
543
|
-
- `imp-h` observes the `imp-h` attribute and imports the declarative shadow DOM template from `root.html`.
|
|
544
|
-
- The `<script type=cede data-extends=el-maker>` tells the mount observer to register the host element by extending `ElementMaker` and applying the feature JSON.
|
|
545
|
-
- No JS class file is required because all behavior is provided by the configured ElementMaker features and the declarative shadow DOM.
|
|
546
|
-
|
|
547
|
-
### When to use this pattern
|
|
548
142
|
|
|
549
|
-
Use the declarative shadow DOM + `cede` pattern when:
|
|
550
143
|
|
|
551
|
-
- The element is primarily visual and static.
|
|
552
|
-
- You want server-side rendering and progressive enhancement with no client-side custom element class.
|
|
553
|
-
- Feature configuration (form association, attribute reflection, reactive wiring, fonts) is sufficient for all behavior.
|
|
554
144
|
|
|
555
|
-
When you need custom runtime behavior beyond the shared features, fall back to the class-based pattern in the earlier sections and add your own feature.
|
|
556
145
|
|
|
557
|
-
## Tips
|
|
558
146
|
|
|
559
|
-
- **Call `wireFeatures` before `customElements.define()`** — features must be on the prototype before instances exist
|
|
560
|
-
- **Use `@ts-check`** in `.mjs` files — catches type errors in the build configuration
|
|
561
|
-
- **Run `npm run build` after editing `defRef.mjs`** — the JSON must be regenerated
|
|
562
|
-
- **Don't eagerly import inherited features** — let `fallbackSpawn` lazy-load them
|
|
563
|
-
- **Keep `def.js` minimal** — it's the canonical handshake; consumers can deviate as needed
|