@beyondwork/docx-react-component 1.0.52 → 1.0.53
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/package.json +31 -40
- package/src/api/public-types.ts +32 -0
- package/src/io/chart-preview-resolver.ts +41 -0
- package/src/io/docx-session.ts +187 -17
- package/src/runtime/collab/runtime-collab-sync.ts +87 -6
- package/src/runtime/document-runtime.ts +159 -0
- package/src/runtime/layout/layout-engine-version.ts +40 -2
- package/src/runtime/layout/public-facet.ts +43 -1
- package/src/runtime/prerender/cache-envelope.ts +30 -0
- package/src/runtime/prerender/customxml-cache.ts +17 -3
- package/src/runtime/prerender/prerender-document.ts +17 -1
- package/src/runtime/render/render-kernel.ts +67 -19
- package/src/runtime/surface-projection.ts +28 -0
- package/src/runtime/table-schema.ts +27 -0
- package/src/runtime/table-style-resolver.ts +51 -0
- package/src/ui/editor-runtime-boundary.ts +39 -2
- package/src/ui-tailwind/editor-surface/perf-probe.ts +1 -0
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +54 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +107 -3
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +2 -2
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +224 -0
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +2 -2
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +2 -2
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +11 -147
- package/src/ui-tailwind/theme/chart-palette-adapter.ts +57 -0
- package/src/ui-tailwind/theme/editor-theme.css +26 -24
- package/src/ui-tailwind/theme/tokens.css +345 -0
- package/src/ui-tailwind/theme/tokens.ts +313 -0
- package/src/ui-tailwind/theme/use-density.ts +60 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beyondwork/docx-react-component — design-token manifest
|
|
3
|
+
*
|
|
4
|
+
* Single TypeScript surface that consumers import. Mirrors
|
|
5
|
+
* `docs/reference/designsystem.md` §3.1 (product token names) + §3.3/§3.4
|
|
6
|
+
* (values). Every key here has a CSS variable alias in
|
|
7
|
+
* `./tokens.css` — the pair is enforced by
|
|
8
|
+
* `test/ui-tailwind/token-coverage.test.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Values below are the LIGHT-MODE hex values only. CSS variables own the
|
|
11
|
+
* light/dark fork; runtime reads resolve via `getComputedStyle`. Pure hex
|
|
12
|
+
* strings remain accessible for design-tool export, snapshot tests, and
|
|
13
|
+
* one-off cases where a consumer cannot enter the CSS cascade.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const BRAND_TOKENS = {
|
|
17
|
+
color: {
|
|
18
|
+
bg: {
|
|
19
|
+
app: "#F4F7F5",
|
|
20
|
+
chrome: "#FAFCFA",
|
|
21
|
+
sidebar: "#F1F6F2",
|
|
22
|
+
canvas: "#FFFFFF",
|
|
23
|
+
elevated: "#FFFFFF",
|
|
24
|
+
hover: "#EAF6EF",
|
|
25
|
+
selected: "#E2F2E8",
|
|
26
|
+
muted: "#F7FAF8",
|
|
27
|
+
overlay: "rgba(21, 26, 23, 0.08)",
|
|
28
|
+
},
|
|
29
|
+
border: {
|
|
30
|
+
subtle: "#E2EAE4",
|
|
31
|
+
default: "#D3DED6",
|
|
32
|
+
strong: "#BECDBF",
|
|
33
|
+
accent: "#8FC9AD",
|
|
34
|
+
},
|
|
35
|
+
text: {
|
|
36
|
+
primary: "#151A17",
|
|
37
|
+
secondary: "#5E6D63",
|
|
38
|
+
tertiary: "#8A978F",
|
|
39
|
+
quiet: "#A4AEA8",
|
|
40
|
+
inverse: "#F7FAF8",
|
|
41
|
+
onAccent: "#F7FAF8",
|
|
42
|
+
onSoftAccent: "#18563F",
|
|
43
|
+
},
|
|
44
|
+
accent: {
|
|
45
|
+
primary: "#1F6B4F",
|
|
46
|
+
primaryHover: "#18563F",
|
|
47
|
+
primaryActive: "#124432",
|
|
48
|
+
secondary: "#72D6AE",
|
|
49
|
+
secondaryHover: "#5FC79C",
|
|
50
|
+
secondaryActive: "#49B785",
|
|
51
|
+
soft: "#DDF1E4",
|
|
52
|
+
softHover: "#CEEAD8",
|
|
53
|
+
},
|
|
54
|
+
semantic: {
|
|
55
|
+
success: "#1F8A5B",
|
|
56
|
+
successSoft: "#DDF3E8",
|
|
57
|
+
warning: "#D58B1E",
|
|
58
|
+
warningSoft: "#FBEFD8",
|
|
59
|
+
error: "#D95C67",
|
|
60
|
+
errorSoft: "#FBE3E6",
|
|
61
|
+
info: "#2F8FCE",
|
|
62
|
+
infoSoft: "#DCEFFC",
|
|
63
|
+
},
|
|
64
|
+
field: {
|
|
65
|
+
fill: "#F6FAF7",
|
|
66
|
+
editable: "#EEF8F0",
|
|
67
|
+
focus: "#DDF1E4",
|
|
68
|
+
invalid: "#FBE3E6",
|
|
69
|
+
warning: "#FBEFD8",
|
|
70
|
+
},
|
|
71
|
+
status: {
|
|
72
|
+
ready: "#1F8A5B",
|
|
73
|
+
inProgress: "#2F8FCE",
|
|
74
|
+
paused: "#8A978F",
|
|
75
|
+
blocked: "#D95C67",
|
|
76
|
+
review: "#1F6B4F",
|
|
77
|
+
draft: "#72907E",
|
|
78
|
+
},
|
|
79
|
+
comment: {
|
|
80
|
+
marker: "#1F6B4F",
|
|
81
|
+
markerHover: "#18563F",
|
|
82
|
+
threadBg: "#F3FAF6",
|
|
83
|
+
},
|
|
84
|
+
change: {
|
|
85
|
+
insertion: "#DDF3E8",
|
|
86
|
+
deletion: "#FBE3E6",
|
|
87
|
+
comment: "#E8F4EC",
|
|
88
|
+
selection: "#DDF1E4",
|
|
89
|
+
},
|
|
90
|
+
scopeTint: {
|
|
91
|
+
blocked: "#FBE3E6",
|
|
92
|
+
inScope: "#E2F2E8",
|
|
93
|
+
suggest: "#FBEFD8",
|
|
94
|
+
comment: "#F3FAF6",
|
|
95
|
+
scheduled: "#DCEFFC",
|
|
96
|
+
proposed: "#EAF6EF",
|
|
97
|
+
},
|
|
98
|
+
chart: {
|
|
99
|
+
categorical: {
|
|
100
|
+
"1": "#1F6B4F",
|
|
101
|
+
"2": "#72D6AE",
|
|
102
|
+
"3": "#2F8FCE",
|
|
103
|
+
"4": "#D58B1E",
|
|
104
|
+
"5": "#7A6AF0",
|
|
105
|
+
"6": "#D95C67",
|
|
106
|
+
"7": "#5E8E7A",
|
|
107
|
+
"8": "#A7D9C1",
|
|
108
|
+
},
|
|
109
|
+
sequential: {
|
|
110
|
+
"1": "#EAF6EF",
|
|
111
|
+
"2": "#D4EEDD",
|
|
112
|
+
"3": "#B4DFC8",
|
|
113
|
+
"4": "#8FC9AD",
|
|
114
|
+
"5": "#5FA985",
|
|
115
|
+
"6": "#1F6B4F",
|
|
116
|
+
},
|
|
117
|
+
diverging: {
|
|
118
|
+
negStrong: "#C94B57",
|
|
119
|
+
neg: "#E9979F",
|
|
120
|
+
neutral: "#E9EFEA",
|
|
121
|
+
pos: "#97D1B3",
|
|
122
|
+
posStrong: "#1F6B4F",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
shadow: {
|
|
127
|
+
soft: "0 6px 20px rgba(21, 26, 23, 0.06)",
|
|
128
|
+
float: "0 12px 32px rgba(21, 26, 23, 0.12)",
|
|
129
|
+
focus: "0 0 0 3px rgba(114, 214, 174, 0.28)",
|
|
130
|
+
},
|
|
131
|
+
radius: {
|
|
132
|
+
sm: "10px",
|
|
133
|
+
md: "12px",
|
|
134
|
+
lg: "14px",
|
|
135
|
+
xl: "16px",
|
|
136
|
+
pill: "9999px",
|
|
137
|
+
},
|
|
138
|
+
space: {
|
|
139
|
+
"1": "4px",
|
|
140
|
+
"2": "8px",
|
|
141
|
+
"3": "12px",
|
|
142
|
+
"4": "16px",
|
|
143
|
+
"5": "20px",
|
|
144
|
+
"6": "24px",
|
|
145
|
+
"8": "32px",
|
|
146
|
+
"10": "40px",
|
|
147
|
+
"12": "48px",
|
|
148
|
+
"16": "64px",
|
|
149
|
+
},
|
|
150
|
+
motion: {
|
|
151
|
+
fast: "120ms",
|
|
152
|
+
default: "160ms",
|
|
153
|
+
slow: "220ms",
|
|
154
|
+
},
|
|
155
|
+
} as const;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Dotted path tuple type used by the coverage test to enumerate every
|
|
159
|
+
* leaf of `BRAND_TOKENS`. Mirrors the `color.accent.primary` / `shadow.soft`
|
|
160
|
+
* form used throughout the spec.
|
|
161
|
+
*/
|
|
162
|
+
export type BrandTokens = typeof BRAND_TOKENS;
|
|
163
|
+
export type BrandTokenPath =
|
|
164
|
+
| `color.bg.${keyof BrandTokens["color"]["bg"]}`
|
|
165
|
+
| `color.border.${keyof BrandTokens["color"]["border"]}`
|
|
166
|
+
| `color.text.${keyof BrandTokens["color"]["text"]}`
|
|
167
|
+
| `color.accent.${keyof BrandTokens["color"]["accent"]}`
|
|
168
|
+
| `color.semantic.${keyof BrandTokens["color"]["semantic"]}`
|
|
169
|
+
| `color.field.${keyof BrandTokens["color"]["field"]}`
|
|
170
|
+
| `color.status.${keyof BrandTokens["color"]["status"]}`
|
|
171
|
+
| `color.comment.${keyof BrandTokens["color"]["comment"]}`
|
|
172
|
+
| `color.change.${keyof BrandTokens["color"]["change"]}`
|
|
173
|
+
| `color.scopeTint.${keyof BrandTokens["color"]["scopeTint"]}`
|
|
174
|
+
| `color.chart.categorical.${keyof BrandTokens["color"]["chart"]["categorical"]}`
|
|
175
|
+
| `color.chart.sequential.${keyof BrandTokens["color"]["chart"]["sequential"]}`
|
|
176
|
+
| `color.chart.diverging.${keyof BrandTokens["color"]["chart"]["diverging"]}`
|
|
177
|
+
| `shadow.${keyof BrandTokens["shadow"]}`
|
|
178
|
+
| `radius.${keyof BrandTokens["radius"]}`
|
|
179
|
+
| `space.${keyof BrandTokens["space"]}`
|
|
180
|
+
| `motion.${keyof BrandTokens["motion"]}`;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Converts a dotted product-token path into its CSS variable name.
|
|
184
|
+
* productPathToCssVar("color.accent.primary") === "--color-accent-primary"
|
|
185
|
+
* productPathToCssVar("color.chart.categorical.1") === "--color-chart-categorical-1"
|
|
186
|
+
* productPathToCssVar("color.text.onAccent") === "--color-text-on-accent"
|
|
187
|
+
*/
|
|
188
|
+
export function productPathToCssVar(path: string): string {
|
|
189
|
+
const kebab = path
|
|
190
|
+
.split(".")
|
|
191
|
+
.join("-")
|
|
192
|
+
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
|
193
|
+
.toLowerCase();
|
|
194
|
+
return `--${kebab}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Enumerates every leaf path in `BRAND_TOKENS`.
|
|
199
|
+
*/
|
|
200
|
+
export function enumerateBrandTokenPaths(): BrandTokenPath[] {
|
|
201
|
+
const out: string[] = [];
|
|
202
|
+
const walk = (node: unknown, prefix: string): void => {
|
|
203
|
+
if (node === null || typeof node !== "object") return;
|
|
204
|
+
for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
|
|
205
|
+
const next = prefix === "" ? key : `${prefix}.${key}`;
|
|
206
|
+
if (typeof value === "string") out.push(next);
|
|
207
|
+
else walk(value, next);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
walk(BRAND_TOKENS, "");
|
|
211
|
+
return out as BrandTokenPath[];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Host override contract (designsystem §8.5 / §9).
|
|
216
|
+
*
|
|
217
|
+
* Hosts MAY override these token families to match their brand / workspace.
|
|
218
|
+
*/
|
|
219
|
+
export const HOST_OVERRIDABLE_TOKENS = [
|
|
220
|
+
"color.accent.primary",
|
|
221
|
+
"color.accent.primaryHover",
|
|
222
|
+
"color.accent.primaryActive",
|
|
223
|
+
"color.accent.secondary",
|
|
224
|
+
"color.accent.secondaryHover",
|
|
225
|
+
"color.accent.secondaryActive",
|
|
226
|
+
"color.accent.soft",
|
|
227
|
+
"color.accent.softHover",
|
|
228
|
+
"color.bg.app",
|
|
229
|
+
"radius.sm",
|
|
230
|
+
"radius.md",
|
|
231
|
+
"radius.lg",
|
|
232
|
+
"radius.xl",
|
|
233
|
+
"radius.pill",
|
|
234
|
+
"motion.fast",
|
|
235
|
+
"motion.default",
|
|
236
|
+
"motion.slow",
|
|
237
|
+
] as const satisfies readonly BrandTokenPath[];
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Hosts MUST NOT override these — semantic legibility, review markup,
|
|
241
|
+
* comment marker, canvas, and accessibility contract.
|
|
242
|
+
*/
|
|
243
|
+
export const HOST_LOCKED_TOKENS = [
|
|
244
|
+
"color.semantic.success",
|
|
245
|
+
"color.semantic.successSoft",
|
|
246
|
+
"color.semantic.warning",
|
|
247
|
+
"color.semantic.warningSoft",
|
|
248
|
+
"color.semantic.error",
|
|
249
|
+
"color.semantic.errorSoft",
|
|
250
|
+
"color.semantic.info",
|
|
251
|
+
"color.semantic.infoSoft",
|
|
252
|
+
"color.change.insertion",
|
|
253
|
+
"color.change.deletion",
|
|
254
|
+
"color.change.comment",
|
|
255
|
+
"color.change.selection",
|
|
256
|
+
"color.comment.marker",
|
|
257
|
+
"color.comment.markerHover",
|
|
258
|
+
"color.comment.threadBg",
|
|
259
|
+
"color.bg.canvas",
|
|
260
|
+
"shadow.focus",
|
|
261
|
+
] as const satisfies readonly BrandTokenPath[];
|
|
262
|
+
|
|
263
|
+
export type HostOverridableToken = (typeof HOST_OVERRIDABLE_TOKENS)[number];
|
|
264
|
+
export type HostLockedToken = (typeof HOST_LOCKED_TOKENS)[number];
|
|
265
|
+
|
|
266
|
+
/** Returns true if `path` is in the locked set (must not be overridden by hosts). */
|
|
267
|
+
export function isTokenPathLocked(path: string): boolean {
|
|
268
|
+
return (HOST_LOCKED_TOKENS as readonly string[]).includes(path);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Dev-mode guard. Subscribes to attribute/style mutations on `document.documentElement`
|
|
273
|
+
* and emits `console.warn` if a host overrides a locked CSS variable.
|
|
274
|
+
*
|
|
275
|
+
* No-op in production (`process.env.NODE_ENV === "production"`) and in
|
|
276
|
+
* non-browser environments (SSR, test runners without DOM).
|
|
277
|
+
*
|
|
278
|
+
* Returns a teardown function. Call it to disconnect the observer.
|
|
279
|
+
*/
|
|
280
|
+
export function devGuardLockedTokens(
|
|
281
|
+
root: HTMLElement = typeof document !== "undefined"
|
|
282
|
+
? document.documentElement
|
|
283
|
+
: (null as unknown as HTMLElement),
|
|
284
|
+
): () => void {
|
|
285
|
+
if (
|
|
286
|
+
typeof document === "undefined" ||
|
|
287
|
+
root === null ||
|
|
288
|
+
(typeof (globalThis as Record<string, unknown>)["__WRE_DEV__"] !== "undefined" &&
|
|
289
|
+
!(globalThis as Record<string, unknown>)["__WRE_DEV__"])
|
|
290
|
+
) {
|
|
291
|
+
return () => {};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const lockedVars = new Set(
|
|
295
|
+
(HOST_LOCKED_TOKENS as readonly string[]).map((p) => productPathToCssVar(p)),
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const observer = new MutationObserver(() => {
|
|
299
|
+
const style = root.getAttribute("style") ?? "";
|
|
300
|
+
for (const v of lockedVars) {
|
|
301
|
+
if (style.includes(v)) {
|
|
302
|
+
console.warn(
|
|
303
|
+
`[docx-react-component] Host override of locked token "${v}" detected. ` +
|
|
304
|
+
`Overriding locked tokens may break review-workspace legibility contracts (§9.2). ` +
|
|
305
|
+
`See docs/reference/designsystem.md §9.`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
observer.observe(root, { attributes: true, attributeFilter: ["style"] });
|
|
312
|
+
return () => observer.disconnect();
|
|
313
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Density mode hook — reads and writes `document.documentElement.dataset.density`
|
|
3
|
+
* and persists the preference in `localStorage["wre.density"]`.
|
|
4
|
+
*
|
|
5
|
+
* Components that use spacing gaps / paddings should multiply by
|
|
6
|
+
* `var(--space-density-multiplier)` in their CSS; this hook owns the
|
|
7
|
+
* attribute that drives the CSS override blocks in `tokens.css`.
|
|
8
|
+
*
|
|
9
|
+
* No component consumer lands in Lane 6a — Lane 6b.U5 / 6c.U9 own the
|
|
10
|
+
* opt-in plumbing. This module ships the hook so downstream lanes can
|
|
11
|
+
* import without a dependency on 6a being in-progress.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useCallback, useEffect, useState } from "react";
|
|
15
|
+
|
|
16
|
+
export type DensityMode = "compact" | "standard" | "comfortable";
|
|
17
|
+
|
|
18
|
+
const STORAGE_KEY = "wre.density";
|
|
19
|
+
const DEFAULT_DENSITY: DensityMode = "standard";
|
|
20
|
+
|
|
21
|
+
function readPersistedDensity(): DensityMode {
|
|
22
|
+
try {
|
|
23
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
24
|
+
if (stored === "compact" || stored === "standard" || stored === "comfortable") {
|
|
25
|
+
return stored;
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// localStorage unavailable (SSR, sandboxed iframe, etc.)
|
|
29
|
+
}
|
|
30
|
+
return DEFAULT_DENSITY;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function applyDensity(mode: DensityMode): void {
|
|
34
|
+
document.documentElement.dataset.density = mode;
|
|
35
|
+
try {
|
|
36
|
+
localStorage.setItem(STORAGE_KEY, mode);
|
|
37
|
+
} catch {
|
|
38
|
+
// ignore
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useDensity(): {
|
|
43
|
+
density: DensityMode;
|
|
44
|
+
setDensity: (mode: DensityMode) => void;
|
|
45
|
+
} {
|
|
46
|
+
const [density, setDensityState] = useState<DensityMode>(DEFAULT_DENSITY);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const initial = readPersistedDensity();
|
|
50
|
+
applyDensity(initial);
|
|
51
|
+
setDensityState(initial);
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
const setDensity = useCallback((mode: DensityMode) => {
|
|
55
|
+
applyDensity(mode);
|
|
56
|
+
setDensityState(mode);
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
return { density, setDensity };
|
|
60
|
+
}
|