@agent-native/core 0.138.0 → 0.140.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +2 -0
- package/corpus/templates/analytics/server/lib/emails.ts +51 -0
- package/corpus/templates/analytics/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/calendar/server/lib/booking-emails.ts +154 -70
- package/corpus/templates/calendar/server/lib/emails.ts +167 -0
- package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +64 -28
- package/corpus/templates/calendar/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/clips/actions/invite-member.ts +39 -15
- package/corpus/templates/clips/server/lib/emails.ts +256 -0
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +24 -0
- package/corpus/templates/clips/server/plugins/transactional-email-catalog.ts +5 -0
- package/corpus/templates/content/server/lib/comment-notifications.ts +57 -26
- package/corpus/templates/content/server/lib/emails.ts +70 -0
- package/corpus/templates/content/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +44 -1
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +265 -1
- package/corpus/templates/design/actions/create-design-system.ts +9 -10
- package/corpus/templates/design/actions/get-design-system.ts +53 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +5 -71
- package/corpus/templates/design/actions/index-design-tokens.ts +61 -80
- package/corpus/templates/design/actions/update-design-system.ts +7 -10
- package/corpus/templates/design/app/components/design/EditPanel.tsx +45 -2
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +12 -0
- package/corpus/templates/design/app/components/design/edit-panel/design-system-swatches.ts +215 -0
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +10 -6
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +19 -3
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +36 -10
- package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +15 -0
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +19 -1
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +6 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +53 -10
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +142 -4
- package/corpus/templates/design/app/components/design/types.ts +6 -0
- package/corpus/templates/design/app/i18n-data.ts +20 -0
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +1 -1
- package/corpus/templates/design/app/pages/DesignSystems.tsx +78 -1
- package/corpus/templates/design/app/pages/design-editor/html-layer-positioning.ts +6 -1
- package/corpus/templates/design/changelog/2026-08-04-design-system-tokens-show-by-name-in-the-editor.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +17 -0
- package/corpus/templates/design/server/lib/design-system-tokens.ts +35 -0
- package/corpus/templates/design/shared/authored-color-styles.ts +401 -0
- package/corpus/templates/design/shared/resolve-tweaks.ts +10 -18
- package/corpus/templates/dispatch/app/routes/transactional-email.$appId.$id.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/transactional-email.tsx +4 -0
- package/corpus/templates/forms/server/lib/emails.ts +52 -0
- package/corpus/templates/forms/server/lib/response-email.ts +2 -0
- package/corpus/templates/forms/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/plan/actions/request-plan-access.ts +33 -12
- package/corpus/templates/plan/server/lib/comment-notifications.ts +53 -27
- package/corpus/templates/plan/server/lib/emails.ts +67 -0
- package/corpus/templates/plan/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/slides/server/lib/comment-notifications.ts +44 -21
- package/corpus/templates/slides/server/lib/emails.ts +40 -0
- package/corpus/templates/slides/server/plugins/transactional-emails.ts +5 -0
- package/dist/brand-kit/index.d.ts +1 -0
- package/dist/brand-kit/index.js +1 -0
- package/dist/brand-kit/tokens.d.ts +92 -0
- package/dist/brand-kit/tokens.js +280 -0
- package/dist/brand-kit/types.d.ts +25 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +2 -2
- package/dist/email-catalog/actions/list-email-activity.d.ts +6 -0
- package/dist/email-catalog/actions/list-email-activity.js +15 -0
- package/dist/email-catalog/actions/list-email-engagement.d.ts +6 -0
- package/dist/email-catalog/actions/list-email-engagement.js +15 -0
- package/dist/email-catalog/actions/list-email-log.d.ts +8 -0
- package/dist/email-catalog/actions/list-email-log.js +22 -0
- package/dist/email-catalog/actions/list-transactional-emails.d.ts +23 -0
- package/dist/email-catalog/actions/list-transactional-emails.js +61 -0
- package/dist/email-catalog/actions/render-transactional-email-preview.d.ts +10 -0
- package/dist/email-catalog/actions/render-transactional-email-preview.js +24 -0
- package/dist/email-catalog/authorize.d.ts +2 -0
- package/dist/email-catalog/authorize.js +10 -0
- package/dist/email-catalog/log.d.ts +57 -0
- package/dist/email-catalog/log.js +117 -0
- package/dist/email-catalog/provider-metrics.d.ts +34 -0
- package/dist/email-catalog/provider-metrics.js +165 -0
- package/dist/email-catalog/registry.d.ts +79 -0
- package/dist/email-catalog/registry.js +71 -0
- package/dist/email-catalog/schema.d.ts +212 -0
- package/dist/email-catalog/schema.js +49 -0
- package/dist/email-catalog/system-emails.d.ts +14 -0
- package/dist/email-catalog/system-emails.js +68 -0
- package/dist/localization/default-messages.d.ts +45 -0
- package/dist/localization/default-messages.js +45 -0
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/org/handlers.js +8 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/action-discovery.js +22 -0
- package/dist/server/better-auth-instance.js +17 -2
- package/dist/server/email.d.ts +9 -0
- package/dist/server/email.js +44 -3
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/package.json +3 -1
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
brandKitRoleTokens,
|
|
4
|
+
classifyBrandKitToken,
|
|
5
|
+
friendlyTokenName,
|
|
6
|
+
resolveBrandKitTokens,
|
|
7
|
+
} from "@agent-native/core/brand-kit/tokens";
|
|
2
8
|
import { extractCssVars } from "@agent-native/core/server/design-token-utils";
|
|
3
9
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
4
10
|
import { eq } from "drizzle-orm";
|
|
@@ -12,52 +18,12 @@ import {
|
|
|
12
18
|
resolveTweaksToCssVars,
|
|
13
19
|
} from "../shared/resolve-tweaks.js";
|
|
14
20
|
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// Token type classification
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
function classifyVar(
|
|
20
|
-
name: string,
|
|
21
|
-
value: string,
|
|
22
|
-
): "color" | "typography" | "spacing" | "radius" | "shadow" | "other" {
|
|
23
|
-
const n = name.toLowerCase();
|
|
24
|
-
if (
|
|
25
|
-
/color|bg|background|text|border|accent|primary|secondary|surface|muted|foreground|fill|stroke/i.test(
|
|
26
|
-
n,
|
|
27
|
-
) ||
|
|
28
|
-
/^(#[0-9a-fA-F]{3,8}|rgba?\(|hsla?\(|oklch\(|color\()/.test(value.trim())
|
|
29
|
-
) {
|
|
30
|
-
return "color";
|
|
31
|
-
}
|
|
32
|
-
if (/font|size|leading|tracking|weight|heading|body|type/i.test(n)) {
|
|
33
|
-
return "typography";
|
|
34
|
-
}
|
|
35
|
-
if (/radius|rounded/i.test(n)) {
|
|
36
|
-
return "radius";
|
|
37
|
-
}
|
|
38
|
-
if (/spacing|gap|padding|margin|space/i.test(n)) {
|
|
39
|
-
return "spacing";
|
|
40
|
-
}
|
|
41
|
-
if (/shadow|blur|drop/i.test(n)) {
|
|
42
|
-
return "shadow";
|
|
43
|
-
}
|
|
44
|
-
return "other";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Derive a friendly display name from a CSS var name like `--primary-color`. */
|
|
48
|
-
function friendlyName(cssVar: string): string {
|
|
49
|
-
return cssVar
|
|
50
|
-
.replace(/^--/, "")
|
|
51
|
-
.replace(/[-_]/g, " ")
|
|
52
|
-
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
53
|
-
}
|
|
54
|
-
|
|
55
21
|
// ---------------------------------------------------------------------------
|
|
56
22
|
// Token entry shape
|
|
57
23
|
// ---------------------------------------------------------------------------
|
|
58
24
|
|
|
59
25
|
export interface DesignToken {
|
|
60
|
-
/**
|
|
26
|
+
/** The linked Brand Kit's own token name when it has one, else derived. */
|
|
61
27
|
name: string;
|
|
62
28
|
/** CSS custom property, e.g. "--primary-color". */
|
|
63
29
|
cssVar: string;
|
|
@@ -67,6 +33,13 @@ export interface DesignToken {
|
|
|
67
33
|
type: "color" | "typography" | "spacing" | "radius" | "shadow" | "other";
|
|
68
34
|
/** Opaque source chip label, e.g. "globals.css" or "Brand Kit". */
|
|
69
35
|
source: string;
|
|
36
|
+
/** Collection path from the Brand Kit, e.g. "Colors/Interactive". */
|
|
37
|
+
group?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Which layer supplied the token. Filter on this, not on `source` — that is a
|
|
40
|
+
* free-text chip label (a filename, "Brand Kit", "Tweaks", …).
|
|
41
|
+
*/
|
|
42
|
+
origin: "design" | "brand-kit";
|
|
70
43
|
/**
|
|
71
44
|
* True when the value comes from the design's own tweak selections (i.e.
|
|
72
45
|
* the user has already customised this token in the editor).
|
|
@@ -74,6 +47,16 @@ export interface DesignToken {
|
|
|
74
47
|
isTweakOverride?: boolean;
|
|
75
48
|
}
|
|
76
49
|
|
|
50
|
+
/** Layer of a token's provenance, before it is flattened into a DesignToken. */
|
|
51
|
+
interface RawToken {
|
|
52
|
+
value: string;
|
|
53
|
+
source: string;
|
|
54
|
+
origin: DesignToken["origin"];
|
|
55
|
+
/** Present only when a Brand Kit supplied the token's own name. */
|
|
56
|
+
name?: string;
|
|
57
|
+
group?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
77
60
|
// ---------------------------------------------------------------------------
|
|
78
61
|
// Action
|
|
79
62
|
// ---------------------------------------------------------------------------
|
|
@@ -81,9 +64,11 @@ export interface DesignToken {
|
|
|
81
64
|
export default defineAction({
|
|
82
65
|
description:
|
|
83
66
|
"Index the design tokens for a design as a friendly { name, cssVar, " +
|
|
84
|
-
"value, type, source } list. Parses CSS custom properties from the " +
|
|
67
|
+
"value, type, source, group } list. Parses CSS custom properties from the " +
|
|
85
68
|
"design's HTML :root block, the linked Brand Kit / design system, and " +
|
|
86
|
-
"the user's applied tweak selections.
|
|
69
|
+
"the user's applied tweak selections. Tokens the linked Brand Kit stores " +
|
|
70
|
+
"by name keep that name and collection group; the rest get a name derived " +
|
|
71
|
+
"from their CSS variable. Returns tokens grouped by type " +
|
|
87
72
|
"(color, typography, spacing, radius, shadow, other).",
|
|
88
73
|
schema: z.object({
|
|
89
74
|
designId: z.string().describe("Design project ID"),
|
|
@@ -109,19 +94,21 @@ export default defineAction({
|
|
|
109
94
|
.from(schema.designFiles)
|
|
110
95
|
.where(eq(schema.designFiles.designId, designId));
|
|
111
96
|
|
|
112
|
-
/** cssVar ->
|
|
113
|
-
const rawTokens: Map<string,
|
|
97
|
+
/** cssVar -> latest layer that set it */
|
|
98
|
+
const rawTokens: Map<string, RawToken> = new Map();
|
|
114
99
|
// Persisted Brand Kit JSON can outlive its schema. Keep one malformed token
|
|
115
100
|
// from taking down the whole read action while preserving valid siblings.
|
|
116
101
|
const setRawToken = (
|
|
117
102
|
cssVar: string,
|
|
118
103
|
value: unknown,
|
|
119
104
|
source: unknown,
|
|
105
|
+
provenance: Omit<RawToken, "value" | "source">,
|
|
120
106
|
): void => {
|
|
121
107
|
if (typeof value !== "string") return;
|
|
122
108
|
rawTokens.set(cssVar, {
|
|
123
109
|
value,
|
|
124
110
|
source: typeof source === "string" && source ? source : "Unknown",
|
|
111
|
+
...provenance,
|
|
125
112
|
});
|
|
126
113
|
};
|
|
127
114
|
|
|
@@ -138,7 +125,7 @@ export default defineAction({
|
|
|
138
125
|
};
|
|
139
126
|
extractCssVars(state, file.content);
|
|
140
127
|
for (const [k, v] of Object.entries(state.cssCustomProperties)) {
|
|
141
|
-
setRawToken(k, v, file.filename);
|
|
128
|
+
setRawToken(k, v, file.filename, { origin: "design" });
|
|
142
129
|
}
|
|
143
130
|
}
|
|
144
131
|
|
|
@@ -165,38 +152,24 @@ export default defineAction({
|
|
|
165
152
|
if (dsRow?.data) {
|
|
166
153
|
try {
|
|
167
154
|
const dsData = JSON.parse(dsRow.data) as Partial<DesignSystemData>;
|
|
168
|
-
//
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
accent: "--color-accent",
|
|
174
|
-
background: "--color-background",
|
|
175
|
-
surface: "--color-surface",
|
|
176
|
-
text: "--color-text",
|
|
177
|
-
textMuted: "--color-text-muted",
|
|
178
|
-
};
|
|
179
|
-
for (const [role, cssVar] of Object.entries(colorRoleMap)) {
|
|
180
|
-
const v = (brandColors as Record<string, string>)[role];
|
|
181
|
-
if (v) setRawToken(cssVar, v, "Brand Kit");
|
|
182
|
-
}
|
|
183
|
-
// Border radius
|
|
184
|
-
if (dsData.borders?.radius) {
|
|
185
|
-
setRawToken("--radius", dsData.borders.radius, "Brand Kit");
|
|
155
|
+
// The role -> var mapping lives in core so a rewriter cannot drift.
|
|
156
|
+
for (const token of brandKitRoleTokens(dsData)) {
|
|
157
|
+
setRawToken(token.cssVar, token.value, "Brand Kit", {
|
|
158
|
+
origin: "brand-kit",
|
|
159
|
+
});
|
|
186
160
|
}
|
|
187
|
-
//
|
|
188
|
-
|
|
161
|
+
// Layered after the roles: the kit's own names win over the
|
|
162
|
+
// seven-slot summary.
|
|
163
|
+
for (const token of resolveBrandKitTokens(dsData, "Brand Kit")) {
|
|
189
164
|
setRawToken(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
"Brand Kit",
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
dsData.spacing.pagePadding,
|
|
199
|
-
"Brand Kit",
|
|
165
|
+
token.cssVar,
|
|
166
|
+
token.value,
|
|
167
|
+
token.source ?? "Brand Kit",
|
|
168
|
+
{
|
|
169
|
+
origin: "brand-kit",
|
|
170
|
+
name: token.name,
|
|
171
|
+
...(token.group ? { group: token.group } : {}),
|
|
172
|
+
},
|
|
200
173
|
);
|
|
201
174
|
}
|
|
202
175
|
} catch {
|
|
@@ -250,20 +223,28 @@ export default defineAction({
|
|
|
250
223
|
...Object.keys(tweakSelections).filter(isDirectCssVarSelectionKey),
|
|
251
224
|
]);
|
|
252
225
|
for (const [cssVar, value] of Object.entries(resolvedOverrides)) {
|
|
253
|
-
|
|
226
|
+
// Retuning a value must not rename the token it came from.
|
|
227
|
+
const inherited = rawTokens.get(cssVar);
|
|
228
|
+
setRawToken(cssVar, value, tokenImportSources[cssVar] ?? "Tweaks", {
|
|
229
|
+
origin: inherited?.origin ?? "design",
|
|
230
|
+
...(inherited?.name ? { name: inherited.name } : {}),
|
|
231
|
+
...(inherited?.group ? { group: inherited.group } : {}),
|
|
232
|
+
});
|
|
254
233
|
}
|
|
255
234
|
|
|
256
235
|
// ------------------------------------------------------------------
|
|
257
236
|
// 4. Build friendly token list
|
|
258
237
|
// ------------------------------------------------------------------
|
|
259
238
|
const tokens: DesignToken[] = [];
|
|
260
|
-
for (const [cssVar, { value, source }] of rawTokens) {
|
|
239
|
+
for (const [cssVar, { value, source, name, group, origin }] of rawTokens) {
|
|
261
240
|
tokens.push({
|
|
262
|
-
name:
|
|
241
|
+
name: name ?? friendlyTokenName(cssVar),
|
|
263
242
|
cssVar,
|
|
264
243
|
value,
|
|
265
|
-
type:
|
|
244
|
+
type: classifyBrandKitToken(cssVar, value),
|
|
266
245
|
source,
|
|
246
|
+
origin,
|
|
247
|
+
...(group ? { group } : {}),
|
|
267
248
|
isTweakOverride: tweakCssVars.has(cssVar),
|
|
268
249
|
});
|
|
269
250
|
}
|
|
@@ -4,6 +4,7 @@ import { eq } from "drizzle-orm";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import { assertStorableDesignSystemTokens } from "../server/lib/design-system-tokens.js";
|
|
7
8
|
|
|
8
9
|
export default defineAction({
|
|
9
10
|
description:
|
|
@@ -23,7 +24,11 @@ export default defineAction({
|
|
|
23
24
|
.trim()
|
|
24
25
|
.min(1, "data cannot be empty")
|
|
25
26
|
.optional()
|
|
26
|
-
.describe(
|
|
27
|
+
.describe(
|
|
28
|
+
"Updated JSON string of DesignSystemData. Replaces the whole object, so " +
|
|
29
|
+
"include the existing `tokens` array (the source system's named tokens) " +
|
|
30
|
+
"alongside any edit, or those names are dropped.",
|
|
31
|
+
),
|
|
27
32
|
assets: z
|
|
28
33
|
.string()
|
|
29
34
|
.optional()
|
|
@@ -36,16 +41,8 @@ export default defineAction({
|
|
|
36
41
|
),
|
|
37
42
|
}),
|
|
38
43
|
run: async ({ id, title, description, data, assets, customInstructions }) => {
|
|
39
|
-
// Validate that data/assets are valid JSON when provided
|
|
40
44
|
if (data !== undefined) {
|
|
41
|
-
|
|
42
|
-
const parsed = JSON.parse(data);
|
|
43
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
44
|
-
throw new Error();
|
|
45
|
-
}
|
|
46
|
-
} catch {
|
|
47
|
-
throw new Error("data must be a valid JSON object string");
|
|
48
|
-
}
|
|
45
|
+
assertStorableDesignSystemTokens(data);
|
|
49
46
|
}
|
|
50
47
|
if (assets !== undefined) {
|
|
51
48
|
try {
|
|
@@ -155,6 +155,12 @@ import {
|
|
|
155
155
|
vscodeDeepLink,
|
|
156
156
|
} from "./edit-panel/code-inspect-helpers";
|
|
157
157
|
import { ComponentSection } from "./edit-panel/component-section";
|
|
158
|
+
import {
|
|
159
|
+
parseTokenReference,
|
|
160
|
+
resolveTokenNameForColor,
|
|
161
|
+
toDesignSystemColorSwatches,
|
|
162
|
+
type DesignSystemColorSwatch,
|
|
163
|
+
} from "./edit-panel/design-system-swatches";
|
|
158
164
|
import {
|
|
159
165
|
type DocumentColorSourceFile,
|
|
160
166
|
extractDocumentColorPalette,
|
|
@@ -383,6 +389,9 @@ export { deriveLockedAspectSize };
|
|
|
383
389
|
export { mergeRotationValue, normalizeRotationDegrees };
|
|
384
390
|
export { mixedElementFromSelection };
|
|
385
391
|
export { authoredStyleValue, resolveInteractionStateValue };
|
|
392
|
+
|
|
393
|
+
/** A `var()` reference names itself, so no kit lookup is needed to label it. */
|
|
394
|
+
const EMPTY_SWATCHES: DesignSystemColorSwatch[] = [];
|
|
386
395
|
export { isTextElement };
|
|
387
396
|
export { ComponentSection };
|
|
388
397
|
export { extractDocumentColorPalette, type DocumentColorSourceFile };
|
|
@@ -1467,12 +1476,15 @@ function PageProperties({
|
|
|
1467
1476
|
styles,
|
|
1468
1477
|
onStyleChange,
|
|
1469
1478
|
onStylesChange,
|
|
1479
|
+
designSystemColors,
|
|
1470
1480
|
canvasBackground,
|
|
1471
1481
|
onCanvasBackgroundChange,
|
|
1472
1482
|
}: {
|
|
1473
1483
|
styles: Record<string, string>;
|
|
1474
1484
|
onStyleChange: StyleChangeHandler;
|
|
1475
1485
|
onStylesChange?: StylesChangeHandler;
|
|
1486
|
+
/** Kit tokens offered by name in the canvas/page background pickers. */
|
|
1487
|
+
designSystemColors?: DesignSystemColorSwatch[];
|
|
1476
1488
|
canvasBackground?: string | null;
|
|
1477
1489
|
onCanvasBackgroundChange?: (value: string, meta?: StyleChangeMeta) => void;
|
|
1478
1490
|
}) {
|
|
@@ -1503,6 +1515,7 @@ function PageProperties({
|
|
|
1503
1515
|
<PanelSection title={t("editPanel.sections.canvas")}>
|
|
1504
1516
|
<ColorInput
|
|
1505
1517
|
label={t("editPanel.labels.background")}
|
|
1518
|
+
designSystemColors={designSystemColors}
|
|
1506
1519
|
value={canvasBackground ?? ""}
|
|
1507
1520
|
// meta carries phase: "preview" while dragging vs "commit" on
|
|
1508
1521
|
// release. Dropping it persists every tick and the picker jumps.
|
|
@@ -1513,6 +1526,7 @@ function PageProperties({
|
|
|
1513
1526
|
<PanelSection title={t("editPanel.sections.page")}>
|
|
1514
1527
|
<ColorInput
|
|
1515
1528
|
label={t("editPanel.labels.background")}
|
|
1529
|
+
designSystemColors={designSystemColors}
|
|
1516
1530
|
value={styles.backgroundColor || ""}
|
|
1517
1531
|
onChange={(v, meta) => onStyleChange("backgroundColor", v, meta)}
|
|
1518
1532
|
backgroundImage={styles.backgroundImage}
|
|
@@ -1724,8 +1738,15 @@ function SelectionColorsProperties({
|
|
|
1724
1738
|
className="size-4 shrink-0 rounded-[3px] border border-border/60"
|
|
1725
1739
|
style={swatchStyle(color.value)}
|
|
1726
1740
|
/>
|
|
1727
|
-
<span
|
|
1728
|
-
{
|
|
1741
|
+
<span
|
|
1742
|
+
className={cn(
|
|
1743
|
+
"min-w-0 flex-1 truncate text-left tabular-nums",
|
|
1744
|
+
// Hex is conventionally upper-case; a token name is not.
|
|
1745
|
+
!parseTokenReference(color.value) && "uppercase",
|
|
1746
|
+
)}
|
|
1747
|
+
>
|
|
1748
|
+
{resolveTokenNameForColor(color.value, EMPTY_SWATCHES) ??
|
|
1749
|
+
selectionDisplayHex(color.value)}
|
|
1729
1750
|
</span>
|
|
1730
1751
|
<span className="shrink-0 tabular-nums text-muted-foreground">
|
|
1731
1752
|
{opacity}%
|
|
@@ -1920,6 +1941,24 @@ export const EditPanel = memo(function EditPanel({
|
|
|
1920
1941
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- keyed on filesContentKey (cheap length+id fingerprint) instead of `files` itself so an unstable-but-equal array identity from the parent doesn't force a full re-scan every render.
|
|
1921
1942
|
[filesContentKey],
|
|
1922
1943
|
);
|
|
1944
|
+
// The linked Brand Kit's named colour tokens, for the fill picker. Shares
|
|
1945
|
+
// react-query's cache with the Tokens panel, so opening both costs one fetch.
|
|
1946
|
+
const { data: indexedTokens } = useActionQuery<{
|
|
1947
|
+
tokens?: {
|
|
1948
|
+
name: string;
|
|
1949
|
+
cssVar: string;
|
|
1950
|
+
value: string;
|
|
1951
|
+
type: string;
|
|
1952
|
+
group?: string;
|
|
1953
|
+
source?: string;
|
|
1954
|
+
}[];
|
|
1955
|
+
}>("index-design-tokens", designId ? { designId } : undefined, {
|
|
1956
|
+
enabled: Boolean(designId),
|
|
1957
|
+
});
|
|
1958
|
+
const designSystemColors = useMemo(
|
|
1959
|
+
() => toDesignSystemColorSwatches(indexedTokens?.tokens),
|
|
1960
|
+
[indexedTokens],
|
|
1961
|
+
);
|
|
1923
1962
|
const selectionAlreadyComponent =
|
|
1924
1963
|
selectedCount === 1 &&
|
|
1925
1964
|
(selectedElementAlreadyComponent ||
|
|
@@ -2326,6 +2365,7 @@ export const EditPanel = memo(function EditPanel({
|
|
|
2326
2365
|
{!inspectorElement && !selectedScreenGeometry && (
|
|
2327
2366
|
<PageProperties
|
|
2328
2367
|
styles={pageStyles}
|
|
2368
|
+
designSystemColors={designSystemColors}
|
|
2329
2369
|
onStyleChange={onStyleChange}
|
|
2330
2370
|
onStylesChange={onStylesChange}
|
|
2331
2371
|
canvasBackground={canvasBackground}
|
|
@@ -2382,11 +2422,13 @@ export const EditPanel = memo(function EditPanel({
|
|
|
2382
2422
|
onStyleChange={onStyleChange}
|
|
2383
2423
|
onStylesChange={onStylesChange}
|
|
2384
2424
|
documentColorPalette={documentColorPalette}
|
|
2425
|
+
designSystemColors={designSystemColors}
|
|
2385
2426
|
glslShaderContext={glslShaderContext}
|
|
2386
2427
|
motionKeyframeContext={motionKeyframeFieldContext}
|
|
2387
2428
|
breakpointOverrideContext={breakpointOverrideFieldContext}
|
|
2388
2429
|
/>
|
|
2389
2430
|
<StrokeProperties
|
|
2431
|
+
designSystemColors={designSystemColors}
|
|
2390
2432
|
element={stateResolvedInspectorElement ?? inspectorElement}
|
|
2391
2433
|
onStyleChange={onStyleChange}
|
|
2392
2434
|
onStylesChange={onStylesChange}
|
|
@@ -2397,6 +2439,7 @@ export const EditPanel = memo(function EditPanel({
|
|
|
2397
2439
|
element={stateResolvedInspectorElement ?? inspectorElement}
|
|
2398
2440
|
onStyleChange={onStyleChange}
|
|
2399
2441
|
onStylesChange={onStylesChange}
|
|
2442
|
+
designSystemColors={designSystemColors}
|
|
2400
2443
|
glslShaderContext={glslShaderContext}
|
|
2401
2444
|
motionKeyframeContext={motionKeyframeFieldContext}
|
|
2402
2445
|
/>
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { createCanvasGestureController } from "@agent-native/toolkit/canvas-interactions";
|
|
31
31
|
|
|
32
|
+
import { collectAuthoredColorStyles } from "../../../../shared/authored-color-styles";
|
|
33
|
+
|
|
32
34
|
declare var __READ_ONLY__: boolean;
|
|
33
35
|
declare var __TEXT_EDITING_ENABLED__: boolean;
|
|
34
36
|
declare var __EDITOR_CHROME_SCALE_X__: string;
|
|
@@ -1852,6 +1854,9 @@ declare var __SELECTED_LAYER_DRAG_PRIORITY__: boolean;
|
|
|
1852
1854
|
// and `right` — only the inline style tells you which side was actually
|
|
1853
1855
|
// set). Empty-string values are omitted so callers can treat key-absence as
|
|
1854
1856
|
// "not authored".
|
|
1857
|
+
// color/backgroundColor/borderColor/outlineColor are reported because
|
|
1858
|
+
// getComputedStyle resolves `var()`: without the authored declaration the
|
|
1859
|
+
// inspector cannot tell a design system token from the colour it resolves to.
|
|
1855
1860
|
var INLINE_STYLE_PROPERTIES = [
|
|
1856
1861
|
"position",
|
|
1857
1862
|
"left",
|
|
@@ -1862,6 +1867,12 @@ declare var __SELECTED_LAYER_DRAG_PRIORITY__: boolean;
|
|
|
1862
1867
|
"height",
|
|
1863
1868
|
"transform",
|
|
1864
1869
|
"whiteSpace",
|
|
1870
|
+
"color",
|
|
1871
|
+
"backgroundColor",
|
|
1872
|
+
"borderColor",
|
|
1873
|
+
"outlineColor",
|
|
1874
|
+
"boxShadow",
|
|
1875
|
+
"textShadow",
|
|
1865
1876
|
];
|
|
1866
1877
|
|
|
1867
1878
|
function collectInlineStyles(el: Element): Record<string, string> {
|
|
@@ -2121,6 +2132,7 @@ declare var __SELECTED_LAYER_DRAG_PRIORITY__: boolean;
|
|
|
2121
2132
|
whiteSpace: cs.whiteSpace,
|
|
2122
2133
|
},
|
|
2123
2134
|
inlineStyles: collectInlineStyles(el),
|
|
2135
|
+
authoredColorStyles: collectAuthoredColorStyles(el),
|
|
2124
2136
|
primitiveKind: el.getAttribute("data-an-primitive") || undefined,
|
|
2125
2137
|
portableStyleSnapshot: collectPortableStyleSnapshot(el),
|
|
2126
2138
|
boundingRect: {
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { parseCssColorExtended, rgbaToHex } from "@shared/color-utils";
|
|
2
|
+
|
|
3
|
+
/** One colour token from the design's linked Brand Kit, as the kit names it. */
|
|
4
|
+
export interface DesignSystemColorSwatch {
|
|
5
|
+
name: string;
|
|
6
|
+
cssVar: string;
|
|
7
|
+
value: string;
|
|
8
|
+
group?: string;
|
|
9
|
+
source?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface IndexedToken {
|
|
13
|
+
name: string;
|
|
14
|
+
cssVar: string;
|
|
15
|
+
value: string;
|
|
16
|
+
type: string;
|
|
17
|
+
group?: string;
|
|
18
|
+
source?: string;
|
|
19
|
+
origin?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Extended: the classifier stores `oklch()`/`color()` tokens as colours. */
|
|
23
|
+
function normalizeHex(value: string): string | null {
|
|
24
|
+
const parsed = parseCssColorExtended(value.trim());
|
|
25
|
+
return parsed ? rgbaToHex(parsed).toUpperCase() : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Guards a cyclic alias chain (`--a: var(--b); --b: var(--a)`). */
|
|
29
|
+
const MAX_ALIAS_DEPTH = 8;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A token's value as a paintable literal, following aliases through the kit's
|
|
33
|
+
* own vocabulary. A kit legitimately defines one token as another, and dropping
|
|
34
|
+
* those loses a selectable token; painting them raw gives an empty swatch.
|
|
35
|
+
*/
|
|
36
|
+
function paintableTokenValue(
|
|
37
|
+
value: string,
|
|
38
|
+
byCssVar: ReadonlyMap<string, string>,
|
|
39
|
+
depth = 0,
|
|
40
|
+
): string | null {
|
|
41
|
+
if (normalizeHex(value)) return value;
|
|
42
|
+
if (depth >= MAX_ALIAS_DEPTH) return null;
|
|
43
|
+
const reference = parseTokenReference(value);
|
|
44
|
+
if (!reference) return null;
|
|
45
|
+
|
|
46
|
+
const aliased = byCssVar.get(reference.cssVar);
|
|
47
|
+
if (aliased !== undefined) {
|
|
48
|
+
const resolved = paintableTokenValue(aliased, byCssVar, depth + 1);
|
|
49
|
+
if (resolved) return resolved;
|
|
50
|
+
}
|
|
51
|
+
return reference.fallback
|
|
52
|
+
? paintableTokenValue(reference.fallback, byCssVar, depth + 1)
|
|
53
|
+
: null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The linked kit's swatchable colour tokens from an `index-design-tokens`
|
|
58
|
+
* result, which mixes the design's own `:root` colours into the same list.
|
|
59
|
+
* Values that resolve to no colour at all are dropped, since they would paint
|
|
60
|
+
* an empty swatch.
|
|
61
|
+
*/
|
|
62
|
+
export function toDesignSystemColorSwatches(
|
|
63
|
+
tokens: readonly IndexedToken[] | undefined,
|
|
64
|
+
limit = 48,
|
|
65
|
+
): DesignSystemColorSwatch[] {
|
|
66
|
+
if (!tokens) return [];
|
|
67
|
+
// Every token, not just kit ones: a kit alias may point at a design `:root`
|
|
68
|
+
// variable, and that still resolves to a real colour.
|
|
69
|
+
const byCssVar = new Map(tokens.map((token) => [token.cssVar, token.value]));
|
|
70
|
+
const seen = new Set<string>();
|
|
71
|
+
const out: DesignSystemColorSwatch[] = [];
|
|
72
|
+
|
|
73
|
+
for (const token of tokens) {
|
|
74
|
+
if (token.type !== "color") continue;
|
|
75
|
+
if (token.origin !== "brand-kit") continue;
|
|
76
|
+
const value = paintableTokenValue(token.value, byCssVar);
|
|
77
|
+
if (!value) continue;
|
|
78
|
+
if (seen.has(token.cssVar)) continue;
|
|
79
|
+
seen.add(token.cssVar);
|
|
80
|
+
out.push({
|
|
81
|
+
name: token.name,
|
|
82
|
+
cssVar: token.cssVar,
|
|
83
|
+
value,
|
|
84
|
+
...(token.group ? { group: token.group } : {}),
|
|
85
|
+
...(token.source ? { source: token.source } : {}),
|
|
86
|
+
});
|
|
87
|
+
if (out.length >= limit) break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* What to call a token in the picker: its CSS variable, minus the leading `--`.
|
|
95
|
+
*
|
|
96
|
+
* The indexed `name` is title-cased for tokens the kit never named ("Color
|
|
97
|
+
* Primary"), which reads as a different vocabulary than the kit's own
|
|
98
|
+
* `cds-background-brand` sitting next to it. The variable is what the design
|
|
99
|
+
* references, so it is the name worth showing.
|
|
100
|
+
*/
|
|
101
|
+
export function swatchLabel(swatch: DesignSystemColorSwatch): string {
|
|
102
|
+
return swatch.cssVar.replace(/^--/, "");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const TOKEN_REFERENCE_HEAD = /^var\(\s*(--[-_a-zA-Z0-9]+)\s*(,?)/;
|
|
106
|
+
|
|
107
|
+
export interface TokenReference {
|
|
108
|
+
cssVar: string;
|
|
109
|
+
/** Literal after the comma. Absent for a bare `var(--x)`. */
|
|
110
|
+
fallback: string | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A fallback is itself a CSS value, so it can nest parentheses. Scanning to the
|
|
115
|
+
* balanced close rather than matching `[^()]+` is what keeps a function-valued
|
|
116
|
+
* token from reading as "not a token" and being dropped by the commit guard.
|
|
117
|
+
*/
|
|
118
|
+
export function parseTokenReference(value: string): TokenReference | null {
|
|
119
|
+
const trimmed = value.trim();
|
|
120
|
+
const head = TOKEN_REFERENCE_HEAD.exec(trimmed);
|
|
121
|
+
if (!head) return null;
|
|
122
|
+
|
|
123
|
+
let depth = 1;
|
|
124
|
+
let index = head[0].length;
|
|
125
|
+
for (; index < trimmed.length && depth > 0; index += 1) {
|
|
126
|
+
const char = trimmed[index];
|
|
127
|
+
if (char === "(") depth += 1;
|
|
128
|
+
else if (char === ")") depth -= 1;
|
|
129
|
+
}
|
|
130
|
+
// Anything after the matching `)` means this is not a lone reference.
|
|
131
|
+
if (depth !== 0 || index !== trimmed.length) return null;
|
|
132
|
+
|
|
133
|
+
const fallback = trimmed.slice(head[0].length, index - 1).trim();
|
|
134
|
+
if (fallback && !head[2]) return null;
|
|
135
|
+
return { cssVar: head[1], fallback: fallback || null };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The declaration to persist for a picked token.
|
|
140
|
+
*
|
|
141
|
+
* The fallback is not optional padding: outside the editor — an export, a
|
|
142
|
+
* published page, a design whose kit link was removed — nothing defines the
|
|
143
|
+
* custom property, and a bare `var(--x)` renders transparent.
|
|
144
|
+
*/
|
|
145
|
+
export function tokenReferenceValue(swatch: DesignSystemColorSwatch): string {
|
|
146
|
+
return `var(${swatch.cssVar}, ${swatch.value})`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const HIDDEN_TOKEN_MIX =
|
|
150
|
+
/^color-mix\(\s*in\s+srgb\s*,\s*(var\(.+\))\s+0%\s*,\s*transparent\s*\)$/i;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* What to write when hiding a colour. Zeroing alpha needs literal channels,
|
|
154
|
+
* which a `var()` reference has none of — so a token is hidden as a 0% mix that
|
|
155
|
+
* keeps the reference inside the persisted CSS. React state would not survive
|
|
156
|
+
* the deselect/reselect that unmounts the inspector.
|
|
157
|
+
*/
|
|
158
|
+
export function hiddenColorWrite(
|
|
159
|
+
color: string,
|
|
160
|
+
zeroAlpha: (literal: string) => string | null,
|
|
161
|
+
): string {
|
|
162
|
+
const reference = parseTokenReference(color);
|
|
163
|
+
if (reference) return `color-mix(in srgb, ${color} 0%, transparent)`;
|
|
164
|
+
return zeroAlpha(color) ?? "transparent";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The reference a hidden token colour carries, or null if it is not one. */
|
|
168
|
+
export function hiddenTokenReference(value: string): string | null {
|
|
169
|
+
return HIDDEN_TOKEN_MIX.exec(value.trim())?.[1] ?? null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Name the token a value came from, or null when that cannot be known. A
|
|
174
|
+
* `var()` reference names itself; a bare colour is matched by value, and a tie
|
|
175
|
+
* returns null — Carbon points four tokens at one blue.
|
|
176
|
+
*/
|
|
177
|
+
export function resolveTokenNameForColor(
|
|
178
|
+
value: string,
|
|
179
|
+
swatches: readonly DesignSystemColorSwatch[],
|
|
180
|
+
): string | null {
|
|
181
|
+
const reference = parseTokenReference(value);
|
|
182
|
+
if (reference) return reference.cssVar.replace(/^--/, "");
|
|
183
|
+
|
|
184
|
+
const target = normalizeHex(value);
|
|
185
|
+
if (!target) return null;
|
|
186
|
+
|
|
187
|
+
let match: string | null = null;
|
|
188
|
+
for (const swatch of swatches) {
|
|
189
|
+
if (normalizeHex(swatch.value) !== target) continue;
|
|
190
|
+
if (match) return null;
|
|
191
|
+
match = swatchLabel(swatch);
|
|
192
|
+
}
|
|
193
|
+
return match;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Group swatches under their kit collection path, preserving token order. */
|
|
197
|
+
export function groupColorSwatches(
|
|
198
|
+
swatches: readonly DesignSystemColorSwatch[],
|
|
199
|
+
): { label: string | null; swatches: DesignSystemColorSwatch[] }[] {
|
|
200
|
+
const groups = new Map<
|
|
201
|
+
string,
|
|
202
|
+
{ label: string | null; swatches: DesignSystemColorSwatch[] }
|
|
203
|
+
>();
|
|
204
|
+
for (const swatch of swatches) {
|
|
205
|
+
const label = swatch.group?.trim() || null;
|
|
206
|
+
const key = label ?? "";
|
|
207
|
+
const existing = groups.get(key);
|
|
208
|
+
if (existing) {
|
|
209
|
+
existing.swatches.push(swatch);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
groups.set(key, { label, swatches: [swatch] });
|
|
213
|
+
}
|
|
214
|
+
return [...groups.values()];
|
|
215
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseCssColor, rgbaToHex } from "@shared/color-utils";
|
|
2
2
|
|
|
3
3
|
import type { ElementInfo } from "../types";
|
|
4
|
+
import { authoredColorValue } from "./interaction-state-helpers";
|
|
4
5
|
|
|
5
6
|
export interface DocumentColorSourceFile {
|
|
6
7
|
id: string;
|
|
@@ -63,13 +64,16 @@ export interface SelectionColorValue {
|
|
|
63
64
|
export function selectionColorValues(
|
|
64
65
|
element: ElementInfo,
|
|
65
66
|
): SelectionColorValue[] {
|
|
66
|
-
|
|
67
|
+
// Authored, not computed: getComputedStyle has already flattened `var()`.
|
|
67
68
|
const rawValues: SelectionColorValue[] = [
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
]
|
|
69
|
+
"color",
|
|
70
|
+
"backgroundColor",
|
|
71
|
+
"borderColor",
|
|
72
|
+
"outlineColor",
|
|
73
|
+
].map((property) => ({
|
|
74
|
+
property,
|
|
75
|
+
value: authoredColorValue(element, property) as string,
|
|
76
|
+
}));
|
|
73
77
|
const seen = new Set<string>();
|
|
74
78
|
return rawValues
|
|
75
79
|
.map((color) => ({ ...color, value: color.value?.trim() }))
|