@satinhq/kit 0.1.0-beta.3 → 0.1.0-beta.4

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 (240) hide show
  1. package/NOTICE +1 -1
  2. package/README.md +1 -2
  3. package/dist/acknowledgements/inventory.json +1 -1
  4. package/dist/actions/RowActions.d.ts +2 -2
  5. package/dist/actions/RowActions.js +91 -25
  6. package/dist/actions/fullSwipeContact.d.ts +73 -0
  7. package/dist/actions/fullSwipeContact.js +186 -0
  8. package/dist/actions/rowScrub.d.ts +7 -4
  9. package/dist/actions/rowScrub.js +39 -37
  10. package/dist/actions/types.d.ts +13 -7
  11. package/dist/app/InstallComponents.d.ts +4 -7
  12. package/dist/app/InstallComponents.js +7 -7
  13. package/dist/app/InstallationDiagnostics.d.ts +2 -1
  14. package/dist/app/InstallationDiagnostics.js +2 -1
  15. package/dist/app/appearance.d.ts +5 -3
  16. package/dist/app/appearance.js +5 -3
  17. package/dist/app/appearanceStore.d.ts +3 -2
  18. package/dist/app/buildInfo.d.ts +4 -4
  19. package/dist/app/buildInfo.js +2 -2
  20. package/dist/app/definition.d.ts +11 -3
  21. package/dist/app/definition.js +2 -0
  22. package/dist/app/head.d.ts +2 -1
  23. package/dist/app/head.js +13 -16
  24. package/dist/app/inlineCSP.d.ts +6 -0
  25. package/dist/app/inlineCSP.js +25 -0
  26. package/dist/app/install.d.ts +1 -0
  27. package/dist/app/install.js +5 -2
  28. package/dist/app/installCheck.js +32 -86
  29. package/dist/app/installContext.d.ts +10 -5
  30. package/dist/app/installContext.js +2 -0
  31. package/dist/app/installCustom.js +1 -0
  32. package/dist/app/installExperience.js +0 -60
  33. package/dist/app/installationFailure.d.ts +3 -2
  34. package/dist/app/installationFailure.js +1 -1
  35. package/dist/app/launchScreen.d.ts +6 -2
  36. package/dist/app/launchScreen.js +104 -1
  37. package/dist/app/launchScreenRuntime.d.ts +31 -4
  38. package/dist/app/launchScreenRuntime.js +150 -22
  39. package/dist/app/launchWatchdog.js +25 -65
  40. package/dist/app/platformScripts.js +11 -0
  41. package/dist/app/styles.d.ts +3 -0
  42. package/dist/app/styles.js +42 -0
  43. package/dist/app/viewportValue.d.ts +2 -1
  44. package/dist/app/vite.d.ts +4 -0
  45. package/dist/app/vite.js +22 -24
  46. package/dist/content/content.d.ts +35 -30
  47. package/dist/content/content.js +19 -16
  48. package/dist/content/layout.d.ts +10 -2
  49. package/dist/content/layout.js +4 -2
  50. package/dist/content/scaledMetric.d.ts +2 -1
  51. package/dist/content/scaledMetric.js +2 -1
  52. package/dist/controls/ColorPicker.d.ts +2 -4
  53. package/dist/controls/ColorPicker.js +2 -4
  54. package/dist/controls/DatePicker.d.ts +4 -4
  55. package/dist/controls/DatePicker.js +2 -2
  56. package/dist/controls/ProgressView.d.ts +4 -4
  57. package/dist/controls/ProgressView.js +1 -2
  58. package/dist/data/database.d.ts +9 -8
  59. package/dist/data/database.js +4 -4
  60. package/dist/docs/reference.md +982 -964
  61. package/dist/docs/tailwind.md +76 -0
  62. package/dist/docs/tokens.md +1 -1
  63. package/dist/gestures/GestureView.d.ts +3 -1
  64. package/dist/gestures/GestureView.js +2 -1
  65. package/dist/gestures/gesture.d.ts +22 -2
  66. package/dist/gestures/gesture.js +7 -2
  67. package/dist/glass/GlassEffect.d.ts +2 -1
  68. package/dist/glass/GlassEffect.js +2 -1
  69. package/dist/glass/GlassPresentation.d.ts +1 -0
  70. package/dist/glass/GlassPresentation.js +21 -4
  71. package/dist/glass/TabBar.js +23 -1
  72. package/dist/glass/errorAlert.js +2 -1
  73. package/dist/glass/errors.d.ts +3 -1
  74. package/dist/glass/fallback/runtime.d.ts +1 -0
  75. package/dist/glass/fallback/runtime.js +8 -1
  76. package/dist/glass/liquidContext.d.ts +2 -0
  77. package/dist/glass/menuPresenter.d.ts +2 -0
  78. package/dist/glass/regions.d.ts +2 -1
  79. package/dist/glass/renderSpecs.d.ts +1 -0
  80. package/dist/glass/renderSpecs.js +1 -0
  81. package/dist/glass/webgpu/controlRim.d.ts +29 -0
  82. package/dist/glass/webgpu/controlRim.js +68 -0
  83. package/dist/glass/webgpu/passes.d.ts +2 -2
  84. package/dist/glass/webgpu/production.d.ts +1 -0
  85. package/dist/glass/webgpu/production.js +27 -1
  86. package/dist/glass/webgpu/rasterUniforms.js +9 -0
  87. package/dist/glass/webgpu/shaders.d.ts +2 -2
  88. package/dist/glass/webgpu/shaders.js +5 -1
  89. package/dist/haptics/SensoryButton.d.ts +4 -4
  90. package/dist/haptics/SensoryButton.js +3 -3
  91. package/dist/image/Image.d.ts +6 -3
  92. package/dist/image/Image.js +4 -3
  93. package/dist/image/asset.d.ts +2 -1
  94. package/dist/keyboard/adapter.d.ts +3 -4
  95. package/dist/keyboard/geometry.d.ts +1 -1
  96. package/dist/list-form/ListForm.d.ts +8 -7
  97. package/dist/list-form/ListForm.js +2 -2
  98. package/dist/list-form/editing.d.ts +5 -6
  99. package/dist/list-form/editing.js +4 -5
  100. package/dist/list-form/offsets.d.ts +2 -2
  101. package/dist/list-form/offsets.js +2 -2
  102. package/dist/media/react.d.ts +2 -1
  103. package/dist/media/react.js +2 -1
  104. package/dist/media/types.d.ts +6 -3
  105. package/dist/motion/TransitionView.d.ts +4 -2
  106. package/dist/motion/TransitionView.js +3 -2
  107. package/dist/motion/animationValue.d.ts +1 -1
  108. package/dist/motion/matchedGeometry.d.ts +6 -5
  109. package/dist/motion/matchedGeometry.js +2 -1
  110. package/dist/motion/transition.d.ts +4 -1
  111. package/dist/motion/transition.js +2 -0
  112. package/dist/navigation/DetailList.d.ts +1 -1
  113. package/dist/navigation/DetailList.js +1 -1
  114. package/dist/navigation/Sidebar.d.ts +3 -1
  115. package/dist/navigation/Sidebar.js +1 -1
  116. package/dist/navigation/SidebarSymbol.d.ts +2 -1
  117. package/dist/navigation/SidebarSymbol.js +2 -1
  118. package/dist/navigation/navigationContext.d.ts +2 -1
  119. package/dist/navigation/navigationContext.js +2 -1
  120. package/dist/navigation/splitContext.d.ts +2 -1
  121. package/dist/navigation/splitContext.js +2 -1
  122. package/dist/navigation/toolbarMinimization.d.ts +4 -3
  123. package/dist/navigation/types.d.ts +7 -5
  124. package/dist/presentation/Presentation.d.ts +8 -7
  125. package/dist/presentation/Presentation.js +3 -2
  126. package/dist/presentation/model.d.ts +2 -1
  127. package/dist/press/Pressable.d.ts +5 -1
  128. package/dist/press/Pressable.js +2 -0
  129. package/dist/press/stateMachine.d.ts +1 -1
  130. package/dist/public/Label.d.ts +2 -1
  131. package/dist/public/Label.js +2 -1
  132. package/dist/public/Screen.d.ts +4 -6
  133. package/dist/public/actions.d.ts +2 -1
  134. package/dist/public/actions.js +2 -1
  135. package/dist/public/buttonBorderShape.d.ts +2 -1
  136. package/dist/public/colors.d.ts +7 -4
  137. package/dist/public/colors.js +3 -2
  138. package/dist/public/controlStyle.d.ts +17 -14
  139. package/dist/public/controls.d.ts +27 -26
  140. package/dist/public/controls.js +20 -19
  141. package/dist/public/deviceActions.d.ts +6 -4
  142. package/dist/public/deviceActions.js +4 -2
  143. package/dist/public/environment.d.ts +16 -13
  144. package/dist/public/environment.js +11 -9
  145. package/dist/public/filePickers.d.ts +6 -4
  146. package/dist/public/filePickers.js +3 -2
  147. package/dist/public/glass.d.ts +6 -3
  148. package/dist/public/glass.js +2 -1
  149. package/dist/public/glassValue.d.ts +3 -4
  150. package/dist/public/glassValue.js +3 -4
  151. package/dist/public/keyboard.d.ts +4 -1
  152. package/dist/public/keyboard.js +2 -1
  153. package/dist/public/navigationTransition.d.ts +5 -5
  154. package/dist/public/navigationTransition.js +2 -2
  155. package/dist/public/presentation.d.ts +8 -7
  156. package/dist/public/presentation.js +5 -5
  157. package/dist/public/runtime.d.ts +9 -8
  158. package/dist/public/runtime.js +7 -7
  159. package/dist/public/scroll.d.ts +6 -3
  160. package/dist/public/scroll.js +3 -1
  161. package/dist/public/structure.d.ts +2 -2
  162. package/dist/public/toolbar.d.ts +15 -11
  163. package/dist/public/toolbar.js +12 -7
  164. package/dist/reachability/model.d.ts +4 -3
  165. package/dist/reachability/react.d.ts +4 -1
  166. package/dist/reachability/react.js +2 -1
  167. package/dist/root/SatinRoot.d.ts +3 -3
  168. package/dist/root/SatinRoot.js +12 -36
  169. package/dist/root/mountSatin.d.ts +1 -1
  170. package/dist/root/mountSatin.js +26 -35
  171. package/dist/root/runtimeContext.d.ts +2 -1
  172. package/dist/root/runtimeContext.js +2 -1
  173. package/dist/safe-area/Overlay.d.ts +3 -2
  174. package/dist/safe-area/Overlay.js +2 -1
  175. package/dist/safe-area/SafeAreaInset.d.ts +11 -7
  176. package/dist/safe-area/SafeAreaInset.js +7 -6
  177. package/dist/safe-area/context.d.ts +3 -3
  178. package/dist/safe-area/context.js +1 -1
  179. package/dist/safe-area/displayCorners.d.ts +4 -2
  180. package/dist/safe-area/displayCorners.js +4 -2
  181. package/dist/safe-area/modifiers.d.ts +14 -10
  182. package/dist/safe-area/modifiers.js +8 -4
  183. package/dist/scroll/ScrollSurface.js +5 -1
  184. package/dist/scroll/ScrollTarget.d.ts +1 -1
  185. package/dist/scroll/ScrollTarget.js +1 -1
  186. package/dist/scroll/edgePolicy.d.ts +2 -2
  187. package/dist/scroll/position.d.ts +5 -3
  188. package/dist/scroll/types.d.ts +1 -1
  189. package/dist/search/PadSearchDrawer.d.ts +7 -0
  190. package/dist/search/PadSearchDrawer.js +8 -0
  191. package/dist/search/PadSearchPresentationContext.d.ts +6 -0
  192. package/dist/search/PadSearchPresentationContext.js +4 -0
  193. package/dist/search/SearchDrawer.d.ts +1 -1
  194. package/dist/search/SearchDrawer.js +6 -1
  195. package/dist/search/SearchSuggestionsPopover.d.ts +6 -0
  196. package/dist/search/SearchSuggestionsPopover.js +42 -0
  197. package/dist/search/SearchToolbarField.d.ts +2 -1
  198. package/dist/search/SearchToolbarField.js +28 -7
  199. package/dist/search/padSearchBarMotion.d.ts +69 -0
  200. package/dist/search/padSearchBarMotion.js +73 -0
  201. package/dist/search/padSearchFieldSamples.json +491 -0
  202. package/dist/search/searchPlacement.d.ts +3 -0
  203. package/dist/search/searchPlacement.js +7 -0
  204. package/dist/search/searchable.d.ts +14 -12
  205. package/dist/search/usePadSearchFieldMotion.d.ts +4 -0
  206. package/dist/search/usePadSearchFieldMotion.js +100 -0
  207. package/dist/search/useSearchPresentation.d.ts +2 -1
  208. package/dist/search/useSearchPresentation.js +23 -5
  209. package/dist/server.d.ts +4 -4
  210. package/dist/server.js +4 -4
  211. package/dist/shapes/corners.d.ts +3 -3
  212. package/dist/shapes/corners.js +1 -2
  213. package/dist/styles.css +8289 -8244
  214. package/dist/sw/vite.d.ts +4 -0
  215. package/dist/sw/vite.js +2 -0
  216. package/dist/symbols/Symbol.d.ts +7 -5
  217. package/dist/symbols/Symbol.js +5 -3
  218. package/dist/symbols/catalogue.d.ts +1 -1
  219. package/dist/symbols/types.d.ts +6 -4
  220. package/dist/tabs/TabSection.d.ts +3 -2
  221. package/dist/tabs/TabSection.js +2 -1
  222. package/dist/tabs/TabView.js +54 -51
  223. package/dist/tasks/useTask.d.ts +5 -4
  224. package/dist/tasks/useTask.js +4 -3
  225. package/dist/testing.d.ts +3 -2
  226. package/dist/testing.js +3 -2
  227. package/dist/text-input/focusChain.d.ts +4 -3
  228. package/dist/text-input/focusChain.js +3 -2
  229. package/dist/text-input/kinds.d.ts +1 -1
  230. package/dist/web/WebView.d.ts +10 -3
  231. package/dist/web/WebView.js +3 -2
  232. package/dist/web/authentication.d.ts +12 -1
  233. package/dist/web/authentication.js +2 -1
  234. package/dist/web/openURL.d.ts +10 -4
  235. package/dist/web/openURL.js +4 -2
  236. package/dist/web/useWebAuthentication.d.ts +2 -1
  237. package/dist/web/useWebAuthentication.js +2 -1
  238. package/dist/zoom/Zoomable.d.ts +3 -3
  239. package/dist/zoom/Zoomable.js +2 -2
  240. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  import { flushSync } from 'react-dom';
2
2
  import { createRoot } from 'react-dom/client';
3
- import { launchErrorLayer } from '../app/launchScreen.js';
3
+ import { prelaunchController, showLaunchFailure } from '../app/launchScreenRuntime.js';
4
4
  /** Mount once from the app entry point, after resolving its initial screen.
5
- * The first commit runs in this task so installed WebKit cannot defer it behind
5
+ * After managed styles load, the first commit runs synchronously so WebKit cannot defer it behind
6
6
  * its launch presentation. Later updates retain React's normal scheduling.
7
7
  * Do not call from a component or an effect, or suspend the initial screen.
8
8
  */
@@ -17,9 +17,29 @@ export function mountSatin(container, content, options) {
17
17
  reportError(error);
18
18
  },
19
19
  });
20
- performance.mark('satin-mount-start');
21
- flushSync(() => root.render(content));
22
- performance.mark('satin-mount-committed');
20
+ let disposed = false;
21
+ const unmount = root.unmount.bind(root);
22
+ root.unmount = () => {
23
+ disposed = true;
24
+ unmount();
25
+ };
26
+ const commit = () => {
27
+ if (disposed)
28
+ return;
29
+ performance.mark('satin-mount-start');
30
+ flushSync(() => root.render(content));
31
+ performance.mark('satin-mount-committed');
32
+ };
33
+ // A glass renderer cannot measure or present an unstyled React tree. The
34
+ // independent head environment owns this wait, including any load failure.
35
+ if (document.querySelector('link[data-satin-style]')) {
36
+ void prelaunchController().stylesReady().then(commit, error => {
37
+ if (!disposed)
38
+ showLaunchFailure(error instanceof Error ? error.message : String(error));
39
+ });
40
+ }
41
+ else
42
+ commit();
23
43
  return root;
24
44
  }
25
45
  /** An uncaught render or effect error unmounts the whole app. Without this the launch cover
@@ -27,34 +47,5 @@ export function mountSatin(container, content, options) {
27
47
  * above the launch cover, with the same presentation as a failed launch. */
28
48
  export function showAppFailure(error) {
29
49
  performance.mark('satin-app-failed');
30
- document.querySelector('[data-satin-app-failure]')?.remove();
31
- const panel = document.createElement('div');
32
- panel.dataset.satinRole = 'chrome';
33
- panel.dataset.satinLaunchError = 'true';
34
- panel.dataset.satinAppFailure = 'true';
35
- panel.setAttribute('role', 'alert');
36
- panel.style.zIndex = String(launchErrorLayer);
37
- const message = document.createElement('div');
38
- message.className = 'satin-launch-error-message';
39
- const title = document.createElement('h1');
40
- title.textContent = 'App Failed to Start';
41
- const detail = document.createElement('pre');
42
- detail.style.whiteSpace = 'pre-wrap';
43
- detail.style.overflowWrap = 'anywhere';
44
- detail.textContent = error instanceof Error ? error.message : String(error);
45
- const reload = document.createElement('button');
46
- reload.type = 'button';
47
- reload.textContent = 'Reload';
48
- reload.addEventListener('click', () => window.location.reload());
49
- message.append(title, detail, reload);
50
- const icon = document.querySelector('#satin-launch-screen .satin-launch-icon img');
51
- if (icon?.src) {
52
- const image = document.createElement('img');
53
- image.className = 'satin-launch-error-icon';
54
- image.src = icon.src;
55
- image.alt = '';
56
- panel.append(image);
57
- }
58
- panel.append(message);
59
- document.body.append(panel);
50
+ showLaunchFailure(error instanceof Error ? error.message : String(error), 'App Failed to Start');
60
51
  }
@@ -22,5 +22,6 @@ export declare function useSatinLayoutRuntime(): Omit<SatinRuntime, 'metrics'> &
22
22
  export declare function useSatinRuntime(): SatinRuntime;
23
23
  /** Classes follow the layout viewport; keyboard occlusion and zoom do not select panes. */
24
24
  export declare function useLayoutClass(): LayoutClass;
25
- /** Register app-managed unsaved state while it exists. Native inputs are checked automatically. */
25
+ /** Tells Satin the app has unsaved work while `unsaved` is true, so a pending app update waits
26
+ * instead of reloading over it. Edits in text fields are detected without this hook. */
26
27
  export declare function useUnsavedChanges(unsaved: boolean): void;
@@ -19,7 +19,8 @@ export function useSatinRuntime() {
19
19
  export function useLayoutClass() {
20
20
  return useSatinLayoutRuntime().layoutClass;
21
21
  }
22
- /** Register app-managed unsaved state while it exists. Native inputs are checked automatically. */
22
+ /** Tells Satin the app has unsaved work while `unsaved` is true, so a pending app update waits
23
+ * instead of reloading over it. Edits in text fields are detected without this hook. */
23
24
  export function useUnsavedChanges(unsaved) {
24
25
  const { registerUnsavedChanges } = useSatinLayoutRuntime();
25
26
  useEffect(() => {
@@ -1,9 +1,10 @@
1
1
  import { type HTMLAttributes, type ReactNode } from 'react';
2
2
  import type { ContentAlignment } from '../content/content.js';
3
- /** `overlay(alignment:content:)`: children determine the frame; content respects its safe area. */
3
+ /** The layered `content` and its alignment. The children set the frame. */
4
4
  export type OverlayProps = Omit<HTMLAttributes<HTMLDivElement>, 'content'> & Readonly<{
5
5
  alignment?: ContentAlignment;
6
6
  content: ReactNode;
7
7
  }>;
8
- /** An overlay in SafeAreaInset shares its reduced safe area and keyboard carrier with the composer. */
8
+ /** Layers `content` over its children without changing their frame. Inside a `SafeAreaInset`, the
9
+ * overlay keeps to the reduced safe area and moves with the keyboard, like the composer. */
9
10
  export declare function Overlay({ alignment, content, children, ...props }: OverlayProps): import("react").JSX.Element;
@@ -4,7 +4,8 @@ import { BottomInsetContext } from './SafeAreaInset.js';
4
4
  // Overlay content is already inside the parent's reduced safe area. Its descendants consume
5
5
  // only their own local insets and inherit the parent's keyboard transform.
6
6
  const CONTENT_SCOPE = { consumed: true };
7
- /** An overlay in SafeAreaInset shares its reduced safe area and keyboard carrier with the composer. */
7
+ /** Layers `content` over its children without changing their frame. Inside a `SafeAreaInset`, the
8
+ * overlay keeps to the reduced safe area and moves with the keyboard, like the composer. */
8
9
  export function Overlay({ alignment = 'center', content, children, ...props }) {
9
10
  const { geometry, consumed } = useContext(BottomInsetContext);
10
11
  const layer = useRef(null);
@@ -5,21 +5,25 @@ export declare const BottomInsetContext: import("react").Context<{
5
5
  consumed: boolean;
6
6
  geometry?: ReturnType<typeof createBottomInsetGeometry>;
7
7
  }>;
8
- /** `VerticalEdge`/`HorizontalEdge`: `leading` and `trailing` follow the layout direction. */
8
+ /** The edge a `SafeAreaInset` accessory sits on. `leading` and `trailing` follow the layout
9
+ * direction. */
9
10
  export type SafeAreaInsetEdge = 'top' | 'bottom' | 'leading' | 'trailing';
10
11
  /** The edge, the accessory (`content`) and its spacing; `children` is the content it insets. */
11
12
  export type SafeAreaInsetProps = Omit<HTMLAttributes<HTMLDivElement>, 'content'> & Readonly<{
12
13
  edge: SafeAreaInsetEdge;
13
14
  content: ReactNode;
15
+ /** Room between the content and the accessory, in points. Defaults to the system spacing,
16
+ * 8 pt. */
14
17
  spacing?: number;
15
18
  }>;
16
19
  /**
17
- * `safeAreaInset(edge:spacing:content:)`. The accessory takes room from the content's safe
18
- * area on its edge; a scroll view inside still spans the whole frame and scrolls under it.
20
+ * Pins an accessory, such as a composer or a filter bar, to one edge of its content. The
21
+ * accessory takes room from the content's safe area on its edge; a scroll view inside still
22
+ * spans the whole frame and scrolls under it.
19
23
  *
20
- * Bottom: content can scroll under the accessory; its terminal anchor clears it.
21
- * The accessory's height reaches the content's scroll inset in the same frame it changes (CSS
22
- * `--satin-inset-accessory-height`, written by its resize observer), so a composer that grows by
23
- * a line keeps the newest message above it with no frame between.
24
+ * At the bottom, content scrolls under the accessory and its last item stays clear of it. When
25
+ * the accessory resizes, the scroll inset changes in the same frame (through
26
+ * `--satin-inset-accessory-height`), so a composer that grows by a line keeps the newest message
27
+ * in view.
24
28
  */
25
29
  export declare function SafeAreaInset({ edge, content, spacing, children, ...props }: SafeAreaInsetProps): import("react").JSX.Element;
@@ -10,13 +10,14 @@ export const BottomInsetContext = createContext({ consumed: false });
10
10
  const SYSTEM_INSET_SPACING_PX = 8;
11
11
  const ACCESSORY_INSET_SCOPE = { consumed: true };
12
12
  /**
13
- * `safeAreaInset(edge:spacing:content:)`. The accessory takes room from the content's safe
14
- * area on its edge; a scroll view inside still spans the whole frame and scrolls under it.
13
+ * Pins an accessory, such as a composer or a filter bar, to one edge of its content. The
14
+ * accessory takes room from the content's safe area on its edge; a scroll view inside still
15
+ * spans the whole frame and scrolls under it.
15
16
  *
16
- * Bottom: content can scroll under the accessory; its terminal anchor clears it.
17
- * The accessory's height reaches the content's scroll inset in the same frame it changes (CSS
18
- * `--satin-inset-accessory-height`, written by its resize observer), so a composer that grows by
19
- * a line keeps the newest message above it with no frame between.
17
+ * At the bottom, content scrolls under the accessory and its last item stays clear of it. When
18
+ * the accessory resizes, the scroll inset changes in the same frame (through
19
+ * `--satin-inset-accessory-height`), so a composer that grows by a line keeps the newest message
20
+ * in view.
20
21
  */
21
22
  export function SafeAreaInset({ edge, content, spacing = SYSTEM_INSET_SPACING_PX, children, ...props }) {
22
23
  if (edge !== 'bottom')
@@ -1,12 +1,12 @@
1
- /** `Edge`. `leading` and `trailing` follow the layout direction. */
1
+ /** A logical edge. `leading` and `trailing` follow the layout direction. */
2
2
  export type Edge = 'top' | 'bottom' | 'leading' | 'trailing';
3
- /** `EdgeInsets` in points, by layout direction. */
3
+ /** Optional lengths per logical edge, in points. */
4
4
  export type EdgeLengths = Readonly<Partial<Record<Edge, number>>>;
5
5
  export type Lengths = Readonly<Record<Edge, number>>;
6
6
  export declare const NO_LENGTHS: Lengths;
7
7
  /** Safe-area padding the enclosing modifiers add to the next scroll view's content, per edge. */
8
8
  export declare const SafeAreaPaddingContext: import("react").Context<Readonly<Record<Edge, number>>>;
9
- /** `contentMargins(for: .scrollContent)` in effect for every scroll view below, per edge. */
9
+ /** The content margins in effect for every scroll view below, per edge. */
10
10
  export declare const ContentMarginsContext: import("react").Context<Readonly<Partial<Record<Edge, number>>>>;
11
11
  /** The safe-area regions and edges an enclosing `IgnoresSafeArea` gives up. */
12
12
  export declare const IgnoresSafeAreaContext: import("react").Context<{
@@ -2,7 +2,7 @@ import { createContext } from 'react';
2
2
  export const NO_LENGTHS = { top: 0, bottom: 0, leading: 0, trailing: 0 };
3
3
  /** Safe-area padding the enclosing modifiers add to the next scroll view's content, per edge. */
4
4
  export const SafeAreaPaddingContext = createContext(NO_LENGTHS);
5
- /** `contentMargins(for: .scrollContent)` in effect for every scroll view below, per edge. */
5
+ /** The content margins in effect for every scroll view below, per edge. */
6
6
  export const ContentMarginsContext = createContext({});
7
7
  /** The safe-area regions and edges an enclosing `IgnoresSafeArea` gives up. */
8
8
  export const IgnoresSafeAreaContext = createContext({ container: new Set(), keyboard: false });
@@ -15,7 +15,9 @@ export type DisplayCornerMetrics = Readonly<{
15
15
  export declare function resolveDisplayCornerMetrics(display: DisplayGeometry): DisplayCornerMetrics;
16
16
  /** Framework token for the screen containing this app. Display Zoom and unknown models fall back to zero. */
17
17
  export declare function readDisplayCornerMetrics(): DisplayCornerMetrics;
18
- /** Updates after rotation, window moves and DPR changes announced by a resize. */
18
+ /** The corner radius of the device screen in points, or 0 when the model is unknown. It updates
19
+ * after rotation, window moves and pixel-ratio changes that fire a resize. */
19
20
  export declare function useDisplayCornerRadius(): number;
20
- /** Radius for a shape inset equally from a rounded container. Insets consume, never invert, corners. */
21
+ /** The corner radius for a shape inset evenly inside a rounded container, so the two corners stay
22
+ * concentric. The result never goes below 0; a negative or non-finite argument throws. */
21
23
  export declare function concentricRadius(containerRadius: number, inset: number): number;
@@ -44,7 +44,8 @@ export function readDisplayCornerMetrics() {
44
44
  scale: window.devicePixelRatio,
45
45
  });
46
46
  }
47
- /** Updates after rotation, window moves and DPR changes announced by a resize. */
47
+ /** The corner radius of the device screen in points, or 0 when the model is unknown. It updates
48
+ * after rotation, window moves and pixel-ratio changes that fire a resize. */
48
49
  export function useDisplayCornerRadius() {
49
50
  const [radius, setRadius] = useState(() => readDisplayCornerMetrics().radius);
50
51
  useLayoutEffect(() => {
@@ -59,7 +60,8 @@ export function useDisplayCornerRadius() {
59
60
  }, []);
60
61
  return radius;
61
62
  }
62
- /** Radius for a shape inset equally from a rounded container. Insets consume, never invert, corners. */
63
+ /** The corner radius for a shape inset evenly inside a rounded container, so the two corners stay
64
+ * concentric. The result never goes below 0; a negative or non-finite argument throws. */
63
65
  export function concentricRadius(containerRadius, inset) {
64
66
  if (!Number.isFinite(containerRadius) ||
65
67
  containerRadius < 0 ||
@@ -4,22 +4,23 @@ import { type HTMLAttributes, type ReactNode } from 'react';
4
4
  export type { Edge, EdgeLengths } from './context.js';
5
5
  /** A logical edge, an axis pair or all edges. */
6
6
  export type EdgeSetMember = Edge | 'horizontal' | 'vertical' | 'all';
7
- /** `Edge.Set`: one member or several. */
7
+ /** One edge group or a list of them. */
8
8
  export type EdgeSet = EdgeSetMember | readonly EdgeSetMember[];
9
- /** The content and standard DOM attributes of a layout modifier wrapper. */
9
+ /** The children and plain div attributes every layout wrapper takes. */
10
10
  export type LayoutModifierProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & Readonly<{
11
11
  children?: ReactNode;
12
12
  }>;
13
13
  /** Which edges to pad, and by how much. */
14
14
  export type SafeAreaPaddingProps = LayoutModifierProps & Readonly<{
15
- /** Edges to pad. Defaults to all edges, as `safeAreaPadding()`. */
15
+ /** Edges to pad. Defaults to all edges. */
16
16
  edges?: EdgeSet;
17
17
  /** Points. Defaults to the system padding, 16 pt. */
18
18
  length?: number;
19
- /** `safeAreaPadding(_ insets: EdgeInsets)`; overrides `edges` and `length`. */
19
+ /** A length per edge. Overrides `edges` and `length`. */
20
20
  insets?: EdgeLengths;
21
21
  }>;
22
- /** Adds to the content safe area. Scroll views keep their frame and inset content; other content is padded. Nested padding adds up. */
22
+ /** Grows the safe area of the content inside it. Scroll views keep their frame and inset their
23
+ * content; other content is padded. Nested padding adds up. */
23
24
  export declare function SafeAreaPadding({ edges, length, insets, children, ...props }: SafeAreaPaddingProps): import("react").JSX.Element;
24
25
  /** Which edges get a margin, how much, and whether it applies to content or indicators. */
25
26
  export type ContentMarginsProps = LayoutModifierProps & Readonly<{
@@ -27,26 +28,29 @@ export type ContentMarginsProps = LayoutModifierProps & Readonly<{
27
28
  /** Points. */
28
29
  length: number;
29
30
  /**
30
- * `ContentMarginPlacement`. `scrollContent` (and `automatic`) margins the content;
31
+ * What the margin applies to. `scrollContent` (and `automatic`) margins the content;
31
32
  * `scrollIndicators` only moves the indicator, which Satin draws in the screen frame and does
32
33
  * not inset, so it has no effect yet.
33
34
  */
34
35
  placement?: 'automatic' | 'scrollContent' | 'scrollIndicators';
35
36
  }>;
36
- /** Sets margins for descendant scroll views. Lists use horizontal layout margins; the innermost value for each edge wins. */
37
+ /** Sets content margins for every scroll view inside it. A `List` takes a horizontal margin as its
38
+ * layout margin. The innermost value for each edge wins. */
37
39
  export declare function ContentMargins({ edges, length, placement, children, ...props }: ContentMarginsProps): import("react").JSX.Element;
38
40
  /** A `SafeAreaInset` limited to the top or bottom edge, with no spacing. */
39
41
  export type SafeAreaBarProps = Omit<SafeAreaInsetProps, 'edge' | 'spacing'> & Readonly<{
40
42
  edge: 'top' | 'bottom';
41
43
  }>;
42
- /** Places a top or bottom safe-area inset with no spacing and its own scroll-edge material. */
44
+ /** A bar on the top or bottom edge that content scrolls under. It is a `SafeAreaInset` with no
45
+ * spacing and its own scroll-edge blur. */
43
46
  export declare function SafeAreaBar({ edge, ...props }: SafeAreaBarProps): import("react").JSX.Element;
44
47
  /** Which safe-area regions and edges the content ignores. */
45
48
  export type IgnoresSafeAreaProps = LayoutModifierProps & Readonly<{
46
- /** `SafeAreaRegions`. Defaults to `'all'`. */
49
+ /** `container` (the bars and screen edges), `keyboard`, or `all`, the default. */
47
50
  regions?: 'all' | 'container' | 'keyboard';
48
51
  /** Defaults to all edges. */
49
52
  edges?: EdgeSet;
50
53
  }>;
51
- /** Extends content through selected safe-area edges. Container edges and keyboard avoidance can be controlled independently. */
54
+ /** Lets content extend through the safe area on the chosen edges, for full-bleed backgrounds and
55
+ * media. The container edges and keyboard avoidance can be ignored separately. */
52
56
  export declare function IgnoresSafeArea({ regions, edges, children, ...props }: IgnoresSafeAreaProps): import("react").JSX.Element;
@@ -29,7 +29,8 @@ function lengthsOf(edges, length, insets) {
29
29
  trailing: set.has('trailing') ? amount : 0,
30
30
  };
31
31
  }
32
- /** Adds to the content safe area. Scroll views keep their frame and inset content; other content is padded. Nested padding adds up. */
32
+ /** Grows the safe area of the content inside it. Scroll views keep their frame and inset their
33
+ * content; other content is padded. Nested padding adds up. */
33
34
  export function SafeAreaPadding({ edges, length, insets, children, ...props }) {
34
35
  const parent = useContext(SafeAreaPaddingContext);
35
36
  const own = lengthsOf(edges, length, insets);
@@ -47,7 +48,8 @@ export function SafeAreaPadding({ edges, length, insets, children, ...props }) {
47
48
  ['--satin-padding-trailing']: `${own.trailing}px`,
48
49
  }, "data-satin-safe-area-padding": "true", children: _jsx(SafeAreaPaddingContext, { value: total, children: children }) }));
49
50
  }
50
- /** Sets margins for descendant scroll views. Lists use horizontal layout margins; the innermost value for each edge wins. */
51
+ /** Sets content margins for every scroll view inside it. A `List` takes a horizontal margin as its
52
+ * layout margin. The innermost value for each edge wins. */
51
53
  export function ContentMargins({ edges = 'all', length, placement = 'automatic', children, ...props }) {
52
54
  const parent = useContext(ContentMarginsContext);
53
55
  const set = edgesOf(edges);
@@ -57,11 +59,13 @@ export function ContentMargins({ edges = 'all', length, placement = 'automatic',
57
59
  margins[edge] = length;
58
60
  return (_jsx("div", { ...props, "data-satin-content-margins": "true", children: _jsx(ContentMarginsContext, { value: margins, children: children }) }));
59
61
  }
60
- /** Places a top or bottom safe-area inset with no spacing and its own scroll-edge material. */
62
+ /** A bar on the top or bottom edge that content scrolls under. It is a `SafeAreaInset` with no
63
+ * spacing and its own scroll-edge blur. */
61
64
  export function SafeAreaBar({ edge, ...props }) {
62
65
  return _jsx(SafeAreaInset, { ...props, edge: edge, spacing: 0, "data-satin-safe-area-bar": edge });
63
66
  }
64
- /** Extends content through selected safe-area edges. Container edges and keyboard avoidance can be controlled independently. */
67
+ /** Lets content extend through the safe area on the chosen edges, for full-bleed backgrounds and
68
+ * media. The container edges and keyboard avoidance can be ignored separately. */
65
69
  export function IgnoresSafeArea({ regions = 'all', edges = 'all', children, ...props }) {
66
70
  const parent = useContext(IgnoresSafeAreaContext);
67
71
  const set = edgesOf(edges);
@@ -15,9 +15,10 @@ import { TabActivityContext, TabRegistrationContext } from '../tabs/context.js';
15
15
  import { SearchDrawer } from '../search/SearchDrawer.js';
16
16
  import { ContentAdmission } from '../image/ContentAdmission.js';
17
17
  import { SearchToolbarField } from '../search/SearchToolbarField.js';
18
+ import { readDeviceIdiom } from '../layout/deviceIdiom.js';
18
19
  import { resolveSearchPlacement } from '../search/searchPlacement.js';
19
20
  import { Children, createContext, forwardRef, isValidElement, useCallback, useContext, useImperativeHandle, useLayoutEffect, useRef, useState, useSyncExternalStore, } from 'react';
20
- import { ScreenContext, SystemScrollRegistryContext, useScreenScrollRegistry, } from '../navigation/navigationContext.js';
21
+ import { NavigationPaneContext, ScreenContext, SystemScrollRegistryContext, useScreenScrollRegistry, } from '../navigation/navigationContext.js';
21
22
  import { readScrollOffset } from './registry.js';
22
23
  import { installScrollPressOwnership } from './pressOwnership.js';
23
24
  /** Inside another scroll surface: never the screen's primary scroll view. */
@@ -151,8 +152,11 @@ export const ScrollSurface = forwardRef(function ScrollSurface({ axis = 'vertica
151
152
  ? (listNavigation?.searchable ?? chrome.searchable)
152
153
  : undefined;
153
154
  const tabbed = useContext(TabRegistrationContext) !== null;
155
+ const searchPane = useContext(NavigationPaneContext);
154
156
  const searchPlacement = searchable
155
157
  ? resolveSearchPlacement(searchable.placement, {
158
+ idiom: readDeviceIdiom(),
159
+ pane: searchPane,
156
160
  compact: chrome.horizontalSizeClass === 'compact',
157
161
  bottomBarItems: chrome.hasBottomBar === true,
158
162
  searchToolbarItem: chrome.searchToolbarItem !== undefined,
@@ -1,6 +1,6 @@
1
1
  import { type HTMLAttributes } from 'react';
2
2
  export declare const ScrollTargetContext: import("react").Context<((id: string, element: HTMLDivElement | null) => void) | null>;
3
- /** A stable identity in a vertical ScrollView's normal-flow target layout. */
3
+ /** Marks content a vertical `ScrollView` can scroll to by `id`. */
4
4
  export declare function ScrollTarget({ id, ...props }: HTMLAttributes<HTMLDivElement> & {
5
5
  id: string;
6
6
  }): import("react").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext } from 'react';
3
3
  export const ScrollTargetContext = createContext(null);
4
- /** A stable identity in a vertical ScrollView's normal-flow target layout. */
4
+ /** Marks content a vertical `ScrollView` can scroll to by `id`. */
5
5
  export function ScrollTarget({ id, ...props }) {
6
6
  const register = useContext(ScrollTargetContext);
7
7
  if (!register)
@@ -1,8 +1,8 @@
1
- /** `Edge`: a logical edge of a scroll view. */
1
+ /** A logical edge of a scroll view. */
2
2
  export type ScrollEdge = 'top' | 'bottom' | 'leading' | 'trailing';
3
3
  /** One or more scroll edges, or all edges. */
4
4
  export type ScrollEdgeSelection = ScrollEdge | 'all' | readonly ScrollEdge[];
5
- /** `ScrollEdgeEffectStyle`: the requested edge material treatment. */
5
+ /** The material under bars at a scroll view's edge: `soft`, `hard` or `automatic`. */
6
6
  export type ScrollEdgeEffectStyle = 'soft' | 'hard' | 'automatic';
7
7
  /** Whether to hide scroll edge material, optionally for specific edges. */
8
8
  export type ScrollEdgeEffectHidden = boolean | {
@@ -1,4 +1,4 @@
1
- /** `UnitPoint`: the vertical alignment of a scroll target. */
1
+ /** Where a scroll target lines up in the viewport: `top`, `center` or `bottom`. */
2
2
  export type ScrollAnchor = 'top' | 'center' | 'bottom';
3
3
  /** Vertical scroll geometry: the current viewport and content offset in points. */
4
4
  export type VerticalScrollGeometry = Readonly<{
@@ -7,13 +7,15 @@ export type VerticalScrollGeometry = Readonly<{
7
7
  containerHeight: number;
8
8
  distanceToBottom: number;
9
9
  }>;
10
- /** `ScrollPosition`: a target identity and the callback that receives its changes. */
10
+ /** A controlled scroll position: the id of the target in view, its anchor and the callback that
11
+ * receives changes. */
11
12
  export type ScrollPositionBinding = Readonly<{
12
13
  id: string | null;
13
14
  anchor?: ScrollAnchor;
14
15
  onChange(id: string | null): void;
15
16
  }>;
16
- /** A scroll target binding, anchor and geometry observation policy. */
17
+ /** The scroll view's position options: where it starts, whether it keeps visible content in
18
+ * place, a controlled position and a geometry callback. */
17
19
  export type ScrollPositionOptions = Readonly<{
18
20
  defaultScrollAnchor?: 'top' | 'bottom';
19
21
  maintainVisibleContentPosition?: boolean;
@@ -6,7 +6,7 @@ export type ScrollOffset = Readonly<{
6
6
  y: number;
7
7
  }>;
8
8
  export type ScrollSnapshot = ReadonlyMap<string, ScrollOffset>;
9
- /** Options for restoring a named scroll surface to its top. */
9
+ /** Options for `scrollToTop`: whether to animate. */
10
10
  export type ScrollToTopOptions = Readonly<{
11
11
  animated?: boolean;
12
12
  }>;
@@ -0,0 +1,7 @@
1
+ import { type RefObject } from 'react';
2
+ import type { Searchable } from './searchable.js';
3
+ /** A column-owned field. Its host stays in this scroll surface, never a sibling pane. */
4
+ export declare function PadSearchDrawer({ configuration, scroller, }: {
5
+ configuration: Searchable;
6
+ scroller: RefObject<HTMLDivElement | null>;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { SearchToolbarField } from './SearchToolbarField.js';
4
+ /** A column-owned field. Its host stays in this scroll surface, never a sibling pane. */
5
+ export function PadSearchDrawer({ configuration, scroller, }) {
6
+ const [host, setHost] = useState(null);
7
+ return (_jsx("div", { ref: setHost, "data-satin-search-pad-drawer": "", "data-satin-search-toolbar-host": "", children: host && (_jsx(SearchToolbarField, { configuration: configuration, scroller: scroller, host: host, navigation: true, drawer: true })) }));
8
+ }
@@ -0,0 +1,6 @@
1
+ /** Internal coordination between a retained search destination and its owning tab bar. */
2
+ export declare const PadSearchPresentationContext: import("react").Context<{
3
+ owner: string | null;
4
+ setOwner: (owner: string | null) => void;
5
+ } | null>;
6
+ export declare const PadSearchTabContext: import("react").Context<string | null>;
@@ -0,0 +1,4 @@
1
+ import { createContext } from 'react';
2
+ /** Internal coordination between a retained search destination and its owning tab bar. */
3
+ export const PadSearchPresentationContext = createContext(null);
4
+ export const PadSearchTabContext = createContext(null);
@@ -10,7 +10,7 @@ import { type Searchable } from './searchable.js';
10
10
  * visual state of each part (FLIP), on the native springs (searchMotion.ts). A retarget mid-way
11
11
  * starts from where everything is on screen, keeping the field's velocity.
12
12
  */
13
- export declare function SearchDrawer({ configuration, scroller, }: {
13
+ export declare function SearchDrawer(props: {
14
14
  configuration: Searchable;
15
15
  scroller: RefObject<HTMLDivElement | null>;
16
16
  }): import("react").JSX.Element;
@@ -1,4 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { readDeviceIdiom } from '../layout/deviceIdiom.js';
3
+ import { PadSearchDrawer } from './PadSearchDrawer.js';
2
4
  import { NavigationSearchRow, navigationSearchRowOf } from '../navigation/NavigationSearchRow.js';
3
5
  import { animateElement } from '../motion/animate.js';
4
6
  import { beginSearchBackdrop, endSearchBackdrop, refreshSearchBackdrop } from './searchBackdrop.js';
@@ -28,7 +30,10 @@ const TITLE_SELECTOR = ':scope > :is([data-satin-list-title], [data-satin-scroll
28
30
  * visual state of each part (FLIP), on the native springs (searchMotion.ts). A retarget mid-way
29
31
  * starts from where everything is on screen, keeping the field's velocity.
30
32
  */
31
- export function SearchDrawer({ configuration, scroller, }) {
33
+ export function SearchDrawer(props) {
34
+ return readDeviceIdiom() === 'pad' ? (_jsx(PadSearchDrawer, { ...props })) : (_jsx(PhoneSearchDrawer, { ...props }));
35
+ }
36
+ function PhoneSearchDrawer({ configuration, scroller, }) {
32
37
  validateSearchPresentation(configuration);
33
38
  const liquid = useContext(MaterialEffectContext);
34
39
  const navigator = useContext(NavigatorContext);
@@ -0,0 +1,6 @@
1
+ import { type RefObject, type ReactNode } from 'react';
2
+ /** Suggestions belong to their field, including when it lives in a split column. */
3
+ export declare function SearchSuggestionsPopover({ anchor, children, }: {
4
+ anchor: RefObject<HTMLElement | null>;
5
+ children: ReactNode;
6
+ }): import("react").ReactPortal | null;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { GlassBackground } from '../glass/GlassEffect.js';
3
+ import { useSafeAreaMetrics } from '../safe-area/useSafeAreaMetrics.js';
4
+ import { useLayoutEffect, useState } from 'react';
5
+ import { createPortal } from 'react-dom';
6
+ /** Suggestions belong to their field, including when it lives in a split column. */
7
+ export function SearchSuggestionsPopover({ anchor, children, }) {
8
+ const metrics = useSafeAreaMetrics();
9
+ const [frame, setFrame] = useState();
10
+ useLayoutEffect(() => {
11
+ const element = anchor.current;
12
+ if (!element)
13
+ return;
14
+ const update = () => {
15
+ const rect = element.getBoundingClientRect();
16
+ const next = { left: Math.max(8, rect.left - 10), top: rect.bottom + 6, width: rect.width };
17
+ setFrame(previous => previous &&
18
+ previous.left === next.left &&
19
+ previous.top === next.top &&
20
+ previous.width === next.width
21
+ ? previous
22
+ : next);
23
+ };
24
+ update();
25
+ const observer = new ResizeObserver(update);
26
+ observer.observe(element);
27
+ window.addEventListener('resize', update);
28
+ document.addEventListener('scroll', update, true);
29
+ return () => {
30
+ observer.disconnect();
31
+ window.removeEventListener('resize', update);
32
+ document.removeEventListener('scroll', update, true);
33
+ };
34
+ }, [anchor]);
35
+ if (!frame)
36
+ return null;
37
+ return createPortal(_jsxs("div", { "data-satin-search-popover": "", style: {
38
+ ...frame,
39
+ maxHeight: Math.max(0, metrics.visualViewport.offsetTop + metrics.visualViewport.height - frame.top - 8),
40
+ overflowY: 'auto',
41
+ }, role: "list", "aria-label": "Suggestions", onPointerDown: event => event.preventDefault(), children: [_jsx(GlassBackground, { shape: { kind: 'roundedRectangle', cornerRadius: 28 } }), children] }), document.body);
42
+ }
@@ -1,8 +1,9 @@
1
1
  import { type RefObject } from 'react';
2
2
  import type { Searchable } from './searchable.js';
3
- export declare function SearchToolbarField({ configuration, scroller, host, navigation, }: {
3
+ export declare function SearchToolbarField({ configuration, scroller, host, navigation, drawer, }: {
4
4
  configuration: Searchable;
5
5
  scroller: RefObject<HTMLDivElement | null>;
6
6
  host: HTMLElement;
7
7
  navigation?: boolean;
8
+ drawer?: boolean;
8
9
  }): import("react").JSX.Element;