@yahoo/uds-create-config 5.64.1 → 5.64.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/dist/entities/system/LinkedSystem.js +2 -1
- package/dist/entities/system/component-module.js +2 -1
- package/dist/entities/system/link-plan.d.ts +10 -0
- package/dist/entities/system/link-plan.js +22 -4
- package/dist/framework/Config.d.ts +35 -0
- package/dist/framework/Config.js +106 -1
- package/dist/framework/projections.js +1 -1
- package/dist/framework/source-integrity.d.ts +5 -0
- package/dist/framework/source-integrity.js +28 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -316,10 +316,11 @@ const LinkedSystemDefinition = defineEntity({
|
|
|
316
316
|
title: (entity) => `Link something from ${entity}`,
|
|
317
317
|
example: linkExample,
|
|
318
318
|
handler: (input, config) => {
|
|
319
|
-
const { plan: { linked, patches }, after } = planLinkApplied({
|
|
319
|
+
const { plan: { linked, patches, stranded, blocked }, after } = planLinkApplied({
|
|
320
320
|
config,
|
|
321
321
|
input: linkPlanInputOf(input)
|
|
322
322
|
});
|
|
323
|
+
if (stranded && stranded.length > 0 && blocked) rejectData(blocked, ["onLinked"]);
|
|
323
324
|
if (linked.length > 0 && input.onLinked === void 0) {
|
|
324
325
|
const named = linked.map((at) => `${at.path} (from ${at.from})`);
|
|
325
326
|
rejectData(`Linking through "${String(input.path)}" reaches what another linked system provides: ${named.join(", ")}. Say whether to take them (\`onLinked: 'take'\`) or keep them where they come from (\`onLinked: 'keep'\`).`, ["onLinked"]);
|
|
@@ -419,7 +419,8 @@ function linkedComponentModule(config, componentPathOrRef) {
|
|
|
419
419
|
const owner = config.configFor(fileRef);
|
|
420
420
|
const pin = publishedPin(config, slug);
|
|
421
421
|
if (!owner || !pin) return void 0;
|
|
422
|
-
const
|
|
422
|
+
const shadowed = askedSource !== void 0;
|
|
423
|
+
const view = cachedView(config, (shadowed && owner === config.sourceFor(slug) ? config.drawnSourceFor(slug) : void 0) ?? owner, slug, shadowed);
|
|
423
424
|
const moduleKey = componentModuleKey(view, filePath);
|
|
424
425
|
if (!moduleKey) return void 0;
|
|
425
426
|
const authored = Component.list(owner).find((candidate) => {
|
|
@@ -29,6 +29,12 @@ interface DroppedDirective extends EntityAddress {
|
|
|
29
29
|
/** The accessor the entry sits under in that pin's `options`. */
|
|
30
30
|
readonly accessor: string;
|
|
31
31
|
}
|
|
32
|
+
/** An entry in another pin's `options` a take would leave naming something that pin's own copy can't
|
|
33
|
+
* be drawn with ({@link Config.pinCopyOf}). The take refuses rather than remove it. */
|
|
34
|
+
interface StrandedDirective extends DroppedDirective {
|
|
35
|
+
/** The refs the entry names that the pin's copy has no name for. */
|
|
36
|
+
readonly unreadable: readonly string[];
|
|
37
|
+
}
|
|
32
38
|
/** One row a link mints. `named` when a path the call named covers it; otherwise it comes along
|
|
33
39
|
* because something linked points at it. */
|
|
34
40
|
interface LinkArrival extends EntityAddress {
|
|
@@ -51,6 +57,10 @@ interface LinkPlan {
|
|
|
51
57
|
* rows their pin derives, and the row there follows this one now. Not carried onto the taken row,
|
|
52
58
|
* since an entry corrects what that pin brings rather than stating a value of this config's own. */
|
|
53
59
|
readonly dropped: readonly DroppedDirective[];
|
|
60
|
+
/** The other pins' entries at what the take moves that it can't carry: each lays over a copy that
|
|
61
|
+
* pin still builds, and names something the copy can't be drawn with. The take refuses while any
|
|
62
|
+
* remain, and `blocked` says what to change. */
|
|
63
|
+
readonly stranded?: readonly StrandedDirective[];
|
|
54
64
|
/** Members already here from the source's own origin, left alone and kept out of the declaration. */
|
|
55
65
|
readonly shared: readonly EntityAddress[];
|
|
56
66
|
/** Paths another pin offers that a `hybrid` link reaches as a different entity. The other pin's
|
|
@@ -267,12 +267,13 @@ function planLinkApplied({ config, input }) {
|
|
|
267
267
|
];
|
|
268
268
|
let result = planned.length > 0 ? applied(config, planned) : void 0;
|
|
269
269
|
if (takes.length > 0 && result && "failed" in result) throw result.failed;
|
|
270
|
-
const { dropped, patches: drops } = takes.length > 0 && result && "config" in result ? directivesTakenFrom({
|
|
270
|
+
const { dropped, stranded, patches: drops } = takes.length > 0 && result && "config" in result ? directivesTakenFrom({
|
|
271
271
|
config,
|
|
272
272
|
slug,
|
|
273
273
|
after: result.config
|
|
274
274
|
}) : {
|
|
275
275
|
dropped: [],
|
|
276
|
+
stranded: [],
|
|
276
277
|
patches: []
|
|
277
278
|
};
|
|
278
279
|
if (drops.length > 0 && result && "config" in result) result = applied(result.config, drops);
|
|
@@ -292,9 +293,13 @@ function planLinkApplied({ config, input }) {
|
|
|
292
293
|
extended,
|
|
293
294
|
dropped,
|
|
294
295
|
shared,
|
|
295
|
-
patches
|
|
296
|
+
patches,
|
|
297
|
+
...stranded.length > 0 ? {
|
|
298
|
+
stranded,
|
|
299
|
+
blocked: strandedMessage(slug, stranded)
|
|
300
|
+
} : {}
|
|
296
301
|
},
|
|
297
|
-
after: result && "config" in result ? result.config : void 0
|
|
302
|
+
after: stranded.length === 0 && result && "config" in result ? result.config : void 0
|
|
298
303
|
};
|
|
299
304
|
}
|
|
300
305
|
/**
|
|
@@ -314,14 +319,20 @@ function directivesTakenFrom({ config, slug, after }) {
|
|
|
314
319
|
const key = (at) => `${at.slug}\u0000${at.accessor}\u0000${at.path}`;
|
|
315
320
|
const already = new Set(danglingDirectives(config).map(key));
|
|
316
321
|
const dropped = [];
|
|
322
|
+
const stranded = [];
|
|
317
323
|
for (const at of danglingDirectives(after)) {
|
|
318
324
|
if (at.missing !== "row" || at.heldBy !== slug || already.has(key(at))) continue;
|
|
319
|
-
|
|
325
|
+
const entry = {
|
|
320
326
|
kind: config.accessors()[at.accessor]?.kind ?? at.accessor,
|
|
321
327
|
path: at.path,
|
|
322
328
|
from: at.slug,
|
|
323
329
|
accessor: at.accessor
|
|
330
|
+
};
|
|
331
|
+
if (at.unreadable) stranded.push({
|
|
332
|
+
...entry,
|
|
333
|
+
unreadable: at.unreadable
|
|
324
334
|
});
|
|
335
|
+
else dropped.push(entry);
|
|
325
336
|
}
|
|
326
337
|
const edits = [];
|
|
327
338
|
for (const pin of new Set(dropped.map((at) => at.from))) {
|
|
@@ -347,9 +358,16 @@ function directivesTakenFrom({ config, slug, after }) {
|
|
|
347
358
|
}
|
|
348
359
|
return {
|
|
349
360
|
dropped,
|
|
361
|
+
stranded,
|
|
350
362
|
patches: edits
|
|
351
363
|
};
|
|
352
364
|
}
|
|
365
|
+
/** Why a take refuses over the entries it can't carry ({@link StrandedDirective}). */
|
|
366
|
+
function strandedMessage(slug, stranded) {
|
|
367
|
+
const named = stranded.map((at) => `${at.from}'s ${at.accessor} entry at "${at.path}" names ${at.unreadable.join(", ")}`).join("; ");
|
|
368
|
+
const [one] = stranded;
|
|
369
|
+
return `Can't take through "${slug}": ${named}. ${stranded.length === 1 && one ? `${one.from} still builds its own ${one.path}, which its components render, and that entry applies to it` : "each pin still builds its own copy, which its components render, and the entry applies to it"}, but Create can draw that copy only with this system's own tokens, not yet with this system's own components, composites or motion presets. Point those values at something the pin links, or remove them from the entry, then take again.`;
|
|
370
|
+
}
|
|
353
371
|
/** What a plan reports of a refusal: its message, and the collisions when that's what it names. */
|
|
354
372
|
function refusal(error) {
|
|
355
373
|
const collisions = error.issues.flatMap((issue) => issue.collisions ?? []);
|
|
@@ -2460,6 +2460,41 @@ declare class Config {
|
|
|
2460
2460
|
kind: string;
|
|
2461
2461
|
path: string;
|
|
2462
2462
|
}): boolean;
|
|
2463
|
+
/**
|
|
2464
|
+
* The source at `slug` as this build draws the copies it still builds of its components that another
|
|
2465
|
+
* row stands in for here, each drawn the way the pin's own row was: with the entry the pin's
|
|
2466
|
+
* `options` lay at its path ({@link pinCopyOf}).
|
|
2467
|
+
*
|
|
2468
|
+
* A product that takes Yahoo OS's Button still builds the Bridge's, which the Bridge's Banner
|
|
2469
|
+
* renders, and the Bridge pin's entry for its Button is what that copy is drawn with. Everything else
|
|
2470
|
+
* is the source's own, so the source itself comes back when no copy carries an entry.
|
|
2471
|
+
*/
|
|
2472
|
+
drawnSourceFor(slug: string): Config | undefined;
|
|
2473
|
+
/**
|
|
2474
|
+
* The body `slug`'s copy of `kind:path` is drawn with, spelled as the source spells it: the source's
|
|
2475
|
+
* entity with the entry the pin's `options` lay at the path, read as the pin's row would read it
|
|
2476
|
+
* here. `undefined` when this config holds the pin's row at the path (the row is what's drawn), holds
|
|
2477
|
+
* nothing there, or the pin lays nothing over it.
|
|
2478
|
+
*
|
|
2479
|
+
* A token it names that the source has no name for (one of this config's own, or one it takes
|
|
2480
|
+
* through another pin) is named under {@link CONSUMER_TOKENS} instead, and `tokens` gives each such
|
|
2481
|
+
* path the variable this config's stylesheet declares for it. `unreadable` names any other ref the
|
|
2482
|
+
* source has no name for: the copy can't be drawn with it, so the entry is reported instead.
|
|
2483
|
+
*/
|
|
2484
|
+
pinCopyOf({
|
|
2485
|
+
slug,
|
|
2486
|
+
kind,
|
|
2487
|
+
path
|
|
2488
|
+
}: {
|
|
2489
|
+
slug: string;
|
|
2490
|
+
kind: string;
|
|
2491
|
+
path: string;
|
|
2492
|
+
}): {
|
|
2493
|
+
readonly body: Record<string, unknown>;
|
|
2494
|
+
readonly tokens: ReadonlyMap<string, string>;
|
|
2495
|
+
} | {
|
|
2496
|
+
readonly unreadable: readonly string[];
|
|
2497
|
+
} | undefined;
|
|
2463
2498
|
/**
|
|
2464
2499
|
* The group counterpart of {@link heldThroughAnotherPin}: this config holds the group at `path`
|
|
2465
2500
|
* through another pin, and every member `slug` offers under it is held that way too. A group is a
|
package/dist/framework/Config.js
CHANGED
|
@@ -9,7 +9,7 @@ import { isComposedValue } from "../entities/system/composed-values.js";
|
|
|
9
9
|
import { borrowedSlug, classifyOverlay, uniteEntries } from "./source-integrity.js";
|
|
10
10
|
import { kindBorrowable, titleCase } from "./registry.js";
|
|
11
11
|
import { isGroupBody } from "./utils/group.js";
|
|
12
|
-
import { authoredBag, pinBodyStyleNumbers, stylePropNumberUnit } from "./projections.js";
|
|
12
|
+
import { authoredBag, cssVar, declaresCssVariable, pinBodyStyleNumbers, stylePropNumberUnit } from "./projections.js";
|
|
13
13
|
import { isAuthoringPhantom, phantomsIn } from "./utils/authoring-phantom.js";
|
|
14
14
|
import { addressOf, editOp, inputOf, opVerb } from "./config-op.js";
|
|
15
15
|
import { configSchemas } from "./config-ops.js";
|
|
@@ -696,6 +696,13 @@ function objectSchemaOf(schema) {
|
|
|
696
696
|
* of {@link Config.sourceClosure}. Per config, since a source's files and components only change
|
|
697
697
|
* with the source. */
|
|
698
698
|
const implementedByCache = /* @__PURE__ */ new WeakMap();
|
|
699
|
+
/**
|
|
700
|
+
* The folder a source's drawn copy ({@link Config.drawnSourceFor}) names this config's own tokens
|
|
701
|
+
* under. Not a path a system authors, so it never stands for one of the source's.
|
|
702
|
+
*/
|
|
703
|
+
const CONSUMER_TOKENS = "__consumer";
|
|
704
|
+
/** {@link Config.drawnSourceFor} per config and slug. */
|
|
705
|
+
const drawnSources = /* @__PURE__ */ new WeakMap();
|
|
699
706
|
function implementedBy(config) {
|
|
700
707
|
const cached = implementedByCache.get(config);
|
|
701
708
|
if (cached) return cached;
|
|
@@ -5009,6 +5016,104 @@ var Config = class Config {
|
|
|
5009
5016
|
});
|
|
5010
5017
|
}
|
|
5011
5018
|
/**
|
|
5019
|
+
* The source at `slug` as this build draws the copies it still builds of its components that another
|
|
5020
|
+
* row stands in for here, each drawn the way the pin's own row was: with the entry the pin's
|
|
5021
|
+
* `options` lay at its path ({@link pinCopyOf}).
|
|
5022
|
+
*
|
|
5023
|
+
* A product that takes Yahoo OS's Button still builds the Bridge's, which the Bridge's Banner
|
|
5024
|
+
* renders, and the Bridge pin's entry for its Button is what that copy is drawn with. Everything else
|
|
5025
|
+
* is the source's own, so the source itself comes back when no copy carries an entry.
|
|
5026
|
+
*/
|
|
5027
|
+
drawnSourceFor(slug) {
|
|
5028
|
+
const source = this.sourceFor(slug);
|
|
5029
|
+
if (!source) return void 0;
|
|
5030
|
+
let bySlug = drawnSources.get(this);
|
|
5031
|
+
if (!bySlug) {
|
|
5032
|
+
bySlug = /* @__PURE__ */ new Map();
|
|
5033
|
+
drawnSources.set(this, bySlug);
|
|
5034
|
+
}
|
|
5035
|
+
const cached = bySlug.get(slug);
|
|
5036
|
+
if (cached) return cached;
|
|
5037
|
+
const changes = [];
|
|
5038
|
+
for (const { kind, path } of this.sourceDirectives(slug)) {
|
|
5039
|
+
const entry = kind === void 0 ? void 0 : this.entityOf(kind);
|
|
5040
|
+
if (kind !== "component" || !entry) continue;
|
|
5041
|
+
const copy = this.pinCopyOf({
|
|
5042
|
+
slug,
|
|
5043
|
+
kind,
|
|
5044
|
+
path
|
|
5045
|
+
});
|
|
5046
|
+
if (!copy || !("body" in copy)) continue;
|
|
5047
|
+
changes.push({
|
|
5048
|
+
kind,
|
|
5049
|
+
target: "item",
|
|
5050
|
+
path,
|
|
5051
|
+
value: new Entity(path, copy.body, entry.fieldKeys)
|
|
5052
|
+
});
|
|
5053
|
+
const tokenEntry = this.entityOf("token");
|
|
5054
|
+
for (const [at, cssVariable] of copy.tokens) {
|
|
5055
|
+
if (!tokenEntry) break;
|
|
5056
|
+
changes.push({
|
|
5057
|
+
kind: "token",
|
|
5058
|
+
target: "item",
|
|
5059
|
+
path: at,
|
|
5060
|
+
value: new Entity(at, { cssVariable }, tokenEntry.fieldKeys)
|
|
5061
|
+
});
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
const drawn = changes.length === 0 ? source : source.derive({ snapshot: source.state.snapshot.withChanges(changes) });
|
|
5065
|
+
bySlug.set(slug, drawn);
|
|
5066
|
+
return drawn;
|
|
5067
|
+
}
|
|
5068
|
+
/**
|
|
5069
|
+
* The body `slug`'s copy of `kind:path` is drawn with, spelled as the source spells it: the source's
|
|
5070
|
+
* entity with the entry the pin's `options` lay at the path, read as the pin's row would read it
|
|
5071
|
+
* here. `undefined` when this config holds the pin's row at the path (the row is what's drawn), holds
|
|
5072
|
+
* nothing there, or the pin lays nothing over it.
|
|
5073
|
+
*
|
|
5074
|
+
* A token it names that the source has no name for (one of this config's own, or one it takes
|
|
5075
|
+
* through another pin) is named under {@link CONSUMER_TOKENS} instead, and `tokens` gives each such
|
|
5076
|
+
* path the variable this config's stylesheet declares for it. `unreadable` names any other ref the
|
|
5077
|
+
* source has no name for: the copy can't be drawn with it, so the entry is reported instead.
|
|
5078
|
+
*/
|
|
5079
|
+
pinCopyOf({ slug, kind, path }) {
|
|
5080
|
+
const stored = this.storedBody(kind, path);
|
|
5081
|
+
if (stored === void 0 || isRef(stored) && sourceOf(stored) === slug) return;
|
|
5082
|
+
const overlay = this.sourceDirective(slug, kind, path).overlay;
|
|
5083
|
+
const source = this.sourceFor(slug);
|
|
5084
|
+
const theirs = this.theirBody({
|
|
5085
|
+
slug,
|
|
5086
|
+
kind,
|
|
5087
|
+
path
|
|
5088
|
+
});
|
|
5089
|
+
if (!overlay || !source || !theirs) return void 0;
|
|
5090
|
+
const read = this.derive({ snapshot: this.state.snapshot.withChanges([derivedItemChange(kind, path, ref(kind, path, slug).__ref, overlayBody({
|
|
5091
|
+
overlay,
|
|
5092
|
+
theirs
|
|
5093
|
+
}))]) }).resolve(kind, path)?.toJSON();
|
|
5094
|
+
if (!read) return void 0;
|
|
5095
|
+
const owned = new Set(source.ownedKinds());
|
|
5096
|
+
const unreadable = [...new Set(collectRefs(read).filter((target) => sourceOf(target) === void 0 && owned.has(kindOf(target)) && pathOf(target) !== "" && !this.borrowsExactly({ __ref: target }, slug)))];
|
|
5097
|
+
let body = read;
|
|
5098
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
5099
|
+
const others = [];
|
|
5100
|
+
for (const target of unreadable) {
|
|
5101
|
+
const path = pathOf(target);
|
|
5102
|
+
if (kindOf(target) !== "token" || !declaresCssVariable(this, path)) {
|
|
5103
|
+
others.push(target);
|
|
5104
|
+
continue;
|
|
5105
|
+
}
|
|
5106
|
+
const at = `${CONSUMER_TOKENS}/${path}`;
|
|
5107
|
+
body = rewriteRefs(body, target, ref("token", at).__ref);
|
|
5108
|
+
tokens.set(at, cssVar(this, "token", path));
|
|
5109
|
+
}
|
|
5110
|
+
if (others.length > 0) return { unreadable: others };
|
|
5111
|
+
return {
|
|
5112
|
+
body: rewriteRefSource(body, slug, void 0),
|
|
5113
|
+
tokens
|
|
5114
|
+
};
|
|
5115
|
+
}
|
|
5116
|
+
/**
|
|
5012
5117
|
* The group counterpart of {@link heldThroughAnotherPin}: this config holds the group at `path`
|
|
5013
5118
|
* through another pin, and every member `slug` offers under it is held that way too. A group is a
|
|
5014
5119
|
* folder over its members, so it is the same folder exactly when they are the same entities.
|
|
@@ -80,7 +80,7 @@ const sourceVarPrefix = (config, slug) => prefixed({
|
|
|
80
80
|
*/
|
|
81
81
|
function linkedVarNaming(config, slug) {
|
|
82
82
|
const pinned = sourceVarPrefix(config, slug);
|
|
83
|
-
const source = config.
|
|
83
|
+
const source = config.drawnSourceFor(slug);
|
|
84
84
|
const named = /* @__PURE__ */ new Map();
|
|
85
85
|
return (tokenPath) => {
|
|
86
86
|
const hit = named.get(tokenPath);
|
|
@@ -249,6 +249,11 @@ interface DanglingDirective {
|
|
|
249
249
|
missing: 'accessor' | 'path' | 'row';
|
|
250
250
|
/** With `row`: the pin the row there follows instead, absent when it is this config's own. */
|
|
251
251
|
heldBy?: string;
|
|
252
|
+
/**
|
|
253
|
+
* With `row`, for a component the pin still builds its own copy of beside that row: the refs the
|
|
254
|
+
* entry names that the source has no name for, so the copy can't be drawn with it.
|
|
255
|
+
*/
|
|
256
|
+
unreadable?: readonly string[];
|
|
252
257
|
}
|
|
253
258
|
/**
|
|
254
259
|
* Every directive aimed at nothing — an accessor this config has no kind for, a path the source
|
|
@@ -534,6 +534,15 @@ function heldElsewhere(config, { slug, kind, path }) {
|
|
|
534
534
|
*/
|
|
535
535
|
function danglingDirectives(config) {
|
|
536
536
|
const found = [];
|
|
537
|
+
const reached = /* @__PURE__ */ new Map();
|
|
538
|
+
const reachedComponents = (slug) => {
|
|
539
|
+
let paths = reached.get(slug);
|
|
540
|
+
if (!paths) {
|
|
541
|
+
paths = new Set(config.sourceClosure(slug, "component").map(pathOf));
|
|
542
|
+
reached.set(slug, paths);
|
|
543
|
+
}
|
|
544
|
+
return paths;
|
|
545
|
+
};
|
|
537
546
|
for (const slug of config.sourceSlugs()) {
|
|
538
547
|
const source = config.liveSourceFor(slug);
|
|
539
548
|
if (!source) continue;
|
|
@@ -555,6 +564,25 @@ function danglingDirectives(config) {
|
|
|
555
564
|
path
|
|
556
565
|
});
|
|
557
566
|
if (heldBy === void 0) continue;
|
|
567
|
+
if (kind === "component" && reachedComponents(slug).has(path)) {
|
|
568
|
+
const copy = config.pinCopyOf({
|
|
569
|
+
slug,
|
|
570
|
+
kind,
|
|
571
|
+
path
|
|
572
|
+
});
|
|
573
|
+
if (copy && "unreadable" in copy) {
|
|
574
|
+
found.push({
|
|
575
|
+
slug,
|
|
576
|
+
accessor,
|
|
577
|
+
path,
|
|
578
|
+
missing: "row",
|
|
579
|
+
...heldBy === null ? {} : { heldBy },
|
|
580
|
+
unreadable: copy.unreadable
|
|
581
|
+
});
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (copy) continue;
|
|
585
|
+
}
|
|
558
586
|
found.push({
|
|
559
587
|
slug,
|
|
560
588
|
accessor,
|