@snap-rail/framework-shadcn 0.0.0-stage → 0.2.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.
Files changed (99) hide show
  1. package/README.md +21 -2
  2. package/docs/framework-authoring.md +59 -0
  3. package/lib/fallback.js +315 -0
  4. package/lib/index.js +3074 -0
  5. package/lib/runtime.js +2865 -0
  6. package/lib/semantic.js +104 -0
  7. package/lib/session.js +48 -0
  8. package/lib/settings-pages.js +68 -0
  9. package/lib/slots.js +103 -0
  10. package/lib/types/Shell.d.ts +13 -0
  11. package/lib/types/Shell.js +17 -0
  12. package/lib/types/client.d.ts +41 -0
  13. package/lib/types/client.js +39 -0
  14. package/lib/types/fallback.d.ts +15 -0
  15. package/lib/types/fallback.js +52 -0
  16. package/lib/types/index.d.ts +17 -0
  17. package/lib/types/index.js +17 -0
  18. package/lib/types/runtime.d.ts +67 -0
  19. package/lib/types/runtime.js +81 -0
  20. package/lib/types/semantic.d.ts +55 -0
  21. package/lib/types/semantic.js +63 -0
  22. package/lib/types/session.d.ts +43 -0
  23. package/lib/types/session.js +76 -0
  24. package/lib/types/settings-dialog.d.ts +17 -0
  25. package/lib/types/settings-dialog.js +138 -0
  26. package/lib/types/settings-pages.d.ts +68 -0
  27. package/lib/types/settings-pages.js +85 -0
  28. package/lib/types/settings-plugins-page.d.ts +29 -0
  29. package/lib/types/settings-plugins-page.js +466 -0
  30. package/lib/types/settings-update-page.d.ts +17 -0
  31. package/lib/types/settings-update-page.js +124 -0
  32. package/lib/types/slots.d.ts +122 -0
  33. package/lib/types/slots.js +127 -0
  34. package/lib/types/surface-shell.d.ts +21 -0
  35. package/lib/types/surface-shell.js +17 -0
  36. package/lib/types/ui/badge.d.ts +17 -0
  37. package/lib/types/ui/badge.js +29 -0
  38. package/lib/types/ui/button.d.ts +20 -0
  39. package/lib/types/ui/button.js +39 -0
  40. package/lib/types/ui/card.d.ts +14 -0
  41. package/lib/types/ui/card.js +18 -0
  42. package/lib/types/ui/checkbox.d.ts +10 -0
  43. package/lib/types/ui/checkbox.js +13 -0
  44. package/lib/types/ui/collapsible.d.ts +24 -0
  45. package/lib/types/ui/collapsible.js +31 -0
  46. package/lib/types/ui/dialog.d.ts +22 -0
  47. package/lib/types/ui/dialog.js +29 -0
  48. package/lib/types/ui/drag-scroll.d.ts +13 -0
  49. package/lib/types/ui/drag-scroll.js +85 -0
  50. package/lib/types/ui/hooks.d.ts +16 -0
  51. package/lib/types/ui/hooks.js +26 -0
  52. package/lib/types/ui/icons.d.ts +21 -0
  53. package/lib/types/ui/icons.js +18 -0
  54. package/lib/types/ui/index.d.ts +32 -0
  55. package/lib/types/ui/index.js +32 -0
  56. package/lib/types/ui/input.d.ts +8 -0
  57. package/lib/types/ui/input.js +6 -0
  58. package/lib/types/ui/label.d.ts +9 -0
  59. package/lib/types/ui/label.js +12 -0
  60. package/lib/types/ui/led.d.ts +13 -0
  61. package/lib/types/ui/led.js +11 -0
  62. package/lib/types/ui/number-input.d.ts +35 -0
  63. package/lib/types/ui/number-input.js +48 -0
  64. package/lib/types/ui/progress.d.ts +10 -0
  65. package/lib/types/ui/progress.js +14 -0
  66. package/lib/types/ui/radio-group.d.ts +12 -0
  67. package/lib/types/ui/radio-group.js +17 -0
  68. package/lib/types/ui/schema-form.d.ts +51 -0
  69. package/lib/types/ui/schema-form.js +124 -0
  70. package/lib/types/ui/scroll-area.d.ts +10 -0
  71. package/lib/types/ui/scroll-area.js +13 -0
  72. package/lib/types/ui/switch.d.ts +10 -0
  73. package/lib/types/ui/switch.js +13 -0
  74. package/lib/types/ui/table.d.ts +20 -0
  75. package/lib/types/ui/table.js +28 -0
  76. package/lib/types/ui/tabs.d.ts +16 -0
  77. package/lib/types/ui/tabs.js +23 -0
  78. package/lib/types/ui/textarea.d.ts +10 -0
  79. package/lib/types/ui/textarea.js +6 -0
  80. package/lib/types/ui/theme.d.ts +30 -0
  81. package/lib/types/ui/theme.js +33 -0
  82. package/lib/types/ui/tooltip.d.ts +23 -0
  83. package/lib/types/ui/tooltip.js +15 -0
  84. package/lib/types/ui/touch-select.d.ts +29 -0
  85. package/lib/types/ui/touch-select.js +37 -0
  86. package/lib/types/ui/utils.d.ts +13 -0
  87. package/lib/types/ui/utils.js +16 -0
  88. package/lib/types/variables.d.ts +168 -0
  89. package/lib/types/variables.js +282 -0
  90. package/lib/types/workflows.d.ts +98 -0
  91. package/lib/types/workflows.js +95 -0
  92. package/lib/ui/index.js +1101 -0
  93. package/lib/ui/theme.css +271 -0
  94. package/lib/variables.js +260 -0
  95. package/lib/workflows.js +85 -0
  96. package/package.json +138 -3
  97. package/src/ui/theme.css +271 -0
  98. package/templates/ui-extension/README.md +17 -0
  99. package/templates/ui-view/README.md +17 -0
@@ -0,0 +1,104 @@
1
+ import "@snap-rail/cordis";
2
+ //#region lib/types/slots.js
3
+ /** Stable semantic slot names shared by every layout implementation. */
4
+ const UI_SLOT_IDS = {
5
+ layout: "shell.layout",
6
+ titlebar: "shell.titlebar",
7
+ titlebarActions: "shell.titlebar.actions",
8
+ menu: "shell.menu",
9
+ sidebar: "shell.sidebar",
10
+ main: "shell.main",
11
+ view: "shell.view",
12
+ statusbar: "shell.statusbar",
13
+ overlay: "shell.overlay",
14
+ /**
15
+ * The floating ball (`shell.ball`): the auxiliary-surface face. A package
16
+ * that ships a `ball` client bundle occupies it; when nobody does, the
17
+ * surface renders the framework's default (an orb that summons the operator
18
+ * window). `single`, like every other "one tenant paints this window" slot.
19
+ */
20
+ ball: "shell.ball"
21
+ };
22
+ /** Runtime set used by manifest/semantic validation as well as TypeScript. */
23
+ const STANDARD_UI_SLOT_IDS = new Set(Object.values(UI_SLOT_IDS));
24
+ /** A custom slot is owned by a publisher and a local slot name. */
25
+ const CUSTOM_UI_SLOT_PATTERN = /^custom:[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*$/;
26
+ /** Check the public slot naming contract at runtime. */
27
+ function isValidUiSlotId(value) {
28
+ return STANDARD_UI_SLOT_IDS.has(value) || CUSTOM_UI_SLOT_PATTERN.test(value);
29
+ }
30
+ /**
31
+ * The mode table for the standard slots — the framework owns it.
32
+ *
33
+ * `titlebar` and `main` are `single`: exactly one tenant renders (a skin or
34
+ * replacement displaces the default and falls back on unload). `layout`
35
+ * stays `stack` on purpose — the settings dialog is a permanent overlay
36
+ * occupant beside the active layout, and the fallback shell yields by
37
+ * counting; every other slot collects contributions.
38
+ */
39
+ const UI_SLOT_MODES = {
40
+ [UI_SLOT_IDS.layout]: "stack",
41
+ [UI_SLOT_IDS.titlebar]: "single",
42
+ [UI_SLOT_IDS.titlebarActions]: "stack",
43
+ [UI_SLOT_IDS.menu]: "stack",
44
+ [UI_SLOT_IDS.sidebar]: "stack",
45
+ [UI_SLOT_IDS.main]: "single",
46
+ [UI_SLOT_IDS.view]: "stack",
47
+ [UI_SLOT_IDS.statusbar]: "stack",
48
+ [UI_SLOT_IDS.overlay]: "stack",
49
+ [UI_SLOT_IDS.ball]: "single"
50
+ };
51
+ new Map(Object.entries(UI_SLOT_MODES));
52
+ //#endregion
53
+ //#region lib/types/semantic.js
54
+ /** Mount the semantic contribution registry on top of the slot registry. */
55
+ const semanticPlugin = {
56
+ name: "framework semantic ui contract",
57
+ inject: ["uiSlots"],
58
+ apply(ctx) {
59
+ const slots = ctx.uiSlots;
60
+ const views = /* @__PURE__ */ new Map();
61
+ const layouts = /* @__PURE__ */ new Map();
62
+ const navigation = /* @__PURE__ */ new Map();
63
+ let sequence = 0;
64
+ const ordered = (values) => [...values].sort((a, b) => a.value.order - b.value.order || a.seq - b.seq).map((item) => item.value);
65
+ const register = (table, caller, id, value, disposeSlot) => {
66
+ table.set(id, {
67
+ value,
68
+ seq: sequence++
69
+ });
70
+ const kind = table === views ? "view" : table === layouts ? "layout" : "navigation";
71
+ ctx.emit("ui/semantic-changed", kind);
72
+ const remove = () => {
73
+ if (!table.delete(id)) return;
74
+ disposeSlot?.();
75
+ ctx.emit("ui/semantic-changed", kind);
76
+ };
77
+ caller.effect(() => remove);
78
+ return remove;
79
+ };
80
+ const validateSlot = (kind, id, slot) => {
81
+ if (!isValidUiSlotId(slot)) ctx.emit("ui/semantic-invalid", kind, id, slot);
82
+ };
83
+ ctx.provide("uiContract", {
84
+ registerView(caller, contribution) {
85
+ validateSlot("view", contribution.id, contribution.slot);
86
+ const disposeSlot = slots.register(caller, contribution.slot, contribution);
87
+ return register(views, caller, contribution.id, contribution, disposeSlot);
88
+ },
89
+ registerLayout(caller, contribution) {
90
+ for (const slot of contribution.slots) validateSlot("layout", contribution.id, slot);
91
+ const disposeSlot = slots.register(caller, UI_SLOT_IDS.layout, contribution);
92
+ return register(layouts, caller, contribution.id, contribution, disposeSlot);
93
+ },
94
+ registerNavigation(caller, contribution) {
95
+ return register(navigation, caller, contribution.id, contribution);
96
+ },
97
+ listViews: () => ordered(views.values()),
98
+ listLayouts: () => ordered(layouts.values()),
99
+ listNavigation: () => ordered(navigation.values())
100
+ });
101
+ }
102
+ };
103
+ //#endregion
104
+ export { semanticPlugin as default };
package/lib/session.js ADDED
@@ -0,0 +1,48 @@
1
+ import { SESSION_OPERATOR_KEY, settingsChangedSchema } from "@snap-rail/station-rpc/contract";
2
+ import "@snap-rail/cordis";
3
+ import { subscribeFrame } from "@snap-rail/connection";
4
+ //#region lib/types/session.js
5
+ /** Mounts the `ctx.session` mirror; state lives in the apply closure. */
6
+ const sessionPlugin = {
7
+ name: "client-session",
8
+ inject: ["client"],
9
+ apply(ctx) {
10
+ const { link } = ctx.client;
11
+ let operator = null;
12
+ let restored = false;
13
+ const sync = (next) => {
14
+ operator = next;
15
+ ctx.emit("session/changed", operator);
16
+ };
17
+ link.call("session.current", {}).then((result) => {
18
+ if (result.ok) sync(result.value.operator);
19
+ }).catch(() => {}).finally(() => {
20
+ restored = true;
21
+ ctx.emit("session/restored");
22
+ });
23
+ ctx.effect(() => subscribeFrame(link, "settings/changed", settingsChangedSchema, (change) => {
24
+ if (change.key !== SESSION_OPERATOR_KEY) return;
25
+ link.call("session.current", {}).then((result) => {
26
+ if (result.ok && result.value.operator !== operator) sync(result.value.operator);
27
+ }).catch(() => {});
28
+ }));
29
+ ctx.provide("session", {
30
+ current: () => operator,
31
+ restored: () => restored,
32
+ async login(raw) {
33
+ const trimmed = raw.trim();
34
+ if (trimmed === "") throw new Error("工号不能为空");
35
+ const result = await link.call("session.login", { operator: trimmed });
36
+ if (!result.ok) throw new Error(`登录失败(${result.error.code})`);
37
+ sync(trimmed);
38
+ },
39
+ async logout() {
40
+ const result = await link.call("session.logout", {});
41
+ if (!result.ok) throw new Error(`退出失败(${result.error.code})`);
42
+ sync(null);
43
+ }
44
+ });
45
+ }
46
+ };
47
+ //#endregion
48
+ export { sessionPlugin as default };
@@ -0,0 +1,68 @@
1
+ import "@snap-rail/cordis";
2
+ //#region lib/types/settings-pages.js
3
+ /**
4
+ * Mounts the `ctx.settingsPages` registry. All state lives in
5
+ * construction-time closures: traceable context proxies rebind `this` on
6
+ * every method access, so `this`-reached state cannot back a service here
7
+ * (the uiSlots lesson).
8
+ */
9
+ const settingsPlugin = {
10
+ name: "client-settings",
11
+ apply(ctx) {
12
+ const pages = /* @__PURE__ */ new Map();
13
+ let nextSeq = 0;
14
+ let open = false;
15
+ let requestedId;
16
+ const firstId = () => [...pages.values()].sort((a, b) => a.def.order - b.def.order || a.seq - b.seq)[0]?.def.id;
17
+ const activeId = () => {
18
+ if (!open) return void 0;
19
+ if (requestedId !== void 0 && pages.has(requestedId)) return requestedId;
20
+ return firstId();
21
+ };
22
+ const openDialog = (id) => {
23
+ if (id !== void 0) requestedId = id;
24
+ if (open && id === void 0) return;
25
+ open = true;
26
+ ctx.emit("settings/open-changed");
27
+ };
28
+ const closeDialog = () => {
29
+ if (!open) return;
30
+ open = false;
31
+ ctx.emit("settings/open-changed");
32
+ };
33
+ ctx.provide("settingsPages", {
34
+ list() {
35
+ const current = activeId();
36
+ return [...pages.values()].sort((a, b) => a.def.order - b.def.order || a.seq - b.seq).map(({ def }) => ({
37
+ ...def,
38
+ active: def.id === current
39
+ }));
40
+ },
41
+ register(caller, def) {
42
+ const replaced = pages.has(def.id);
43
+ pages.set(def.id, {
44
+ def,
45
+ seq: nextSeq++
46
+ });
47
+ ctx.emit(replaced ? "settings/open-changed" : "settings/pages-changed");
48
+ const remove = () => {
49
+ if (pages.delete(def.id)) ctx.emit("settings/pages-changed");
50
+ };
51
+ caller.effect(() => remove);
52
+ return remove;
53
+ },
54
+ open: openDialog,
55
+ close: closeDialog,
56
+ toggle() {
57
+ if (open) closeDialog();
58
+ else openDialog();
59
+ },
60
+ isOpen() {
61
+ return open;
62
+ },
63
+ active: activeId
64
+ });
65
+ }
66
+ };
67
+ //#endregion
68
+ export { settingsPlugin as default };
package/lib/slots.js ADDED
@@ -0,0 +1,103 @@
1
+ import "@snap-rail/cordis";
2
+ //#region lib/types/slots.js
3
+ /** Stable semantic slot names shared by every layout implementation. */
4
+ const UI_SLOT_IDS = {
5
+ layout: "shell.layout",
6
+ titlebar: "shell.titlebar",
7
+ titlebarActions: "shell.titlebar.actions",
8
+ menu: "shell.menu",
9
+ sidebar: "shell.sidebar",
10
+ main: "shell.main",
11
+ view: "shell.view",
12
+ statusbar: "shell.statusbar",
13
+ overlay: "shell.overlay",
14
+ /**
15
+ * The floating ball (`shell.ball`): the auxiliary-surface face. A package
16
+ * that ships a `ball` client bundle occupies it; when nobody does, the
17
+ * surface renders the framework's default (an orb that summons the operator
18
+ * window). `single`, like every other "one tenant paints this window" slot.
19
+ */
20
+ ball: "shell.ball"
21
+ };
22
+ /** Runtime set used by manifest/semantic validation as well as TypeScript. */
23
+ const STANDARD_UI_SLOT_IDS = new Set(Object.values(UI_SLOT_IDS));
24
+ /** A custom slot is owned by a publisher and a local slot name. */
25
+ const CUSTOM_UI_SLOT_PATTERN = /^custom:[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*$/;
26
+ /** Check the public slot naming contract at runtime. */
27
+ function isValidUiSlotId(value) {
28
+ return STANDARD_UI_SLOT_IDS.has(value) || CUSTOM_UI_SLOT_PATTERN.test(value);
29
+ }
30
+ /**
31
+ * The mode table for the standard slots — the framework owns it.
32
+ *
33
+ * `titlebar` and `main` are `single`: exactly one tenant renders (a skin or
34
+ * replacement displaces the default and falls back on unload). `layout`
35
+ * stays `stack` on purpose — the settings dialog is a permanent overlay
36
+ * occupant beside the active layout, and the fallback shell yields by
37
+ * counting; every other slot collects contributions.
38
+ */
39
+ const UI_SLOT_MODES = {
40
+ [UI_SLOT_IDS.layout]: "stack",
41
+ [UI_SLOT_IDS.titlebar]: "single",
42
+ [UI_SLOT_IDS.titlebarActions]: "stack",
43
+ [UI_SLOT_IDS.menu]: "stack",
44
+ [UI_SLOT_IDS.sidebar]: "stack",
45
+ [UI_SLOT_IDS.main]: "single",
46
+ [UI_SLOT_IDS.view]: "stack",
47
+ [UI_SLOT_IDS.statusbar]: "stack",
48
+ [UI_SLOT_IDS.overlay]: "stack",
49
+ [UI_SLOT_IDS.ball]: "single"
50
+ };
51
+ const STANDARD_SLOT_MODES = new Map(Object.entries(UI_SLOT_MODES));
52
+ /** The occupancy mode of a slot; `custom:*` slots are `stack`. */
53
+ function slotModeOf(slot) {
54
+ return STANDARD_SLOT_MODES.get(slot) ?? "stack";
55
+ }
56
+ /** Mounts the `ctx.uiSlots` registry.
57
+ *
58
+ * The facade binds everything to construction-time closures: traceable
59
+ * context proxies rebind `this` on every method access, so `this`-reached
60
+ * state cannot back a service here (mirrors the field seam's external core).
61
+ */
62
+ const slotsPlugin = {
63
+ name: "framework slots",
64
+ apply(ctx) {
65
+ const slots = /* @__PURE__ */ new Map();
66
+ const entryOf = (slot) => {
67
+ let entry = slots.get(slot);
68
+ if (entry === void 0) {
69
+ entry = {
70
+ occupants: /* @__PURE__ */ new Map(),
71
+ nextSeq: 0
72
+ };
73
+ slots.set(slot, entry);
74
+ }
75
+ return entry;
76
+ };
77
+ ctx.provide("uiSlots", {
78
+ list(slot) {
79
+ const ranked = [...entryOf(slot).occupants.values()].sort((a, b) => a.occupant.order - b.occupant.order || a.seq - b.seq);
80
+ if (slotModeOf(slot) === "single" && ranked.length > 0) return [ranked.reduce((best, item) => {
81
+ const byPrecedence = (item.occupant.precedence ?? 0) - (best.occupant.precedence ?? 0);
82
+ return byPrecedence > 0 || byPrecedence === 0 && item.seq > best.seq ? item : best;
83
+ }).occupant];
84
+ return ranked.map((item) => item.occupant);
85
+ },
86
+ register(caller, slot, occupant) {
87
+ const entry = entryOf(slot);
88
+ entry.occupants.set(occupant.id, {
89
+ occupant,
90
+ seq: entry.nextSeq++
91
+ });
92
+ ctx.emit("ui/slot-changed", slot);
93
+ const remove = () => {
94
+ if (entry.occupants.delete(occupant.id)) ctx.emit("ui/slot-changed", slot);
95
+ };
96
+ caller.effect(() => remove);
97
+ return remove;
98
+ }
99
+ });
100
+ }
101
+ };
102
+ //#endregion
103
+ export { CUSTOM_UI_SLOT_PATTERN, STANDARD_UI_SLOT_IDS, UI_SLOT_IDS, UI_SLOT_MODES, slotsPlugin as default, isValidUiSlotId, slotModeOf };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The slot-driven shell: renders every registered layout occupant, or the
3
+ * degradation notice when none did. Any `ui/slot-changed` emission re-renders
4
+ * the whole shell — occupants stay tiny; reconciliation is React's problem.
5
+ *
6
+ * @module @snap-rail/framework-shadcn/runtime/Shell
7
+ */
8
+ import type { Context } from '@snap-rail/cordis';
9
+ import { type ReactNode } from 'react';
10
+ /** Shell root; mounted once by {@link ../index.tsx | createClientRuntime}. */
11
+ export declare function Shell(props: {
12
+ ctx: Context;
13
+ }): ReactNode;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Fragment, useEffect, useState } from 'react';
3
+ import { UI_SLOT_IDS } from "./slots.js";
4
+ /** Shell root; mounted once by {@link ../index.tsx | createClientRuntime}. */
5
+ export function Shell(props) {
6
+ const [tick, setTick] = useState(0);
7
+ useEffect(() => {
8
+ const detach = props.ctx.on('ui/slot-changed', () => setTick(value => value + 1));
9
+ return () => { detach(); };
10
+ }, [props.ctx]);
11
+ const layouts = props.ctx.uiSlots.list(UI_SLOT_IDS.layout);
12
+ const content = layouts.length > 0
13
+ ? layouts.map(occupant => _jsx(Fragment, { children: occupant.render() }, occupant.id))
14
+ : _jsx("div", { className: "m-auto text-muted-foreground", children: "\u6CA1\u6709\u5DF2\u52A0\u8F7D\u7684\u5E03\u5C40\u63D2\u4EF6\u3002" });
15
+ // `tick` participates so slot changes re-run the render.
16
+ return _jsx("div", { className: "flex h-full flex-col", "data-tick": tick, children: content });
17
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The framework's installable client face: one bundle carrying every face —
3
+ * the runtime, the slot registry, the shared primitives, the client
4
+ * services, and the fallback layout — re-exported as the contributed-seed
5
+ * map. The shell requires this face once and seeds each
6
+ * `@snap-rail/framework-shadcn/<key>` id from the map, so every installed
7
+ * component shares these exact instances (one slots registry, one primitives
8
+ * set, one runtime entry).
9
+ *
10
+ * @module @snap-rail/framework-shadcn/client
11
+ */
12
+ import * as fallback from './fallback.tsx';
13
+ import * as runtime from './runtime.tsx';
14
+ import * as session from './session.ts';
15
+ import * as settingsPages from './settings-pages.ts';
16
+ import * as slots from './slots.tsx';
17
+ import * as semantic from './semantic.tsx';
18
+ import * as ui from './ui/index.ts';
19
+ import * as variables from './variables.ts';
20
+ import * as workflows from './workflows.ts';
21
+ /** The faces this framework contributes as shared seeds. */
22
+ export interface FrameworkSeedFaces {
23
+ ui: typeof ui;
24
+ slots: typeof slots;
25
+ semantic: typeof semantic;
26
+ 'settings-pages': typeof settingsPages;
27
+ session: typeof session;
28
+ variables: typeof variables;
29
+ workflows: typeof workflows;
30
+ runtime: typeof runtime;
31
+ fallback: typeof fallback;
32
+ }
33
+ /**
34
+ * The client-face default export: the contributed-seed map. No plugin
35
+ * object rides along — the framework mounts nothing into an occupant seat;
36
+ * the runtime face's `createClientRuntime` is the integration point.
37
+ */
38
+ declare const frameworkClientFace: {
39
+ seeds: FrameworkSeedFaces;
40
+ };
41
+ export default frameworkClientFace;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The framework's installable client face: one bundle carrying every face —
3
+ * the runtime, the slot registry, the shared primitives, the client
4
+ * services, and the fallback layout — re-exported as the contributed-seed
5
+ * map. The shell requires this face once and seeds each
6
+ * `@snap-rail/framework-shadcn/<key>` id from the map, so every installed
7
+ * component shares these exact instances (one slots registry, one primitives
8
+ * set, one runtime entry).
9
+ *
10
+ * @module @snap-rail/framework-shadcn/client
11
+ */
12
+ import * as fallback from "./fallback.js";
13
+ import * as runtime from "./runtime.js";
14
+ import * as session from "./session.js";
15
+ import * as settingsPages from "./settings-pages.js";
16
+ import * as slots from "./slots.js";
17
+ import * as semantic from "./semantic.js";
18
+ import * as ui from "./ui/index.js";
19
+ import * as variables from "./variables.js";
20
+ import * as workflows from "./workflows.js";
21
+ /**
22
+ * The client-face default export: the contributed-seed map. No plugin
23
+ * object rides along — the framework mounts nothing into an occupant seat;
24
+ * the runtime face's `createClientRuntime` is the integration point.
25
+ */
26
+ const frameworkClientFace = {
27
+ seeds: {
28
+ ui,
29
+ slots,
30
+ semantic,
31
+ 'settings-pages': settingsPages,
32
+ session,
33
+ variables,
34
+ workflows,
35
+ runtime,
36
+ fallback,
37
+ },
38
+ };
39
+ export default frameworkClientFace;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The fallback shell (兜底壳): the built-in minimal layout that shows when
3
+ * no business suite is active — a bare titlebar (window controls + the
4
+ * settings entry, so a fresh install can always reach the plugin manager)
5
+ * and an empty state pointing there. It yields the moment a suite layout
6
+ * registers: it renders only while it is the sole full-screen layout
7
+ * occupant (the settings dialog's own occupant is a portal, not a layout).
8
+ *
9
+ * @module @snap-rail/framework-shadcn/fallback
10
+ */
11
+ import { type Plugin } from '@snap-rail/cordis';
12
+ import './settings-pages.ts';
13
+ /** The fallback shell occupant. */
14
+ declare const fallbackShellPlugin: Plugin.Object<void>;
15
+ export default fallbackShellPlugin;
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The fallback shell (兜底壳): the built-in minimal layout that shows when
4
+ * no business suite is active — a bare titlebar (window controls + the
5
+ * settings entry, so a fresh install can always reach the plugin manager)
6
+ * and an empty state pointing there. It yields the moment a suite layout
7
+ * registers: it renders only while it is the sole full-screen layout
8
+ * occupant (the settings dialog's own occupant is a portal, not a layout).
9
+ *
10
+ * @module @snap-rail/framework-shadcn/fallback
11
+ */
12
+ import { Context } from '@snap-rail/cordis';
13
+ import "./settings-pages.js";
14
+ import { UI_SLOT_IDS } from "./slots.js";
15
+ import { Button, useRefresh } from "./ui/index.js";
16
+ import { Fragment } from 'react';
17
+ /** The settings dialog's occupant id (a Dialog portal, not a layout). */
18
+ const SETTINGS_OCCUPANT_ID = 'settings-dialog';
19
+ /** Window-control glyphs; inline so the shell needs no icon dependency. */
20
+ function ControlIcons(props) {
21
+ const base = 'flex h-10 w-10 items-center justify-center text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground';
22
+ return (_jsxs("div", { className: "flex items-center", children: [_jsx("button", { type: "button", "aria-label": "\u6700\u5C0F\u5316", className: base, onClick: props.onMinimize, children: _jsx("svg", { viewBox: "0 0 12 12", className: "h-3 w-3", fill: "none", children: _jsx("path", { d: "M2 6h8", stroke: "currentColor", strokeWidth: "1.4" }) }) }), _jsx("button", { type: "button", "aria-label": "\u6700\u5927\u5316", className: base, onClick: props.onToggleMaximize, children: _jsx("svg", { viewBox: "0 0 12 12", className: "h-3 w-3", fill: "none", children: _jsx("rect", { x: "2.5", y: "2.5", width: "7", height: "7", stroke: "currentColor", strokeWidth: "1.2" }) }) }), _jsx("button", { type: "button", "aria-label": "\u5173\u95ED", className: `${base} hover:bg-destructive hover:text-white`, onClick: props.onClose, children: _jsx("svg", { viewBox: "0 0 12 12", className: "h-3 w-3", fill: "none", children: _jsx("path", { d: "m3 3 6 6M9 3l-6 6", stroke: "currentColor", strokeWidth: "1.4" }) }) })] }));
23
+ }
24
+ /** The fallback body: minimal titlebar + install guidance. */
25
+ function FallbackShell(props) {
26
+ useRefresh(props.ctx, ['ui/slot-changed', 'settings/open-changed']);
27
+ const layouts = props.ctx.uiSlots.list(UI_SLOT_IDS.layout);
28
+ const fullScreen = layouts.filter(occupant => occupant.id !== SETTINGS_OCCUPANT_ID);
29
+ // A suite layout took over: render nothing.
30
+ if (fullScreen.length > 1)
31
+ return null;
32
+ const windowControl = (action) => {
33
+ void props.link.call('window.control', { action }).catch(() => undefined);
34
+ };
35
+ return (_jsxs("div", { className: "flex h-full flex-col", "data-region": "fallback-shell", children: [_jsxs("div", { className: "flex h-10 shrink-0 items-center justify-between border-b border-border pl-4 select-none", style: { WebkitAppRegion: 'drag' }, children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "snap-rail" }), _jsxs("div", { className: "flex items-center", style: { WebkitAppRegion: 'no-drag' }, children: [props.ctx.uiSlots.list(UI_SLOT_IDS.titlebarActions).map(occupant => (_jsx(Fragment, { children: occupant.render() }, occupant.id))), _jsx("button", { type: "button", "aria-label": "\u8BBE\u7F6E", className: "flex h-10 w-10 items-center justify-center text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground", onClick: () => props.ctx.settingsPages.open(), children: _jsxs("svg", { viewBox: "0 0 24 24", className: "h-5 w-5", fill: "none", stroke: "currentColor", strokeWidth: "1.6", children: [_jsx("circle", { cx: "12", cy: "12", r: "3.2" }), _jsx("path", { d: "M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1 1.55V21a2 2 0 1 1-4 0v-.09a1.7 1.7 0 0 0-1-1.55 1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.7 1.7 0 0 0 .34-1.87 1.7 1.7 0 0 0-1.55-1H3a2 2 0 1 1 0-4h.09a1.7 1.7 0 0 0 1.55-1 1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.7 1.7 0 0 0 1.87.34h0a1.7 1.7 0 0 0 1-1.55V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1 1.55h0a1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.7 1.7 0 0 0-.34 1.87v0a1.7 1.7 0 0 0 1.55 1H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.55 1Z" })] }) }), _jsx(ControlIcons, { onMinimize: () => windowControl('minimize'), onToggleMaximize: () => windowControl('toggle-maximize'), onClose: () => windowControl('close') })] })] }), _jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center p-6", children: _jsxs("div", { className: "grid max-w-md gap-4 text-center", children: [_jsx("div", { className: "text-xl font-medium text-foreground", children: "\u5C1A\u672A\u6FC0\u6D3B\u4E1A\u52A1\u5957\u4EF6" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "\u6253\u5F00\u8BBE\u7F6E\u7684\u300C\u63D2\u4EF6\u7BA1\u7406\u300D\u9875\uFF0C\u5B89\u88C5\u5E76\u6FC0\u6D3B\u4E00\u4E2A\u4E1A\u52A1\u5957\u4EF6\uFF1B\u901A\u8BAF\u9A71\u52A8\u4E5F\u5728\u540C\u4E00\u5904\u5B89\u88C5\u3002" }), _jsx("div", { children: _jsx(Button, { "data-fallback-open-settings": true, onClick: () => props.ctx.settingsPages.open(), children: "\u6253\u5F00\u8BBE\u7F6E" }) })] }) })] }));
36
+ }
37
+ /** The fallback shell occupant. */
38
+ const fallbackShellPlugin = {
39
+ name: 'framework fallback',
40
+ inject: ['uiSlots', 'settingsPages'],
41
+ apply(ctx) {
42
+ const client = ctx.get('client');
43
+ ctx.uiSlots.register(ctx, UI_SLOT_IDS.layout, {
44
+ id: 'fallback-shell',
45
+ order: 200,
46
+ render() {
47
+ return _jsx(FallbackShell, { ctx: ctx, link: client.link });
48
+ },
49
+ });
50
+ },
51
+ };
52
+ export default fallbackShellPlugin;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The framework root barrel: one import site for the whole UI substrate —
3
+ * the renderer runtime, the slot registry, the shared shadcn-style
4
+ * primitives, the theme tokens, and the settings/session/variables/workflows
5
+ * services. Component plugins bind to this package through
6
+ * `snapRail.framework`; replacing it is an ecosystem switch, not a hot swap.
7
+ *
8
+ * @module @snap-rail/framework-shadcn
9
+ */
10
+ export * from './runtime.tsx';
11
+ export * from './slots.tsx';
12
+ export * from './semantic.tsx';
13
+ export * from './settings-pages.ts';
14
+ export * from './session.ts';
15
+ export * from './variables.ts';
16
+ export * from './workflows.ts';
17
+ export * from './fallback.tsx';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The framework root barrel: one import site for the whole UI substrate —
3
+ * the renderer runtime, the slot registry, the shared shadcn-style
4
+ * primitives, the theme tokens, and the settings/session/variables/workflows
5
+ * services. Component plugins bind to this package through
6
+ * `snapRail.framework`; replacing it is an ecosystem switch, not a hot swap.
7
+ *
8
+ * @module @snap-rail/framework-shadcn
9
+ */
10
+ export * from "./runtime.js";
11
+ export * from "./slots.js";
12
+ export * from "./semantic.js";
13
+ export * from "./settings-pages.js";
14
+ export * from "./session.js";
15
+ export * from "./variables.js";
16
+ export * from "./workflows.js";
17
+ export * from "./fallback.js";
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The client renderer host: takes over the React root from the kernel,
3
+ * mounts the phase-1 occupant plugins into one client Cordis context, and
4
+ * renders the slot-driven shell that re-renders whenever any occupied slot
5
+ * changes. When no layout plugin registered, the shell degrades to a notice
6
+ * instead of an empty window.
7
+ *
8
+ * @module @snap-rail/framework-shadcn/runtime
9
+ */
10
+ import { Context, type Plugin } from '@snap-rail/cordis';
11
+ import type { ClientHandle } from '@snap-rail/client-kernel';
12
+ import type { HostLink } from '@snap-rail/connection';
13
+ import type { SurfaceId } from '@snap-rail/protocol';
14
+ export type { ClientHandle } from '@snap-rail/client-kernel';
15
+ declare module '@snap-rail/cordis' {
16
+ interface Context {
17
+ /** Shared client-side services every occupant may consume. */
18
+ client: ClientServices;
19
+ }
20
+ }
21
+ /** Services mounted once for all occupant plugins. */
22
+ export interface ClientServices {
23
+ /** The typed host link shared by every occupant. */
24
+ link: HostLink;
25
+ /**
26
+ * Which window this client tree serves (`main` or `ball`). A face bound to
27
+ * the auxiliary surface reads it to pick its compact layout, and a face
28
+ * that only makes sense on `main` can refuse to mount there.
29
+ */
30
+ surface: SurfaceId;
31
+ }
32
+ /** One occupant seat: the plugin plus the config carved from its plugins.yml row. */
33
+ export interface OccupantSpec {
34
+ /** The occupant plugin (layout, chrome, process pages). */
35
+ plugin: Plugin;
36
+ /** The occupant's config when its user-layer row carries one. */
37
+ config?: unknown;
38
+ }
39
+ /** Options for {@link createClientRuntime}. */
40
+ export interface RuntimeOptions {
41
+ /**
42
+ * Occupant plugins (layout, chrome, process pages) — a bare plugin or one
43
+ * paired with config. Phase 1 ships them from an in-app list; the
44
+ * boot-graph-driven loader replaces this seat when plugin directories ship
45
+ * outside the bundle.
46
+ */
47
+ plugins: readonly (Plugin | OccupantSpec)[];
48
+ /**
49
+ * The window this runtime serves; defaults to `main`. The auxiliary surface
50
+ * gets the same spine services but its own shell: one slot paints the whole
51
+ * window (`shell.ball`), and the operator shell — layouts, titlebar,
52
+ * views — is never mounted into a 128px orb.
53
+ */
54
+ surface?: SurfaceId | undefined;
55
+ }
56
+ export interface RuntimeHandle {
57
+ ctx: Context;
58
+ dispose(): Promise<void>;
59
+ }
60
+ /**
61
+ * Take over the booted root: stop the startup page, mount the client plugin
62
+ * tree, render the shell.
63
+ *
64
+ * @param handle - the kernel's boot result.
65
+ * @param options - see {@link RuntimeOptions}.
66
+ */
67
+ export declare function createClientRuntime(handle: ClientHandle, options: RuntimeOptions): Promise<RuntimeHandle>;