@conduction/nextcloud-vue 2.144.0 → 2.145.0

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 (85) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +1 -0
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  6. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +55 -2
  7. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
  10. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +16 -0
  11. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -1
  13. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  14. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +1 -1
  15. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +1 -1
  16. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +3 -3
  18. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -1
  19. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
  20. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +2 -2
  21. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  22. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  23. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +6 -3
  24. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  25. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  26. package/dist/esm/components/CnMailTab/CnMailTab.vue.js +1 -1
  27. package/dist/esm/components/CnMailTab/CnMailTab.vue2.js.map +1 -1
  28. package/dist/esm/components/CnMailTab/CnMailTab.vue3.js +2 -2
  29. package/dist/esm/components/CnMailTab/CnMailTab.vue3.js.map +1 -1
  30. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue.js +1 -1
  31. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue2.js.map +1 -1
  32. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue3.js.map +1 -1
  33. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  34. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +73 -12
  35. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  36. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +165 -126
  37. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  38. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
  39. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js +14 -0
  40. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
  41. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +2 -1
  42. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
  43. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  44. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -8
  45. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  46. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  47. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue.js +1 -1
  48. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +10 -2
  49. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  50. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  51. package/dist/esm/composables/indexSources.js +35 -9
  52. package/dist/esm/composables/indexSources.js.map +1 -1
  53. package/dist/esm/l10n/nl.json.js +2 -0
  54. package/dist/esm/l10n/nl.json.js.map +1 -1
  55. package/dist/esm/library-style.js +1 -1
  56. package/dist/esm/nextcloud-vue.css +742 -326
  57. package/dist/esm/utils/builtInFormatters.js +18 -1
  58. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  59. package/dist/esm/utils/localHeaderRows.js +95 -0
  60. package/dist/esm/utils/localHeaderRows.js.map +1 -0
  61. package/dist/nextcloud-vue.cjs.js +514 -179
  62. package/dist/nextcloud-vue.cjs.js.map +2 -2
  63. package/dist/nextcloud-vue.css +742 -326
  64. package/l10n/nl.json +2 -0
  65. package/package.json +1 -1
  66. package/src/components/CnActionDialog/CnActionDialog.vue +8 -1
  67. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +55 -2
  68. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +18 -0
  69. package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +2 -1
  70. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +2 -2
  71. package/src/components/CnIndexPage/CnIndexPage.vue +6 -3
  72. package/src/components/CnMailTab/CnMailTab.vue +6 -4
  73. package/src/components/CnNotificationMatrix/CnNotificationMatrix.vue +4 -1
  74. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +131 -20
  75. package/src/components/CnSettingsField/CnSettingsField.vue +15 -0
  76. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -8
  77. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +10 -2
  78. package/src/composables/indexSources.js +35 -9
  79. package/src/css/dialog.css +18 -9
  80. package/src/css/look-board-detail.css +219 -23
  81. package/src/css/look-board-index.css +38 -4
  82. package/src/css/look-board.css +138 -8
  83. package/src/css/table.css +8 -1
  84. package/src/utils/builtInFormatters.js +17 -0
  85. package/src/utils/localHeaderRows.js +91 -0
@@ -664,12 +664,17 @@
664
664
  }
665
665
 
666
666
  /* Two buttons in a card's title row stand 8px apart (DqZaakContact "Live
667
- gesprek", DqZaakEmail "Mail koppelen" / "Mail opstellen").
667
+ gesprek", DqZaakEmail "Mail koppelen" / "Mail opstellen"), also in an
668
+ integration card's CnDetailCard header (DqZaakTaken "Afspraken", "Kaarten").
668
669
  @spec openspec/changes/board-card-header-actions-gap/specs/board-card-header-actions-gap/spec.md#requirement-a-board-cards-title-row-buttons-stand-8px-apart */
669
670
  .cn-look-board .cn-widget-wrapper__actions:not(.cn-look-nextcloud *) {
670
671
  gap: 8px;
671
672
  }
672
673
 
674
+ .cn-look-board .cn-detail-card__header-right:not(.cn-look-nextcloud *) {
675
+ gap: 8px;
676
+ }
677
+
673
678
  /* A detail page marked `cardButtons: "regular"` draws them at the 40px
674
679
  control height: the case page's tab cards (DqZaakContact "Live gesprek",
675
680
  DqZaakHistorie, DqZaakBetrokkenen), where setting and case-type pages keep 34.
@@ -1467,6 +1472,90 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
1467
1472
  text-decoration: underline;
1468
1473
  }
1469
1474
 
1475
+ /* A plain table an app draws in a settings card (dossiq's ZGW mapping, StUF
1476
+ endpoints and audit log, AWB terms) reads as the board's settings tables:
1477
+ 14px, a near-white head with muted 600 labels, 10px 12px cells, soft rules.
1478
+ The theme paints every th through its table header tokens, so the head
1479
+ hands those tokens the board's values too.
1480
+ @spec openspec/changes/board-settings-plain-tables/specs/settings-board-look/spec.md */
1481
+ .cn-look-board .settings-section table:not(.cn-data-table):not(.cn-look-nextcloud *) {
1482
+ width: 100%;
1483
+ border-collapse: collapse;
1484
+ font-size: 14px;
1485
+ line-height: 1.2;
1486
+ }
1487
+
1488
+ .cn-look-board .settings-section table:not(.cn-data-table):not(.cn-look-nextcloud *) th {
1489
+ --nldesign-component-table-header-background-color: var(--cn-board-table-head-bg, color-mix(in srgb, var(--color-background-hover) 40%, var(--color-main-background)));
1490
+ --nldesign-component-table-header-color: var(--color-text-maxcontrast);
1491
+ --nldesign-component-table-header-font-weight: 600;
1492
+ padding: 10px 12px;
1493
+ border-bottom: 1px solid var(--color-border);
1494
+ background: var(--cn-board-table-head-bg, color-mix(in srgb, var(--color-background-hover) 40%, var(--color-main-background)));
1495
+ color: var(--color-text-maxcontrast);
1496
+ font-size: 14px;
1497
+ font-weight: 600;
1498
+ line-height: 1.2;
1499
+ text-align: start;
1500
+ }
1501
+
1502
+ /* The working calendar's month reads as the board draws it: the month name
1503
+ first, at the content edge, with the previous and next month buttons after
1504
+ it, and the buttons add no height to the line, so the month sits 20px under
1505
+ the card title (DqInstellingenAlgemeen "Werkdagen en feestdagen": the
1506
+ arrow put the month 30px in and 28px down).
1507
+ @spec openspec/changes/working-calendar-board-head/specs/settings-board-look/spec.md */
1508
+ .cn-look-board .cn-working-calendar__title:not(.cn-look-nextcloud *) {
1509
+ order: -1;
1510
+ min-width: 0;
1511
+ margin-inline-end: 4px;
1512
+ line-height: 1.2;
1513
+ text-align: start;
1514
+ }
1515
+
1516
+ .cn-look-board .settings-section table:not(.cn-data-table):not(.cn-look-nextcloud *) td {
1517
+ padding: 10px 12px;
1518
+ border-bottom: 1px solid var(--color-background-dark);
1519
+ color: var(--color-main-text);
1520
+ font-size: 14px;
1521
+ line-height: 1.2;
1522
+ vertical-align: middle;
1523
+ }
1524
+
1525
+ /* A strong cell's name in a settings card reads 15px bold over its muted
1526
+ second line, as the boards draw a connection's name (DqInstellingen
1527
+ "Koppelingen": <strong> 15px), not the index tables' 14px 500.
1528
+ @spec openspec/changes/board-settings-strong-cell-name/specs/settings-board-look/spec.md */
1529
+ .cn-look-board .settings-section .cn-data-table td.cn-cell--strong:not(.cn-look-nextcloud *) {
1530
+ font-weight: 400;
1531
+ }
1532
+
1533
+ .cn-look-board .settings-section .cn-data-table td.cn-cell--strong:not(.cn-look-nextcloud *) > .cn-cell-renderer {
1534
+ font-size: 15px;
1535
+ font-weight: 700;
1536
+ }
1537
+
1538
+ .cn-look-board .cn-working-calendar__head:not(.cn-look-nextcloud *) > .button-vue:not(.cn-working-calendar__add) {
1539
+ margin-block: -8px;
1540
+ }
1541
+
1542
+ /* In a one-column settings card a single-line field is at most 560px wide,
1543
+ its hint under it, as the boards draw a field on its own row
1544
+ (DqBeheerGeavanceerd "Financiële integratie", "Storeregister"); a text
1545
+ area, a note or a switch still spans the card.
1546
+ @spec openspec/changes/board-settings-single-field-width/specs/settings-board-look/spec.md */
1547
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--text,
1548
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--password,
1549
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--secret,
1550
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--number,
1551
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--email,
1552
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--url,
1553
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--select,
1554
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--date,
1555
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--datetime-local {
1556
+ max-width: 560px;
1557
+ }
1558
+
1470
1559
  /* Section actions sit at the end of the title row. The row is a grid, so a
1471
1560
  long title wraps beside the buttons instead of running under them. A
1472
1561
  half-width card keeps them there (DqVthInstellingen "Inspectiechecklists",
@@ -1523,6 +1612,24 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
1523
1612
  align-self: center;
1524
1613
  }
1525
1614
 
1615
+ /* A switch or radio in a board settings card starts at the card's content
1616
+ edge, its label 12px (switch) or 10px (radio) after the control, as the
1617
+ boards draw them (DqBeheerinstellingen "AI-ondersteunde verwerking").
1618
+ NcCheckboxRadioSwitch pads its content 9px on each side, which set the
1619
+ control 9px in from the fields above and below it.
1620
+ @spec openspec/changes/board-settings-switch-radio-flush/specs/settings-board-look/spec.md */
1621
+ .cn-look-board .cn-settings-fields:not(.cn-look-nextcloud *) .checkbox-content {
1622
+ padding-inline: 0;
1623
+ }
1624
+
1625
+ .cn-look-board .cn-settings-fields:not(.cn-look-nextcloud *) .checkbox-content-switch {
1626
+ gap: 12px;
1627
+ }
1628
+
1629
+ .cn-look-board .cn-settings-fields:not(.cn-look-nextcloud *) .checkbox-content-radio {
1630
+ gap: 10px;
1631
+ }
1632
+
1526
1633
  /* The "Uitleg tonen" toggle is a text link flush with the card's right edge
1527
1634
  (DqBeheerinstellingen Zaak-e-mail), not a padded tertiary button.
1528
1635
  @spec openspec/changes/board-settings-title-centred-and-link-flush/specs/settings-board-look/spec.md */
@@ -1596,8 +1703,12 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
1596
1703
  max-width: none;
1597
1704
  }
1598
1705
 
1599
- .cn-look-board .settings-section.cn-settings-section--wide:not(.cn-look-nextcloud *) .settings-section__name,
1600
- .cn-look-board .settings-section.cn-settings-section--wide:not(.cn-look-nextcloud *) .settings-section__desc {
1706
+ /* A board card's title and subtitle run the card's width, wide or not
1707
+ (DqBeheerGeavanceerd "Eerste keer", "StUF-ZKN-endpoints": the subtitle
1708
+ wrapped at NcSettingsSection's 900px measure, the board's does not).
1709
+ @spec openspec/changes/board-settings-desc-full-width/specs/settings-board-look/spec.md */
1710
+ .cn-look-board .settings-section:not(.cn-look-nextcloud *) .settings-section__name,
1711
+ .cn-look-board .settings-section:not(.cn-look-nextcloud *) .settings-section__desc {
1601
1712
  max-width: none;
1602
1713
  }
1603
1714
 
@@ -2156,7 +2267,7 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
2156
2267
  12px follows it: the boards' card is title, 6px, rule, row (DqMijnTeam
2157
2268
  "Vastgelopen zaken", DqMijnWerk "Zaken die u volgt").
2158
2269
  @spec openspec/changes/board-card-title-above-ruled-list/specs/board-card-title-above-ruled-list/spec.md#requirement-a-ruled-list-starts-6px-under-the-card-title */
2159
- .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--nc-dashboard):not(.cn-widget-wrapper--borderless):has(> .cn-widget-wrapper__content .cn-record-list--rule-above, > .cn-widget-wrapper__content .cn-tasks-widget__list, > .cn-widget-wrapper__content .cn-decision-list__rows) .cn-widget-wrapper__header {
2270
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--nc-dashboard):not(.cn-widget-wrapper--borderless):has(> .cn-widget-wrapper__content .cn-record-list--rule-above, > .cn-widget-wrapper__content .cn-tasks-widget__list, > .cn-widget-wrapper__content .cn-decision-list__rows, > .cn-widget-wrapper__content .cn-nc-widget-widget__body--vertical) .cn-widget-wrapper__header {
2160
2271
  padding-bottom: 6px;
2161
2272
  }
2162
2273
 
@@ -2692,13 +2803,13 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
2692
2803
 
2693
2804
  /* A vertical NC widget is the board's activity list (DqMijnWerk "Recente
2694
2805
  activiteit"): hairline rows 12px above and below the text, a 10px dot in
2695
- the icon's place, the title 14px in the main ink and wrapping, the first
2696
- hairline 12px under the title. Thematiq underlines links with a heavier
2697
- selector, hence the !important.
2806
+ the icon's place, the title 14px in the main ink and wrapping. Thematiq underlines links with a heavier
2807
+ selector, hence the !important. The first hairline stands 6px under the
2808
+ title, as on every Dq list card (design-system #432).
2698
2809
  @spec openspec/changes/board-nc-widget-activity-rows/specs/board-nc-widget-activity-rows/spec.md#requirement-a-board-dashboard-nc-widget-draws-its-items-as-activity-rows */
2699
2810
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-nc-widget-widget__body--vertical {
2700
2811
  gap: 0;
2701
- padding: 2px var(--cn-board-dashboard-card-inset) 10px;
2812
+ padding: 0 var(--cn-board-dashboard-card-inset) 10px;
2702
2813
  }
2703
2814
 
2704
2815
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-nc-widget-widget__body--vertical a.cn-nc-widget-widget__item--vertical {
@@ -3132,6 +3243,25 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
3132
3243
  line-height: normal;
3133
3244
  }
3134
3245
 
3246
+ /* A bar list in a framed section of a sections card is the board's small
3247
+ list: 13px rows 8px apart, a 150px label column, a 10px bar and a 48px
3248
+ value column (DqDoorlooptijd "Naleving per zaaktype").
3249
+ @spec openspec/changes/board-section-bar-list-compact/specs/board-section-bar-list-compact/spec.md#requirement-a-bar-list-in-a-sections-card-section-is-compact */
3250
+ .cn-look-board .cn-sections-widget__section .cn-chart-widget__bar-list:not(.cn-look-nextcloud *) {
3251
+ gap: 8px;
3252
+ }
3253
+
3254
+ .cn-look-board .cn-sections-widget__section .cn-chart-widget__bar-list:not(.cn-look-nextcloud *) .cn-chart-widget__bar-line {
3255
+ grid-template-columns: 150px minmax(0, 1fr) 48px;
3256
+ gap: 10px;
3257
+ font-size: 13px;
3258
+ }
3259
+
3260
+ .cn-look-board .cn-sections-widget__section .cn-chart-widget__bar-list:not(.cn-look-nextcloud *) .cn-chart-widget__bar-track {
3261
+ height: 10px;
3262
+ border-radius: 5px;
3263
+ }
3264
+
3135
3265
  /* A bar list with the value at the bar's end (DqProcesanalyse "Doorlooptijd
3136
3266
  per status") sets its rows in 13px, the label in the secondary text, the
3137
3267
  value in the main ink at 600, rows 34px apart.
package/src/css/table.css CHANGED
@@ -310,7 +310,7 @@
310
310
  .cn-data-table--fit td.cn-table-col--grow.cn-cell--wrap {
311
311
  min-width: 8em;
312
312
  max-width: none;
313
- overflow-wrap: anywhere;
313
+ overflow-wrap: break-word;
314
314
  white-space: normal;
315
315
  }
316
316
 
@@ -359,6 +359,13 @@
359
359
  font-weight: 500;
360
360
  }
361
361
 
362
+ /* A lead cell's value reads 15px, a person's name over their function
363
+ (DqAfdelingen). */
364
+ .cn-data-table td.cn-cell--lead > .cn-cell-renderer,
365
+ .cn-data-table td.cn-cell--lead .cn-table-row__link > .cn-cell-renderer {
366
+ font-size: 15px;
367
+ }
368
+
362
369
  .cn-data-table td.cn-cell--muted {
363
370
  color: var(--color-text-maxcontrast);
364
371
  }
@@ -2,6 +2,7 @@ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
2
2
  import { formatRelativeDate } from './boardDate.js'
3
3
  import { formatMoney } from './formatMetric.js'
4
4
  import { parseDuration } from './isoDuration.js'
5
+ import { userDisplayName } from './userDisplayName.js'
5
6
 
6
7
  /**
7
8
  * @file Built-in formatters merged into the `cnFormatters` registry
@@ -523,6 +524,21 @@ export function formatIsoDuration(value) {
523
524
  return words[period.unit] ? words[period.unit]() : String(value)
524
525
  }
525
526
 
527
+ /**
528
+ * A Nextcloud user id as its display name (`userName` registry key): the id
529
+ * while the name is looked up, and for a user nobody answers for.
530
+ *
531
+ * @spec openspec/changes/user-name-formatter/specs/user-name-formatter/spec.md#requirement-a-user-id-can-read-as-its-display-name
532
+ * @param {unknown} value The user id.
533
+ * @return {string} The display name, the id, or ''.
534
+ */
535
+ export function formatUserName(value) {
536
+ if (value === null || value === undefined || value === '' || typeof value === 'object') {
537
+ return ''
538
+ }
539
+ return userDisplayName(value) || String(value)
540
+ }
541
+
526
542
  export const BUILT_IN_FORMATTERS = {
527
543
  date: formatDate,
528
544
  datetime: formatDateTime,
@@ -538,4 +554,5 @@ export const BUILT_IN_FORMATTERS = {
538
554
  masked: formatMasked,
539
555
  isoDuration: formatIsoDuration,
540
556
  valueMap: formatValueMap,
557
+ userName: formatUserName,
541
558
  }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * Header sort and filter applied to rows the table already holds (an
6
+ * `endpointSource` table): the same `{ field, dir }` sort and
7
+ * `{ paramKey: values[] }` filter map a self-fetching table sends to the
8
+ * server, read here in the browser.
9
+ *
10
+ * @module utils/localHeaderRows
11
+ */
12
+
13
+ import { CONTAINS_OPERATOR } from './columnFilters.js'
14
+
15
+ /**
16
+ * A row value as one comparable scalar: an array counts as its joined items.
17
+ *
18
+ * @param {unknown} value The raw value.
19
+ * @return {string|number|null} The value to compare.
20
+ */
21
+ function scalar(value) {
22
+ if (value === null || value === undefined) {
23
+ return null
24
+ }
25
+ if (Array.isArray(value)) {
26
+ return value.map((item) => (item && typeof item === 'object' ? (item.title || item.name || '') : String(item))).join(', ')
27
+ }
28
+ if (typeof value === 'number' || typeof value === 'boolean') {
29
+ return typeof value === 'boolean' ? String(value) : value
30
+ }
31
+ return typeof value === 'object' ? (value.title || value.name || '') : String(value)
32
+ }
33
+
34
+ /**
35
+ * Whether a row holds a value the filter entry asks for.
36
+ *
37
+ * @param {object} row The row.
38
+ * @param {string} param The filter key (`field`, `field[like]`, `field[gte]`, `field[lte]`).
39
+ * @param {Array<string>} values The chosen values.
40
+ * @return {boolean} True when the row passes.
41
+ */
42
+ function passes(row, param, values) {
43
+ const match = /^(.+)\[(\w+)\]$/.exec(param)
44
+ const field = match ? match[1] : param
45
+ const op = match ? match[2] : ''
46
+ const value = scalar(row[field])
47
+ if (op === CONTAINS_OPERATOR) {
48
+ const term = String(values[0]).toLowerCase()
49
+ return value !== null && String(value).toLowerCase().includes(term)
50
+ }
51
+ if (op === 'gte' || op === 'lte') {
52
+ if (value === null) {
53
+ return false
54
+ }
55
+ const left = typeof value === 'number' ? value : String(value)
56
+ const right = typeof value === 'number' ? Number(values[0]) : String(values[0])
57
+ return op === 'gte' ? left >= right : left <= right
58
+ }
59
+ return value !== null && values.map(String).includes(String(value))
60
+ }
61
+
62
+ /**
63
+ * The rows that pass every active header filter, in the header sort's order.
64
+ *
65
+ * @param {Array<object>} rows The rows.
66
+ * @param {{field: string, dir: string}|null} sort The header sort.
67
+ * @param {object} filters The header filters, `{ paramKey: values[] }`.
68
+ * @return {Array<object>} The rows to show (the same array when nothing applies).
69
+ */
70
+ export function localHeaderRows(rows, sort, filters) {
71
+ if (!Array.isArray(rows)) {
72
+ return []
73
+ }
74
+ const active = Object.entries(filters || {}).filter(([, values]) => Array.isArray(values) && values.length > 0)
75
+ let out = active.length > 0 ? rows.filter((row) => row && active.every(([param, values]) => passes(row, param, values))) : rows
76
+ if (sort && sort.field) {
77
+ const sign = sort.dir === 'desc' ? -1 : 1
78
+ out = [...out].sort((a, b) => {
79
+ const left = scalar(a && a[sort.field])
80
+ const right = scalar(b && b[sort.field])
81
+ if (left === null || right === null) {
82
+ return left === right ? 0 : (left === null ? 1 : -1)
83
+ }
84
+ if (typeof left === 'number' && typeof right === 'number') {
85
+ return sign * (left - right)
86
+ }
87
+ return sign * String(left).localeCompare(String(right), undefined, { numeric: true, sensitivity: 'base' })
88
+ })
89
+ }
90
+ return out
91
+ }