@yahoo/uds-create-config 5.75.0 → 5.77.0
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/dist/capture/capture-to-spec.js +741 -28
- package/dist/capture/class-grammar.js +46 -6
- package/dist/configs/build-options.js +10 -0
- package/dist/configs/react-native-system.d.ts +1 -0
- package/dist/configs/system.js +2 -0
- package/dist/entities/native/NativeSettings.d.ts +1 -0
- package/dist/entities/system/Component.js +3 -3
- package/dist/entities/system/Settings.d.ts +2 -0
- package/dist/framework/Config.d.ts +1 -1
- package/dist/framework/Config.js +1 -1
- package/dist/framework/defineDerivedEntity.js +1 -1
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/index.js +2 -2
- package/dist/migrations/20261104151013_build_debug.d.ts +9 -0
- package/dist/migrations/20261104151013_build_debug.js +10 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { isRef, pathOf } from "../framework/utils/refs.js";
|
|
1
|
+
import { isRef, kindOf, pathOf } from "../framework/utils/refs.js";
|
|
2
2
|
import { StyleProperty } from "../entities/system/StyleProperty.js";
|
|
3
|
-
import { LAYER_PROPS, authoredValue } from "../framework/projections.js";
|
|
3
|
+
import { LAYER_PROPS, authoredValue, glyphComponentOf } from "../framework/projections.js";
|
|
4
4
|
import { intoName } from "../framework/prop-surface.js";
|
|
5
|
+
import { arbitraryClassName, stylePropClassName } from "../framework/class-names.js";
|
|
5
6
|
import { Component } from "../entities/system/Component.js";
|
|
7
|
+
import { iconLibraries } from "../entities/system/icon-library.js";
|
|
6
8
|
import { elementFromCaptured, isCapturedElement } from "./captured-tree.js";
|
|
7
9
|
import { classGrammar, normalizeCssValue } from "./class-grammar.js";
|
|
8
10
|
//#region src/capture/capture-to-spec.ts
|
|
@@ -114,6 +116,15 @@ function specFromCapture(options) {
|
|
|
114
116
|
* value, `<width>x<height>`, is the layout size its content keeps.
|
|
115
117
|
*/
|
|
116
118
|
const FOLD = "data-uds-fold";
|
|
119
|
+
/** The page capture's mark on the empty box it left where an embed was. */
|
|
120
|
+
const EMBED_SPACE_ATTR = "data-uds-embed";
|
|
121
|
+
/**
|
|
122
|
+
* The styles the page's stylesheets gave an unmarked element, as the browser computed them, which the
|
|
123
|
+
* capture writes beside its own `style`. They are what a wrapper styled by anything the system does not
|
|
124
|
+
* name (an app's Tailwind, a CSS module) drew with, and rank below both the element's own inline style
|
|
125
|
+
* and the system's classes on it, which name the value rather than the pixels it came to.
|
|
126
|
+
*/
|
|
127
|
+
const COMPUTED_STYLE_ATTR = "data-uds-computed";
|
|
117
128
|
/** Marks a layer whose component's root is not in the DOM with `<component>#<layer>`. */
|
|
118
129
|
const ORPHAN_LAYER = "data-uds-orphan-layer";
|
|
119
130
|
/**
|
|
@@ -183,10 +194,38 @@ var Converter = class {
|
|
|
183
194
|
counter = 0;
|
|
184
195
|
childCap;
|
|
185
196
|
ruled = /* @__PURE__ */ new Map();
|
|
197
|
+
/** Each component that draws a glyph by name → the prop that names it. */
|
|
198
|
+
glyphDrawers;
|
|
199
|
+
/** Each breakpoint's class prefix (`lg`) → whether it applied at the capture's width, and its rank. */
|
|
200
|
+
breakpoints;
|
|
201
|
+
/** Each modifier's class prefix (`md`) → the prop key its bag is written under (`_md`). */
|
|
202
|
+
modifierKeys;
|
|
186
203
|
constructor(options, grammar) {
|
|
187
204
|
this.options = options;
|
|
188
205
|
this.grammar = grammar;
|
|
189
206
|
this.childCap = options.childCap ?? DEFAULT_CHILD_CAP;
|
|
207
|
+
const drawers = /* @__PURE__ */ new Map();
|
|
208
|
+
for (const library of iconLibraries(options.config)) {
|
|
209
|
+
const drawer = glyphComponentOf(options.config, library.path);
|
|
210
|
+
if (drawer) drawers.set(drawer.path, drawer.prop);
|
|
211
|
+
}
|
|
212
|
+
this.glyphDrawers = drawers;
|
|
213
|
+
const width = options.width ?? DEFAULT_CAPTURE_WIDTH;
|
|
214
|
+
const breakpoints = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const modifier of options.config.list("modifier")) {
|
|
216
|
+
const { type, value } = modifier.toJSON();
|
|
217
|
+
if (type !== "media-query" || typeof value !== "string") continue;
|
|
218
|
+
const min = /min-width:\s*(\d+(?:\.\d+)?)px/.exec(value);
|
|
219
|
+
const max = /max-width:\s*(\d+(?:\.\d+)?)px/.exec(value);
|
|
220
|
+
if (!min && !max) continue;
|
|
221
|
+
const active = (!min || width >= Number(min[1])) && (!max || width <= Number(max[1]));
|
|
222
|
+
breakpoints.set(modifier.leaf, {
|
|
223
|
+
active,
|
|
224
|
+
rank: min ? Number(min[1]) : 0
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
this.breakpoints = breakpoints;
|
|
228
|
+
this.modifierKeys = new Map(options.config.list("modifier").map((modifier) => [modifier.leaf, `_${modifier.leaf}`]));
|
|
190
229
|
}
|
|
191
230
|
convertTop(el) {
|
|
192
231
|
return this.convert(el);
|
|
@@ -200,15 +239,25 @@ var Converter = class {
|
|
|
200
239
|
}
|
|
201
240
|
return out;
|
|
202
241
|
}
|
|
203
|
-
/**
|
|
204
|
-
|
|
242
|
+
/**
|
|
243
|
+
* The components under `layer`, skipping every other layer `path` marks inside it. With
|
|
244
|
+
* `callerMarkup`, the layer is one a slot of elements lands on, so the unmarked markup in it is
|
|
245
|
+
* the caller's, kept as the plain tags it is, rather than the render's own.
|
|
246
|
+
*/
|
|
247
|
+
convertOutsideLayers(layer, path, callerMarkup = false) {
|
|
205
248
|
const out = [];
|
|
249
|
+
const mixed = callerMarkup && Array.from(layer.childNodes).some((node) => isElement(node) && visible(node));
|
|
206
250
|
for (let i = 0; i < layer.childNodes.length; i += 1) {
|
|
207
251
|
if (this.childCap > 0 && out.length >= this.childCap) break;
|
|
208
252
|
const node = layer.childNodes[i];
|
|
209
|
-
if (!isElement(node))
|
|
253
|
+
if (!isElement(node)) {
|
|
254
|
+
const text = (node.textContent ?? "").replace(/\s+/g, " ");
|
|
255
|
+
if (mixed && text.trim()) out.push(...this.plainTextRun(text, layer));
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
210
258
|
if (node.getAttribute("data-uds-component") === path && node.getAttribute("data-uds-layer") !== "root") continue;
|
|
211
259
|
if (node.tagName.toLowerCase() === "svg" || isImage(node) || node.hasAttribute("data-uds-component") && node.getAttribute("data-uds-layer") === "root") out.push(...this.convert(node));
|
|
260
|
+
else if (visible(node) && callerMarkup) out.push(...this.plain(node));
|
|
212
261
|
else if (visible(node) && framesItself(node)) out.push(...this.frame(node));
|
|
213
262
|
else if (visible(node)) out.push(...this.pinFolded(node, this.convertOutsideLayers(node, path)));
|
|
214
263
|
}
|
|
@@ -222,10 +271,270 @@ var Converter = class {
|
|
|
222
271
|
return key ? [key] : [];
|
|
223
272
|
}
|
|
224
273
|
if (el.tagName.toLowerCase() === "svg") return this.icon(el);
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
274
|
+
if (el.hasAttribute(ORPHAN_LAYER)) return this.frame(el);
|
|
275
|
+
return this.plain(el);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* What an element inside a component holds, by the rule for a component's render: a nested
|
|
279
|
+
* component is itself, and an unmarked element folds into what it holds, or is a frame when it
|
|
280
|
+
* places itself, since it may be the render's own markup rather than the page's.
|
|
281
|
+
*/
|
|
282
|
+
convertInside(el) {
|
|
283
|
+
const out = [];
|
|
284
|
+
for (let i = 0; i < el.childNodes.length; i += 1) {
|
|
285
|
+
if (this.childCap > 0 && out.length >= this.childCap) break;
|
|
286
|
+
const node = el.childNodes[i];
|
|
287
|
+
if (!isElement(node) || !visible(node)) continue;
|
|
288
|
+
if (node.tagName.toLowerCase() === "svg" || node.hasAttribute("data-uds-component") && node.getAttribute("data-uds-layer") === "root") out.push(...this.convert(node));
|
|
289
|
+
else if (isImage(node)) out.push(...this.image(node));
|
|
290
|
+
else if (isField(node)) out.push(...this.field(node));
|
|
291
|
+
else if (framesItself(node)) out.push(...this.frame(node));
|
|
292
|
+
else out.push(...this.pinFolded(node, this.convertInside(node)));
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* An unmarked element as the plain tag it is: an `img` keeps its source, a link its `href`, a field
|
|
298
|
+
* its type and placeholder, and its text stays inside it. Its styling is stored as classes, the way
|
|
299
|
+
* the canvas paints and the design panel edits a plain element: the classes it wears that the system
|
|
300
|
+
* names, then its own inline style and the styles its stylesheets gave it written as the system's
|
|
301
|
+
* classes (`16px` of gap is `gap-4`, an off-scale `13px` is `gap-[13px]`). What no style prop can
|
|
302
|
+
* hold stays as an inline style. A `div` or `span` that carries nothing only groups its children and
|
|
303
|
+
* folds away, as a wrapper the capture marked foldable does.
|
|
304
|
+
*/
|
|
305
|
+
plain(el, convertKids = () => this.plainChildren(el)) {
|
|
306
|
+
const tag = el.tagName.toLowerCase();
|
|
307
|
+
if (FORBIDDEN_TAGS.has(tag)) return [];
|
|
308
|
+
if (el.hasAttribute(FOLD)) return this.pinFolded(el, convertKids());
|
|
309
|
+
if ([...inlineDeclarations(el), ...declarationsOf(el.getAttribute("data-uds-computed") ?? "")].find(([property]) => property === "display")?.[1].trim() === "contents") return convertKids();
|
|
310
|
+
const { className, style, paints } = this.plainStyle(el);
|
|
311
|
+
const attrs = plainAttributes(el);
|
|
312
|
+
if (tag === "img" && !attrs.src) return [];
|
|
313
|
+
const children = VOID_TAGS.has(tag) ? [] : convertKids();
|
|
314
|
+
const text = children.length === 0 ? plainText(el) : "";
|
|
315
|
+
if (children.length === 0 && !text && !VOID_TAGS.has(tag) && !paints && !el.hasAttribute(EMBED_SPACE_ATTR)) return [];
|
|
316
|
+
if (!(className !== "" || Object.keys(style).length > 0 || Object.keys(attrs).length > 0) && GROUPING_TAGS.has(tag)) {
|
|
317
|
+
if (text) return this.plainTextRun(text);
|
|
318
|
+
return children;
|
|
319
|
+
}
|
|
320
|
+
const props = { ...attrs };
|
|
321
|
+
if (className) props.className = className;
|
|
322
|
+
if (Object.keys(style).length > 0) props.style = style;
|
|
323
|
+
if (text) props.children = text;
|
|
324
|
+
const key = this.key(tag);
|
|
325
|
+
this.elements[key] = {
|
|
326
|
+
type: tag,
|
|
327
|
+
props,
|
|
328
|
+
...children.length > 0 ? { children } : {}
|
|
329
|
+
};
|
|
330
|
+
return [key];
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* An inline SVG as itself: the `svg` and every shape in it, each with its attributes under the
|
|
334
|
+
* names React takes them by. Definitions it references (a clip path, a gradient) are left out with
|
|
335
|
+
* the references to them, since an id the capture copied may name nothing on the canvas.
|
|
336
|
+
*/
|
|
337
|
+
plainSvg(svg) {
|
|
338
|
+
if (holdsMoreThan(svg, SVG_SHAPE_LIMIT)) {
|
|
339
|
+
const covered = /* @__PURE__ */ new Set();
|
|
340
|
+
const size = (attr) => {
|
|
341
|
+
const value = svg.getAttribute(attr);
|
|
342
|
+
return value && /^\d+(?:\.\d+)?$/.test(value) ? this.classForDeclaration(attr, `${value}px`, covered) : void 0;
|
|
343
|
+
};
|
|
344
|
+
const className = [size("width"), size("height")].filter((cls) => !!cls && cls !== "covered").join(" ");
|
|
345
|
+
const key = this.key("div");
|
|
346
|
+
this.elements[key] = {
|
|
347
|
+
type: "div",
|
|
348
|
+
props: className ? { className } : {}
|
|
349
|
+
};
|
|
350
|
+
return key;
|
|
351
|
+
}
|
|
352
|
+
const build = (el) => {
|
|
353
|
+
const tag = el.tagName.toLowerCase();
|
|
354
|
+
const props = { [VERBATIM_ATTRIBUTES]: true };
|
|
355
|
+
const styled = [];
|
|
356
|
+
for (const { name, value } of el.attributes) {
|
|
357
|
+
if (SVG_SKIPPED_ATTRIBUTES.test(name)) continue;
|
|
358
|
+
if (name === "class" || name === "style") continue;
|
|
359
|
+
if (name === "width" || name === "height") continue;
|
|
360
|
+
const prop = svgAttributeName(name);
|
|
361
|
+
if (this.options.config.resolve("style-property", prop)) {
|
|
362
|
+
styled.push([name, value]);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
props[prop] = value;
|
|
366
|
+
}
|
|
367
|
+
const { className, style } = this.plainStyle(el, styled);
|
|
368
|
+
if (className) props.className = className;
|
|
369
|
+
if (Object.keys(style).length > 0) props.style = style;
|
|
370
|
+
const children = [];
|
|
371
|
+
let text = "";
|
|
372
|
+
for (const node of el.childNodes) {
|
|
373
|
+
if (!isElement(node)) {
|
|
374
|
+
text += node.textContent ?? "";
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (SVG_SKIPPED_TAGS.has(node.tagName.toLowerCase())) continue;
|
|
378
|
+
children.push(build(node));
|
|
379
|
+
}
|
|
380
|
+
if (children.length === 0 && text.trim()) props.children = text.trim();
|
|
381
|
+
const key = this.key(SVG_TAGS.get(tag) ?? tag);
|
|
382
|
+
this.elements[key] = {
|
|
383
|
+
type: SVG_TAGS.get(tag) ?? tag,
|
|
384
|
+
props,
|
|
385
|
+
...children.length > 0 ? { children } : {}
|
|
386
|
+
};
|
|
387
|
+
return key;
|
|
388
|
+
};
|
|
389
|
+
return build(svg);
|
|
390
|
+
}
|
|
391
|
+
/** A plain element's content: its elements, and each run of text between them as a `span`. */
|
|
392
|
+
plainChildren(el) {
|
|
393
|
+
if (!Array.from(el.childNodes).some((node) => isElement(node) && visible(node))) return [];
|
|
394
|
+
const out = [];
|
|
395
|
+
for (let i = 0; i < el.childNodes.length; i += 1) {
|
|
396
|
+
if (this.childCap > 0 && out.length >= this.childCap) break;
|
|
397
|
+
const node = el.childNodes[i];
|
|
398
|
+
if (isElement(node)) {
|
|
399
|
+
out.push(...this.convert(node));
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const text = (node.textContent ?? "").replace(/\s+/g, " ");
|
|
403
|
+
if (text.trim()) out.push(...this.plainTextRun(text));
|
|
404
|
+
}
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* A run of text as a `span`. It keeps how its holder wraps (`white-space: nowrap` on a nav item),
|
|
409
|
+
* which the holder, a component that takes no such prop, may have no way to say itself.
|
|
410
|
+
*/
|
|
411
|
+
plainTextRun(text, holder) {
|
|
412
|
+
const props = { children: text };
|
|
413
|
+
const wraps = declarationsOf(holder?.getAttribute("data-uds-computed") ?? "").find(([property]) => property === "white-space");
|
|
414
|
+
if (wraps) {
|
|
415
|
+
const cls = this.classForDeclaration("white-space", normalizeCssValue(wraps[1]), /* @__PURE__ */ new Set());
|
|
416
|
+
if (cls && cls !== "covered") props.className = cls;
|
|
417
|
+
else props.style = { whiteSpace: wraps[1] };
|
|
418
|
+
}
|
|
419
|
+
const key = this.key("span");
|
|
420
|
+
this.elements[key] = {
|
|
421
|
+
type: "span",
|
|
422
|
+
props
|
|
423
|
+
};
|
|
424
|
+
return [key];
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* A plain element's styling as the system's classes, and what none can hold as inline style. The
|
|
428
|
+
* page's own inline style painted over its classes, so it is read first and a class for a property it
|
|
429
|
+
* sets is dropped; the computed styles fill only what neither says.
|
|
430
|
+
*/
|
|
431
|
+
plainStyle(el, attributes = []) {
|
|
432
|
+
const classes = [];
|
|
433
|
+
const style = {};
|
|
434
|
+
const covered = /* @__PURE__ */ new Set();
|
|
435
|
+
let paints = false;
|
|
436
|
+
const notePaint = (property) => {
|
|
437
|
+
if (PAINT_PROPERTY.test(property)) paints = true;
|
|
438
|
+
};
|
|
439
|
+
const written = /* @__PURE__ */ new Set();
|
|
440
|
+
const fromDeclarations = (declarations) => {
|
|
441
|
+
for (const [property, raw] of declarations.flatMap(gridAreaLines)) {
|
|
442
|
+
if (written.has(property) || written.has(LOGICAL_SIDES[property] ?? property)) continue;
|
|
443
|
+
let value = normalizeCssValue(raw);
|
|
444
|
+
const paintNone = value === "none" && (property === "fill" || property === "stroke");
|
|
445
|
+
if (!value || SKIP_INLINE_PROPERTY.has(property) || SKIP_INLINE_VALUE.has(value) && !paintNone) continue;
|
|
446
|
+
if (property === "position" && value === "fixed") value = "absolute";
|
|
447
|
+
if (VIEWPORT_VALUE.test(value) && !WHOLE_VIEWPORT.test(value)) value = frameUnits(value, this.options.width, this.options.height);
|
|
448
|
+
const cls = this.classForDeclaration(property, value, covered);
|
|
449
|
+
if (cls === "covered") continue;
|
|
450
|
+
notePaint(property);
|
|
451
|
+
if (cls) {
|
|
452
|
+
classes.push(cls);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
let placed = false;
|
|
456
|
+
for (const [longhand, part] of gridLonghands({
|
|
457
|
+
property,
|
|
458
|
+
value
|
|
459
|
+
})) {
|
|
460
|
+
const longClass = this.classForDeclaration(longhand, part, covered);
|
|
461
|
+
if (longClass && longClass !== "covered") classes.push(longClass);
|
|
462
|
+
placed = placed || longClass !== void 0;
|
|
463
|
+
}
|
|
464
|
+
if (!placed && !(camelCase(property) in style)) style[camelCase(property)] = raw.trim();
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
fromDeclarations(inlineDeclarations(el));
|
|
468
|
+
fromDeclarations(attributes);
|
|
469
|
+
fromDeclarations(["width", "height"].flatMap((attr) => {
|
|
470
|
+
const value = el.getAttribute(attr);
|
|
471
|
+
return value && /^\d+(?:\.\d+)?$/.test(value) ? [[attr, `${value}px`]] : [];
|
|
472
|
+
}));
|
|
473
|
+
const read = classesOf(el).flatMap((cls) => {
|
|
474
|
+
const each = this.readModifiedClass(cls);
|
|
475
|
+
if (!each) return [];
|
|
476
|
+
if (each.modifiers.length === 0 && covered.has(each.style.path)) return [];
|
|
477
|
+
const { properties = [] } = this.options.config.resolve("style-property", each.style.path)?.toJSON() ?? {};
|
|
478
|
+
const atoms = atomsOf(properties);
|
|
479
|
+
if (atoms.size === 0) atoms.add(`prop:${each.style.path}`);
|
|
480
|
+
return [{
|
|
481
|
+
...each,
|
|
482
|
+
properties,
|
|
483
|
+
atoms
|
|
484
|
+
}];
|
|
485
|
+
});
|
|
486
|
+
const winners = /* @__PURE__ */ new Map();
|
|
487
|
+
for (const each of read) for (const atom of each.atoms) {
|
|
488
|
+
const slot = `${each.modifiers.join(".")}|${atom}`;
|
|
489
|
+
const prior = winners.get(slot);
|
|
490
|
+
if (!prior || each.rank > prior.rank || each.rank === prior.rank && each.atoms.size < prior.atoms.size) winners.set(slot, each);
|
|
491
|
+
}
|
|
492
|
+
const painted = new Set(winners.values());
|
|
493
|
+
for (const each of read) {
|
|
494
|
+
if (!painted.has(each)) continue;
|
|
495
|
+
if (each.modifiers.length === 0) covered.add(each.style.path);
|
|
496
|
+
for (const property of each.properties) {
|
|
497
|
+
notePaint(property);
|
|
498
|
+
if (each.modifiers.length === 0) for (const longhand of longhandsOf(property)) written.add(longhand);
|
|
499
|
+
}
|
|
500
|
+
const owned = [...each.atoms].filter((atom) => winners.get(`${each.modifiers.join(".")}|${atom}`) === each);
|
|
501
|
+
classes.push(...owned.length < each.atoms.size ? this.narrowed(each, new Set(owned)) : [each.bare]);
|
|
502
|
+
}
|
|
503
|
+
fromDeclarations(declarationsOf(el.getAttribute("data-uds-computed") ?? ""));
|
|
504
|
+
return {
|
|
505
|
+
className: [...new Set(classes)].join(" "),
|
|
506
|
+
style,
|
|
507
|
+
paints
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* The system's class for one declaration: the class of the leaf whose value it is, or the bracket
|
|
512
|
+
* class of an off-scale value on a prop that takes one. `covered` when a prop for the property is
|
|
513
|
+
* already set, so the first source to set it wins; nothing when no prop writes the property.
|
|
514
|
+
*/
|
|
515
|
+
classForDeclaration(property, value, covered) {
|
|
516
|
+
const logical = LOGICAL_SIDES[property];
|
|
517
|
+
const options = [...this.grammar.stylePropsFor(property), ...logical ? this.grammar.stylePropsFor(logical) : []];
|
|
518
|
+
for (const option of options) {
|
|
519
|
+
if (covered.has(option.path)) return "covered";
|
|
520
|
+
const css = option.negates ? negated(value) : value;
|
|
521
|
+
if (css === void 0) continue;
|
|
522
|
+
const leaf = equivalents({
|
|
523
|
+
property,
|
|
524
|
+
value: css
|
|
525
|
+
}).map((spelling) => option.leafFor(spelling)).find((found) => found !== void 0);
|
|
526
|
+
let cls;
|
|
527
|
+
if (WHOLE_VIEWPORT.test(css) && option.fullLeaf !== void 0) cls = stylePropClassName(this.options.config, option.path, option.fullLeaf);
|
|
528
|
+
else if (leaf !== void 0) cls = stylePropClassName(this.options.config, option.path, leaf);
|
|
529
|
+
else if (option.arbitrary) cls = arbitraryClassName({
|
|
530
|
+
config: this.options.config,
|
|
531
|
+
path: option.path,
|
|
532
|
+
value: `[${css.replaceAll(" ", "_")}]`
|
|
533
|
+
});
|
|
534
|
+
if (cls === void 0) continue;
|
|
535
|
+
covered.add(option.path);
|
|
536
|
+
return cls;
|
|
537
|
+
}
|
|
229
538
|
}
|
|
230
539
|
/**
|
|
231
540
|
* The content of an unmarked wrapper the capture folded, with the wrapper's measured box written
|
|
@@ -236,6 +545,18 @@ var Converter = class {
|
|
|
236
545
|
const size = el.getAttribute(FOLD)?.match(/^(\d+(?:\.\d+)?)x(\d+(?:\.\d+)?)$/);
|
|
237
546
|
if (!size || keys.length !== 1 || el.hasAttribute("data-uds-component")) return keys;
|
|
238
547
|
const element = this.elements[keys[0]];
|
|
548
|
+
if (kindOf(element.type) !== "component") {
|
|
549
|
+
const covered = /* @__PURE__ */ new Set();
|
|
550
|
+
const sized = [this.classForDeclaration("width", `${size[1]}px`, covered), this.classForDeclaration("height", `${size[2]}px`, covered)].filter((cls) => !!cls && cls !== "covered");
|
|
551
|
+
const own = String(element.props.className ?? "").split(/\s+/).filter((cls) => {
|
|
552
|
+
const read = cls ? this.readModifiedClass(cls) : void 0;
|
|
553
|
+
if (!read || read.modifiers.length > 0) return Boolean(cls);
|
|
554
|
+
const atoms = this.styleAtoms().get(read.style.path);
|
|
555
|
+
return !(atoms?.has("width") || atoms?.has("height"));
|
|
556
|
+
});
|
|
557
|
+
element.props.className = [...own, ...sized].join(" ");
|
|
558
|
+
return keys;
|
|
559
|
+
}
|
|
239
560
|
const grammar = this.grammar.component(element.type.replace(/^component:/, ""));
|
|
240
561
|
if (grammar) this.propsFromDeclarations([["width", `${size[1]}px`], ["height", `${size[2]}px`]], grammar, element.props);
|
|
241
562
|
return keys;
|
|
@@ -247,7 +568,7 @@ var Converter = class {
|
|
|
247
568
|
* nothing.
|
|
248
569
|
*/
|
|
249
570
|
frame(el) {
|
|
250
|
-
const children = this.
|
|
571
|
+
const children = this.convertInside(el);
|
|
251
572
|
if (children.length === 0 && !paintsItself(el)) return [];
|
|
252
573
|
const role = canvasRole(this.options.config, "frame");
|
|
253
574
|
const grammar = role ? this.grammar.component(role.component) : void 0;
|
|
@@ -264,6 +585,102 @@ var Converter = class {
|
|
|
264
585
|
return [key];
|
|
265
586
|
}
|
|
266
587
|
/**
|
|
588
|
+
* The style props `classes` spell for a component with `grammar`, each class under a modifier
|
|
589
|
+
* prefix written into that modifier's bag (`md:hover:flex` → `_md._hover.display`). A class whose
|
|
590
|
+
* prefix names no modifier the config has, or whose prop the component does not take, is not read.
|
|
591
|
+
* A value already set is kept: the first class for a prop wins, as a stamped value does.
|
|
592
|
+
*/
|
|
593
|
+
stylePropsFromClasses(classes, grammar, props) {
|
|
594
|
+
const ranks = /* @__PURE__ */ new Map();
|
|
595
|
+
for (const cls of classes) {
|
|
596
|
+
const read = this.readModifiedClass(cls);
|
|
597
|
+
if (!read) continue;
|
|
598
|
+
const prop = grammar.propForStyleProperty.get(read.style.path);
|
|
599
|
+
if (!prop) continue;
|
|
600
|
+
const slot = `${read.modifiers.join(".")}|${prop}`;
|
|
601
|
+
const before = ranks.get(slot);
|
|
602
|
+
if (before !== void 0 && before >= read.rank) continue;
|
|
603
|
+
ranks.set(slot, read.rank);
|
|
604
|
+
let bag = props;
|
|
605
|
+
for (const key of read.modifiers) {
|
|
606
|
+
const inner = bag[key];
|
|
607
|
+
if (inner && typeof inner === "object" && !Array.isArray(inner)) bag = inner;
|
|
608
|
+
else if (inner === void 0) {
|
|
609
|
+
const next = {};
|
|
610
|
+
bag[key] = next;
|
|
611
|
+
bag = next;
|
|
612
|
+
} else bag = {};
|
|
613
|
+
}
|
|
614
|
+
bag[prop] = this.frameValue(read.style.path, read.style.leaf);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
atomsByStyle;
|
|
618
|
+
/** Each style property → the physical longhands it writes. */
|
|
619
|
+
styleAtoms() {
|
|
620
|
+
if (!this.atomsByStyle) this.atomsByStyle = new Map(this.options.config.list("style-property").map((prop) => {
|
|
621
|
+
const { properties = [] } = prop.toJSON();
|
|
622
|
+
return [prop.path, atomsOf(properties)];
|
|
623
|
+
}));
|
|
624
|
+
return this.atomsByStyle;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* A shorthand class that a narrower one overrode on some sides (`p-5` under `px-16`), as the
|
|
628
|
+
* classes for the sides it still painted (`py-5`), so the canvas's stylesheet order cannot let it
|
|
629
|
+
* win the others back. The class as written when the system has no narrower class at its value.
|
|
630
|
+
*/
|
|
631
|
+
narrowed(shorthand, owned) {
|
|
632
|
+
const config = this.options.config;
|
|
633
|
+
const options = /* @__PURE__ */ new Map();
|
|
634
|
+
for (const [path, atoms] of this.styleAtoms()) {
|
|
635
|
+
if (path === shorthand.style.path || atoms.size === 0) continue;
|
|
636
|
+
if ([...atoms].every((each) => owned.has(each))) options.set(path, atoms);
|
|
637
|
+
}
|
|
638
|
+
const left = new Set(owned);
|
|
639
|
+
const out = [];
|
|
640
|
+
for (const [path, atoms] of [...options].sort((a, b) => b[1].size - a[1].size)) {
|
|
641
|
+
if (![...atoms].every((each) => left.has(each))) continue;
|
|
642
|
+
const cls = stylePropClassName(config, path, shorthand.style.leaf);
|
|
643
|
+
if (this.grammar.styleProp(cls)?.path !== path) continue;
|
|
644
|
+
out.push(`${shorthand.prefix}${cls}`);
|
|
645
|
+
for (const each of atoms) left.delete(each);
|
|
646
|
+
}
|
|
647
|
+
return left.size === 0 ? out : [shorthand.bare];
|
|
648
|
+
}
|
|
649
|
+
/** A class as the modifier keys over it and the style prop it names, or nothing for one it doesn't. */
|
|
650
|
+
readModifiedClass(cls) {
|
|
651
|
+
const bracket = cls.indexOf("[");
|
|
652
|
+
const head = bracket === -1 ? cls : cls.slice(0, bracket);
|
|
653
|
+
const at = head.lastIndexOf(":");
|
|
654
|
+
const prefixes = at === -1 ? [] : head.slice(0, at).split(":");
|
|
655
|
+
const modifiers = [];
|
|
656
|
+
const kept = [];
|
|
657
|
+
let rank = 0;
|
|
658
|
+
for (const prefix of prefixes) {
|
|
659
|
+
const breakpoint = this.breakpoints.get(prefix);
|
|
660
|
+
if (breakpoint) {
|
|
661
|
+
if (!breakpoint.active) return void 0;
|
|
662
|
+
rank = Math.max(rank, breakpoint.rank);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
const key = this.modifierKeys.get(prefix);
|
|
666
|
+
if (!key) return void 0;
|
|
667
|
+
modifiers.push(key);
|
|
668
|
+
kept.push(prefix);
|
|
669
|
+
}
|
|
670
|
+
let leaf = at === -1 ? cls : cls.slice(at + 1);
|
|
671
|
+
const important = /^!|!$/.test(leaf);
|
|
672
|
+
if (important) leaf = leaf.replace(/^!|!$/, "");
|
|
673
|
+
if (important) rank += IMPORTANT_RANK;
|
|
674
|
+
const style = this.grammar.styleProp(leaf);
|
|
675
|
+
return style ? {
|
|
676
|
+
modifiers,
|
|
677
|
+
style,
|
|
678
|
+
rank,
|
|
679
|
+
bare: [...kept, leaf].join(":"),
|
|
680
|
+
prefix: kept.map((each) => `${each}:`).join("")
|
|
681
|
+
} : void 0;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
267
684
|
* The styles the config gives an orphaned layer, as the frame's props: the rules that always apply
|
|
268
685
|
* and the ones its classes say are on, in authored order. Only values the frame component declares,
|
|
269
686
|
* spelled as an author writes them, are kept.
|
|
@@ -388,7 +805,8 @@ var Converter = class {
|
|
|
388
805
|
image(img) {
|
|
389
806
|
const src = img.getAttribute("src");
|
|
390
807
|
const role = canvasRole(this.options.config, "image");
|
|
391
|
-
if (!src
|
|
808
|
+
if (!src) return [];
|
|
809
|
+
if (!role) return this.plain(img);
|
|
392
810
|
const props = { ...role.defaultProps };
|
|
393
811
|
const grammar = this.grammar.component(role.component);
|
|
394
812
|
if (grammar) {
|
|
@@ -443,7 +861,7 @@ var Converter = class {
|
|
|
443
861
|
const answer = this.options.icons?.resolve(svgOf(svg));
|
|
444
862
|
if (!answer) {
|
|
445
863
|
this.unresolvedIcons += 1;
|
|
446
|
-
return [];
|
|
864
|
+
return [this.plainSvg(svg)];
|
|
447
865
|
}
|
|
448
866
|
const key = this.key(answer.component);
|
|
449
867
|
this.elements[key] = {
|
|
@@ -461,8 +879,16 @@ var Converter = class {
|
|
|
461
879
|
const props = {};
|
|
462
880
|
const stamped = stampedProps(el);
|
|
463
881
|
if (grammar) this.propsFromClasses(el, grammar, props);
|
|
882
|
+
if (grammar) for (const [prop, raw] of Object.entries(grammar.declared)) {
|
|
883
|
+
if (stamped[prop] !== void 0) continue;
|
|
884
|
+
const declared = raw;
|
|
885
|
+
const attr = declared?.type === "forward" && declared.target?.__ref === "layer:root" && typeof declared.into === "string" ? declared.into : declared?.type === "string" && ROOT_ATTRIBUTE_PROPS.includes(prop) ? prop : void 0;
|
|
886
|
+
const value = attr ? el.getAttribute(attr) : null;
|
|
887
|
+
if (value) props[prop] = value;
|
|
888
|
+
}
|
|
464
889
|
Object.assign(props, stamped);
|
|
465
890
|
if (grammar) this.propsFromInlineStyle(el, grammar, props, path);
|
|
891
|
+
if (grammar) this.propsFromComputed(el, grammar, props, path);
|
|
466
892
|
const layerProps = stampedLayerProps({
|
|
467
893
|
el,
|
|
468
894
|
layers: this.layerNames(path)
|
|
@@ -476,9 +902,14 @@ var Converter = class {
|
|
|
476
902
|
if (answer) for (const [prop, value] of Object.entries(answer.props)) props[prop] ??= value;
|
|
477
903
|
else if (Object.keys(stamped).length === 0) {
|
|
478
904
|
this.unresolvedIcons += 1;
|
|
479
|
-
return;
|
|
905
|
+
return svg ? this.plainSvg(svg) : void 0;
|
|
480
906
|
}
|
|
481
907
|
}
|
|
908
|
+
const glyphProp = this.glyphDrawers.get(path);
|
|
909
|
+
if (glyphProp && props[glyphProp] === void 0) {
|
|
910
|
+
const svg = el.tagName.toLowerCase() === "svg" ? el : el.querySelector("svg");
|
|
911
|
+
if (svg) return this.plainSvg(svg);
|
|
912
|
+
}
|
|
482
913
|
const key = this.key(path);
|
|
483
914
|
const element = {
|
|
484
915
|
type: `component:${path}`,
|
|
@@ -486,8 +917,10 @@ var Converter = class {
|
|
|
486
917
|
};
|
|
487
918
|
const declared = grammar?.declared ?? {};
|
|
488
919
|
const slots = {};
|
|
489
|
-
|
|
490
|
-
|
|
920
|
+
let rootSlotted = false;
|
|
921
|
+
const marked = [...el.hasAttribute("data-uds-slot") ? [el] : [], ...asArray(el.querySelectorAll("[data-uds-slot]"))];
|
|
922
|
+
for (const layer of marked) {
|
|
923
|
+
if (layer !== el && layer.getAttribute("data-uds-layer") === "root" || layer !== el && nearestRoot(layer) !== el || hiddenBetween(layer, el)) continue;
|
|
491
924
|
const names = slotNames(layer).filter((slot) => !grammar || slot in declared);
|
|
492
925
|
const intoOf = (slot) => grammar?.layerProps.get(slot)?.into ?? "children";
|
|
493
926
|
const ownChildren = names.filter((slot) => intoOf(slot) === "children");
|
|
@@ -496,10 +929,13 @@ var Converter = class {
|
|
|
496
929
|
if (into === "children" && slot !== ownChildren.at(-1)) continue;
|
|
497
930
|
const holder = into === "children" ? layer : slotLayerNamed(layer, into);
|
|
498
931
|
if (!holder) continue;
|
|
499
|
-
const
|
|
932
|
+
const declaration = declared[slot];
|
|
933
|
+
const callerMarkup = declaration?.type === "slot" && declaration.text !== true;
|
|
934
|
+
const fills = holder === layer ? this.convertOutsideLayers(layer, path, callerMarkup) : this.convertChildren(holder);
|
|
935
|
+
if (layer === el && holder === layer) rootSlotted = true;
|
|
500
936
|
if (fills.length > 0) slots[slot] = fills;
|
|
501
|
-
else
|
|
502
|
-
const text = collapse(ownText(holder));
|
|
937
|
+
else {
|
|
938
|
+
const text = collapse(holder === layer ? ownText(layer, /* @__PURE__ */ new Set()) : ownText(holder));
|
|
503
939
|
if (text) props[slot] = text;
|
|
504
940
|
}
|
|
505
941
|
}
|
|
@@ -541,7 +977,7 @@ var Converter = class {
|
|
|
541
977
|
}
|
|
542
978
|
const textSlot = declared.children;
|
|
543
979
|
const holdsText = textSlot?.type === "slot" && textSlot.text === true;
|
|
544
|
-
const loose = this.looseContent({
|
|
980
|
+
const loose = rootSlotted ? [] : this.looseContent({
|
|
545
981
|
el,
|
|
546
982
|
claimed,
|
|
547
983
|
takesGlyphs: !renders,
|
|
@@ -574,9 +1010,24 @@ var Converter = class {
|
|
|
574
1010
|
if ("checked" in declared) props.checked = true;
|
|
575
1011
|
else if ("selected" in declared) props.selected = true;
|
|
576
1012
|
}
|
|
577
|
-
if (grammar)
|
|
578
|
-
if (
|
|
579
|
-
|
|
1013
|
+
if (grammar) {
|
|
1014
|
+
if (props.style !== void 0 && !("style" in declared)) {
|
|
1015
|
+
const bags = props["layerProps"] ?? {};
|
|
1016
|
+
const root = bags.root ?? {};
|
|
1017
|
+
bags.root = {
|
|
1018
|
+
...root,
|
|
1019
|
+
style: {
|
|
1020
|
+
...Object.fromEntries(Object.entries(props.style).map(([property, value]) => [property, inlineCss(value)])),
|
|
1021
|
+
...root.style ?? {}
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
props[LAYER_PROPS] = bags;
|
|
1025
|
+
delete props.style;
|
|
1026
|
+
}
|
|
1027
|
+
for (const prop of Object.keys(props)) {
|
|
1028
|
+
if (prop in declared || prop === "aria-label" || prop === "layerProps") continue;
|
|
1029
|
+
delete props[prop];
|
|
1030
|
+
}
|
|
580
1031
|
}
|
|
581
1032
|
this.elements[key] = element;
|
|
582
1033
|
return key;
|
|
@@ -593,11 +1044,7 @@ var Converter = class {
|
|
|
593
1044
|
props[composite.prop] = composite.value;
|
|
594
1045
|
continue;
|
|
595
1046
|
}
|
|
596
|
-
|
|
597
|
-
if (style) {
|
|
598
|
-
const prop = grammar.propForStyleProperty.get(style.path);
|
|
599
|
-
if (prop && props[prop] === void 0) props[prop] = this.frameValue(style.path, style.leaf);
|
|
600
|
-
}
|
|
1047
|
+
this.stylePropsFromClasses([cls], grammar, props);
|
|
601
1048
|
}
|
|
602
1049
|
}
|
|
603
1050
|
/**
|
|
@@ -619,6 +1066,46 @@ var Converter = class {
|
|
|
619
1066
|
propsFromInlineStyle(el, grammar, props, path) {
|
|
620
1067
|
this.propsFromDeclarations(inlineDeclarations(el), grammar, props, path === void 0 ? void 0 : this.ruledProperties(path));
|
|
621
1068
|
}
|
|
1069
|
+
/**
|
|
1070
|
+
* The styles the page's own classes gave a component's root, from what the browser computed, for
|
|
1071
|
+
* the CSS properties the component does not set itself: a stack's `display` and direction are its
|
|
1072
|
+
* own, while the width and flex share an app's Tailwind gave it are the page's, and no prop said
|
|
1073
|
+
* them. A property a rule, a default or a prop already read sets is left to that.
|
|
1074
|
+
*/
|
|
1075
|
+
propsFromComputed(el, grammar, props, path) {
|
|
1076
|
+
const computed = el.getAttribute(COMPUTED_STYLE_ATTR);
|
|
1077
|
+
if (!computed) return;
|
|
1078
|
+
const own = new Set(this.ruledProperties(path));
|
|
1079
|
+
const { defaultProps = {} } = Component.get(this.options.config, path) ?? {};
|
|
1080
|
+
const named = new Set(Object.keys(props));
|
|
1081
|
+
for (const [stylePath, prop] of grammar.propForStyleProperty) {
|
|
1082
|
+
if (!named.has(prop)) continue;
|
|
1083
|
+
for (const property of StyleProperty.get(this.options.config, stylePath)?.properties ?? []) for (const each of longhandsOf(property)) own.add(each);
|
|
1084
|
+
}
|
|
1085
|
+
const fill = {};
|
|
1086
|
+
const unheld = {};
|
|
1087
|
+
for (const declaration of declarationsOf(computed)) {
|
|
1088
|
+
const [property, value] = declaration;
|
|
1089
|
+
if (own.has(property) || own.has(LOGICAL_SIDES[property] ?? property)) continue;
|
|
1090
|
+
const before = Object.keys(fill).length;
|
|
1091
|
+
this.propsFromDeclarations([declaration], grammar, fill);
|
|
1092
|
+
if (Object.keys(fill).length === before && COMPONENT_INLINE_LAYOUT.has(property)) {
|
|
1093
|
+
const arbitrary = this.grammar.stylePropsFor(property).some((option) => option.arbitrary);
|
|
1094
|
+
unheld[camelCase(property)] = arbitrary ? `[${normalizeCssValue(value).replaceAll(" ", "_")}]` : value;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
for (const [prop, value] of Object.entries(fill)) {
|
|
1098
|
+
if (props[prop] !== void 0 || defaultProps[prop] === value) continue;
|
|
1099
|
+
props[prop] = value;
|
|
1100
|
+
}
|
|
1101
|
+
if (Object.keys(unheld).length > 0) {
|
|
1102
|
+
const own = props.style ?? {};
|
|
1103
|
+
props.style = {
|
|
1104
|
+
...unheld,
|
|
1105
|
+
...own
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
622
1109
|
/** The CSS properties a component's own style rules write on its root layer. */
|
|
623
1110
|
ruledProperties(path) {
|
|
624
1111
|
const known = this.ruled.get(path);
|
|
@@ -744,6 +1231,226 @@ const ARTBOARD = [
|
|
|
744
1231
|
];
|
|
745
1232
|
/** How a single-line input lays out its text: centred across its height. */
|
|
746
1233
|
const INPUT_LINE = [["display", "flex"], ["align-items", "center"]];
|
|
1234
|
+
/** Tags the canvas refuses to render, which a capture leaves out. */
|
|
1235
|
+
/** A value in class form (`[304px_auto]`) as the CSS it stands for (`304px auto`); others as they are. */
|
|
1236
|
+
function inlineCss(value) {
|
|
1237
|
+
if (typeof value !== "string" || !/^\[.*\]$/.test(value)) return value;
|
|
1238
|
+
return value.slice(1, -1).replace(/url\([^)]*\)|_/g, (span) => span === "_" ? " " : span);
|
|
1239
|
+
}
|
|
1240
|
+
/** Whether `el` holds more than `limit` elements, counted until the limit is passed. */
|
|
1241
|
+
function holdsMoreThan(el, limit) {
|
|
1242
|
+
let count = 0;
|
|
1243
|
+
const visit = (node) => {
|
|
1244
|
+
for (const child of node.children) {
|
|
1245
|
+
count += 1;
|
|
1246
|
+
if (count > limit || visit(child)) return true;
|
|
1247
|
+
}
|
|
1248
|
+
return false;
|
|
1249
|
+
};
|
|
1250
|
+
return visit(el);
|
|
1251
|
+
}
|
|
1252
|
+
/** The most shapes a drawing the capture keeps as itself may hold. */
|
|
1253
|
+
const SVG_SHAPE_LIMIT = 300;
|
|
1254
|
+
const FORBIDDEN_TAGS = new Set([
|
|
1255
|
+
"script",
|
|
1256
|
+
"style",
|
|
1257
|
+
"noscript",
|
|
1258
|
+
"template",
|
|
1259
|
+
"iframe",
|
|
1260
|
+
"object",
|
|
1261
|
+
"embed",
|
|
1262
|
+
"base",
|
|
1263
|
+
"link",
|
|
1264
|
+
"meta"
|
|
1265
|
+
]);
|
|
1266
|
+
const SIDES = [
|
|
1267
|
+
"top",
|
|
1268
|
+
"right",
|
|
1269
|
+
"bottom",
|
|
1270
|
+
"left"
|
|
1271
|
+
];
|
|
1272
|
+
const CORNERS = [
|
|
1273
|
+
"top-left",
|
|
1274
|
+
"top-right",
|
|
1275
|
+
"bottom-right",
|
|
1276
|
+
"bottom-left"
|
|
1277
|
+
];
|
|
1278
|
+
/** A property with the longhands a shorthand of it sets, physical and logical. */
|
|
1279
|
+
/** Outranks any breakpoint's rank, which is its min-width in pixels. */
|
|
1280
|
+
const IMPORTANT_RANK = 1e6;
|
|
1281
|
+
/** The physical longhands a set of CSS properties writes: `margin-inline` → margin-left, margin-right. */
|
|
1282
|
+
function atomsOf(properties) {
|
|
1283
|
+
const physical = new Map(Object.entries(LOGICAL_SIDES).map(([side, logical]) => [logical, side]));
|
|
1284
|
+
const atoms = /* @__PURE__ */ new Set();
|
|
1285
|
+
for (const property of properties) for (const each of longhandsOf(property)) {
|
|
1286
|
+
const side = physical.get(each) ?? each;
|
|
1287
|
+
if (longhandsOf(side).length <= 1) atoms.add(side);
|
|
1288
|
+
}
|
|
1289
|
+
return atoms;
|
|
1290
|
+
}
|
|
1291
|
+
function longhandsOf(property) {
|
|
1292
|
+
const boxed = /^(padding|margin)(-inline|-block)?$/.exec(property);
|
|
1293
|
+
if (boxed) {
|
|
1294
|
+
const [, box, axis] = boxed;
|
|
1295
|
+
return [property, ...(axis === "-inline" ? ["left", "right"] : axis === "-block" ? ["top", "bottom"] : SIDES).flatMap((side) => [`${box}-${side}`, LOGICAL_SIDES[`${box}-${side}`] ?? ""])].filter(Boolean);
|
|
1296
|
+
}
|
|
1297
|
+
switch (property) {
|
|
1298
|
+
case "inset": return [property, ...SIDES];
|
|
1299
|
+
case "gap": return [
|
|
1300
|
+
property,
|
|
1301
|
+
"row-gap",
|
|
1302
|
+
"column-gap"
|
|
1303
|
+
];
|
|
1304
|
+
case "overflow": return [
|
|
1305
|
+
property,
|
|
1306
|
+
"overflow-x",
|
|
1307
|
+
"overflow-y"
|
|
1308
|
+
];
|
|
1309
|
+
case "border-radius": return [property, ...CORNERS.map((corner) => `border-${corner}-radius`)];
|
|
1310
|
+
case "border-width":
|
|
1311
|
+
case "border-color":
|
|
1312
|
+
case "border-style": {
|
|
1313
|
+
const part = property.slice(7);
|
|
1314
|
+
return [property, ...SIDES.map((side) => `border-${side}-${part}`)];
|
|
1315
|
+
}
|
|
1316
|
+
default: return [property];
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
/** Each physical side's logical name in a left-to-right page. */
|
|
1320
|
+
const LOGICAL_SIDES = {
|
|
1321
|
+
"padding-left": "padding-inline-start",
|
|
1322
|
+
"padding-right": "padding-inline-end",
|
|
1323
|
+
"margin-left": "margin-inline-start",
|
|
1324
|
+
"margin-right": "margin-inline-end",
|
|
1325
|
+
"border-left-width": "border-inline-start-width",
|
|
1326
|
+
"border-right-width": "border-inline-end-width",
|
|
1327
|
+
"border-left-color": "border-inline-start-color",
|
|
1328
|
+
"border-right-color": "border-inline-end-color",
|
|
1329
|
+
left: "inset-inline-start",
|
|
1330
|
+
right: "inset-inline-end",
|
|
1331
|
+
"border-top-left-radius": "border-start-start-radius",
|
|
1332
|
+
"border-top-right-radius": "border-start-end-radius",
|
|
1333
|
+
"border-bottom-left-radius": "border-end-start-radius",
|
|
1334
|
+
"border-bottom-right-radius": "border-end-end-radius"
|
|
1335
|
+
};
|
|
1336
|
+
/** The renderer's `VERBATIM_ATTRIBUTES` key (`renderer/wrappers/html-aliases`), spelled here so the
|
|
1337
|
+
* capture does not load the React renderer. */
|
|
1338
|
+
const VERBATIM_ATTRIBUTES = "__verbatimAttributes";
|
|
1339
|
+
/** SVG attributes a copied drawing leaves out: script handlers, ids and references to definitions. */
|
|
1340
|
+
const SVG_SKIPPED_ATTRIBUTES = /^(on|data-|id$|clip-path$|mask$|filter$|xlink:href$|href$)/;
|
|
1341
|
+
/** SVG elements a copied drawing leaves out: its definitions, and what holds only them. */
|
|
1342
|
+
const SVG_SKIPPED_TAGS = new Set([
|
|
1343
|
+
"defs",
|
|
1344
|
+
"clippath",
|
|
1345
|
+
"mask",
|
|
1346
|
+
"lineargradient",
|
|
1347
|
+
"radialgradient",
|
|
1348
|
+
"filter",
|
|
1349
|
+
"pattern",
|
|
1350
|
+
"symbol",
|
|
1351
|
+
"script",
|
|
1352
|
+
"style",
|
|
1353
|
+
"title",
|
|
1354
|
+
"desc",
|
|
1355
|
+
"use"
|
|
1356
|
+
]);
|
|
1357
|
+
/** SVG tags whose case the captured tree lost. */
|
|
1358
|
+
const SVG_TAGS = new Map([["foreignobject", "foreignObject"]]);
|
|
1359
|
+
/** An SVG attribute under the name React takes it by: `fill-rule` is `fillRule`, `viewBox` stays. */
|
|
1360
|
+
function svgAttributeName(name) {
|
|
1361
|
+
if (name === "viewbox") return "viewBox";
|
|
1362
|
+
if (name.startsWith("aria-") || name === "role") return name;
|
|
1363
|
+
if (name === "xmlns" || name === "xmlns:xlink") return name === "xmlns" ? "xmlns" : "xmlnsXlink";
|
|
1364
|
+
return name.replace(/[-:]([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1365
|
+
}
|
|
1366
|
+
/** The layout a component's root may need inline when none of its props can say it. */
|
|
1367
|
+
const COMPONENT_INLINE_LAYOUT = new Set([
|
|
1368
|
+
"grid-template-columns",
|
|
1369
|
+
"grid-template-rows",
|
|
1370
|
+
"grid-column",
|
|
1371
|
+
"grid-row",
|
|
1372
|
+
"white-space",
|
|
1373
|
+
"aspect-ratio",
|
|
1374
|
+
"flex-basis",
|
|
1375
|
+
"flex-grow",
|
|
1376
|
+
"flex-shrink",
|
|
1377
|
+
"max-width",
|
|
1378
|
+
"min-width"
|
|
1379
|
+
]);
|
|
1380
|
+
/** The width a capture is read at when it states none, which settles its breakpoints. */
|
|
1381
|
+
const DEFAULT_CAPTURE_WIDTH = 1600;
|
|
1382
|
+
/** The attributes a component's root renders a string prop of the same name as. */
|
|
1383
|
+
const ROOT_ATTRIBUTE_PROPS = [
|
|
1384
|
+
"src",
|
|
1385
|
+
"alt",
|
|
1386
|
+
"href",
|
|
1387
|
+
"title",
|
|
1388
|
+
"placeholder"
|
|
1389
|
+
];
|
|
1390
|
+
/** The CSS properties that draw something of an element's own. */
|
|
1391
|
+
const PAINT_PROPERTY = /^(background|border-(top-|right-|bottom-|left-)?(width|color)|box-shadow|outline)/;
|
|
1392
|
+
/** Tags that only group what they hold, and fold away when they carry nothing of their own. */
|
|
1393
|
+
const GROUPING_TAGS = new Set(["div", "span"]);
|
|
1394
|
+
/** Tags with no content. */
|
|
1395
|
+
const VOID_TAGS = new Set([
|
|
1396
|
+
"img",
|
|
1397
|
+
"input",
|
|
1398
|
+
"br",
|
|
1399
|
+
"hr",
|
|
1400
|
+
"wbr",
|
|
1401
|
+
"area",
|
|
1402
|
+
"col",
|
|
1403
|
+
"source",
|
|
1404
|
+
"track"
|
|
1405
|
+
]);
|
|
1406
|
+
/** The attributes a plain element keeps, as the props React takes them under. */
|
|
1407
|
+
const PLAIN_ATTRIBUTES = {
|
|
1408
|
+
href: "href",
|
|
1409
|
+
src: "src",
|
|
1410
|
+
alt: "alt",
|
|
1411
|
+
title: "title",
|
|
1412
|
+
role: "role",
|
|
1413
|
+
type: "type",
|
|
1414
|
+
placeholder: "placeholder",
|
|
1415
|
+
name: "name",
|
|
1416
|
+
target: "target",
|
|
1417
|
+
rel: "rel",
|
|
1418
|
+
colspan: "colSpan",
|
|
1419
|
+
rowspan: "rowSpan",
|
|
1420
|
+
for: "htmlFor"
|
|
1421
|
+
};
|
|
1422
|
+
function plainAttributes(el) {
|
|
1423
|
+
const out = {};
|
|
1424
|
+
const tag = el.tagName.toLowerCase();
|
|
1425
|
+
for (const [attr, prop] of Object.entries(PLAIN_ATTRIBUTES)) {
|
|
1426
|
+
const value = el.getAttribute(attr);
|
|
1427
|
+
if (value === null || value === void 0) continue;
|
|
1428
|
+
if ((attr === "href" || attr === "src") && /^\s*javascript:/i.test(value)) continue;
|
|
1429
|
+
out[prop] = value;
|
|
1430
|
+
}
|
|
1431
|
+
for (const { name, value } of el.attributes) if (name.startsWith("aria-")) out[name] = value;
|
|
1432
|
+
if (tag === "input" || tag === "textarea") {
|
|
1433
|
+
const value = tag === "textarea" ? el.textContent ?? "" : el.getAttribute("value");
|
|
1434
|
+
if (value) out.defaultValue = value;
|
|
1435
|
+
}
|
|
1436
|
+
return out;
|
|
1437
|
+
}
|
|
1438
|
+
/** The text an element holds directly, its whitespace collapsed; empty for a field. */
|
|
1439
|
+
/** An element's text as it showed: what a hidden descendant (a tooltip, `hidden`) holds is left out. */
|
|
1440
|
+
function plainText(el) {
|
|
1441
|
+
if (isField(el)) return "";
|
|
1442
|
+
let text = "";
|
|
1443
|
+
const read = (node) => {
|
|
1444
|
+
for (const child of node.childNodes) if (child.nodeType === 3) text += child.textContent ?? "";
|
|
1445
|
+
else if (child.nodeType === 1 && visible(child)) read(child);
|
|
1446
|
+
};
|
|
1447
|
+
read(el);
|
|
1448
|
+
return text.replace(/\s+/g, " ").trim();
|
|
1449
|
+
}
|
|
1450
|
+
function camelCase(property) {
|
|
1451
|
+
if (property.startsWith("--")) return property;
|
|
1452
|
+
return property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1453
|
+
}
|
|
747
1454
|
function isImage(el) {
|
|
748
1455
|
return el.tagName.toLowerCase() === "img" && !el.hasAttribute("data-uds-component");
|
|
749
1456
|
}
|
|
@@ -816,8 +1523,12 @@ function paintsItself(el) {
|
|
|
816
1523
|
}
|
|
817
1524
|
/** An element's inline declarations as `[property, value]` pairs. */
|
|
818
1525
|
function inlineDeclarations(el) {
|
|
1526
|
+
return declarationsOf(el.getAttribute("style") ?? "");
|
|
1527
|
+
}
|
|
1528
|
+
/** A declaration list (`display: flex; gap: 8px`) as property and value pairs. */
|
|
1529
|
+
function declarationsOf(text) {
|
|
819
1530
|
const out = [];
|
|
820
|
-
for (const declaration of
|
|
1531
|
+
for (const declaration of text.split(";")) {
|
|
821
1532
|
const at = declaration.indexOf(":");
|
|
822
1533
|
if (at < 0) continue;
|
|
823
1534
|
const property = declaration.slice(0, at).trim().toLowerCase();
|
|
@@ -841,6 +1552,8 @@ function negated(value) {
|
|
|
841
1552
|
*/
|
|
842
1553
|
function equivalents({ property, value }) {
|
|
843
1554
|
if ((property === "grid-column" || property === "grid-row") && /^span \d+$/.test(value)) return [value, `${value} / ${value}`];
|
|
1555
|
+
const px = /^(-?(?:\d+(?:\.\d+)?|\.\d+))px$/.exec(value);
|
|
1556
|
+
if (px) return [value, `${Number(px[1]) / 16}rem`];
|
|
844
1557
|
return [value];
|
|
845
1558
|
}
|
|
846
1559
|
/**
|