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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.80",
3
+ "version": "0.0.82",
4
4
  "description": "This package provides a utility function for carefully merging one object into another.",
5
5
  "homepage": "https://github.com/bahrus/assign-gingerly#readme",
6
6
  "bugs": {