@revenexx/studio-shared 0.3.1 → 0.3.3

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 (85) hide show
  1. package/dist/module.d.mts +4 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +9 -0
  4. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.d.vue.ts +15 -0
  5. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +39 -0
  6. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue.d.ts +15 -0
  7. package/dist/runtime/components/Dashboard/DashboardControls.d.vue.ts +57 -0
  8. package/dist/runtime/components/Dashboard/DashboardControls.vue +209 -0
  9. package/dist/runtime/components/Dashboard/DashboardControls.vue.d.ts +57 -0
  10. package/dist/runtime/components/Dashboard/DashboardGrid.d.vue.ts +16 -0
  11. package/dist/runtime/components/Dashboard/DashboardGrid.vue +87 -0
  12. package/dist/runtime/components/Dashboard/DashboardGrid.vue.d.ts +16 -0
  13. package/dist/runtime/components/Dashboard/DashboardNameDialog.d.vue.ts +18 -0
  14. package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +61 -0
  15. package/dist/runtime/components/Dashboard/DashboardNameDialog.vue.d.ts +18 -0
  16. package/dist/runtime/components/Dashboard/DashboardPickerDialog.d.vue.ts +14 -0
  17. package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +100 -0
  18. package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue.d.ts +14 -0
  19. package/dist/runtime/components/Dashboard/DashboardShareDialog.d.vue.ts +27 -0
  20. package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +155 -0
  21. package/dist/runtime/components/Dashboard/DashboardShareDialog.vue.d.ts +27 -0
  22. package/dist/runtime/components/Dashboard/DashboardTopBar.d.vue.ts +59 -0
  23. package/dist/runtime/components/Dashboard/DashboardTopBar.vue +79 -0
  24. package/dist/runtime/components/Dashboard/DashboardTopBar.vue.d.ts +59 -0
  25. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.d.vue.ts +42 -0
  26. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +153 -0
  27. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue.d.ts +42 -0
  28. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +27 -0
  29. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +80 -0
  30. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +27 -0
  31. package/dist/runtime/components/Dashboard/DashboardWidgetHost.d.vue.ts +6 -0
  32. package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue +30 -0
  33. package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue.d.ts +6 -0
  34. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.d.vue.ts +8 -0
  35. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue +13 -0
  36. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue.d.ts +8 -0
  37. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.d.vue.ts +23 -0
  38. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue +140 -0
  39. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue.d.ts +23 -0
  40. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.d.vue.ts +22 -0
  41. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue +124 -0
  42. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue.d.ts +22 -0
  43. package/dist/runtime/components/DataTable/DataTable.d.vue.ts +39 -3
  44. package/dist/runtime/components/DataTable/DataTable.vue +306 -32
  45. package/dist/runtime/components/DataTable/DataTable.vue.d.ts +39 -3
  46. package/dist/runtime/components/DataTable/FacetChips.d.vue.ts +16 -0
  47. package/dist/runtime/components/DataTable/FacetChips.vue +72 -0
  48. package/dist/runtime/components/DataTable/FacetChips.vue.d.ts +16 -0
  49. package/dist/runtime/components/DataTable/FacetPanel.d.vue.ts +20 -0
  50. package/dist/runtime/components/DataTable/FacetPanel.vue +359 -0
  51. package/dist/runtime/components/DataTable/FacetPanel.vue.d.ts +20 -0
  52. package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +5 -1
  53. package/dist/runtime/components/DataTable/SavedViewsMenu.vue +83 -63
  54. package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +5 -1
  55. package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +5 -1
  56. package/dist/runtime/components/DataTable/SortMenu.vue +40 -12
  57. package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +5 -1
  58. package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +2 -2
  59. package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +2 -2
  60. package/dist/runtime/components/Media/MediaUploader.d.vue.ts +2 -2
  61. package/dist/runtime/components/Media/MediaUploader.vue.d.ts +2 -2
  62. package/dist/runtime/components/Shared/SortHeader.vue +6 -2
  63. package/dist/runtime/components/Shared/TabBar.d.vue.ts +23 -7
  64. package/dist/runtime/components/Shared/TabBar.vue +3 -2
  65. package/dist/runtime/components/Shared/TabBar.vue.d.ts +23 -7
  66. package/dist/runtime/composables/useDashboards.d.ts +27 -0
  67. package/dist/runtime/composables/useDashboards.js +143 -0
  68. package/dist/runtime/composables/useSavedViews.d.ts +44 -0
  69. package/dist/runtime/composables/useSavedViews.js +44 -0
  70. package/dist/runtime/composables/useStickyListView.d.ts +60 -0
  71. package/dist/runtime/composables/useStickyListView.js +32 -0
  72. package/dist/runtime/lib/canonicalJson.d.ts +43 -0
  73. package/dist/runtime/lib/canonicalJson.js +38 -0
  74. package/dist/runtime/lib/dashboardIcons.d.ts +4 -0
  75. package/dist/runtime/lib/dashboardIcons.js +50 -0
  76. package/dist/runtime/lib/facets.d.ts +200 -0
  77. package/dist/runtime/lib/facets.js +171 -0
  78. package/dist/runtime/lib/gridTracks.d.ts +55 -0
  79. package/dist/runtime/lib/gridTracks.js +29 -0
  80. package/dist/runtime/lib/widgetRegistry.d.ts +124 -0
  81. package/dist/runtime/lib/widgetRegistry.js +66 -0
  82. package/dist/runtime/types/dashboard.d.ts +95 -0
  83. package/dist/runtime/types/dashboard.js +2 -0
  84. package/dist/types.d.mts +8 -0
  85. package/package.json +4 -3
@@ -0,0 +1,171 @@
1
+ export function isEmptyFacetValue(value) {
2
+ if (value === null || value === void 0) return true;
3
+ if (Array.isArray(value)) return value.length === 0;
4
+ if (typeof value === "string") return value.trim() === "";
5
+ if (typeof value === "object") {
6
+ const range = value;
7
+ return !range.from?.trim() && !range.to?.trim();
8
+ }
9
+ return false;
10
+ }
11
+ export function pruneSelection(selection) {
12
+ const out = {};
13
+ for (const [key, value] of Object.entries(selection)) {
14
+ if (!isEmptyFacetValue(value)) out[key] = value;
15
+ }
16
+ return out;
17
+ }
18
+ export function countInFacet(selection, key) {
19
+ const value = selection[key];
20
+ if (isEmptyFacetValue(value)) return 0;
21
+ return Array.isArray(value) ? value.length : 1;
22
+ }
23
+ export function countSelected(facets, selection) {
24
+ return facets.reduce((n, facet) => n + countInFacet(selection, facet.key), 0);
25
+ }
26
+ export function flattenNodes(nodes, expanded) {
27
+ const out = [];
28
+ const walk = (list, depth) => {
29
+ for (const node of list) {
30
+ out.push({ node, depth });
31
+ if (node.children?.length && expanded.has(node.value)) walk(node.children, depth + 1);
32
+ }
33
+ };
34
+ walk(nodes, 0);
35
+ return out;
36
+ }
37
+ export function allNodes(nodes) {
38
+ const out = [];
39
+ const walk = (list) => {
40
+ for (const node of list) {
41
+ out.push(node);
42
+ if (node.children?.length) walk(node.children);
43
+ }
44
+ };
45
+ walk(nodes);
46
+ return out;
47
+ }
48
+ export function nodePath(nodes, value) {
49
+ const walk = (list, trail) => {
50
+ for (const node of list) {
51
+ const here = [...trail, node];
52
+ if (node.value === value) return here;
53
+ if (node.children?.length) {
54
+ const hit = walk(node.children, here);
55
+ if (hit) return hit;
56
+ }
57
+ }
58
+ return null;
59
+ };
60
+ return walk(nodes, []);
61
+ }
62
+ export function expandTo(nodes, value) {
63
+ if (!value) return /* @__PURE__ */ new Set();
64
+ const path = nodePath(nodes, value);
65
+ if (!path) return /* @__PURE__ */ new Set();
66
+ return new Set(path.slice(0, -1).map((node) => node.value));
67
+ }
68
+ export function initialExpanded(nodes) {
69
+ return new Set(nodes.filter((node) => node.children?.length).map((node) => node.value));
70
+ }
71
+ export function facetValueIds(key, value) {
72
+ if (isEmptyFacetValue(value)) return [];
73
+ if (Array.isArray(value)) return value.map((one) => `${key}:${one}`);
74
+ return [`${key}:`];
75
+ }
76
+ function optionLabel(facet, value) {
77
+ if (facet.kind === "tree") {
78
+ const path = nodePath(facet.nodes ?? [], value);
79
+ return path?.length ? path.map((node) => node.label).join(" \u203A ") : value;
80
+ }
81
+ return facet.options?.find((option) => option.value === value)?.label ?? value;
82
+ }
83
+ export function appliedFacets(facets, selection, format) {
84
+ const out = [];
85
+ for (const facet of facets) {
86
+ const value = selection[facet.key];
87
+ if (isEmptyFacetValue(value)) continue;
88
+ if (Array.isArray(value)) {
89
+ for (const one of value) {
90
+ out.push({
91
+ id: `${facet.key}:${one}`,
92
+ facetKey: facet.key,
93
+ facetLabel: facet.label,
94
+ label: optionLabel(facet, one),
95
+ value: one
96
+ });
97
+ }
98
+ continue;
99
+ }
100
+ out.push({
101
+ id: `${facet.key}:`,
102
+ facetKey: facet.key,
103
+ facetLabel: facet.label,
104
+ label: typeof value === "string" ? format.text(value) : format.range(value)
105
+ });
106
+ }
107
+ return out;
108
+ }
109
+ export function toggleFacetValue(selection, key, value, on, opts = {}) {
110
+ if (opts.single) {
111
+ const next2 = { ...selection };
112
+ if (on) next2[key] = [value];
113
+ else delete next2[key];
114
+ return next2;
115
+ }
116
+ const current = selection[key];
117
+ const set = new Set(Array.isArray(current) ? current : []);
118
+ if (on) set.add(value);
119
+ else set.delete(value);
120
+ const next = { ...selection };
121
+ if (set.size) next[key] = [...set];
122
+ else delete next[key];
123
+ return next;
124
+ }
125
+ export function setFacetValue(selection, key, value) {
126
+ const next = { ...selection };
127
+ if (isEmptyFacetValue(value)) delete next[key];
128
+ else next[key] = value;
129
+ return next;
130
+ }
131
+ export function clearFacetValue(selection, key, value) {
132
+ if (value === void 0) {
133
+ const next = { ...selection };
134
+ delete next[key];
135
+ return next;
136
+ }
137
+ return toggleFacetValue(selection, key, value, false);
138
+ }
139
+ export function sameSelection(a, b) {
140
+ const left = pruneSelection(a);
141
+ const right = pruneSelection(b);
142
+ const keys = Object.keys(left);
143
+ if (keys.length !== Object.keys(right).length) return false;
144
+ for (const key of keys) {
145
+ const l = left[key];
146
+ const r = right[key];
147
+ if (r === void 0) return false;
148
+ if (Array.isArray(l) !== Array.isArray(r)) return false;
149
+ if (Array.isArray(l) && Array.isArray(r)) {
150
+ if (l.length !== r.length) return false;
151
+ const sorted = [...r].sort();
152
+ if ([...l].sort().some((one, i) => one !== sorted[i])) return false;
153
+ continue;
154
+ }
155
+ if (typeof l === "string" || typeof r === "string") {
156
+ if (l !== r) return false;
157
+ continue;
158
+ }
159
+ const lr = l;
160
+ const rr = r;
161
+ if ((lr.from ?? "") !== (rr.from ?? "") || (lr.to ?? "") !== (rr.to ?? "")) return false;
162
+ }
163
+ return true;
164
+ }
165
+ export function copySelection(selection) {
166
+ const out = {};
167
+ for (const [key, value] of Object.entries(selection)) {
168
+ out[key] = Array.isArray(value) ? [...value] : typeof value === "string" ? value : { ...value };
169
+ }
170
+ return out;
171
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Grid track sizing for the data table's columns.
3
+ *
4
+ * A column declares either a fixed size it wants (`'120px'`, `'max-content'`)
5
+ * or nothing at all, meaning "give it what is left". CSS Grid expresses the
6
+ * second as an `fr` track, and an `fr` track's automatic minimum is the one
7
+ * thing that makes it dangerous: `minmax(0,1fr)` may be given ZERO.
8
+ *
9
+ * That is not hypothetical. On `/commerce/contacts` thirteen columns declare
10
+ * 1340px of fixed track, the gaps add 96px, and the container is 1438px — so
11
+ * the only flexible column, the one holding the record's NAME, was sized 0px
12
+ * and the last column was clipped by the panel's `overflow: hidden`. The
13
+ * anchor of every row, invisible, on the surface that needs it most.
14
+ *
15
+ * `datatable-ux` already names the rule: a column whose resting state must
16
+ * stay legible gets a FLOOR, not a share. This applies it to the tracks the
17
+ * component generates, so a manifest gets it without having to ask — and the
18
+ * table overflows its box, where a reader can scroll to what was cut off,
19
+ * rather than silently dropping a column to nothing.
20
+ */
21
+ /**
22
+ * The narrowest a flexible column may become.
23
+ *
24
+ * Roughly the record-name cell at its minimum: the 28px icon tile, its gap,
25
+ * and enough left for a truncated word plus the ellipsis that says there is
26
+ * more. Below this a column stops being a column and becomes a smear.
27
+ */
28
+ export declare const FLEX_TRACK_FLOOR = "120px";
29
+ /**
30
+ * One column's declared width, resolved to a track with a floor under it.
31
+ *
32
+ * Fixed sizes are left exactly as declared — they are a statement about the
33
+ * content, and shrinking them is the manifest author's call, not this
34
+ * function's. Only the flexible ones are touched, and only their minimum.
35
+ */
36
+ export declare function trackFor(width: string | undefined | null, floor?: string): string;
37
+ /**
38
+ * The `grid-template-columns` value for a row of columns.
39
+ *
40
+ * A row spans `1 / -1` — the first track line to the last — so the tracks are
41
+ * also how WIDE a row is. Where every column declares a fixed size, they sum to
42
+ * less than the panel and nothing takes the remainder: on
43
+ * `/commerce/procurement/purchase-requests` the tracks and their gaps came to
44
+ * 1486px inside a 1698px box, and the header band and every row background
45
+ * stopped there while the toolbar and the footer went the whole way. A table
46
+ * torn off two centimetres short of its own panel.
47
+ *
48
+ * So when no column asked for a share, the LAST one absorbs what is left, and
49
+ * it keeps its declared size as the minimum it may never go below. On these
50
+ * lists the last column is the row actions — content right-aligned, so the
51
+ * menu button stays pinned to the right edge and every other column is exactly
52
+ * where the manifest put it. On a list without one it is the trailing data
53
+ * column that grows, which is where the eye is least likely to miss the space.
54
+ */
55
+ export declare function gridTemplateColumns(widths: Array<string | undefined | null>, floor?: string): string;
@@ -0,0 +1,29 @@
1
+ export const FLEX_TRACK_FLOOR = "120px";
2
+ const MINMAX_FR = /^minmax\(\s*[^,]+,\s*([\d.]+fr)\s*\)$/i;
3
+ const BARE_FR = /^([\d.]+)fr$/i;
4
+ export function trackFor(width, floor = FLEX_TRACK_FLOOR) {
5
+ if (!width) return `minmax(${floor},1fr)`;
6
+ const declared = width.trim();
7
+ const weighted = MINMAX_FR.exec(declared);
8
+ if (weighted) return `minmax(${floor},${weighted[1].toLowerCase()})`;
9
+ const bare = BARE_FR.exec(declared);
10
+ if (bare) return `minmax(${floor},${bare[1]}fr)`;
11
+ return declared;
12
+ }
13
+ function isFlexible(track) {
14
+ return /\dfr\s*\)?$/i.test(track);
15
+ }
16
+ const MINMAX = /^minmax\(\s*([^,]+?)\s*,\s*(.+?)\s*\)$/i;
17
+ function absorbing(track) {
18
+ const parts = MINMAX.exec(track);
19
+ return `minmax(${parts ? parts[2] : track},1fr)`;
20
+ }
21
+ export function gridTemplateColumns(widths, floor) {
22
+ const tracks = widths.map((w) => trackFor(w, floor));
23
+ if (tracks.length === 0) return "";
24
+ if (!tracks.some(isFlexible)) {
25
+ const last = tracks.length - 1;
26
+ tracks[last] = absorbing(tracks[last]);
27
+ }
28
+ return tracks.join(" ");
29
+ }
@@ -0,0 +1,124 @@
1
+ import type { Component, InjectionKey, MaybeRef } from 'vue';
2
+ export type WidgetCategory = 'commerce' | 'operations' | 'ai' | 'workspace' | 'apps';
3
+ /**
4
+ * The schematic the library draws for a widget. Not a screenshot and not the
5
+ * live widget: a shape, so the picture cannot go stale and costs no query.
6
+ */
7
+ export type WidgetPreviewShape = 'kpi-row' | 'bar-chart' | 'ranked-list' | 'funnel' | 'stat-split' | 'feed' | 'status-grid' | 'text' | 'links' | 'greeting' | 'sparkline-grid' | 'count';
8
+ export interface WidgetConfigField {
9
+ key: string;
10
+ /** Catalogue key for the field label. */
11
+ labelKey?: string;
12
+ /** Ready-made label — a field a manifest named, which arrives translated. */
13
+ label?: string;
14
+ type: 'text' | 'textarea' | 'number' | 'boolean' | 'select' | 'links' | 'filters' | 'sort';
15
+ /** Catalogue keys for the help line and the placeholder. */
16
+ helpKey?: string;
17
+ placeholderKey?: string;
18
+ help?: string;
19
+ options?: {
20
+ value: string;
21
+ labelKey?: string;
22
+ label?: string;
23
+ }[];
24
+ default?: unknown;
25
+ }
26
+ export interface WidgetDefinition {
27
+ /** Stable id persisted in `dashboards.widgets[].type`. Never rename in place. */
28
+ type: string;
29
+ /**
30
+ * Catalogue key for the title. A surface whose widgets are declared in code
31
+ * uses this; one whose widgets come from a manifest already has the operator's
32
+ * language and passes `title` instead.
33
+ */
34
+ titleKey?: string;
35
+ title?: string;
36
+ descriptionKey?: string;
37
+ description?: string;
38
+ /** Solar icon name — must also exist in WidgetIcon's map. */
39
+ icon: string;
40
+ category: WidgetCategory;
41
+ /** The schematic the library draws. */
42
+ preview: WidgetPreviewShape;
43
+ defaultSize: {
44
+ w: number;
45
+ h: number;
46
+ };
47
+ minSize?: {
48
+ w: number;
49
+ h: number;
50
+ };
51
+ /**
52
+ * The component, always as a loader, always memoised per type. An adapter
53
+ * that renders many types through ONE component hands in a loader that
54
+ * resolves immediately (`() => Promise.resolve(Adapter)`) rather than the
55
+ * component itself — a Vue component can be a function too, and telling the
56
+ * two apart by inspection is how a functional component ends up being awaited.
57
+ */
58
+ component: () => Promise<Component | {
59
+ default: Component;
60
+ }>;
61
+ /** Rendered by the generic config form; omit for a widget with no options. */
62
+ configFields?: WidgetConfigField[];
63
+ /** Installed apps this widget reads from. Empty = works for every tenant. */
64
+ requiresApps?: string[];
65
+ /**
66
+ * Announced but not built. It appears in the library so people can see what
67
+ * is coming, and cannot be placed — a card that adds an empty promise to a
68
+ * dashboard is worse than one that says "not yet".
69
+ */
70
+ comingSoon?: boolean;
71
+ /** Widget draws its own header/card; the frame then renders body-only. */
72
+ bare?: boolean;
73
+ /**
74
+ * Config the widget always carries, merged under the operator's own. The
75
+ * adapter for manifest widgets uses it to say WHICH manifest widget a type
76
+ * stands for, without that ending up in the config form.
77
+ */
78
+ fixedConfig?: Record<string, unknown>;
79
+ }
80
+ export interface WidgetCategoryGroup {
81
+ category: WidgetCategory;
82
+ labelKey: string;
83
+ widgets: WidgetDefinition[];
84
+ }
85
+ /** Catalogue keys for the library's group headings. */
86
+ export declare const WIDGET_CATEGORY_KEYS: Record<WidgetCategory, string>;
87
+ export interface WidgetRegistry {
88
+ all: () => WidgetDefinition[];
89
+ get: (type: string) => WidgetDefinition | undefined;
90
+ byCategory: () => WidgetCategoryGroup[];
91
+ /** The component for a type, memoised. Null when the type is unknown. */
92
+ componentFor: (type: string) => Component | null;
93
+ /** Defaults from `configFields`, plus whatever the definition fixes. */
94
+ defaultConfigFor: (type: string) => Record<string, unknown>;
95
+ }
96
+ /**
97
+ * Build a registry over a fixed list or a reactive one.
98
+ *
99
+ * Reactive is the interesting case: Commerce derives its definitions from the
100
+ * installed apps' manifests, which resolve after mount. A registry built over
101
+ * the computed sees them arrive without the surface re-providing anything.
102
+ */
103
+ export declare function createWidgetRegistry(source: MaybeRef<WidgetDefinition[]>): WidgetRegistry;
104
+ export declare const WIDGET_REGISTRY: InjectionKey<WidgetRegistry>;
105
+ /** Called once by the surface that owns the dashboard. */
106
+ export declare function provideWidgetRegistry(registry: WidgetRegistry): void;
107
+ /**
108
+ * The registry of the surface this component is rendering on.
109
+ *
110
+ * An empty registry rather than a throw when nothing is provided: a dashboard
111
+ * component mounted outside a surface should render the same "unknown widget"
112
+ * placeholder it renders for a retired type, not take the page down.
113
+ */
114
+ export declare function useWidgetRegistry(): WidgetRegistry;
115
+ /** Apps a widget needs that are not in the given installed set. */
116
+ export declare function missingAppsFor(definition: WidgetDefinition, installed: Set<string>): string[];
117
+ /**
118
+ * A definition's heading, with the translator the host registered. Written
119
+ * once because four components need it and each got it subtly wrong on its own:
120
+ * an operator's own title wins, then the definition's text, then its key.
121
+ */
122
+ export declare function widgetTitle(definition: WidgetDefinition | undefined, t: (key: string) => string, operatorTitle?: string): string;
123
+ /** Same rule for the library's second line. */
124
+ export declare function widgetDescription(definition: WidgetDefinition, t: (key: string) => string): string;
@@ -0,0 +1,66 @@
1
+ import { computed, defineAsyncComponent, inject, isRef, provide } from "vue";
2
+ export const WIDGET_CATEGORY_KEYS = {
3
+ commerce: "dashboard.category.commerce",
4
+ operations: "dashboard.category.operations",
5
+ ai: "dashboard.category.ai",
6
+ workspace: "dashboard.category.workspace",
7
+ apps: "dashboard.category.apps"
8
+ };
9
+ const CATEGORY_ORDER = ["commerce", "apps", "operations", "ai", "workspace"];
10
+ export function createWidgetRegistry(source) {
11
+ const definitions = computed(() => isRef(source) ? source.value : source);
12
+ const byType = computed(() => new Map(definitions.value.map((d) => [d.type, d])));
13
+ const components = /* @__PURE__ */ new Map();
14
+ function get(type) {
15
+ return byType.value.get(type);
16
+ }
17
+ return {
18
+ all: () => definitions.value,
19
+ get,
20
+ byCategory() {
21
+ return CATEGORY_ORDER.map((category) => ({
22
+ category,
23
+ labelKey: WIDGET_CATEGORY_KEYS[category],
24
+ widgets: definitions.value.filter((d) => d.category === category)
25
+ })).filter((group) => group.widgets.length > 0);
26
+ },
27
+ componentFor(type) {
28
+ const cached = components.get(type);
29
+ if (cached) return cached;
30
+ const definition = get(type);
31
+ if (!definition) return null;
32
+ const component = defineAsyncComponent(definition.component);
33
+ components.set(type, component);
34
+ return component;
35
+ },
36
+ defaultConfigFor(type) {
37
+ const definition = get(type);
38
+ if (!definition) return {};
39
+ const out = {};
40
+ for (const field of definition.configFields ?? []) {
41
+ if (field.default !== void 0) out[field.key] = field.default;
42
+ }
43
+ return { ...out, ...definition.fixedConfig ?? {} };
44
+ }
45
+ };
46
+ }
47
+ export const WIDGET_REGISTRY = Symbol("studio-widget-registry");
48
+ export function provideWidgetRegistry(registry) {
49
+ provide(WIDGET_REGISTRY, registry);
50
+ }
51
+ export function useWidgetRegistry() {
52
+ return inject(WIDGET_REGISTRY, null) ?? createWidgetRegistry([]);
53
+ }
54
+ export function missingAppsFor(definition, installed) {
55
+ return (definition.requiresApps ?? []).filter((app) => !installed.has(app));
56
+ }
57
+ export function widgetTitle(definition, t, operatorTitle) {
58
+ if (operatorTitle) return operatorTitle;
59
+ if (!definition) return "";
60
+ if (definition.title) return definition.title;
61
+ return definition.titleKey ? t(definition.titleKey) : "";
62
+ }
63
+ export function widgetDescription(definition, t) {
64
+ if (definition.description) return definition.description;
65
+ return definition.descriptionKey ? t(definition.descriptionKey) : "";
66
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The dashboard contract, shared by every surface that has one (PE-618).
3
+ *
4
+ * It started in the cockpit's `shared/dashboards/types.ts`, where the home
5
+ * screen and its Nitro routes were the only readers. The Commerce Studio now
6
+ * renders the same dashboards from a published module that cannot see the
7
+ * host's `#shared` alias — so the shape moves here, and the cockpit's file
8
+ * re-exports it. One declaration, two consumers, no drift between the rows
9
+ * Nitro writes and the layout the grid reads.
10
+ *
11
+ * The layout vocabulary is deliberately identical to `shared/analytics/types`:
12
+ * two dashboard surfaces in one product must not grow two models of a grid.
13
+ */
14
+ /** Grid placement on a twelve-column grid. `i` is the widget instance id. */
15
+ export interface DashboardLayoutItem {
16
+ i: string;
17
+ x: number;
18
+ y: number;
19
+ w: number;
20
+ h: number;
21
+ }
22
+ /** One placed widget. `type` resolves against the surface's widget registry. */
23
+ export interface DashboardWidget {
24
+ id: string;
25
+ type: string;
26
+ /** Operator-set title; falls back to the registry entry's title when unset. */
27
+ title?: string;
28
+ config?: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Which surface a dashboard belongs to.
32
+ *
33
+ * A dashboard is made of the widgets available where it is edited, so it is
34
+ * only meaningful on its own surface: a Commerce dashboard listed in the
35
+ * cockpit's home switcher would open as a grid of widgets that screen cannot
36
+ * render. Stored on the row rather than encoded in the slug, because a
37
+ * convention nothing enforces is one typo away from the wrong surface.
38
+ */
39
+ export type DashboardScope = 'home' | 'commerce';
40
+ export declare const DASHBOARD_SCOPES: readonly DashboardScope[];
41
+ /** `private` — owner only. `tenant` — anyone in the tenant may see it. */
42
+ export type DashboardVisibility = 'private' | 'tenant';
43
+ /**
44
+ * What a tenant-wide share grants. `view` is the default and the safe one; a
45
+ * dashboard several people can change becomes whatever the last one left
46
+ * behind, so `edit` is a decision the owner makes explicitly.
47
+ */
48
+ export type DashboardTenantPermission = 'view' | 'edit';
49
+ export interface Dashboard {
50
+ id: string;
51
+ slug: string;
52
+ name: string;
53
+ description?: string | null;
54
+ icon?: string | null;
55
+ /** The surface this dashboard belongs to. Older rows read as `home`. */
56
+ scope: DashboardScope;
57
+ ownerUserId: string;
58
+ /** Display name captured when the dashboard was created. */
59
+ ownerName?: string | null;
60
+ /** Who last saved it, and under what name at that moment. */
61
+ updatedByUserId?: string | null;
62
+ updatedByName?: string | null;
63
+ visibility: DashboardVisibility;
64
+ tenantPermission: DashboardTenantPermission;
65
+ layout: DashboardLayoutItem[];
66
+ widgets: DashboardWidget[];
67
+ version: number;
68
+ createdAt: string;
69
+ updatedAt: string;
70
+ /**
71
+ * Server-derived, per caller. True for the owner, and for anyone in the
72
+ * tenant when the dashboard is shared with `edit`.
73
+ */
74
+ canEdit: boolean;
75
+ /** Server-derived: this caller created it. Only the owner may share or delete. */
76
+ isOwner: boolean;
77
+ /** Server-derived: true when this is the caller's landing dashboard. */
78
+ isDefault: boolean;
79
+ }
80
+ export interface DashboardListResponse {
81
+ dashboards: Dashboard[];
82
+ defaultDashboardId: string | null;
83
+ }
84
+ export interface DashboardWritePayload {
85
+ name?: string;
86
+ description?: string | null;
87
+ icon?: string | null;
88
+ visibility?: DashboardVisibility;
89
+ tenantPermission?: DashboardTenantPermission;
90
+ layout?: DashboardLayoutItem[];
91
+ widgets?: DashboardWidget[];
92
+ /** Required on update — a stale value is answered with 409, not an overwrite. */
93
+ version?: number;
94
+ }
95
+ export declare const MAX_DASHBOARD_COLUMNS = 12;
@@ -0,0 +1,2 @@
1
+ export const DASHBOARD_SCOPES = ["home", "commerce"];
2
+ export const MAX_DASHBOARD_COLUMNS = 12;
package/dist/types.d.mts CHANGED
@@ -16,6 +16,12 @@ export { type TalkbackChannel, type TalkbackChannelOptions, type TalkbackMaybeRe
16
16
 
17
17
  export { type MediaLibraryClient } from '../dist/runtime/composables/useMediaLibrary.js'
18
18
 
19
+ export { type SavedViewsStore, type StoredSavedView } from '../dist/runtime/composables/useSavedViews.js'
20
+
21
+ export { type WidgetCategory, type WidgetCategoryGroup, type WidgetConfigField, type WidgetDefinition, type WidgetPreviewShape, type WidgetRegistry } from '../dist/runtime/lib/widgetRegistry.js'
22
+
23
+ export { type StickyListView } from '../dist/runtime/composables/useStickyListView.js'
24
+
19
25
  export { type FloatingPanelDock, type FloatingPanelMode, type FloatingPanelOptions } from '../dist/runtime/composables/useFloatingPanel.js'
20
26
 
21
27
  export { type IsoOption } from '../dist/runtime/lib/isoLists.js'
@@ -27,3 +33,5 @@ export { default } from './module.mjs'
27
33
  export * from '../dist/runtime/components/DataTable/types.js'
28
34
 
29
35
  export * from '../dist/runtime/types/storage.js'
36
+
37
+ export * from '../dist/runtime/types/dashboard.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/studio-shared",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "Shared revenexx Studio shell primitives (PageHeader, SidebarNav, StateCard, DataTable, …) + the platform provider contract (usePlatformAuth/usePlatformTenant). A Nuxt module — add it to `modules`.",
6
6
  "main": "./dist/module.mjs",
@@ -25,10 +25,11 @@
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "@nuxt/kit": "^4.0.0"
28
+ "@nuxt/kit": "^4.0.0",
29
+ "grid-layout-plus": "^1.1.1"
29
30
  },
30
31
  "peerDependencies": {
31
- "@revenexx/studio": "^0.2.0",
32
+ "@revenexx/studio": "^0.2.3",
32
33
  "@revenexx/talkback-js": "^0.3.0",
33
34
  "@solar-icons/nuxt": "^1.2.0",
34
35
  "centrifuge": ">=5.2.0",