@mmlogic/components 0.3.11 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/cjs/{index-BPj2cBXs.js → index-CAzwGt9u.js} +38 -0
  2. package/dist/cjs/loader.cjs.js +2 -2
  3. package/dist/cjs/mosterdcomponents.cjs.js +2 -2
  4. package/dist/cjs/mrd-boolean-field_23.cjs.entry.js +1313 -1088
  5. package/dist/collection/collection-manifest.json +23 -23
  6. package/dist/collection/components/{mrd-document-container/mrd-document-container.scss → documents/mrd-document-container/mrd-document-container.css} +3 -3
  7. package/dist/collection/components/{mrd-document-container → documents/mrd-document-container}/mrd-document-container.js +7 -7
  8. package/dist/collection/components/documents/mrd-document-list/folder-tree.js +109 -0
  9. package/dist/collection/components/{mrd-document-list/mrd-document-list.scss → documents/mrd-document-list/mrd-document-list.css} +6 -4
  10. package/dist/collection/components/{mrd-document-list → documents/mrd-document-list}/mrd-document-list.js +83 -240
  11. package/dist/collection/components/documents/mrd-document-list/tree-view.js +75 -0
  12. package/dist/collection/components/{mrd-document-view/mrd-document-view.scss → documents/mrd-document-view/mrd-document-view.css} +1 -1
  13. package/dist/collection/components/{mrd-document-view → documents/mrd-document-view}/mrd-document-view.js +4 -4
  14. package/dist/collection/components/fields/field-helpers.js +17 -0
  15. package/dist/collection/components/fields/file-upload-common.js +38 -0
  16. package/dist/collection/components/{mrd-boolean-field/mrd-boolean-field.scss → fields/mrd-boolean-field/mrd-boolean-field.css} +3 -3
  17. package/dist/collection/components/{mrd-boolean-field → fields/mrd-boolean-field}/mrd-boolean-field.js +1 -1
  18. package/dist/collection/components/{mrd-currency-field/mrd-currency-field.scss → fields/mrd-currency-field/mrd-currency-field.css} +11 -9
  19. package/dist/collection/components/{mrd-currency-field → fields/mrd-currency-field}/mrd-currency-field.js +7 -13
  20. package/dist/collection/components/{mrd-date-field/mrd-date-field.scss → fields/mrd-date-field/mrd-date-field.css} +8 -10
  21. package/dist/collection/components/{mrd-date-field → fields/mrd-date-field}/mrd-date-field.js +3 -10
  22. package/dist/collection/components/{mrd-datetime-field/mrd-datetime-field.scss → fields/mrd-datetime-field/mrd-datetime-field.css} +8 -10
  23. package/dist/collection/components/{mrd-datetime-field → fields/mrd-datetime-field}/mrd-datetime-field.js +3 -10
  24. package/dist/collection/components/{mrd-email-field/mrd-email-field.scss → fields/mrd-email-field/mrd-email-field.css} +2 -11
  25. package/dist/collection/components/{mrd-email-field → fields/mrd-email-field}/mrd-email-field.js +6 -13
  26. package/dist/collection/components/{mrd-file-field/mrd-file-field.scss → fields/mrd-file-field/mrd-file-field.css} +19 -24
  27. package/dist/collection/components/{mrd-file-field → fields/mrd-file-field}/mrd-file-field.js +13 -24
  28. package/dist/collection/components/{mrd-hyperlink-field/mrd-hyperlink-field.scss → fields/mrd-hyperlink-field/mrd-hyperlink-field.css} +7 -16
  29. package/dist/collection/components/{mrd-hyperlink-field → fields/mrd-hyperlink-field}/mrd-hyperlink-field.js +9 -15
  30. package/dist/collection/components/{mrd-image-field/mrd-image-field.scss → fields/mrd-image-field/mrd-image-field.css} +15 -24
  31. package/dist/collection/components/{mrd-image-field → fields/mrd-image-field}/mrd-image-field.js +13 -28
  32. package/dist/collection/components/{mrd-list-field/mrd-list-field.scss → fields/mrd-list-field/mrd-list-field.css} +18 -21
  33. package/dist/collection/components/{mrd-list-field → fields/mrd-list-field}/mrd-list-field.js +10 -25
  34. package/dist/collection/components/{mrd-longtext-field/mrd-longtext-field.scss → fields/mrd-longtext-field/mrd-longtext-field.css} +21 -18
  35. package/dist/collection/components/{mrd-longtext-field → fields/mrd-longtext-field}/mrd-longtext-field.js +3 -10
  36. package/dist/collection/components/{mrd-number-field/mrd-number-field.scss → fields/mrd-number-field/mrd-number-field.css} +9 -10
  37. package/dist/collection/components/{mrd-number-field → fields/mrd-number-field}/mrd-number-field.js +7 -6
  38. package/dist/collection/components/{mrd-relation-field/mrd-relation-field.scss → fields/mrd-relation-field/mrd-relation-field.css} +61 -59
  39. package/dist/collection/components/{mrd-relation-field → fields/mrd-relation-field}/mrd-relation-field.js +16 -26
  40. package/dist/collection/components/{mrd-secret-field/mrd-secret-field.scss → fields/mrd-secret-field/mrd-secret-field.css} +7 -10
  41. package/dist/collection/components/{mrd-secret-field → fields/mrd-secret-field}/mrd-secret-field.js +4 -11
  42. package/dist/collection/components/{mrd-text-field/mrd-text-field.scss → fields/mrd-text-field/mrd-text-field.css} +2 -11
  43. package/dist/collection/components/{mrd-text-field → fields/mrd-text-field}/mrd-text-field.js +3 -10
  44. package/dist/collection/components/{mrd-textarea-field/mrd-textarea-field.scss → fields/mrd-textarea-field/mrd-textarea-field.css} +7 -10
  45. package/dist/collection/components/{mrd-textarea-field → fields/mrd-textarea-field}/mrd-textarea-field.js +3 -9
  46. package/dist/collection/components/{mrd-time-field/mrd-time-field.scss → fields/mrd-time-field/mrd-time-field.css} +8 -10
  47. package/dist/collection/components/{mrd-time-field → fields/mrd-time-field}/mrd-time-field.js +3 -10
  48. package/dist/collection/components/{mrd-field/mrd-field.scss → form/mrd-field/mrd-field.css} +1 -1
  49. package/dist/collection/components/{mrd-field → form/mrd-field}/mrd-field.js +3 -3
  50. package/dist/collection/components/{mrd-form/mrd-form.scss → form/mrd-form/mrd-form.css} +1 -1
  51. package/dist/collection/components/{mrd-form → form/mrd-form}/mrd-form.js +5 -5
  52. package/dist/collection/components/layout/mrd-layout-section/field-value.js +111 -0
  53. package/dist/collection/components/layout/mrd-layout-section/layout-helpers.js +29 -0
  54. package/dist/collection/components/{mrd-layout-section/mrd-layout-section.scss → layout/mrd-layout-section/mrd-layout-section.css} +4 -4
  55. package/dist/collection/components/{mrd-layout-section → layout/mrd-layout-section}/mrd-layout-section.js +52 -142
  56. package/dist/collection/components/{mrd-table/mrd-table.scss → table/mrd-table/mrd-table.css} +30 -12
  57. package/dist/collection/components/{mrd-table → table/mrd-table}/mrd-table.js +234 -443
  58. package/dist/collection/components/table/mrd-table/table-filter-popup.js +85 -0
  59. package/dist/collection/components/table/mrd-table/table-parts.js +110 -0
  60. package/dist/collection/components/table/mrd-table/table-query.js +178 -0
  61. package/dist/collection/components/table/mrd-table/virtual-scroll.js +63 -0
  62. package/dist/collection/dev/app.js +19 -18
  63. package/dist/collection/dev/example-data.js +58 -43
  64. package/dist/collection/utils/i18n.js +16 -0
  65. package/dist/collection/utils/query-params.js +43 -0
  66. package/dist/components/field-helpers.js +1 -0
  67. package/dist/components/file-upload-common.js +1 -0
  68. package/dist/components/i18n.js +1 -1
  69. package/dist/components/mrd-boolean-field2.js +1 -1
  70. package/dist/components/mrd-currency-field2.js +1 -1
  71. package/dist/components/mrd-date-field2.js +1 -1
  72. package/dist/components/mrd-datetime-field2.js +1 -1
  73. package/dist/components/mrd-document-container2.js +1 -1
  74. package/dist/components/mrd-document-list2.js +1 -1
  75. package/dist/components/mrd-document-view2.js +1 -1
  76. package/dist/components/mrd-email-field2.js +1 -1
  77. package/dist/components/mrd-file-field2.js +1 -1
  78. package/dist/components/mrd-form.js +1 -1
  79. package/dist/components/mrd-hyperlink-field2.js +1 -1
  80. package/dist/components/mrd-image-field2.js +1 -1
  81. package/dist/components/mrd-layout-section.js +1 -1
  82. package/dist/components/mrd-list-field2.js +1 -1
  83. package/dist/components/mrd-longtext-field2.js +1 -1
  84. package/dist/components/mrd-number-field2.js +1 -1
  85. package/dist/components/mrd-relation-field2.js +1 -1
  86. package/dist/components/mrd-secret-field2.js +1 -1
  87. package/dist/components/mrd-table2.js +1 -1
  88. package/dist/components/mrd-text-field2.js +1 -1
  89. package/dist/components/mrd-textarea-field2.js +1 -1
  90. package/dist/components/mrd-time-field2.js +1 -1
  91. package/dist/components/query-params.js +1 -0
  92. package/dist/esm/{index-_tsCCkAi.js → index-DiSEJMA8.js} +38 -1
  93. package/dist/esm/loader.js +3 -3
  94. package/dist/esm/mosterdcomponents.js +3 -3
  95. package/dist/esm/mrd-boolean-field_23.entry.js +1313 -1088
  96. package/dist/mosterdcomponents/mosterdcomponents.css +1 -1
  97. package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
  98. package/dist/mosterdcomponents/p-DiSEJMA8.js +2 -0
  99. package/dist/mosterdcomponents/p-b1a2e108.entry.js +1 -0
  100. package/dist/types/components/{mrd-document-container → documents/mrd-document-container}/mrd-document-container.d.ts +2 -2
  101. package/dist/types/components/documents/mrd-document-list/folder-tree.d.ts +66 -0
  102. package/dist/types/components/{mrd-document-list → documents/mrd-document-list}/mrd-document-list.d.ts +4 -37
  103. package/dist/types/components/documents/mrd-document-list/tree-view.d.ts +32 -0
  104. package/dist/types/components/{mrd-document-view → documents/mrd-document-view}/mrd-document-view.d.ts +2 -2
  105. package/dist/types/components/fields/field-helpers.d.ts +18 -0
  106. package/dist/types/components/fields/file-upload-common.d.ts +18 -0
  107. package/dist/types/components/{mrd-boolean-field → fields/mrd-boolean-field}/mrd-boolean-field.d.ts +1 -1
  108. package/dist/types/components/{mrd-currency-field → fields/mrd-currency-field}/mrd-currency-field.d.ts +2 -2
  109. package/dist/types/components/{mrd-date-field → fields/mrd-date-field}/mrd-date-field.d.ts +1 -1
  110. package/dist/types/components/{mrd-datetime-field → fields/mrd-datetime-field}/mrd-datetime-field.d.ts +1 -1
  111. package/dist/types/components/{mrd-email-field → fields/mrd-email-field}/mrd-email-field.d.ts +1 -1
  112. package/dist/types/components/{mrd-file-field → fields/mrd-file-field}/mrd-file-field.d.ts +2 -4
  113. package/dist/types/components/{mrd-hyperlink-field → fields/mrd-hyperlink-field}/mrd-hyperlink-field.d.ts +2 -2
  114. package/dist/types/components/{mrd-image-field → fields/mrd-image-field}/mrd-image-field.d.ts +2 -4
  115. package/dist/types/components/{mrd-list-field → fields/mrd-list-field}/mrd-list-field.d.ts +2 -2
  116. package/dist/types/components/{mrd-longtext-field → fields/mrd-longtext-field}/mrd-longtext-field.d.ts +1 -1
  117. package/dist/types/components/{mrd-number-field → fields/mrd-number-field}/mrd-number-field.d.ts +2 -2
  118. package/dist/types/components/{mrd-relation-field → fields/mrd-relation-field}/mrd-relation-field.d.ts +2 -2
  119. package/dist/types/components/{mrd-secret-field → fields/mrd-secret-field}/mrd-secret-field.d.ts +1 -1
  120. package/dist/types/components/{mrd-text-field → fields/mrd-text-field}/mrd-text-field.d.ts +1 -1
  121. package/dist/types/components/{mrd-textarea-field → fields/mrd-textarea-field}/mrd-textarea-field.d.ts +1 -1
  122. package/dist/types/components/{mrd-time-field → fields/mrd-time-field}/mrd-time-field.d.ts +1 -1
  123. package/dist/types/components/{mrd-field → form/mrd-field}/mrd-field.d.ts +2 -2
  124. package/dist/types/components/{mrd-form → form/mrd-form}/mrd-form.d.ts +2 -2
  125. package/dist/types/components/layout/mrd-layout-section/field-value.d.ts +22 -0
  126. package/dist/types/components/layout/mrd-layout-section/layout-helpers.d.ts +8 -0
  127. package/dist/types/components/{mrd-layout-section → layout/mrd-layout-section}/mrd-layout-section.d.ts +4 -11
  128. package/dist/types/components/{mrd-table → table/mrd-table}/mrd-table.d.ts +29 -13
  129. package/dist/types/components/table/mrd-table/table-filter-popup.d.ts +35 -0
  130. package/dist/types/components/table/mrd-table/table-parts.d.ts +61 -0
  131. package/dist/types/components/table/mrd-table/table-query.d.ts +50 -0
  132. package/dist/types/components/table/mrd-table/virtual-scroll.d.ts +33 -0
  133. package/dist/types/components.d.ts +19 -5
  134. package/dist/types/utils/query-params.d.ts +14 -0
  135. package/package.json +7 -2
  136. package/dist/components/validation.js +0 -1
  137. package/dist/mosterdcomponents/p-_tsCCkAi.js +0 -2
  138. package/dist/mosterdcomponents/p-a9618055.entry.js +0 -1
@@ -0,0 +1,85 @@
1
+ import { h } from "@stencil/core";
2
+ import { t } from "../../../utils/i18n";
3
+ import { TEXT_TYPES, NUMERIC_TYPES, DATE_TYPES, NO_FILTER_TYPES } from "./table-query";
4
+ export const FilterEditor = ({ col, dataType, openFilterCol, pendingFilter, locale, cb }) => {
5
+ var _a, _b, _c, _d, _e, _f;
6
+ const pf = pendingFilter;
7
+ if (NO_FILTER_TYPES.has(dataType)) {
8
+ return h("p", { class: "mrd-table__filter-no-support" }, t('filter_no_support', locale));
9
+ }
10
+ if (dataType === 'BOOLEAN') {
11
+ const boolOp = pf.operator;
12
+ const noValueOp = boolOp === 'isEmpty' || boolOp === 'isNotEmpty';
13
+ return (h("div", { class: "mrd-table__filter-radio-group" }, [
14
+ { labelKey: 'filter_all', value: null },
15
+ { labelKey: 'yes', value: true },
16
+ { labelKey: 'no', value: false },
17
+ ].map(opt => (h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${openFilterCol}`, checked: !noValueOp && pf.value === opt.value, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: undefined, value: opt.value })) }), t(opt.labelKey, locale)))), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${openFilterCol}`, checked: boolOp === 'isEmpty', onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: 'isEmpty', value: undefined })) }), t('filter_is_empty', locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${openFilterCol}`, checked: boolOp === 'isNotEmpty', onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: 'isNotEmpty', value: undefined })) }), t('filter_is_not_empty', locale))));
18
+ }
19
+ if (dataType === 'LIST') {
20
+ const items = (_a = col.listItems) !== null && _a !== void 0 ? _a : [];
21
+ const selected = (_b = pf.values) !== null && _b !== void 0 ? _b : [];
22
+ return (h("div", { class: "mrd-table__filter-list" }, h("div", { class: "mrd-table__filter-list-controls" }, h("button", { class: "mrd-table__filter-list-btn", onClick: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { values: items.map(i => i.key) })) }, t('filter_select_all', locale)), h("button", { class: "mrd-table__filter-list-btn", onClick: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { values: [] })) }, t('filter_select_none', locale))), items.map(item => (h("label", { class: "mrd-table__filter-checkbox-label" }, h("input", { type: "checkbox", checked: selected.includes(item.key), onChange: (e) => cb.onToggleValue(item.key, e.target.checked) }), item.label)))));
23
+ }
24
+ if (TEXT_TYPES.has(dataType) || dataType === 'RELATION') {
25
+ const op = (_c = pf.operator) !== null && _c !== void 0 ? _c : 'startsWith';
26
+ const noInput = op === 'isEmpty' || op === 'isNotEmpty';
27
+ return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => cb.onSetPending('operator', e.target.value) }, [
28
+ { val: 'startsWith', labelKey: 'filter_starts_with' },
29
+ { val: 'equals', labelKey: 'filter_equals' },
30
+ { val: 'isEmpty', labelKey: 'filter_is_empty' },
31
+ { val: 'isNotEmpty', labelKey: 'filter_is_not_empty' },
32
+ ].map(o => h("option", { value: o.val, selected: op === o.val }, t(o.labelKey, locale)))), !noInput && (h("input", { type: "text", class: "mrd-table__filter-input", value: String((_d = pf.value) !== null && _d !== void 0 ? _d : ''), placeholder: t('filter_search_value', locale), onInput: (e) => cb.onSetPending('value', e.target.value) }))));
33
+ }
34
+ if (NUMERIC_TYPES.has(dataType)) {
35
+ const numOp = pf.operator;
36
+ const noInput = numOp === 'isEmpty' || numOp === 'isNotEmpty';
37
+ const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
38
+ return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => {
39
+ const val = e.target.value;
40
+ if (val === 'isEmpty' || val === 'isNotEmpty') {
41
+ cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined }));
42
+ }
43
+ else {
44
+ cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: undefined }));
45
+ }
46
+ } }, h("option", { value: "", selected: !noInput }, t('filter_has_value', locale)), h("option", { value: "isEmpty", selected: numOp === 'isEmpty' }, t('filter_is_empty', locale)), h("option", { value: "isNotEmpty", selected: numOp === 'isNotEmpty' }, t('filter_is_not_empty', locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `nm-${openFilterCol}`, checked: !rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { from: undefined, to: undefined })) }), t('filter_exact', locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `nm-${openFilterCol}`, checked: rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null })) }), t('filter_range', locale))), !rangeMode ? (h("input", { type: "number", class: "mrd-table__filter-input", value: pf.value != null ? String(pf.value) : '', onInput: (e) => cb.onSetPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range" }, h("input", { type: "number", class: "mrd-table__filter-input", placeholder: t('filter_from', locale), value: pf.from != null ? String(pf.from) : '', onInput: (e) => cb.onSetPending('from', e.target.value) }), h("span", { class: "mrd-table__filter-range-sep" }, "\u2013"), h("input", { type: "number", class: "mrd-table__filter-input", placeholder: t('filter_to', locale), value: pf.to != null ? String(pf.to) : '', onInput: (e) => cb.onSetPending('to', e.target.value) })))))));
47
+ }
48
+ if (dataType === 'DATETIME') {
49
+ const dtOp = pf.operator;
50
+ const noInput = dtOp === 'isEmpty' || dtOp === 'isNotEmpty';
51
+ const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
52
+ return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => {
53
+ const val = e.target.value;
54
+ if (val === 'isEmpty' || val === 'isNotEmpty') {
55
+ cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined }));
56
+ }
57
+ else {
58
+ cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: undefined }));
59
+ }
60
+ } }, h("option", { value: "", selected: !noInput }, t('filter_has_value', locale)), h("option", { value: "isEmpty", selected: dtOp === 'isEmpty' }, t('filter_is_empty', locale)), h("option", { value: "isNotEmpty", selected: dtOp === 'isNotEmpty' }, t('filter_is_not_empty', locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${openFilterCol}`, checked: !rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { from: undefined, to: undefined })) }), t('filter_exact', locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${openFilterCol}`, checked: rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null })) }), t('filter_range', locale))), !rangeMode ? (h("input", { type: "date", class: "mrd-table__filter-input", value: String((_e = pf.value) !== null && _e !== void 0 ? _e : ''), onInput: (e) => cb.onSetPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range mrd-table__filter-range--stacked" }, h("label", { class: "mrd-table__filter-range-label" }, t('filter_from', locale)), h("input", { type: "date", class: "mrd-table__filter-input", value: pf.from != null ? String(pf.from) : '', onInput: (e) => cb.onSetPending('from', e.target.value) }), h("label", { class: "mrd-table__filter-range-label" }, t('filter_to', locale)), h("input", { type: "date", class: "mrd-table__filter-input", value: pf.to != null ? String(pf.to) : '', onInput: (e) => cb.onSetPending('to', e.target.value) })))))));
61
+ }
62
+ if (DATE_TYPES.has(dataType)) {
63
+ const inputType = dataType === 'DATE' ? 'date' : 'time';
64
+ const dtdOp = pf.operator;
65
+ const noInput = dtdOp === 'isEmpty' || dtdOp === 'isNotEmpty';
66
+ const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
67
+ return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => {
68
+ const val = e.target.value;
69
+ if (val === 'isEmpty' || val === 'isNotEmpty') {
70
+ cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined }));
71
+ }
72
+ else {
73
+ cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: undefined }));
74
+ }
75
+ } }, h("option", { value: "", selected: !noInput }, t('filter_has_value', locale)), h("option", { value: "isEmpty", selected: dtdOp === 'isEmpty' }, t('filter_is_empty', locale)), h("option", { value: "isNotEmpty", selected: dtdOp === 'isNotEmpty' }, t('filter_is_not_empty', locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${openFilterCol}`, checked: !rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { from: undefined, to: undefined })) }), t('filter_exact', locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${openFilterCol}`, checked: rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null })) }), t('filter_range', locale))), !rangeMode ? (h("input", { type: inputType, class: "mrd-table__filter-input", value: String((_f = pf.value) !== null && _f !== void 0 ? _f : ''), onInput: (e) => cb.onSetPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range" }, h("input", { type: inputType, class: "mrd-table__filter-input", placeholder: t('filter_from', locale), value: pf.from != null ? String(pf.from) : '', onInput: (e) => cb.onSetPending('from', e.target.value) }), h("input", { type: inputType, class: "mrd-table__filter-input", placeholder: t('filter_to', locale), value: pf.to != null ? String(pf.to) : '', onInput: (e) => cb.onSetPending('to', e.target.value) })))))));
76
+ }
77
+ return null;
78
+ };
79
+ export const FilterPopup = (props) => {
80
+ var _a;
81
+ const { col, openFilterCol, popupPos, sortField, sortDir, locale, cb } = props;
82
+ const label = (_a = col.label) !== null && _a !== void 0 ? _a : openFilterCol;
83
+ const sortActive = sortField === openFilterCol;
84
+ return (h("div", { class: "mrd-table__filter-popup", style: { top: `${popupPos.top}px`, left: `${popupPos.left}px` }, onClick: (e) => e.stopPropagation() }, h("div", { class: "mrd-table__filter-popup-header" }, h("span", { class: "mrd-table__filter-popup-title" }, label), h("button", { class: "mrd-table__filter-close", onClick: cb.onClose }, "\u2715")), h("div", { class: "mrd-table__filter-section" }, h("div", { class: "mrd-table__filter-section-label" }, t('filter_sorting', locale)), h("div", { class: "mrd-table__filter-sort-buttons" }, h("button", { class: `mrd-table__filter-sort-btn${sortActive && sortDir === 'asc' ? ' mrd-table__filter-sort-btn--active' : ''}`, onClick: () => cb.onApplySort('asc') }, "\u25B2 ", t('filter_ascending', locale)), h("button", { class: `mrd-table__filter-sort-btn${sortActive && sortDir === 'desc' ? ' mrd-table__filter-sort-btn--active' : ''}`, onClick: () => cb.onApplySort('desc') }, "\u25BC ", t('filter_descending', locale)))), h("div", { class: "mrd-table__filter-divider" }), h("div", { class: "mrd-table__filter-section" }, h("div", { class: "mrd-table__filter-section-label" }, t('filter_section', locale)), h(FilterEditor, Object.assign({}, props))), h("div", { class: "mrd-table__filter-popup-footer" }, h("button", { class: "mrd-table__filter-btn mrd-table__filter-btn--clear", onClick: cb.onClear }, t('filter_clear', locale)), h("button", { class: "mrd-table__filter-btn mrd-table__filter-btn--apply", onClick: cb.onApply }, t('filter_apply', locale)))));
85
+ };
@@ -0,0 +1,110 @@
1
+ import { h } from "@stencil/core";
2
+ import { CellRenderer } from "../../../utils/cell-renderer";
3
+ import { t } from "../../../utils/i18n";
4
+ import { formatNumber, formatPercentage, formatCurrency } from "../../../utils/format";
5
+ import { NUMERIC_TYPES } from "./table-query";
6
+ /**
7
+ * Stateless render parts of mrd-table. All state and handlers stay in the
8
+ * component; these components receive plain values and callbacks.
9
+ */
10
+ export const FilterIcon = () => (h("span", { class: "mrd-table__filter-icon", "aria-hidden": "true" }, h("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "currentColor" }, h("path", { d: "M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" }))));
11
+ const TEXTBLOCK_MAX = 200;
12
+ export const TableCell = ({ col, row, locale, onDownload, onOpenTextblock, onOpenJson }) => {
13
+ var _a, _b, _c;
14
+ const dataType = (_a = col.dataType) !== null && _a !== void 0 ? _a : '';
15
+ const isNumeric = col.type === 'FIELD' && NUMERIC_TYPES.has(dataType);
16
+ const isFile = col.type === 'FIELD' && (dataType === 'FILE' || dataType === 'IMAGE');
17
+ if (isFile) {
18
+ const name = (_b = col.name) !== null && _b !== void 0 ? _b : '';
19
+ const fileVal = row === null || row === void 0 ? void 0 : row[name];
20
+ const href = fileVal === null || fileVal === void 0 ? void 0 : fileVal.href;
21
+ const fileName = fileVal === null || fileVal === void 0 ? void 0 : fileVal.fileName;
22
+ return (h("td", { class: "mrd-table__cell" }, href && fileName ? (h("button", { class: "mrd-table__file-btn", title: fileName, onClick: (e) => {
23
+ e.stopPropagation();
24
+ onDownload({ href, fileName });
25
+ } }, h("svg", { class: "mrd-table__file-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 7V3.5L18.5 9H13zm-3 8l-3-3 1.41-1.41L10 14.17l4.59-4.58L16 11l-6 6z" })), t('download', locale))) : ''));
26
+ }
27
+ if (dataType === 'JSON') {
28
+ const name = (_c = col.name) !== null && _c !== void 0 ? _c : '';
29
+ const rawValue = name ? row === null || row === void 0 ? void 0 : row[name] : undefined;
30
+ if (rawValue == null || rawValue === '')
31
+ return h("td", { class: "mrd-table__cell" });
32
+ const compactHtml = CellRenderer.formatJson(rawValue, true);
33
+ const prettyHtml = CellRenderer.formatJson(rawValue, false);
34
+ return (h("td", { class: "mrd-table__cell" }, h("span", { class: "mrd-table__json-preview", innerHTML: compactHtml }), h("button", { class: "mrd-table__textblock-btn", onClick: (e) => { e.stopPropagation(); onOpenJson(prettyHtml); }, "aria-label": t('textblock_show_more', locale) }, "\u22EF")));
35
+ }
36
+ if (dataType === 'TEXTBLOCK') {
37
+ const full = CellRenderer.render(col, row, locale);
38
+ if (full.length <= TEXTBLOCK_MAX) {
39
+ return h("td", { class: "mrd-table__cell" }, full);
40
+ }
41
+ const preview = full.slice(0, TEXTBLOCK_MAX) + '…';
42
+ return (h("td", { class: "mrd-table__cell" }, preview, h("button", { class: "mrd-table__textblock-btn", onClick: (e) => { e.stopPropagation(); onOpenTextblock(full); }, "aria-label": t('textblock_show_more', locale) }, "\u22EF")));
43
+ }
44
+ const value = CellRenderer.render(col, row, locale);
45
+ return (h("td", { class: `mrd-table__cell${isNumeric ? ' mrd-table__cell--numeric' : ''}` }, value));
46
+ };
47
+ /** Formats the aggregation total for one column, '' when not applicable. */
48
+ export function formatAggregationValue(col, aggregations, locale) {
49
+ var _a;
50
+ if (col.type !== 'FIELD' || !col.aggregate || !aggregations)
51
+ return '';
52
+ const fn = col.aggregate.toLowerCase();
53
+ const aggData = aggregations[fn];
54
+ const val = typeof aggData === 'object' && aggData !== null
55
+ ? aggData[(_a = col.name) !== null && _a !== void 0 ? _a : '']
56
+ : undefined;
57
+ if (val == null)
58
+ return '';
59
+ const dt = col.dataType;
60
+ if (dt === 'INTEGER')
61
+ return formatNumber(val, locale, { maximumFractionDigits: 0 });
62
+ if (dt === 'PERCENTAGE')
63
+ return formatPercentage(val, locale);
64
+ if (dt === 'CURRENCY' && col.currencyCode)
65
+ return formatCurrency(val, col.currencyCode, locale);
66
+ return formatNumber(val, locale);
67
+ }
68
+ export const TotalsRow = ({ columns, aggregations, locale }) => {
69
+ if (!aggregations)
70
+ return null;
71
+ if (!columns.some(c => c.type === 'FIELD' && c.aggregate))
72
+ return null;
73
+ return (h("tfoot", null, h("tr", { class: "mrd-table__totals-row" }, columns.map(col => {
74
+ var _a;
75
+ const val = formatAggregationValue(col, aggregations, locale);
76
+ const isNumeric = col.type === 'FIELD' && NUMERIC_TYPES.has((_a = col.dataType) !== null && _a !== void 0 ? _a : '');
77
+ return (h("td", { class: `mrd-table__totals-cell${isNumeric ? ' mrd-table__totals-cell--numeric' : ''}` }, val));
78
+ }))));
79
+ };
80
+ export const TextblockModal = ({ text, locale, onClose }) => {
81
+ if (text === null)
82
+ return null;
83
+ return (h("div", { class: "mrd-table__modal-backdrop", onClick: onClose, role: "dialog", "aria-modal": "true" }, h("div", { class: "mrd-table__modal", onClick: (e) => e.stopPropagation() }, h("button", { class: "mrd-table__modal-close", onClick: onClose, "aria-label": t('close', locale) }, "\u2715"), h("p", { class: "mrd-table__modal-text" }, text))));
84
+ };
85
+ export const JsonModal = ({ html, locale, onClose }) => {
86
+ if (html === null)
87
+ return null;
88
+ return (h("div", { class: "mrd-table__modal-backdrop", onClick: onClose, role: "dialog", "aria-modal": "true" }, h("div", { class: "mrd-table__modal", onClick: (e) => e.stopPropagation() }, h("button", { class: "mrd-table__modal-close", onClick: onClose, "aria-label": t('close', locale) }, "\u2715"), h("pre", { class: "mrd-table__modal-json", innerHTML: html }))));
89
+ };
90
+ export const Toolbar = ({ locale, filterMode, filterCount, actions, views, activeViewIdx, viewPopoverOpen, createPickerOpen, createTypes, cb }) => {
91
+ var _a;
92
+ const hasActions = actions.length > 0;
93
+ const hasViewSwitcher = views.length > 1;
94
+ return (h("div", { class: "mrd-table__toolbar" }, h("div", { class: "mrd-table__toolbar-left" }, h("button", { class: `mrd-table__action mrd-table__action--secondary mrd-table__filter-toggle${filterMode ? ' mrd-table__filter-toggle--active' : ''}`, onClick: cb.onFilterToggle }, h("svg", { class: "mrd-table__action-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" })), filterCount > 0 && h("span", { class: "mrd-table__filter-badge" }, filterCount), h("span", { class: "mrd-table__action-tooltip" }, filterMode ? t('table_filter_hide', locale) : t('table_filter', locale), filterCount > 0 ? ` (${filterCount} ${t('table_filter_active', locale)})` : '')), filterCount > 0 && (h("button", { class: "mrd-table__action mrd-table__action--secondary", onClick: cb.onClearAllFilters }, h("svg", { class: "mrd-table__action-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" })), h("span", { class: "mrd-table__action-tooltip" }, t('table_filter_clear_all', locale))))), hasViewSwitcher && (h("div", { class: "mrd-table__toolbar-center" }, h("div", { class: "mrd-table__view-switcher" }, h("button", { class: "mrd-table__view-arrow", "aria-label": "Previous view", onClick: () => cb.onViewSwitch((activeViewIdx - 1 + views.length) % views.length) }, "\u25C0"), h("div", { class: "mrd-table__view-title-wrap" }, h("button", { class: "mrd-table__view-title", onClick: (e) => cb.onToggleViewPopover(e) }, (_a = views[activeViewIdx]) === null || _a === void 0 ? void 0 : _a.label), viewPopoverOpen && (h("div", { class: "mrd-table__view-popover", onClick: (e) => e.stopPropagation() }, views.map((v, i) => (h("button", { key: String(i), class: `mrd-table__view-popover-item${i === activeViewIdx ? ' mrd-table__view-popover-item--active' : ''}`, onClick: () => {
95
+ if (i !== activeViewIdx)
96
+ cb.onViewSwitch(i);
97
+ cb.onCloseViewPopover();
98
+ } }, v.label)))))), h("button", { class: "mrd-table__view-arrow", "aria-label": "Next view", onClick: () => cb.onViewSwitch((activeViewIdx + 1) % views.length) }, "\u25B6")))), hasActions && (h("div", { class: "mrd-table__toolbar-right" }, actions.map(a => {
99
+ var _a, _b;
100
+ const pickerTypes = a.action === 'create' ? createTypes : null;
101
+ if (pickerTypes === null || pickerTypes === void 0 ? void 0 : pickerTypes.length) {
102
+ return (h("div", { class: "mrd-table__create-picker-wrap", key: `action-${a.action}` }, h("button", { class: `mrd-table__action mrd-table__action--${(_a = a.variant) !== null && _a !== void 0 ? _a : 'secondary'}`, onClick: cb.onToggleCreatePicker }, a.icon
103
+ ? h("svg", { class: "mrd-table__action-icon", "aria-hidden": "true" }, h("use", { href: a.icon }))
104
+ : a.label, h("span", { class: "mrd-table__action-tooltip" }, a.label)), createPickerOpen && (h("div", { class: "mrd-table__create-picker" }, pickerTypes.map(ct => (h("button", { key: ct.type, class: "mrd-table__create-picker-item", onClick: () => cb.onCreate(ct.type) }, ct.label)))))));
105
+ }
106
+ return (h("button", { key: `action-${a.action}`, class: `mrd-table__action mrd-table__action--${(_b = a.variant) !== null && _b !== void 0 ? _b : 'secondary'}`, disabled: a.disabled, onClick: () => cb.onAction(a.action) }, a.icon
107
+ ? h("svg", { class: "mrd-table__action-icon", "aria-hidden": "true" }, h("use", { href: a.icon }))
108
+ : a.label, h("span", { class: "mrd-table__action-tooltip" }, a.label)));
109
+ })))));
110
+ };
@@ -0,0 +1,178 @@
1
+ import { applyViewFilters } from "../../../utils/query-params";
2
+ /**
3
+ * Pure sort/filter/aggregation query logic for mrd-table.
4
+ * No component state — everything is passed in.
5
+ */
6
+ export const TEXT_TYPES = new Set(['TEXT', 'TEXTBLOCK', 'EMAIL', 'HYPERLINK']);
7
+ export const NUMERIC_TYPES = new Set(['INTEGER', 'DECIMAL', 'PERCENTAGE', 'CURRENCY']);
8
+ export const DATE_TYPES = new Set(['DATE', 'DATETIME', 'TIME']);
9
+ export const NO_FILTER_TYPES = new Set(['FILE', 'IMAGE']);
10
+ /** Column types that cannot be sorted or filtered (not stored in PostgreSQL). */
11
+ export const NON_INTERACTIVE_TYPES = new Set(['LONGTEXT', 'JSON', 'FILE', 'IMAGE']);
12
+ export function parseDefaultSort(defaultSort) {
13
+ var _a;
14
+ if (!defaultSort)
15
+ return { field: '', dir: 'asc' };
16
+ const parts = defaultSort.split(',');
17
+ return { field: parts[0].trim(), dir: ((_a = parts[1]) === null || _a === void 0 ? void 0 : _a.trim()) === 'desc' ? 'desc' : 'asc' };
18
+ }
19
+ /** Raw sort query-param value, e.g. "name" or "name,desc". */
20
+ export function buildSortParam(field, dir) {
21
+ if (!field)
22
+ return '';
23
+ return dir === 'desc' ? `${field},desc` : field;
24
+ }
25
+ /** Query params for a page request: page + sort + filterClass + view filters + active column filters. */
26
+ export function buildTableQueryParams(opts) {
27
+ var _a, _b;
28
+ const p = new URLSearchParams();
29
+ if (opts.page > 0)
30
+ p.set('page', String(opts.page));
31
+ if (opts.sort)
32
+ p.set('sort', opts.sort);
33
+ if (opts.filterClass)
34
+ p.set('type', opts.filterClass);
35
+ applyViewFilters(p, opts.viewFilters);
36
+ for (const f of opts.activeFilters) {
37
+ if (f.operator === 'isEmpty') {
38
+ p.set(f.field, '');
39
+ continue;
40
+ }
41
+ if (f.operator === 'isNotEmpty') {
42
+ p.set(f.field + '_notempty', 'true');
43
+ continue;
44
+ }
45
+ if (f.operator === 'startsWith') {
46
+ p.set(f.field + '_startswith', String((_a = f.value) !== null && _a !== void 0 ? _a : ''));
47
+ continue;
48
+ }
49
+ if ((_b = f.values) === null || _b === void 0 ? void 0 : _b.length) {
50
+ p.set(f.field, f.values.join(','));
51
+ continue;
52
+ }
53
+ if (f.value != null)
54
+ p.set(f.field, String(f.value));
55
+ if (f.from != null)
56
+ p.set(f.field + '_from', String(f.from));
57
+ if (f.to != null)
58
+ p.set(f.field + '_to', String(f.to));
59
+ }
60
+ return p.toString();
61
+ }
62
+ /** Groups aggregate columns by function; null when the view has no aggregates. */
63
+ export function buildAggregationParams(columns) {
64
+ var _a;
65
+ const groups = { sum: [], avg: [], count: [] };
66
+ for (const col of columns) {
67
+ if (col.type !== 'FIELD' || !col.aggregate)
68
+ continue;
69
+ const fn = col.aggregate.toLowerCase();
70
+ if (fn in groups)
71
+ groups[fn].push((_a = col.name) !== null && _a !== void 0 ? _a : '');
72
+ }
73
+ const params = {};
74
+ if (groups.sum.length)
75
+ params.sum = groups.sum;
76
+ if (groups.avg.length)
77
+ params.avg = groups.avg;
78
+ if (groups.count.length)
79
+ params.count = groups.count;
80
+ return Object.keys(params).length > 0 ? params : null;
81
+ }
82
+ /** Aggregation query string: the page-0 query params without page/sort, plus sum/avg/count. */
83
+ export function buildAggregationQs(basePageQs, columns) {
84
+ var _a, _b, _c;
85
+ const p = new URLSearchParams(basePageQs);
86
+ p.delete('page');
87
+ p.delete('sort');
88
+ const groups = buildAggregationParams(columns);
89
+ if ((_a = groups === null || groups === void 0 ? void 0 : groups.sum) === null || _a === void 0 ? void 0 : _a.length)
90
+ p.set('sum', groups.sum.join(','));
91
+ if ((_b = groups === null || groups === void 0 ? void 0 : groups.avg) === null || _b === void 0 ? void 0 : _b.length)
92
+ p.set('avg', groups.avg.join(','));
93
+ if ((_c = groups === null || groups === void 0 ? void 0 : groups.count) === null || _c === void 0 ? void 0 : _c.length)
94
+ p.set('count', groups.count.join(','));
95
+ return p.toString();
96
+ }
97
+ /** True when the pending filter actually constrains anything. */
98
+ export function filterHasValue(f) {
99
+ if (f.operator === 'isEmpty' || f.operator === 'isNotEmpty')
100
+ return true;
101
+ if (f.values !== undefined && f.values.length > 0)
102
+ return true;
103
+ if (f.value != null && f.value !== '')
104
+ return true;
105
+ if (typeof f.value === 'boolean')
106
+ return true;
107
+ if (f.from != null && f.from !== '')
108
+ return true;
109
+ if (f.to != null && f.to !== '')
110
+ return true;
111
+ return false;
112
+ }
113
+ // ── DATETIME filters: the user edits local "YYYY-MM-DD" dates, the API stores UTC ISO ──
114
+ /** UTC ISO string at the start (midnight) of the given local day. */
115
+ export function dateLocalToUTCStart(dateStr) {
116
+ if (!dateStr)
117
+ return dateStr;
118
+ const [year, month, day] = dateStr.split('-').map(Number);
119
+ return new Date(year, month - 1, day).toISOString().replace(/\.\d{3}Z$/, 'Z');
120
+ }
121
+ /** Start of the day AFTER the given local date (exclusive range end). */
122
+ export function dateLocalToUTCEndExclusive(dateStr) {
123
+ if (!dateStr)
124
+ return dateStr;
125
+ const [year, month, day] = dateStr.split('-').map(Number);
126
+ return new Date(year, month - 1, day + 1).toISOString().replace(/\.\d{3}Z$/, 'Z');
127
+ }
128
+ /** Stored UTC ISO string back to the local "YYYY-MM-DD" date. */
129
+ export function utcISOToLocalDate(utcStr) {
130
+ if (!utcStr)
131
+ return utcStr;
132
+ const d = new Date(utcStr);
133
+ if (isNaN(d.getTime()))
134
+ return utcStr;
135
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
136
+ }
137
+ /** The stored "to" is the exclusive end (next midnight); subtract a day to recover the entered date. */
138
+ export function utcISOToLocalDateExclusiveEnd(utcStr) {
139
+ if (!utcStr)
140
+ return utcStr;
141
+ const d = new Date(utcStr);
142
+ if (isNaN(d.getTime()))
143
+ return utcStr;
144
+ d.setDate(d.getDate() - 1);
145
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
146
+ }
147
+ /** Applies the local→UTC conversion when a DATETIME filter is submitted.
148
+ * Exact date → range covering the full local day. */
149
+ export function normalizeDatetimeFilter(f) {
150
+ const normalized = Object.assign({}, f);
151
+ if (f.dataType === 'DATETIME' && f.operator !== 'isEmpty' && f.operator !== 'isNotEmpty') {
152
+ if (typeof normalized.value === 'string' && normalized.value) {
153
+ normalized.from = dateLocalToUTCStart(normalized.value);
154
+ normalized.to = dateLocalToUTCEndExclusive(normalized.value);
155
+ normalized.value = undefined;
156
+ }
157
+ else {
158
+ if (typeof normalized.from === 'string' && normalized.from)
159
+ normalized.from = dateLocalToUTCStart(normalized.from);
160
+ if (typeof normalized.to === 'string' && normalized.to)
161
+ normalized.to = dateLocalToUTCEndExclusive(normalized.to);
162
+ }
163
+ }
164
+ return normalized;
165
+ }
166
+ /** Converts a stored DATETIME filter back to local dates for the popup.
167
+ * A range covering a single local day is restored to exact-date mode. */
168
+ export function datetimeFilterToDisplay(existing) {
169
+ const display = Object.assign({}, existing);
170
+ if (typeof display.from === 'string' && display.from)
171
+ display.from = utcISOToLocalDate(display.from);
172
+ if (typeof display.to === 'string' && display.to)
173
+ display.to = utcISOToLocalDateExclusiveEnd(display.to);
174
+ if (display.from && display.to && display.from === display.to) {
175
+ return Object.assign(Object.assign({}, display), { value: display.from, from: undefined, to: undefined });
176
+ }
177
+ return display;
178
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Pure window/paging maths for the mrd-table virtual scroll.
3
+ * The window state itself stays @State in the component; these
4
+ * functions only compute values from it.
5
+ */
6
+ export const BUFFER = 10;
7
+ /** Wait this long (ms) after the last scroll event before requesting pages,
8
+ * so pages the user quickly scrolls past are skipped. */
9
+ export const REQUEST_DEBOUNCE_MS = 150;
10
+ /** Default time (ms) to wait for setPage() before treating a request as stale and retrying. */
11
+ export const REQUEST_TIMEOUT_MS = 15000;
12
+ /** Max retries for a stale page request before giving up and showing a retry row. */
13
+ export const MAX_RETRY_ATTEMPTS = 3;
14
+ export function visibleCount(tableHeight, rowHeight) {
15
+ return Math.ceil(tableHeight / rowHeight);
16
+ }
17
+ /** Render window (with buffer) for the given scroll offset. */
18
+ export function computeWindow(scrollTop, rowHeight, tableHeight, total) {
19
+ const visStart = Math.floor(scrollTop / rowHeight);
20
+ const visEnd = Math.min(visStart + visibleCount(tableHeight, rowHeight), total - 1);
21
+ return {
22
+ renderStart: Math.max(0, visStart - BUFFER),
23
+ renderEnd: Math.min(total - 1, visEnd + BUFFER),
24
+ };
25
+ }
26
+ /** First/last page (0-based) covering the given row window. */
27
+ export function pageRange(start, end, pageSize) {
28
+ return { firstPage: Math.floor(start / pageSize), lastPage: Math.floor(end / pageSize) };
29
+ }
30
+ export function getRowFromPages(pages, index, pageSize) {
31
+ var _a;
32
+ const page = pages.get(Math.floor(index / pageSize));
33
+ return (_a = page === null || page === void 0 ? void 0 : page[index % pageSize]) !== null && _a !== void 0 ? _a : null;
34
+ }
35
+ /** Authoritative row count: a page shorter than pageSize is the last page,
36
+ * so the true total cannot exceed its end index. */
37
+ export function computeEffectiveTotal(loadedPages, pageSize, baseTotal) {
38
+ let effectiveTotal = baseTotal;
39
+ for (const [pageNum, pageRows] of loadedPages) {
40
+ if (pageRows.length < pageSize) {
41
+ effectiveTotal = Math.min(effectiveTotal, pageNum * pageSize + pageRows.length);
42
+ }
43
+ }
44
+ return effectiveTotal;
45
+ }
46
+ /** True when some loaded page was short — the exact total is then known. */
47
+ export function isTotalKnownFromPages(loadedPages, pageSize) {
48
+ for (const rows of loadedPages.values()) {
49
+ if (rows.length < pageSize)
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ /** Pages whose request timestamp is older than timeoutMs — candidates for retry or failure.
55
+ * Caller is expected to already exclude pages present in loadedPages. */
56
+ export function getStalePages(requestedAt, now, timeoutMs) {
57
+ const stale = [];
58
+ for (const [page, ts] of requestedAt) {
59
+ if (now - ts >= timeoutMs)
60
+ stale.push(page);
61
+ }
62
+ return stale;
63
+ }
@@ -119,27 +119,33 @@ function showTab(name) {
119
119
 
120
120
  let _detailViewInitialized = false;
121
121
 
122
- function initDetailViewTab() {
122
+ async function initDetailViewTab() {
123
123
  if (_detailViewInitialized) return;
124
124
  _detailViewInitialized = true;
125
125
 
126
126
  const table = document.getElementById('companies-table');
127
- const meta = window.EXAMPLE_COMPANY_METADATA;
127
+ if (typeof table.componentOnReady === 'function') {
128
+ await table.componentOnReady();
129
+ } else {
130
+ await customElements.whenDefined('mrd-table');
131
+ }
128
132
 
129
- table.columns = window.EXAMPLE_COMPANY_COLUMNS;
133
+ table.item = window.EXAMPLE_COMPANY_VIEW_ITEM;
130
134
  table.totalElements = window.EXAMPLE_COMPANIES.length;
131
- table.pageSize = 20;
132
- table.locale = 'nl-NL';
135
+ table.pageSize = 20;
136
+ table.locale = 'nl-NL';
133
137
 
138
+ // Register before init(): init() emits mrdLoadPage for page 0 itself.
134
139
  table.addEventListener('mrdLoadPage', async (e) => {
135
- await table.setPage(e.detail.page, window.EXAMPLE_COMPANIES);
140
+ logEvent('mrdLoadPage', e.detail);
141
+ await table.setPage(e.detail.page, window.EXAMPLE_COMPANIES, false);
136
142
  });
137
143
 
138
144
  table.addEventListener('mrdRowClick', (e) => {
139
145
  showCompanyDetail(e.detail);
140
146
  });
141
147
 
142
- table.init().then(() => table.setPage(0, window.EXAMPLE_COMPANIES));
148
+ await table.init();
143
149
  }
144
150
 
145
151
  function showCompanyDetail(row) {
@@ -166,24 +172,19 @@ function showCompanyDetail(row) {
166
172
  const section = document.createElement('mrd-layout-section');
167
173
  section.items = layout.items;
168
174
  section.data = company;
169
- section.views = meta.views;
170
175
  section.locale = 'nl-NL';
171
176
 
172
177
  section.addEventListener('mrdNavigate', (e) => {
173
178
  logEvent('mrdNavigate', e.detail);
174
179
  });
175
180
 
176
- section.addEventListener('mrdLoadView', async (e) => {
177
- const { name, viewConfig } = e.detail;
178
- const mockRows = name === 'view0' ? window.EXAMPLE_APPOINTMENTS : window.EXAMPLE_SHARE_CLASSES;
179
- await section.setViewPage(name, 0, mockRows, mockRows.length);
180
- });
181
-
182
181
  section.addEventListener('mrdLoadViewPage', async (e) => {
183
- // No more pages in the mock just set the same rows again
184
- const { name } = e.detail;
185
- const mockRows = name === 'view0' ? window.EXAMPLE_APPOINTMENTS : window.EXAMPLE_SHARE_CLASSES;
186
- await section.setViewPage(name, e.detail.page, mockRows);
182
+ // Single mock page: answer every page request with the same rows + total.
183
+ // `name` is the view key: relatedClass for a RELATED_VIEW item.
184
+ const { name, page } = e.detail;
185
+ logEvent('mrdLoadViewPage', e.detail);
186
+ const mockRows = name === 'appointments' ? window.EXAMPLE_APPOINTMENTS : window.EXAMPLE_SHARE_CLASSES;
187
+ await section.setViewPage(name, page, mockRows, mockRows.length);
187
188
  });
188
189
 
189
190
  wrapper.appendChild(section);