@moldable-ai/ui 0.2.20 → 0.2.22

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 (196) hide show
  1. package/dist/catalog/catalog-coverage.d.ts +15 -0
  2. package/dist/catalog/catalog-coverage.d.ts.map +1 -0
  3. package/dist/catalog/catalog-coverage.js +44 -0
  4. package/dist/catalog/catalog-manifest.d.ts.map +1 -1
  5. package/dist/catalog/catalog-manifest.js +35 -23
  6. package/dist/catalog/catalog-types.d.ts +17 -0
  7. package/dist/catalog/catalog-types.d.ts.map +1 -1
  8. package/dist/catalog/catalog-types.js +7 -0
  9. package/dist/catalog/component-catalog.d.ts.map +1 -1
  10. package/dist/catalog/component-catalog.js +16 -6
  11. package/dist/catalog/core-catalog-exclusions.d.ts +8 -0
  12. package/dist/catalog/core-catalog-exclusions.d.ts.map +1 -0
  13. package/dist/catalog/core-catalog-exclusions.js +155 -0
  14. package/dist/catalog/core-catalog.d.ts +0 -4
  15. package/dist/catalog/core-catalog.d.ts.map +1 -1
  16. package/dist/catalog/core-catalog.js +12 -98
  17. package/dist/catalog/core-collection-stories.d.ts +3 -0
  18. package/dist/catalog/core-collection-stories.d.ts.map +1 -0
  19. package/dist/catalog/core-collection-stories.js +42 -0
  20. package/dist/catalog/core-control-stories.d.ts +3 -0
  21. package/dist/catalog/core-control-stories.d.ts.map +1 -0
  22. package/dist/catalog/core-control-stories.js +97 -0
  23. package/dist/catalog/core-feedback-stories.d.ts +3 -0
  24. package/dist/catalog/core-feedback-stories.d.ts.map +1 -0
  25. package/dist/catalog/core-feedback-stories.js +50 -0
  26. package/dist/catalog/core-foundation-stories.d.ts +3 -0
  27. package/dist/catalog/core-foundation-stories.d.ts.map +1 -0
  28. package/dist/catalog/core-foundation-stories.js +53 -0
  29. package/dist/catalog/core-navigation-stories.d.ts +3 -0
  30. package/dist/catalog/core-navigation-stories.d.ts.map +1 -0
  31. package/dist/catalog/core-navigation-stories.js +25 -0
  32. package/dist/catalog/core-overlay-stories.d.ts +3 -0
  33. package/dist/catalog/core-overlay-stories.d.ts.map +1 -0
  34. package/dist/catalog/core-overlay-stories.js +20 -0
  35. package/dist/catalog/index.d.ts +3 -1
  36. package/dist/catalog/index.d.ts.map +1 -1
  37. package/dist/catalog/index.js +3 -1
  38. package/dist/components/app-error-boundary.d.ts.map +1 -1
  39. package/dist/components/app-error-boundary.js +3 -2
  40. package/dist/components/chat/chat-panel.d.ts +0 -1
  41. package/dist/components/chat/chat-panel.d.ts.map +1 -1
  42. package/dist/components/chat/chat-panel.js +7 -11
  43. package/dist/components/ui/alert-dialog.d.ts +10 -4
  44. package/dist/components/ui/alert-dialog.d.ts.map +1 -1
  45. package/dist/components/ui/alert-dialog.js +14 -6
  46. package/dist/components/ui/alert.d.ts +19 -3
  47. package/dist/components/ui/alert.d.ts.map +1 -1
  48. package/dist/components/ui/alert.js +25 -8
  49. package/dist/components/ui/app-frame.d.ts +9 -3
  50. package/dist/components/ui/app-frame.d.ts.map +1 -1
  51. package/dist/components/ui/app-frame.js +45 -6
  52. package/dist/components/ui/button.d.ts +6 -4
  53. package/dist/components/ui/button.d.ts.map +1 -1
  54. package/dist/components/ui/button.js +40 -8
  55. package/dist/components/ui/collapsible.d.ts +9 -1
  56. package/dist/components/ui/collapsible.d.ts.map +1 -1
  57. package/dist/components/ui/collapsible.js +13 -2
  58. package/dist/components/ui/collection-navigation.d.ts +8 -0
  59. package/dist/components/ui/collection-navigation.d.ts.map +1 -0
  60. package/dist/components/ui/collection-navigation.js +19 -0
  61. package/dist/components/ui/command.d.ts +5 -1
  62. package/dist/components/ui/command.d.ts.map +1 -1
  63. package/dist/components/ui/command.js +13 -1
  64. package/dist/components/ui/confirm-dialog.d.ts +19 -0
  65. package/dist/components/ui/confirm-dialog.d.ts.map +1 -0
  66. package/dist/components/ui/confirm-dialog.js +45 -0
  67. package/dist/components/ui/date-picker.d.ts +24 -0
  68. package/dist/components/ui/date-picker.d.ts.map +1 -0
  69. package/dist/components/ui/date-picker.js +92 -0
  70. package/dist/components/ui/dialog-primary-action.d.ts +4 -0
  71. package/dist/components/ui/dialog-primary-action.d.ts.map +1 -0
  72. package/dist/components/ui/dialog-primary-action.js +37 -0
  73. package/dist/components/ui/dialog.d.ts +8 -2
  74. package/dist/components/ui/dialog.d.ts.map +1 -1
  75. package/dist/components/ui/dialog.js +15 -5
  76. package/dist/components/ui/empty.d.ts +7 -2
  77. package/dist/components/ui/empty.d.ts.map +1 -1
  78. package/dist/components/ui/empty.js +20 -7
  79. package/dist/components/ui/error-boundary-view.d.ts +12 -0
  80. package/dist/components/ui/error-boundary-view.d.ts.map +1 -0
  81. package/dist/components/ui/error-boundary-view.js +8 -0
  82. package/dist/components/ui/grid.d.ts.map +1 -1
  83. package/dist/components/ui/grid.js +70 -46
  84. package/dist/components/ui/icon-button.d.ts.map +1 -1
  85. package/dist/components/ui/icon-button.js +1 -2
  86. package/dist/components/ui/index.d.ts +5 -0
  87. package/dist/components/ui/index.d.ts.map +1 -1
  88. package/dist/components/ui/index.js +5 -0
  89. package/dist/components/ui/input-group.d.ts +1 -1
  90. package/dist/components/ui/list.d.ts +5 -1
  91. package/dist/components/ui/list.d.ts.map +1 -1
  92. package/dist/components/ui/list.js +27 -16
  93. package/dist/components/ui/material.d.ts +34 -0
  94. package/dist/components/ui/material.d.ts.map +1 -0
  95. package/dist/components/ui/material.js +34 -0
  96. package/dist/components/ui/message-scroller-controller.d.ts +162 -0
  97. package/dist/components/ui/message-scroller-controller.d.ts.map +1 -0
  98. package/dist/components/ui/message-scroller-controller.js +888 -0
  99. package/dist/components/ui/message-scroller-policy.d.ts +74 -0
  100. package/dist/components/ui/message-scroller-policy.d.ts.map +1 -0
  101. package/dist/components/ui/message-scroller-policy.js +129 -0
  102. package/dist/components/ui/message-scroller-primitive.d.ts +55 -0
  103. package/dist/components/ui/message-scroller-primitive.d.ts.map +1 -0
  104. package/dist/components/ui/message-scroller-primitive.js +226 -0
  105. package/dist/components/ui/message-scroller.d.ts +2 -2
  106. package/dist/components/ui/message-scroller.d.ts.map +1 -1
  107. package/dist/components/ui/message-scroller.js +3 -15
  108. package/dist/components/ui/navigation-button-group.d.ts +16 -0
  109. package/dist/components/ui/navigation-button-group.d.ts.map +1 -0
  110. package/dist/components/ui/navigation-button-group.js +9 -0
  111. package/dist/components/ui/popover.d.ts +9 -2
  112. package/dist/components/ui/popover.d.ts.map +1 -1
  113. package/dist/components/ui/popover.js +13 -3
  114. package/dist/components/ui/scroll-area.d.ts +8 -2
  115. package/dist/components/ui/scroll-area.d.ts.map +1 -1
  116. package/dist/components/ui/scroll-area.js +6 -3
  117. package/dist/components/ui/search-field.d.ts.map +1 -1
  118. package/dist/components/ui/segmented-control.d.ts.map +1 -1
  119. package/dist/components/ui/segmented-control.js +6 -5
  120. package/dist/components/ui/sheet.d.ts +4 -2
  121. package/dist/components/ui/sheet.d.ts.map +1 -1
  122. package/dist/components/ui/sheet.js +12 -9
  123. package/dist/components/ui/sonner.d.ts +1 -1
  124. package/dist/components/ui/sonner.d.ts.map +1 -1
  125. package/dist/components/ui/spinner.d.ts.map +1 -1
  126. package/dist/components/ui/spinner.js +1 -1
  127. package/dist/components/ui/table.d.ts.map +1 -1
  128. package/dist/components/ui/table.js +7 -2
  129. package/dist/components/ui/toggle.d.ts +12 -4
  130. package/dist/components/ui/toggle.d.ts.map +1 -1
  131. package/dist/components/ui/toggle.js +13 -2
  132. package/dist/components/ui/toolbar.d.ts +9 -2
  133. package/dist/components/ui/toolbar.d.ts.map +1 -1
  134. package/dist/components/ui/toolbar.js +23 -4
  135. package/dist/components/ui/tooltip.d.ts +8 -3
  136. package/dist/components/ui/tooltip.d.ts.map +1 -1
  137. package/dist/components/ui/tooltip.js +11 -6
  138. package/dist/index.d.ts +6 -1
  139. package/dist/index.d.ts.map +1 -1
  140. package/dist/index.js +7 -1
  141. package/dist/lib/frame-lifecycle.d.ts +23 -0
  142. package/dist/lib/frame-lifecycle.d.ts.map +1 -1
  143. package/dist/lib/frame-lifecycle.js +30 -3
  144. package/dist/lib/host-date-time-picker.d.ts +36 -0
  145. package/dist/lib/host-date-time-picker.d.ts.map +1 -0
  146. package/dist/lib/host-date-time-picker.js +162 -0
  147. package/dist/lib/host-file-dialog.d.ts +66 -0
  148. package/dist/lib/host-file-dialog.d.ts.map +1 -0
  149. package/dist/lib/host-file-dialog.js +221 -0
  150. package/dist/lib/host-native-menu.d.ts +75 -0
  151. package/dist/lib/host-native-menu.d.ts.map +1 -0
  152. package/dist/lib/host-native-menu.js +187 -0
  153. package/dist/lib/host-services.d.ts +13 -0
  154. package/dist/lib/host-services.d.ts.map +1 -0
  155. package/dist/lib/host-services.js +161 -0
  156. package/dist/lib/host-share.d.ts +39 -0
  157. package/dist/lib/host-share.d.ts.map +1 -0
  158. package/dist/lib/host-share.js +161 -0
  159. package/dist/lib/theme.d.ts.map +1 -1
  160. package/dist/lib/theme.js +2 -1
  161. package/docs/README.md +4 -1
  162. package/docs/adaptive-materials.md +79 -0
  163. package/docs/app-anatomy.md +3 -1
  164. package/docs/component-catalog.md +26 -3
  165. package/docs/design-tokens.md +17 -4
  166. package/docs/foundations.md +5 -3
  167. package/docs/standalone-app-windows.md +8 -0
  168. package/docs/visual-regression.md +78 -0
  169. package/package.json +12 -4
  170. package/src/components/ui/alert-dialog.md +58 -0
  171. package/src/components/ui/alert.md +28 -6
  172. package/src/components/ui/app-frame.md +5 -0
  173. package/src/components/ui/button.md +10 -3
  174. package/src/components/ui/collapsible.md +52 -0
  175. package/src/components/ui/combobox.md +0 -1
  176. package/src/components/ui/command.md +54 -0
  177. package/src/components/ui/date-field.md +1 -1
  178. package/src/components/ui/date-picker.md +55 -0
  179. package/src/components/ui/dialog.md +59 -0
  180. package/src/components/ui/empty.md +8 -3
  181. package/src/components/ui/error-boundary-view.md +98 -0
  182. package/src/components/ui/grid.md +8 -4
  183. package/src/components/ui/icon-button.md +2 -2
  184. package/src/components/ui/list.md +35 -2
  185. package/src/components/ui/material.md +51 -0
  186. package/src/components/ui/navigation-button-group.md +46 -0
  187. package/src/components/ui/popover.md +31 -0
  188. package/src/components/ui/scroll-area.md +11 -7
  189. package/src/components/ui/search-field.md +1 -1
  190. package/src/components/ui/segmented-control.md +2 -5
  191. package/src/components/ui/sheet.md +30 -0
  192. package/src/components/ui/table.md +4 -2
  193. package/src/components/ui/toggle-button.md +48 -0
  194. package/src/components/ui/toolbar.md +5 -0
  195. package/src/components/ui/tooltip.md +31 -0
  196. package/src/styles/index.css +213 -0
@@ -0,0 +1,187 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { HostServiceRequestError, isHostServiceAvailable, requestHostService, } from './host-services';
3
+ const HOST_CONTEXT_CHANGE_EVENT = 'moldable:host-context-change';
4
+ const NATIVE_MENU_SERVICE_NAME = 'nativeMenu';
5
+ export const HOST_NATIVE_MENU_MAX_DEPTH = 3;
6
+ export const HOST_NATIVE_MENU_MAX_ITEMS = 200;
7
+ export const HOST_NATIVE_MENU_MAX_TITLE_LENGTH = 256;
8
+ export const HOST_NATIVE_MENU_MAX_SUBLABEL_LENGTH = 512;
9
+ function finitePoint(value) {
10
+ return Number.isFinite(value.x) && Number.isFinite(value.y);
11
+ }
12
+ function menuAnchorRect(anchor) {
13
+ const rect = typeof Element !== 'undefined' && anchor instanceof Element
14
+ ? anchor.getBoundingClientRect()
15
+ : anchor;
16
+ if (!finitePoint(rect) ||
17
+ !Number.isFinite(rect.width) ||
18
+ !Number.isFinite(rect.height) ||
19
+ rect.width < 0 ||
20
+ rect.height < 0) {
21
+ throw new TypeError('Native menu anchors require a finite, non-negative rect.');
22
+ }
23
+ return {
24
+ x: rect.x,
25
+ y: rect.y,
26
+ width: rect.width,
27
+ height: rect.height,
28
+ };
29
+ }
30
+ function validateShortcut(shortcut) {
31
+ if (typeof shortcut.key !== 'string' || shortcut.key.length === 0) {
32
+ throw new TypeError('Native menu shortcuts require a non-empty key.');
33
+ }
34
+ if (shortcut.modifiers !== undefined &&
35
+ (!Array.isArray(shortcut.modifiers) ||
36
+ !shortcut.modifiers.every((modifier) => modifier === 'cmd' ||
37
+ modifier === 'shift' ||
38
+ modifier === 'alt' ||
39
+ modifier === 'ctrl'))) {
40
+ throw new TypeError('Native menu shortcuts contain an invalid modifier.');
41
+ }
42
+ }
43
+ function validateMenuItems(items) {
44
+ if (!Array.isArray(items) ||
45
+ items.length === 0 ||
46
+ items.length > HOST_NATIVE_MENU_MAX_ITEMS) {
47
+ throw new TypeError(`Native menus require 1–${HOST_NATIVE_MENU_MAX_ITEMS} total items.`);
48
+ }
49
+ const ids = new Set();
50
+ let totalItems = 0;
51
+ const visit = (values, depth) => {
52
+ if (depth > HOST_NATIVE_MENU_MAX_DEPTH) {
53
+ throw new TypeError(`Native menu depth must not exceed ${HOST_NATIVE_MENU_MAX_DEPTH}.`);
54
+ }
55
+ for (const item of values) {
56
+ totalItems += 1;
57
+ if (totalItems > HOST_NATIVE_MENU_MAX_ITEMS) {
58
+ throw new TypeError(`Native menus must not exceed ${HOST_NATIVE_MENU_MAX_ITEMS} total items.`);
59
+ }
60
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
61
+ throw new TypeError('Native menu items must be objects.');
62
+ }
63
+ const kind = item.kind ?? 'normal';
64
+ if (kind !== 'normal' &&
65
+ kind !== 'separator' &&
66
+ kind !== 'checkbox' &&
67
+ kind !== 'radio') {
68
+ throw new TypeError('Native menu items contain an unsupported kind.');
69
+ }
70
+ if (kind !== 'separator') {
71
+ if (typeof item.id !== 'string' ||
72
+ item.id.trim().length === 0 ||
73
+ ids.has(item.id)) {
74
+ throw new TypeError('Selectable native menu items require unique, non-empty ids.');
75
+ }
76
+ ids.add(item.id);
77
+ if (typeof item.title !== 'string' ||
78
+ item.title.length === 0 ||
79
+ item.title.length > HOST_NATIVE_MENU_MAX_TITLE_LENGTH) {
80
+ throw new TypeError(`Native menu item titles must contain 1–${HOST_NATIVE_MENU_MAX_TITLE_LENGTH} characters.`);
81
+ }
82
+ }
83
+ else {
84
+ if (item.id !== undefined && typeof item.id !== 'string') {
85
+ throw new TypeError('Native menu separator ids must be strings when provided.');
86
+ }
87
+ if (item.title !== undefined &&
88
+ (typeof item.title !== 'string' ||
89
+ item.title.length > HOST_NATIVE_MENU_MAX_TITLE_LENGTH)) {
90
+ throw new TypeError(`Native menu separator titles must not exceed ${HOST_NATIVE_MENU_MAX_TITLE_LENGTH} characters.`);
91
+ }
92
+ }
93
+ if (item.sublabel !== undefined &&
94
+ (typeof item.sublabel !== 'string' ||
95
+ item.sublabel.length > HOST_NATIVE_MENU_MAX_SUBLABEL_LENGTH)) {
96
+ throw new TypeError(`Native menu sublabels must not exceed ${HOST_NATIVE_MENU_MAX_SUBLABEL_LENGTH} characters.`);
97
+ }
98
+ if (item.enabled !== undefined && typeof item.enabled !== 'boolean') {
99
+ throw new TypeError('Native menu enabled values must be booleans.');
100
+ }
101
+ if (item.destructive !== undefined &&
102
+ typeof item.destructive !== 'boolean') {
103
+ throw new TypeError('Native menu destructive values must be booleans.');
104
+ }
105
+ if (item.shortcut !== undefined)
106
+ validateShortcut(item.shortcut);
107
+ if (kind === 'checkbox' || kind === 'radio') {
108
+ if (item.checked !== undefined &&
109
+ item.checked !== true &&
110
+ item.checked !== false &&
111
+ item.checked !== 'mixed') {
112
+ throw new TypeError('Native menu checked values must be true, false, or mixed.');
113
+ }
114
+ if (kind === 'radio' && item.checked === 'mixed') {
115
+ throw new TypeError('Native menu radio items do not support the mixed state.');
116
+ }
117
+ }
118
+ else if (item.checked !== undefined) {
119
+ throw new TypeError('Only checkbox and radio native menu items may be checked.');
120
+ }
121
+ if (item.submenu !== undefined) {
122
+ if (kind !== 'normal' || !Array.isArray(item.submenu)) {
123
+ throw new TypeError('Only normal native menu items may contain submenu arrays.');
124
+ }
125
+ visit(item.submenu, depth + 1);
126
+ }
127
+ }
128
+ };
129
+ visit(items, 1);
130
+ }
131
+ function validatePlacement(options) {
132
+ const hasAnchor = options.anchor !== undefined;
133
+ const hasPosition = options.position !== undefined;
134
+ if (hasAnchor === hasPosition) {
135
+ throw new TypeError('Native menus require exactly one anchor or pointer position.');
136
+ }
137
+ if (options.anchor)
138
+ return { anchor: menuAnchorRect(options.anchor) };
139
+ if (!options.position || !finitePoint(options.position)) {
140
+ throw new TypeError('Native menu positions require finite x and y coordinates.');
141
+ }
142
+ return { position: { ...options.position } };
143
+ }
144
+ function abortError() {
145
+ return new DOMException('The native menu request was aborted.', 'AbortError');
146
+ }
147
+ export async function openNativeMenuWithHost(items, options) {
148
+ validateMenuItems(items);
149
+ const placement = validatePlacement(options);
150
+ if (options.signal?.aborted)
151
+ throw abortError();
152
+ if (!isHostServiceAvailable(NATIVE_MENU_SERVICE_NAME)) {
153
+ return { status: 'fallback' };
154
+ }
155
+ try {
156
+ const result = await requestHostService(NATIVE_MENU_SERVICE_NAME, { items, ...placement }, {
157
+ signal: options.signal,
158
+ timeoutMs: options.timeoutMs,
159
+ });
160
+ if (typeof result !== 'object' ||
161
+ result === null ||
162
+ !('selectedId' in result) ||
163
+ typeof result.selectedId !== 'string' ||
164
+ result.selectedId.length === 0) {
165
+ throw new Error('The native menu returned an invalid result.');
166
+ }
167
+ return { status: 'completed', selectedId: result.selectedId };
168
+ }
169
+ catch (error) {
170
+ if (error instanceof HostServiceRequestError &&
171
+ error.status === 'cancelled') {
172
+ return { status: 'cancelled' };
173
+ }
174
+ throw error;
175
+ }
176
+ }
177
+ export function useHostNativeMenu() {
178
+ const [available, setAvailable] = useState(() => isHostServiceAvailable(NATIVE_MENU_SERVICE_NAME));
179
+ useEffect(() => {
180
+ const update = () => setAvailable(isHostServiceAvailable(NATIVE_MENU_SERVICE_NAME));
181
+ update();
182
+ window.addEventListener(HOST_CONTEXT_CHANGE_EVENT, update);
183
+ return () => window.removeEventListener(HOST_CONTEXT_CHANGE_EVENT, update);
184
+ }, []);
185
+ const open = useCallback((items, options) => openNativeMenuWithHost(items, options), []);
186
+ return { available, open };
187
+ }
@@ -0,0 +1,13 @@
1
+ export interface HostServiceRequestOptions {
2
+ signal?: AbortSignal;
3
+ timeoutMs?: number;
4
+ }
5
+ export type HostServiceResponseStatus = 'completed' | 'cancelled' | 'error' | 'timeout';
6
+ export declare class HostServiceRequestError extends Error {
7
+ readonly status: Exclude<HostServiceResponseStatus, 'completed'>;
8
+ readonly code?: string;
9
+ constructor(status: Exclude<HostServiceResponseStatus, 'completed'>, message: string, code?: string);
10
+ }
11
+ export declare function isHostServiceAvailable(name: string): boolean;
12
+ export declare function requestHostService<TResult = unknown>(name: string, payload: unknown, options?: HostServiceRequestOptions): Promise<TResult>;
13
+ //# sourceMappingURL=host-services.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-services.d.ts","sourceRoot":"","sources":["../../src/lib/host-services.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,yBAAyB;IACxC,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,yBAAyB,GACjC,WAAW,GACX,WAAW,GACX,OAAO,GACP,SAAS,CAAA;AAuBb,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,yBAAyB,EAAE,WAAW,CAAC,CAAA;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;gBAGpB,MAAM,EAAE,OAAO,CAAC,yBAAyB,EAAE,WAAW,CAAC,EACvD,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM;CAOhB;AA4GD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAM5D;AAED,wBAAgB,kBAAkB,CAAC,OAAO,GAAG,OAAO,EAClD,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,OAAO,CAAC,CAiFlB"}
@@ -0,0 +1,161 @@
1
+ import { getMoldableHostServices } from './frame-lifecycle';
2
+ const HOST_SERVICE_REQUEST_TYPE = 'moldable:service-request';
3
+ const HOST_SERVICE_RESPONSE_TYPE = 'moldable:service-response';
4
+ const HOST_SERVICE_CANCEL_TYPE = 'moldable:service-cancel';
5
+ const HOST_SERVICE_DEFAULT_TIMEOUT_MS = 15_000;
6
+ const HOST_SERVICE_CLIENT_TIMEOUT_GRACE_MS = 500;
7
+ export class HostServiceRequestError extends Error {
8
+ status;
9
+ code;
10
+ constructor(status, message, code) {
11
+ super(message);
12
+ this.name = 'HostServiceRequestError';
13
+ this.status = status;
14
+ this.code = code;
15
+ }
16
+ }
17
+ const pendingRequests = new Map();
18
+ let responseListenerInstalled = false;
19
+ function isServiceResponse(value) {
20
+ if (typeof value !== 'object' ||
21
+ value === null ||
22
+ !('type' in value) ||
23
+ value.type !== HOST_SERVICE_RESPONSE_TYPE ||
24
+ !('requestId' in value) ||
25
+ typeof value.requestId !== 'string' ||
26
+ !('status' in value)) {
27
+ return false;
28
+ }
29
+ return (value.status === 'completed' ||
30
+ value.status === 'cancelled' ||
31
+ value.status === 'error' ||
32
+ value.status === 'timeout');
33
+ }
34
+ function removePending(requestId) {
35
+ const pending = pendingRequests.get(requestId);
36
+ if (!pending)
37
+ return null;
38
+ pendingRequests.delete(requestId);
39
+ window.clearTimeout(pending.timeoutId);
40
+ if (pending.signal && pending.abortHandler) {
41
+ pending.signal.removeEventListener('abort', pending.abortHandler);
42
+ }
43
+ return pending;
44
+ }
45
+ function responseError(response, status) {
46
+ const code = typeof response.error === 'object' &&
47
+ response.error !== null &&
48
+ typeof response.error.code === 'string'
49
+ ? response.error.code
50
+ : undefined;
51
+ const message = typeof response.error === 'string'
52
+ ? response.error
53
+ : typeof response.error === 'object' &&
54
+ response.error !== null &&
55
+ typeof response.error.message === 'string'
56
+ ? response.error.message
57
+ : status === 'cancelled'
58
+ ? 'The host service request was cancelled.'
59
+ : status === 'timeout'
60
+ ? 'The host service request timed out.'
61
+ : 'The host service request failed.';
62
+ return new HostServiceRequestError(status, message, code);
63
+ }
64
+ function installResponseListener() {
65
+ if (responseListenerInstalled || typeof window === 'undefined')
66
+ return;
67
+ responseListenerInstalled = true;
68
+ window.addEventListener('message', (event) => {
69
+ if (event.source !== window.parent || !isServiceResponse(event.data)) {
70
+ return;
71
+ }
72
+ const pending = removePending(event.data.requestId);
73
+ if (!pending)
74
+ return;
75
+ if (event.data.status === 'completed') {
76
+ pending.resolve(event.data.result);
77
+ }
78
+ else {
79
+ pending.reject(responseError(event.data, event.data.status));
80
+ }
81
+ });
82
+ }
83
+ function createRequestId() {
84
+ if (typeof globalThis.crypto !== 'undefined' &&
85
+ typeof globalThis.crypto.randomUUID === 'function') {
86
+ return globalThis.crypto.randomUUID();
87
+ }
88
+ return `host-service-${Date.now()}-${Math.random().toString(36).slice(2)}`;
89
+ }
90
+ function abortError() {
91
+ return new DOMException('The host service request was aborted.', 'AbortError');
92
+ }
93
+ function postCancel(requestId) {
94
+ window.parent.postMessage({
95
+ type: HOST_SERVICE_CANCEL_TYPE,
96
+ requestId,
97
+ }, '*');
98
+ }
99
+ export function isHostServiceAvailable(name) {
100
+ const advertisement = getMoldableHostServices();
101
+ return Boolean(advertisement &&
102
+ Object.prototype.hasOwnProperty.call(advertisement.services, name));
103
+ }
104
+ export function requestHostService(name, payload, options = {}) {
105
+ if (typeof window === 'undefined' || !isHostServiceAvailable(name)) {
106
+ return Promise.reject(new HostServiceRequestError('error', `Host service "${name}" is not available.`, 'service_not_available'));
107
+ }
108
+ if (options.signal?.aborted) {
109
+ return Promise.reject(abortError());
110
+ }
111
+ if (options.timeoutMs !== undefined &&
112
+ (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) {
113
+ return Promise.reject(new TypeError('timeoutMs must be a positive finite number.'));
114
+ }
115
+ installResponseListener();
116
+ const requestId = createRequestId();
117
+ const hostTimeoutMs = Math.min(HOST_SERVICE_DEFAULT_TIMEOUT_MS, options.timeoutMs ?? HOST_SERVICE_DEFAULT_TIMEOUT_MS);
118
+ return new Promise((resolve, reject) => {
119
+ const timeoutId = window.setTimeout(() => {
120
+ const pending = removePending(requestId);
121
+ if (!pending)
122
+ return;
123
+ postCancel(requestId);
124
+ pending.reject(new HostServiceRequestError('timeout', 'The host service response did not arrive before the client timeout.', 'client_timeout'));
125
+ }, hostTimeoutMs + HOST_SERVICE_CLIENT_TIMEOUT_GRACE_MS);
126
+ const pending = {
127
+ resolve: (value) => resolve(value),
128
+ reject,
129
+ timeoutId,
130
+ signal: options.signal,
131
+ };
132
+ if (options.signal) {
133
+ pending.abortHandler = () => {
134
+ const active = removePending(requestId);
135
+ if (!active)
136
+ return;
137
+ postCancel(requestId);
138
+ active.reject(abortError());
139
+ };
140
+ options.signal.addEventListener('abort', pending.abortHandler, {
141
+ once: true,
142
+ });
143
+ }
144
+ pendingRequests.set(requestId, pending);
145
+ try {
146
+ window.parent.postMessage({
147
+ type: HOST_SERVICE_REQUEST_TYPE,
148
+ service: name,
149
+ requestId,
150
+ payload,
151
+ ...(options.timeoutMs === undefined
152
+ ? {}
153
+ : { timeoutMs: options.timeoutMs }),
154
+ }, '*');
155
+ }
156
+ catch (error) {
157
+ removePending(requestId);
158
+ reject(error);
159
+ }
160
+ });
161
+ }
@@ -0,0 +1,39 @@
1
+ import { type HostServiceRequestOptions } from './host-services';
2
+ export type HostShareItem = {
3
+ kind: 'text';
4
+ text: string;
5
+ } | {
6
+ kind: 'url';
7
+ url: string;
8
+ } | {
9
+ kind: 'fileReference';
10
+ path: string;
11
+ };
12
+ export interface HostShareAnchorRect {
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ }
18
+ export interface HostShareOptions extends HostServiceRequestOptions {
19
+ anchor?: Element | HostShareAnchorRect;
20
+ }
21
+ export type HostShareResult = {
22
+ status: 'completed';
23
+ method: 'native' | 'web';
24
+ } | {
25
+ status: 'cancelled';
26
+ method: 'native' | 'web';
27
+ } | {
28
+ status: 'fallback';
29
+ method: 'portable';
30
+ items: HostShareItem[];
31
+ reason: 'web_share_unavailable' | 'file_references_unsupported';
32
+ };
33
+ export interface HostShare {
34
+ available: boolean;
35
+ share: (items: HostShareItem[], options?: HostShareOptions) => Promise<HostShareResult>;
36
+ }
37
+ export declare function shareWithHost(items: HostShareItem[], options?: HostShareOptions): Promise<HostShareResult>;
38
+ export declare function useHostShare(): HostShare;
39
+ //# sourceMappingURL=host-share.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-share.d.ts","sourceRoot":"","sources":["../../src/lib/host-share.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,yBAAyB,EAG/B,MAAM,iBAAiB,CAAA;AAOxB,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAE3C,MAAM,WAAW,mBAAmB;IAClC,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,gBAAiB,SAAQ,yBAAyB;IACjE,MAAM,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAA;CACvC;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,QAAQ,GAAG,KAAK,CAAA;CAAE,GACjD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,QAAQ,GAAG,KAAK,CAAA;CAAE,GACjD;IACE,MAAM,EAAE,UAAU,CAAA;IAClB,MAAM,EAAE,UAAU,CAAA;IAClB,KAAK,EAAE,aAAa,EAAE,CAAA;IACtB,MAAM,EAAE,uBAAuB,GAAG,6BAA6B,CAAA;CAChE,CAAA;AAML,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,CACL,KAAK,EAAE,aAAa,EAAE,EACtB,OAAO,CAAC,EAAE,gBAAgB,KACvB,OAAO,CAAC,eAAe,CAAC,CAAA;CAC9B;AA2HD,wBAAsB,aAAa,CACjC,KAAK,EAAE,aAAa,EAAE,EACtB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAuD1B;AAED,wBAAgB,YAAY,IAAI,SAAS,CAoBxC"}
@@ -0,0 +1,161 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { HostServiceRequestError, isHostServiceAvailable, requestHostService, } from './host-services';
3
+ const HOST_CONTEXT_CHANGE_EVENT = 'moldable:host-context-change';
4
+ const SHARE_SERVICE_NAME = 'share';
5
+ const MAX_SHARE_ITEMS = 64;
6
+ const MAX_SHARE_ITEM_LENGTH = 256 * 1024;
7
+ function isAllowedShareUrl(value) {
8
+ try {
9
+ const protocol = new URL(value).protocol;
10
+ return (protocol === 'http:' || protocol === 'https:' || protocol === 'mailto:');
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ function validateItems(items) {
17
+ if (!Array.isArray(items) ||
18
+ items.length === 0 ||
19
+ items.length > MAX_SHARE_ITEMS) {
20
+ throw new TypeError('Share requests require 1–64 items.');
21
+ }
22
+ for (const item of items) {
23
+ if (!item || typeof item !== 'object') {
24
+ throw new TypeError('Share items must be objects.');
25
+ }
26
+ if (item.kind === 'text' &&
27
+ (typeof item.text !== 'string' ||
28
+ item.text.length === 0 ||
29
+ item.text.length > MAX_SHARE_ITEM_LENGTH)) {
30
+ throw new TypeError('Text share items require non-empty text.');
31
+ }
32
+ if (item.kind === 'url' &&
33
+ (typeof item.url !== 'string' ||
34
+ item.url.length > MAX_SHARE_ITEM_LENGTH ||
35
+ !isAllowedShareUrl(item.url))) {
36
+ throw new TypeError('URL share items must use http, https, or mailto.');
37
+ }
38
+ if (item.kind === 'fileReference' &&
39
+ (typeof item.path !== 'string' ||
40
+ item.path.length === 0 ||
41
+ item.path.length > MAX_SHARE_ITEM_LENGTH ||
42
+ item.path.includes('\0') ||
43
+ item.path.split(/[\\/]/).includes('..'))) {
44
+ throw new TypeError('File-reference share items require a non-traversing path.');
45
+ }
46
+ if (item.kind !== 'text' &&
47
+ item.kind !== 'url' &&
48
+ item.kind !== 'fileReference') {
49
+ throw new TypeError('Share items contain an unsupported kind.');
50
+ }
51
+ }
52
+ }
53
+ function anchorRect(anchor) {
54
+ if (!anchor)
55
+ return undefined;
56
+ const rect = typeof Element !== 'undefined' && anchor instanceof Element
57
+ ? anchor.getBoundingClientRect()
58
+ : anchor;
59
+ if (!Number.isFinite(rect.x) ||
60
+ !Number.isFinite(rect.y) ||
61
+ !Number.isFinite(rect.width) ||
62
+ !Number.isFinite(rect.height) ||
63
+ rect.width < 0 ||
64
+ rect.height < 0) {
65
+ throw new TypeError('Share anchors require a finite, non-negative rect.');
66
+ }
67
+ return {
68
+ x: rect.x,
69
+ y: rect.y,
70
+ width: rect.width,
71
+ height: rect.height,
72
+ };
73
+ }
74
+ function webShareData(items) {
75
+ if (items.some((item) => item.kind === 'fileReference'))
76
+ return null;
77
+ const textParts = [];
78
+ let url;
79
+ for (const item of items) {
80
+ if (item.kind === 'text') {
81
+ textParts.push(item.text);
82
+ }
83
+ else if (item.kind === 'url' && !url) {
84
+ url = item.url;
85
+ }
86
+ else if (item.kind === 'url') {
87
+ textParts.push(item.url);
88
+ }
89
+ }
90
+ return {
91
+ ...(textParts.length > 0 ? { text: textParts.join('\n') } : {}),
92
+ ...(url ? { url } : {}),
93
+ };
94
+ }
95
+ function abortError() {
96
+ return new DOMException('The share request was aborted.', 'AbortError');
97
+ }
98
+ function isCancelledError(error) {
99
+ return ((error instanceof HostServiceRequestError &&
100
+ error.status === 'cancelled') ||
101
+ (error instanceof DOMException && error.name === 'AbortError'));
102
+ }
103
+ export async function shareWithHost(items, options = {}) {
104
+ validateItems(items);
105
+ if (options.signal?.aborted)
106
+ throw abortError();
107
+ if (isHostServiceAvailable(SHARE_SERVICE_NAME)) {
108
+ try {
109
+ const result = await requestHostService(SHARE_SERVICE_NAME, {
110
+ items,
111
+ ...(options.anchor ? { anchor: anchorRect(options.anchor) } : {}),
112
+ }, {
113
+ signal: options.signal,
114
+ timeoutMs: options.timeoutMs,
115
+ });
116
+ if (result?.status !== 'completed') {
117
+ throw new Error('The native share service returned an invalid result.');
118
+ }
119
+ return { status: 'completed', method: 'native' };
120
+ }
121
+ catch (error) {
122
+ if (error instanceof HostServiceRequestError &&
123
+ error.status === 'cancelled') {
124
+ return { status: 'cancelled', method: 'native' };
125
+ }
126
+ throw error;
127
+ }
128
+ }
129
+ const shareData = webShareData(items);
130
+ if (shareData &&
131
+ typeof navigator !== 'undefined' &&
132
+ typeof navigator.share === 'function') {
133
+ try {
134
+ await navigator.share(shareData);
135
+ return { status: 'completed', method: 'web' };
136
+ }
137
+ catch (error) {
138
+ if (isCancelledError(error)) {
139
+ return { status: 'cancelled', method: 'web' };
140
+ }
141
+ throw error;
142
+ }
143
+ }
144
+ return {
145
+ status: 'fallback',
146
+ method: 'portable',
147
+ items,
148
+ reason: shareData ? 'web_share_unavailable' : 'file_references_unsupported',
149
+ };
150
+ }
151
+ export function useHostShare() {
152
+ const [available, setAvailable] = useState(() => isHostServiceAvailable(SHARE_SERVICE_NAME));
153
+ useEffect(() => {
154
+ const update = () => setAvailable(isHostServiceAvailable(SHARE_SERVICE_NAME));
155
+ update();
156
+ window.addEventListener(HOST_CONTEXT_CHANGE_EVENT, update);
157
+ return () => window.removeEventListener(HOST_CONTEXT_CHANGE_EVENT, update);
158
+ }, []);
159
+ const share = useCallback((items, options) => shareWithHost(items, options), []);
160
+ return { available, share };
161
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/lib/theme.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAMf,MAAM,OAAO,CAAA;AAEd,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;AAC/C,KAAK,aAAa,GAAG,OAAO,GAAG,MAAM,CAAA;AAErC,UAAU,iBAAiB;IACzB,KAAK,EAAE,KAAK,CAAA;IACZ,aAAa,EAAE,aAAa,CAAA;IAC5B,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC;AA6CD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CA0ClE;AAED,wBAAgB,QAAQ,sBAMvB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,kYAYvB,CAAA"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/lib/theme.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAMf,MAAM,OAAO,CAAA;AAGd,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;AAC/C,KAAK,aAAa,GAAG,OAAO,GAAG,MAAM,CAAA;AAErC,UAAU,iBAAiB;IACzB,KAAK,EAAE,KAAK,CAAA;IACZ,aAAa,EAAE,aAAa,CAAA;IAC5B,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC;AA6CD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CA0ClE;AAED,wBAAgB,QAAQ,sBAMvB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,kYAYvB,CAAA"}
package/dist/lib/theme.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useContext, useEffect, useState, } from 'react';
4
+ import { TooltipProvider } from '../components/ui/tooltip';
4
5
  const ThemeContext = createContext(null);
5
6
  const STORAGE_KEY = 'moldable-theme';
6
7
  function getSystemTheme() {
@@ -69,7 +70,7 @@ export function ThemeProvider({ children }) {
69
70
  mediaQuery.addEventListener('change', handleChange);
70
71
  return () => mediaQuery.removeEventListener('change', handleChange);
71
72
  }, [theme]);
72
- return (_jsx(ThemeContext.Provider, { value: { theme, resolvedTheme, setTheme }, children: children }));
73
+ return (_jsx(ThemeContext.Provider, { value: { theme, resolvedTheme, setTheme }, children: _jsx(TooltipProvider, { children: children }) }));
73
74
  }
74
75
  export function useTheme() {
75
76
  const context = useContext(ThemeContext);
package/docs/README.md CHANGED
@@ -8,9 +8,11 @@ feels at home on macOS without imitating private platform implementation.
8
8
 
9
9
  - [Foundations](./foundations.md)
10
10
  - [Design tokens](./design-tokens.md)
11
+ - [Adaptive materials](./adaptive-materials.md)
11
12
  - [App anatomy](./app-anatomy.md)
12
13
  - [Standalone app windows](./standalone-app-windows.md)
13
14
  - [Component catalog](./component-catalog.md)
15
+ - [Visual and accessibility regression](./visual-regression.md)
14
16
  - [Accessibility](./accessibility.md)
15
17
  - [Native integration](./native-integration.md)
16
18
  - [Component guide template](./component-guide-template.md)
@@ -34,4 +36,5 @@ changed view.
34
36
  7. Design loading, empty, error, disabled, and permission states with the main
35
37
  view, not afterward.
36
38
  8. Review component work in the catalog across theme, density, width, and
37
- interaction states.
39
+ interaction states. Material-bearing components also cover reduced
40
+ transparency, increased contrast, and reduced motion.