@colixsystems/widget-sdk 0.245.0 → 0.247.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +60 -2
  2. package/dist/button-tokens.js +154 -0
  3. package/dist/button-view.js +251 -93
  4. package/dist/button.js +5 -3
  5. package/dist/button.native.js +6 -4
  6. package/dist/chart-logic.js +575 -0
  7. package/dist/chart-tokens.js +131 -0
  8. package/dist/chart-view.js +262 -0
  9. package/dist/chart.js +21 -0
  10. package/dist/chart.native.js +10 -0
  11. package/dist/contract.cjs +132 -35
  12. package/dist/contract.js +132 -35
  13. package/dist/detail-parts-view.js +31 -2
  14. package/dist/file-image-logic.js +162 -0
  15. package/dist/file-image-source.js +71 -0
  16. package/dist/file-image-view.js +119 -0
  17. package/dist/file-image.js +8 -0
  18. package/dist/file-image.native.js +8 -0
  19. package/dist/form-control-logic.js +23 -0
  20. package/dist/form-control-tokens.js +25 -10
  21. package/dist/grid-logic.js +222 -0
  22. package/dist/grid-view.js +109 -0
  23. package/dist/grid.js +7 -0
  24. package/dist/grid.native.js +7 -0
  25. package/dist/host.d.ts +6 -0
  26. package/dist/host.js +2 -0
  27. package/dist/index.d.ts +481 -20
  28. package/dist/index.js +14 -1
  29. package/dist/index.native.js +14 -1
  30. package/dist/menu-view.js +168 -0
  31. package/dist/overlay-tokens.js +50 -0
  32. package/dist/overlay-view.js +67 -24
  33. package/dist/overlay.native.js +3 -1
  34. package/dist/people-picker-logic.js +203 -0
  35. package/dist/people-picker-view.js +247 -0
  36. package/dist/people-picker.js +11 -0
  37. package/dist/people-picker.native.js +9 -0
  38. package/dist/popover-tokens.js +145 -0
  39. package/dist/popover-view.js +252 -0
  40. package/dist/popover.js +23 -0
  41. package/dist/popover.native.js +22 -0
  42. package/dist/radio-group-logic.js +61 -0
  43. package/dist/radio-group-tokens.js +62 -0
  44. package/dist/radio-group-view.js +262 -0
  45. package/dist/radio-group.js +12 -0
  46. package/dist/radio-group.native.js +7 -0
  47. package/dist/select-search-logic.js +150 -0
  48. package/dist/select-view.js +255 -43
  49. package/dist/select.js +6 -2
  50. package/dist/select.native.js +5 -3
  51. package/dist/tabs-tokens.js +59 -0
  52. package/dist/tabs-view.js +9 -43
  53. package/dist/tooltip-view.js +137 -0
  54. package/dist/web-modal.js +95 -23
  55. package/package.json +1 -1
  56. package/src/contract.js +132 -35
@@ -0,0 +1,222 @@
1
+ // The `<Grid>` arithmetic, without a renderer: how many cells fit a line, how
2
+ // the cells split into balanced lines, and how big a definite-size cell is.
3
+ // These are the formulas the AI widget builder is taught for a grid it lays
4
+ // out by hand, so the component and that guidance cannot disagree.
5
+
6
+ import { widthTier } from "./container-width.js";
7
+
8
+ export const GRID_BALANCES = Object.freeze(["cards", "tiles"]);
9
+ export const DEFAULT_GRID_BALANCE = "cards";
10
+ /** The card width a cards grid counts with when it is given none. */
11
+ export const DEFAULT_MIN_CELL_WIDTH = 240;
12
+ /** The smallest a definite-size cell gets when `cell.min` is not given. */
13
+ export const DEFAULT_CELL_MIN = 32;
14
+ export const DEFAULT_GRID_GAP = "md";
15
+ /** A tile row's per-line ceiling at each width tier. */
16
+ export const TILE_COLUMNS_BY_TIER = Object.freeze({ narrow: 2, medium: 3, wide: 4 });
17
+
18
+ function positiveInt(value) {
19
+ const n = Math.floor(Number(value));
20
+ return Number.isFinite(n) && n >= 1 ? n : null;
21
+ }
22
+
23
+ function nonNegative(value) {
24
+ const n = Number(value);
25
+ return Number.isFinite(n) && n >= 0 ? n : null;
26
+ }
27
+
28
+ /** A requested balance, or cards when it names none we draw. */
29
+ export function gridBalance(balance) {
30
+ return GRID_BALANCES.includes(balance) ? balance : DEFAULT_GRID_BALANCE;
31
+ }
32
+
33
+ /**
34
+ * The gap in pixels: a theme spacing name (`"xs"` … `"xl"`) or a number.
35
+ *
36
+ * @param {string|number|undefined} gap
37
+ * @param {Record<string, number>} spacing the theme's spacing scale
38
+ */
39
+ export function resolveGridGap(gap, spacing) {
40
+ const scale = spacing || {};
41
+ const asNumber = nonNegative(gap);
42
+ if (typeof gap === "number" && asNumber !== null) return asNumber;
43
+ const named = typeof gap === "string" ? nonNegative(scale[gap]) : null;
44
+ if (named !== null) return named;
45
+ return nonNegative(scale[DEFAULT_GRID_GAP]) ?? 16;
46
+ }
47
+
48
+ /** How many cells of at least `minCellWidth` fit `width`: at least one. */
49
+ export function cellsPerLine(width, gap, minCellWidth) {
50
+ return Math.max(1, Math.floor((width + gap) / (minCellWidth + gap)));
51
+ }
52
+
53
+ /**
54
+ * Tiles stretch, so the remainder spreads over every line: 5 at 4 are 3 + 2,
55
+ * 7 at 3 are 3 + 2 + 2, and a short line only ever comes last.
56
+ */
57
+ export function tileLineLengths(count, max) {
58
+ if (!(count > 0)) return [];
59
+ const lines = Math.ceil(count / max);
60
+ const base = Math.floor(count / lines);
61
+ const extra = count % lines;
62
+ return Array.from({ length: lines }, (_, i) => base + (i < extra ? 1 : 0));
63
+ }
64
+
65
+ /**
66
+ * Cards keep one width, so every line but the last is full: 6 that fit 5 are
67
+ * 3 + 3, 10 that fit 4 are 4 + 4 + 2. `cols` is the width every line keeps.
68
+ */
69
+ export function cardLineLengths(count, max) {
70
+ if (!(count > 0)) return { cols: 0, lengths: [] };
71
+ const lines = Math.ceil(count / max);
72
+ const cols = Math.ceil(count / lines);
73
+ const lengths = Array.from({ length: lines }, (_, i) => Math.min(cols, count - i * cols));
74
+ return { cols, lengths };
75
+ }
76
+
77
+ /** A definite cell: the share of the line each column gets, clamped. */
78
+ export function clampCellSize(usable, gap, columns, min, max) {
79
+ const share = Math.floor((usable - gap * (columns - 1)) / columns);
80
+ return Math.max(min, Math.min(share, max));
81
+ }
82
+
83
+ function exactLines(count, columns) {
84
+ const lines = Math.ceil(count / columns);
85
+ return Array.from({ length: lines }, (_, i) => Math.min(columns, count - i * columns));
86
+ }
87
+
88
+ function definiteCell(cell) {
89
+ if (!cell || typeof cell !== "object") return null;
90
+ const max = nonNegative(cell.max);
91
+ if (max === null || max <= 0) return null;
92
+ const min = Math.min(nonNegative(cell.min) ?? DEFAULT_CELL_MIN, max);
93
+ return { min, max };
94
+ }
95
+
96
+ /**
97
+ * Lay out `count` cells in a `width`-wide box.
98
+ *
99
+ * Returns `null` while a count or a size depends on a width not yet measured
100
+ * (0), so the grid renders nothing size-dependent until it arrives. Otherwise
101
+ * `lengths` is the number of cells on each line, `slots` the positions every
102
+ * line is padded up to (null when lines stretch, as tiles do), and, for
103
+ * definite cells, `cellSize`, the block's `blockWidth` and its `align`: the
104
+ * block centres, but one wider than the grid starts at the leading edge so
105
+ * only its far end is cut off.
106
+ *
107
+ * @param {{count: number, width: number, gap: number, balance?: string,
108
+ * minCellWidth?: number, maxColumns?: number, columns?: number,
109
+ * cell?: {min?: number, max: number}}} input
110
+ */
111
+ export function planGrid(input) {
112
+ const count = Math.max(0, Math.floor(Number(input.count) || 0));
113
+ const width = Number(input.width) > 0 ? Number(input.width) : 0;
114
+ const gap = nonNegative(input.gap) ?? 0;
115
+ const balance = gridBalance(input.balance);
116
+ const columns = positiveInt(input.columns);
117
+ const maxColumns = positiveInt(input.maxColumns);
118
+ const minCellWidth = nonNegative(input.minCellWidth);
119
+ const cell = definiteCell(input.cell);
120
+ const capped = (n) => (maxColumns ? Math.min(n, maxColumns) : n);
121
+
122
+ if (cell) {
123
+ if (width === 0) return null;
124
+ let perLine = columns;
125
+ if (!perLine) {
126
+ // As many columns as keep each cell at or under its max, filling the line.
127
+ perLine = capped(Math.max(1, Math.ceil((width + gap) / (cell.max + gap))));
128
+ while (perLine > 1 && clampCellSize(width, gap, perLine, 0, cell.max) < cell.min) perLine -= 1;
129
+ }
130
+ const plan = columns
131
+ ? { cols: columns, lengths: exactLines(count, columns) }
132
+ : cardLineLengths(count, perLine);
133
+ const cols = plan.cols || perLine;
134
+ const cellSize = clampCellSize(width, gap, cols, cell.min, cell.max);
135
+ const blockWidth = cellSize * cols + gap * (cols - 1);
136
+ return {
137
+ lengths: plan.lengths,
138
+ slots: cols,
139
+ cellSize,
140
+ blockWidth,
141
+ align: blockWidth > width ? "flex-start" : "center",
142
+ };
143
+ }
144
+
145
+ if (columns) {
146
+ return { lengths: exactLines(count, columns), slots: columns, cellSize: null, blockWidth: null };
147
+ }
148
+
149
+ const counted = minCellWidth !== null && minCellWidth > 0 ? minCellWidth : null;
150
+ if (balance === "tiles") {
151
+ let max;
152
+ if (counted) {
153
+ if (width === 0) return null;
154
+ max = capped(cellsPerLine(width, gap, counted));
155
+ } else if (maxColumns) {
156
+ max = maxColumns;
157
+ } else {
158
+ if (width === 0) return null;
159
+ max = TILE_COLUMNS_BY_TIER[widthTier(width)];
160
+ }
161
+ return { lengths: tileLineLengths(count, max), slots: null, cellSize: null, blockWidth: null };
162
+ }
163
+
164
+ if (width === 0) return null;
165
+ const max = capped(cellsPerLine(width, gap, counted || DEFAULT_MIN_CELL_WIDTH));
166
+ const { cols, lengths } = cardLineLengths(count, max);
167
+ return { lengths, slots: cols, cellSize: null, blockWidth: null };
168
+ }
169
+
170
+ /** Split `items` into lines of the planned lengths. */
171
+ export function splitIntoLines(items, lengths) {
172
+ const lines = [];
173
+ let start = 0;
174
+ for (const length of lengths) {
175
+ lines.push(items.slice(start, start + length));
176
+ start += length;
177
+ }
178
+ return lines;
179
+ }
180
+
181
+ // Floored so a line of percentages never sums past 100% and wraps early.
182
+ function percentOf(parts) {
183
+ return `${Math.floor((100 / parts) * 10000) / 10000}%`;
184
+ }
185
+
186
+ function place(plan, gap, position, perLine) {
187
+ if (plan.cellSize !== null) return { width: plan.cellSize, padStart: 0, padEnd: 0 };
188
+ // The gap is split between neighbours, so every cell keeps an equal share.
189
+ return {
190
+ width: percentOf(perLine),
191
+ padStart: (gap * position) / perLine,
192
+ padEnd: (gap * (perLine - 1 - position)) / perLine,
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Where each cell sits in the ONE wrapping box the grid draws: its `line`,
198
+ * its `width` (pixels for a definite cell, else a share of the box) and the
199
+ * `padStart` / `padEnd` that make up the gap between flexible neighbours.
200
+ * `spacers` are the empty cells that pad the last line to `slots`. Every
201
+ * cell stays a child of the same box, so moving it to another line never
202
+ * remounts it.
203
+ *
204
+ * @param {{lengths: number[], slots: number|null, cellSize: number|null}} plan
205
+ * @param {number} gap
206
+ */
207
+ export function placeCells(plan, gap) {
208
+ const cells = [];
209
+ let spacers = [];
210
+ plan.lengths.forEach((length, line) => {
211
+ const perLine = plan.slots ?? length;
212
+ for (let position = 0; position < length; position += 1) {
213
+ cells.push({ line, ...place(plan, gap, position, perLine) });
214
+ }
215
+ if (line === plan.lengths.length - 1 && plan.slots !== null) {
216
+ spacers = Array.from({ length: Math.max(0, plan.slots - length) }, (_, s) =>
217
+ place(plan, gap, length + s, perLine),
218
+ );
219
+ }
220
+ });
221
+ return { cells, spacers };
222
+ }
@@ -0,0 +1,109 @@
1
+ // The ONE implementation behind the `<Grid>` SDK primitive. The platform
2
+ // bindings (`grid.js` / `grid.native.js`) differ only in where `View` comes
3
+ // from, so the hosts lay a grid out the same way.
4
+ //
5
+ // The grid measures an inner View that carries no padding, so a `style`
6
+ // padding on the grid is never counted into the width its cells share.
7
+
8
+ import React from "react";
9
+ import { useContainerWidth, useHostTheme } from "./hooks.js";
10
+ import { DEFAULT_THEME_TOKENS } from "./_theme-tokens.js";
11
+ import { placeCells, planGrid, resolveGridGap } from "./grid-logic.js";
12
+
13
+ const h = React.createElement;
14
+
15
+ const HIDDEN_FROM_READERS = {
16
+ "aria-hidden": true,
17
+ accessibilityElementsHidden: true,
18
+ importantForAccessibility: "no-hide-descendants",
19
+ };
20
+
21
+ function cellStyle(place, plan, square, hidden) {
22
+ const own = hidden ? { pointerEvents: "none" } : null;
23
+ if (plan.cellSize !== null) {
24
+ const size = { width: plan.cellSize, flexShrink: 0, ...own };
25
+ return square ? { ...size, height: plan.cellSize } : size;
26
+ }
27
+ return { width: place.width, paddingStart: place.padStart, paddingEnd: place.padEnd, ...own };
28
+ }
29
+
30
+ /**
31
+ * Bind the shared grid to one platform's primitives.
32
+ *
33
+ * @param {{View: any}} rn
34
+ */
35
+ export function makeGrid(rn) {
36
+ const { View } = rn;
37
+
38
+ // A flexible square cell squares its content box, inside the gap padding.
39
+ function cell(key, style, square, flexible, child, extra) {
40
+ const inner = square && flexible ? h(View, { style: { aspectRatio: 1 } }, child) : child;
41
+ return h(View, { key, style, ...extra }, inner);
42
+ }
43
+
44
+ function Grid({
45
+ children,
46
+ balance,
47
+ minCellWidth,
48
+ maxColumns,
49
+ columns,
50
+ cell: cellSize,
51
+ square = false,
52
+ gap,
53
+ style,
54
+ testID,
55
+ accessibilityLabel,
56
+ }) {
57
+ const theme = useHostTheme() || DEFAULT_THEME_TOKENS;
58
+ const spacing = theme.spacing || DEFAULT_THEME_TOKENS.spacing;
59
+ const space = resolveGridGap(gap, spacing);
60
+ const [width, onLayout] = useContainerWidth();
61
+ const items = React.Children.toArray(children);
62
+ const plan = planGrid({
63
+ count: items.length,
64
+ width,
65
+ gap: space,
66
+ balance,
67
+ minCellWidth,
68
+ maxColumns,
69
+ columns,
70
+ cell: cellSize,
71
+ });
72
+
73
+ let content = null;
74
+ if (plan) {
75
+ const isSquare = square === true;
76
+ const flexible = plan.cellSize === null;
77
+ const { cells, spacers } = placeCells(plan, space);
78
+ const wrap = flexible
79
+ ? { flexDirection: "row", flexWrap: "wrap", rowGap: space }
80
+ : {
81
+ flexDirection: "row",
82
+ flexWrap: "wrap",
83
+ gap: space,
84
+ width: plan.blockWidth,
85
+ alignSelf: plan.align,
86
+ };
87
+ // One box for every cell, so a cell changing line keeps its state.
88
+ content = h(
89
+ View,
90
+ { style: wrap },
91
+ ...items.map((child, i) =>
92
+ cell(child.key, cellStyle(cells[i], plan, isSquare, false), isSquare, flexible, child),
93
+ ),
94
+ ...spacers.map((place, s) =>
95
+ cell(`spacer-${s}`, cellStyle(place, plan, isSquare, true), isSquare, flexible, null, HIDDEN_FROM_READERS),
96
+ ),
97
+ );
98
+ }
99
+
100
+ const named = accessibilityLabel ? { role: "group", accessibilityLabel } : null;
101
+ return h(
102
+ View,
103
+ { testID, ...named, style: [{ alignSelf: "stretch" }, style] },
104
+ h(View, { onLayout }, content),
105
+ );
106
+ }
107
+
108
+ return Grid;
109
+ }
package/dist/grid.js ADDED
@@ -0,0 +1,7 @@
1
+ // `<Grid>` (web). Binds ./grid-view.js to react-native-web; see
2
+ // ./primitives.js for why not the bare specifier.
3
+
4
+ import * as ReactNative from "react-native-web";
5
+ import { makeGrid } from "./grid-view.js";
6
+
7
+ export const Grid = makeGrid({ View: ReactNative.View });
@@ -0,0 +1,7 @@
1
+ // `<Grid>` (native). Binds ./grid-view.js to React Native's own primitives;
2
+ // only the import differs from ./grid.js.
3
+
4
+ import { View } from "react-native";
5
+ import { makeGrid } from "./grid-view.js";
6
+
7
+ export const Grid = makeGrid({ View });
package/dist/host.d.ts CHANGED
@@ -51,6 +51,12 @@ export function bannerScopeDefaults(theme: unknown): Record<string, string | num
51
51
  */
52
52
  export function chipScopeDefaults(theme: unknown): Record<string, string | number>;
53
53
 
54
+ /**
55
+ * What each token of the `chart` component scope resolves to while unset,
56
+ * keyed by token name: the theme colours `<Chart>` paints an unthemed app with.
57
+ */
58
+ export function chartScopeDefaults(theme: unknown): Record<string, string | number>;
59
+
54
60
  /**
55
61
  * What each token of the `search` component scope resolves to while unset,
56
62
  * keyed by token name: the input look `<SearchField>` falls back to.
package/dist/host.js CHANGED
@@ -54,6 +54,8 @@ export { resolveModalTokens, modalScopeDefaults } from "./modal-tokens.js";
54
54
  // What each `chip` scope token resolves to while unset, for the Studio's
55
55
  // Design page to preview beside its controls.
56
56
  export { chipScopeDefaults } from "./chip-tokens.js";
57
+ // The theme colours each unset `chart` token falls back to, for the same page.
58
+ export { chartScopeDefaults } from "./chart-tokens.js";
57
59
  // The input look each unset `search` token falls back to, for the same page.
58
60
  export { searchScopeDefaults } from "./search-field-tokens.js";
59
61