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,278 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
## Reference Implementations
|
|
4
|
+
|
|
5
|
+
- **[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.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Step 5: Create the Element Class, if the complexity is too much for a "code-free" solution, which is probably the reason the custom element is js-first.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Create `[element-name].js` (e.g., `my-element.js`):
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { ElementMaker } from 'el-maker/ElementMaker.js';
|
|
15
|
+
|
|
16
|
+
export class MyElementElement extends ElementMaker {
|
|
17
|
+
static supportedFeatures = {
|
|
18
|
+
...ElementMaker.supportedFeatures,
|
|
19
|
+
myFeature: {},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Key patterns:**
|
|
25
|
+
- Extends `ElementMaker` — inherits `propagator`, `#internals`, `attachInternals()`, and all shared features (`roundabout`, `truthSourcer`, `faceUp`, `reflector`, `templateMaker`)
|
|
26
|
+
- Spreads `ElementMaker.supportedFeatures` to inherit the base feature slots
|
|
27
|
+
- Only declares additional feature slots unique to this element
|
|
28
|
+
- No need for `static formAssociated = true` — `FaceUp.onAssigned` sets it automatically
|
|
29
|
+
- No constructor needed unless you have element-specific initialization
|
|
30
|
+
|
|
31
|
+
## Optional Step 6: Create the Element-Specific Feature (if any)
|
|
32
|
+
|
|
33
|
+
If your element has unique behavior beyond what the inherited features provide, but the functionality is more than trivial in implementing, consider creating a custom element feature for that functionality, following [NewCustomElementFeature.md](./NewCustomElementFeature.md).
|
|
34
|
+
|
|
35
|
+
For example, `time-ticker` has a `TimeTicker.js` feature that provides precise drift-correcting ticking.
|
|
36
|
+
|
|
37
|
+
If the feature proves useful beyond that one component, it is probably a good idea to move that feature into the el-maker package.
|
|
38
|
+
|
|
39
|
+
## Step 7: Create defRef.mjs (Roundabout Configuration)
|
|
40
|
+
|
|
41
|
+
Create `defRef.mjs` — this generates the JSON configuration that drives the roundabout reactive wiring:
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
//@ts-check
|
|
45
|
+
|
|
46
|
+
/** @import {RAConfig} from './types/roundabout/types' */
|
|
47
|
+
/** @import {T} from './types/[project-name]/types' */
|
|
48
|
+
/** @import {AttrPatterns} from './types/assign-gingerly/types' */
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @type {{ [K in keyof T]: K }}
|
|
52
|
+
*/
|
|
53
|
+
const props = {
|
|
54
|
+
myProp: 'myProp',
|
|
55
|
+
disabled: 'disabled',
|
|
56
|
+
// ... all properties that roundabout manages
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @type {RAConfig<T,T,T>}
|
|
61
|
+
*/
|
|
62
|
+
export const raConfig = {
|
|
63
|
+
propagate: /** @type {Array<keyof T>} */ (Object.keys(props)),
|
|
64
|
+
compacts: {
|
|
65
|
+
// Reactive shorthand rules
|
|
66
|
+
},
|
|
67
|
+
merges: [
|
|
68
|
+
// Reactive assignment rules
|
|
69
|
+
],
|
|
70
|
+
yields: {
|
|
71
|
+
// Derived property rules
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @type {AttrPatterns<T>}
|
|
77
|
+
*/
|
|
78
|
+
const withAttrs = {
|
|
79
|
+
// Attribute-to-property mappings for truthSourcer
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const cef = {
|
|
83
|
+
features: {
|
|
84
|
+
roundabout: {
|
|
85
|
+
customData: {
|
|
86
|
+
raConfig
|
|
87
|
+
},
|
|
88
|
+
withAttrs
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export function render() {
|
|
94
|
+
return JSON.stringify(cef, null, 4);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(render());
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Key patterns:**
|
|
101
|
+
- The `props` object provides type-safe property name references (keys must be in `T`, values must equal the key)
|
|
102
|
+
- `raConfig` defines the reactive wiring: compacts (shorthand rules), merges (assignment rules), yields (derived values)
|
|
103
|
+
- `withAttrs` maps HTML attributes to properties (used by `truthSourcer`)
|
|
104
|
+
- The `render()` function outputs JSON for the build step
|
|
105
|
+
|
|
106
|
+
Run `npm run build` to generate `defRef.json`.
|
|
107
|
+
|
|
108
|
+
## Step 8: build script
|
|
109
|
+
|
|
110
|
+
Add the following to package.json:
|
|
111
|
+
|
|
112
|
+
```JSON
|
|
113
|
+
"scripts": {
|
|
114
|
+
"build": "node defRef.mjs > defRef.json",
|
|
115
|
+
...
|
|
116
|
+
},
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Step 9: Create wireFeatures.js
|
|
120
|
+
|
|
121
|
+
This module resolves async fallback spawns and calls `assignFeatures` with the element-specific configuration:
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
import { MyFeature } from './MyFeature.js';
|
|
125
|
+
import { resolveAndAssignFeatures } from 'assign-gingerly/resolveAndAssignFeatures.js';
|
|
126
|
+
|
|
127
|
+
export async function wireFeatures(ElementClass, cfg) {
|
|
128
|
+
const { roundabout } = cfg.features;
|
|
129
|
+
const { customData, withAttrs } = roundabout;
|
|
130
|
+
|
|
131
|
+
await resolveAndAssignFeatures(ElementClass, {
|
|
132
|
+
myFeature: { spawn: MyFeature },
|
|
133
|
+
truthSourcer: {
|
|
134
|
+
callbackForwarding: ['connectedCallback', 'attributeChangedCallback'],
|
|
135
|
+
},
|
|
136
|
+
faceUp: {
|
|
137
|
+
customData: { integrateWithRoundabout: true },
|
|
138
|
+
callbackForwarding: [
|
|
139
|
+
'connectedCallback', 'disconnectedCallback',
|
|
140
|
+
'formDisabledCallback', 'formResetCallback', 'formStateRestoreCallback',
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
roundabout: {
|
|
144
|
+
customData,
|
|
145
|
+
withAttrs,
|
|
146
|
+
callbackForwarding: ['connectedCallback'],
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Key patterns:**
|
|
153
|
+
- Only eagerly imports the feature(s) unique to this element
|
|
154
|
+
- Inherited features (`truthSourcer`, `faceUp`, `roundabout`, `reflector`) use their async `fallbackSpawn` from `ElementMaker` — no explicit `spawn` needed
|
|
155
|
+
- `resolveAndAssignFeatures` resolves async fallback spawns before calling `assignFeatures`, ensuring `onAssigned` hooks (like `FaceUp.onAssigned` setting `static formAssociated = true`) run before `define()`
|
|
156
|
+
- `callbackForwarding` and `customData` are per-element configuration that gets unioned with the author defaults from `supportedFeatures`
|
|
157
|
+
|
|
158
|
+
## Step 10: Create def.js
|
|
159
|
+
|
|
160
|
+
The side-effect module that registers the custom element with its canonical tag name and default feature wiring:
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
import { MyElementElement } from './my-element-element.js';
|
|
164
|
+
import { wireFeatures } from './wireFeatures.js';
|
|
165
|
+
import defRef from './defRef.json' with { type: 'json' };
|
|
166
|
+
|
|
167
|
+
await wireFeatures(MyElementElement, defRef);
|
|
168
|
+
customElements.define('my-element', MyElementElement);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Key patterns:**
|
|
172
|
+
- `def.js` = "default define" — centralizes all side effects
|
|
173
|
+
- Imports the JSON config and passes it to `wireFeatures`
|
|
174
|
+
- Consumers who want a different tag name, scoped registry, or DI overrides write their own version of this file
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
<details>
|
|
178
|
+
<summary>Kiro only</summary>
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
## Step 10.5: Set Up Auto-Build Hook
|
|
182
|
+
|
|
183
|
+
Create `.kiro/hooks/auto-build-config.kiro.hook`:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"name": "Auto-build Configuration",
|
|
188
|
+
"version": "1.0.0",
|
|
189
|
+
"description": "Automatically runs npm run build when defRef.mjs is saved",
|
|
190
|
+
"when": {
|
|
191
|
+
"type": "fileEdited",
|
|
192
|
+
"patterns": ["**/*.mjs"]
|
|
193
|
+
},
|
|
194
|
+
"then": {
|
|
195
|
+
"type": "askAgent",
|
|
196
|
+
"prompt": "A .mjs file was changed. Run npm run build to regenerate the output."
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
</details>
|
|
202
|
+
|
|
203
|
+
## Step 11: Create Test HTML
|
|
204
|
+
|
|
205
|
+
Create `tests/test1.html`:
|
|
206
|
+
|
|
207
|
+
```html
|
|
208
|
+
<!DOCTYPE html>
|
|
209
|
+
<html lang="en">
|
|
210
|
+
<head>
|
|
211
|
+
<meta charset="UTF-8">
|
|
212
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
213
|
+
<title>Test - my-element</title>
|
|
214
|
+
<!-- #include virtual="/imports.html" -->
|
|
215
|
+
<script type=module>
|
|
216
|
+
import '[project-name]/def.js';
|
|
217
|
+
</script>
|
|
218
|
+
</head>
|
|
219
|
+
<body>
|
|
220
|
+
<my-element></my-element>
|
|
221
|
+
</body>
|
|
222
|
+
</html>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
## Architecture Overview
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
[project-name]/
|
|
233
|
+
├── .kiro/
|
|
234
|
+
│ ├── hooks/
|
|
235
|
+
│ │ └── auto-build-config.kiro.hook
|
|
236
|
+
│ └── steering/
|
|
237
|
+
│ └── project-context.md
|
|
238
|
+
├── .vscode/
|
|
239
|
+
│ └── settings.json
|
|
240
|
+
├── types/ (git submodule)
|
|
241
|
+
│ └── [project-name]/
|
|
242
|
+
│ └── types.d.ts
|
|
243
|
+
├── [element-name]-element.js (element class — extends ElementMaker)
|
|
244
|
+
├── [FeatureName].js (element-specific feature, if any)
|
|
245
|
+
├── wireFeatures.js (resolves + assigns features)
|
|
246
|
+
├── def.js (side-effect: wire + define)
|
|
247
|
+
├── defRef.mjs (build script → defRef.json)
|
|
248
|
+
├── defRef.json (generated — roundabout config)
|
|
249
|
+
├── imports.html (import map for browser)
|
|
250
|
+
├── package.json
|
|
251
|
+
├── tests/
|
|
252
|
+
│ └── test1.html
|
|
253
|
+
└── README.md
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## The Three-File Pattern
|
|
257
|
+
|
|
258
|
+
Every custom element package exports three key modules:
|
|
259
|
+
|
|
260
|
+
| File | Role | Side effects? |
|
|
261
|
+
|------|------|---------------|
|
|
262
|
+
| `[element-name]-element.js` | Class definition + `supportedFeatures` declaration | No |
|
|
263
|
+
| `wireFeatures.js` | Resolves spawns + calls `assignFeatures` with config | No |
|
|
264
|
+
| `def.js` | Imports config, wires features, calls `define()` | Yes |
|
|
265
|
+
|
|
266
|
+
This separation enables:
|
|
267
|
+
- **Different tag names** — write your own `def.js` with a different `define()` call
|
|
268
|
+
- **Scoped registries** — call `scopedRegistry.define()` instead of `customElements.define()`
|
|
269
|
+
- **DI / testing** — call `resolveAndAssignFeatures` directly with mock spawns
|
|
270
|
+
- **Declarative definition** — use `defineWithFeatures` from a cede script without any JS class code
|
|
271
|
+
|
|
272
|
+
## Tips
|
|
273
|
+
|
|
274
|
+
- **Call `wireFeatures` before `customElements.define()`** — features must be on the prototype before instances exist
|
|
275
|
+
- **Use `@ts-check`** in `.mjs` files — catches type errors in the build configuration
|
|
276
|
+
- **Run `npm run build` after editing `defRef.mjs`** — the JSON must be regenerated
|
|
277
|
+
- **Don't eagerly import inherited features** — let `fallbackSpawn` lazy-load them
|
|
278
|
+
- **Keep `def.js` minimal** — it's the canonical handshake; consumers can deviate as needed
|
|
@@ -54,6 +54,7 @@ export type Compacts<TProps = any, TActions = TProps, TEvents extends string = s
|
|
|
54
54
|
| Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_inc_${keyof TProps & string}_by`]: number}>
|
|
55
55
|
| Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_set_${keyof TProps & string}_to`]: any}>
|
|
56
56
|
| Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_assign`]: Record<string, any>}>
|
|
57
|
+
| Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_assignFromEvent`]: Record<string, any>}>
|
|
57
58
|
;
|
|
58
59
|
|
|
59
60
|
export type Hitches<TProps = any, TActions = TProps> =
|
|
@@ -160,7 +161,7 @@ export interface RAConfig<
|
|
|
160
161
|
initialPropVals?: Partial<{[key in keyof TProps & string]: unknown}>,
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
export interface RoundaboutOptions<TProps = unknown, TActions = TProps, ETProps = TProps> extends RAConfig<TProps, TActions, ETProps> {
|
|
164
|
+
export interface RoundaboutOptions<TProps = unknown, TActions = TProps, ETProps = TProps, EventTypes extends string = string> extends RAConfig<TProps, TActions, ETProps, unknown, EventTypes> {
|
|
164
165
|
vm?: TProps & TActions & RoundaboutReady,
|
|
165
166
|
//for enhanced elements, pass in the container, referenced via $0.
|
|
166
167
|
container?: EventTarget,
|
package/package.json
CHANGED