@synopackageland/ui 0.25.7 → 0.25.9

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.25.9 (2026-11-12)
6
+
7
+ ### Dialog
8
+
9
+ - **A dialog opened from a floating dialog masks only the app window.** With `<AppRoot dialog-bounds="viewport">` (an app in a DSM window), a Dialog opened from a floating panel, such as WizardDialog's "Discard changes?" confirm, put its mask in the viewport-wide floating host, so it tinted the whole DSM desktop: icons, taskbar, widgets and notifications. The mask now goes in the app root's own portal host, inside the window, as the outer dialog's does. The floating parent sits above the window, so it draws the same mask over itself (`data-ui-dialog-cover`) while the child is open, as DSM masks a modal window under the message box it opens. Apps need no change. On a phone, where panels are not floating, and without `dialog-bounds="viewport"`, nothing changes. A Sheet opened from a floating Dialog still masks the viewport (docs/app-local-modal.md)
10
+
11
+ ### Grid
12
+
13
+ - **An empty GridHost only header-tall no longer draws "No data" over its header.** The empty layer under the header is clipped (`overflow: hidden`), so with no room it shows nothing. Found in Studio Office, whose empty tables are header-tall with their own empty state under the grid
14
+
15
+ ### Development
16
+
17
+ - `node scripts/check-wizard-confirm.mjs` (`tests/dsm-host/wizard-desktop.html`) opens a WizardDialog's confirm in an app window on a DSM-like desktop at 1280x800 and on a 390x844 touch phone: the desktop stays unmasked, the mask stays in the window, the wizard is masked and takes no presses, and Keep editing and Discard work. Before this change it had 4 failures
18
+
19
+ ## 0.25.8 (2026-11-11)
20
+
21
+ On a phone (640px or narrower) a list is no longer a short inner scroller under the page's other content. Found in an App whose Explore page put a map above a DataGrid in a vertical SplitPane: at 360px the grid scrolled in a 385px box with a drag handle, and a pager count was cut to "38 ite…". The fix is in the SDK; Apps need no change and there is no new prop.
22
+
23
+ ### Phone layout
24
+
25
+ - **Lists take their rows' height and the page scrolls as one.** `DataGrid`, `DataTable`, `SplitPane`, `VirtualList`, `VirtualCardGrid`, `DataView` and `Panel` mark themselves `data-ui-natural-height` in the phone presentation. The content area becomes the page's one scroller, and every element between it and the list takes its content's height, also an App's own `min-h-0 flex-1 overflow-hidden` and `h-full` wrappers. `fill` does nothing on a phone: the list bar sticks under the bar (`--ui-sticky-top-offset`), the selection bar sticks to the bottom above the phone bars, and the footer (count, pager) follows the last row instead of being pinned. A long list keeps its pages, and clips sideways only, so a row action's tap area never widens the page
26
+ - **An App's `maxHeight` keeps at least 60% of the screen.** A DataGrid's `maxHeight` stays on a phone as `max(<maxHeight>, 60dvh)` (`PHONE_MIN_CAP`)
27
+ - **SplitPane stacks.** On a phone both orientations stack start above end with no separator (`data-stacked`). A start pane with no height of its own keeps `min(size, 45%)` of the screen, at least `minStart`
28
+ - **Virtual lists virtualize against the page's scroll.** VirtualList and VirtualCardGrid read their visible range from the content area's scroll position (`usePageWindow`, TanStack `scrollMargin`), so they still render only a window of rows (19 of 2,000 at 390px)
29
+ - **A pushed page opens at its top, and Back returns to the scroll position.** ListDetail's pushed detail and a SectionTabs push pane take their content's height instead of clipping
30
+ - **A flush Dialog (a bottom sheet) scrolls a list in its body.** The sheet's body is its one scroller instead of clipping the list at 92% of the screen
31
+ - **A phone pager never cuts the count.** In a narrow card `‹ Page 1 of N ›` moves to its own line under "38 items" instead of cutting it
32
+ - **KeyValueRow caps an App's `labelWidth` at 40% of the row on a phone**, as the theme's default label column does
33
+ - **AdvancePanelGrid shows its row action on touch.** Under a coarse pointer the `row-hover-action` slot shows at the end of the last cell, beside the text. Before, it was invisible but still took taps, over the text
34
+
35
+ ### Development
36
+
37
+ - `node scripts/check-phone-fill.mjs` (`tests/dsm-host/phone-fill.html`) checks ten fill layouts at 360, 390 and 1280 px: one scroller, the first row not cut, the list bar sticking, no splitter handle, the footer and pager whole, virtual windows, a flush sheet, labels and row actions. Before this change it had 49 failures
38
+
39
+ ### Migration notes
40
+
41
+ - A test or an App style that relied on a phone DataGrid with `fill` scrolling inside its own box should scroll the content area instead (`[data-ui-layout-scroll='page'] > [data-ui-layout-main] > main`)
42
+
3
43
  ## 0.25.7 (2026-11-11)
4
44
 
5
45
  ### Grid
@@ -9,12 +9,18 @@ type __VLS_Props = {
9
9
  declare function deactivateRow(): void;
10
10
  declare var __VLS_2: `header-${string}`, __VLS_3: {
11
11
  column: AdvancePanelGridColumn;
12
- }, __VLS_36: {}, __VLS_39: string, __VLS_40: {
12
+ }, __VLS_36: {}, __VLS_38: {
13
+ item: AdvancePanelGridRow;
14
+ column: AdvancePanelGridColumn;
15
+ index: number;
16
+ activate: () => void;
17
+ deactivate: typeof deactivateRow;
18
+ }, __VLS_41: string, __VLS_42: {
13
19
  cell: AdvancePanelGridCell;
14
20
  item: AdvancePanelGridRow;
15
21
  column: AdvancePanelGridColumn;
16
22
  index: number;
17
- }, __VLS_42: {
23
+ }, __VLS_44: {
18
24
  item: AdvancePanelGridRow;
19
25
  column: AdvancePanelGridColumn;
20
26
  index: number;
@@ -24,11 +30,13 @@ declare var __VLS_2: `header-${string}`, __VLS_3: {
24
30
  type __VLS_Slots = {} & {
25
31
  [K in NonNullable<typeof __VLS_2>]?: (props: typeof __VLS_3) => any;
26
32
  } & {
27
- [K in NonNullable<typeof __VLS_39>]?: (props: typeof __VLS_40) => any;
33
+ [K in NonNullable<typeof __VLS_41>]?: (props: typeof __VLS_42) => any;
28
34
  } & {
29
35
  'empty-body'?: (props: typeof __VLS_36) => any;
30
36
  } & {
31
- 'row-hover-action'?: (props: typeof __VLS_42) => any;
37
+ 'row-hover-action'?: (props: typeof __VLS_38) => any;
38
+ } & {
39
+ 'row-hover-action'?: (props: typeof __VLS_44) => any;
32
40
  };
33
41
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
34
42
  "update:sort": (sort: AdvancePanelGridSort) => any;
@@ -17,11 +17,15 @@ type __VLS_Props = {
17
17
  label?: string;
18
18
  };
19
19
  declare function reset(): void;
20
- declare var __VLS_1: {}, __VLS_3: {};
20
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {};
21
21
  type __VLS_Slots = {} & {
22
22
  start?: (props: typeof __VLS_1) => any;
23
23
  } & {
24
24
  end?: (props: typeof __VLS_3) => any;
25
+ } & {
26
+ start?: (props: typeof __VLS_5) => any;
27
+ } & {
28
+ end?: (props: typeof __VLS_7) => any;
25
29
  };
26
30
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
27
31
  reset: typeof reset;
@@ -0,0 +1,10 @@
1
+ export type DialogScrim = 'dim' | 'light' | 'none';
2
+ /**
3
+ * A dialog floating over the DSM desktop sits above its app window, so a
4
+ * dialog it opens masks the app window but not it. As DSM masks the window
5
+ * that owns a modal, the parent panel then draws the child's mask over
6
+ * itself: this returns the scrim to draw, or null.
7
+ */
8
+ export declare function provideDialogCover(): import('vue').ComputedRef<DialogScrim | null>;
9
+ /** Asks the enclosing dialog for a mask while `active` holds. */
10
+ export declare function useParentDialogCover(active: () => boolean, scrim: () => DialogScrim): void;
@@ -5,3 +5,8 @@ import { ComputedRef } from 'vue';
5
5
  * viewport width decides instead. Call it in a component's setup.
6
6
  */
7
7
  export declare function usePhonePresentation(): ComputedRef<boolean>;
8
+ /**
9
+ * The least an App's own height cap leaves a list or a pane on a phone, where
10
+ * the page scrolls as one: a cap lower than this made a short inner scroller.
11
+ */
12
+ export declare const PHONE_MIN_CAP = "60dvh";
@@ -1,4 +1,4 @@
1
- import { Ref, ShallowRef } from 'vue';
1
+ import { InjectionKey, Ref, ShallowRef } from 'vue';
2
2
  import { Box } from '../lib/floatingDialog';
3
3
  export declare const FLOATING_HOST_KEY = "@synopkgland/floating-host";
4
4
  export declare const FLOATING_HOST_ATTRIBUTE = "data-ui-floating";
@@ -13,6 +13,13 @@ export type FloatingHost = {
13
13
  /** False on a narrow viewport or with a coarse pointer: panels become viewport sheets. */
14
14
  draggable: Ref<boolean>;
15
15
  };
16
+ /**
17
+ * The app root's own portal host (inside the app window). A Dialog opened from
18
+ * a floating panel puts its mask there, so the mask covers the app window and
19
+ * not the viewport-wide floating host.
20
+ */
21
+ export declare const APP_PORTAL_KEY: InjectionKey<Readonly<Ref<HTMLElement | null>>>;
22
+ export declare function injectAppPortal(): Readonly<Ref<HTMLElement | null, HTMLElement | null>> | null;
16
23
  export declare function injectFloatingHost(): FloatingHost | null;
17
24
  /**
18
25
  * A viewport-sized layer for dialogs that may leave the app window, as DSM's
@@ -0,0 +1,24 @@
1
+ import { Ref } from 'vue';
2
+ /** The content area of an AppLayout whose page scrolls as one (a phone). */
3
+ export declare const PAGE_SCROLL_MAIN = "[data-ui-layout-scroll='page'] > [data-ui-layout-main] > main";
4
+ /**
5
+ * A virtual list on a phone page that scrolls as one. On a desktop a virtual
6
+ * list is its own fixed-height scroller; on a phone that is a short inner
7
+ * scroller under the page's other content. There the list takes its full
8
+ * height (it marks itself `data-ui-natural-height`, see adaptive.css), the
9
+ * page's scroller moves it, and the visible range is read from that scroller:
10
+ * `top` is how far the list's top has scrolled above the scroller's top edge
11
+ * (0 while the list starts below it) and `height` the scroller's visible
12
+ * height. Outside such a page `active` stays false and the list scrolls
13
+ * itself as before.
14
+ */
15
+ export declare function usePageWindow(target: Ref<HTMLElement | null | undefined>): {
16
+ natural: import('vue').ComputedRef<boolean>;
17
+ active: import('vue').ComputedRef<boolean>;
18
+ scroller: import('vue').ShallowRef<HTMLElement | null, HTMLElement | null>;
19
+ top: Ref<number, number>;
20
+ height: Ref<number, number>;
21
+ read: () => void;
22
+ scrollTo: (offset: number, behavior?: ScrollBehavior) => void;
23
+ margin: () => number;
24
+ };
package/dist/grid.js CHANGED
@@ -3407,7 +3407,7 @@ const Ps = () => {
3407
3407
  N.value ? (pe(), Ce("div", {
3408
3408
  key: 0,
3409
3409
  "data-ui-grid-empty": "",
3410
- style: Be([{ top: `${P.value}px` }, { position: "absolute", left: "0", right: "0", bottom: "0", display: "flex", "align-items": "center", "justify-content": "center", color: "var(--ui-text-muted)", "font-size": "var(--ui-font-size)", "pointer-events": "none", "z-index": "1" }])
3410
+ style: Be([{ top: `${P.value}px` }, { position: "absolute", left: "0", right: "0", bottom: "0", display: "flex", "align-items": "center", "justify-content": "center", overflow: "hidden", color: "var(--ui-text-muted)", "font-size": "var(--ui-font-size)", "pointer-events": "none", "z-index": "1" }])
3411
3411
  }, [
3412
3412
  hn(d.$slots, "empty", {}, () => [
3413
3413
  ti(we(J(a)("No data")), 1)