@synopackageland/ui 0.10.5 → 0.11.1

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,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.1 (2026-10-17)
4
+
5
+ - Fill-height pages: `AppLayout height="fill"` takes its container's height (a DSM window) and scrolls its content area instead of the document; `PageContent height="fill"` keeps the page header, tabs and toolbar in place and lets its last child (a `DataGrid`, or a flex column holding one) take the height left. The grid scrolls its rows under its sticky header and its pager stays in view, so a 50-row page no longer pushes the pager far below. Both default to `height="auto"` (unchanged). The SDK's `AppShell` page `fill: true` does the same.
6
+ - `DataGrid` column `priority`: when the shown columns do not fit at their minimum widths, the grid leaves out the lowest priority first (the rightmost of a tie first), so an 800 px DSM window drops its least needed columns instead of scrolling sideways. Columns without a `priority`, or with `hideable: false`, always show. A column left out for width is not saved as hidden, is still searched by the quick filter, returns when the window widens, and is listed unchecked in the column chooser with "Too narrow" (new label `hiddenByWidth`, nine locales); checking it there shows it for this view.
7
+ - New `formatDateTime(value, { locale, style, timeZone, now, unknown })`. `style: 'compact'` fits a 140 px column in every built-in locale: this year `10/17 14:32` (zh-TW, zh-CN, ja), `10. 17. 14:32` (ko), `Oct 17, 2:32 PM` (en), `17. Okt., 14:32` (de); another year the date with its year (`2025/3/2`, `Mar 2, 2025`). The default `full` style (`Oct 17, 2026, 2:32:05 PM`, `2026年10月17日 下午2:32:05`) is for the tooltip. `DateTimeFormatOptions` is exported.
8
+ - **Visible change:** a running `CellStatus` fits one DataGrid row: the label line is 16 px, the gap 2 px and the bar a 4 px line (new `Progress size="sm"`), 22 px in all, so rows with a progress bar are as high as the others. A `detail` still adds a line.
9
+ - Every DataGrid body cell, a `#cell-<key>` slot's included, carries `data-column="<key>"`.
10
+ - Checkbox menu entries take a `hint`, a quiet note after the label. The column chooser keeps its menu open while columns are toggled (documented; unchanged).
11
+ - Visible changes for apps: Download Hub should mark its downloads page `fill: true` in `AppShell` and make the list's wrapper a flex column (`display:flex; flex-direction:column; min-height:0`), give optional columns a `priority` (start time 1, speed 2, location 3, size 4), format times with `formatDateTime(..., { style: 'compact' })` with the full form as `title` instead of its own `Intl.DateTimeFormat`, and drop its own `data-cell` spans used only to find cells in tests.
12
+
13
+ 繁中摘要:新增填滿高度的頁面:`AppLayout height="fill"` 取容器高度(DSM 視窗)、由內容區捲動而非整份文件;`PageContent height="fill"` 讓頁首、分頁籤、工具列固定,最後一個子元素(`DataGrid`,或包著它的 flex 欄)填滿剩餘高度,表格在固定表頭下捲動列、頁尾 pager 永遠可見。兩者預設 `height="auto"`(不變);SDK `AppShell` 的頁面 `fill: true` 效果相同。`DataGrid` 欄位新增 `priority`:最小寬度放不下時,從最低優先的欄開始隱藏(同優先從最右邊),800px DSM 視窗會少顯示次要欄而不橫向捲動;沒設 `priority` 或 `hideable: false` 的欄一定顯示。因寬度隱藏的欄不會存成使用者隱藏、仍參與快速篩選、視窗變寬即回來,欄位選單中以未勾選加「空間不足」(新 label `hiddenByWidth`,九語系)列出,勾選即在此檢視中顯示。新增 `formatDateTime`:`style: 'compact'` 在所有內建語系都放得進 140px 欄,今年為 `10/17 14:32`、`Oct 17, 2:32 PM`,其他年份只顯示含年份的日期(`2025/3/2`、`Mar 2, 2025`);預設 `full` 含秒,供 tooltip。**可見變更:**執行中的 `CellStatus` 符合一列高度(標籤 16px+間距 2px+新的 `Progress size="sm"` 4px 細條=22px),有進度條的列不再較高;`detail` 仍會多一行。DataGrid 每個儲存格(含 slot)都有 `data-column`。勾選型選單項目新增 `hint`;欄位選單勾選時保持開啟(已寫入文件,行為不變)。Download Hub 升級時:下載頁設 `fill: true`、清單外層改為 flex 欄、選用欄設 `priority`(開始時間 1、速度 2、存放位置 3、大小 4)、時間改用 `formatDateTime` compact 並以完整格式作 `title`、移除只為測試定位而加的 `data-cell`。
14
+
15
+ ## 0.11.0 (2026-10-17)
16
+
17
+ - `DataGrid` handles long lists by itself. Every column gets a pixel width: a column with `width` keeps it, a column without one is flexible and shares the rest by the new `flex` weight (default 1), never below its `minWidth` (default 80 px for a flexible column). A long file name can no longer take the whole width, a header never wraps one character per line, and the grid scrolls sideways only when the minimums do not fit (a phone) or the user widened columns. A plain cell is cut to one line with its full text in a tooltip; `wrap: true` wraps a column instead. Headers carry their label as a tooltip.
18
+ - Column chooser: `column-chooser` adds a columns button at the right of the header where the user shows and hides columns (`hideable: false` keeps one, `defaultHidden: true` starts one hidden) and resets order, visibility and widths. `v-model:hidden-columns`; `resetColumns()` is exposed.
19
+ - Column reorder: `column-reorder` moves a column by dragging its header onto another, or with Alt+Left/Right on a focused header. `v-model:column-order`.
20
+ - Quick filter: `filter` (with `searchable` / `searchText` per column, `filter-row`, or `:local-filter="false"` for server results) runs before sorting and paging, so it finds rows on every page. The footer reads "3 of 240 items" and an empty result says "No matching items".
21
+ - Paging: `page` and `page-size` work unbound as well as with `v-model:page` / `v-model:page-size`; `page-sizes` adds a page-size choice to the footer. A new filter, sort or page size goes back to page 1; a page past the end (the rows shrank) shows the last page.
22
+ - Saved view: `state-key` saves the user's column order, hidden columns, widths, sort and page size in localStorage and restores them; bound props win. `loadGridState` / `saveGridState` and the `DataGridViewState` type are exported for apps that keep the view elsewhere.
23
+ - New `labels`: `columns`, `resetColumns`, `filtered(count, total)`, `noMatch`, `pageSize` (optional, like the row labels; translated in all nine locales).
24
+ - **Visible change:** columns without a `width` share the free width evenly (by `flex`) instead of sizing to their content, and the table always has a trailing filler column. **Behavior change:** a grid that did not bind `page` now pages when the footer buttons are clicked (before, it only emitted `update:page`).
25
+ - Visible changes for apps: Download Hub's list no longer lets a long name push 存放位置 and 狀態 into one-character columns. Give the main column `flex: 2` and a `minWidth`, mark optional columns (start/finish time, speed) `defaultHidden`, add `column-chooser`, `column-reorder`, `page-sizes` and a `state-key`, and bind a `SearchField variant="filter"` to `filter`. Remove hand-written `title` attributes on plain cells and app-side filtering of the current page.
26
+
27
+ 繁中摘要:`DataGrid` 自己處理長清單。每欄都有像素寬度:有 `width` 的欄維持,沒有的欄依新的 `flex`(預設 1)分配剩餘寬度、不小於 `minWidth`(彈性欄預設 80px);長檔名不再吃掉整個寬度,表頭不會一字一行,只有最小寬度放不下(手機)或使用者拉寬欄時才橫向捲動。一般儲存格單行省略並以 tooltip 顯示全文,`wrap: true` 改為換行;表頭也有 tooltip。新增欄位選擇(`column-chooser`:表頭右側按鈕,可顯示/隱藏欄、重設順序/顯示/寬度;`hideable: false`、`defaultHidden`、`v-model:hidden-columns`、`resetColumns()`)、欄位排序(`column-reorder`:拖曳表頭或 Alt+←/→;`v-model:column-order`)、快速篩選(`filter`,在排序與分頁前搜尋全部列;`searchable`、`searchText`、`filter-row`、`:local-filter="false"`;頁尾顯示「3/240 個項目」,無結果顯示「沒有符合的項目」)、分頁改進(`page`/`page-size` 不綁也能用,`page-sizes` 選每頁筆數;篩選、排序、每頁筆數變更時回到第 1 頁,超出頁數時顯示最後一頁)、狀態保存(`state-key` 把欄順序、隱藏、寬度、排序、每頁筆數存 localStorage;綁定的 props 優先;匯出 `loadGridState`/`saveGridState`)。新增 labels:`columns`、`resetColumns`、`filtered`、`noMatch`、`pageSize`(九種語系皆已翻譯)。**可見變更:**沒有 `width` 的欄改為平均分配剩餘寬度而非依內容;表格最後固定有填充欄。**行為變更:**沒綁 `page` 的 grid 點頁尾按鈕會自己換頁(以前只 emit)。App 升級時:主欄設 `flex: 2` 與 `minWidth`,選用欄(開始/完成時間、速度)設 `defaultHidden`,加上 `column-chooser`、`column-reorder`、`page-sizes`、`state-key`,並把 `SearchField variant="filter"` 綁到 `filter`;移除自己在一般儲存格加的 `title` 與只篩選目前頁的程式。
28
+
3
29
  ## 0.10.5 (2026-10-17)
4
30
 
5
31
  - **Visible change:** `Checkbox`, `RadioGroup` and the `TreeView` checkbox use an 18 px box with a 9 px text gap (was 16 px and 8 px); the check, indeterminate dash and radio dot scale with the box. Labels use a 20 px line height and vertical `RadioGroup` / `CheckboxGroup` items have an 8 px gap: the net row pitch is 28 px (20 + 8), down from the intermediate 30 px. Tokens `--ui-choice-box-size`, `--ui-choice-box-gap`, `--ui-choice-box-offset` and `--ui-choice-stack-gap` control these sizes. Textless DataGrid selection boxes, including select-all, are centred without the inline line-box offset; the horizontal gap remains 34 px (`--ui-choice-inline-gap`).
package/README.md CHANGED
@@ -335,6 +335,31 @@ import { AppLayout, Button, PageHeader } from '@synopackageland/ui'
335
335
  `PageHeader` the space is the header's own bottom padding (12px in a DSM
336
336
  window), with no section gap added.
337
337
 
338
+ **Fill-height pages.** A list page whose grid pages its rows should not
339
+ scroll as a whole, or a 50-row page pushes its pager far below. Give both the
340
+ layout and the page `height="fill"`: `AppLayout` then takes its container's
341
+ height (a DSM window) and scrolls its content area instead of the document;
342
+ `PageContent` keeps its header, tabs and toolbar in place, and its **last
343
+ child** takes the height left. Make that child the `DataGrid` itself, or a
344
+ flex column (`flex flex-col min-h-0`) holding it: the grid scrolls its rows
345
+ under its sticky header, with the pager always in view. Pages that are not
346
+ `fill` in the same layout scroll as before. In the SDK's `AppShell`, the same
347
+ is a page's `fill: true`.
348
+
349
+ ```vue
350
+ <AppLayout height="fill" min-height="0">
351
+ <template #sidebar>…</template>
352
+ <PageContent height="fill">
353
+ <PageHeader title="下載" />
354
+ <div class="flex items-center justify-between gap-2">
355
+ <SectionTabs v-model="tab" :items="tabs" />
356
+ <SearchField v-model="filter" variant="filter" />
357
+ </div>
358
+ <DataGrid :columns="columns" :rows="rows" :filter="filter" :page-sizes="[50, 100, 200]" column-chooser />
359
+ </PageContent>
360
+ </AppLayout>
361
+ ```
362
+
338
363
  To set a block apart inside a card, use `<Surface variant="inset">`. DSM draws it as a
339
364
  faint grey block with no border of its own, at 12px × 16px padding.
340
365
 
@@ -475,30 +500,51 @@ maps its own states to six link states.
475
500
 
476
501
  ### DataGrid
477
502
 
503
+ `DataGrid` is the list for data that grows (downloads, tasks, files): sorting,
504
+ column chooser, column reorder, resize, quick filter, paging and saved view
505
+ state. `DataTable` stays the small read-only table that becomes cards on a
506
+ phone; use `DataGrid` for anything a user searches or arranges.
507
+
478
508
  ```vue
509
+ <SearchField v-model="filter" variant="filter" />
479
510
  <DataGrid
480
- :columns="[{ key: 'name', label: 'Name', sortable: true, width: 170, minWidth: 80 }]"
511
+ :columns="[
512
+ { key: 'name', label: '名稱', sortable: true, flex: 2, minWidth: 200, hideable: false },
513
+ { key: 'size', label: '大小', sortable: true, width: 90, align: 'right' },
514
+ { key: 'location', label: '存放位置', minWidth: 120 },
515
+ { key: 'started', label: '開始時間', sortable: true, width: 140, defaultHidden: true },
516
+ ]"
481
517
  :rows="rows"
482
- :default-sort="{ key: 'name', direction: 'asc' }"
483
- v-model:column-widths="savedWidths"
518
+ :filter="filter"
519
+ :page-sizes="[50, 100, 200]"
520
+ state-key="downloads"
521
+ column-chooser
522
+ column-reorder
484
523
  footer
485
524
  refreshable
486
- :labels="{ items: n => `${n} 個項目`, refresh: '重新整理', empty: '沒有資料' }"
487
525
  @refresh="reload"
488
526
  />
489
527
  ```
490
528
 
529
+ - **Column widths.** A column with `width` (px, or a percentage of the grid) keeps it; a column without one is flexible and shares the rest by `flex` (default 1), never below its `minWidth` (default 80px for a flexible column, 40px for a fixed one). A long text cannot widen its column, and a header never wraps. When the minimums do not fit (a phone), the grid scrolls sideways instead of squeezing.
530
+ - **Long text.** A plain cell is cut to one line with an ellipsis and shows its full text on hover (`title`); `wrap: true` wraps a column instead. A `#cell-<key>` slot renders its own content: `CellText` cuts and titles it the same way. Every body cell, a slot's included, carries `data-column="<key>"`.
531
+ - **Row height.** Rows are one height (28px, 24px compact). `CellStatus` with a running `progress` fits it (a 4px bar under the label); its `detail` adds a line, so put speed or a reason in its own column. Dates: `formatDateTime(value, { locale, style: 'compact' })` in the cell (this year: `10/17 14:32`, `Oct 17, 2:32 PM`; another year: the date with its year, `2025/3/2`, `Mar 2, 2025`) and the full `formatDateTime(value, { locale })` as its `title`; it fits a 140px column in every built-in locale.
491
532
  - **Sort.** Click a sortable header, or focus it and press Enter/Space (`aria-sort` is set). Cycle: ascending, descending, off. Leave `sort` unbound and the grid sorts itself (start with `default-sort`); bind `v-model:sort` to control it; with `:local-sort="false"` it only emits.
492
- - **Column resize.** Drag the right edge of a header, or focus the edge and press the left/right arrows (10px). Widths are px, never below the column's `minWidth` (default 40). Persist them with `v-model:column-widths` (or `@column-resize`); pass them back to restore. `resizable` is on by default; turn it off per grid or per column.
533
+ - **Column chooser.** `column-chooser` adds a columns button at the right of the header: the user shows and hides columns and resets them. `hideable: false` keeps a column, `defaultHidden: true` starts one hidden; the last shown column cannot be hidden. Bind `v-model:hidden-columns` to control it. A click on a column toggles it and **keeps the menu open**, so several columns can be changed in a row; Reset, Escape or a click outside closes it.
534
+ - **Column priority (narrow windows).** Give optional columns a `priority`: when the shown columns do not fit at their minimum widths (a fixed `width`, else `minWidth`), the grid leaves out the lowest priority first (the rightmost of a tie first) until the rest fits, so an 800px DSM window loses its least needed columns instead of scrolling sideways. A column without a `priority`, or with `hideable: false`, is never left out, so a grid of such columns still scrolls (a phone). A column left out for width is not hidden by the user: it is not saved, the quick filter still searches it, it returns when the window widens, and the chooser lists it unchecked with "Too narrow" (`labels.hiddenByWidth`). Checking it there shows it anyway for this view; the next lowest priority then makes room, or the grid scrolls.
535
+ - **Column reorder.** `column-reorder` lets the user drag a header onto another, or focus it and press Alt+Left/Right. Bind `v-model:column-order` to control it; keys of new columns not in a saved order follow at the end.
536
+ - **Column resize.** Drag the right edge of a header, or focus the edge and press the left/right arrows (10px). Persist widths with `v-model:column-widths` (or `@column-resize`); pass them back to restore. `resizable` is on by default; turn it off per grid or per column.
537
+ - **Quick filter.** `filter` shows the rows whose shown, searchable columns contain the text (case-insensitive), across every page, before sorting and paging. `searchable: false` leaves a column out, `searchText(row)` gives the text to search, `filter-row(row, text)` replaces the match. Rows that come filtered from a server: `:local-filter="false"`. The footer reads "3 of 240 items" and an empty result says nothing matches.
538
+ - **Paging.** Long lists are paged in the footer (20 rows per page by default). `page` and `page-size` work unbound or with `v-model:page` / `v-model:page-size`; `page-sizes` adds the page-size choice. A new filter, sort or page size goes back to page 1, and a page past the end shows the last one. With `:local-pagination="false"` and `total-rows` the app pages on its server. Virtual scrolling is not provided.
539
+ - **Saved view.** `state-key="downloads"` saves the user's column order, hidden columns, widths, sort and page size in this browser (localStorage) and restores them; bound props win. To keep them on a server, bind the v-models instead, or use the exported `loadGridState` / `saveGridState`. `resetColumns()` (exposed) and the chooser's Reset restore order, visibility and widths.
493
540
  - **Layout.** Sticky header (default), column dividers, hover and selected row colours, `max-height` to scroll inside the grid. `selectable` adds checkboxes; with `select-on-row-click` a row click selects the row and Ctrl/Cmd-click toggles it.
494
- - **Footer bar.** `footer` shows the DSM bar with the item count; `refreshable` adds the refresh button (`@refresh`, `refreshing` spins it). Without `footer` it appears only when there is more than one page or a `#footer` slot.
495
- - **Localisation.** Every built-in string goes through `labels`: `empty`, `items(count)`, `page(page, count)`, `previous`, `next`, `refresh`, `selectAll`, `selectRow(key)`, `resizeColumn(label)`. `emptyText` still works.
541
+ - **Footer bar.** `footer` shows the DSM bar with the item count; `refreshable` adds the refresh button (`@refresh`, `refreshing` spins it). Without `footer` it appears when there is more than one page, a page-size choice that matters, or a `#footer` slot.
542
+ - **Localisation.** Every built-in string goes through `labels`: `empty`, `noMatch`, `items(count)`, `filtered(count, total)`, `page(page, count)`, `previous`, `next`, `pageSize`, `refresh`, `selectAll`, `selectRow(key)`, `resizeColumn(label)`, `columns`, `resetColumns`, `hiddenByWidth`. `emptyText` still works.
496
543
  - **Loading.** `loading` masks the table with a centred indicator; `loading-text` adds a message.
497
544
  - **Footer layout.** The footer has top and bottom divider lines and a separator
498
545
  before Refresh. In a flex column with a bounded height, the grid fills the
499
546
  remaining space and its rows scroll above the footer. Use `class="flex-none"`
500
547
  for a grid that should retain its content height.
501
- - Virtual scrolling is not provided; page long lists (`page-size`).
502
548
 
503
549
  ### Loading
504
550
 
@@ -0,0 +1,13 @@
1
+ type __VLS_Props = {
2
+ stateKey?: string;
3
+ locale?: 'zh-TW' | 'en';
4
+ filter?: string;
5
+ toolbar?: boolean;
6
+ };
7
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
8
+ filter: string;
9
+ locale: "zh-TW" | "en";
10
+ toolbar: boolean;
11
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
@@ -0,0 +1,8 @@
1
+ type __VLS_Props = {
2
+ locale?: 'zh-TW' | 'en';
3
+ };
4
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
5
+ locale: "zh-TW" | "en";
6
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -12,6 +12,12 @@ type __VLS_Props = {
12
12
  brandName?: string;
13
13
  /** Logo URL beside the brand name; defaults to the page icon. */
14
14
  brandIcon?: string;
15
+ /**
16
+ * `'fill'` takes the container's height (a DSM window) and scrolls the
17
+ * content area instead of the document, so a `PageContent height="fill"`
18
+ * page can keep its header and pager in view.
19
+ */
20
+ height?: 'auto' | 'fill';
15
21
  };
16
22
  type __VLS_Slots = {
17
23
  sidebar?: () => unknown;
@@ -22,6 +28,7 @@ type __VLS_Slots = {
22
28
  footer?: () => unknown;
23
29
  };
24
30
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
31
+ height: "auto" | "fill";
25
32
  minHeight: string;
26
33
  sidebarWidth: string;
27
34
  responsive: boolean;
@@ -6,12 +6,19 @@ type __VLS_Props = {
6
6
  width?: 'fill' | 'readable' | (string & {});
7
7
  /** Web/portal mode uses a page surface by default. Set flat for card-based dashboards. */
8
8
  surface?: 'auto' | 'flat' | 'card';
9
+ /**
10
+ * `'fill'` takes the container's height and does not scroll: the header,
11
+ * tabs and toolbar stay, and the last child (a DataGrid, or a flex column
12
+ * holding one) takes the height left and scrolls itself.
13
+ */
14
+ height?: 'auto' | 'fill';
9
15
  };
10
16
  declare var __VLS_1: {};
11
17
  type __VLS_Slots = {} & {
12
18
  default?: (props: typeof __VLS_1) => any;
13
19
  };
14
20
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
21
+ height: "auto" | "fill";
15
22
  width: "fill" | "readable" | (string & {});
16
23
  surface: "auto" | "flat" | "card";
17
24
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
@@ -6,9 +6,28 @@ export type DataGridSort = {
6
6
  export type DataGridColumn = {
7
7
  key: string;
8
8
  label: string;
9
- /** A pixel width (`120`, `"120px"`). Other CSS units size the column but cannot be resized from. */
9
+ /** A pixel width (`120`, `"120px"`) or a percentage of the grid; without it the column is flexible. */
10
10
  width?: string | number;
11
+ /** Defaults to 80px for a flexible column, 40px for a fixed one. */
11
12
  minWidth?: string | number;
13
+ /** A flexible column's share of the space the fixed columns leave; defaults to 1. */
14
+ flex?: number;
15
+ /** Wraps the cell text instead of cutting it to one line with an ellipsis. */
16
+ wrap?: boolean;
17
+ /** `false` keeps the column shown: the column chooser cannot hide it. */
18
+ hideable?: boolean;
19
+ /** Starts hidden; the user can show it from the column chooser. */
20
+ defaultHidden?: boolean;
21
+ /**
22
+ * Leaves the column out when the grid is too narrow for every column's
23
+ * minimum width: the lowest priority goes first. Without one the column
24
+ * always shows. The column chooser still lists it, to show it anyway.
25
+ */
26
+ priority?: number;
27
+ /** `false` leaves the column out of the quick filter. */
28
+ searchable?: boolean;
29
+ /** The text the quick filter searches for a row; defaults to the cell value. */
30
+ searchText?: (row: Record<string, unknown>) => string;
12
31
  align?: 'left' | 'center' | 'right';
13
32
  sortable?: boolean;
14
33
  /** Overrides the grid's `resizable` for this column. */
@@ -105,9 +124,19 @@ type __VLS_Props = {
105
124
  selectOnRowClick?: boolean;
106
125
  /** One selected row at a time, picked by clicking the row; no checkbox column. */
107
126
  singleSelect?: boolean;
127
+ /** Bind to control the page; leave unset and the grid pages by itself. */
108
128
  page?: number;
129
+ /** Rows per page; defaults to the first of `pageSizes`, else 20. Bind with `v-model:page-size` to follow the user's choice. */
109
130
  pageSize?: number;
131
+ /** Page sizes the user can pick from in the footer, e.g. `[20, 50, 100]`. */
132
+ pageSizes?: number[];
110
133
  totalRows?: number;
134
+ /** Quick filter text: shows the rows whose shown, searchable columns contain it, across all pages. */
135
+ filter?: string;
136
+ /** Replaces the quick filter's match with your own; it gets the trimmed, lower-cased filter text. */
137
+ filterRow?: (row: Record<string, unknown>, text: string) => boolean;
138
+ /** `false` when the rows arrive filtered (a server search): the grid shows them as they are. */
139
+ localFilter?: boolean;
111
140
  localSort?: boolean;
112
141
  localPagination?: boolean;
113
142
  loading?: boolean;
@@ -119,6 +148,20 @@ type __VLS_Props = {
119
148
  resizable?: boolean;
120
149
  /** Persisted column widths in px, by column key. */
121
150
  columnWidths?: DataGridColumnWidths;
151
+ /** Column keys in display order; bind to keep the user's order, leave unset and the grid tracks it. */
152
+ columnOrder?: string[];
153
+ /** Keys of hidden columns; defaults to the columns marked `defaultHidden`. */
154
+ hiddenColumns?: string[];
155
+ /**
156
+ * Saves the user's column order, hidden columns, widths, sort and page size
157
+ * in this browser under this key and restores them. Bound props win; to keep
158
+ * them on a server, bind the v-models instead.
159
+ */
160
+ stateKey?: string;
161
+ /** A header menu where the user shows and hides columns and resets them. */
162
+ columnChooser?: boolean;
163
+ /** Drag a header onto another, or press Alt+Left/Right on it, to move the column. */
164
+ columnReorder?: boolean;
122
165
  /** `true` always shows the footer bar, `false` never; unset shows it when paged or given a slot. */
123
166
  footer?: boolean;
124
167
  refreshable?: boolean;
@@ -137,6 +180,7 @@ type __VLS_Props = {
137
180
  /** Row reordering with drag and drop plus keyboard support. */
138
181
  draggable?: boolean | DataGridDraggable;
139
182
  };
183
+ declare function resetColumns(): void;
140
184
  type __VLS_Slots = {
141
185
  empty?: (props: Record<string, never>) => unknown;
142
186
  'group-title'?: (props: DataGridGroupSlotProps) => unknown;
@@ -160,12 +204,16 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
160
204
  collapseAllGroups: () => void;
161
205
  moveRowUp: (row: Record<string, unknown>) => void;
162
206
  moveRowDown: (row: Record<string, unknown>) => void;
207
+ resetColumns: typeof resetColumns;
163
208
  }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
164
209
  refresh: () => any;
210
+ "update:pageSize": (value: number) => any;
165
211
  "update:sort": (value: DataGridSort | null) => any;
166
212
  "update:selectedKeys": (value: string[]) => any;
167
213
  "update:page": (value: number) => any;
168
214
  "update:columnWidths": (value: DataGridColumnWidths) => any;
215
+ "update:columnOrder": (value: string[]) => any;
216
+ "update:hiddenColumns": (value: string[]) => any;
169
217
  "update:rows": (value: Record<string, unknown>[]) => any;
170
218
  "update:expandedKeys": (value: string[]) => any;
171
219
  columnResize: (value: {
@@ -192,10 +240,13 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
192
240
  cellUpdate: (value: DataGridCellUpdate) => any;
193
241
  }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
194
242
  onRefresh?: (() => any) | undefined;
243
+ "onUpdate:pageSize"?: ((value: number) => any) | undefined;
195
244
  "onUpdate:sort"?: ((value: DataGridSort | null) => any) | undefined;
196
245
  "onUpdate:selectedKeys"?: ((value: string[]) => any) | undefined;
197
246
  "onUpdate:page"?: ((value: number) => any) | undefined;
198
247
  "onUpdate:columnWidths"?: ((value: DataGridColumnWidths) => any) | undefined;
248
+ "onUpdate:columnOrder"?: ((value: string[]) => any) | undefined;
249
+ "onUpdate:hiddenColumns"?: ((value: string[]) => any) | undefined;
199
250
  "onUpdate:rows"?: ((value: Record<string, unknown>[]) => any) | undefined;
200
251
  "onUpdate:expandedKeys"?: ((value: string[]) => any) | undefined;
201
252
  onColumnResize?: ((value: {
@@ -222,6 +273,7 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
222
273
  onCellUpdate?: ((value: DataGridCellUpdate) => any) | undefined;
223
274
  }>, {
224
275
  footer: boolean;
276
+ filter: string;
225
277
  sort: DataGridSort | null;
226
278
  loading: boolean;
227
279
  group: DataGridGroup | null;
@@ -231,15 +283,20 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
231
283
  pageSize: number;
232
284
  selectable: boolean;
233
285
  rowKey: string;
286
+ columnOrder: string[];
287
+ hiddenColumns: string[];
234
288
  defaultSort: DataGridSort | null;
235
289
  selectedKeys: string[];
236
290
  selectOnRowClick: boolean;
237
291
  singleSelect: boolean;
292
+ localFilter: boolean;
238
293
  localSort: boolean;
239
294
  localPagination: boolean;
240
295
  loadingRows: number;
241
296
  stickyHeader: boolean;
242
297
  resizable: boolean;
298
+ columnChooser: boolean;
299
+ columnReorder: boolean;
243
300
  refreshable: boolean;
244
301
  refreshing: boolean;
245
302
  expandable: DataGridExpandable | null;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Columns in a saved order: keys the order names come first, in its order;
3
+ * columns it does not name (added since it was saved) follow in their own
4
+ * order; keys of columns that are gone are ignored.
5
+ */
6
+ export declare function orderColumns<T extends {
7
+ key: string;
8
+ }>(columns: T[], order: string[]): T[];
9
+ /** `keys` with `key` moved to index `to`. */
10
+ export declare function moveKey(keys: string[], key: string, to: number): string[];
@@ -1,6 +1,34 @@
1
1
  export declare const DEFAULT_MIN_COLUMN_WIDTH = 40;
2
- export declare const FALLBACK_COLUMN_WIDTH = 100;
2
+ /** A flexible column keeps room for a short header and a few characters. */
3
+ export declare const FLEX_MIN_COLUMN_WIDTH = 80;
3
4
  export declare const KEYBOARD_RESIZE_STEP = 10;
4
5
  /** A pixel width from a number or a `"120px"` / `"120"` string; other units are unknown. */
5
6
  export declare function toPixels(value: string | number | undefined): number | undefined;
6
7
  export declare function clampWidth(width: number, minWidth: number): number;
8
+ export type ColumnWidthSpec = {
9
+ key: string;
10
+ /** A fixed width in px; without it the column shares the rest by `flex`. */
11
+ width?: number;
12
+ minWidth: number;
13
+ flex: number;
14
+ };
15
+ /**
16
+ * Pixel widths for every column: fixed ones keep theirs, flexible ones share
17
+ * what is left by weight and never drop below their minimum. Unknown space
18
+ * (`0`) gives the minimums, so the grid scrolls rather than squeezes.
19
+ */
20
+ export declare function distributeColumnWidths(columns: ColumnWidthSpec[], available: number): Record<string, number>;
21
+ export type ColumnPrioritySpec = {
22
+ key: string;
23
+ /** The least width the column takes: its fixed width or its minimum. */
24
+ minWidth: number;
25
+ /** Lower hides first; without one the column is never hidden for width. */
26
+ priority?: number;
27
+ };
28
+ /**
29
+ * Keys of the columns to leave out so the rest fits `available` at their
30
+ * minimum widths: the lowest priority first, the rightmost of a tie first.
31
+ * Columns without a priority stay, so the grid may still scroll. Unknown
32
+ * space (`0`) hides none.
33
+ */
34
+ export declare function columnsHiddenByWidth(columns: ColumnPrioritySpec[], available: number): string[];
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Text overrides for the grid chrome. The three row-operation labels were added
2
+ * Text overrides for the grid chrome. The row-operation and column labels were added
3
3
  * after the first release and stay optional so an existing external
4
4
  * `DataGridLabels` object literal keeps compiling; the grid merges the defaults in.
5
5
  */
@@ -16,12 +16,29 @@ export type DataGridLabels = {
16
16
  expandRow?: (key: string) => string;
17
17
  collapseRow?: (key: string) => string;
18
18
  reorderRow?: (key: string) => string;
19
+ /** The column chooser button. */
20
+ columns?: string;
21
+ resetColumns?: string;
22
+ /** The item count while the quick filter hides rows. */
23
+ filtered?: (count: number, total: number) => string;
24
+ /** The empty text when rows exist but none matches the filter. */
25
+ noMatch?: string;
26
+ /** The page-size chooser. */
27
+ pageSize?: string;
28
+ /** Beside a column the chooser lists that the width leaves out. */
29
+ hiddenByWidth?: string;
19
30
  };
20
31
  /** `DataGridLabels` after the defaults were merged in: every label is present. */
21
32
  export type ResolvedDataGridLabels = DataGridLabels & {
22
33
  expandRow: (key: string) => string;
23
34
  collapseRow: (key: string) => string;
24
35
  reorderRow: (key: string) => string;
36
+ columns: string;
37
+ resetColumns: string;
38
+ filtered: (count: number, total: number) => string;
39
+ noMatch: string;
40
+ pageSize: string;
41
+ hiddenByWidth: string;
25
42
  };
26
43
  export declare const defaultDataGridLabels: ResolvedDataGridLabels;
27
44
  /** Locale-aware chrome; the exported English defaults remain available to callers. */
@@ -0,0 +1,10 @@
1
+ type FilterColumn = {
2
+ key: string;
3
+ searchable?: boolean;
4
+ searchText?: (row: Record<string, unknown>) => string;
5
+ };
6
+ /** The filter text as matched: trimmed and case-folded; empty matches every row. */
7
+ export declare const normalizeFilter: (text: string | undefined) => string;
8
+ /** Whether a searchable column's text (its value, or `searchText`) contains the normalized filter. */
9
+ export declare function rowMatches(row: Record<string, unknown>, columns: FilterColumn[], filter: string): boolean;
10
+ export {};
@@ -0,0 +1,15 @@
1
+ /** What a user changed in a grid's view; every field is optional. */
2
+ export type DataGridViewState = {
3
+ columnOrder?: string[];
4
+ hiddenColumns?: string[];
5
+ columnWidths?: Record<string, number>;
6
+ sort?: {
7
+ key: string;
8
+ direction: 'asc' | 'desc';
9
+ } | null;
10
+ pageSize?: number;
11
+ };
12
+ /** The view state saved under `key` in this browser; fields it cannot trust are dropped. */
13
+ export declare function loadGridState(key: string): DataGridViewState;
14
+ /** Saves the view state under `key`; a browser that refuses storage keeps the grid working unsaved. */
15
+ export declare function saveGridState(key: string, state: DataGridViewState): void;
@@ -7,7 +7,9 @@ declare var __VLS_1: {};
7
7
  type __VLS_Slots = {} & {
8
8
  default?: (props: typeof __VLS_1) => any;
9
9
  };
10
- declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
10
+ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
11
+ viewport: Readonly<import('vue').ShallowRef<HTMLElement | null>>;
12
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
11
13
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
12
14
  declare const _default: typeof __VLS_export;
13
15
  export default _default;
@@ -19,6 +19,8 @@ export type DropdownMenuCheckboxEntry = {
19
19
  disabled?: boolean;
20
20
  danger?: boolean;
21
21
  shortcut?: string;
22
+ /** A quiet note after the label, such as why the item is unchecked. */
23
+ hint?: string;
22
24
  };
23
25
  export type DropdownMenuRadioEntry = {
24
26
  type: 'radio';
@@ -6,8 +6,11 @@ type __VLS_Props = {
6
6
  valueLabel?: string;
7
7
  /** Colours the bar by meaning, such as `warning` for a volume that is nearly full. */
8
8
  tone?: 'primary' | 'success' | 'warning' | 'danger';
9
+ /** `sm` is a 4px line that fits under a label in a dense table row. */
10
+ size?: 'md' | 'sm';
9
11
  };
10
12
  declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
13
+ size: "md" | "sm";
11
14
  value: number;
12
15
  max: number;
13
16
  tone: "primary" | "success" | "warning" | "danger";
@@ -1,4 +1,4 @@
1
- import { ShallowRef } from 'vue';
1
+ import { Ref } from 'vue';
2
2
  /** Observe the component's container, including DSM window resizes that do not
3
3
  * resize the document viewport. Zero is unknown (e.g. hidden tab or SSR). */
4
- export declare function useElementWidth(element: Readonly<ShallowRef<HTMLElement | null>>): import('vue').Ref<number, number>;
4
+ export declare function useElementWidth(element: Readonly<Ref<HTMLElement | null | undefined>>): Ref<number, number>;
@@ -122,6 +122,11 @@ export declare const englishUiText: {
122
122
  readonly "Expand row {key}": "Expand row {key}";
123
123
  readonly "Collapse row {key}": "Collapse row {key}";
124
124
  readonly "Reorder row {key}": "Reorder row {key}";
125
+ readonly Columns: "Columns";
126
+ readonly "Reset columns": "Reset columns";
127
+ readonly "Too narrow": "Too narrow";
128
+ readonly "{count} of {total} items": "{count} of {total} items";
129
+ readonly "No matching items": "No matching items";
125
130
  readonly "Sort by {name}": "Sort by {name}";
126
131
  readonly "Value is required": "Value is required";
127
132
  readonly "Value does not match the expected format": "Value does not match the expected format";
package/dist/index.d.ts CHANGED
@@ -105,8 +105,8 @@ export { default as RepeatRow } from './components/composite/repeat-rows/RepeatR
105
105
  export { default as FieldCell } from './components/composite/repeat-rows/FieldCell.vue';
106
106
  export { formatFileSize, formatSpeed } from './lib/fileSize';
107
107
  export type { UnknownOption } from './lib/fileSize';
108
- export { formatDuration, formatPercent, formatRelativeTime } from './lib/format';
109
- export type { FormatOptions } from './lib/format';
108
+ export { formatDateTime, formatDuration, formatPercent, formatRelativeTime } from './lib/format';
109
+ export type { DateTimeFormatOptions, FormatOptions } from './lib/format';
110
110
  export { resolveFieldWidth } from './lib/fieldWidth';
111
111
  export type { FieldWidth } from './lib/fieldWidth';
112
112
  export { default as SettingsSection } from './components/composite/settings-section/SettingsSection.vue';
@@ -126,6 +126,7 @@ export { default as Statistic } from './components/composite/statistic/Statistic
126
126
  export type { DataTableColumn } from './components/composite/data-table/DataTable.vue';
127
127
  export { default as DataGrid } from './components/composite/data-grid/DataGrid.vue';
128
128
  export type { DataGridColumn, DataGridColumnWidths, DataGridLabels, ResolvedDataGridLabels, DataGridSort, DataGridGroup, DataGridExpandable, DataGridDraggable, DataGridReorder, DataGridCellUpdate, DataGridAdapter, DataGridCellSlotProps } from './components/composite/data-grid/DataGrid.vue';
129
+ export { loadGridState, saveGridState, type DataGridViewState } from './components/composite/data-grid/viewState';
129
130
  export { default as TreeView } from './components/composite/tree-view/TreeView.vue';
130
131
  export type { TreeNode, TreeViewCheckPayload, TreeViewDraggableConfig, TreeViewPosition, TreeViewReorder } from './components/composite/tree-view/TreeView.vue';
131
132
  export { default as FilePicker } from './components/composite/file-picker/FilePicker.vue';