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
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## Reference Implementations
|
|
5
|
+
|
|
6
|
+
- **[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.
|
|
7
|
+
|
|
8
|
+
- **[plus-minus](https://github.com/bahrus/plus-minus)** -- Expand / Collapse component - More robust examples of dynamic DOM manipulation with the help of roundabout configuration. Also demonstrates use of the DX libraries to get typing intellisense help.
|
|
9
|
+
|
|
10
|
+
## Step 4
|
|
11
|
+
|
|
12
|
+
Add the following additional dependencies in package.json:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
...
|
|
17
|
+
"dependencies": {
|
|
18
|
+
...
|
|
19
|
+
"be-hive": "0.1.16",
|
|
20
|
+
"imp-h": "0.0.5",
|
|
21
|
+
"mount-observer": "0.1.50"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Step 5
|
|
27
|
+
|
|
28
|
+
Add the additional mappings to import.html
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
"be-hive/": "/node_modules/be-hive/",
|
|
32
|
+
"imp-h/": "/node_modules/imp-h/",
|
|
33
|
+
"mount-observer/": "/node_modules/mount-observer/",
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Step 6
|
|
37
|
+
|
|
38
|
+
Create an html file that provides the shadowDOM content.
|
|
39
|
+
|
|
40
|
+
It can contain other stuff outside the start / end markers.
|
|
41
|
+
|
|
42
|
+
<details>
|
|
43
|
+
<summary>For example:</summary>
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<!DOCTYPE html>
|
|
47
|
+
<html lang="en">
|
|
48
|
+
<head>
|
|
49
|
+
<meta charset="UTF-8">
|
|
50
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
51
|
+
<title>plus-minus</title>
|
|
52
|
+
</head>
|
|
53
|
+
<body>
|
|
54
|
+
<plus-minus>
|
|
55
|
+
<template shadowrootmode="open"><?start><?start>
|
|
56
|
+
<style adopt>
|
|
57
|
+
div, section {
|
|
58
|
+
display: inline;
|
|
59
|
+
}
|
|
60
|
+
button {
|
|
61
|
+
background-color: transparent;
|
|
62
|
+
background-repeat: no-repeat;
|
|
63
|
+
border: none;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
outline: none;
|
|
67
|
+
margin: 0;
|
|
68
|
+
padding: 0;
|
|
69
|
+
position: relative;
|
|
70
|
+
top: 2px;
|
|
71
|
+
}
|
|
72
|
+
:host{
|
|
73
|
+
margin: 0;
|
|
74
|
+
padding: 0;
|
|
75
|
+
display: inline;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
78
|
+
<section>
|
|
79
|
+
<button disabled type=button name=expand part="collapsed expand button" aria-label="Show Details">
|
|
80
|
+
<svg width="16px" height="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-plus-square">
|
|
81
|
+
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
|
|
82
|
+
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
|
|
83
|
+
</svg>
|
|
84
|
+
</button>
|
|
85
|
+
<button disabled type=button name=collapse part="expanded collapse button" aria-label="Hide Details" hidden>
|
|
86
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
|
|
87
|
+
<g>
|
|
88
|
+
<line fill="none" stroke="#000000" stroke-width="4" stroke-miterlimit="10" x1="14" y1="31" x2="50" y2="31"/>
|
|
89
|
+
</g>
|
|
90
|
+
<rect x="1" y="1" fill="none" stroke="#000000" stroke-width="6" stroke-miterlimit="10" width="62" height="62"/>
|
|
91
|
+
</svg>
|
|
92
|
+
</button>
|
|
93
|
+
</section>
|
|
94
|
+
<?end><?end></template>
|
|
95
|
+
</plus-minus>
|
|
96
|
+
</body>
|
|
97
|
+
</html>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
</details>
|
|
101
|
+
|
|
102
|
+
If using element enhancement / custom attribute libraries, include the be-hive folder:
|
|
103
|
+
|
|
104
|
+
<details>
|
|
105
|
+
<summary>For example:</summary>
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<html>
|
|
109
|
+
<head>
|
|
110
|
+
<title>scratch-box</title>
|
|
111
|
+
<link rel=stylesheet href="https://fonts.googleapis.com/css?family=Indie+Flower">
|
|
112
|
+
</head>
|
|
113
|
+
<body>
|
|
114
|
+
<scratch-box>
|
|
115
|
+
<template shadowrootmode=open><?start><?start>
|
|
116
|
+
<style adopt>
|
|
117
|
+
:host[hidden]{
|
|
118
|
+
display:none;
|
|
119
|
+
}
|
|
120
|
+
:host{
|
|
121
|
+
display:block;
|
|
122
|
+
background-color: HSL(250, 22%, 41%);
|
|
123
|
+
padding: 1vw;
|
|
124
|
+
}
|
|
125
|
+
:has(input[disabled]){
|
|
126
|
+
opacity: 0.5;
|
|
127
|
+
}
|
|
128
|
+
.checkbox-wrapper {
|
|
129
|
+
position: relative;
|
|
130
|
+
|
|
131
|
+
margin: .5em 1em;
|
|
132
|
+
font-size: 3em;
|
|
133
|
+
color: #eee;
|
|
134
|
+
font-family: Indie Flower;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
svg {
|
|
138
|
+
width: 1em;
|
|
139
|
+
height: 1em;
|
|
140
|
+
position: absolute;
|
|
141
|
+
left: 0.5em;
|
|
142
|
+
top: .3em;
|
|
143
|
+
border: 2px solid #eee;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
label {
|
|
147
|
+
display: block;
|
|
148
|
+
padding: .25em .5em .25em 2em;
|
|
149
|
+
position: relative;
|
|
150
|
+
cursor: pointer;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
input[type="checkbox"] {
|
|
154
|
+
opacity: 0;
|
|
155
|
+
position: absolute;
|
|
156
|
+
left: .75em;
|
|
157
|
+
top: .75em;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
label svg path {
|
|
161
|
+
transition: stroke-dashoffset .4s linear;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
input[type="checkbox"]:checked ~ label svg {
|
|
165
|
+
border-color: #111;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
input[type="checkbox"]:checked ~ label svg path {
|
|
169
|
+
stroke-dashoffset: 0;
|
|
170
|
+
stroke: currentColor;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
input[type="checkbox"] ~ label svg path {
|
|
174
|
+
stroke: #eee;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
input[type="checkbox"]:checked ~ label {
|
|
178
|
+
color: #111;
|
|
179
|
+
text-decoration: line-through;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
input[type="checkbox"]:focus ~ label {
|
|
183
|
+
outline: 2px solid black;
|
|
184
|
+
}
|
|
185
|
+
</style>
|
|
186
|
+
<form class="checkbox-wrapper">
|
|
187
|
+
<!-- length of the path is 270px -->
|
|
188
|
+
<input 🪢 name=value type="checkbox" id="option"/>
|
|
189
|
+
<link itemprop=value>
|
|
190
|
+
<label for="option">
|
|
191
|
+
<slot name="labelTxt">scratch-box</slot>
|
|
192
|
+
<svg viewBox="0 0 60 40" aria-hidden="true" focusable="false"><path d="M21,2 C13.4580219,4.16027394 1.62349378,18.3117469 3,19 C9.03653312,22.0182666 25.2482171,10.3758914 30,8 C32.9363621,6.53181896 41.321398,1.67860195 39,4 C36.1186011,6.8813989 3.11316157,27.1131616 5,29 C10.3223659,34.3223659 30.6434647,19.7426141 35,18 C41.2281047,15.5087581 46.3445303,13.6554697 46,14 C42.8258073,17.1741927 36.9154967,19.650702 33,22 C30.3136243,23.6118254 17,31.162498 17,34 C17,40.4724865 54,12.4064021 54,17 C54,23.7416728 34,27.2286213 34,37" stroke-width="4" fill="none" stroke-dasharray="270" stroke-dashoffset="270" ></path></svg>
|
|
193
|
+
</label>
|
|
194
|
+
</form>
|
|
195
|
+
|
|
196
|
+
<be-hive>
|
|
197
|
+
<script type=emc-parser
|
|
198
|
+
src="be-hive/parsers/parse-grouped-capture-statements.js"
|
|
199
|
+
parser-name=parse-grouped-capture-statements></script>
|
|
200
|
+
<script type=emc
|
|
201
|
+
src="be-bound/🪢.json"
|
|
202
|
+
wait-for-parsers=parse-grouped-capture-statements></script>
|
|
203
|
+
</be-hive>
|
|
204
|
+
<?end><?end></template>
|
|
205
|
+
</scratch-box>
|
|
206
|
+
</body>
|
|
207
|
+
</html>
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Key details:
|
|
212
|
+
|
|
213
|
+
- `shadowrootmode=open` gives the element a declarative shadow DOM that the browser attaches before any script runs.
|
|
214
|
+
- 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.
|
|
215
|
+
- `<link itemprop=value>` lets the `faceUp` feature expose the element as a form-associated value without any JS wiring.
|
|
216
|
+
- The `<slot name="labelTxt">` lets users provide the label from light DOM via `<span slot="labelTxt">...</span>`.
|
|
217
|
+
- `<style adopt>` with `adopt` ensures the styles are adopted into the shadow root instead of a separate `<style>` element.
|
|
218
|
+
|
|
219
|
+
</details>
|
|
220
|
+
|
|
221
|
+
So what have in this file, within the <?start> and <?end> markers should be all HTML, css, and the only script tags should be one of the types supported by mount-observer -- emc-parser, emc, mountobserver, cede.
|
|
222
|
+
|
|
223
|
+
Ideally, this standalone html file can serve as a primitive demo of the web component itself, and it's quite acceptable to add a little script or other things outside the <?start> and <?end> markers to make the demo page more functional.
|
|
224
|
+
|
|
225
|
+
## Step 7.
|
|
226
|
+
|
|
227
|
+
Create el-maker.mjs, which generates the el-maker.json file.
|
|
228
|
+
|
|
229
|
+
<details>
|
|
230
|
+
<summary>For example</summary>
|
|
231
|
+
|
|
232
|
+
```JS
|
|
233
|
+
//@ts-check
|
|
234
|
+
|
|
235
|
+
import { writeFileSync } from 'fs';
|
|
236
|
+
import { resolve } from 'path';
|
|
237
|
+
import { fileURLToPath } from 'url';
|
|
238
|
+
import {akaMethods as m, aka, builtInEmoji} from 'assign-gingerly/DX/emojis.js';
|
|
239
|
+
import {paths, doAssign, set, smoothOver} from 'assign-gingerly/DX/paths.js';
|
|
240
|
+
|
|
241
|
+
/** @import {EndUserProps, AP, RuntimeProps} from './types'; */
|
|
242
|
+
/** @import {RoundaboutOptions} from './types/roundabout/types' */
|
|
243
|
+
/** @import {ElMakerConfig} from './types/el-maker/types' */
|
|
244
|
+
|
|
245
|
+
const withMethods = [m['🔍']];
|
|
246
|
+
|
|
247
|
+
const $ = (/** @type {typeof paths<RuntimeProps>} */ (/** @type {any} */(paths)))({withMethods});
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @type {RoundaboutOptions<AP>}
|
|
252
|
+
*/
|
|
253
|
+
const raConfig = {
|
|
254
|
+
weakRef: {
|
|
255
|
+
properties: ['expandButton', 'collapseButton'],
|
|
256
|
+
logIfCollected: 'warn'
|
|
257
|
+
},
|
|
258
|
+
assignOptions: {
|
|
259
|
+
akaMethods:{
|
|
260
|
+
'🔍': m['🔍']
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
compacts: {
|
|
264
|
+
on_click_of_expandButton_assign: {
|
|
265
|
+
[$.expandButton.hidden.Path]: true,
|
|
266
|
+
[$.collapseButton.hidden.Path]: false,
|
|
267
|
+
expanded: true,
|
|
268
|
+
|
|
269
|
+
},
|
|
270
|
+
on_click_of_collapseButton_assign: {
|
|
271
|
+
expanded: false,
|
|
272
|
+
[$.expandButton.hidden.Path]: false,
|
|
273
|
+
[$.collapseButton.hidden.Path]: true,
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
merges: smoothOver([
|
|
277
|
+
{
|
|
278
|
+
ifKeyIn: ['clone'],
|
|
279
|
+
...doAssign(
|
|
280
|
+
set($.expandButton).to($.clone.querySelector('[name=expand]')),
|
|
281
|
+
set($.collapseButton).to($.clone.querySelector('[name=collapse]')),
|
|
282
|
+
)
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
ifKeyIn: ['expanded'],
|
|
286
|
+
...doAssign(
|
|
287
|
+
set($.ariaExpanded).to($.expanded),
|
|
288
|
+
set($.ariaControlsElements.Each.hidden.QMEq).to([$.expanded, false, 'until-found'])
|
|
289
|
+
)
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
ifKeyIn: ['disabled'],
|
|
293
|
+
ifAllOf: ['clone'],
|
|
294
|
+
...doAssign(
|
|
295
|
+
set($.expandButton.disabled).to($.disabled),
|
|
296
|
+
set($.collapseButton.disabled).to($.disabled),
|
|
297
|
+
)
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
]),
|
|
301
|
+
defaultPropVals: {
|
|
302
|
+
disabled: false
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** @type {ElMakerConfig<AP>} */
|
|
307
|
+
const features = {
|
|
308
|
+
assignFeatures: {
|
|
309
|
+
roundabout: {
|
|
310
|
+
customData: {
|
|
311
|
+
raConfig,
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
templateMaker: {}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function render() {
|
|
319
|
+
return JSON.stringify(features, null, 4);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
323
|
+
const outputFile = __filename.replace(/\.mjs$/, '.json');
|
|
324
|
+
writeFileSync(outputFile, render(), 'utf8');
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
</details>
|
|
328
|
+
|
|
329
|
+
Some non obvious scenarios:
|
|
330
|
+
|
|
331
|
+
### How can I perform an action when the escape key is pressed somewhere on the document?
|
|
332
|
+
|
|
333
|
+
Answer:
|
|
334
|
+
|
|
335
|
+
From the side-burger example.
|
|
336
|
+
|
|
337
|
+
```JS
|
|
338
|
+
const raConfig = {
|
|
339
|
+
...
|
|
340
|
+
compacts: {
|
|
341
|
+
on_keydown_of_ownerDocument_assignFromEvent: {
|
|
342
|
+
[$.escapeKeyPressed.QMEq.Path]: [['?.key', 'Escape'], true, false]
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
merges: smoothOver([
|
|
346
|
+
{
|
|
347
|
+
ifKeyIn: ['expanded'],
|
|
348
|
+
...doAssign(
|
|
349
|
+
...
|
|
350
|
+
set($.escapeKeyPressed).to(false)
|
|
351
|
+
)
|
|
352
|
+
},
|
|
353
|
+
...
|
|
354
|
+
{
|
|
355
|
+
ifAllOf: ['escapeKeyPressed'],
|
|
356
|
+
assign: {
|
|
357
|
+
expanded: false
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
]),
|
|
361
|
+
};
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Step 8
|
|
365
|
+
|
|
366
|
+
Run `node el-maker.mjs` (or `npm run build-el-maker` if your `package.json` includes a watch script) to regenerate `el-maker.json`.
|
|
367
|
+
|
|
368
|
+
Create a build instruction in package.json:
|
|
369
|
+
|
|
370
|
+
```JSON
|
|
371
|
+
"scripts": {
|
|
372
|
+
...
|
|
373
|
+
"build-el-maker": "node --watch el-maker.mjs",
|
|
374
|
+
},
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Create the Demo Page
|
|
378
|
+
|
|
379
|
+
For example:
|
|
380
|
+
|
|
381
|
+
```html
|
|
382
|
+
<!DOCTYPE html>
|
|
383
|
+
<html lang="en">
|
|
384
|
+
<head>
|
|
385
|
+
<meta charset="UTF-8">
|
|
386
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
387
|
+
<title>Dev</title>
|
|
388
|
+
<!-- #include virtual="/imports.html" -->
|
|
389
|
+
|
|
390
|
+
<script type=module>
|
|
391
|
+
import 'be-hive/be-hive.js';
|
|
392
|
+
import 'imp-h/imp-h.js';
|
|
393
|
+
import 'el-maker/def.js';
|
|
394
|
+
</script>
|
|
395
|
+
</head>
|
|
396
|
+
<body>
|
|
397
|
+
<be-hive></be-hive>
|
|
398
|
+
<plus-minus imp-h="plus-minus/root.html">
|
|
399
|
+
<script type=precede data-extends=el-maker src="plus-minus/el-maker.json"></script>
|
|
400
|
+
</plus-minus>
|
|
401
|
+
|
|
402
|
+
</body>
|
|
403
|
+
</html>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
<details>
|
|
407
|
+
<summary>How scratch-box is structured</summary>
|
|
408
|
+
|
|
409
|
+
| File | Role |
|
|
410
|
+
|------|------|
|
|
411
|
+
| `root.html` | Declarative shadow DOM template, styles, inner form, and enhancement metadata. |
|
|
412
|
+
| `el-maker.mjs` | Type-checked configuration generator for the ElementMaker features. |
|
|
413
|
+
| `el-maker.json` | Generated JSON consumed by the `cede` script. |
|
|
414
|
+
|
|
415
|
+
### The template file (`root.html`)
|
|
416
|
+
|
|
417
|
+
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:
|
|
418
|
+
|
|
419
|
+
```html
|
|
420
|
+
<scratch-box>
|
|
421
|
+
<template shadowrootmode=open>
|
|
422
|
+
<style adopt>
|
|
423
|
+
:host[hidden] { display:none; }
|
|
424
|
+
:host { display:block; background-color: HSL(250, 22%, 41%); padding: 1vw; }
|
|
425
|
+
/* ... remaining styles ... */
|
|
426
|
+
</style>
|
|
427
|
+
<form class="checkbox-wrapper">
|
|
428
|
+
<input 🪢 name=value type="checkbox" id="option"/>
|
|
429
|
+
<link itemprop=value>
|
|
430
|
+
<label for="option">
|
|
431
|
+
<slot name="labelTxt">scratch-box</slot>
|
|
432
|
+
<svg viewBox="0 0 60 40" aria-hidden="true" focusable="false">
|
|
433
|
+
<path d="M21,2 ..." stroke-width="4" fill="none" stroke-dasharray="270" stroke-dashoffset="270"></path>
|
|
434
|
+
</svg>
|
|
435
|
+
</label>
|
|
436
|
+
</form>
|
|
437
|
+
|
|
438
|
+
<be-hive>
|
|
439
|
+
<script type=emc-parser
|
|
440
|
+
src="be-hive/parsers/parse-grouped-capture-statements.js"
|
|
441
|
+
parser-name=parse-grouped-capture-statements></script>
|
|
442
|
+
<script type=emc
|
|
443
|
+
src="be-bound/🪢.json"
|
|
444
|
+
wait-for-parsers=parse-grouped-capture-statements></script>
|
|
445
|
+
</be-hive>
|
|
446
|
+
</template>
|
|
447
|
+
</scratch-box>
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
Notes:
|
|
452
|
+
|
|
453
|
+
- `imp-h` observes the `imp-h` attribute and imports the declarative shadow DOM template from `root.html`.
|
|
454
|
+
- 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.
|
|
455
|
+
- No JS class file is required because all behavior is provided by the configured ElementMaker features and the declarative shadow DOM.
|
|
456
|
+
|
|
457
|
+
### When to use this pattern
|
|
458
|
+
|
|
459
|
+
Use the declarative shadow DOM + `cede` pattern when:
|
|
460
|
+
|
|
461
|
+
- The element is primarily visual and static.
|
|
462
|
+
- You want server-side rendering and progressive enhancement with no client-side custom element class.
|
|
463
|
+
- Feature configuration (form association, attribute reflection, reactive wiring, fonts) is sufficient for all behavior.
|
|
464
|
+
|
|
465
|
+
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.
|
|
466
|
+
|