@volter/editor-sdk 0.5.57

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.
Files changed (104) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +19 -0
  4. package/package.json +90 -0
  5. package/src/account.ts +210 -0
  6. package/src/chrome.ts +83 -0
  7. package/src/client.ts +1547 -0
  8. package/src/commands.ts +66 -0
  9. package/src/contributions.ts +985 -0
  10. package/src/document-probe.ts +237 -0
  11. package/src/editor-view.ts +220 -0
  12. package/src/extension.ts +40 -0
  13. package/src/generations.ts +178 -0
  14. package/src/host.ts +1167 -0
  15. package/src/http-transport.browser.ts +14 -0
  16. package/src/http-transport.node.ts +19 -0
  17. package/src/index.ts +128 -0
  18. package/src/layout-arrangements.ts +5 -0
  19. package/src/layouts.tsx +108 -0
  20. package/src/looks.ts +14 -0
  21. package/src/project/output-roots.ts +73 -0
  22. package/src/project/tab-census.ts +149 -0
  23. package/src/project-tool-catalog.ts +96 -0
  24. package/src/selection.tsx +108 -0
  25. package/src/services.ts +18 -0
  26. package/src/session/build-report.ts +19 -0
  27. package/src/session/collaboration-types.ts +262 -0
  28. package/src/session/command-table.ts +333 -0
  29. package/src/session/discovery.ts +90 -0
  30. package/src/session/editor-brand.ts +73 -0
  31. package/src/session/editor-compatibility.ts +248 -0
  32. package/src/session/editor-control-lifecycle.ts +68 -0
  33. package/src/session/editor-control-protocol.ts +5 -0
  34. package/src/session/entrypoint-selection-readers.ts +66 -0
  35. package/src/session/entrypoint-selection-source.ts +120 -0
  36. package/src/session/game-css-scope.ts +30 -0
  37. package/src/session/product-create.ts +24 -0
  38. package/src/session/product-locator.ts +389 -0
  39. package/src/session/project-module-url.ts +245 -0
  40. package/src/session/registry-format.ts +203 -0
  41. package/src/session/relative-path-guard.ts +56 -0
  42. package/src/session/source-glob.ts +15 -0
  43. package/src/session/tool-contribution-convention.ts +116 -0
  44. package/src/session/workbench-locator.ts +650 -0
  45. package/src/session.ts +41 -0
  46. package/src/share.ts +160 -0
  47. package/src/tools/errors.ts +91 -0
  48. package/src/tools/provider-execution.ts +70 -0
  49. package/src/tools/registry.ts +341 -0
  50. package/src/tools/types.ts +159 -0
  51. package/src/transport.ts +97 -0
  52. package/src/types.ts +1581 -0
  53. package/src/views.ts +164 -0
  54. package/src/widgets/design-system.ts +93 -0
  55. package/src/widgets/editor-appearance.ts +149 -0
  56. package/src/widgets/editor-material.ts +83 -0
  57. package/src/widgets/icon-set-registry.ts +105 -0
  58. package/src/widgets/index.ts +71 -0
  59. package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
  60. package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
  61. package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
  62. package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
  63. package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
  64. package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
  65. package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
  66. package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
  67. package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
  68. package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
  69. package/src/widgets/inspector-widgets/color-utils.ts +201 -0
  70. package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
  71. package/src/widgets/inspector-widgets/index.ts +24 -0
  72. package/src/widgets/inspector-widgets/shared.tsx +140 -0
  73. package/src/widgets/interactive-edit-scope.ts +33 -0
  74. package/src/widgets/patterns/Dialog.tsx +129 -0
  75. package/src/widgets/patterns/Fields.tsx +44 -0
  76. package/src/widgets/patterns/List.tsx +25 -0
  77. package/src/widgets/patterns/StateSurface.tsx +40 -0
  78. package/src/widgets/patterns/Surfaces.tsx +122 -0
  79. package/src/widgets/patterns/Tabs.tsx +80 -0
  80. package/src/widgets/patterns/Toolbar.tsx +72 -0
  81. package/src/widgets/patterns/Tree.tsx +72 -0
  82. package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
  83. package/src/widgets/primitives/Button.tsx +62 -0
  84. package/src/widgets/primitives/ColorInput.tsx +78 -0
  85. package/src/widgets/primitives/DraftTextInput.tsx +63 -0
  86. package/src/widgets/primitives/EditorIcon.tsx +157 -0
  87. package/src/widgets/primitives/FormControls.tsx +88 -0
  88. package/src/widgets/primitives/HoverPreview.tsx +96 -0
  89. package/src/widgets/primitives/JsonInput.tsx +113 -0
  90. package/src/widgets/primitives/Layout.tsx +100 -0
  91. package/src/widgets/primitives/Menu.tsx +140 -0
  92. package/src/widgets/primitives/NumberInput.tsx +169 -0
  93. package/src/widgets/primitives/Panel.tsx +80 -0
  94. package/src/widgets/primitives/SectionHeader.tsx +77 -0
  95. package/src/widgets/primitives/Text.tsx +54 -0
  96. package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
  97. package/src/widgets/primitives/Tooltip.tsx +204 -0
  98. package/src/widgets/primitives/Vec3Input.tsx +70 -0
  99. package/src/widgets/primitives/banner-tones.ts +32 -0
  100. package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
  101. package/src/widgets/primitives/editor-icons.ts +245 -0
  102. package/src/widgets/primitives/panel-header-styles.ts +42 -0
  103. package/src/widgets/theme.ts +2633 -0
  104. package/src/widgets/z-index.ts +25 -0
package/src/views.ts ADDED
@@ -0,0 +1,164 @@
1
+ /**
2
+ * A VIEW'S VERBS — the one product door into a panel that is not the centre
3
+ * document (ARCHITECTURE-CORE §Our tier over the Code-OSS frame, "a panel →
4
+ * view"; WORK.md §The core is Code-OSS U8, ruling (1), 2026-09-19).
5
+ *
6
+ * ## The problem this exists for, and the ruling that shaped it
7
+ *
8
+ * `editor.document.*` reaches only the ACTIVE CENTRE DOCUMENT, by its own
9
+ * contract. Every drawer utility — Console, Profiler, Network, I5's node
10
+ * editor — is therefore undrivable by the product, and each one that needed
11
+ * driving was shipping its OWN session verb to compensate (I5's
12
+ * `blender-node-view` is the worked case, and UV Editing, Animation and
13
+ * Texture Paint were each about to pay the same price).
14
+ *
15
+ * The ruling refused both a utility scope on the document door and a verb per
16
+ * view: **under the Code-OSS frame every one of our views is a VS Code view
17
+ * in a view container, and its state and verbs are reached through
18
+ * `vgai.<view>.<verb>` commands the bridge dispatches into the view** — the
19
+ * same one-name door as every other verb, so `vgai eval` reaches it through
20
+ * the frame's command service.
21
+ *
22
+ * ## Why the registry is HERE and not in the editor
23
+ *
24
+ * Both sides of it are outside `packages/editor/src`: a view's verbs are
25
+ * registered by the package that OWNS the view (`@vgai/blender`'s node
26
+ * editor), and they are read by the fork's bridge. The editor host mediates
27
+ * nothing, so a registry inside it would be a closure file with no reader of
28
+ * its own — the same reasoning `registerEditorHost` follows one module over.
29
+ *
30
+ * ## The two doors, one table
31
+ *
32
+ * Standalone `vgai edit` has no VS Code command service, so the SESSION's own
33
+ * verb is how a view is driven there. That is not a second implementation:
34
+ * the session verb calls {@link invokeViewVerb} on the same table the frame's
35
+ * commands call, exactly as `key-actions.ts`'s action table is one
36
+ * table behind two keyboards. A NEW view registers verbs here and adds no
37
+ * session verb at all.
38
+ */
39
+
40
+ export interface ViewVerb {
41
+ /** The verb, bare: `state`, `view-all`, `zoom`. The frame publishes it as
42
+ * `vgai.<view>.<verb>`; the session spells it its own way. */
43
+ readonly id: string;
44
+ /** What the palette calls it, when the verb is one a PERSON would run. A
45
+ * verb with no title is reachable by command id and by the session, and is
46
+ * not listed — a read (`state`) or a refusal probe is not a menu item. */
47
+ readonly title?: string;
48
+ /** Run it. `args` is the command's own argument object, unvalidated: a view
49
+ * validates its own arguments and REFUSES BY NAME, because what an argument
50
+ * means is the view's and no registry can know it. */
51
+ readonly run: (args?: Record<string, unknown>) => unknown;
52
+ }
53
+
54
+ export interface ViewVerbContribution {
55
+ /** The view's id, and the middle segment of every command it publishes —
56
+ * `blender-node-view` → `vgai.blender-node-view.view-all`. It is the same
57
+ * id the view registers under when it becomes a VS Code view. */
58
+ readonly view: string;
59
+ /** What the view is called, for the palette entry's category. */
60
+ readonly title: string;
61
+ readonly verbs: readonly ViewVerb[];
62
+ }
63
+
64
+ const REGISTRY_KEY = Symbol.for('vgai.editor.viewVerbs');
65
+ interface RegistryState {
66
+ contributions: readonly ViewVerbContribution[];
67
+ listeners: Set<() => void>;
68
+ version: number;
69
+ }
70
+ const globals = globalThis as typeof globalThis & { [REGISTRY_KEY]?: RegistryState };
71
+ // One registry across module copies, for the reason `host.ts` has one: the
72
+ // fork's bridge and a package's contribution can be different instances of
73
+ // this module, and a view registered into a second table is a view the frame
74
+ // never sees.
75
+ globals[REGISTRY_KEY] ??= { contributions: [], listeners: new Set(), version: 0 };
76
+ const state: RegistryState = globals[REGISTRY_KEY];
77
+
78
+ function emit(): void {
79
+ state.version++;
80
+ for (const listener of state.listeners) listener();
81
+ }
82
+
83
+ /**
84
+ * Register a view's verbs. Returns the removal, which only fires while this
85
+ * contribution is still the live one for its id.
86
+ *
87
+ * THE SAME VIEW REGISTERING AGAIN REPLACES ITSELF. A contribution module is
88
+ * evaluated more than once per session by design: the tool loader imports it
89
+ * as `/@fs/<path>?t=<version>` (packages/editor/src/tool-loader.ts) so a
90
+ * project's save re-evaluates it, and a BUNDLED package's contribution is
91
+ * reached by its bare specifier as well — while this registry is ONE table on
92
+ * `globalThis` across
93
+ * every module copy. Module-level `registerViewVerbs(...)` in a contribution
94
+ * therefore runs once per evaluation, and on 2026-09-19 the second evaluation
95
+ * threw and the editor did not boot. The re-evaluation is recognised by the
96
+ * view id AND title agreeing — the same source, loaded again — and the newer
97
+ * table wins, its predecessor's disposer becoming a no-op.
98
+ *
99
+ * A DIFFERENT view claiming the id still THROWS: the id keys the whole
100
+ * `vgai.<view>.<verb>` command namespace, and a second view under it would
101
+ * publish commands that shadow the first with no sign of it.
102
+ */
103
+ export function registerViewVerbs(contribution: ViewVerbContribution): () => void {
104
+ const existing = state.contributions.find((entry) => entry.view === contribution.view);
105
+ if (existing && existing.title !== contribution.title) {
106
+ throw new Error(
107
+ `registerViewVerbs: the view "${contribution.view}" already publishes verbs as "${existing.title}"; "${contribution.title}" is a different view under the same id. A view id keys its whole \`vgai.<view>.<verb>\` command namespace, so a second view under it would shadow the first with no sign of it.`,
108
+ );
109
+ }
110
+ state.contributions = [
111
+ ...state.contributions.filter((entry) => entry !== existing),
112
+ contribution,
113
+ ];
114
+ emit();
115
+ return () => {
116
+ if (!state.contributions.includes(contribution)) return;
117
+ state.contributions = state.contributions.filter((entry) => entry !== contribution);
118
+ emit();
119
+ };
120
+ }
121
+
122
+ /** Every view publishing verbs right now. */
123
+ export function viewVerbContributions(): readonly ViewVerbContribution[] {
124
+ return state.contributions;
125
+ }
126
+
127
+ export function viewVerbsVersion(): number {
128
+ return state.version;
129
+ }
130
+
131
+ export function subscribeViewVerbs(listener: () => void): () => void {
132
+ state.listeners.add(listener);
133
+ return () => state.listeners.delete(listener);
134
+ }
135
+
136
+ /**
137
+ * Run one view's verb. The refusal is a THROW naming the whole vocabulary,
138
+ * because both callers — the frame's command and the session's verb — report
139
+ * a thrown message and a silent `undefined` is how a typo becomes a mystery.
140
+ */
141
+ export function invokeViewVerb(
142
+ view: string,
143
+ verb: string,
144
+ args?: Record<string, unknown>,
145
+ ): unknown {
146
+ const contribution = state.contributions.find((entry) => entry.view === view);
147
+ if (!contribution) {
148
+ const known = state.contributions.map((entry) => entry.view).join(', ') || 'none';
149
+ throw new Error(`No view "${view}" publishes verbs right now — the views that do: ${known}.`);
150
+ }
151
+ const entry = contribution.verbs.find((candidate) => candidate.id === verb);
152
+ if (!entry) {
153
+ throw new Error(
154
+ `The view "${view}" has no verb "${verb}" — it publishes: ${contribution.verbs.map((candidate) => candidate.id).join(', ')}.`,
155
+ );
156
+ }
157
+ return entry.run(args);
158
+ }
159
+
160
+ export function __resetViewVerbsForTest(): void {
161
+ state.contributions = [];
162
+ state.listeners.clear();
163
+ state.version = 0;
164
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The editor design system — patterns, primitives and the theme members its
3
+ * own surfaces read, as ONE module. Theme definitions, CSS recipes,
4
+ * primitives and recurring patterns stay implementation details below it.
5
+ *
6
+ * It is not a published subpath: `./index.ts` re-exports it, and the face is
7
+ * `@volter/editor-sdk/widgets` (ARCHITECTURE-CORE §The target shape, rule 5 —
8
+ * one API, four faces). Everything in and below this file must stay
9
+ * react-only, or the Apache package starts reaching AGPL host source.
10
+ */
11
+
12
+ export {
13
+ composeEditorAppearance,
14
+ EDITOR_MATERIAL_IDS,
15
+ EDITOR_MATERIALS,
16
+ type EditorMaterialChoice,
17
+ type EditorMaterialId,
18
+ type EditorPalette,
19
+ isEditorMaterialId,
20
+ } from './editor-appearance';
21
+ export * from './patterns/Dialog';
22
+ export * from './patterns/Fields';
23
+ export * from './patterns/List';
24
+ export * from './patterns/StateSurface';
25
+ export * from './patterns/Surfaces';
26
+ export * from './patterns/Tabs';
27
+ export * from './patterns/Toolbar';
28
+ export * from './patterns/Tree';
29
+ export * from './primitives/AnchoredMenu';
30
+ export * from './primitives/Button';
31
+ export * from './primitives/banner-tones';
32
+ export * from './primitives/ColorInput';
33
+ export * from './primitives/clamp-to-viewport';
34
+ export * from './primitives/EditorIcon';
35
+ export { editorIcons } from './primitives/editor-icons';
36
+ export * from './primitives/FormControls';
37
+ export * from './primitives/HoverPreview';
38
+ export * from './primitives/JsonInput';
39
+ export * from './primitives/Layout';
40
+ export * from './primitives/Menu';
41
+ export * from './primitives/NumberInput';
42
+ export * from './primitives/Panel';
43
+ export * from './primitives/SectionHeader';
44
+ export * from './primitives/Text';
45
+ export * from './primitives/ThemeRootPortal';
46
+ export * from './primitives/Tooltip';
47
+ export * from './primitives/Vec3Input';
48
+ // Product surfaces consume runtime theme references through this boundary as
49
+ // they migrate away from direct `theme.ts` imports. Keeping the references
50
+ // here preserves custom-theme behavior without exposing design-system internals.
51
+ export {
52
+ accent,
53
+ accentMuted,
54
+ applyEditorTheme,
55
+ bg,
56
+ border,
57
+ chromeSize,
58
+ controlSize,
59
+ danger,
60
+ dangerFaint,
61
+ dangerMuted,
62
+ EDITOR_THEME_CLASS,
63
+ type EditorTheme,
64
+ type EditorThemeId,
65
+ type EditorThemeVariable,
66
+ editorThemes,
67
+ editorThemeVariables,
68
+ fontMono,
69
+ fontSans,
70
+ fontSize,
71
+ fontSizeVar,
72
+ fontWeight,
73
+ graphiteDarkEditorTheme,
74
+ lineHeight,
75
+ lineHeightVar,
76
+ midnightHighContrastEditorTheme,
77
+ motion,
78
+ motionVar,
79
+ radius,
80
+ scrim,
81
+ selection,
82
+ shadow,
83
+ space,
84
+ spaceVar,
85
+ strokeWidth,
86
+ success,
87
+ successMuted,
88
+ text,
89
+ themeVars,
90
+ warn,
91
+ warnMuted,
92
+ } from './theme';
93
+ export { zIndex } from './z-index';
@@ -0,0 +1,149 @@
1
+ import { CLASSIC_MATERIAL, GLASS_MATERIAL } from './editor-material';
2
+ import type { EditorTheme } from './theme';
3
+
4
+ /**
5
+ * Editor appearance is deliberately composed from independent axes:
6
+ *
7
+ * palette (a theme-library document) × material
8
+ *
9
+ * Palette documents own chroma and typography. Materials own surface physics,
10
+ * boundaries, neutral interaction overlays, shape, elevation, and treatment.
11
+ * Components only receive the resolved EditorTheme token set and therefore
12
+ * remain blind to both axes.
13
+ */
14
+ export const EDITOR_MATERIAL_IDS = ['classic', 'glass'] as const;
15
+ /** One of the editor's own materials, or one a package's style bundle
16
+ * carries ({@link registerContributedMaterial}). */
17
+ export type EditorMaterialId = string;
18
+
19
+ export interface EditorMaterialChoice {
20
+ readonly id: EditorMaterialId;
21
+ readonly title: string;
22
+ readonly description: string;
23
+ }
24
+
25
+ /**
26
+ * An OPAQUE material a package's style bundle carries (`@volter/editor-sdk/looks`
27
+ * `MaterialContribution`): Classic's physics under its own shape and
28
+ * elevation. Glass is the editor's own and is not contributable.
29
+ */
30
+ export interface ContributedMaterialDefinition extends EditorMaterialChoice {
31
+ readonly shape: EditorTheme['shape'];
32
+ readonly elevation: EditorTheme['elevation'];
33
+ readonly density?: EditorTheme['density'];
34
+ }
35
+
36
+ /** The only fields a color-theme document may contribute to composition. */
37
+ export type EditorPalette = Pick<EditorTheme, 'id' | 'color' | 'typography'>;
38
+
39
+ export const EDITOR_MATERIALS: readonly EditorMaterialChoice[] = Object.freeze([
40
+ {
41
+ id: 'classic',
42
+ title: 'Classic',
43
+ description: 'Opaque editor surfaces with conventional bars and panels.',
44
+ },
45
+ {
46
+ id: 'glass',
47
+ title: 'Glass',
48
+ description: 'Adaptive clear material with refraction, frost, and floating chrome.',
49
+ },
50
+ ]);
51
+
52
+ const contributedMaterials = new Map<string, ContributedMaterialDefinition>();
53
+ let cachedMaterials: readonly EditorMaterialChoice[] | null = null;
54
+ const materialListeners = new Set<() => void>();
55
+
56
+ /** Every material the switch surfaces offer — the editor's own, then the
57
+ * contributed in registration order. Stable between registrations. */
58
+ export function editorMaterials(): readonly EditorMaterialChoice[] {
59
+ cachedMaterials ??= [
60
+ ...EDITOR_MATERIALS,
61
+ ...[...contributedMaterials.values()].map(({ id, title, description }) => ({
62
+ id,
63
+ title,
64
+ description,
65
+ })),
66
+ ];
67
+ return cachedMaterials;
68
+ }
69
+
70
+ export function subscribeEditorMaterials(listener: () => void): () => void {
71
+ materialListeners.add(listener);
72
+ return () => materialListeners.delete(listener);
73
+ }
74
+
75
+ /** Register the material a style bundle carries. Returns the unregister. A
76
+ * duplicate id throws — ids key the persisted appearance setting. */
77
+ export function registerContributedMaterial(definition: ContributedMaterialDefinition): () => void {
78
+ if (isEditorMaterialId(definition.id))
79
+ throw new Error(
80
+ `registerContributedMaterial: material "${definition.id}" is already registered.`,
81
+ );
82
+ contributedMaterials.set(definition.id, definition);
83
+ cachedMaterials = null;
84
+ for (const listener of materialListeners) listener();
85
+ return () => {
86
+ if (contributedMaterials.get(definition.id) !== definition) return;
87
+ contributedMaterials.delete(definition.id);
88
+ cachedMaterials = null;
89
+ for (const listener of materialListeners) listener();
90
+ };
91
+ }
92
+
93
+ export function isEditorMaterialId(value: unknown): value is EditorMaterialId {
94
+ return (
95
+ typeof value === 'string' &&
96
+ (EDITOR_MATERIAL_IDS.includes(value as (typeof EDITOR_MATERIAL_IDS)[number]) ||
97
+ contributedMaterials.has(value))
98
+ );
99
+ }
100
+
101
+ /** Compose one palette source with the selected material. */
102
+ export function composeEditorAppearance(
103
+ palette: EditorPalette,
104
+ materialId: EditorMaterialId,
105
+ reducedTransparency = false,
106
+ ): EditorTheme {
107
+ if (materialId !== 'glass') {
108
+ // Classic, or a contributed opaque material: Classic's physics under the
109
+ // material's own shape and elevation.
110
+ const material = contributedMaterials.get(materialId);
111
+ return {
112
+ id: material ? `${palette.id}--${material.id}` : palette.id,
113
+ color: palette.color,
114
+ typography: palette.typography,
115
+ shape: material?.shape ?? CLASSIC_MATERIAL.shape,
116
+ elevation: material?.elevation ?? CLASSIC_MATERIAL.elevation,
117
+ ...(material?.density ? { density: material.density } : {}),
118
+ appearance: { material: 'classic', transparency: 'standard' },
119
+ };
120
+ }
121
+
122
+ const resolvedId = `${palette.id}--${reducedTransparency ? 'glass-reduced' : 'glass'}`;
123
+
124
+ return {
125
+ id: resolvedId,
126
+ color: {
127
+ // Material owns the physical surface; palette owns every semantic hue.
128
+ surface: reducedTransparency
129
+ ? GLASS_MATERIAL.reducedTransparency.surface
130
+ : GLASS_MATERIAL.color.surface,
131
+ boundary: GLASS_MATERIAL.color.boundary,
132
+ content: palette.color.content,
133
+ accent: palette.color.accent,
134
+ semantic: palette.color.semantic,
135
+ neutralOverlay: GLASS_MATERIAL.color.neutralOverlay,
136
+ scrim: reducedTransparency
137
+ ? GLASS_MATERIAL.reducedTransparency.scrim
138
+ : GLASS_MATERIAL.color.scrim,
139
+ },
140
+ typography: palette.typography,
141
+ shape: GLASS_MATERIAL.shape,
142
+ elevation: GLASS_MATERIAL.elevation,
143
+ appearance: {
144
+ material: 'glass',
145
+ transparency: reducedTransparency ? 'reduced' : 'standard',
146
+ },
147
+ ...(reducedTransparency ? {} : { treatment: GLASS_MATERIAL.treatment }),
148
+ };
149
+ }
@@ -0,0 +1,83 @@
1
+ import type { EditorTheme } from './theme';
2
+
3
+ type MaterialColor = Pick<
4
+ EditorTheme['color'],
5
+ 'surface' | 'boundary' | 'neutralOverlay' | 'scrim'
6
+ >;
7
+
8
+ export interface EditorMaterialDefinition {
9
+ readonly id: 'glass';
10
+ readonly color: MaterialColor;
11
+ readonly shape: EditorTheme['shape'];
12
+ readonly elevation: EditorTheme['elevation'];
13
+ readonly treatment: NonNullable<EditorTheme['treatment']>;
14
+ readonly reducedTransparency: {
15
+ readonly surface: EditorTheme['color']['surface'];
16
+ readonly scrim: string;
17
+ };
18
+ }
19
+
20
+ export const CLASSIC_MATERIAL = Object.freeze({
21
+ id: 'classic' as const,
22
+ shape: { small: '3px', medium: '6px', large: '8px', full: '9999px' },
23
+ elevation: {
24
+ small: '0 2px 8px rgba(0,0,0,0.6)',
25
+ medium: '0 4px 12px rgba(0,0,0,0.5)',
26
+ large: '0 8px 32px rgba(0,0,0,0.6)',
27
+ },
28
+ });
29
+
30
+ /** Canonical Glass material. It cannot contain accent or semantic colors. */
31
+ export const GLASS_MATERIAL: EditorMaterialDefinition = Object.freeze({
32
+ id: 'glass',
33
+ color: {
34
+ surface: {
35
+ shell: '#12161f',
36
+ panel: 'rgba(255,255,255,0.06)',
37
+ chrome: 'rgba(255,255,255,0.10)',
38
+ raised: 'rgba(255,255,255,0.16)',
39
+ inset: 'rgba(10,14,22,0.38)',
40
+ overlay: 'rgba(255,255,255,0.08)',
41
+ },
42
+ boundary: {
43
+ default: 'rgba(255,255,255,0.22)',
44
+ strong: 'rgba(255,255,255,0.38)',
45
+ },
46
+ neutralOverlay: {
47
+ hover: 'color-mix(in srgb, currentColor 10%, transparent)',
48
+ active: 'color-mix(in srgb, currentColor 16%, transparent)',
49
+ },
50
+ scrim: 'rgba(8,12,20,0.35)',
51
+ },
52
+ shape: { small: '4px', medium: '8px', large: '14px', full: '9999px' },
53
+ elevation: {
54
+ small: '0 4px 14px -6px rgba(0,0,0,0.45)',
55
+ medium: '0 8px 24px -10px rgba(0,0,0,0.5)',
56
+ large: '0 20px 46px -16px rgba(0,0,0,0.55), 0 4px 14px -6px rgba(0,0,0,0.35)',
57
+ },
58
+ treatment: {
59
+ backdropBlurPx: 1,
60
+ backdropSaturation: 1.15,
61
+ edgeSpecular: 0.7,
62
+ specularAngleDeg: 120,
63
+ refractionBezelPx: 56,
64
+ refractionThickness: 1.25,
65
+ textShadowOpacity: 0.72,
66
+ contentFrostBlurPx: 24,
67
+ ambientLiftOpacity: 0.07,
68
+ adaptiveContent: true,
69
+ brightTextShadowOpacity: 0,
70
+ brightFrostBg: 'rgba(255,255,255,0.3)',
71
+ },
72
+ reducedTransparency: {
73
+ surface: {
74
+ shell: '#12161f',
75
+ panel: '#33373e',
76
+ chrome: '#3d4047',
77
+ raised: '#4b4e55',
78
+ inset: '#23272f',
79
+ overlay: '#383b43',
80
+ },
81
+ scrim: 'rgba(8,12,20,0.5)',
82
+ },
83
+ });
@@ -0,0 +1,105 @@
1
+ /**
2
+ * THE ICON SET AXIS — the fourth appearance axis beside palette, material and
3
+ * composition. Every glyph the editor draws renders through `EditorIcon`
4
+ * (`primitives/EditorIcon.tsx`, the one consumer of Font Awesome),
5
+ * so a style bundle can carry a whole icon set as DATA keyed by the Font
6
+ * Awesome icon name each site names (`plus`, `xmark`, `trash`, …): a glyph
7
+ * present in the active set paints instead of the editor's own; a glyph
8
+ * absent falls through, so a set may be partial while it is drawn.
9
+ *
10
+ * This module is REGISTRY AND ACTIVE STATE ONLY — no settings, because the
11
+ * icon primitive sits in every core closure and must stay a leaf. The stored
12
+ * preference (`appearance.icons`) is read and written by `theme-preference.ts`,
13
+ * which applies it here, exactly as it owns the palette and material choices.
14
+ * Registered by `workspace-style.ts` with the bundle that carries it.
15
+ */
16
+ import type { IconCategoryTone, IconSetContribution } from '../looks';
17
+
18
+ export type { IconCategoryTone };
19
+
20
+ export interface EditorIconGlyph {
21
+ /** Defaults to a 16-unit square. */
22
+ readonly viewBox?: string;
23
+ /** One `path` `d`, painted with `currentColor`. */
24
+ readonly path: string;
25
+ /** THE COLOUR CHANNEL — the glyph's own category (`IconCategoryTone`),
26
+ * painted `var(--vgai-category-<tone>, currentColor)`. With `tonedPath`
27
+ * it tints only that second path; alone it tints the whole glyph. An
28
+ * explicit `tone` prop at the site wins over both. */
29
+ readonly tone?: IconCategoryTone;
30
+ /** A second `path` `d` drawn OVER `path` and carrying `tone`: the operated
31
+ * element of an operator mark, where the cube itself stays neutral. */
32
+ readonly tonedPath?: string;
33
+ }
34
+ export type EditorIconSet = IconSetContribution;
35
+ export interface EditorIconSetChoice {
36
+ readonly id: string;
37
+ readonly title: string;
38
+ }
39
+
40
+ export const DEFAULT_ICON_SET_ID = 'default';
41
+ const DEFAULT_CHOICE: EditorIconSetChoice = { id: DEFAULT_ICON_SET_ID, title: 'Editor' };
42
+
43
+ const contributedSets = new Map<string, EditorIconSet>();
44
+ let cachedChoices: readonly EditorIconSetChoice[] | null = null;
45
+ let activeId: string = DEFAULT_ICON_SET_ID;
46
+ const listeners = new Set<() => void>();
47
+
48
+ function emit(): void {
49
+ for (const listener of listeners) listener();
50
+ }
51
+
52
+ /** Every set the switch surfaces offer — the editor's own, then the contributed. */
53
+ export function editorIconSets(): readonly EditorIconSetChoice[] {
54
+ cachedChoices ??= [
55
+ DEFAULT_CHOICE,
56
+ ...[...contributedSets.values()].map(({ id, title }) => ({ id, title })),
57
+ ];
58
+ return cachedChoices;
59
+ }
60
+
61
+ export function isEditorIconSetId(value: unknown): value is string {
62
+ return typeof value === 'string' && (value === DEFAULT_ICON_SET_ID || contributedSets.has(value));
63
+ }
64
+
65
+ /** Register the set a style bundle carries. Returns the unregister. A
66
+ * duplicate id throws — ids key the persisted appearance setting. */
67
+ export function registerContributedIconSet(set: EditorIconSet): () => void {
68
+ if (isEditorIconSetId(set.id))
69
+ throw new Error(`registerContributedIconSet: icon set "${set.id}" is already registered.`);
70
+ contributedSets.set(set.id, set);
71
+ cachedChoices = null;
72
+ emit();
73
+ return () => {
74
+ if (contributedSets.get(set.id) !== set) return;
75
+ contributedSets.delete(set.id);
76
+ cachedChoices = null;
77
+ emit();
78
+ };
79
+ }
80
+
81
+ /** The set painting now. */
82
+ export function activeIconSetSnapshot(): string {
83
+ return activeId;
84
+ }
85
+
86
+ /** Applied by `theme-preference.ts` from the stored choice; an unregistered
87
+ * id paints the editor's own until its package registers. */
88
+ export function setActiveIconSet(id: string): void {
89
+ const next = isEditorIconSetId(id) ? id : DEFAULT_ICON_SET_ID;
90
+ if (next === activeId) return;
91
+ activeId = next;
92
+ emit();
93
+ }
94
+
95
+ /** Fires when the registry or the active set changes. */
96
+ export function subscribeIconSets(listener: () => void): () => void {
97
+ listeners.add(listener);
98
+ return () => listeners.delete(listener);
99
+ }
100
+
101
+ /** The active set's glyph for a Font Awesome icon name, or `null` to paint the editor's own. */
102
+ export function activeIconGlyph(iconName: string): EditorIconGlyph | null {
103
+ if (activeId === DEFAULT_ICON_SET_ID) return null;
104
+ return contributedSets.get(activeId)?.glyphs[iconName] ?? null;
105
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * `@volter/editor-sdk/widgets` — THE WIDGET KIT, the API's fourth face beside
3
+ * `protocol`, the host doors + `layouts`, and `session`
4
+ * (ARCHITECTURE-CORE §The target shape, rule 5: "One API, four faces,
5
+ * Apache"). These are the editor's OWN widgets — the exact components its
6
+ * inspector renders — so a panel built from them looks native and re-skins
7
+ * with the editor. Apache-2.0 like the rest of the SDK: a package or a
8
+ * project may draw a panel without taking the host's AGPL.
9
+ *
10
+ * Import it from a package contribution or a project's own
11
+ * `src/contributions/my-tool.document.tsx`:
12
+ *
13
+ * ```tsx
14
+ * import { Inline, NumberInput, SectionHeader, themeVars } from '@volter/editor-sdk/widgets';
15
+ * ```
16
+ *
17
+ * What's here:
18
+ * - **Tokens**: typed semantic `themeVars`; legacy `THEME`/font aliases remain
19
+ * compatibility exports for existing inspector widgets only.
20
+ * - **Structure**: layout, typography, surfaces, dialogs, toolbars, tabs,
21
+ * trees, fields, feedback, and menus.
22
+ * - **Inputs**: `NumberInput` (scrub-or-type), `Vec3Input`, `ColorInput`,
23
+ * `DraftTextInput`, `ScrubbableInput`, `ColorPicker`/`ColorSwatch`,
24
+ * `GradientEditor`, `FontPicker`, `AlignmentGrid`, `BorderEditor`,
25
+ * `ShadowEditor`, `FilterEditor`, `CurveEditor`, `AssetSlotPicker`,
26
+ * `ToggleButton`.
27
+ * - **Appearance**: the theme table, the editor materials, the icon-set
28
+ * registry, the glyph table and the stacking scale — the values the
29
+ * components above paint with. `design-system.ts` re-exports the members
30
+ * its own surfaces use; this face is the UNION, which is why each of those
31
+ * modules also has its own line below. Where a name arrives by two stars
32
+ * both resolve to the SAME declaration, so nothing is ambiguous.
33
+ *
34
+ * Contract notes:
35
+ * - The rich widgets share one change contract, `ChangeHandlers<T>`
36
+ * (`value` + `onChange` per interaction tick + optional `onChangeEnd`
37
+ * commit — see its doc comment).
38
+ * - Layout and state chrome are API so built-in and project-authored tools
39
+ * cannot drift into parallel UI kits. Domain charts remain project-owned.
40
+ * - ONE SUBPATH: there is no `./design-system` and no `./theme` door, and the
41
+ * deep paths the host used to take (`./editor-material`,
42
+ * `./icon-set-registry`, `./interactive-edit-scope`, `./z-index`,
43
+ * `components/primitives/editor-icons`) all arrive through this module.
44
+ * The kit's dependency closure is deliberately shallow — react, react-dom,
45
+ * Font Awesome, and the SDK's own `looks` types — so a scaffolded
46
+ * project's `tsc` never drags the editor's internals along, and the
47
+ * Apache face never reaches AGPL host source (`interactive-edit-scope.ts`
48
+ * exists precisely to keep `NumberInput`/`ColorInput` off `EditorContext`).
49
+ * Don't add re-exports here that import the editor store, adapters, or
50
+ * server code.
51
+ * - The CLASSES these components paint with (`vgai-btn`, `vgai-input`,
52
+ * `vgai-menu`, …) are declared by the host's own `theme.css`, which stays
53
+ * in `packages/editor/src` because it is the editor's chrome sheet — the
54
+ * TOKENS those rules read are installed at runtime from `theme.ts` here
55
+ * (`editorThemeVariables`).
56
+ *
57
+ * The export lines are ONE alphabetical block because Biome's
58
+ * `organizeImports` sorts them; each line's subject is named above rather
59
+ * than beside it, so a re-sort cannot separate a comment from its target.
60
+ */
61
+
62
+ export * from './design-system';
63
+ export * from './editor-appearance';
64
+ export * from './editor-material';
65
+ export * from './icon-set-registry';
66
+ export * from './inspector-widgets';
67
+ export * from './interactive-edit-scope';
68
+ export * from './primitives/DraftTextInput';
69
+ export * from './primitives/editor-icons';
70
+ export * from './theme';
71
+ export * from './z-index';