@antelopejs/dms 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/hooks/tenant-instances.d.ts +1 -0
  3. package/dist/hooks/tenant-instances.js +13 -0
  4. package/dist/hooks/tenant-instances.js.map +1 -0
  5. package/dist/index.js +3 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/test/unit/interfaces/dms/page-topics.test.js +67 -0
  8. package/dist/test/unit/interfaces/dms/page-topics.test.js.map +1 -1
  9. package/dist/test/unit/interfaces/dms-base/block-vocabulary.test.js +1 -0
  10. package/dist/test/unit/interfaces/dms-base/block-vocabulary.test.js.map +1 -1
  11. package/dist/test/unit/interfaces/dms-base/code-block-catalog.test.d.ts +1 -0
  12. package/dist/test/unit/interfaces/dms-base/code-block-catalog.test.js +46 -0
  13. package/dist/test/unit/interfaces/dms-base/code-block-catalog.test.js.map +1 -0
  14. package/dist/test/unit/interfaces/dms-base/column-display.test.js +11 -0
  15. package/dist/test/unit/interfaces/dms-base/column-display.test.js.map +1 -1
  16. package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js +31 -0
  17. package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js.map +1 -1
  18. package/dist/test/unit/utils/tenant-instances.test.d.ts +1 -0
  19. package/dist/test/unit/utils/tenant-instances.test.js +53 -0
  20. package/dist/test/unit/utils/tenant-instances.test.js.map +1 -0
  21. package/dist/utils/index.d.ts +1 -0
  22. package/dist/utils/index.js +1 -0
  23. package/dist/utils/index.js.map +1 -1
  24. package/dist/utils/tenant-instances.d.ts +2 -0
  25. package/dist/utils/tenant-instances.js +38 -0
  26. package/dist/utils/tenant-instances.js.map +1 -0
  27. package/dist/validation/auth.schema.d.ts +2 -2
  28. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +4 -0
  29. package/frontend-vue/layers/dms-ui/app/build/components/activity-feed/activityFeedDays.ts +35 -23
  30. package/frontend-vue/layers/dms-ui/app/build/components/blocks/BlockActions.vue +34 -6
  31. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +26 -8
  32. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +3 -1
  33. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +1 -1
  34. package/frontend-vue/layers/dms-ui/app/build/components/table/MonoCell.vue +3 -2
  35. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +24 -13
  36. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +1 -1
  37. package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useBlockItems.ts +12 -5
  38. package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useRefreshTriggers.ts +40 -0
  39. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +15 -4
  40. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +85 -22
  41. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/clearTableFilters.ts +10 -2
  42. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/usePersistedPagination.ts +79 -0
  43. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +94 -0
  44. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +41 -5
  45. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +3 -0
  46. package/frontend-vue/layers/dms-ui/app/build/utils/codeEditor.ts +2 -13
  47. package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +59 -0
  48. package/frontend-vue/layers/dms-ui/app/build/utils/codeLanguages.ts +36 -0
  49. package/frontend-vue/layers/dms-ui/app/build/utils/codePalette.ts +32 -0
  50. package/frontend-vue/layers/dms-ui/app/build/utils/shellParser.ts +55 -0
  51. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +77 -20
  52. package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +15 -4
  53. package/frontend-vue/layers/dms-ui/app/components/blocks/BannerBlock.vue +111 -25
  54. package/frontend-vue/layers/dms-ui/app/components/blocks/CodeBlock.vue +95 -0
  55. package/frontend-vue/layers/dms-ui/app/components/blocks/EmptyStateBlock.vue +18 -0
  56. package/frontend-vue/layers/dms-ui/app/components/blocks/KeyValueListBlock.vue +8 -0
  57. package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +21 -9
  58. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +8 -0
  59. package/frontend-vue/layers/dms-ui/app/components/code-snippet/CodeSnippet.vue +97 -0
  60. package/frontend-vue/layers/dms-ui/app/components/empty-state/EmptyState.vue +13 -4
  61. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +14 -3
  62. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +2 -8
  63. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +19 -0
  64. package/frontend-vue/layers/dms-ui/app/components/master-detail/MasterDetail.vue +54 -48
  65. package/frontend-vue/layers/dms-ui/app/components/meter/MeterBlock.vue +24 -9
  66. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +4 -1
  67. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +66 -74
  68. package/frontend-vue/layers/dms-ui/app/components/top-list/internal/TopListRow.vue +4 -3
  69. package/frontend-vue/layers/dms-ui/app/composables/chart/types.ts +5 -2
  70. package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +2 -29
  71. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +2 -1
  72. package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +2 -0
  73. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +4 -0
  74. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +2 -2
  75. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +1 -0
  76. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +1 -0
  77. package/frontend-vue/package.json +4 -3
  78. package/frontend-vue/pnpm-lock.yaml +8 -5
  79. package/frontend-vue/tests/activity-feed-days.test.ts +26 -12
  80. package/frontend-vue/tests/banner-block-fetch.test.ts +162 -0
  81. package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +100 -0
  82. package/frontend-vue/tests/block-items-period-realtime.test.ts +147 -0
  83. package/frontend-vue/tests/block-route-params.test.ts +48 -0
  84. package/frontend-vue/tests/code-block.test.ts +155 -0
  85. package/frontend-vue/tests/code-highlight.test.ts +72 -0
  86. package/frontend-vue/tests/composed-text-blocks.test.ts +5 -0
  87. package/frontend-vue/tests/composed-text-more-blocks.test.ts +391 -0
  88. package/frontend-vue/tests/drawer-target-direction.test.ts +205 -0
  89. package/frontend-vue/tests/empty-state-late-slots.test.ts +53 -0
  90. package/frontend-vue/tests/flow-canvas-delete-badge.test.ts +88 -0
  91. package/frontend-vue/tests/form-redirect-placeholders.test.ts +11 -0
  92. package/frontend-vue/tests/key-value-list-copy.test.ts +114 -0
  93. package/frontend-vue/tests/master-detail-layout.test.ts +53 -0
  94. package/frontend-vue/tests/mono-cell-truncate.test.ts +50 -0
  95. package/frontend-vue/tests/nav-card-grid-empty-category.test.ts +1 -0
  96. package/frontend-vue/tests/page-fill-height.test.ts +20 -3
  97. package/frontend-vue/tests/route-param-occurrences.test.ts +9 -0
  98. package/frontend-vue/tests/table-actions-search-label.test.ts +147 -0
  99. package/frontend-vue/tests/table-clear-filters.test.ts +22 -0
  100. package/frontend-vue/tests/table-empty-body-viewport.test.ts +25 -0
  101. package/frontend-vue/tests/table-metric-cells.test.ts +22 -0
  102. package/frontend-vue/tests/table-pills-cell.test.ts +126 -0
  103. package/frontend-vue/tests/table-source-navigation.test.ts +123 -0
  104. package/frontend-vue/tests/table-view-hidden-filters-page.test.ts +137 -0
  105. package/frontend-vue/tests/table-view-refresh.test.ts +84 -0
  106. package/frontend-vue/tests/table-view-sources.test.ts +67 -0
  107. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  108. package/package.json +2 -2
  109. package/skills/dms/SKILL.md +14 -3
  110. package/skills/dms-pages/REFERENCE.md +17 -1
@@ -47,7 +47,6 @@ import {
47
47
  } from "../../composables/table-view/kanban";
48
48
  import { useTableRowActions } from "../../build/composables/table-view/useTableViewRowActions";
49
49
  import {
50
- buildTableDataKey,
51
50
  buildTableQuery,
52
51
  isFilterEffective,
53
52
  } from "../../build/composables/table-view/utils/tableQuery";
@@ -107,7 +106,9 @@ import { useTableViews } from "../../build/composables/table-view/useTableViews"
107
106
  import { useGroupedRows } from "../../build/composables/table-view/useGroupedRows";
108
107
  import { useTableFooter } from "../../build/composables/table-view/useTableFooter";
109
108
  import { useAccumulatedPages } from "../../build/composables/table-view/useAccumulatedPages";
109
+ import { usePersistedPagination } from "../../build/composables/table-view/usePersistedPagination";
110
110
  import { useTableRows } from "../../build/composables/table-view/useTableRows";
111
+ import { useRefreshTriggers } from "../../build/composables/blocks/useRefreshTriggers";
111
112
  import { useTableReorder } from "../../build/composables/table-view/useTableReorder";
112
113
  import {
113
114
  groupedOptionsFor,
@@ -118,7 +119,10 @@ import { readTableUrlKey } from "../../build/composables/table-view/utils/views"
118
119
  import TableViews, {
119
120
  type TableViewItem,
120
121
  } from "../../build/components/table/Views.vue";
121
- import { useServerRenderedAsyncData } from "../../build/composables/table-view/useServerRenderedAsyncData";
122
+ import {
123
+ useTableCountsData,
124
+ useTableListData,
125
+ } from "../../build/composables/table-view/useTableListData";
122
126
  import { useTableDataChanges } from "../../composables/table-view/useTableDataChanges";
123
127
 
124
128
  const REALTIME_ROW_TOPIC_PREFIX = "tableview:row:";
@@ -172,6 +176,8 @@ interface TableViewProps<T extends Data> extends TableViewConfig<T> {
172
176
  tableId?: string;
173
177
  /** The page carries no other table view: `?view=` / `?tab=` are its own. */
174
178
  isSoleTableView?: boolean;
179
+ /** Topics whose events make the table read its rows again. */
180
+ realtimeTopic?: string | string[];
175
181
  }
176
182
 
177
183
  const props = defineProps<TableViewProps<T>>();
@@ -347,12 +353,42 @@ const resolvedChrome = resolveTableChrome(props.layout, {
347
353
  });
348
354
  const GLOBAL_FILTER_DEBOUNCE_MS = 400;
349
355
 
350
- const paginationState = ref<PaginationState>(
351
- getPreference<PaginationState>(
352
- getTablePreferenceKey("pagination"),
353
- DEFAULT_PAGINATION,
354
- ),
355
- );
356
+ const route = useDmsRoute();
357
+
358
+ const DEFAULT_FILTER_MODE = "is";
359
+
360
+ const queryParamHiddenFilters = computed<TableFilter[]>(() => {
361
+ if (!queryParamFilters) return [];
362
+
363
+ return Object.entries(queryParamFilters)
364
+ .filter(([param]) => route.query[param] !== undefined)
365
+ .map(([param, config]) => ({
366
+ accessorKey: config.field,
367
+ mode: config.mode || DEFAULT_FILTER_MODE,
368
+ value: route.query[param] as string,
369
+ }));
370
+ });
371
+
372
+ const routeParamHiddenFilters = computed<TableFilter[]>(() => {
373
+ if (!routeParamFilters || !routeParams) return [];
374
+
375
+ return Object.entries(routeParamFilters)
376
+ .filter(([param]) => routeParams[param] !== undefined)
377
+ .map(([param, config]) => ({
378
+ accessorKey: config.field,
379
+ mode: config.mode || DEFAULT_FILTER_MODE,
380
+ value: routeParams[param] as string,
381
+ }));
382
+ });
383
+
384
+ // A hidden filter's value changed with the URL, in place or through a
385
+ // remount: the page reached among the previous rows lists from page 1.
386
+ const paginationState = usePersistedPagination({
387
+ preferences: { getPreference, setPreference },
388
+ preferenceKey: getTablePreferenceKey,
389
+ defaults: DEFAULT_PAGINATION,
390
+ scope: () => [queryParamHiddenFilters.value, routeParamHiddenFilters.value],
391
+ });
356
392
  const rowSelect = ref<RowSelectionState>({});
357
393
  // A column's grid header, for the sort messages.
358
394
  const columnLabel = (id: string): string => {
@@ -615,35 +651,11 @@ const tableRows = useTableRows<T>({
615
651
  location,
616
652
  source: props.source,
617
653
  columns: props.columns,
654
+ route: () => ({
655
+ routeParams: props.routeParams,
656
+ routeQuery: route.query as Record<string, unknown>,
657
+ }),
618
658
  });
619
- const route = useDmsRoute();
620
-
621
- const DEFAULT_FILTER_MODE = "is";
622
-
623
- const queryParamHiddenFilters = computed<TableFilter[]>(() => {
624
- if (!queryParamFilters) return [];
625
-
626
- return Object.entries(queryParamFilters)
627
- .filter(([param]) => route.query[param] !== undefined)
628
- .map(([param, config]) => ({
629
- accessorKey: config.field,
630
- mode: config.mode || DEFAULT_FILTER_MODE,
631
- value: route.query[param] as string,
632
- }));
633
- });
634
-
635
- const routeParamHiddenFilters = computed<TableFilter[]>(() => {
636
- if (!routeParamFilters || !routeParams) return [];
637
-
638
- return Object.entries(routeParamFilters)
639
- .filter(([param]) => routeParams[param] !== undefined)
640
- .map(([param, config]) => ({
641
- accessorKey: config.field,
642
- mode: config.mode || DEFAULT_FILTER_MODE,
643
- value: routeParams[param] as string,
644
- }));
645
- });
646
-
647
659
  // Quick filters: dropdowns of the toolbar over a column's values (a select's
648
660
  // items, a boolean, the rows a relation points to), writing that column's
649
661
  // filter like the filters row does.
@@ -788,34 +800,15 @@ const archiveQuery = computed(() =>
788
800
  archiveMode ? { showArchived: showArchived.value } : {},
789
801
  );
790
802
 
791
- const EMPTY_LIST_RESULT: TableViewListResponse<never> = {
792
- results: [],
793
- total: 0,
794
- offset: 0,
795
- limit: 0,
796
- };
797
-
798
- const tableDataKey = buildTableDataKey({
803
+ const { data, status, error, refresh } = await useTableListData<T>({
799
804
  componentId,
800
805
  pageId,
801
- query: queryRequest.value,
802
- archiveQuery: archiveQuery.value,
803
- isSelfManaged: isActiveDisplaySelfManaged.value,
806
+ rows: tableRows,
807
+ query: queryRequest,
808
+ archiveQuery,
809
+ isSelfManaged: isActiveDisplaySelfManaged,
804
810
  });
805
811
 
806
- const { data, status, error, refresh } = await useServerRenderedAsyncData(
807
- tableDataKey,
808
- (): Promise<TableViewListResponse<T>> => {
809
- // Self-managed displays (e.g. kanban) fetch their own data; skip the
810
- // shared list query for them.
811
- if (isActiveDisplaySelfManaged.value) {
812
- return Promise.resolve(EMPTY_LIST_RESULT);
813
- }
814
- return tableRows.list({ ...queryRequest.value, ...archiveQuery.value });
815
- },
816
- { watch: [queryRequest, archiveQuery, isActiveDisplaySelfManaged] },
817
- );
818
-
819
812
  // Nothing listed yet (a client navigation paints before the first page
820
813
  // arrives): the table draws its skeleton, never the empty state.
821
814
  const isFirstPageLoading = computed(() => data.value === null && !error.value);
@@ -949,19 +942,13 @@ const tabCountsQuery = computed(() => [
949
942
  // No default: `null` until the counts arrive, so the tabs draw placeholders
950
943
  // instead of a count of nothing.
951
944
  const { data: tabCountsData, refresh: refreshTabCounts } =
952
- await useServerRenderedAsyncData<Record<string, number>>(
953
- `table-view-${componentId}-${pageId}-tab-counts`,
954
- async () => {
955
- if (tabCountsQuery.value.length === 0) return {};
956
- return await tableRows.countBatch(
957
- tabCountsQuery.value.map(({ id, query }) => ({
958
- id,
959
- query: { ...query, ...archiveQuery.value },
960
- })),
961
- );
962
- },
963
- { watch: [tabCountsQuery, archiveQuery] },
964
- );
945
+ await useTableCountsData<T>({
946
+ componentId,
947
+ pageId,
948
+ rows: tableRows,
949
+ queries: tabCountsQuery,
950
+ archiveQuery,
951
+ });
965
952
 
966
953
  const viewItems = computed<TableViewItem[]>(() =>
967
954
  tableViewItems.value.map((view) => ({
@@ -1131,6 +1118,11 @@ const refreshAll = async () => {
1131
1118
  ]);
1132
1119
  };
1133
1120
 
1121
+ // The rows can move without the table seeing it (a batch, a job, another
1122
+ // block's action): a topic of the backend or a page refresh reads them again,
1123
+ // keeping the current rows on screen until the answer lands.
1124
+ useRefreshTriggers(props.realtimeTopic, () => void refreshAll());
1125
+
1134
1126
  // "Preview as role": a link tab to a page the previewed role could not open
1135
1127
  // is drawn locked, one to a page it opens partially is drawn partially
1136
1128
  // locked. Never outside a preview (`entryState` is null then).
@@ -1847,7 +1839,6 @@ defineShortcuts(
1847
1839
 
1848
1840
  const tableStatePreferences = {
1849
1841
  sorting,
1850
- pagination: paginationState,
1851
1842
  columnFilters: columnFilters,
1852
1843
  columnVisibility,
1853
1844
  filtersOpen: filtersRowOpen,
@@ -1980,6 +1971,7 @@ onMounted(() => {
1980
1971
  :quick-filters="resolvedQuickFilters"
1981
1972
  :footer="resolvedFooter"
1982
1973
  :empty-states="props.emptyStates"
1974
+ :hidden-filters-apply="queryParamHiddenFilters.length > 0"
1983
1975
  :default-page-size="props.pageSize"
1984
1976
  :archive-toggle="canToggleArchived"
1985
1977
  :displays="availableDisplays"
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from "vue";
3
3
  import type { TopListItem } from "../../../composables/chart/types";
4
+ import { useComposedText } from "../../../../../dms-core/app/composables/translation/useComposedText";
4
5
  // Imported rather than resolved from the registry: a registered component is
5
6
  // a lazy chunk of its own, fetched only when the data first shows it, so the
6
7
  // trend would pop in a beat after the row.
@@ -31,11 +32,11 @@ const props = withDefaults(defineProps<Props>(), {
31
32
 
32
33
  const PERCENT_UNIT = "%";
33
34
 
34
- const { processI18n } = useTranslation();
35
+ const { processText } = useComposedText();
35
36
 
36
- const titleDisplay = computed(() => processI18n(props.item.title));
37
+ const titleDisplay = computed(() => processText(props.item.title));
37
38
  const descriptionDisplay = computed(() =>
38
- props.item.description ? processI18n(props.item.description) : "",
39
+ props.item.description ? processText(props.item.description) : "",
39
40
  );
40
41
 
41
42
  const hasSparklineData = computed(
@@ -1,3 +1,5 @@
1
+ import type { BlockText } from "#dms-core/app/types/composed-text";
2
+
1
3
  /** Serializable X-axis settings, matching the DMS server contract. */
2
4
  export interface ChartXAxis {
3
5
  /** Requested intervals; ignored by ApexCharts on datetime axes. */
@@ -100,8 +102,9 @@ export interface KpiCardResponse {
100
102
 
101
103
  export interface TopListItem {
102
104
  id: string | number;
103
- title: string;
104
- description?: string;
105
+ /** A literal, a `$`-prefixed i18n key, or a composed text. */
106
+ title: BlockText;
107
+ description?: BlockText;
105
108
  value: number;
106
109
  delta?: number | null;
107
110
  sparkline?: number[];
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  computed,
3
- onBeforeUnmount,
4
3
  onMounted,
5
4
  ref,
6
5
  watch,
@@ -12,12 +11,11 @@ import {
12
11
  usePeriodScope,
13
12
  } from "../../../../dms-core/app/composables/period/usePeriodScope";
14
13
  import type { PeriodState } from "../../../../dms-core/app/composables/period/types";
15
- import { useRealtimeTopic } from "../../../../dms-core/app/composables/realtime/useRealtimeTopic";
16
14
  import {
17
15
  hasUrlVariables,
18
16
  resolveUrlVariables,
19
17
  } from "../../build/utils/urlVariables";
20
- import { onPageBlocksRefresh } from "../../utils/blockRefresh";
18
+ import { useRefreshTriggers } from "../../build/composables/blocks/useRefreshTriggers";
21
19
 
22
20
  export interface UseChartFetchOptions<T> {
23
21
  /**
@@ -132,20 +130,6 @@ function readStaticData<T>(
132
130
  return source ?? null;
133
131
  }
134
132
 
135
- function subscribeRealtimeTopics(
136
- topic: string | string[] | undefined,
137
- onEvent: () => void,
138
- ): void {
139
- if (!topic) return;
140
- const topics = Array.isArray(topic) ? topic : [topic];
141
- for (const entry of topics) {
142
- useRealtimeTopic(entry, (event) => {
143
- if (!("type" in event)) return;
144
- onEvent();
145
- });
146
- }
147
- }
148
-
149
133
  /**
150
134
  * The URL to request: `fetchUrl` with its tokens filled, or `undefined` while
151
135
  * one has no value. Only a URL naming a token reads the route.
@@ -164,16 +148,6 @@ function useResolvedFetchUrl<T>(
164
148
  );
165
149
  }
166
150
 
167
- // Mounted only: the page refresh is a browser event, and a block the server
168
- // renders has nothing to refetch.
169
- function subscribePageRefresh(onRefresh: () => void): void {
170
- let unsubscribe: (() => void) | undefined;
171
- onMounted(() => {
172
- unsubscribe = onPageBlocksRefresh(onRefresh);
173
- });
174
- onBeforeUnmount(() => unsubscribe?.());
175
- }
176
-
177
151
  export function useChartFetch<T>(
178
152
  options: UseChartFetchOptions<T>,
179
153
  ): UseChartFetchReturn<T> {
@@ -290,8 +264,7 @@ export function useChartFetch<T>(
290
264
  scheduleRefresh,
291
265
  );
292
266
  onMounted(fetchOnMount);
293
- subscribeRealtimeTopics(options.realtimeTopic, refreshSameInputs);
294
- subscribePageRefresh(refreshSameInputs);
267
+ useRefreshTriggers(options.realtimeTopic, refreshSameInputs);
295
268
  } else if (typeof options.staticData === "function") {
296
269
  watch(
297
270
  () => readStaticData(options.staticData),
@@ -13,6 +13,7 @@ import {
13
13
  type ReplaceUrlVariablesContext,
14
14
  hasUrlVariables,
15
15
  replaceUrlVariables,
16
+ resolveTextVariables,
16
17
  resolveUrlVariables,
17
18
  } from "../../build/utils/urlVariables";
18
19
 
@@ -347,7 +348,7 @@ export function resolveSubmitDefaults(
347
348
  const resolved: Record<string, unknown> = {};
348
349
  for (const [key, value] of Object.entries(submitDefaults)) {
349
350
  if (typeof value === "string" && hasUrlVariables(value)) {
350
- const replaced = resolveUrlVariables(value, context);
351
+ const replaced = resolveTextVariables(value, context);
351
352
  if (replaced === undefined) continue;
352
353
  resolved[key] = replaced;
353
354
  } else {
@@ -29,6 +29,8 @@ export interface TabProps extends TabComponentProps {
29
29
  * names, read in one request and again when a watch action fires.
30
30
  */
31
31
  badgesUrl?: string;
32
+ /** Topics whose events make the tab set read `badgesUrl` again. */
33
+ realtimeTopic?: string | string[];
32
34
  }
33
35
 
34
36
  /** The badges `badgesUrl` answers with, by tab slot. */
@@ -1,9 +1,13 @@
1
+ import type { DrawerDirection } from "../../containers/types";
2
+
1
3
  export type ActionTarget =
2
4
  | {
3
5
  type: "drawer";
4
6
  component: ComponentInfo;
5
7
  title?: string;
6
8
  description?: string;
9
+ /** Edge the drawer slides in from (`bottom` by default). */
10
+ direction?: DrawerDirection;
7
11
  }
8
12
  | {
9
13
  type: "modal";
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The window event a data block (StatGroup, KeyValueList, NavCardGrid,
3
- * ActivityFeed, KPI, top list, meter, chart, a tab set's badges) re-fetches
4
- * on, keeping its values on screen until the new answer lands.
3
+ * ActivityFeed, KPI, top list, meter, chart, a tab set's badges, table view)
4
+ * re-fetches on, keeping its values on screen until the new answer lands.
5
5
  */
6
6
  export const BLOCK_REFRESH_EVENT = "DmsComponent.Block.Refresh";
7
7
 
@@ -25,6 +25,7 @@
25
25
  "edit_item_description": "Change its fields, then save.",
26
26
  "edit_item_in": "Changes are saved to {caption}.",
27
27
  "search_placeholder": "Search...",
28
+ "search_open": "Open search",
28
29
  "options_title": "Table options",
29
30
  "filters_title": "Filters",
30
31
  "columns_title": "Columns",
@@ -25,6 +25,7 @@
25
25
  "edit_item_description": "Modifiez ses champs, puis enregistrez.",
26
26
  "edit_item_in": "Les modifications sont enregistrées dans « {caption} ».",
27
27
  "search_placeholder": "Rechercher...",
28
+ "search_open": "Ouvrir la recherche",
28
29
  "options_title": "Options de la table",
29
30
  "filters_title": "Filtres",
30
31
  "columns_title": "Colonnes",
@@ -11,7 +11,7 @@
11
11
  "license": "Apache-2.0",
12
12
  "packageManager": "pnpm@10.6.5",
13
13
  "engines": {
14
- "@antelopejs/dms-frontend": ">=0.5.1 <0.6.0"
14
+ "@antelopejs/dms-frontend": ">=0.5.2 <0.6.0"
15
15
  },
16
16
  "scripts": {
17
17
  "typecheck": "ajs dms verify-source --layer \"$PWD\" --local-package @antelopejs/dms=.. --local-package @antelopejs/interface-dms=../../interface-dms",
@@ -30,12 +30,13 @@
30
30
  "@codemirror/lang-json": "^6.0.2",
31
31
  "@codemirror/lang-sql": "^6.10.0",
32
32
  "@codemirror/language": "^6.11.0",
33
- "@codemirror/streamparser": "^6.0.0",
34
33
  "@codemirror/state": "^6.5.2",
34
+ "@codemirror/streamparser": "^6.0.0",
35
35
  "@codemirror/view": "^6.38.1",
36
36
  "@iconify-json/lucide": "^1.2.64",
37
37
  "@iconify-json/ph": "^1.2.2",
38
38
  "@internationalized/date": "^3.10.0",
39
+ "@lezer/common": "^1.5.3",
39
40
  "@lezer/highlight": "^1.2.5",
40
41
  "@tailwindcss/typography": "^0.5.19",
41
42
  "@tanstack/vue-table": "^8.21.3",
@@ -73,7 +74,7 @@
73
74
  },
74
75
  "devDependencies": {
75
76
  "@antelopejs/core": ">=1.13.4 <2",
76
- "@antelopejs/dms-frontend": ">=0.5.1 <0.6.0",
77
+ "@antelopejs/dms-frontend": ">=0.5.2 <0.6.0",
77
78
  "@eslint/js": "^8.57.0",
78
79
  "@inertiajs/vue3": "^3.7.0",
79
80
  "@nuxt/ui": "4.10.0",
@@ -47,6 +47,9 @@ importers:
47
47
  '@internationalized/date':
48
48
  specifier: ^3.10.0
49
49
  version: 3.10.0
50
+ '@lezer/common':
51
+ specifier: ^1.5.3
52
+ version: 1.5.3
50
53
  '@lezer/highlight':
51
54
  specifier: ^1.2.5
52
55
  version: 1.2.5
@@ -157,8 +160,8 @@ importers:
157
160
  specifier: '>=1.13.4 <2'
158
161
  version: 1.13.5
159
162
  '@antelopejs/dms-frontend':
160
- specifier: '>=0.5.1 <0.6.0'
161
- version: 0.5.1(@antelopejs/core@1.13.5)
163
+ specifier: '>=0.5.2 <0.6.0'
164
+ version: 0.5.2(@antelopejs/core@1.13.5)
162
165
  '@eslint/js':
163
166
  specifier: ^8.57.0
164
167
  version: 8.57.0
@@ -234,8 +237,8 @@ packages:
234
237
  engines: {node: '>=20.19'}
235
238
  hasBin: true
236
239
 
237
- '@antelopejs/dms-frontend@0.5.1':
238
- resolution: {integrity: sha512-ykrqlHfYF+BqNy+h4FuZBbqAW6UjviauCiI8cQCW1M55MzIxk2Ar3ZnMH1VKH8QsfDBBNapDik5rd/AMLQ33dw==}
240
+ '@antelopejs/dms-frontend@0.5.2':
241
+ resolution: {integrity: sha512-0kChPedSTpKES7aN/gLj5tAe4WRaU+FrwaRKpOyvo3fvvRPfDTvlTYjjdUpabsF150d3+V/AksXstZ5eb36QwQ==}
239
242
  engines: {node: '>=20.12.0'}
240
243
  hasBin: true
241
244
  peerDependencies:
@@ -4205,7 +4208,7 @@ snapshots:
4205
4208
  semver: 7.8.5
4206
4209
  typescript: 5.9.3
4207
4210
 
4208
- '@antelopejs/dms-frontend@0.5.1(@antelopejs/core@1.13.5)':
4211
+ '@antelopejs/dms-frontend@0.5.2(@antelopejs/core@1.13.5)':
4209
4212
  dependencies:
4210
4213
  '@antelopejs/core': 1.13.5
4211
4214
  chokidar: 4.0.3
@@ -3,6 +3,7 @@ import {
3
3
  formatActivityTime,
4
4
  formatShortDate,
5
5
  groupActivityByDay,
6
+ activityText,
6
7
  resolveActivityParams,
7
8
  relativeDayBucket,
8
9
  } from "../layers/dms-ui/app/build/components/activity-feed/activityFeedDays";
@@ -87,25 +88,38 @@ describe("activity feed day groups", () => {
87
88
  });
88
89
 
89
90
  describe("resolveActivityParams", () => {
90
- // Echoes the key and its values, so assertions read the whole text.
91
- const translate = (key: string, params?: Record<string, string> | null) =>
92
- `${key}(${JSON.stringify(params ?? {})})`;
93
-
94
- it("translates a $ value with the entry's literal values", () => {
91
+ it("makes a $ value a text of its own, with the entry's other values", () => {
92
+ const count = { type: "count", value: 2 } as const;
95
93
  expect(
96
- resolveActivityParams(
97
- { device: "$activity.device", browser: "Chrome", os: "Windows" },
98
- translate,
99
- ),
94
+ resolveActivityParams({
95
+ device: "$activity.device",
96
+ browser: "Chrome",
97
+ count,
98
+ }),
100
99
  ).toEqual({
101
- device: '$activity.device({"browser":"Chrome","os":"Windows"})',
100
+ device: {
101
+ key: "$activity.device",
102
+ params: { browser: "Chrome", count },
103
+ },
102
104
  browser: "Chrome",
103
- os: "Windows",
105
+ count,
104
106
  });
105
107
  });
106
108
 
107
109
  it("gives nothing for an entry without values", () => {
108
- expect(resolveActivityParams(undefined, translate)).toBeNull();
110
+ expect(resolveActivityParams(undefined)).toBeUndefined();
111
+ });
112
+ });
113
+
114
+ describe("activityText", () => {
115
+ it("gives a $ title the entry's values and leaves other texts alone", () => {
116
+ expect(activityText("$a.title", { name: "Léa" })).toEqual({
117
+ key: "$a.title",
118
+ params: { name: "Léa" },
119
+ });
120
+ expect(activityText("Plain", { name: "Léa" })).toBe("Plain");
121
+ const composed = { key: "a.composed" };
122
+ expect(activityText(composed, { name: "Léa" })).toBe(composed);
109
123
  });
110
124
  });
111
125