@xleddyl/443-components-vue 0.16.3 → 0.16.5
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/CATALOG.md +27 -27
- package/README.md +8 -3
- package/dist/components/templates/DashboardLayout.vue +4 -0
- package/dist/components/templates/DashboardLayout.vue.d.ts +3 -0
- package/dist/components/templates/SettingsModal.vue +13 -5
- package/dist/components/templates/SettingsModal.vue.d.ts +7 -0
- package/dist/composables/useRouteState.d.ts +6 -2
- package/dist/composables/useRouteState.mjs +16 -3
- package/dist/grid/DataGrid.vue +3 -0
- package/dist/grid/DataGrid.vue.d.ts +25 -0
- package/dist/grid/GridToolbar.vue +1 -0
- package/dist/grid/GridToolbar.vue.d.ts +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1 -0
- package/dist/internal/keys.d.ts +1 -0
- package/dist/internal/keys.mjs +1 -0
- package/dist/internal/layout/query.d.ts +10 -2
- package/dist/internal/layout/query.mjs +84 -13
- package/dist/nuxt/module.d.ts +4 -0
- package/dist/nuxt/module.mjs +7 -2
- package/dist/nuxt/runtime/route-state.d.ts +2 -0
- package/dist/nuxt/runtime/route-state.mjs +14 -0
- package/package.json +1 -1
package/CATALOG.md
CHANGED
|
@@ -66,7 +66,7 @@ Playground paths in this file are relative to `playground/nuxt/app/pages/compone
|
|
|
66
66
|
| Detail or edit panel from the side | `Drawer` (or `useDrawer().open`) |
|
|
67
67
|
| Dialog with a form or content | `Modal` (or `useModal().open` to get the result as a promise) |
|
|
68
68
|
| Buttons in a Modal or Drawer header | `#actions` slot of `Modal` / `Drawer` (right side, before the close button) |
|
|
69
|
-
| Deep link to a drawer or modal (`?family=fam_1`,
|
|
69
|
+
| Deep link to a drawer or modal (`?family=fam_1`, `#family/fam_1`) | `useRouteState('family')` + `:open="!!familyId"`, `location: 'hash'`; settings: `route`, `settingsRoute` |
|
|
70
70
|
| Menu opened by a button | `DropdownMenu` |
|
|
71
71
|
| Right click menu | `ContextMenu` |
|
|
72
72
|
| Small floating panel with content or a form | `Popover` |
|
|
@@ -249,25 +249,25 @@ Mount `OverlayHost` once for the imperative hooks (see [Composables](#composable
|
|
|
249
249
|
|
|
250
250
|
Templates combine core parts into whole pages and layouts. Start from them for new pages.
|
|
251
251
|
|
|
252
|
-
| Component | Purpose and when to use it | Main props, models, slots, events
|
|
253
|
-
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
254
|
-
| `DashboardLayout` | Ready app frame: brand, navigation, command search, account menu, settings modal, breadcrumbs, theme switch. Put it in `layouts/default.vue`. | Models: `v-model:collapsed`, `v-model:open`, `v-model:settingsOpen`, `v-model:settingsSection`. Props: `navigation` (`NavItem[]` or `NavSection[]`), `account` (`name`, `email`, `avatar`, `role`, `menu`), `brandName`, `brandIcon`, `brandLogo`, `breadcrumbs`, `search` (true, groups or function), `settingsSections`, `settingsRoute`, `settingsResolvePage`, `settingsHideSession`, `variant`, `content` (`page`, `fill`, `centered`, `bleed`; per page with `definePageMeta({ content })` in `k-dashboard`), `fluid`, `fill`, `onSignOut`. Slots: default, `actions`, `brand`, `sidebar`, `sidebar-footer`, `account`, `topbar`, `search-item`, `settings-account-fields`, `settings-section-<id>`, `settings-actions-<id>`. Event: `accountSelect`. | `templates/dashboard-layout.vue` |
|
|
255
|
-
| `AuthLayout` | Login, sign up and reset password frame. Centered card or split with aside. | Props: `variant` (centered, split), `title`, `description`, `footer`, `brandName`, `brandIcon`, `brandLogo`, `asideTitle`, `asideDescription`, `themeSwitch`. Slots: default, `links`, `footer`, `brand`, `aside`, `corner`.
|
|
256
|
-
| `ListPage` | Whole list page: header, search, filters, table, pagination, bulk actions, loading, empty, no results and error states. Client or server mode (`total`). | Models: `page`, `pageSize`, `search`, `filter`, `sort`, `selected`. Props: `title`, `rows`, `columns`, `rowKey`, `rowTo`, `selectable`, `loading`, `error`, `total`, `paginate`, `searchable`, `searchKeys`, `filters`, `filterKey`, `filterMultiple`, `emptyTitle`. Slots: `actions`, `toolbar`, `toolbar-end`, `bulk`, `empty`, `empty-actions`, `no-results`, `error`, `cell-<key>`, `table`. Events: `retry`, `clear`, `rowClick`.
|
|
257
|
-
| `DetailPage` | Record page: back link, title, status, actions, tabs, main column and aside with description lists. | Model: `v-model:tab`. Props: `title`, `backTo`, `backLabel`, `status` (`label`, `tone`, `dot`), `tabs`, `sections` (`title`, `items`), `loading`, `error`. Slots: default, `actions`, `badge`, `meta`, `aside`, `aside-before`, `panel-<value>`.
|
|
258
|
-
| `FormPage` | Form page: header, cards in a `Form`, sticky submit and cancel footer, dirty guard. | Props: `title`, `backTo`, `form` (from `useForm`), `dirty`, `saving`, `guard`, `requireDirty`, `submitLabel`, `cancelLabel`, `cancelTo`, `width`, `sticky`. Slots: default, `actions`, `footer`, `status`. Events: `submit`, `submitted`, `invalid`, `reset`, `cancel`.
|
|
259
|
-
| `DashboardPage` | Overview page: header, stat grid, content grid. | Props: `title`, `eyebrow`, `description`, `stats` (`PageStat[]`), `statsColumns`, `columns`, `loading`. Slots: default, `actions`, `stats`, `sparkline`, `before`, `after`, `eyebrow`.
|
|
260
|
-
| `SettingsPage` | Settings as a page: section nav, panels, floating save bar when `dirty`. | Model: `v-model:section`. Props: `title`, `sections` (`value`, `label`, `icon`, `to`), `dirty`, `saving`, `saveBar`, `form`. Slots: `section-<value>`, `actions`, `nav-footer`. Events: `save`, `discard`.
|
|
261
|
-
| `SettingsSection` | Card of one settings section with a save footer. | Props: `title`, `description`, `icon`, `hint`, `saveable`, `saving`, `saveDisabled`, `saveLabel`, `form`. Slots: default, `actions`, `footer`, `hint`. Event: `save`.
|
|
262
|
-
| `SettingsGroup` | Block with label, description and actions inside a settings section. | Props: `label`, `description`, `level`, `row`. Slots: default, `label`, `description`, `actions`.
|
|
263
|
-
| `SettingsModal` | Settings dialog with nav, built-in account and appearance sections, your own sections and subpages. | Models: `v-model:open`, `v-model:section`, `v-model:pages`. Props: `sections` (`id`, `label`, `icon`, `group`, `badge`, `to`), `account`, `presets`, `title`, `onSignOut`, `hideSession`, `beforeClose`, `route` (sync with `?settings=<section>/<page>`), `resolvePage(section, id, parents)`. Slots: `section-<id>`, `actions-<id>` (right of the header row, also on a subpage), `account-fields`.
|
|
264
|
-
| `AppearanceSettings` | Theme (light, dark, system) and preset cards. Used by `SettingsModal`. Use alone in a settings page. | Props: `presets` (list or false), `themes`, `level`.
|
|
265
|
-
| `SaveBar` | Floating bar with save and discard for unsaved changes. | Props: `visible`, `saving`, `message`, `saveLabel`, `discardLabel`, `form`. Slots: default, `actions`. Events: `save`, `discard`.
|
|
266
|
-
| `EmptyPage` | First run screen with illustration, actions and onboarding steps. | Props: `title`, `description`, `icon`, `actionLabel`, `actionTo`, `onAction`, `secondaryLabel`, `steps` (`title`, `description`, `done`, `to`), `fill`. Slots: default, `illustration`, `title`, `description`, `actions`.
|
|
267
|
-
| `ErrorPage` | Error screen. Reads a Nuxt `error`. Use it in `app/error.vue`. | Props: `error`, `statusCode`, `title`, `message`, `homeTo`, `homeLabel`, `retryLabel`, `brandName`, `fill`, `onHome`, `onRetry`. Slots: default, `title`, `message`, `actions`, `brand`.
|
|
268
|
-
| `NotFoundPage` | 404 screen. | Props: `title`, `message`, `homeTo`, `homeLabel`, `backLabel`, `brandName`, `fill`, `onHome`. Slots: default, `actions`, `brand`.
|
|
269
|
-
| `Brand` | Logo and name block (image URL, inline SVG or component). | Props: `name`, `subtitle`, `icon`, `logo`, `to`, `size`, `accent`, `hideText`, `showName`. Slots: default, `logo`, `mark`, `subtitle`.
|
|
270
|
-
| `AccountMenu` | Account button with avatar, name and a dropdown menu. Used by `DashboardLayout`. | Props: `name`, `email`, `avatar`, `menu` (menu items), `placement`, `icon`. Event: `select`.
|
|
252
|
+
| Component | Purpose and when to use it | Main props, models, slots, events | Playground |
|
|
253
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
254
|
+
| `DashboardLayout` | Ready app frame: brand, navigation, command search, account menu, settings modal, breadcrumbs, theme switch. Put it in `layouts/default.vue`. | Models: `v-model:collapsed`, `v-model:open`, `v-model:settingsOpen`, `v-model:settingsSection`. Props: `navigation` (`NavItem[]` or `NavSection[]`), `account` (`name`, `email`, `avatar`, `role`, `menu`), `brandName`, `brandIcon`, `brandLogo`, `breadcrumbs`, `search` (true, groups or function), `settingsSections`, `settingsRoute`, `settingsRouteLocation`, `settingsAccountSection`, `settingsResolvePage`, `settingsHideSession`, `variant`, `content` (`page`, `fill`, `centered`, `bleed`; per page with `definePageMeta({ content })` in `k-dashboard`), `fluid`, `fill`, `onSignOut`. Slots: default, `actions`, `brand`, `sidebar`, `sidebar-footer`, `account`, `topbar`, `search-item`, `settings-account-fields`, `settings-section-<id>`, `settings-actions-<id>`. Event: `accountSelect`. | `templates/dashboard-layout.vue` |
|
|
255
|
+
| `AuthLayout` | Login, sign up and reset password frame. Centered card or split with aside. | Props: `variant` (centered, split), `title`, `description`, `footer`, `brandName`, `brandIcon`, `brandLogo`, `asideTitle`, `asideDescription`, `themeSwitch`. Slots: default, `links`, `footer`, `brand`, `aside`, `corner`. | `templates/auth-layout.vue` |
|
|
256
|
+
| `ListPage` | Whole list page: header, search, filters, table, pagination, bulk actions, loading, empty, no results and error states. Client or server mode (`total`). | Models: `page`, `pageSize`, `search`, `filter`, `sort`, `selected`. Props: `title`, `rows`, `columns`, `rowKey`, `rowTo`, `selectable`, `loading`, `error`, `total`, `paginate`, `searchable`, `searchKeys`, `filters`, `filterKey`, `filterMultiple`, `emptyTitle`. Slots: `actions`, `toolbar`, `toolbar-end`, `bulk`, `empty`, `empty-actions`, `no-results`, `error`, `cell-<key>`, `table`. Events: `retry`, `clear`, `rowClick`. | `templates/list-page.vue` |
|
|
257
|
+
| `DetailPage` | Record page: back link, title, status, actions, tabs, main column and aside with description lists. | Model: `v-model:tab`. Props: `title`, `backTo`, `backLabel`, `status` (`label`, `tone`, `dot`), `tabs`, `sections` (`title`, `items`), `loading`, `error`. Slots: default, `actions`, `badge`, `meta`, `aside`, `aside-before`, `panel-<value>`. | `templates/detail-page.vue` |
|
|
258
|
+
| `FormPage` | Form page: header, cards in a `Form`, sticky submit and cancel footer, dirty guard. | Props: `title`, `backTo`, `form` (from `useForm`), `dirty`, `saving`, `guard`, `requireDirty`, `submitLabel`, `cancelLabel`, `cancelTo`, `width`, `sticky`. Slots: default, `actions`, `footer`, `status`. Events: `submit`, `submitted`, `invalid`, `reset`, `cancel`. | `templates/form-page.vue` |
|
|
259
|
+
| `DashboardPage` | Overview page: header, stat grid, content grid. | Props: `title`, `eyebrow`, `description`, `stats` (`PageStat[]`), `statsColumns`, `columns`, `loading`. Slots: default, `actions`, `stats`, `sparkline`, `before`, `after`, `eyebrow`. | `templates/dashboard-page.vue` |
|
|
260
|
+
| `SettingsPage` | Settings as a page: section nav, panels, floating save bar when `dirty`. | Model: `v-model:section`. Props: `title`, `sections` (`value`, `label`, `icon`, `to`), `dirty`, `saving`, `saveBar`, `form`. Slots: `section-<value>`, `actions`, `nav-footer`. Events: `save`, `discard`. | `templates/settings-page.vue` |
|
|
261
|
+
| `SettingsSection` | Card of one settings section with a save footer. | Props: `title`, `description`, `icon`, `hint`, `saveable`, `saving`, `saveDisabled`, `saveLabel`, `form`. Slots: default, `actions`, `footer`, `hint`. Event: `save`. | `templates/settings-page.vue` |
|
|
262
|
+
| `SettingsGroup` | Block with label, description and actions inside a settings section. | Props: `label`, `description`, `level`, `row`. Slots: default, `label`, `description`, `actions`. | `templates/settings-modal.vue` |
|
|
263
|
+
| `SettingsModal` | Settings dialog with nav, built-in account and appearance sections, your own sections and subpages. | Models: `v-model:open`, `v-model:section`, `v-model:pages`. Props: `sections` (`id`, `label`, `icon`, `group`, `badge`, `to`), `account`, `presets`, `title`, `onSignOut`, `hideSession`, `beforeClose`, `route` (sync with `?settings=<section>/<page>`), `routeLocation` (`'hash'`: `#settings/<section>/<page>`), `accountSection` (id with the built-in account content, default `account`), `resolvePage(section, id, parents)`. Slots: `section-<id>`, `actions-<id>` (right of the header row, also on a subpage), `account-fields`. | `templates/settings-modal.vue` |
|
|
264
|
+
| `AppearanceSettings` | Theme (light, dark, system) and preset cards. Used by `SettingsModal`. Use alone in a settings page. | Props: `presets` (list or false), `themes`, `level`. | `presets.vue` (pages root) |
|
|
265
|
+
| `SaveBar` | Floating bar with save and discard for unsaved changes. | Props: `visible`, `saving`, `message`, `saveLabel`, `discardLabel`, `form`. Slots: default, `actions`. Events: `save`, `discard`. | `templates/settings-page.vue` |
|
|
266
|
+
| `EmptyPage` | First run screen with illustration, actions and onboarding steps. | Props: `title`, `description`, `icon`, `actionLabel`, `actionTo`, `onAction`, `secondaryLabel`, `steps` (`title`, `description`, `done`, `to`), `fill`. Slots: default, `illustration`, `title`, `description`, `actions`. | `templates/empty-page.vue` |
|
|
267
|
+
| `ErrorPage` | Error screen. Reads a Nuxt `error`. Use it in `app/error.vue`. | Props: `error`, `statusCode`, `title`, `message`, `homeTo`, `homeLabel`, `retryLabel`, `brandName`, `fill`, `onHome`, `onRetry`. Slots: default, `title`, `message`, `actions`, `brand`. | `templates/error-page.vue` |
|
|
268
|
+
| `NotFoundPage` | 404 screen. | Props: `title`, `message`, `homeTo`, `homeLabel`, `backLabel`, `brandName`, `fill`, `onHome`. Slots: default, `actions`, `brand`. | `templates/error-page.vue` |
|
|
269
|
+
| `Brand` | Logo and name block (image URL, inline SVG or component). | Props: `name`, `subtitle`, `icon`, `logo`, `to`, `size`, `accent`, `hideText`, `showName`. Slots: default, `logo`, `mark`, `subtitle`. | `templates/dashboard-layout.vue` |
|
|
270
|
+
| `AccountMenu` | Account button with avatar, name and a dropdown menu. Used by `DashboardLayout`. | Props: `name`, `email`, `avatar`, `menu` (menu items), `placement`, `icon`. Event: `select`. | `templates/dashboard-layout.vue` |
|
|
271
271
|
|
|
272
272
|
Nuxt ready layouts: with `k: { layouts: true }` the module registers the layouts `k-dashboard` and `k-auth`. They read brand, navigation, account and settings from `app.config.ts` (`k` key). `useDashboard()` gives the runtime account, breadcrumbs and badges for `k-dashboard`. See README, section "Nuxt: ready layouts".
|
|
273
273
|
|
|
@@ -275,12 +275,12 @@ Nuxt ready layouts: with `k: { layouts: true }` the module registers the layouts
|
|
|
275
275
|
|
|
276
276
|
Import from `@xleddyl/443-components-vue/grid`. It wraps AG Grid Community.
|
|
277
277
|
|
|
278
|
-
| Component | Purpose and when to use it | Main props, models, slots, events
|
|
279
|
-
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
280
|
-
| `DataGrid` | Big table: virtual scroll, column menu, search, CSV export, selection, server sort and paging, inline editing. For small tables use `Table`. | Models: `v-model:query`, `v-model:page`, `v-model:pageSize`, `v-model:sort` (`{ column, direction }`). Props: `rows`, `columns` (AG Grid `ColDef`), `rowIdKey` or `getRowId`, `storageKey` (columns and page size in the preferences store), `density`, `selectable`, `loading`, `height`, `fill`, `autoHeight`, `toolbar`, `searchable`, `columnMenu`, `exportable`, `serverSort`, `pagination`, `serverPagination`, `total`, `filterable`, `editable`, `spreadsheet`, `gridOptions`, `messages`, `filterCount`. Slots: `actions`, `filters` (`{ close, count }`, hosted in a modal opened by a funnel button next to the search). Events: `filtersReset`, `rowClick`, `selectionChange`, `sortChange`, `filterChange`, `cellsChange`, `cellsInvalid`, `dirtyChange`, `rangeChange`, `copy`. | `grid/data-grid.vue` |
|
|
281
|
-
| `DataGrid` with `spreadsheet` | Excel-like editing: cell range selection, copy and paste (TSV), fill down and right, clear, undo and redo, dirty cell tracking. Set `rowIdKey` or `getRowId`. | Template ref methods (`GridSpreadsheetMethods`): `getRange`, `getSelectedCells`, `selectRange`, `selectAll`, `setRangeValue`, `copyRange`, `paste`, `fillDown`, `fillRight`, `clearCells`, `undo`, `redo`, `getDirtyCells`, `clearDirty`. The `actions` slot gets the same methods plus `range`, `selectedCellCount`, `dirtyCount`, `canUndo`, `canRedo`. Use `cellsChange` to save edits, `cellsInvalid` for rejected input.
|
|
282
|
-
| `GridToolbar` | Toolbar of `DataGrid` (search, column menu, export). `DataGrid` already renders it. Use it alone only for a custom grid setup. | Props: `api`, `searchable`, `columnMenu`, `exportable`, `exportFileName`, `debounce`, `filterCount`. Model: `v-model` (query). Slots: default (actions), `filters`. Events: `resetColumns`, `filtersReset`.
|
|
283
|
-
| Cell renderers | Use as `cellRenderer` in a column definition, with `cellRendererParams`. | `BadgeCell` (`tones`, `labels`, `defaultTone`, `variant`; array values render one badge each, `max` adds a `+N` badge, `wrap`), `BooleanCell` (`trueLabel`, `falseLabel`), `DateCell` (`locale`, `options`, `relative`), `EmptyCell` (placeholder for empty values), `ImageCell` (`alt`, `rounded`, `fit`, `size`), `LinkCell` (`href`, `label`, `target`), `ButtonCell` (`onClick`, `label`, `ariaLabel`, `emptyLabel`; an inline button that does not trigger the row click).
|
|
278
|
+
| Component | Purpose and when to use it | Main props, models, slots, events | Playground |
|
|
279
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
|
|
280
|
+
| `DataGrid` | Big table: virtual scroll, column menu, search, CSV export, selection, server sort and paging, inline editing. For small tables use `Table`. | Models: `v-model:query`, `v-model:page`, `v-model:pageSize`, `v-model:sort` (`{ column, direction }`). Props: `rows`, `columns` (AG Grid `ColDef`), `rowIdKey` or `getRowId`, `storageKey` (columns and page size in the preferences store), `density`, `selectable`, `loading`, `height`, `fill`, `autoHeight`, `toolbar`, `searchable`, `columnMenu`, `exportable`, `serverSort`, `pagination`, `serverPagination`, `total`, `filterable`, `editable`, `spreadsheet`, `gridOptions`, `messages`, `filterCount`. Slots: `actions`, `actions-end` (after the column menu and export buttons), `filters` (`{ close, count }`, hosted in a modal opened by a funnel button next to the search). Events: `filtersReset`, `rowClick`, `selectionChange`, `sortChange`, `filterChange`, `cellsChange`, `cellsInvalid`, `dirtyChange`, `rangeChange`, `copy`. | `grid/data-grid.vue` |
|
|
281
|
+
| `DataGrid` with `spreadsheet` | Excel-like editing: cell range selection, copy and paste (TSV), fill down and right, clear, undo and redo, dirty cell tracking. Set `rowIdKey` or `getRowId`. | Template ref methods (`GridSpreadsheetMethods`): `getRange`, `getSelectedCells`, `selectRange`, `selectAll`, `setRangeValue`, `copyRange`, `paste`, `fillDown`, `fillRight`, `clearCells`, `undo`, `redo`, `getDirtyCells`, `clearDirty`. The `actions` slot gets the same methods plus `range`, `selectedCellCount`, `dirtyCount`, `canUndo`, `canRedo`. Use `cellsChange` to save edits, `cellsInvalid` for rejected input. | `grid/spreadsheet.vue` |
|
|
282
|
+
| `GridToolbar` | Toolbar of `DataGrid` (search, column menu, export). `DataGrid` already renders it. Use it alone only for a custom grid setup. | Props: `api`, `searchable`, `columnMenu`, `exportable`, `exportFileName`, `debounce`, `filterCount`. Model: `v-model` (query). Slots: default (actions), `filters`. Events: `resetColumns`, `filtersReset`. | - |
|
|
283
|
+
| Cell renderers | Use as `cellRenderer` in a column definition, with `cellRendererParams`. | `BadgeCell` (`tones`, `labels`, `defaultTone`, `variant`; array values render one badge each, `max` adds a `+N` badge, `wrap`), `BooleanCell` (`trueLabel`, `falseLabel`), `DateCell` (`locale`, `options`, `relative`), `EmptyCell` (placeholder for empty values), `ImageCell` (`alt`, `rounded`, `fit`, `size`), `LinkCell` (`href`, `label`, `target`), `ButtonCell` (`onClick`, `label`, `ariaLabel`, `emptyLabel`; an inline button that does not trigger the row click). | `grid/data-grid.vue` |
|
|
284
284
|
|
|
285
285
|
Grid helpers: `registerGridModules`, `registerGridEditorModules`, `registerGridFilterModules`, `registerGridSpreadsheetModules` (register AG Grid modules by hand), `createGridTheme`, `parseTsv`, `formatTsv`.
|
|
286
286
|
|
|
@@ -331,7 +331,7 @@ All are exported from `@xleddyl/443-components-vue` and auto-imported in Nuxt.
|
|
|
331
331
|
| `usePreset` | Read or set the visual preset. | `presetOptions` lists the presets. | `presets.vue` (pages root) |
|
|
332
332
|
| `usePreference`, `usePreferences`, `createPreferences`, `providePreferences` | Remember UI choices per user (theme, preset, sidebar collapsed, DataGrid columns and page size, your own keys) in one pluggable store. | `app.use(createPreferences(store))`; a store is `{ get, set, subscribe?, clientOnly? }`. `createRemotePreferenceStore({ initial, save, debounce })` for an API, `createLocalPreferenceStore()` is the default. `usePreference(key, fallback, { validate })` is a writable computed. Keys: `theme`, `preset`, `shell:<storageKey>`, `grid:<storageKey>` (`PREFERENCE_KEYS`). | `core/preferences.vue` |
|
|
333
333
|
| `useAppShell` | Read the `AppShell` state (collapsed, open) from a child component. | Returns the shell context or `null` outside a shell. | `layout/app-shell.vue` |
|
|
334
|
-
| `useRouteState` | Bind a ref to a URL query parameter: deep links to drawers and modals, back button closes them, filters in the URL.
|
|
334
|
+
| `useRouteState` | Bind a ref to a URL query parameter or hash entry: deep links to drawers and modals, back button closes them, filters in the URL. | `useRouteState(key, { parse, serialize, default, history: 'push' \| 'replace', location: 'query' \| 'hash' })`. Returns a ref; `undefined` or `''` removes the parameter. Hash entries are `key/value` joined with `&` (`#family/fam_1&variant/var_2`), read after mount. `withRouteStateScroll(scrollBehavior)` for plain vue-router. No-op without a router. | `overlays/drawer.vue` |
|
|
335
335
|
| `useScrollLock` | Lock the page scroll while something is open. | `useScrollLock(activeRef)`. | - |
|
|
336
336
|
| `useLocale` | Translate component texts and read the active locale (en, it, de). | `const { locale, t } = useLocale()`. `provideLocale` and `createLocale` set it. Nuxt follows `k.locale`. | `languages.vue` (pages root) |
|
|
337
337
|
| `useIcons` helpers, `createIcons`, `provideIcons` | Register your own icons by name. | Nuxt: option `k.icons: { dir: '~/assets/icons' }`. | `components/core/icon.vue` |
|
package/README.md
CHANGED
|
@@ -49,6 +49,7 @@ Every component is registered with the prefix (`Button.vue` becomes `<KButton>`)
|
|
|
49
49
|
| `cssLayers` | `'theme, base, k, components, utilities'` | Cascade layer order declared before any other CSS. The library layer `k` sits after the `theme` and `base` layers of Tailwind (so preflight does not reset components) and before `components` and `utilities` (so host classes win). `false` declares nothing. See [Scoping](#scoping). |
|
|
50
50
|
| `locale` | `'en'` | Language of the built-in copy: `'en'`, `'it'`, `'de'`, or any code completed with `messages`. With `@nuxtjs/i18n` the current i18n locale wins. See [Languages](#languages). |
|
|
51
51
|
| `messages` | `{}` | Overrides per locale code, for example `{ it: { common: { cancel: 'Indietro' } } }`. |
|
|
52
|
+
| `routeState.location` | `'query'` | Where `useRouteState` and the settings modal `route` keep their value by default: `'query'` (`?family=fam_1`) or `'hash'` (`#family/fam_1`). See [Deep links](#deep-links). |
|
|
52
53
|
| `composables` | `true` | Auto-imports the composables. |
|
|
53
54
|
| `global` | `false` | Puts `k-root` and `data-theme` on `<html>` for apps that are dashboard only. |
|
|
54
55
|
| `theme.cookie` | `'k-theme'` | Cookie that stores the theme preference (SSR safe, no flash). `false` sets no cookie and always renders `theme.default`. |
|
|
@@ -557,7 +558,7 @@ const confirmDiscard = () => !dirty.value || confirm({ title: 'Discard changes?'
|
|
|
557
558
|
|
|
558
559
|
### Deep links
|
|
559
560
|
|
|
560
|
-
`useRouteState(key, options?)` binds a ref to one query parameter of the current URL, so any Drawer or Modal can be opened from a link, survives a reload and closes with the browser back button. It reads the router through `$router` (vue-router or Nuxt, no import of either); without a router it is a plain ref.
|
|
561
|
+
`useRouteState(key, options?)` binds a ref to one query parameter (or one hash entry) of the current URL, so any Drawer or Modal can be opened from a link, survives a reload and closes with the browser back button. It reads the router through `$router` (vue-router or Nuxt, no import of either); without a router it is a plain ref.
|
|
561
562
|
|
|
562
563
|
```ts
|
|
563
564
|
const familyId = useRouteState('family')
|
|
@@ -587,6 +588,8 @@ const archived = useRouteState('archived', {
|
|
|
587
588
|
})
|
|
588
589
|
```
|
|
589
590
|
|
|
591
|
+
- Hash: `location: 'hash'` keeps the value in the hash instead of the query, like `#settings/general`. One entry is `key/value` (the value may contain `/`); several entries are joined with `&` in the order they were opened: `#family/fam_1&variant/var_2`. Query and hash writes in the same tick make one navigation. Set the default for the app with `k.routeState.location` in the Nuxt module, or `app.provide(ROUTE_LOCATION_KEY, 'hash')` in plain Vue; an explicit `location` wins. Use the hash for overlays and the query for page state (filters, tabs). The server never receives the hash, so a hash value is read after mount: the server and hydration render the overlay closed, then it opens.
|
|
592
|
+
- Scroll: a hash made only of `key/value` entries is not an anchor. The Nuxt module wraps the router `scrollBehavior` so that it sees no hash for these routes (no scroll to top when an overlay closes, no lookup of `#settings/general` as an element). With plain vue-router wrap your own: `scrollBehavior: withRouteStateScroll((to, from, saved) => ...)`.
|
|
590
593
|
- Create forms: use a reserved value (`?family=new`) and branch on it, like above.
|
|
591
594
|
- Nested views inside one overlay: keep the path in one parameter (`?settings=users/usr_123`, `?family=fam_1/variants`) and split it on `/` in a computed. One parameter keeps every step in one history entry and avoids two refs racing. Encode ids that may contain `/` with `encodeURIComponent` per segment.
|
|
592
595
|
- Pages that set `definePageMeta({ key: (route) => route.fullPath })` remount on every query change; keep the default page key for pages with deep links.
|
|
@@ -627,7 +630,7 @@ Ready defaults to start a dashboard project: an app frame, an auth frame and pag
|
|
|
627
630
|
| `brandName`, `brandSubtitle`, `brandIcon`, `brandLogo`, `brandShowName`, `brandTo` | `brandTo: '/'` | `brandLogo` takes an image URL, inline SVG markup or a component; the name is hidden next to a logo unless `brandShowName`. Or the `#brand` slot. `brandAlign` (`'start'` or `'center'`) centers the brand in the expanded sidebar, with the collapse button on the right. The collapse button uses the icons `sidebar-collapse` and `sidebar-expand` (chevrons by default), so `k.icons.aliases: { 'sidebar-collapse': 'lucide:panel-left-close', 'sidebar-expand': 'lucide:panel-left-open' }` changes only that button. |
|
|
628
631
|
| `account` | | `{ name, email?, avatar?, role?, menu? }`. Shown as a row (avatar, name, email, chevron) at the bottom of the sidebar. |
|
|
629
632
|
| `accountAction` | `'settings'` | `settings`: the row opens a `SettingsModal` (`settingsSections`, `settingsPresets`, `v-model:settings-open`, `v-model:settings-section`, `@sign-out`). `menu`: a DropdownMenu with `account.menu`, selections emit `account-select`. Or replace it with the `#account` slot. |
|
|
630
|
-
| `settingsRoute`, `settingsResolvePage` | `false` | Sync the settings modal with the URL (`?settings=users/usr_123`): `true` uses the key `settings`, a string sets the key. `settingsResolvePage(section, id, parents)` gives the label of a subpage restored from the URL. See SettingsModal.
|
|
633
|
+
| `settingsRoute`, `settingsResolvePage` | `false` | Sync the settings modal with the URL (`?settings=users/usr_123`): `true` uses the key `settings`, a string sets the key. `settingsRouteLocation: 'hash'` gives `#settings/users/usr_123`. `settingsAccountSection` renames the built-in account section. `settingsResolvePage(section, id, parents)` gives the label of a subpage restored from the URL. See SettingsModal. |
|
|
631
634
|
| `settingsHideSession` | `false` | Hides the Session group (sign out) in the account section of the settings modal. The Sign out item in the nav stays. |
|
|
632
635
|
| `breadcrumbs` | | Rendered in the desktop topbar. `#topbar` replaces them, `#actions` adds buttons on the right. |
|
|
633
636
|
| `search` | `false` | `true` adds a CommandPalette with every navigation item, an array adds your own command groups, an async function `(query, signal) => CommandGroup<T>[]` searches on your server (item `data` reaches `#search-item` typed) (matching navigation items come first unless `searchNavigation` is `false`). `#search` replaces it. |
|
|
@@ -677,7 +680,8 @@ A large dialog (60rem) with a section nav on the left and the content on the rig
|
|
|
677
680
|
- Labels: `title` (accessible name of the dialog, default `Settings`), `closeLabel`, `navLabel`, `signOutLabel`, `accountLabel`, `accountText`, `appearanceLabel`, `appearanceText`, `sessionLabel`, `sessionText`, `themeLabel`, `themeText`, `presetLabel`, `presetText`.
|
|
678
681
|
- Header actions: `#actions-<id>` renders buttons on the right of the section header row (an "Add user" button), next to the title instead of on a row of its own in the body. It stays there when a subpage is open, so render different actions per page from its `page` prop. When the content is too narrow, the actions wrap under the title. On `DashboardLayout` the slot is `#settings-actions-<id>`.
|
|
679
682
|
- Subpages: `#section-<id>` and `#actions-<id>` get `page`, `pages`, `openPage({ id, label })` and `back()`. With a page open, the header title becomes the path (`Members > Grace Hopper`, same size, only the last part in the text color, earlier parts are buttons that go back to them), the section description stays, and a Back button (`backLabel`, default `common.back`) shows on the right before `#actions-<id>`. `v-model:pages` binds the stack (it also works unbound). Changing section clears it. Render the detail when `page` is set, the list otherwise.
|
|
680
|
-
-
|
|
683
|
+
- Account section: `accountSection` (default `'account'`) is the id of the section that shows the built-in account content (profile card, `#account-fields`, session group), so a section `{ id: 'general' }` can hold it.
|
|
684
|
+
- Deep links: `route` (`true` for the key `settings`, or your own query key, default `false`) syncs the open state, the section and the subpage stack with the URL: `?settings=users` for a section, `?settings=users/usr_123` for a subpage, deeper pages add segments. Opening, switching section, opening a subpage, Back and the crumbs push history entries, so the browser back button steps back through them and finally closes the dialog; closing removes the parameter. Loading a URL with the parameter opens the dialog at that section and subpage after mount. A section id that is not selectable falls back to the first section and the URL is corrected with `replace`. The URL holds only page ids, so `resolvePage(section, id, parents)` returns the label of a page restored from the URL: a string, `undefined` (the page and the ones after it are dropped, the URL is corrected) or a promise of either (the page opens at once with an empty label, then the label fills in). Without `resolvePage` the label is the id. Pages opened with `openPage` keep their own label. Link sections (`to`) do not close the dialog themselves when the route is synced: the navigation drops the parameter and closes it. `routeLocation: 'hash'` (default from `k.routeState.location`) keeps the same value in the hash: `#settings/users/usr_123`. On `DashboardLayout` the props are `settingsRoute`, `settingsRouteLocation` and `settingsResolvePage`; in the `k-dashboard` layout `settings.route` in `app.config`.
|
|
681
685
|
- `hide-session` hides the Session group (sign out) in the account section; the Sign out item in the nav stays. `beforeClose` works as on Modal.
|
|
682
686
|
- Accessibility: native modal `<dialog>` (focus trap, Escape, focus returns to the trigger), the nav is a labelled `nav` of buttons with `aria-current` and `aria-controls`, the content is a `section` labelled by its heading.
|
|
683
687
|
|
|
@@ -826,6 +830,7 @@ Accessibility related props and defaults:
|
|
|
826
830
|
- `DataGrid` sort is a model: `v-model:sort` (`{ column, direction } | null`) sets the sorted column from outside (server side sort, a sort restored from the URL) and follows header clicks. Leave it unset for an uncontrolled grid. `sortChange` still fires with `serverSort`.
|
|
827
831
|
- `DataGrid` takes `rowHeight` and `headerHeight` (pixels) for an exact row size on top of `density`, and `headerText: (field, column) => string | undefined` to name every column that has a `field` and no `headerName` (from your i18n).
|
|
828
832
|
- `DataGrid` `filterable` registers the AG Grid text, number and date filters (`registerGridFilterModules()` does it without the prop) and turns `filter` on for every column. `filterChange(model, event)` fires with `api.getFilterModel()` whenever a column filter changes, also when the filters come from `gridOptions` or your own registered modules.
|
|
833
|
+
- `DataGrid` `#actions-end` slot: same props as `#actions`, rendered at the end of the toolbar, after the column menu and export buttons (a view switch). `GridToolbar` takes it as `#end`.
|
|
829
834
|
- `DataGrid` `#filters` slot: put your own filter controls (Select, Combobox, DateInput bound with `v-model`) in it and the toolbar shows a square funnel button right after the search input (at the start with `:searchable="false"`), before the `actions`. The button opens a `Modal` titled "Filters" that hosts the slot, so the filters still apply live; its footer has "Reset filters" (emits `filtersReset`, disabled while `filterCount` is 0) and "Show results" (closes it). `filterCount` shows the number of active filters as a badge on the button and in its accessible name ("Filters, 2 active", `grid.filtersActive`). Slot props: `{ close, count }`. Focus returns to the button on close, and the modal becomes a bottom sheet on small screens. `GridToolbar` takes the same slot, prop and event.
|
|
830
835
|
|
|
831
836
|
```vue
|
|
@@ -146,6 +146,8 @@
|
|
|
146
146
|
:title="settingsTitleText"
|
|
147
147
|
:on-sign-out="onSignOut"
|
|
148
148
|
:route="settingsRoute"
|
|
149
|
+
:route-location="settingsRouteLocation"
|
|
150
|
+
:account-section="settingsAccountSection"
|
|
149
151
|
:hide-session="settingsHideSession"
|
|
150
152
|
:resolve-page="settingsResolvePage"
|
|
151
153
|
>
|
|
@@ -191,6 +193,8 @@ const props = defineProps({
|
|
|
191
193
|
settingsPresets: { type: [Array, Boolean], required: false, default: void 0 },
|
|
192
194
|
settingsTitle: { type: String, required: false },
|
|
193
195
|
settingsRoute: { type: [String, Boolean], required: false, default: false },
|
|
196
|
+
settingsRouteLocation: { type: String, required: false, default: void 0 },
|
|
197
|
+
settingsAccountSection: { type: String, required: false, default: void 0 },
|
|
194
198
|
settingsHideSession: { type: Boolean, required: false },
|
|
195
199
|
settingsResolvePage: { type: Function, required: false },
|
|
196
200
|
onSignOut: { type: Function, required: false },
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IconName } from '../../composables/useIcons';
|
|
2
|
+
import type { RouteLocation } from '../../composables/useRouteState';
|
|
2
3
|
import type { BreadcrumbItem, ShellContent, SidebarBrandAlign } from '../layout/types';
|
|
3
4
|
import type { CommandGroupLabelSlotProps, CommandItem, MenuItem } from '../overlays/types';
|
|
4
5
|
import type { ThemePreset } from '../../composables/usePreset';
|
|
@@ -22,6 +23,8 @@ declare const __VLS_export: <T = unknown>(__VLS_props: NonNullable<Awaited<typeo
|
|
|
22
23
|
settingsPresets?: readonly ThemePreset[] | false;
|
|
23
24
|
settingsTitle?: string;
|
|
24
25
|
settingsRoute?: string | boolean;
|
|
26
|
+
settingsRouteLocation?: RouteLocation;
|
|
27
|
+
settingsAccountSection?: string;
|
|
25
28
|
settingsHideSession?: boolean;
|
|
26
29
|
settingsResolvePage?: SettingsPageResolver;
|
|
27
30
|
onSignOut?: () => void;
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
}"
|
|
147
147
|
>
|
|
148
148
|
<slot :name="`section-${active.id}`" v-bind="slotProps(active, close)">
|
|
149
|
-
<template v-if="active.id ===
|
|
149
|
+
<template v-if="active.id === accountSection && account">
|
|
150
150
|
<div class="k-settings-modal-account">
|
|
151
151
|
<Avatar
|
|
152
152
|
:name="account.name"
|
|
@@ -204,7 +204,11 @@ import { THEME_PRESETS } from "../../composables/usePreset";
|
|
|
204
204
|
import { useLocale } from "../../composables/useLocale";
|
|
205
205
|
import { useLinkResolver } from "../../internal/core/link";
|
|
206
206
|
import { useScrollEdges } from "../../internal/core/scroll-edges";
|
|
207
|
-
import {
|
|
207
|
+
import {
|
|
208
|
+
useDefaultRouteLocation,
|
|
209
|
+
useRouteValue,
|
|
210
|
+
writeRouteValue
|
|
211
|
+
} from "../../internal/layout/query";
|
|
208
212
|
import { useRouterLike } from "../../internal/layout/route";
|
|
209
213
|
import Button from "../core/Button.vue";
|
|
210
214
|
import Icon from "../core/Icon.vue";
|
|
@@ -236,6 +240,8 @@ const props = defineProps({
|
|
|
236
240
|
hideSession: { type: Boolean, required: false },
|
|
237
241
|
beforeClose: { type: Function, required: false },
|
|
238
242
|
route: { type: [String, Boolean], required: false, default: false },
|
|
243
|
+
routeLocation: { type: String, required: false, default: void 0 },
|
|
244
|
+
accountSection: { type: String, required: false, default: "account" },
|
|
239
245
|
resolvePage: { type: Function, required: false }
|
|
240
246
|
});
|
|
241
247
|
defineSlots();
|
|
@@ -263,7 +269,7 @@ const sectionList = computed(() => {
|
|
|
263
269
|
const list = [];
|
|
264
270
|
if (props.account)
|
|
265
271
|
list.push({
|
|
266
|
-
id:
|
|
272
|
+
id: props.accountSection,
|
|
267
273
|
label: props.accountLabel ?? t("account.label"),
|
|
268
274
|
icon: "user-circle",
|
|
269
275
|
description: props.accountText ?? t("settings.accountDescription")
|
|
@@ -313,7 +319,9 @@ function slotProps(item, close) {
|
|
|
313
319
|
}
|
|
314
320
|
const router = useRouterLike();
|
|
315
321
|
const routeKey = computed(() => props.route === true ? "settings" : props.route || void 0);
|
|
316
|
-
const
|
|
322
|
+
const defaultLocation = useDefaultRouteLocation();
|
|
323
|
+
const location = computed(() => props.routeLocation ?? defaultLocation);
|
|
324
|
+
const routeParam = useRouteValue(router, routeKey, location);
|
|
317
325
|
const syncing = computed(() => Boolean(router && routeKey.value));
|
|
318
326
|
let applying = false;
|
|
319
327
|
let resolution = 0;
|
|
@@ -331,7 +339,7 @@ function decodeSegment(segment) {
|
|
|
331
339
|
function writeRoute(value, history) {
|
|
332
340
|
const key = routeKey.value;
|
|
333
341
|
if (!router || !key || value === routeParam.value) return;
|
|
334
|
-
void
|
|
342
|
+
void writeRouteValue(router, key, value, history, location.value);
|
|
335
343
|
}
|
|
336
344
|
async function settle() {
|
|
337
345
|
await nextTick();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ThemePreset } from '../../composables/usePreset';
|
|
2
|
+
import type { RouteLocation } from '../../internal/layout/query';
|
|
2
3
|
import type { DialogBeforeClose } from '../overlays/types';
|
|
3
4
|
import type { SettingsModalAccount, SettingsModalPage, SettingsModalSection, SettingsPageResolver, SettingsSectionSlotProps } from './types';
|
|
4
5
|
type __VLS_Slots = {
|
|
@@ -33,6 +34,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
33
34
|
hideSession?: boolean;
|
|
34
35
|
beforeClose?: DialogBeforeClose;
|
|
35
36
|
route?: string | boolean;
|
|
37
|
+
routeLocation?: RouteLocation;
|
|
38
|
+
accountSection?: string;
|
|
36
39
|
resolvePage?: SettingsPageResolver;
|
|
37
40
|
open?: boolean | undefined;
|
|
38
41
|
section?: string;
|
|
@@ -277,6 +280,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
277
280
|
hideSession?: boolean;
|
|
278
281
|
beforeClose?: DialogBeforeClose;
|
|
279
282
|
route?: string | boolean;
|
|
283
|
+
routeLocation?: RouteLocation;
|
|
284
|
+
accountSection?: string;
|
|
280
285
|
resolvePage?: SettingsPageResolver;
|
|
281
286
|
open?: boolean | undefined;
|
|
282
287
|
section?: string;
|
|
@@ -506,6 +511,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
506
511
|
presetLabel: string;
|
|
507
512
|
presetText: string;
|
|
508
513
|
route: string | boolean;
|
|
514
|
+
routeLocation: RouteLocation;
|
|
515
|
+
accountSection: string;
|
|
509
516
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
510
517
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
511
518
|
declare const _default: typeof __VLS_export;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { Ref } from 'vue';
|
|
2
|
-
import type { RouteHistory } from '../internal/layout/query';
|
|
3
|
-
export type { RouteHistory };
|
|
2
|
+
import type { RouteHistory, RouteLocation } from '../internal/layout/query';
|
|
3
|
+
export type { RouteHistory, RouteLocation };
|
|
4
4
|
export interface RouteStateOptions<T> {
|
|
5
5
|
parse?: (value: string) => T | undefined;
|
|
6
6
|
serialize?: (value: T) => string | undefined;
|
|
7
7
|
history?: RouteHistory;
|
|
8
|
+
location?: RouteLocation;
|
|
8
9
|
default?: T;
|
|
9
10
|
}
|
|
10
11
|
export declare function useRouteState<T = string>(key: string, options: RouteStateOptions<T> & {
|
|
11
12
|
default: T;
|
|
12
13
|
}): Ref<T>;
|
|
13
14
|
export declare function useRouteState<T = string>(key: string, options?: RouteStateOptions<T>): Ref<T | undefined>;
|
|
15
|
+
export declare function withRouteStateScroll<R extends {
|
|
16
|
+
hash?: string;
|
|
17
|
+
}, A extends unknown[], Result>(scrollBehavior: (to: R, from: R, ...rest: A) => Result): (to: R, from: R, ...rest: A) => Result;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { computed, shallowRef, watch } from "vue";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isRouteStateHash,
|
|
4
|
+
useDefaultRouteLocation,
|
|
5
|
+
useRouteValue,
|
|
6
|
+
writeRouteValue
|
|
7
|
+
} from "../internal/layout/query.mjs";
|
|
3
8
|
import { useRouterLike } from "../internal/layout/route.mjs";
|
|
4
9
|
export function useRouteState(key, options = {}) {
|
|
5
10
|
const router = useRouterLike();
|
|
6
|
-
const
|
|
11
|
+
const location = options.location ?? useDefaultRouteLocation();
|
|
12
|
+
const raw = useRouteValue(router, key, location);
|
|
7
13
|
const fallback = options.default;
|
|
8
14
|
const parse = options.parse ?? ((value) => value);
|
|
9
15
|
const serialize = options.serialize ?? ((value) => String(value));
|
|
@@ -29,9 +35,16 @@ export function useRouteState(key, options = {}) {
|
|
|
29
35
|
state.value = value;
|
|
30
36
|
if (!router) return;
|
|
31
37
|
const current = ++version;
|
|
32
|
-
|
|
38
|
+
const history = options.history ?? "push";
|
|
39
|
+
void writeRouteValue(router, key, toRaw(value), history, location).then((ok) => {
|
|
33
40
|
if (!ok && current === version) state.value = fromRaw(raw.value);
|
|
34
41
|
});
|
|
35
42
|
}
|
|
36
43
|
});
|
|
37
44
|
}
|
|
45
|
+
function withoutStateHash(route) {
|
|
46
|
+
return route.hash && isRouteStateHash(route.hash) ? { ...route, hash: "" } : route;
|
|
47
|
+
}
|
|
48
|
+
export function withRouteStateScroll(scrollBehavior) {
|
|
49
|
+
return (to, from, ...rest) => scrollBehavior(withoutStateHash(to), withoutStateHash(from), ...rest);
|
|
50
|
+
}
|
package/dist/grid/DataGrid.vue
CHANGED
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
@filters-reset="emit('filtersReset')"
|
|
28
28
|
>
|
|
29
29
|
<slot name="actions" :api="api" v-bind="sheetSlot" />
|
|
30
|
+
<template v-if="$slots['actions-end']" #end>
|
|
31
|
+
<slot name="actions-end" :api="api" v-bind="sheetSlot" />
|
|
32
|
+
</template>
|
|
30
33
|
<template v-if="$slots.filters" #filters="scope">
|
|
31
34
|
<slot name="filters" v-bind="scope" />
|
|
32
35
|
</template>
|
|
@@ -109,6 +109,31 @@ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_se
|
|
|
109
109
|
clearDirty: (rowIds?: string[]) => void;
|
|
110
110
|
api: GridApi<T> | null;
|
|
111
111
|
}) => any;
|
|
112
|
+
} & {
|
|
113
|
+
'actions-end'?: (props: {
|
|
114
|
+
range: GridCellRange | null;
|
|
115
|
+
selectedCellCount: number;
|
|
116
|
+
dirtyCount: number;
|
|
117
|
+
canUndo: boolean;
|
|
118
|
+
canRedo: boolean;
|
|
119
|
+
getRange: () => GridCellRange | null;
|
|
120
|
+
getSelectedCells: () => import("./types").GridSelectedCell<T>[];
|
|
121
|
+
selectRange: (range: import("./types").GridRangeInput) => void;
|
|
122
|
+
selectAll: () => void;
|
|
123
|
+
clearRange: () => void;
|
|
124
|
+
setRangeValue: (value: unknown, options?: import("./types").GridSetRangeValueOptions) => number;
|
|
125
|
+
copyRange: () => Promise<string | null>;
|
|
126
|
+
paste: (text: string) => number;
|
|
127
|
+
fillDown: () => number;
|
|
128
|
+
fillRight: () => number;
|
|
129
|
+
clearCells: () => number;
|
|
130
|
+
undo: () => boolean;
|
|
131
|
+
redo: () => boolean;
|
|
132
|
+
clearHistory: () => void;
|
|
133
|
+
getDirtyCells: () => import("./types").GridDirtyCell[];
|
|
134
|
+
clearDirty: (rowIds?: string[]) => void;
|
|
135
|
+
api: GridApi<T> | null;
|
|
136
|
+
}) => any;
|
|
112
137
|
} & {
|
|
113
138
|
filters?: (props: {
|
|
114
139
|
close: () => void;
|
|
@@ -3,11 +3,13 @@ declare function closeFilters(): void;
|
|
|
3
3
|
declare var __VLS_28: {
|
|
4
4
|
close: typeof closeFilters;
|
|
5
5
|
count: number;
|
|
6
|
-
}, __VLS_45: {};
|
|
6
|
+
}, __VLS_45: {}, __VLS_65: {};
|
|
7
7
|
type __VLS_Slots = {} & {
|
|
8
8
|
filters?: (props: typeof __VLS_28) => any;
|
|
9
9
|
} & {
|
|
10
10
|
default?: (props: typeof __VLS_45) => any;
|
|
11
|
+
} & {
|
|
12
|
+
end?: (props: typeof __VLS_65) => any;
|
|
11
13
|
};
|
|
12
14
|
declare const __VLS_base: import("vue").DefineComponent<{
|
|
13
15
|
api?: import("ag-grid-community").GridApi | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,5 +11,5 @@ export { builtinLocales, de, defineLocale, en, it } from './locale';
|
|
|
11
11
|
export type { DefineLocaleOptions } from './locale';
|
|
12
12
|
export type * from './locale/types';
|
|
13
13
|
export type { BuiltinIconName } from './icons/builtin';
|
|
14
|
-
export { FIELD_KEY, FORM_CONFIG_KEY, FORM_KEY, ICON_USAGE_KEY, ICONS_KEY, LINK_KEY, LOCALE_KEY, PRESET_KEY, THEME_KEY, } from './internal/keys';
|
|
14
|
+
export { FIELD_KEY, FORM_CONFIG_KEY, FORM_KEY, ICON_USAGE_KEY, ICONS_KEY, LINK_KEY, LOCALE_KEY, PRESET_KEY, ROUTE_LOCATION_KEY, THEME_KEY, } from './internal/keys';
|
|
15
15
|
export type * from './types/dom';
|
package/dist/index.mjs
CHANGED
package/dist/internal/keys.d.ts
CHANGED
|
@@ -16,3 +16,4 @@ export declare const ROOT_KEY: InjectionKey<true>;
|
|
|
16
16
|
export declare const FORM_KEY: InjectionKey<FormContext>;
|
|
17
17
|
export declare const FORM_CONFIG_KEY: InjectionKey<FormConfig>;
|
|
18
18
|
export declare const LOCALE_KEY: InjectionKey<ComputedRef<KLocale>>;
|
|
19
|
+
export declare const ROUTE_LOCATION_KEY: InjectionKey<'query' | 'hash'>;
|
package/dist/internal/keys.mjs
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { MaybeRefOrGetter } from 'vue';
|
|
2
2
|
import type { RouterLike } from './route';
|
|
3
3
|
export type RouteHistory = 'push' | 'replace';
|
|
4
|
+
export type RouteLocation = 'query' | 'hash';
|
|
5
|
+
type HashEntry = [key: string, value: string | undefined];
|
|
4
6
|
export declare function firstQueryValue(value: unknown): string | undefined;
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
7
|
+
export declare function parseRouteHash(hash: string | undefined): HashEntry[];
|
|
8
|
+
export declare function stringifyRouteHash(entries: HashEntry[]): string;
|
|
9
|
+
export declare function isRouteStateHash(hash: string | undefined): boolean;
|
|
10
|
+
export declare function hashParamValue(hash: string | undefined, key: string): string | undefined;
|
|
11
|
+
export declare function useDefaultRouteLocation(): RouteLocation;
|
|
12
|
+
export declare function useRouteValue(router: RouterLike | undefined, key: MaybeRefOrGetter<string | undefined>, location?: MaybeRefOrGetter<RouteLocation>): import("vue").ComputedRef<string | undefined>;
|
|
13
|
+
export declare function writeRouteValue(router: RouterLike, key: string, value: string | undefined, history: RouteHistory, location?: RouteLocation): Promise<boolean>;
|
|
14
|
+
export {};
|
|
@@ -1,15 +1,59 @@
|
|
|
1
|
-
import { computed, toValue } from "vue";
|
|
1
|
+
import { computed, getCurrentInstance, inject, onMounted, shallowRef, toValue } from "vue";
|
|
2
2
|
import { isClient } from "../core/env.mjs";
|
|
3
|
+
import { ROUTE_LOCATION_KEY } from "../keys.mjs";
|
|
3
4
|
const batches = /* @__PURE__ */ new WeakMap();
|
|
4
5
|
export function firstQueryValue(value) {
|
|
5
6
|
if (Array.isArray(value)) return firstQueryValue(value[0]);
|
|
6
7
|
return typeof value === "string" ? value : void 0;
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
+
function escapeHashValue(value) {
|
|
10
|
+
return value.replace(/[%&]/g, (char) => char === "%" ? "%25" : "%26");
|
|
11
|
+
}
|
|
12
|
+
function unescapeHashValue(value) {
|
|
13
|
+
return value.replace(/%(25|26)/g, (_, code) => code === "25" ? "%" : "&");
|
|
14
|
+
}
|
|
15
|
+
export function parseRouteHash(hash) {
|
|
16
|
+
const text = (hash ?? "").replace(/^#/, "");
|
|
17
|
+
if (!text) return [];
|
|
18
|
+
return text.split("&").map((part) => {
|
|
19
|
+
const index = part.indexOf("/");
|
|
20
|
+
if (index <= 0) return [part, void 0];
|
|
21
|
+
return [part.slice(0, index), unescapeHashValue(part.slice(index + 1))];
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function stringifyRouteHash(entries) {
|
|
25
|
+
const text = entries.filter(([key]) => key !== "").map(([key, value]) => value === void 0 ? key : `${key}/${escapeHashValue(value)}`).join("&");
|
|
26
|
+
return text ? `#${text}` : "";
|
|
27
|
+
}
|
|
28
|
+
export function isRouteStateHash(hash) {
|
|
29
|
+
return parseRouteHash(hash).every(([, value]) => value !== void 0);
|
|
30
|
+
}
|
|
31
|
+
export function hashParamValue(hash, key) {
|
|
32
|
+
for (const [name, value] of parseRouteHash(hash)) if (name === key && value) return value;
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
export function useDefaultRouteLocation() {
|
|
36
|
+
if (!getCurrentInstance()) return "query";
|
|
37
|
+
return inject(ROUTE_LOCATION_KEY, "query");
|
|
38
|
+
}
|
|
39
|
+
function useHashReady() {
|
|
40
|
+
const ready = shallowRef(false);
|
|
41
|
+
if (getCurrentInstance())
|
|
42
|
+
onMounted(() => {
|
|
43
|
+
ready.value = true;
|
|
44
|
+
});
|
|
45
|
+
else if (isClient()) ready.value = true;
|
|
46
|
+
return ready;
|
|
47
|
+
}
|
|
48
|
+
export function useRouteValue(router, key, location = "query") {
|
|
49
|
+
const hashReady = useHashReady();
|
|
9
50
|
return computed(() => {
|
|
10
51
|
const name = toValue(key);
|
|
11
52
|
if (!router || !name) return void 0;
|
|
12
|
-
|
|
53
|
+
const route = router.currentRoute?.value;
|
|
54
|
+
if (toValue(location) === "hash")
|
|
55
|
+
return hashReady.value ? hashParamValue(route?.hash, name) : void 0;
|
|
56
|
+
return firstQueryValue(route?.query?.[name]);
|
|
13
57
|
});
|
|
14
58
|
}
|
|
15
59
|
function isPreviousEntry(router, location) {
|
|
@@ -18,12 +62,10 @@ function isPreviousEntry(router, location) {
|
|
|
18
62
|
if (typeof back !== "string") return false;
|
|
19
63
|
return router.resolve(location).fullPath === back;
|
|
20
64
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const route = router.currentRoute?.value;
|
|
24
|
-
const query = { ...route?.query };
|
|
65
|
+
function applyQuery(current, changes) {
|
|
66
|
+
const query = { ...current };
|
|
25
67
|
let changed = false;
|
|
26
|
-
for (const [key, value] of
|
|
68
|
+
for (const [key, value] of changes) {
|
|
27
69
|
if (value === void 0) {
|
|
28
70
|
if (!(key in query)) continue;
|
|
29
71
|
delete query[key];
|
|
@@ -33,8 +75,36 @@ async function flush(router, batch) {
|
|
|
33
75
|
changed = true;
|
|
34
76
|
}
|
|
35
77
|
}
|
|
36
|
-
|
|
37
|
-
|
|
78
|
+
return { query, changed };
|
|
79
|
+
}
|
|
80
|
+
function applyHash(current, changes) {
|
|
81
|
+
let entries = parseRouteHash(current);
|
|
82
|
+
let changed = false;
|
|
83
|
+
for (const [key, value] of changes) {
|
|
84
|
+
const index = entries.findIndex(([name, entry]) => name === key && entry !== void 0);
|
|
85
|
+
if (value === void 0) {
|
|
86
|
+
if (index < 0) continue;
|
|
87
|
+
entries = entries.filter((_, position) => position !== index);
|
|
88
|
+
changed = true;
|
|
89
|
+
} else if (index < 0) {
|
|
90
|
+
entries = [...entries, [key, value]];
|
|
91
|
+
changed = true;
|
|
92
|
+
} else if (entries[index][1] !== value) {
|
|
93
|
+
entries = entries.map(
|
|
94
|
+
(entry, position) => position === index ? [key, value] : entry
|
|
95
|
+
);
|
|
96
|
+
changed = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { hash: changed ? stringifyRouteHash(entries) : current, changed };
|
|
100
|
+
}
|
|
101
|
+
async function flush(router, batch) {
|
|
102
|
+
batches.delete(router);
|
|
103
|
+
const route = router.currentRoute?.value;
|
|
104
|
+
const nextQuery = applyQuery(route?.query ?? {}, batch.query);
|
|
105
|
+
const nextHash = applyHash(route?.hash ?? "", batch.hash);
|
|
106
|
+
if (!nextQuery.changed && !nextHash.changed) return true;
|
|
107
|
+
const location = { path: route?.path, query: nextQuery.query, hash: nextHash.hash };
|
|
38
108
|
try {
|
|
39
109
|
if (batch.history === "push" && isPreviousEntry(router, location)) {
|
|
40
110
|
router.back?.();
|
|
@@ -46,19 +116,20 @@ async function flush(router, batch) {
|
|
|
46
116
|
return false;
|
|
47
117
|
}
|
|
48
118
|
}
|
|
49
|
-
export function
|
|
119
|
+
export function writeRouteValue(router, key, value, history, location = "query") {
|
|
50
120
|
if (!isClient()) return Promise.resolve(true);
|
|
51
121
|
let batch = batches.get(router);
|
|
52
122
|
if (!batch) {
|
|
53
123
|
const created = {
|
|
54
|
-
|
|
124
|
+
query: /* @__PURE__ */ new Map(),
|
|
125
|
+
hash: /* @__PURE__ */ new Map(),
|
|
55
126
|
history,
|
|
56
127
|
promise: Promise.resolve().then(() => flush(router, created))
|
|
57
128
|
};
|
|
58
129
|
batch = created;
|
|
59
130
|
batches.set(router, batch);
|
|
60
131
|
}
|
|
61
|
-
batch.
|
|
132
|
+
batch[location].set(key, value === "" ? void 0 : value);
|
|
62
133
|
if (history === "push") batch.history = "push";
|
|
63
134
|
return batch.promise;
|
|
64
135
|
}
|
package/dist/nuxt/module.d.ts
CHANGED
|
@@ -41,6 +41,9 @@ export interface ModuleOptions {
|
|
|
41
41
|
fonts: boolean | ThemePreset[];
|
|
42
42
|
locale: string;
|
|
43
43
|
messages: Record<string, DeepPartial<KMessages>>;
|
|
44
|
+
routeState: {
|
|
45
|
+
location?: 'query' | 'hash';
|
|
46
|
+
};
|
|
44
47
|
}
|
|
45
48
|
export interface KRuntimeConfig {
|
|
46
49
|
global: boolean;
|
|
@@ -50,6 +53,7 @@ export interface KRuntimeConfig {
|
|
|
50
53
|
presetDefault: ThemePreset;
|
|
51
54
|
locale: string;
|
|
52
55
|
messages: Record<string, DeepPartial<KMessages>>;
|
|
56
|
+
routeLocation: 'query' | 'hash';
|
|
53
57
|
}
|
|
54
58
|
declare module '@nuxt/schema' {
|
|
55
59
|
interface PublicRuntimeConfig {
|
package/dist/nuxt/module.mjs
CHANGED
|
@@ -46,7 +46,10 @@ export default defineNuxtModule({
|
|
|
46
46
|
},
|
|
47
47
|
fonts: true,
|
|
48
48
|
locale: "en",
|
|
49
|
-
messages: {}
|
|
49
|
+
messages: {},
|
|
50
|
+
routeState: {
|
|
51
|
+
location: "query"
|
|
52
|
+
}
|
|
50
53
|
},
|
|
51
54
|
async setup(options, nuxt) {
|
|
52
55
|
const resolver = createResolver(import.meta.url);
|
|
@@ -115,12 +118,14 @@ export default defineNuxtModule({
|
|
|
115
118
|
presetCookie: options.preset.cookie ?? "k-preset",
|
|
116
119
|
presetDefault,
|
|
117
120
|
locale: options.locale,
|
|
118
|
-
messages: options.messages
|
|
121
|
+
messages: options.messages,
|
|
122
|
+
routeLocation: options.routeState.location === "hash" ? "hash" : "query"
|
|
119
123
|
};
|
|
120
124
|
Object.assign(nuxt.options.runtimeConfig.public, { k: runtimeConfig });
|
|
121
125
|
addPlugin(resolver.resolve("./runtime/theme"));
|
|
122
126
|
addPlugin(resolver.resolve("./runtime/storage"));
|
|
123
127
|
addPlugin(resolver.resolve("./runtime/locale"));
|
|
128
|
+
addPlugin(resolver.resolve("./runtime/route-state"));
|
|
124
129
|
if (options.composables)
|
|
125
130
|
addImports({ name: "useDashboard", from: resolver.resolve("./runtime/dashboard") });
|
|
126
131
|
const layouts = resolveLayouts(options.layouts);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useRouter, useRuntimeConfig } from "nuxt/app";
|
|
2
|
+
import { withRouteStateScroll } from "../../composables/useRouteState.mjs";
|
|
3
|
+
import { ROUTE_LOCATION_KEY } from "../../internal/keys.mjs";
|
|
4
|
+
export default defineNuxtPlugin({
|
|
5
|
+
name: "k:route-state",
|
|
6
|
+
setup(nuxtApp) {
|
|
7
|
+
const config = useRuntimeConfig().public.k ?? {};
|
|
8
|
+
nuxtApp.vueApp.provide(ROUTE_LOCATION_KEY, config.routeLocation === "hash" ? "hash" : "query");
|
|
9
|
+
if (import.meta.server) return;
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
const scrollBehavior = router.options.scrollBehavior;
|
|
12
|
+
if (scrollBehavior) router.options.scrollBehavior = withRouteStateScroll(scrollBehavior);
|
|
13
|
+
}
|
|
14
|
+
});
|
package/package.json
CHANGED