@forumone/throughline-design-system 0.0.0 → 1.0.0-next.2
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/CHANGELOG.md +182 -0
- package/LICENSE +21 -0
- package/README.md +23 -0
- package/bin/check-block-props.mjs +51 -0
- package/bin/stub-styles.mjs +28 -0
- package/dist/admin/BlockGuidance.d.ts +13 -0
- package/dist/admin/BlockGuidance.d.ts.map +1 -0
- package/dist/admin/BlockGuidance.js +13 -0
- package/dist/admin/BlockGuidance.js.map +1 -0
- package/dist/admin/BlockSummary.d.ts +10 -0
- package/dist/admin/BlockSummary.d.ts.map +1 -0
- package/dist/admin/BlockSummary.js +73 -0
- package/dist/admin/BlockSummary.js.map +1 -0
- package/dist/admin/RowSummary.d.ts +15 -0
- package/dist/admin/RowSummary.d.ts.map +1 -0
- package/dist/admin/RowSummary.js +16 -0
- package/dist/admin/RowSummary.js.map +1 -0
- package/dist/admin/summary.d.ts +18 -0
- package/dist/admin/summary.d.ts.map +1 -0
- package/dist/admin/summary.js +48 -0
- package/dist/admin/summary.js.map +1 -0
- package/dist/altText.d.ts +16 -0
- package/dist/altText.d.ts.map +1 -0
- package/dist/altText.js +54 -0
- package/dist/altText.js.map +1 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +12 -0
- package/dist/client.js.map +1 -0
- package/dist/contract/index.d.ts +5 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +4 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/lint.d.ts +38 -0
- package/dist/contract/lint.d.ts.map +1 -0
- package/dist/contract/lint.js +110 -0
- package/dist/contract/lint.js.map +1 -0
- package/dist/contract/loader.d.ts +49 -0
- package/dist/contract/loader.d.ts.map +1 -0
- package/dist/contract/loader.js +95 -0
- package/dist/contract/loader.js.map +1 -0
- package/dist/contract/manifest.d.ts +454 -0
- package/dist/contract/manifest.d.ts.map +1 -0
- package/dist/contract/manifest.js +29 -0
- package/dist/contract/manifest.js.map +1 -0
- package/dist/contract/schema.d.ts +334 -0
- package/dist/contract/schema.d.ts.map +1 -0
- package/dist/contract/schema.js +224 -0
- package/dist/contract/schema.js.map +1 -0
- package/dist/generate/blocks.d.ts +79 -0
- package/dist/generate/blocks.d.ts.map +1 -0
- package/dist/generate/blocks.js +118 -0
- package/dist/generate/blocks.js.map +1 -0
- package/dist/generate/fields.d.ts +60 -0
- package/dist/generate/fields.d.ts.map +1 -0
- package/dist/generate/fields.js +478 -0
- package/dist/generate/fields.js.map +1 -0
- package/dist/generate/guidance.d.ts +5 -0
- package/dist/generate/guidance.d.ts.map +1 -0
- package/dist/generate/guidance.js +29 -0
- package/dist/generate/guidance.js.map +1 -0
- package/dist/generate/index.d.ts +13 -0
- package/dist/generate/index.d.ts.map +1 -0
- package/dist/generate/index.js +9 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/generate/labels.d.ts +42 -0
- package/dist/generate/labels.d.ts.map +1 -0
- package/dist/generate/labels.js +226 -0
- package/dist/generate/labels.js.map +1 -0
- package/dist/generate/layout.d.ts +42 -0
- package/dist/generate/layout.d.ts.map +1 -0
- package/dist/generate/layout.js +252 -0
- package/dist/generate/layout.js.map +1 -0
- package/dist/generate/selectOptionSnapshot.d.ts +26 -0
- package/dist/generate/selectOptionSnapshot.d.ts.map +1 -0
- package/dist/generate/selectOptionSnapshot.js +47 -0
- package/dist/generate/selectOptionSnapshot.js.map +1 -0
- package/dist/generate/selectOptions.d.ts +64 -0
- package/dist/generate/selectOptions.d.ts.map +1 -0
- package/dist/generate/selectOptions.js +145 -0
- package/dist/generate/selectOptions.js.map +1 -0
- package/dist/overrides.d.ts +71 -0
- package/dist/overrides.d.ts.map +1 -0
- package/dist/overrides.js +16 -0
- package/dist/overrides.js.map +1 -0
- package/dist/render/RenderBlocks.d.ts +67 -0
- package/dist/render/RenderBlocks.d.ts.map +1 -0
- package/dist/render/RenderBlocks.js +31 -0
- package/dist/render/RenderBlocks.js.map +1 -0
- package/dist/render/coerce.d.ts +78 -0
- package/dist/render/coerce.d.ts.map +1 -0
- package/dist/render/coerce.js +243 -0
- package/dist/render/coerce.js.map +1 -0
- package/dist/render/index.d.ts +5 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +3 -0
- package/dist/render/index.js.map +1 -0
- package/dist/testing/checkBlockProps.d.ts +66 -0
- package/dist/testing/checkBlockProps.d.ts.map +1 -0
- package/dist/testing/checkBlockProps.js +241 -0
- package/dist/testing/checkBlockProps.js.map +1 -0
- package/dist/testing/checkBlockPropsCli.d.ts +12 -0
- package/dist/testing/checkBlockPropsCli.d.ts.map +1 -0
- package/dist/testing/checkBlockPropsCli.js +86 -0
- package/dist/testing/checkBlockPropsCli.js.map +1 -0
- package/dist/testing/contractDefaults.d.ts +21 -0
- package/dist/testing/contractDefaults.d.ts.map +1 -0
- package/dist/testing/contractDefaults.js +65 -0
- package/dist/testing/contractDefaults.js.map +1 -0
- package/dist/testing/describeBlockInvariants.d.ts +39 -0
- package/dist/testing/describeBlockInvariants.d.ts.map +1 -0
- package/dist/testing/describeBlockInvariants.js +71 -0
- package/dist/testing/describeBlockInvariants.js.map +1 -0
- package/dist/testing/index.d.ts +18 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +14 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/untouchedBlocks.d.ts +48 -0
- package/dist/testing/untouchedBlocks.d.ts.map +1 -0
- package/dist/testing/untouchedBlocks.js +155 -0
- package/dist/testing/untouchedBlocks.js.map +1 -0
- package/package.json +108 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { pairTitle } from './labels.js';
|
|
2
|
+
/*
|
|
3
|
+
How a block's fields are arranged on screen, as distinct from what they are.
|
|
4
|
+
|
|
5
|
+
Everything here is presentational. An unnamed `group` and a `collapsible` hold
|
|
6
|
+
their children in the same data object as their siblings, so moving a field
|
|
7
|
+
into one changes where it is drawn and nothing about where it is stored: no
|
|
8
|
+
migration, no change to `payload-types.ts`, and `coerce` — which walks the
|
|
9
|
+
contract, not this config — never sees the difference. That is the property
|
|
10
|
+
that makes it safe to rearrange every block at once.
|
|
11
|
+
|
|
12
|
+
Two rules, both read off the contract rather than configured per block.
|
|
13
|
+
|
|
14
|
+
**A label and its link are one thing.** A contract names a call to action as a
|
|
15
|
+
pair of siblings, `ctaLabel` and `ctaHref`, because the component takes them as
|
|
16
|
+
two props. Generated literally, the editor drew them as unrelated fields with
|
|
17
|
+
unrelated headings: a text box captioned "Cta Label" and, below it, a group
|
|
18
|
+
headed "Cta Href". So a `<prefix>Label` text field and a `<prefix>Href` or
|
|
19
|
+
`<prefix>Url` link become one group headed by what they are together — "Call to
|
|
20
|
+
action", "View all link" — at the position of whichever came first. A
|
|
21
|
+
`<prefix>Icon` beside them joins too, since it is drawn inside the same button.
|
|
22
|
+
|
|
23
|
+
**What most authors should leave alone goes behind a disclosure.** Top-level
|
|
24
|
+
selects, checkboxes and numbers on a block are how it looks — heading level,
|
|
25
|
+
variant, columns, aspect ratio — and every one has a default the component
|
|
26
|
+
already chose well. A field the contract marks `advanced` is the same kind of
|
|
27
|
+
thing in words: a screen-reader label or a status message the component
|
|
28
|
+
already supplies. Both move to one collapsed "More options" section at the end
|
|
29
|
+
of the block, so an author reading a block sees its content first. A field the
|
|
30
|
+
contract marks `required` is never tucked away, whatever its type: a required
|
|
31
|
+
field hidden behind a closed section is an error nobody can find.
|
|
32
|
+
|
|
33
|
+
Only the top level gets the disclosure. Inside an array, a collapsed section per
|
|
34
|
+
row would be one more click on every card, which is the cost this exists to
|
|
35
|
+
remove.
|
|
36
|
+
*/
|
|
37
|
+
/** The data-bearing field types that set how a block looks rather than what it says. */
|
|
38
|
+
const SETTING_TYPES = new Set(['select', 'boolean', 'number']);
|
|
39
|
+
const LINK_SUFFIX = /^(.+)(Href|Url)$/;
|
|
40
|
+
/** The label/link pairs among one level's fields, by contract index. */
|
|
41
|
+
function findPairs(contract, generated) {
|
|
42
|
+
const index = new Map(contract.map((field, i) => [field.name, i]));
|
|
43
|
+
const pairs = [];
|
|
44
|
+
contract.forEach((field, i) => {
|
|
45
|
+
// An `advanced` link is one the component fills in itself —
|
|
46
|
+
// `ShareDiscussion.copyUrl` is the current page unless told otherwise — so
|
|
47
|
+
// it goes with the other defaults rather than beside the label it shares a
|
|
48
|
+
// prefix with.
|
|
49
|
+
if (field.type !== 'link' || field.advanced || !generated[i])
|
|
50
|
+
return;
|
|
51
|
+
const prefix = LINK_SUFFIX.exec(field.name)?.[1];
|
|
52
|
+
if (!prefix)
|
|
53
|
+
return;
|
|
54
|
+
const label = index.get(`${prefix}Label`);
|
|
55
|
+
if (label === undefined || contract[label]?.type !== 'text' || !generated[label])
|
|
56
|
+
return;
|
|
57
|
+
const icon = index.get(`${prefix}Icon`);
|
|
58
|
+
pairs.push({
|
|
59
|
+
prefix,
|
|
60
|
+
link: i,
|
|
61
|
+
label,
|
|
62
|
+
...(icon !== undefined && generated[icon] ? { icon } : {}),
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
return pairs;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A link group drawn inside its pair: the pair's heading already names it.
|
|
69
|
+
*
|
|
70
|
+
* Its description stays on it. Payload draws a group's description only in
|
|
71
|
+
* the header a label opens, so with `label: false` nothing shows twice — and
|
|
72
|
+
* `payload-types.ts`, which carries each description as JSDoc on the field it
|
|
73
|
+
* belongs to, keeps the contract's guidance on the property it describes.
|
|
74
|
+
*/
|
|
75
|
+
function unheaded(link) {
|
|
76
|
+
return { ...link, label: false };
|
|
77
|
+
}
|
|
78
|
+
function pairGroup(pair, contract, generated) {
|
|
79
|
+
const link = generated[pair.link];
|
|
80
|
+
const label = generated[pair.label];
|
|
81
|
+
const icon = pair.icon === undefined ? null : generated[pair.icon];
|
|
82
|
+
// The link's description is about the destination, which is what the pair's
|
|
83
|
+
// heading introduces, so it moves up to sit under that heading.
|
|
84
|
+
const description = contract[pair.link]?.constraints;
|
|
85
|
+
return {
|
|
86
|
+
type: 'group',
|
|
87
|
+
label: pairTitle(pair.prefix),
|
|
88
|
+
...(description ? { admin: { description } } : {}),
|
|
89
|
+
fields: [
|
|
90
|
+
{ ...label, label: 'Label' },
|
|
91
|
+
...(icon ? [{ ...icon, label: 'Icon' }] : []),
|
|
92
|
+
unheaded(link),
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function isSetting(field) {
|
|
97
|
+
return !field.required && (SETTING_TYPES.has(field.type) || field.advanced === true);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* One level's generated fields, in the order and grouping an author reads them.
|
|
101
|
+
*
|
|
102
|
+
* `generated[i]` is the Payload field for `contract[i]`, or null where the
|
|
103
|
+
* contract field is not authorable. Nulls are dropped here, so the caller does
|
|
104
|
+
* not have to.
|
|
105
|
+
*/
|
|
106
|
+
export function arrange(contract, generated, options) {
|
|
107
|
+
const pairs = findPairs(contract, generated);
|
|
108
|
+
const pairAt = new Map();
|
|
109
|
+
const consumed = new Set();
|
|
110
|
+
for (const pair of pairs) {
|
|
111
|
+
const members = [pair.link, pair.label, ...(pair.icon === undefined ? [] : [pair.icon])];
|
|
112
|
+
pairAt.set(Math.min(...members), pair);
|
|
113
|
+
members.forEach(member => consumed.add(member));
|
|
114
|
+
}
|
|
115
|
+
const main = [];
|
|
116
|
+
const more = [];
|
|
117
|
+
contract.forEach((field, i) => {
|
|
118
|
+
const pair = pairAt.get(i);
|
|
119
|
+
if (pair) {
|
|
120
|
+
main.push(pairGroup(pair, contract, generated));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const built = generated[i];
|
|
124
|
+
if (!built || consumed.has(i))
|
|
125
|
+
return;
|
|
126
|
+
(options.disclose && isSetting(field) ? more : main).push(built);
|
|
127
|
+
});
|
|
128
|
+
if (more.length === 0)
|
|
129
|
+
return main;
|
|
130
|
+
return [
|
|
131
|
+
...main,
|
|
132
|
+
{
|
|
133
|
+
type: 'collapsible',
|
|
134
|
+
label: 'More options',
|
|
135
|
+
admin: {
|
|
136
|
+
initCollapsed: true,
|
|
137
|
+
description: 'Layout and wording the component already sets sensibly. Change them only when this block needs to differ.',
|
|
138
|
+
},
|
|
139
|
+
fields: more,
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
/*
|
|
144
|
+
Two more rules, for the rows of an array rather than the top of a block.
|
|
145
|
+
|
|
146
|
+
**A row of a few short strings is one line.** A stat is a figure and a label,
|
|
147
|
+
a fact is a label and a value, and generated literally each row was two
|
|
148
|
+
full-width inputs stacked under each other — a 12-character box as wide as the
|
|
149
|
+
editor, then its partner below it. When every field of a row is a single-line
|
|
150
|
+
text input and there are only two or three of them, they share one line, each
|
|
151
|
+
as wide as its `maxLength` is long against the others', and never narrower
|
|
152
|
+
than it can be read in. A row holding anything else is left stacked: an image
|
|
153
|
+
or a link beside a text box is a layout to design, not one to derive.
|
|
154
|
+
|
|
155
|
+
**A row is named by what it says.** See `../admin/summary.ts`. The header
|
|
156
|
+
reads the row's first one or two plain-text fields — not a setting, not an
|
|
157
|
+
`advanced` field, and not the label of a call to action, which says "Read more"
|
|
158
|
+
in every row it appears in.
|
|
159
|
+
|
|
160
|
+
Both are drawing only. A `row` stores its children flat, exactly as the
|
|
161
|
+
unnamed groups above do, and a row label is a component in the import map.
|
|
162
|
+
*/
|
|
163
|
+
/** How many short fields one line holds before it is easier read stacked. */
|
|
164
|
+
const ONE_LINE_MAX_FIELDS = 3;
|
|
165
|
+
/**
|
|
166
|
+
* An array row's fields, on one line when they are all short single-line text.
|
|
167
|
+
*
|
|
168
|
+
* Widths are each field's share of the row's combined `maxLength`, floored so
|
|
169
|
+
* a 12-character figure beside a 60-character label is still a box somebody
|
|
170
|
+
* can type into.
|
|
171
|
+
*/
|
|
172
|
+
export function oneLine(fields) {
|
|
173
|
+
if (fields.length < 2 || fields.length > ONE_LINE_MAX_FIELDS)
|
|
174
|
+
return fields;
|
|
175
|
+
const lengths = [];
|
|
176
|
+
for (const field of fields) {
|
|
177
|
+
if (field.type !== 'text' || typeof field.maxLength !== 'number')
|
|
178
|
+
return fields;
|
|
179
|
+
lengths.push(field.maxLength);
|
|
180
|
+
}
|
|
181
|
+
// A field whose share falls under the floor is held at it, and the rest of
|
|
182
|
+
// the line is shared among the others in proportion — repeated, because
|
|
183
|
+
// raising one can push a third under.
|
|
184
|
+
const floor = 1 / (fields.length + 2);
|
|
185
|
+
const held = new Set();
|
|
186
|
+
let shares = [];
|
|
187
|
+
for (let settled = false; !settled;) {
|
|
188
|
+
const free = lengths.reduce((sum, length, i) => (held.has(i) ? sum : sum + length), 0);
|
|
189
|
+
const room = 1 - held.size * floor;
|
|
190
|
+
shares = lengths.map((length, i) => (held.has(i) ? floor : (length / free) * room));
|
|
191
|
+
const under = shares.findIndex((share, i) => !held.has(i) && share < floor);
|
|
192
|
+
if (under === -1)
|
|
193
|
+
settled = true;
|
|
194
|
+
else
|
|
195
|
+
held.add(under);
|
|
196
|
+
}
|
|
197
|
+
const widths = shares.map(share => Math.round(share * 100));
|
|
198
|
+
// Rounding can leave the line a point over or under; the last field absorbs it.
|
|
199
|
+
widths[widths.length - 1] += 100 - widths.reduce((sum, width) => sum + width, 0);
|
|
200
|
+
return [
|
|
201
|
+
{
|
|
202
|
+
type: 'row',
|
|
203
|
+
fields: fields.map((field, i) => ({
|
|
204
|
+
...field,
|
|
205
|
+
admin: { ...('admin' in field ? field.admin : {}), width: `${widths[i]}%` },
|
|
206
|
+
})),
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
/** The most fields a row header joins. More reads as a sentence, not a name. */
|
|
211
|
+
const SUMMARY_FIELDS = 2;
|
|
212
|
+
/**
|
|
213
|
+
* Which of an array row's fields name it in its header, by contract name.
|
|
214
|
+
*
|
|
215
|
+
* `generated[i]` is the Payload field for `contract[i]`, as `arrange` takes
|
|
216
|
+
* it — so an omitted field, or a text field an override turned into a select,
|
|
217
|
+
* is judged by what it became rather than by what the contract says.
|
|
218
|
+
*/
|
|
219
|
+
export function summaryFields(contract, generated, limit = SUMMARY_FIELDS) {
|
|
220
|
+
const paired = new Set(findPairs(contract, generated).flatMap(pair => [
|
|
221
|
+
pair.label,
|
|
222
|
+
pair.link,
|
|
223
|
+
...(pair.icon === undefined ? [] : [pair.icon]),
|
|
224
|
+
]));
|
|
225
|
+
return contract
|
|
226
|
+
.filter((field, i) => {
|
|
227
|
+
const built = generated[i];
|
|
228
|
+
return (!paired.has(i) &&
|
|
229
|
+
!field.advanced &&
|
|
230
|
+
(built?.type === 'text' || built?.type === 'textarea'));
|
|
231
|
+
})
|
|
232
|
+
.slice(0, limit)
|
|
233
|
+
.map(field => field.name);
|
|
234
|
+
}
|
|
235
|
+
/** What a block's heading is called when it has one. */
|
|
236
|
+
const NAME_FIELDS = new Set(['heading', 'title']);
|
|
237
|
+
/**
|
|
238
|
+
* Which one field names a block in its collapsed header, by contract name.
|
|
239
|
+
*
|
|
240
|
+
* One rather than `summaryFields`' two: a block's header already carries its
|
|
241
|
+
* number and the pill naming its type, and a heading is the name a reader
|
|
242
|
+
* would give it. So the block's `heading` or `title` when it has one — an
|
|
243
|
+
* eyebrow comes first in a hero's contract and says less — and otherwise its
|
|
244
|
+
* first short text field, which for a statement or a testimonial is the text
|
|
245
|
+
* itself. `[]` for a block with no text of its own.
|
|
246
|
+
*/
|
|
247
|
+
export function blockSummaryFields(contract, generated) {
|
|
248
|
+
const eligible = summaryFields(contract, generated, contract.length);
|
|
249
|
+
const named = eligible.find(name => NAME_FIELDS.has(name));
|
|
250
|
+
return named ? [named] : eligible.slice(0, 1);
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/generate/layout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ManifestLike } from './blocks.js';
|
|
2
|
+
/**
|
|
3
|
+
* Every resolvable set of allowed values, keyed `Component.field.path`, plus
|
|
4
|
+
* the named unions under `#TypeName`.
|
|
5
|
+
*/
|
|
6
|
+
export type SelectOptionSnapshot = Record<string, readonly string[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Reads the design system's types once, at build time, and writes down what it
|
|
9
|
+
* found.
|
|
10
|
+
*
|
|
11
|
+
* Resolution needs a TypeScript `Program` over the whole declaration tree —
|
|
12
|
+
* seconds of work and a dependency on `typescript` being installed. Doing that
|
|
13
|
+
* when the Payload config loads would put it on every serverless cold start,
|
|
14
|
+
* for an answer that cannot change between deploys. So it happens in a script,
|
|
15
|
+
* the result is committed, and the runtime reads a small JSON object.
|
|
16
|
+
*
|
|
17
|
+
* Committing it also makes the values reviewable: rename a member of
|
|
18
|
+
* `CardVariant` and the diff says so, in the same pull request as the rename.
|
|
19
|
+
*/
|
|
20
|
+
export declare function collectSelectOptions(options: {
|
|
21
|
+
manifest: ManifestLike;
|
|
22
|
+
typesEntry: string;
|
|
23
|
+
/** Named unions to include regardless of any field referencing them. */
|
|
24
|
+
namedTypes?: string[];
|
|
25
|
+
}): SelectOptionSnapshot;
|
|
26
|
+
//# sourceMappingURL=selectOptionSnapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectOptionSnapshot.d.ts","sourceRoot":"","sources":["../../src/generate/selectOptionSnapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAI/C;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAA;AAEpE;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,QAAQ,EAAE,YAAY,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACtB,GAAG,oBAAoB,CAiBvB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SelectOptionResolver } from './selectOptions.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reads the design system's types once, at build time, and writes down what it
|
|
4
|
+
* found.
|
|
5
|
+
*
|
|
6
|
+
* Resolution needs a TypeScript `Program` over the whole declaration tree —
|
|
7
|
+
* seconds of work and a dependency on `typescript` being installed. Doing that
|
|
8
|
+
* when the Payload config loads would put it on every serverless cold start,
|
|
9
|
+
* for an answer that cannot change between deploys. So it happens in a script,
|
|
10
|
+
* the result is committed, and the runtime reads a small JSON object.
|
|
11
|
+
*
|
|
12
|
+
* Committing it also makes the values reviewable: rename a member of
|
|
13
|
+
* `CardVariant` and the diff says so, in the same pull request as the rename.
|
|
14
|
+
*/
|
|
15
|
+
export function collectSelectOptions(options) {
|
|
16
|
+
const resolver = new SelectOptionResolver(options.typesEntry);
|
|
17
|
+
const snapshot = {};
|
|
18
|
+
for (const typeName of options.namedTypes ?? []) {
|
|
19
|
+
const values = resolver.resolveNamed(typeName);
|
|
20
|
+
if (values)
|
|
21
|
+
snapshot[`#${typeName}`] = values;
|
|
22
|
+
}
|
|
23
|
+
for (const component of Object.values(options.manifest.components)) {
|
|
24
|
+
walk(component.content.fields, '', path => {
|
|
25
|
+
const values = resolver.resolve(component.name, path);
|
|
26
|
+
if (values)
|
|
27
|
+
snapshot[`${component.name}.${path}`] = values;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return Object.fromEntries(Object.entries(snapshot).sort(([a], [b]) => a.localeCompare(b)));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Visits every field path, not only the `select` ones.
|
|
34
|
+
*
|
|
35
|
+
* A `text` field whose prop is a literal union is a select the contract could
|
|
36
|
+
* not express — `IntroSection.actions[].icon` is the case — so the snapshot has
|
|
37
|
+
* to carry those too or the generator cannot find them.
|
|
38
|
+
*/
|
|
39
|
+
function walk(fields, prefix, visit) {
|
|
40
|
+
for (const field of fields) {
|
|
41
|
+
const path = prefix ? `${prefix}.${field.name}` : field.name;
|
|
42
|
+
visit(path);
|
|
43
|
+
if (field.of)
|
|
44
|
+
walk(field.of, path, visit);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=selectOptionSnapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectOptionSnapshot.js","sourceRoot":"","sources":["../../src/generate/selectOptionSnapshot.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAQzD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAKpC;IACC,MAAM,QAAQ,GAAG,IAAI,oBAAoB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IAC7D,MAAM,QAAQ,GAAyB,EAAE,CAAA;IAEzC,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAA;QAC9C,IAAI,MAAM;YAAE,QAAQ,CAAC,IAAI,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAA;IAC/C,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACnE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE;YACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YACrD,IAAI,MAAM;gBAAE,QAAQ,CAAC,GAAG,SAAS,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,GAAG,MAAM,CAAA;QAC5D,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAC5F,CAAC;AAED;;;;;;GAMG;AACH,SAAS,IAAI,CAAC,MAAsB,EAAE,MAAc,EAAE,KAA6B;IACjF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAA;QAC5D,KAAK,CAAC,IAAI,CAAC,CAAA;QACX,IAAI,KAAK,CAAC,EAAE;YAAE,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;IAC3C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the allowed values of a `select` field from the design system's own
|
|
3
|
+
* types.
|
|
4
|
+
*
|
|
5
|
+
* The manifest does not carry them. `ContentField` has no `options`, and the
|
|
6
|
+
* allowed values live in prose — `constraints: "'primary', 'secondary' or
|
|
7
|
+
* 'on-dark'."` — which is documentation, not data. `content.variants` looks
|
|
8
|
+
* like a substitute and is not: for most components it holds prose labels
|
|
9
|
+
* ("Body only", "Body with rail") or runtime states ("paused", "playing"), so
|
|
10
|
+
* mapping variants to options produces garbage more often than not.
|
|
11
|
+
*
|
|
12
|
+
* The component's own prop type is the one place the values exist as data, and
|
|
13
|
+
* it is the definition the component actually behaves according to. Reading it
|
|
14
|
+
* makes drift impossible in the direction that matters: rename a member of
|
|
15
|
+
* `CardVariant` and the generated block changes with it, which shows up as a
|
|
16
|
+
* diff in review rather than as an option that silently stopped working.
|
|
17
|
+
*
|
|
18
|
+
* The compiler API rather than a regex because the values are reached three
|
|
19
|
+
* different ways — a named alias (`type CardVariant = 'default' | 'panel'`), an
|
|
20
|
+
* inline union on the prop, and through an array element for a nested field
|
|
21
|
+
* like `Credentials.credentials.icon`. The checker resolves all three; a regex
|
|
22
|
+
* resolves the first.
|
|
23
|
+
*/
|
|
24
|
+
export declare class SelectOptionResolver {
|
|
25
|
+
private readonly checker;
|
|
26
|
+
private readonly source;
|
|
27
|
+
/**
|
|
28
|
+
* @param typesEntry Path to the design system's declaration entry — the file
|
|
29
|
+
* that re-exports every component's props, e.g.
|
|
30
|
+
* `design-system/dist/src/lib/index.d.ts`.
|
|
31
|
+
*/
|
|
32
|
+
constructor(typesEntry: string);
|
|
33
|
+
/**
|
|
34
|
+
* `('Card', 'variant')` → `['default', 'panel']`.
|
|
35
|
+
* `('Credentials', 'credentials.icon')` → the icon keys.
|
|
36
|
+
*
|
|
37
|
+
* Null when the path does not resolve or the leaf is not a union of string
|
|
38
|
+
* literals — the caller decides whether that is an error or a cue to look in
|
|
39
|
+
* the overrides.
|
|
40
|
+
*/
|
|
41
|
+
resolve(component: string, path: string): readonly string[] | null;
|
|
42
|
+
/**
|
|
43
|
+
* A named exported type's literal union — `resolveNamed('IconName')` gives
|
|
44
|
+
* every glyph in the global set.
|
|
45
|
+
*
|
|
46
|
+
* For fields whose prop type cannot carry the values: `ReportGrid.ctaIcon` is
|
|
47
|
+
* a `ReactNode` because the component takes a rendered glyph, while the
|
|
48
|
+
* contract, correctly, says an author picks a *name*.
|
|
49
|
+
*/
|
|
50
|
+
resolveNamed(typeName: string): readonly string[] | null;
|
|
51
|
+
private propsType;
|
|
52
|
+
private exportedType;
|
|
53
|
+
private propertyType;
|
|
54
|
+
/**
|
|
55
|
+
* `Item[]` → `Item`, so a nested path can keep walking into an array's rows.
|
|
56
|
+
*
|
|
57
|
+
* Matched on the array symbol rather than on a numeric index signature. A
|
|
58
|
+
* union of string literals *has* a numeric index type — `string`, from
|
|
59
|
+
* character access — so indexing it collapses `'default' | 'panel'` to
|
|
60
|
+
* `string` and every select silently resolves to nothing.
|
|
61
|
+
*/
|
|
62
|
+
private unwrapArray;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=selectOptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectOptions.d.ts","sourceRoot":"","sources":["../../src/generate/selectOptions.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAElD;;;;OAIG;gBACS,UAAU,EAAE,MAAM;IAY9B;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,IAAI;IAalE;;;;;;;OAOG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,IAAI;IAKxD,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,YAAY;IAUpB;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;CAKpB"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the allowed values of a `select` field from the design system's own
|
|
4
|
+
* types.
|
|
5
|
+
*
|
|
6
|
+
* The manifest does not carry them. `ContentField` has no `options`, and the
|
|
7
|
+
* allowed values live in prose — `constraints: "'primary', 'secondary' or
|
|
8
|
+
* 'on-dark'."` — which is documentation, not data. `content.variants` looks
|
|
9
|
+
* like a substitute and is not: for most components it holds prose labels
|
|
10
|
+
* ("Body only", "Body with rail") or runtime states ("paused", "playing"), so
|
|
11
|
+
* mapping variants to options produces garbage more often than not.
|
|
12
|
+
*
|
|
13
|
+
* The component's own prop type is the one place the values exist as data, and
|
|
14
|
+
* it is the definition the component actually behaves according to. Reading it
|
|
15
|
+
* makes drift impossible in the direction that matters: rename a member of
|
|
16
|
+
* `CardVariant` and the generated block changes with it, which shows up as a
|
|
17
|
+
* diff in review rather than as an option that silently stopped working.
|
|
18
|
+
*
|
|
19
|
+
* The compiler API rather than a regex because the values are reached three
|
|
20
|
+
* different ways — a named alias (`type CardVariant = 'default' | 'panel'`), an
|
|
21
|
+
* inline union on the prop, and through an array element for a nested field
|
|
22
|
+
* like `Credentials.credentials.icon`. The checker resolves all three; a regex
|
|
23
|
+
* resolves the first.
|
|
24
|
+
*/
|
|
25
|
+
export class SelectOptionResolver {
|
|
26
|
+
checker;
|
|
27
|
+
source;
|
|
28
|
+
/**
|
|
29
|
+
* @param typesEntry Path to the design system's declaration entry — the file
|
|
30
|
+
* that re-exports every component's props, e.g.
|
|
31
|
+
* `design-system/dist/src/lib/index.d.ts`.
|
|
32
|
+
*/
|
|
33
|
+
constructor(typesEntry) {
|
|
34
|
+
const program = ts.createProgram([typesEntry], {
|
|
35
|
+
target: ts.ScriptTarget.ES2022,
|
|
36
|
+
module: ts.ModuleKind.ESNext,
|
|
37
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
38
|
+
skipLibCheck: true,
|
|
39
|
+
noEmit: true,
|
|
40
|
+
});
|
|
41
|
+
this.checker = program.getTypeChecker();
|
|
42
|
+
this.source = program.getSourceFile(typesEntry);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* `('Card', 'variant')` → `['default', 'panel']`.
|
|
46
|
+
* `('Credentials', 'credentials.icon')` → the icon keys.
|
|
47
|
+
*
|
|
48
|
+
* Null when the path does not resolve or the leaf is not a union of string
|
|
49
|
+
* literals — the caller decides whether that is an error or a cue to look in
|
|
50
|
+
* the overrides.
|
|
51
|
+
*/
|
|
52
|
+
resolve(component, path) {
|
|
53
|
+
const propsType = this.propsType(component);
|
|
54
|
+
if (!propsType)
|
|
55
|
+
return null;
|
|
56
|
+
let current = propsType;
|
|
57
|
+
for (const segment of path.split('.')) {
|
|
58
|
+
current = this.propertyType(current, segment);
|
|
59
|
+
if (!current)
|
|
60
|
+
return null;
|
|
61
|
+
current = this.unwrapArray(current);
|
|
62
|
+
}
|
|
63
|
+
return literalUnion(current);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A named exported type's literal union — `resolveNamed('IconName')` gives
|
|
67
|
+
* every glyph in the global set.
|
|
68
|
+
*
|
|
69
|
+
* For fields whose prop type cannot carry the values: `ReportGrid.ctaIcon` is
|
|
70
|
+
* a `ReactNode` because the component takes a rendered glyph, while the
|
|
71
|
+
* contract, correctly, says an author picks a *name*.
|
|
72
|
+
*/
|
|
73
|
+
resolveNamed(typeName) {
|
|
74
|
+
const type = this.exportedType(typeName);
|
|
75
|
+
return type ? literalUnion(type) : null;
|
|
76
|
+
}
|
|
77
|
+
propsType(component) {
|
|
78
|
+
return this.exportedType(`${component}Props`);
|
|
79
|
+
}
|
|
80
|
+
exportedType(name) {
|
|
81
|
+
if (!this.source)
|
|
82
|
+
return null;
|
|
83
|
+
const moduleSymbol = this.checker.getSymbolAtLocation(this.source);
|
|
84
|
+
if (!moduleSymbol)
|
|
85
|
+
return null;
|
|
86
|
+
const exported = this.checker
|
|
87
|
+
.getExportsOfModule(moduleSymbol)
|
|
88
|
+
.find(symbol => symbol.getName() === name);
|
|
89
|
+
if (!exported)
|
|
90
|
+
return null;
|
|
91
|
+
// Re-exports arrive as alias symbols; the local declaration is behind one
|
|
92
|
+
// hop. A directly-declared export has no alias to follow.
|
|
93
|
+
const symbol = exported.flags & ts.SymbolFlags.Alias ? this.checker.getAliasedSymbol(exported) : exported;
|
|
94
|
+
const declaration = symbol.declarations?.[0];
|
|
95
|
+
return declaration ? this.checker.getDeclaredTypeOfSymbol(symbol) : null;
|
|
96
|
+
}
|
|
97
|
+
propertyType(type, name) {
|
|
98
|
+
const symbol = type.getProperty(name);
|
|
99
|
+
if (!symbol)
|
|
100
|
+
return null;
|
|
101
|
+
const declaration = symbol.declarations?.[0];
|
|
102
|
+
if (!declaration)
|
|
103
|
+
return null;
|
|
104
|
+
// The declared type, not the type at the use site, so an optional prop
|
|
105
|
+
// resolves to its union rather than to `T | undefined` collapsed.
|
|
106
|
+
return this.checker.getTypeOfSymbolAtLocation(symbol, declaration);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* `Item[]` → `Item`, so a nested path can keep walking into an array's rows.
|
|
110
|
+
*
|
|
111
|
+
* Matched on the array symbol rather than on a numeric index signature. A
|
|
112
|
+
* union of string literals *has* a numeric index type — `string`, from
|
|
113
|
+
* character access — so indexing it collapses `'default' | 'panel'` to
|
|
114
|
+
* `string` and every select silently resolves to nothing.
|
|
115
|
+
*/
|
|
116
|
+
unwrapArray(type) {
|
|
117
|
+
const name = type.getSymbol()?.getName();
|
|
118
|
+
if (name !== 'Array' && name !== 'ReadonlyArray')
|
|
119
|
+
return type;
|
|
120
|
+
return this.checker.getTypeArguments(type)[0] ?? type;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The string literals in a union, or null if it is anything else.
|
|
125
|
+
*
|
|
126
|
+
* `undefined` and `null` members are dropped — a prop being optional says
|
|
127
|
+
* nothing about which values are allowed. A union with a non-literal member
|
|
128
|
+
* (`string`, a component type) returns null rather than a partial list, because
|
|
129
|
+
* a partial list would silently forbid values the component accepts.
|
|
130
|
+
*/
|
|
131
|
+
function literalUnion(type) {
|
|
132
|
+
const members = type.isUnion() ? type.types : [type];
|
|
133
|
+
const values = [];
|
|
134
|
+
for (const member of members) {
|
|
135
|
+
if (member.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Null))
|
|
136
|
+
continue;
|
|
137
|
+
if (member.isStringLiteral()) {
|
|
138
|
+
values.push(member.value);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return values.length > 0 ? values : null;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=selectOptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectOptions.js","sourceRoot":"","sources":["../../src/generate/selectOptions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hand-authored half of the bridge.
|
|
3
|
+
*
|
|
4
|
+
* A design-system contract describes a *content model* — what an author writes.
|
|
5
|
+
* A React component has a *prop model* — what it renders from. They are the
|
|
6
|
+
* same thing 99 times out of 100, which is what makes generation worth doing,
|
|
7
|
+
* and the exceptions are few enough to enumerate. This is where they are
|
|
8
|
+
* enumerated, once, for both the generator and the renderer: a field renamed
|
|
9
|
+
* here is renamed in the Payload schema *and* in what gets passed to the
|
|
10
|
+
* component, so the two can never disagree.
|
|
11
|
+
*/
|
|
12
|
+
/** Where a contract's field type is not the whole story. */
|
|
13
|
+
export interface FieldOverride {
|
|
14
|
+
/**
|
|
15
|
+
* Leave the field out of the CMS entirely.
|
|
16
|
+
*
|
|
17
|
+
* For fields that model runtime state rather than authored content — a
|
|
18
|
+
* form's `status`, say, which is the host's business and changes several
|
|
19
|
+
* times a minute. An author cannot usefully set it and a stored value would
|
|
20
|
+
* be wrong the moment it was read.
|
|
21
|
+
*/
|
|
22
|
+
omit?: true;
|
|
23
|
+
/**
|
|
24
|
+
* The prop name to pass this field as, when it differs from the field name.
|
|
25
|
+
* `ArticleBody` is the only case today: its contract calls the body `body`
|
|
26
|
+
* and the component takes it as `children`.
|
|
27
|
+
*/
|
|
28
|
+
propName?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Force a treatment the contract's `type` cannot express.
|
|
31
|
+
*
|
|
32
|
+
* `videoUpload` is for a video the site hosts itself rather than embeds. The
|
|
33
|
+
* contract's `video` type means a provider URL — it is `VideoEmbed`'s
|
|
34
|
+
* YouTube/Vimeo/Wistia iframe src — and the field type enum in
|
|
35
|
+
* `@forumone/throughline-design-system/contract` has no eleventh value for "a file
|
|
36
|
+
* in the media library". So the contract says `video`, and this says which
|
|
37
|
+
* of the two kinds of video it meant: an upload field, resolved to the
|
|
38
|
+
* stored file's URL with no `srcSet` beside it, because a video has no
|
|
39
|
+
* candidate widths to choose between.
|
|
40
|
+
*/
|
|
41
|
+
as?: 'icon' | 'url' | 'videoUpload';
|
|
42
|
+
/**
|
|
43
|
+
* Explicit `select` options. The manifest carries none — allowed values live
|
|
44
|
+
* in prose `constraints` — so they come from the component's own literal
|
|
45
|
+
* union type where one exists, and from here where it does not.
|
|
46
|
+
*/
|
|
47
|
+
options?: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
export interface ComponentOverride {
|
|
50
|
+
/**
|
|
51
|
+
* Keep the component out of the block palette.
|
|
52
|
+
*
|
|
53
|
+
* For components that are real parts of a page but not authorable as blocks:
|
|
54
|
+
* site chrome a template renders (`Header`, `Footer`, `Skiplink`), and
|
|
55
|
+
* anything whose props are supplied by the document rather than by a block
|
|
56
|
+
* author.
|
|
57
|
+
*/
|
|
58
|
+
notABlock?: true;
|
|
59
|
+
/**
|
|
60
|
+
* The block's name in the picker, when splitting the component name reads
|
|
61
|
+
* wrong: `AtAGlance` humanizes to "At AGlance". The slug is unchanged — it
|
|
62
|
+
* is the manifest name, byte for byte, whatever the label says.
|
|
63
|
+
*/
|
|
64
|
+
label?: string;
|
|
65
|
+
/** Keyed by field path — `heading`, `items.title`, `image.src`. */
|
|
66
|
+
fields?: Record<string, FieldOverride>;
|
|
67
|
+
}
|
|
68
|
+
export type Overrides = Record<string, ComponentOverride>;
|
|
69
|
+
/** Look an override up by component and dotted field path. */
|
|
70
|
+
export declare function fieldOverride(overrides: Overrides, component: string, path: string): FieldOverride | undefined;
|
|
71
|
+
//# sourceMappingURL=overrides.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../src/overrides.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,IAAI,CAAA;IACX;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;OAWG;IACH,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,aAAa,CAAA;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,IAAI,CAAA;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;CACvC;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;AAEzD,8DAA8D;AAC9D,wBAAgB,aAAa,CAC3B,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,aAAa,GAAG,SAAS,CAE3B"}
|