assign-gingerly 0.0.91 → 0.0.93
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/inferencer/inferencer.js +123 -2
- package/inferencer/inferencer.ts +122 -3
- package/inferencer/types/NewCustomElement.md +12 -0
- package/inferencer/types/NewCustomElementFeature.md +28 -4
- package/inferencer/types/NewHTMLFirstCustomElement.md +196 -2
- package/inferencer/types/NewJSFirstCustomElement.md +11 -3
- package/inferencer/types/assign-gingerly/types.d.ts +8 -0
- package/inferencer/types/be-intl/types.d.ts +68 -0
- package/inferencer/types/chip-away/types.d.ts +71 -0
- package/inferencer/types/el-maker/types.d.ts +17 -1
- package/inferencer/types/h2o-table/types.d.ts +73 -0
- package/inferencer/types/id-referencer/types.d.ts +35 -0
- package/inferencer/types/inferencer/types.d.ts +19 -1
- package/inferencer/types/nested-regex-groups/types.d.ts +2 -0
- package/inferencer/types/swipe-dismiss/types.d.ts +14 -0
- package/package.json +1 -1
- package/types/assign-gingerly/types.d.ts +8 -0
package/inferencer/inferencer.js
CHANGED
|
@@ -50,12 +50,21 @@ export class Infer {
|
|
|
50
50
|
}
|
|
51
51
|
#value;
|
|
52
52
|
get value() {
|
|
53
|
-
|
|
53
|
+
const element = this.#weakRef.deref();
|
|
54
|
+
if (element === undefined)
|
|
55
|
+
return this.#value;
|
|
56
|
+
const propName = typeof this.#propName === 'string'
|
|
57
|
+
? this.#propName
|
|
58
|
+
: inferValueProperty(element);
|
|
59
|
+
return coerceElementValue(element, propName);
|
|
54
60
|
}
|
|
55
61
|
set value(nv) {
|
|
56
62
|
this.#value = nv;
|
|
57
63
|
const { enhancedElement } = this;
|
|
58
|
-
|
|
64
|
+
const propName = typeof this.#propName === 'string'
|
|
65
|
+
? this.#propName
|
|
66
|
+
: inferValueProperty(enhancedElement);
|
|
67
|
+
enhancedElement[propName] = serializeForProperty(propName, nv);
|
|
59
68
|
}
|
|
60
69
|
#display;
|
|
61
70
|
get display() {
|
|
@@ -80,6 +89,16 @@ export class Infer {
|
|
|
80
89
|
get valueProperty() {
|
|
81
90
|
return inferValueProperty(this.enhancedElement);
|
|
82
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* The effective language for this element, honoring `lang` / `xml:lang`
|
|
94
|
+
* inheritance up through ancestors and shadow-root hosts, then
|
|
95
|
+
* `<html lang>` and finally `navigator.language`.
|
|
96
|
+
* @returns a BCP-47 tag, or undefined if nothing is resolvable
|
|
97
|
+
*/
|
|
98
|
+
get lang() {
|
|
99
|
+
const el = this.#weakRef.deref();
|
|
100
|
+
return el === undefined ? undefined : resolveLang(el);
|
|
101
|
+
}
|
|
83
102
|
['|'](itempropAttr, scopeBoundary = '[itemscope]') {
|
|
84
103
|
return this.#queryScoped(`[itemprop="${itempropAttr}"]`, itempropAttr, scopeBoundary);
|
|
85
104
|
}
|
|
@@ -166,6 +185,108 @@ export function inferValueProperty(element) {
|
|
|
166
185
|
}
|
|
167
186
|
}
|
|
168
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Read the inferred value property off an element and coerce it to a natural
|
|
190
|
+
* JavaScript type, mirroring the legacy be-value-added parsing rules:
|
|
191
|
+
* - `<time>` (dateTime) -> Date (or undefined when empty)
|
|
192
|
+
* - `<input type=number|range>` (valueAsNumber) -> number (undefined when NaN)
|
|
193
|
+
* - `<input type=checkbox|radio>` (checked) -> boolean
|
|
194
|
+
* - schema.org `itemtype` hints (Number/Integer/Float/Boolean/Date/DateTime) are honored
|
|
195
|
+
* - `textContent` is returned verbatim
|
|
196
|
+
* - everything else is JSON-parsed when possible (so `<data value="123">` -> 123,
|
|
197
|
+
* `<data value="true">` -> true), falling back to the raw string
|
|
198
|
+
*/
|
|
199
|
+
export function coerceElementValue(element, propName = inferValueProperty(element)) {
|
|
200
|
+
const raw = element[propName];
|
|
201
|
+
switch (propName) {
|
|
202
|
+
case 'valueAsNumber':
|
|
203
|
+
return Number.isNaN(raw) ? undefined : raw;
|
|
204
|
+
case 'valueAsDate':
|
|
205
|
+
return raw ?? undefined;
|
|
206
|
+
case 'checked':
|
|
207
|
+
case 'selectedIndex':
|
|
208
|
+
return raw;
|
|
209
|
+
case 'dateTime': {
|
|
210
|
+
const s = raw == null ? '' : String(raw);
|
|
211
|
+
return s === '' ? undefined : new Date(s);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (raw == null)
|
|
215
|
+
return undefined;
|
|
216
|
+
if (typeof raw !== 'string')
|
|
217
|
+
return raw;
|
|
218
|
+
switch (element.getAttribute('itemtype')) {
|
|
219
|
+
case 'https://schema.org/Number': return Number(raw);
|
|
220
|
+
case 'https://schema.org/Integer': return parseInt(raw, 10);
|
|
221
|
+
case 'https://schema.org/Float': return parseFloat(raw);
|
|
222
|
+
case 'https://schema.org/Boolean': return raw === 'true' || raw === 'True';
|
|
223
|
+
case 'https://schema.org/Date':
|
|
224
|
+
case 'https://schema.org/DateTime': return new Date(raw);
|
|
225
|
+
}
|
|
226
|
+
if (propName === 'textContent')
|
|
227
|
+
return raw;
|
|
228
|
+
if (raw === '')
|
|
229
|
+
return undefined;
|
|
230
|
+
try {
|
|
231
|
+
return JSON.parse(raw);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return raw;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Serialize a JS value for assignment to a (usually string-typed) DOM value
|
|
239
|
+
* property, mirroring legacy be-value-added write-back:
|
|
240
|
+
* - DOM-typed properties (`checked`, `valueAsNumber`, `valueAsDate`) take the raw value
|
|
241
|
+
* - a `Date` is written as an ISO string (so `<time>.dateTime` round-trips)
|
|
242
|
+
* - plain objects / arrays are JSON-stringified
|
|
243
|
+
*/
|
|
244
|
+
export function serializeForProperty(propName, nv) {
|
|
245
|
+
switch (propName) {
|
|
246
|
+
case 'checked':
|
|
247
|
+
case 'valueAsNumber':
|
|
248
|
+
case 'valueAsDate':
|
|
249
|
+
return nv;
|
|
250
|
+
}
|
|
251
|
+
if (nv instanceof Date)
|
|
252
|
+
return nv.toISOString();
|
|
253
|
+
if (nv !== null && typeof nv === 'object')
|
|
254
|
+
return JSON.stringify(nv);
|
|
255
|
+
return nv;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Resolve the effective language ("computed lang") for an element.
|
|
259
|
+
*
|
|
260
|
+
* There is no DOM accessor for this, so we walk: nearest ancestor element with a
|
|
261
|
+
* `lang` / `xml:lang` attribute, crossing shadow-root boundaries via the host
|
|
262
|
+
* (matching how `:lang()` behaves in modern browsers), then `<html lang>`, then
|
|
263
|
+
* `navigator.language`. Slot reprojection is intentionally not followed — HTML
|
|
264
|
+
* language inheritance is defined over the real node tree, not the flat tree.
|
|
265
|
+
*
|
|
266
|
+
* @returns a BCP-47 tag, or undefined if nothing is resolvable
|
|
267
|
+
*/
|
|
268
|
+
export function resolveLang(element) {
|
|
269
|
+
let node = element;
|
|
270
|
+
while (node != null) {
|
|
271
|
+
if (node.nodeType === 1 /* ELEMENT_NODE */) {
|
|
272
|
+
const el = node;
|
|
273
|
+
const lang = el.getAttribute('lang') || el.getAttribute('xml:lang');
|
|
274
|
+
if (lang)
|
|
275
|
+
return lang;
|
|
276
|
+
node = node.parentNode;
|
|
277
|
+
}
|
|
278
|
+
else if (node.nodeType === 11 /* DOCUMENT_FRAGMENT_NODE, incl. ShadowRoot */) {
|
|
279
|
+
node = node.host ?? null;
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
node = null; // Document (9) or detached — stop
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (typeof document !== 'undefined' && document.documentElement.lang) {
|
|
286
|
+
return document.documentElement.lang;
|
|
287
|
+
}
|
|
288
|
+
return (typeof navigator !== 'undefined' && navigator.language) || undefined;
|
|
289
|
+
}
|
|
169
290
|
/**
|
|
170
291
|
* Infer the most appropriate display property for an element
|
|
171
292
|
* @param element - The element to infer the property for
|
package/inferencer/inferencer.ts
CHANGED
|
@@ -65,13 +65,21 @@ export class Infer<TValue = any, TDisplay = any> {
|
|
|
65
65
|
#value: TValue | undefined;
|
|
66
66
|
|
|
67
67
|
get value(): TValue | undefined {
|
|
68
|
-
|
|
68
|
+
const element = this.#weakRef.deref();
|
|
69
|
+
if (element === undefined) return this.#value;
|
|
70
|
+
const propName = typeof this.#propName === 'string'
|
|
71
|
+
? this.#propName
|
|
72
|
+
: inferValueProperty(element);
|
|
73
|
+
return coerceElementValue(element, propName) as TValue | undefined;
|
|
69
74
|
}
|
|
70
|
-
|
|
75
|
+
|
|
71
76
|
set value(nv: TValue){
|
|
72
77
|
this.#value = nv;
|
|
73
78
|
const {enhancedElement} = this;
|
|
74
|
-
|
|
79
|
+
const propName = typeof this.#propName === 'string'
|
|
80
|
+
? this.#propName
|
|
81
|
+
: inferValueProperty(enhancedElement);
|
|
82
|
+
(enhancedElement as any)[propName] = serializeForProperty(propName, nv);
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
#display: TDisplay | undefined;
|
|
@@ -102,6 +110,17 @@ export class Infer<TValue = any, TDisplay = any> {
|
|
|
102
110
|
return inferValueProperty(this.enhancedElement);
|
|
103
111
|
}
|
|
104
112
|
|
|
113
|
+
/**
|
|
114
|
+
* The effective language for this element, honoring `lang` / `xml:lang`
|
|
115
|
+
* inheritance up through ancestors and shadow-root hosts, then
|
|
116
|
+
* `<html lang>` and finally `navigator.language`.
|
|
117
|
+
* @returns a BCP-47 tag, or undefined if nothing is resolvable
|
|
118
|
+
*/
|
|
119
|
+
get lang(): string | undefined {
|
|
120
|
+
const el = this.#weakRef.deref();
|
|
121
|
+
return el === undefined ? undefined : resolveLang(el);
|
|
122
|
+
}
|
|
123
|
+
|
|
105
124
|
['|'](itempropAttr: string, scopeBoundary: string = '[itemscope]'){
|
|
106
125
|
return this.#queryScoped(`[itemprop="${itempropAttr}"]`, itempropAttr, scopeBoundary);
|
|
107
126
|
}
|
|
@@ -200,6 +219,106 @@ export function inferValueProperty(element: Element): string {
|
|
|
200
219
|
}
|
|
201
220
|
}
|
|
202
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Read the inferred value property off an element and coerce it to a natural
|
|
224
|
+
* JavaScript type, mirroring the legacy be-value-added parsing rules:
|
|
225
|
+
* - `<time>` (dateTime) -> Date (or undefined when empty)
|
|
226
|
+
* - `<input type=number|range>` (valueAsNumber) -> number (undefined when NaN)
|
|
227
|
+
* - `<input type=checkbox|radio>` (checked) -> boolean
|
|
228
|
+
* - schema.org `itemtype` hints (Number/Integer/Float/Boolean/Date/DateTime) are honored
|
|
229
|
+
* - `textContent` is returned verbatim
|
|
230
|
+
* - everything else is JSON-parsed when possible (so `<data value="123">` -> 123,
|
|
231
|
+
* `<data value="true">` -> true), falling back to the raw string
|
|
232
|
+
*/
|
|
233
|
+
export function coerceElementValue(element: Element, propName: string = inferValueProperty(element)): any {
|
|
234
|
+
const raw = (element as any)[propName];
|
|
235
|
+
|
|
236
|
+
switch (propName) {
|
|
237
|
+
case 'valueAsNumber':
|
|
238
|
+
return Number.isNaN(raw) ? undefined : raw;
|
|
239
|
+
case 'valueAsDate':
|
|
240
|
+
return raw ?? undefined;
|
|
241
|
+
case 'checked':
|
|
242
|
+
case 'selectedIndex':
|
|
243
|
+
return raw;
|
|
244
|
+
case 'dateTime': {
|
|
245
|
+
const s = raw == null ? '' : String(raw);
|
|
246
|
+
return s === '' ? undefined : new Date(s);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (raw == null) return undefined;
|
|
251
|
+
if (typeof raw !== 'string') return raw;
|
|
252
|
+
|
|
253
|
+
switch (element.getAttribute('itemtype')) {
|
|
254
|
+
case 'https://schema.org/Number': return Number(raw);
|
|
255
|
+
case 'https://schema.org/Integer': return parseInt(raw, 10);
|
|
256
|
+
case 'https://schema.org/Float': return parseFloat(raw);
|
|
257
|
+
case 'https://schema.org/Boolean': return raw === 'true' || raw === 'True';
|
|
258
|
+
case 'https://schema.org/Date':
|
|
259
|
+
case 'https://schema.org/DateTime': return new Date(raw);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (propName === 'textContent') return raw;
|
|
263
|
+
if (raw === '') return undefined;
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
return JSON.parse(raw);
|
|
267
|
+
} catch {
|
|
268
|
+
return raw;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Serialize a JS value for assignment to a (usually string-typed) DOM value
|
|
274
|
+
* property, mirroring legacy be-value-added write-back:
|
|
275
|
+
* - DOM-typed properties (`checked`, `valueAsNumber`, `valueAsDate`) take the raw value
|
|
276
|
+
* - a `Date` is written as an ISO string (so `<time>.dateTime` round-trips)
|
|
277
|
+
* - plain objects / arrays are JSON-stringified
|
|
278
|
+
*/
|
|
279
|
+
export function serializeForProperty(propName: string, nv: any): any {
|
|
280
|
+
switch (propName) {
|
|
281
|
+
case 'checked':
|
|
282
|
+
case 'valueAsNumber':
|
|
283
|
+
case 'valueAsDate':
|
|
284
|
+
return nv;
|
|
285
|
+
}
|
|
286
|
+
if (nv instanceof Date) return nv.toISOString();
|
|
287
|
+
if (nv !== null && typeof nv === 'object') return JSON.stringify(nv);
|
|
288
|
+
return nv;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Resolve the effective language ("computed lang") for an element.
|
|
293
|
+
*
|
|
294
|
+
* There is no DOM accessor for this, so we walk: nearest ancestor element with a
|
|
295
|
+
* `lang` / `xml:lang` attribute, crossing shadow-root boundaries via the host
|
|
296
|
+
* (matching how `:lang()` behaves in modern browsers), then `<html lang>`, then
|
|
297
|
+
* `navigator.language`. Slot reprojection is intentionally not followed — HTML
|
|
298
|
+
* language inheritance is defined over the real node tree, not the flat tree.
|
|
299
|
+
*
|
|
300
|
+
* @returns a BCP-47 tag, or undefined if nothing is resolvable
|
|
301
|
+
*/
|
|
302
|
+
export function resolveLang(element: Element): string | undefined {
|
|
303
|
+
let node: Node | null = element;
|
|
304
|
+
while (node != null) {
|
|
305
|
+
if (node.nodeType === 1 /* ELEMENT_NODE */) {
|
|
306
|
+
const el = node as Element;
|
|
307
|
+
const lang = el.getAttribute('lang') || el.getAttribute('xml:lang');
|
|
308
|
+
if (lang) return lang;
|
|
309
|
+
node = node.parentNode;
|
|
310
|
+
} else if (node.nodeType === 11 /* DOCUMENT_FRAGMENT_NODE, incl. ShadowRoot */) {
|
|
311
|
+
node = (node as ShadowRoot).host ?? null;
|
|
312
|
+
} else {
|
|
313
|
+
node = null; // Document (9) or detached — stop
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (typeof document !== 'undefined' && document.documentElement.lang) {
|
|
317
|
+
return document.documentElement.lang;
|
|
318
|
+
}
|
|
319
|
+
return (typeof navigator !== 'undefined' && navigator.language) || undefined;
|
|
320
|
+
}
|
|
321
|
+
|
|
203
322
|
/**
|
|
204
323
|
* Infer the most appropriate display property for an element
|
|
205
324
|
* @param element - The element to infer the property for
|
|
@@ -139,6 +139,18 @@ Plus infrastructure:
|
|
|
139
139
|
- `attachInternals()` called in the constructor
|
|
140
140
|
- Async `fallbackSpawn` for lazy-loading all feature implementations
|
|
141
141
|
|
|
142
|
+
### Injecting a custom or package-local feature
|
|
143
|
+
|
|
144
|
+
`assignFeatures` accepts a `spawn` per feature key — a class, an async loader, or
|
|
145
|
+
an **import-path string**. A string `spawn` overrides the catalog `fallbackSpawn`
|
|
146
|
+
and is dynamically `import()`ed through the page's import map, so `el-maker.json`
|
|
147
|
+
stays pure JSON. Use it to add a feature that isn't in the catalog, or to swap in
|
|
148
|
+
a package-local **subclass** of a catalog feature when the generic one needs
|
|
149
|
+
element-specific logic. See
|
|
150
|
+
[NewHTMLFirstCustomElement.md → "give a shared feature element-specific logic"](./NewHTMLFirstCustomElement.md#how-do-i-give-a-shared-feature-element-specific-logic-penciling-in)
|
|
151
|
+
and the [css-charts](https://github.com/bahrus/css-charts) conversion for a
|
|
152
|
+
worked example ("penciling in" `CSSChartsH2OTable extends H2OTable`).
|
|
153
|
+
|
|
142
154
|
|
|
143
155
|
|
|
144
156
|
|
|
@@ -71,10 +71,14 @@ A custom element feature is a class that:
|
|
|
71
71
|
|
|
72
72
|
## Step 3: Create Type Definitions
|
|
73
73
|
|
|
74
|
+
**All types for the feature live in `types/[project-name]/types.d.ts` — nothing else.** Do not scatter `@typedef {Object} ...` blocks through the `.js` file. That includes the `customData` / injection-config shape, the `detail` payload of any event the feature dispatches, and every internal helper type. The `.js` file only ever *imports* these via `@import`; it never defines them. A reader (or the package that later adopts the feature) should be able to learn the whole type surface from the one `.d.ts`.
|
|
75
|
+
|
|
74
76
|
Create `types/[project-name]/types.d.ts` with the feature structure:
|
|
75
77
|
|
|
76
78
|
```typescript
|
|
77
|
-
import { SpawnContext } from "../assign-gingerly/types";
|
|
79
|
+
import { SpawnContext, FeatureSpawnContext } from "../assign-gingerly/types";
|
|
80
|
+
|
|
81
|
+
export { FeatureSpawnContext };
|
|
78
82
|
|
|
79
83
|
/**
|
|
80
84
|
* Configuration/properties that the feature exposes
|
|
@@ -96,9 +100,26 @@ export type AP = AllProps;
|
|
|
96
100
|
export type PAP = Partial<AP>;
|
|
97
101
|
|
|
98
102
|
/**
|
|
99
|
-
*
|
|
103
|
+
* The `customData` passed through the injection config — parsed by the
|
|
104
|
+
* constructor into initial state. Keep every configurable knob here.
|
|
105
|
+
*/
|
|
106
|
+
export interface CustomData {
|
|
107
|
+
myProp?: string;
|
|
108
|
+
eventType?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* `detail` payload for any CustomEvent the feature dispatches on the host.
|
|
100
113
|
*/
|
|
101
|
-
export interface
|
|
114
|
+
export interface MyFeatureResolvedDetail {
|
|
115
|
+
// ...
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If a project's `types/assign-gingerly/types.d.ts` does not yet export `FeatureSpawnContext`, define it locally in this file instead (as `truth-sourcer` and `face-up` do):
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
export interface FeatureSpawnContext {
|
|
102
123
|
key: string;
|
|
103
124
|
optIn: any;
|
|
104
125
|
injection: any;
|
|
@@ -110,7 +131,9 @@ export interface FeatureSpawnContext extends SpawnContext {
|
|
|
110
131
|
**Key points:**
|
|
111
132
|
- `FeatureProps` — the public API of the feature
|
|
112
133
|
- `AllProps` — includes internal state like a WeakRef to the host element
|
|
134
|
+
- `CustomData` — the injection-config shape; the constructor reads `ctx.injection.customData` and narrows it to this type
|
|
113
135
|
- The feature class does NOT need to extend any base class
|
|
136
|
+
- The `.js` file imports all of the above with `/** @import {...} from './types/[project-name]/types' */` — it defines no types of its own
|
|
114
137
|
|
|
115
138
|
## Step 4: Create the Feature Class
|
|
116
139
|
|
|
@@ -150,7 +173,7 @@ export { MyFeature };
|
|
|
150
173
|
- Constructor signature: `(hostElement, ctx, initVals)`
|
|
151
174
|
- Store host as a `WeakRef` to avoid preventing garbage collection
|
|
152
175
|
- Apply `initVals` via `Object.assign` in the constructor
|
|
153
|
-
- Use `@ts-check` with JSDoc type imports from the `types/` folder
|
|
176
|
+
- Use `@ts-check` with JSDoc type imports from the `types/` folder — all type definitions live in `types/[project-name]/types.d.ts`, never as inline `@typedef` blocks in the `.js`
|
|
154
177
|
- No compiled TypeScript — ship raw `.js` files
|
|
155
178
|
|
|
156
179
|
## Step 5: Create imports.html
|
|
@@ -679,6 +702,7 @@ customElements.define('my-element', MyElement);
|
|
|
679
702
|
|
|
680
703
|
- **Call `assignFeatures` before `customElements.define()`** — getters must be on the prototype before instances exist
|
|
681
704
|
- **Use `@ts-check`** — catches type errors early in `.js` files
|
|
705
|
+
- **Keep all types in `types/[project-name]/types.d.ts`** — including `customData` and event `detail` shapes; the `.js` only `@import`s them, it never declares `@typedef`s
|
|
682
706
|
- **Store host as WeakRef** — prevents memory leaks
|
|
683
707
|
- **Keep features focused** — one responsibility per feature class
|
|
684
708
|
- **Use `validateShape`** — catches injection errors early in development
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
|
|
8
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
9
|
|
|
10
|
-
- **[side-burger](https://github.com/bahrus/side-burger)** -- Side Drawer component with menu.
|
|
10
|
+
- **[side-burger](https://github.com/bahrus/side-burger)** -- Side Drawer component with menu. Shows integration with many features, including swipe-dismiss, focus-trap-feature, others
|
|
11
|
+
|
|
12
|
+
- **[up-down-counter](https://github.com/bahrus/up-down-counter)** -- Shows use of toLocaleString() formatting.
|
|
13
|
+
|
|
14
|
+
- **[css-charts](https://github.com/bahrus/css-charts)** -- Wrapper around [charts.css](https://chartscss.org/). Demonstrates: reflecting boolean props onto a child element as CSS classes with the multi-invoke (`=*`) operator (see [Step 7 → "toggle a CSS class from a boolean property"](#how-can-i-toggle-a-css-class-from-a-boolean-property)); list rendering with `manageTemplateList`; and **"penciling in"** a package-local feature subclass (`CSSChartsH2OTable extends H2OTable`) via a string `spawn` when a shared feature needs element-specific logic (see [Step 7 → "give a shared feature element-specific logic"](#how-do-i-give-a-shared-feature-element-specific-logic-penciling-in)). Its `Chats/Conversion.md` is a blow-by-blow log of the trans-render → assign-gingerly conversion.
|
|
15
|
+
|
|
11
16
|
|
|
12
17
|
## Step 4
|
|
13
18
|
|
|
@@ -453,6 +458,176 @@ const raConfig = {
|
|
|
453
458
|
|
|
454
459
|
[Please fully digest all the attribute parsing tha assign-gingerly provides before configuring the attributes.](https://github.com/bahrus/assign-gingerly/blob/baseline/docs/withAttrs.md)
|
|
455
460
|
|
|
461
|
+
### How can I display a number with local formatting?
|
|
462
|
+
|
|
463
|
+
**Goal:** the legacy `xtal-element` transform `"% count": "localize"` wrote
|
|
464
|
+
`count.toLocaleString()` (e.g. `30000` → `"30,000"`) into the display element.
|
|
465
|
+
Reproduce it declaratively by calling `toLocaleString` from inside the RHS path
|
|
466
|
+
string of a `merge`.
|
|
467
|
+
|
|
468
|
+
**How it works:** the `assign-gingerly` path evaluator normally *reads* each
|
|
469
|
+
segment as a property. If a segment names a method that you listed in
|
|
470
|
+
`withMethods`, it is *invoked* instead. When that method is the **last** segment,
|
|
471
|
+
has **no `|` suffix**, and is **not followed by an argument segment**, it is
|
|
472
|
+
called with zero arguments and its **return value becomes the resolved value**.
|
|
473
|
+
So `'?.count?.toLocaleString'` resolves to `count.toLocaleString()` → `"30,000"`.
|
|
474
|
+
|
|
475
|
+
> Contrast with the `|` suffix (e.g. `'?.deref|'`): that also calls the method
|
|
476
|
+
> with no arguments but **discards** the return value — use `|` for side effects,
|
|
477
|
+
> omit it when you want the result.
|
|
478
|
+
|
|
479
|
+
**Wiring (in `el-maker.mjs`):**
|
|
480
|
+
|
|
481
|
+
```JS
|
|
482
|
+
import { akaMethods as m } from 'assign-gingerly/DX/emojis.js';
|
|
483
|
+
|
|
484
|
+
// 1. Register toLocaleString as a callable method for the path evaluator.
|
|
485
|
+
// m['🌐'] is the DX alias for the string 'toLocaleString'.
|
|
486
|
+
const withMethods = [m['🔍'], m['🌐']];
|
|
487
|
+
|
|
488
|
+
const $ = (/** @type {typeof paths<RuntimeProps>} */ (/** @type {any} */(paths)))({ withMethods });
|
|
489
|
+
|
|
490
|
+
const raConfig = {
|
|
491
|
+
assignOptions: {
|
|
492
|
+
akaMethods: {
|
|
493
|
+
'🔍': m['🔍'], // querySelector
|
|
494
|
+
'🌐': m['🌐'], // toLocaleString
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
merges: [
|
|
498
|
+
{
|
|
499
|
+
ifKeyIn: ['count'],
|
|
500
|
+
assign: {
|
|
501
|
+
// '?.count?.🌐' works too — the emoji is just an alias for the
|
|
502
|
+
// 'toLocaleString' segment. Both serialize to a plain string.
|
|
503
|
+
'?.countData?.textContent': '?.count?.toLocaleString',
|
|
504
|
+
value: '?.count',
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
defaultPropVals: {
|
|
509
|
+
count: 30000,
|
|
510
|
+
name: '',
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**Passing a locale or options:** append the argument as the next segment, e.g.
|
|
516
|
+
`'?.count?.toLocaleString?.en-US'` → `count.toLocaleString('en-US')`.
|
|
517
|
+
|
|
518
|
+
**Serialization:** `render()` emits the segment as a literal string
|
|
519
|
+
(`"?.count?.toLocaleString"`), so the generated `el-maker.json` stays fully
|
|
520
|
+
JSON-serializable — no function is ever embedded.
|
|
521
|
+
|
|
522
|
+
### How can I toggle a CSS class from a boolean property?
|
|
523
|
+
|
|
524
|
+
**Goal:** the legacy `trans-render` transform `{sa: '.show-primary-axis', o: 'showPrimaryAxis'}`
|
|
525
|
+
means "add class `show-primary-axis` when `showPrimaryAxis` is truthy, remove it
|
|
526
|
+
otherwise." Reproduce it with the assign-gingerly **multi-invoke** (`=*`)
|
|
527
|
+
operator, which calls one `withMethods` method **once per argument-list** on the RHS.
|
|
528
|
+
|
|
529
|
+
**How it works:** `'?.el?.classList?.toggle =*'` calls `el.classList.toggle(...)`
|
|
530
|
+
once for each entry in the RHS array. Each entry is `[className, condition]`. The
|
|
531
|
+
`!!` prefix coerces the condition to a real boolean, so a missing / falsy source
|
|
532
|
+
*removes* the class instead of flipping it — the whole merge is idempotent.
|
|
533
|
+
`toggle` must be listed in `withMethods`.
|
|
534
|
+
|
|
535
|
+
**Wiring (in `el-maker.mjs`):**
|
|
536
|
+
|
|
537
|
+
```JS
|
|
538
|
+
const raConfig = {
|
|
539
|
+
assignOptions: {
|
|
540
|
+
withMethods: ['querySelector', 'toggle'],
|
|
541
|
+
},
|
|
542
|
+
merges: smoothOver([
|
|
543
|
+
{
|
|
544
|
+
// capture the child once…
|
|
545
|
+
ifAllOf: ['clone'],
|
|
546
|
+
...doAssign(set($.tableEl).to($.clone.querySelector('table'))),
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
// …then (re)run the toggles when the ref appears or any input flips
|
|
550
|
+
ifAllOf: ['tableEl'],
|
|
551
|
+
ifKeyIn: ['tableEl', 'isBar', 'isColumn', 'showLabels', 'showPrimaryAxis'],
|
|
552
|
+
assign: {
|
|
553
|
+
'?.tableEl?.classList?.toggle =*': [
|
|
554
|
+
['bar', '!!?.isBar'],
|
|
555
|
+
['column', '!!?.isColumn'],
|
|
556
|
+
['show-labels', '!!?.showLabels'],
|
|
557
|
+
['show-primary-axis', '!!?.showPrimaryAxis'],
|
|
558
|
+
],
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
]),
|
|
562
|
+
};
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
There is no DX (`$` / `set`) token for `=*` yet — write the key and the
|
|
566
|
+
`[class, '!!?.prop']` pairs as plain strings inside `assign`. `smoothOver` leaves
|
|
567
|
+
them untouched and they serialize straight to JSON.
|
|
568
|
+
|
|
569
|
+
See [multi-invoke.md](https://github.com/bahrus/assign-gingerly/blob/baseline/docs/multi-invoke.md)
|
|
570
|
+
and [css-charts](https://github.com/bahrus/css-charts) (a 10-class example) for more.
|
|
571
|
+
|
|
572
|
+
### How do I give a shared feature element-specific logic? ("penciling in")
|
|
573
|
+
|
|
574
|
+
Sometimes a reusable el-maker feature does *most* of what you need but not the
|
|
575
|
+
last mile. Example: the `h2o-table` feature scrapes a slotted `<table>` into
|
|
576
|
+
`[{ key, value }, …]`, but [css-charts](https://github.com/bahrus/css-charts) also
|
|
577
|
+
has to scale those values per `chart-type` (`Math.max`, cumulative sums) —
|
|
578
|
+
genuinely imperative code that can't be expressed as a merge.
|
|
579
|
+
|
|
580
|
+
Rather than fork the feature or drop back to a full custom-element class, **pencil
|
|
581
|
+
in a subclass defined inside your package and inject it by string `spawn`:**
|
|
582
|
+
|
|
583
|
+
1. `MyH2OTable.js` at your package root — override just the hook the base exposes:
|
|
584
|
+
|
|
585
|
+
```JS
|
|
586
|
+
import { H2OTable } from 'h2o-table/H2OTable.js'; // the generic base
|
|
587
|
+
|
|
588
|
+
export default class MyH2OTable extends H2OTable {
|
|
589
|
+
massageData(rows) {
|
|
590
|
+
const { chartType } = /** @type {any} */ (this.hostElement);
|
|
591
|
+
/* …element-specific transform… */
|
|
592
|
+
return rows;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
2. Point the feature at it in `el-maker.mjs`:
|
|
598
|
+
|
|
599
|
+
```JS
|
|
600
|
+
const features = {
|
|
601
|
+
assignFeatures: {
|
|
602
|
+
roundabout: { customData: { raConfig }, withAttrs },
|
|
603
|
+
templateMaker: {},
|
|
604
|
+
h2oTable: {
|
|
605
|
+
spawn: 'my-element/MyH2OTable.js', // resolved via the import map
|
|
606
|
+
customData: { itemprops: ['key', 'value'] },
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
};
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
Why it works:
|
|
613
|
+
|
|
614
|
+
- `assignFeatures` uses `featureConfig.spawn` when present and only falls back to
|
|
615
|
+
the catalog `fallbackSpawn` otherwise — so your subclass wins.
|
|
616
|
+
- A string `spawn` is dynamically `import()`ed through the page's import map
|
|
617
|
+
(`"my-element/": "/"`), and the module's `default` export (or first exported
|
|
618
|
+
class) is used. `el-maker.json` stays pure JSON — the string is all that's stored.
|
|
619
|
+
- The base must expose the hook (`massageData()` here) and any state the subclass
|
|
620
|
+
needs as **public** getters — `#private` fields are invisible across the
|
|
621
|
+
subclass boundary.
|
|
622
|
+
- Pull the feature's output into the reactive graph with a merge, gated on
|
|
623
|
+
whatever changes its inputs:
|
|
624
|
+
`{ ifKeyIn: ['slotChangeCount', 'chartType'], assign: { data: '?.h2oTable?.data' } }`.
|
|
625
|
+
The feature re-computes lazily on read; the merge is what makes roundabout
|
|
626
|
+
re-read it.
|
|
627
|
+
|
|
628
|
+
This keeps the generic feature reusable while the element-specific bit lives where
|
|
629
|
+
it belongs. Once the pattern proves out, that logic can be promoted into the
|
|
630
|
+
feature itself (config-driven) or published as its own feature.
|
|
456
631
|
|
|
457
632
|
## Step 8
|
|
458
633
|
|
|
@@ -469,7 +644,20 @@ Create a build instruction in package.json:
|
|
|
469
644
|
|
|
470
645
|
## Create the Demo Page
|
|
471
646
|
|
|
472
|
-
|
|
647
|
+
**Only the first instance of the custom element on the page carries the `imp-h`
|
|
648
|
+
attribute and the `<script type=precede>`.** That first instance does the
|
|
649
|
+
one-time global setup:
|
|
650
|
+
|
|
651
|
+
- `imp-h="<name>/root.html"` imports the declarative shadow-DOM template once.
|
|
652
|
+
- `<script type=precede data-extends=el-maker src="<name>/el-maker.json">`
|
|
653
|
+
registers the element (extends `ElementMaker`, applies the feature JSON) via
|
|
654
|
+
`customElements.define`.
|
|
655
|
+
|
|
656
|
+
Once the element is defined, **every other instance is just the bare tag** —
|
|
657
|
+
repeating `imp-h` or the `precede` script is unnecessary (and wasteful: it
|
|
658
|
+
re-imports and re-registers). Subsequent instances upgrade automatically and
|
|
659
|
+
reuse the same template and configuration; they can still set their own
|
|
660
|
+
attributes.
|
|
473
661
|
|
|
474
662
|
```html
|
|
475
663
|
<!DOCTYPE html>
|
|
@@ -488,10 +676,16 @@ For example:
|
|
|
488
676
|
</head>
|
|
489
677
|
<body>
|
|
490
678
|
<be-hive></be-hive>
|
|
679
|
+
|
|
680
|
+
<!-- FIRST instance: imp-h + precede => imports the template and defines the element -->
|
|
491
681
|
<plus-minus imp-h="plus-minus/root.html">
|
|
492
682
|
<script type=precede data-extends=el-maker src="plus-minus/el-maker.json"></script>
|
|
493
683
|
</plus-minus>
|
|
494
684
|
|
|
685
|
+
<!-- Every later instance is just the bare tag; attributes still work -->
|
|
686
|
+
<plus-minus></plus-minus>
|
|
687
|
+
<plus-minus expanded></plus-minus>
|
|
688
|
+
|
|
495
689
|
</body>
|
|
496
690
|
</html>
|
|
497
691
|
```
|
|
@@ -30,11 +30,19 @@ export class MyElementElement extends ElementMaker {
|
|
|
30
30
|
|
|
31
31
|
## Optional Step 6: Create the Element-Specific Feature (if any)
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Most element-specific behavior can and should live directly in the element class (see Step 5). Reserve the custom element feature pattern for behavior that is genuinely reusable across multiple custom element projects.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Consider creating a custom element feature **only** when:
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
- The behavior is non-trivial to implement, **and**
|
|
38
|
+
- There is a significant chance that other custom element libraries would want to reuse it, **and**
|
|
39
|
+
- Extracting it into a separate module improves testability or dependency management without adding unnecessary indirection.
|
|
40
|
+
|
|
41
|
+
If the behavior is unique to a single element — for example, rendering chips for a specific `<select multiple>` UI pattern — keep the logic in the element class instead. Follow [NewCustomElementFeature.md](./NewCustomElementFeature.md) when a separate feature is warranted.
|
|
42
|
+
|
|
43
|
+
`time-ticker`, for example, defines a `TimeTicker.js` feature because precise drift-correcting ticking is a generally useful capability.
|
|
44
|
+
|
|
45
|
+
If a feature proves useful beyond the element that first introduced it, consider moving it into the `el-maker` catalog or publishing it as a standalone package.
|
|
38
46
|
|
|
39
47
|
## Step 7: Create defRef.mjs (Roundabout Configuration)
|
|
40
48
|
|
|
@@ -360,6 +360,14 @@ export interface IAssignGingerlyOptions {
|
|
|
360
360
|
* ]
|
|
361
361
|
*/
|
|
362
362
|
enhance?: Array<{ emc: string; matching?: string; parse?: boolean }>;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Handler implementations scoped to this call, forwarded to `assignFrom` when
|
|
366
|
+
* these options are reused by higher-level features (e.g. roundabout merges).
|
|
367
|
+
* Key: the `do` name referenced in handler configs. Value: a class
|
|
368
|
+
* constructor, an import path, or a `builtIns.*` alias string.
|
|
369
|
+
*/
|
|
370
|
+
handlers?: Record<string, AssignFromHandlerConstructor | string>;
|
|
363
371
|
}
|
|
364
372
|
|
|
365
373
|
/**
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ElementEnhancementGateway, SpawnContext } from "../assign-gingerly/types";
|
|
2
|
+
|
|
3
|
+
export interface EndUserProps {
|
|
4
|
+
/**
|
|
5
|
+
* Intl.NumberFormatOptions / Intl.DateTimeFormatOptions, parsed as JSON from
|
|
6
|
+
* the base attribute (`be-intl='{ ... }'`). The semantic sub-attributes below
|
|
7
|
+
* are folded into a copy of this object by `onFormattingChange`; explicit JSON
|
|
8
|
+
* keys win over the semantic equivalents.
|
|
9
|
+
*/
|
|
10
|
+
format?: Intl.NumberFormatOptions | Intl.DateTimeFormatOptions;
|
|
11
|
+
|
|
12
|
+
/** `be-intl-style` — e.g. "currency", "decimal", "percent". */
|
|
13
|
+
style?: string;
|
|
14
|
+
/** `be-intl-currency` — ISO 4217 code, e.g. "EUR". */
|
|
15
|
+
currency?: string;
|
|
16
|
+
/** `be-intl-weekday` — e.g. "long", "short", "narrow". */
|
|
17
|
+
weekday?: string;
|
|
18
|
+
/** `be-intl-year` — e.g. "numeric", "2-digit". */
|
|
19
|
+
year?: string;
|
|
20
|
+
/** `be-intl-month` — e.g. "long", "short", "numeric". */
|
|
21
|
+
month?: string;
|
|
22
|
+
/** `be-intl-day` — e.g. "numeric", "2-digit". */
|
|
23
|
+
day?: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* BCP-47 locale tag. When not supplied explicitly it is derived from the
|
|
27
|
+
* enhanced element's `lang` attribute, falling back to the runtime default locale.
|
|
28
|
+
*/
|
|
29
|
+
locale?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* When true, re-derive `locale` whenever the enhanced element's `lang` attribute changes.
|
|
33
|
+
* Off by default (the legacy `observeAttr` behavior).
|
|
34
|
+
*/
|
|
35
|
+
observeLang?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AllProps extends EndUserProps {
|
|
39
|
+
enhancedElement: Element & ElementEnhancementGateway;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The raw value pulled off the enhanced element:
|
|
43
|
+
* a number for `<data>` / `<output>`, a Date for `<time>`.
|
|
44
|
+
*/
|
|
45
|
+
value?: number | Date | string;
|
|
46
|
+
|
|
47
|
+
intlDateFormat?: Intl.DateTimeFormat;
|
|
48
|
+
intlNumberFormat?: Intl.NumberFormat;
|
|
49
|
+
|
|
50
|
+
resolved?: boolean;
|
|
51
|
+
|
|
52
|
+
/** Flipped true once `roundabout()` has finished its initial attribute-read pass. */
|
|
53
|
+
initialized?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type AP = AllProps;
|
|
57
|
+
|
|
58
|
+
export type PAP = Partial<AP>;
|
|
59
|
+
|
|
60
|
+
export type ProPAP = Promise<PAP>;
|
|
61
|
+
|
|
62
|
+
export interface Actions {
|
|
63
|
+
init(self: AP, enhancedElement: Element & ElementEnhancementGateway, ctx: SpawnContext, initVals: PAP): Promise<void>;
|
|
64
|
+
hydrate(self: AP): ProPAP;
|
|
65
|
+
onFormattingChange(self: AP): PAP;
|
|
66
|
+
formatNumber(self: AP): void;
|
|
67
|
+
formatDate(self: AP): void;
|
|
68
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {IIdRefs} from '../id-referencer/types.d.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public API properties for the chip-away custom element
|
|
5
|
+
*/
|
|
6
|
+
export interface EndUserProps {
|
|
7
|
+
/**
|
|
8
|
+
* Space-separated list of IDs for the select elements to mirror as chips
|
|
9
|
+
*/
|
|
10
|
+
for: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* When `true`, collapse each referenced `<select>` to a **single** chip
|
|
14
|
+
* whose label is a comma-delimited list of the selected option texts,
|
|
15
|
+
* instead of one chip per selected option. Its delete (✕) clears every
|
|
16
|
+
* selected option for that `<select>`.
|
|
17
|
+
*
|
|
18
|
+
* The boolean `join` attribute seeds the initial value (server-rendered
|
|
19
|
+
* config); after that, set the `join` property to change it at runtime — a
|
|
20
|
+
* `when_join_changes_call_hydrate` compact re-renders. Not a `sourceOfTruth`
|
|
21
|
+
* attribute, so the attribute is not kept in sync with the property.
|
|
22
|
+
*/
|
|
23
|
+
join: boolean;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* When `true`, render chips for display only — no per-option delete (✕), no
|
|
27
|
+
* per-`<select>` "clear all", and no joined-chip delete. The referenced
|
|
28
|
+
* `<select>` elements are not otherwise touched.
|
|
29
|
+
*
|
|
30
|
+
* Same wiring as {@link join}: boolean `readonly` attribute seeds the
|
|
31
|
+
* initial value, the `readonly` property is authoritative afterward, and a
|
|
32
|
+
* `when_readonly_changes_call_hydrate` compact re-renders.
|
|
33
|
+
*/
|
|
34
|
+
readonly: boolean;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `join` only: once the number of selected options **exceeds** this, the
|
|
38
|
+
* single summary chip's label becomes `"<n> Selected"` instead of the
|
|
39
|
+
* comma-delimited list. Leave unset for no limit.
|
|
40
|
+
*
|
|
41
|
+
* Seeded from the numeric `max-join` attribute; the `maxJoin` property is
|
|
42
|
+
* authoritative afterward, and a `when_maxJoin_changes_call_hydrate` compact
|
|
43
|
+
* re-renders. See {@link join}.
|
|
44
|
+
*/
|
|
45
|
+
maxJoin?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Full property set including internal state managed by the custom element
|
|
50
|
+
*/
|
|
51
|
+
export interface AllProps extends EndUserProps {
|
|
52
|
+
/** `for` split on whitespace into an id list. */
|
|
53
|
+
splitFor: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type AP = AllProps;
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Runtime type for the custom element instance, including the lazily-spawned
|
|
62
|
+
* `idRefs` feature.
|
|
63
|
+
*/
|
|
64
|
+
export interface RunTimeProps extends AllProps, HTMLElement {
|
|
65
|
+
idRefs: IIdRefs
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface Actions {
|
|
69
|
+
hydrate(self: AP): void;
|
|
70
|
+
temp(self: AP): void;
|
|
71
|
+
}
|
|
@@ -3,6 +3,7 @@ import {FontFaceFeatureConfig} from '../font-face-feature/types.js';
|
|
|
3
3
|
import {CustomData as TSCD} from '../truth-sourcer/types.js';
|
|
4
4
|
import {CustomData as FUCD} from '../face-up/types.js';
|
|
5
5
|
import {AttrPatterns} from '../assign-gingerly/types.js';
|
|
6
|
+
import {IdRefsCustomData} from '../id-referencer/types.js';
|
|
6
7
|
|
|
7
8
|
export interface ElMakerConfig<AllProps = any, TActions = AllProps> {
|
|
8
9
|
assignFeatures: {
|
|
@@ -30,6 +31,21 @@ export interface ElMakerConfig<AllProps = any, TActions = AllProps> {
|
|
|
30
31
|
truthSourcer?: {
|
|
31
32
|
spawn?: string,
|
|
32
33
|
customData?: TSCD
|
|
34
|
+
},
|
|
35
|
+
idRefs?: {
|
|
36
|
+
spawn?: string,
|
|
37
|
+
customData?: IdRefsCustomData
|
|
38
|
+
},
|
|
39
|
+
focusTrap?: {
|
|
40
|
+
spawn?: string,
|
|
41
|
+
customData?: any
|
|
42
|
+
},
|
|
43
|
+
h2oTable?: {
|
|
44
|
+
spawn?: string,
|
|
45
|
+
customData?: {
|
|
46
|
+
/** `itemprop` names to scrape from each `[itemscope]` row, in order. */
|
|
47
|
+
itemprops: string[]
|
|
48
|
+
}
|
|
33
49
|
}
|
|
34
50
|
}
|
|
35
|
-
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { FeatureSpawnContext } from "../assign-gingerly/types";
|
|
2
|
+
|
|
3
|
+
export { FeatureSpawnContext };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for the `H2OTable` ("HTML → Object Table") feature, supplied via
|
|
7
|
+
* the injection's `customData`.
|
|
8
|
+
*
|
|
9
|
+
* ```js
|
|
10
|
+
* customElements.assignFeatures(MyElement, {
|
|
11
|
+
* h2oTable: { customData: { itemprops: ['key', 'value'] } }
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export interface CustomData {
|
|
16
|
+
/**
|
|
17
|
+
* The `itemprop` names to read from each light-DOM `[itemscope]` row, in
|
|
18
|
+
* order. Each one becomes a key on the matching {@link DataRecord}.
|
|
19
|
+
*/
|
|
20
|
+
itemprops: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* One extracted row: each configured `itemprop` name mapped to the value the
|
|
25
|
+
* assign-gingerly inferencer reads for that element (`<data>`/`<input type=number>`
|
|
26
|
+
* → number, `<time>` → date string, form controls → `value`, anything else →
|
|
27
|
+
* `textContent`). Props whose element is missing from a given row are omitted.
|
|
28
|
+
*/
|
|
29
|
+
export type DataRecord = Record<string, any>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The members the feature exposes on `host.h2oTable`.
|
|
33
|
+
*/
|
|
34
|
+
export interface IH2OTable {
|
|
35
|
+
/**
|
|
36
|
+
* The host's light-DOM `[itemscope]` rows, projected onto plain objects
|
|
37
|
+
* using the configured {@link CustomData.itemprops} and then passed through
|
|
38
|
+
* {@link IH2OTable.massageData}. Re-scraped from the DOM on every read —
|
|
39
|
+
* there is no caching, so it always reflects the current light DOM.
|
|
40
|
+
*/
|
|
41
|
+
readonly data: DataRecord[];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The configured `itemprop` names, in order.
|
|
45
|
+
*/
|
|
46
|
+
readonly itemprops: string[];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Post-process hook, called by {@link IH2OTable.data} with the freshly
|
|
50
|
+
* scraped rows. The base class implements this as the identity function;
|
|
51
|
+
* subclasses override it to add computed columns, filter, sort, etc.
|
|
52
|
+
*/
|
|
53
|
+
massageData(data: DataRecord[]): DataRecord[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Public prop surface — alias kept for parity with the other el-maker feature
|
|
58
|
+
* type modules (`TemplateMakerProps`, `TruthSourcerProps`, …).
|
|
59
|
+
*/
|
|
60
|
+
export interface H2OTableProps extends IH2OTable {}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Full internal state of an `H2OTable` instance.
|
|
64
|
+
*/
|
|
65
|
+
export interface AllProps extends H2OTableProps {
|
|
66
|
+
/** WeakRef back to the host custom element. */
|
|
67
|
+
hostRef: WeakRef<HTMLElement> | null;
|
|
68
|
+
/** Resolved copy of {@link CustomData.itemprops}. */
|
|
69
|
+
itemprops: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type AP = AllProps;
|
|
73
|
+
export type PAP = Partial<AP>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FeatureSpawnContext } from "../assign-gingerly/types";
|
|
2
|
+
|
|
3
|
+
export { FeatureSpawnContext };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for the `IdRefs` feature, supplied via the injection's
|
|
7
|
+
* `customData`.
|
|
8
|
+
*
|
|
9
|
+
* Note: this local variant is handed its id list directly (via
|
|
10
|
+
* `idRefs.searchFor = string[]`) rather than reading a host attribute, so there is
|
|
11
|
+
*/
|
|
12
|
+
export interface IdRefsCustomData {
|
|
13
|
+
/**
|
|
14
|
+
* Event dispatched on the host whenever a DOM-mutation-driven pass changes
|
|
15
|
+
* the resolved element set. Defaults to `'id-referencer:resolved'`.
|
|
16
|
+
*/
|
|
17
|
+
eventType?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `detail` payload of the event named by {@link IdRefsCustomData.eventType}.
|
|
22
|
+
*/
|
|
23
|
+
export interface IdRefsResolvedDetail {
|
|
24
|
+
/** The ordered id list currently being resolved. */
|
|
25
|
+
ids: string[];
|
|
26
|
+
/** The resolved, still-connected elements, in id order. */
|
|
27
|
+
elements: Element[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface IIdRefs {
|
|
31
|
+
|
|
32
|
+
searchFor: string[];
|
|
33
|
+
|
|
34
|
+
readonly elements: Element[];
|
|
35
|
+
}
|
|
@@ -15,14 +15,32 @@ export declare const display: symbol;
|
|
|
15
15
|
*/
|
|
16
16
|
export declare class Infer<TValue = any, TDisplay = any> {
|
|
17
17
|
get enhancedElement(): Element;
|
|
18
|
-
constructor(enhancedElement?: Element);
|
|
18
|
+
constructor(enhancedElement?: Element, propName?: string);
|
|
19
|
+
/** Live, type-coerced read of the element's inferred value property. */
|
|
19
20
|
get value(): TValue | undefined;
|
|
20
21
|
set value(nv: TValue);
|
|
21
22
|
get display(): TDisplay | undefined;
|
|
22
23
|
set display(nv: TDisplay);
|
|
23
24
|
get eventType(): string;
|
|
25
|
+
/** The inferred value property name (e.g. 'value', 'checked', 'dateTime'). */
|
|
26
|
+
get valueProperty(): string;
|
|
27
|
+
get defaultRemoteBindingPropName(): string;
|
|
28
|
+
/**
|
|
29
|
+
* EventTarget that emits an event named after the changed property.
|
|
30
|
+
* Custom elements with a native propagator return that; otherwise an
|
|
31
|
+
* InferencedPropagator using best-effort change detection.
|
|
32
|
+
*/
|
|
33
|
+
getPropagator(): Promise<EventTarget>;
|
|
34
|
+
setDisplay(vm: any): void;
|
|
24
35
|
}
|
|
25
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Read the inferred value property off an element and coerce it to a natural
|
|
39
|
+
* JavaScript type (Date for <time>, number/boolean via JSON parse for <data>,
|
|
40
|
+
* schema.org itemtype hints honored, textContent verbatim).
|
|
41
|
+
*/
|
|
42
|
+
export declare function coerceElementValue(element: Element, propName?: string): any;
|
|
43
|
+
|
|
26
44
|
/**
|
|
27
45
|
* Registry item for the Infer enhancement
|
|
28
46
|
*/
|
|
@@ -38,6 +38,20 @@ export interface AllProps extends SwipeDismissProps {
|
|
|
38
38
|
/** WeakRef to the host custom element. */
|
|
39
39
|
hostRef: WeakRef<Element>;
|
|
40
40
|
|
|
41
|
+
/** Live gesture progress, refreshed on every pointermove and on release. */
|
|
42
|
+
progressState: {
|
|
43
|
+
/** Always-positive drag magnitude along the axis, clamped to panel size. */
|
|
44
|
+
deltaPx: number;
|
|
45
|
+
/** `deltaPx` as a fraction of panel size (0–1). */
|
|
46
|
+
fraction: number;
|
|
47
|
+
/**
|
|
48
|
+
* `deltaPx` signed for a screen-space `translate`: negative for a
|
|
49
|
+
* left/up drawer, positive for right/down. Feed this straight into
|
|
50
|
+
* `translateX()` / `translateY()` so the panel follows the finger
|
|
51
|
+
* regardless of which edge it is docked to.
|
|
52
|
+
*/
|
|
53
|
+
translatePx: number;
|
|
54
|
+
};
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
export type AP = AllProps;
|
package/package.json
CHANGED
|
@@ -360,6 +360,14 @@ export interface IAssignGingerlyOptions {
|
|
|
360
360
|
* ]
|
|
361
361
|
*/
|
|
362
362
|
enhance?: Array<{ emc: string; matching?: string; parse?: boolean }>;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Handler implementations scoped to this call, forwarded to `assignFrom` when
|
|
366
|
+
* these options are reused by higher-level features (e.g. roundabout merges).
|
|
367
|
+
* Key: the `do` name referenced in handler configs. Value: a class
|
|
368
|
+
* constructor, an import path, or a `builtIns.*` alias string.
|
|
369
|
+
*/
|
|
370
|
+
handlers?: Record<string, AssignFromHandlerConstructor | string>;
|
|
363
371
|
}
|
|
364
372
|
|
|
365
373
|
/**
|