@escape-game-over/atlas 0.1.18 → 0.1.19
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/docs/client-scripts.md +6 -0
- package/package.json +1 -1
- package/src/astro/dom.ts +8 -26
- package/src/astro/element.ts +46 -124
- package/src/astro/filters-view.ts +46 -147
- package/src/astro/filters.ts +88 -248
- package/src/astro/markup.ts +163 -142
package/src/astro/markup.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A markup contract: the attributes a template writes and a script reads,
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* In `astro/` because the reading half takes elements. The writing half runs in
|
|
6
|
-
* an Astro template at build time, and both halves come off the one object —
|
|
7
|
-
* which is the entire point.
|
|
2
|
+
* A markup contract: the attributes a template writes and a script reads, named
|
|
3
|
+
* once and typed on both sides.
|
|
8
4
|
*
|
|
9
5
|
* ```ts
|
|
10
6
|
* // network-markup.ts, imported by the template and by the script
|
|
@@ -23,47 +19,116 @@
|
|
|
23
19
|
* }
|
|
24
20
|
* ```
|
|
25
21
|
*
|
|
26
|
-
* **Why
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
22
|
+
* **Why.** A script and the template it enhances agree on attribute names, and
|
|
23
|
+
* nothing checks the agreement: `data-filter-regoin` in one and
|
|
24
|
+
* `dataset.filterRegion` in the other compile, build and ship, and the facet
|
|
25
|
+
* just never matches. Here names are derived rather than spelled, values are
|
|
26
|
+
* typed on the way in, and parsed on the way out — a missing or malformed
|
|
27
|
+
* attribute throws, naming the element, instead of reading as an empty string.
|
|
32
28
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
29
|
+
* `component` below binds a set of roles to one custom element, which is what
|
|
30
|
+
* most consumers want. See docs/client-scripts.md.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type { Digit, Letter } from "../types.ts";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The names below become attributes, and a malformed one is not a small
|
|
37
|
+
* mistake: `data-store row` is refused by `setAttribute`, `[data-store row]`
|
|
38
|
+
* is refused by `querySelectorAll`, and a tag without a hyphen is refused by
|
|
39
|
+
* `customElements.define` — each of them in the reader's browser, long after
|
|
40
|
+
* the page was built. They are literals in a source file, so the rules are
|
|
41
|
+
* spelled as types and the mistake is an error in the editor instead.
|
|
43
42
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* names come from the project: nothing in this package has to be matched.
|
|
43
|
+
* Per character rather than by pattern, in the way `i18n/placeholders.ts`
|
|
44
|
+
* checks a placeholder's name. A widened `string` — from a variable rather than
|
|
45
|
+
* a literal — is accepted: there is nothing to check.
|
|
48
46
|
*/
|
|
47
|
+
type WordChar = Lowercase<Letter> | Digit;
|
|
48
|
+
|
|
49
|
+
/** `store`, `row2`: one lowercase word. */
|
|
50
|
+
type IsWord<S extends string> = S extends `${infer Head}${infer Tail}`
|
|
51
|
+
? Head extends WordChar
|
|
52
|
+
? Tail extends ""
|
|
53
|
+
? true
|
|
54
|
+
: IsWord<Tail>
|
|
55
|
+
: false
|
|
56
|
+
: false;
|
|
57
|
+
|
|
58
|
+
/** `store-row`, `go-faq`: lowercase words joined by single hyphens. */
|
|
59
|
+
type IsHyphenated<S extends string> = S extends `${infer Head}-${infer Rest}`
|
|
60
|
+
? IsWord<Head> extends true
|
|
61
|
+
? IsHyphenated<Rest>
|
|
62
|
+
: false
|
|
63
|
+
: IsWord<S>;
|
|
64
|
+
|
|
65
|
+
/** Letters and digits, in either case: what follows a field's first letter. */
|
|
66
|
+
type IsAlphanumeric<S extends string> = S extends `${infer Head}${infer Tail}`
|
|
67
|
+
? Head extends Letter | Lowercase<Letter> | Digit
|
|
68
|
+
? Tail extends ""
|
|
69
|
+
? true
|
|
70
|
+
: IsAlphanumeric<Tail>
|
|
71
|
+
: false
|
|
72
|
+
: false;
|
|
73
|
+
|
|
74
|
+
/** `blockedLabel`: a lowercase letter, then letters and digits. */
|
|
75
|
+
type IsCamelCase<S extends string> = S extends `${infer Head}${infer Tail}`
|
|
76
|
+
? Head extends Lowercase<Letter>
|
|
77
|
+
? Tail extends ""
|
|
78
|
+
? true
|
|
79
|
+
: IsAlphanumeric<Tail>
|
|
80
|
+
: false
|
|
81
|
+
: false;
|
|
82
|
+
|
|
83
|
+
export interface MalformedMarkupName<N extends string> {
|
|
84
|
+
readonly __MALFORMED_MARKUP_NAME__: `"${N}" cannot be a role name: it becomes an attribute, so it must be lowercase words joined by single hyphens, as in "store-row"`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface MalformedTagName<N extends string> {
|
|
88
|
+
readonly __MALFORMED_TAG_NAME__: `"${N}" cannot be a custom element name: lowercase words with at least one hyphen, as in "go-faq"`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface MalformedKey<N extends string> {
|
|
92
|
+
readonly __MALFORMED_KEY__: `"${N}" cannot be a field or role name: it becomes part of an attribute, so it must be camelCase, as in "blockedLabel"`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Nothing extra for a good name, and an impossible shape for a bad one. */
|
|
96
|
+
type CheckedMarkupName<N extends string> = string extends N
|
|
97
|
+
? unknown
|
|
98
|
+
: IsHyphenated<N> extends true
|
|
99
|
+
? unknown
|
|
100
|
+
: MalformedMarkupName<N>;
|
|
101
|
+
|
|
102
|
+
type CheckedTagName<N extends string> = string extends N
|
|
103
|
+
? unknown
|
|
104
|
+
: IsHyphenated<N> extends true
|
|
105
|
+
? N extends `${string}-${string}`
|
|
106
|
+
? unknown
|
|
107
|
+
: MalformedTagName<N>
|
|
108
|
+
: MalformedTagName<N>;
|
|
109
|
+
|
|
110
|
+
/** The same, for every key of a fields or roles object. */
|
|
111
|
+
type CheckedKeys<T> = {
|
|
112
|
+
[K in keyof T]: K extends string
|
|
113
|
+
? IsCamelCase<K> extends true
|
|
114
|
+
? unknown
|
|
115
|
+
: MalformedKey<K>
|
|
116
|
+
: never;
|
|
117
|
+
};
|
|
49
118
|
|
|
50
119
|
/**
|
|
51
120
|
* What one field is, as data rather than a constructor — the choice `filters`
|
|
52
|
-
* makes, for the same reason
|
|
53
|
-
* `text` into every module that imports this.
|
|
121
|
+
* makes, and for the same reason.
|
|
54
122
|
*
|
|
55
123
|
* - `text` — any string, the empty one included.
|
|
56
|
-
* - `number` — finite only
|
|
57
|
-
*
|
|
58
|
-
* - `
|
|
59
|
-
*
|
|
60
|
-
* - `choice` — one of `of`. Out-of-list values fail on both sides: at compile
|
|
61
|
-
* time in the template, at run time when read.
|
|
62
|
-
* - `flag` — present or absent. Off is the attribute left out, not `"false"`,
|
|
124
|
+
* - `number` — finite only.
|
|
125
|
+
* - `list` — several words, space-separated, as `class` is.
|
|
126
|
+
* - `choice` — one of `of`, checked at compile time and again when read.
|
|
127
|
+
* - `flag` — present or absent. Off is the attribute left out, never `"false"`,
|
|
63
128
|
* so a stylesheet can select it with a bare `[data-…]`.
|
|
64
129
|
*
|
|
65
|
-
* `optional` lets a field be left off, and reads back `undefined
|
|
66
|
-
*
|
|
130
|
+
* `optional` lets a field be left off, and reads back `undefined`. A `flag` has
|
|
131
|
+
* no `optional`: absent is already one of its two values.
|
|
67
132
|
*/
|
|
68
133
|
export type MarkupField =
|
|
69
134
|
| { readonly kind: "text"; readonly optional?: boolean }
|
|
@@ -115,10 +180,8 @@ type Flatten<T> = { [K in keyof T]: T[K] };
|
|
|
115
180
|
|
|
116
181
|
/**
|
|
117
182
|
* What a template hands `attrs`: every required field, and any of the rest.
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* that is itself optional — `store.state` — can be passed straight through
|
|
121
|
-
* without a conditional spread, whatever `exactOptionalPropertyTypes` says.
|
|
183
|
+
* The optional ones also accept `undefined`, so a value that is itself optional
|
|
184
|
+
* — `store.state` — passes straight through.
|
|
122
185
|
*/
|
|
123
186
|
export type MarkupInput<F extends MarkupFields> = Flatten<
|
|
124
187
|
{ readonly [K in RequiredKeys<F>]: Held<F[K]> } & {
|
|
@@ -135,29 +198,19 @@ export interface Marked<T extends Element, F extends MarkupFields> {
|
|
|
135
198
|
}
|
|
136
199
|
|
|
137
200
|
export interface Markup<F extends MarkupFields> {
|
|
138
|
-
/**
|
|
139
|
-
* `[data-<name>]`: what finds this role.
|
|
140
|
-
*
|
|
141
|
-
* For the lookups the methods below do not cover — `closest`, a
|
|
142
|
-
* `matches` in an event handler — so those do not spell it either.
|
|
143
|
-
*/
|
|
201
|
+
/** `[data-<name>]`: what finds this role, for the lookups below and `closest`. */
|
|
144
202
|
readonly selector: string;
|
|
145
203
|
|
|
146
204
|
/**
|
|
147
|
-
* The attribute one field lives in
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* `MutationObserver`'s `attributeFilter`. A stylesheet cannot use it —
|
|
151
|
-
* Tailwind has to see its selectors as literals in the source — so a
|
|
152
|
-
* styling hook still spells the attribute once, in a class; see the docs.
|
|
205
|
+
* The attribute one field lives in, for a consumer that needs the name as a
|
|
206
|
+
* string — a `MutationObserver`'s `attributeFilter`. A stylesheet cannot use
|
|
207
|
+
* it: Tailwind only sees selectors written as literals.
|
|
153
208
|
*/
|
|
154
209
|
attribute(field: keyof F & string): string;
|
|
155
210
|
|
|
156
211
|
/**
|
|
157
|
-
* The attributes to spread onto an element, in a template.
|
|
158
|
-
*
|
|
159
|
-
* Always includes the marker. A role with nothing required may be called
|
|
160
|
-
* with no argument at all.
|
|
212
|
+
* The attributes to spread onto an element, in a template. Always includes
|
|
213
|
+
* the marker; a role with nothing required takes no argument at all.
|
|
161
214
|
*/
|
|
162
215
|
attrs(
|
|
163
216
|
...values: [RequiredKeys<F>] extends [never]
|
|
@@ -166,12 +219,9 @@ export interface Markup<F extends MarkupFields> {
|
|
|
166
219
|
): Record<string, string>;
|
|
167
220
|
|
|
168
221
|
/**
|
|
169
|
-
* One element's values, parsed.
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* missing or malformed — naming the element and the attribute, because an
|
|
173
|
-
* `undefined` returned here would only resurface later as a list that
|
|
174
|
-
* silently matches nothing.
|
|
222
|
+
* One element's values, parsed. Throws when the element does not carry this
|
|
223
|
+
* role, or when a field is missing or malformed — an `undefined` here would
|
|
224
|
+
* only resurface later as a list that silently matches nothing.
|
|
175
225
|
*/
|
|
176
226
|
read(element: Element): MarkupValues<F>;
|
|
177
227
|
|
|
@@ -185,28 +235,13 @@ export interface Markup<F extends MarkupFields> {
|
|
|
185
235
|
require<T extends Element = HTMLElement>(root: ParentNode): Marked<T, F>;
|
|
186
236
|
|
|
187
237
|
/**
|
|
188
|
-
* Writes some of this role's fields back onto an element
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* selected — so the script sets it through the same names the template
|
|
192
|
-
* wrote. A field set to `undefined`, or a flag set to `false`, removes the
|
|
193
|
-
* attribute; fields left out of `values` are not touched.
|
|
238
|
+
* Writes some of this role's fields back onto an element, for state a
|
|
239
|
+
* stylesheet reads. A field set to `undefined`, or a flag set to `false`,
|
|
240
|
+
* removes the attribute; fields left out are not touched.
|
|
194
241
|
*/
|
|
195
242
|
write(element: Element, values: Partial<MarkupInput<F>>): void;
|
|
196
243
|
}
|
|
197
244
|
|
|
198
|
-
/** Lowercase words joined by single hyphens: what follows `data-`. */
|
|
199
|
-
const NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* camelCase, so the hyphenated form below is unambiguous.
|
|
203
|
-
*
|
|
204
|
-
* `aB` → `a-b` is one-to-one only while a field cannot contain a hyphen of its
|
|
205
|
-
* own: allowing `a-b` too would give two fields one attribute, and each write
|
|
206
|
-
* would erase the other.
|
|
207
|
-
*/
|
|
208
|
-
const FIELD = /^[a-z][a-zA-Z0-9]*$/;
|
|
209
|
-
|
|
210
245
|
const hyphenate = (field: string): string =>
|
|
211
246
|
field.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
212
247
|
|
|
@@ -217,10 +252,9 @@ const show = (value: unknown): string =>
|
|
|
217
252
|
/**
|
|
218
253
|
* One field's value as its attribute, or `undefined` to leave it off.
|
|
219
254
|
*
|
|
220
|
-
* Checked although the types
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* script will throw on every visit.
|
|
255
|
+
* Checked although the types were: a value can reach here from a cast or an env
|
|
256
|
+
* var, and this runs at build time, where a throw fails the build instead of
|
|
257
|
+
* shipping a page whose script throws on every visit.
|
|
224
258
|
*/
|
|
225
259
|
function encode(
|
|
226
260
|
field: MarkupField,
|
|
@@ -301,41 +335,24 @@ function decode(
|
|
|
301
335
|
}
|
|
302
336
|
|
|
303
337
|
/**
|
|
304
|
-
*
|
|
338
|
+
* One role, built from a name the caller has already vouched for.
|
|
305
339
|
*
|
|
306
|
-
* `
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* error in the browser, far from the line that caused it.
|
|
310
|
-
*
|
|
311
|
-
* Two roles whose names make one a prefix of the other can collide:
|
|
312
|
-
* `markup("card")` with a field `title` and `markup("card-title")` both write
|
|
313
|
-
* `data-card-title`. Name roles so they do not.
|
|
340
|
+
* Not exported: `markup` is this with the name checked, and `component` builds
|
|
341
|
+
* its names from a tag it checked itself, so neither can reach here with
|
|
342
|
+
* something malformed.
|
|
314
343
|
*/
|
|
315
|
-
|
|
344
|
+
function role<const F extends MarkupFields>(
|
|
316
345
|
name: string,
|
|
317
|
-
fields
|
|
346
|
+
fields: F
|
|
318
347
|
): Markup<F> {
|
|
319
|
-
if (!NAME.test(name)) {
|
|
320
|
-
throw new Error(
|
|
321
|
-
`markup(${show(name)}): a name is lowercase words joined by single hyphens, as in "store-row"`
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
348
|
const marker = `data-${name}`;
|
|
326
349
|
const selector = `[${marker}]`;
|
|
327
350
|
|
|
328
|
-
const entries = Object.entries(fields
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
return { key, field, attribute: `${marker}-${hyphenate(key)}` };
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
const describe = (element: Element): string =>
|
|
338
|
-
`<${element.localName} ${marker}>`;
|
|
351
|
+
const entries = Object.entries(fields).map(([key, field]) => ({
|
|
352
|
+
key,
|
|
353
|
+
field,
|
|
354
|
+
attribute: `${marker}-${hyphenate(key)}`,
|
|
355
|
+
}));
|
|
339
356
|
|
|
340
357
|
const read = (element: Element): MarkupValues<F> => {
|
|
341
358
|
if (!element.hasAttribute(marker)) {
|
|
@@ -354,7 +371,7 @@ export function markup<const F extends MarkupFields = Record<never, never>>(
|
|
|
354
371
|
} catch (error) {
|
|
355
372
|
const reason =
|
|
356
373
|
error instanceof Error ? error.message : String(error);
|
|
357
|
-
throw new Error(
|
|
374
|
+
throw new Error(`<${element.localName} ${marker}>: ${reason}`, {
|
|
358
375
|
cause: error,
|
|
359
376
|
});
|
|
360
377
|
}
|
|
@@ -413,8 +430,8 @@ export function markup<const F extends MarkupFields = Record<never, never>>(
|
|
|
413
430
|
},
|
|
414
431
|
|
|
415
432
|
write(element, values) {
|
|
416
|
-
// Checked like `read
|
|
417
|
-
//
|
|
433
|
+
// Checked like `read`: writing a role's fields onto an element that
|
|
434
|
+
// is not that role leaves it half-marked, and nothing reads it.
|
|
418
435
|
if (!element.hasAttribute(marker)) {
|
|
419
436
|
throw new Error(
|
|
420
437
|
`<${element.localName}> does not carry ${marker}, so its fields cannot be written to it`
|
|
@@ -431,6 +448,21 @@ export function markup<const F extends MarkupFields = Record<never, never>>(
|
|
|
431
448
|
};
|
|
432
449
|
}
|
|
433
450
|
|
|
451
|
+
/**
|
|
452
|
+
* Declares one role: `data-<name>` marks the element, `data-<name>-<field>`
|
|
453
|
+
* holds each field.
|
|
454
|
+
*
|
|
455
|
+
* Two roles whose names make one a prefix of the other collide: `markup("card")`
|
|
456
|
+
* with a field `title`, and `markup("card-title")`, both write `data-card-title`.
|
|
457
|
+
* `component` below refuses that; here, name them so it cannot happen.
|
|
458
|
+
*/
|
|
459
|
+
export function markup<
|
|
460
|
+
const N extends string,
|
|
461
|
+
const F extends MarkupFields & CheckedKeys<F> = Record<never, never>,
|
|
462
|
+
>(name: N & CheckedMarkupName<N>, fields?: F): Markup<F> {
|
|
463
|
+
return role(name, (fields ?? {}) as F);
|
|
464
|
+
}
|
|
465
|
+
|
|
434
466
|
/** A component's roles, by name: each is the fields of one `markup` role. */
|
|
435
467
|
export type ComponentRoles = Readonly<Record<string, MarkupFields>>;
|
|
436
468
|
|
|
@@ -444,9 +476,6 @@ export type Component<Tag extends string, R extends ComponentRoles> = {
|
|
|
444
476
|
define(element: CustomElementConstructor): void;
|
|
445
477
|
} & { readonly [K in keyof R]: Markup<R[K]> };
|
|
446
478
|
|
|
447
|
-
/** A custom element name, as far as a pattern can check it: needs a hyphen. */
|
|
448
|
-
const TAG = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/;
|
|
449
|
-
|
|
450
479
|
/**
|
|
451
480
|
* A custom element and the roles inside it, with lookups scoped to one
|
|
452
481
|
* instance.
|
|
@@ -466,8 +495,8 @@ const TAG = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/;
|
|
|
466
495
|
* - **Names.** Every attribute is `data-<tag>-<role>[-<field>]`. The browser
|
|
467
496
|
* refuses to define one tag twice, so two components cannot share an
|
|
468
497
|
* attribute, and clashes inside one component are refused here.
|
|
469
|
-
* - **Scope.** A lookup returns only elements owned by the same instance as
|
|
470
|
-
*
|
|
498
|
+
* - **Scope.** A lookup returns only elements owned by the same instance as its
|
|
499
|
+
* root — the owner being the nearest ancestor with this tag. A nested
|
|
471
500
|
* instance keeps its elements to itself, and the root may be any element
|
|
472
501
|
* inside the instance, not only the instance itself.
|
|
473
502
|
*
|
|
@@ -476,14 +505,9 @@ const TAG = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/;
|
|
|
476
505
|
*/
|
|
477
506
|
export function component<
|
|
478
507
|
const Tag extends string,
|
|
479
|
-
const R extends ComponentRoles &
|
|
480
|
-
>
|
|
481
|
-
|
|
482
|
-
throw new Error(
|
|
483
|
-
`component(${show(tag)}): a custom element name is lowercase words with at least one hyphen, as in "go-faq"`
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
|
|
508
|
+
const R extends ComponentRoles &
|
|
509
|
+
CheckedKeys<R> & { readonly [K in Reserved]?: never },
|
|
510
|
+
>(tag: Tag & CheckedTagName<Tag>, roles: R): Component<Tag, R> {
|
|
487
511
|
// Every attribute the component writes, so two that coincide are refused
|
|
488
512
|
// rather than left to overwrite each other.
|
|
489
513
|
const claimed = new Map<string, string>();
|
|
@@ -504,24 +528,21 @@ export function component<
|
|
|
504
528
|
: null;
|
|
505
529
|
|
|
506
530
|
const scoped = (
|
|
507
|
-
|
|
531
|
+
name: string,
|
|
508
532
|
fields: MarkupFields
|
|
509
533
|
): Markup<MarkupFields> => {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
if (!FIELD.test(role)) {
|
|
534
|
+
// A compile error already, and cheap to keep: these two would overwrite
|
|
535
|
+
// the component's own keys and leave nothing to define the element with.
|
|
536
|
+
if (name === "tag" || name === "define") {
|
|
516
537
|
throw new Error(
|
|
517
|
-
`component(${show(tag)}):
|
|
538
|
+
`component(${show(tag)}): ${show(name)} is taken by the component itself`
|
|
518
539
|
);
|
|
519
540
|
}
|
|
520
541
|
|
|
521
|
-
const base =
|
|
522
|
-
claim(base.selector.slice(1, -1), `the role ${show(
|
|
542
|
+
const base = role(`${tag}-${hyphenate(name)}`, fields);
|
|
543
|
+
claim(base.selector.slice(1, -1), `the role ${show(name)}`);
|
|
523
544
|
for (const field of Object.keys(fields)) {
|
|
524
|
-
claim(base.attribute(field), `${
|
|
545
|
+
claim(base.attribute(field), `${name}.${field}`);
|
|
525
546
|
}
|
|
526
547
|
|
|
527
548
|
// Filtered before anything is read, so a malformed element in a nested
|
|
@@ -567,8 +588,8 @@ export function component<
|
|
|
567
588
|
define: (element: CustomElementConstructor) =>
|
|
568
589
|
customElements.define(tag, element),
|
|
569
590
|
};
|
|
570
|
-
for (const [
|
|
571
|
-
built[
|
|
591
|
+
for (const [name, fields] of Object.entries(roles)) {
|
|
592
|
+
built[name] = scoped(name, fields);
|
|
572
593
|
}
|
|
573
594
|
return built as Component<Tag, R>;
|
|
574
595
|
}
|