@xleddyl/443-components-vue 0.8.1 → 0.8.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 (164) hide show
  1. package/README.md +52 -4
  2. package/dist/chart/BarChart.vue +33 -22
  3. package/dist/chart/BarChart.vue.d.ts +4 -0
  4. package/dist/chart/chart.css +2 -2
  5. package/dist/chart/useChartColors.mjs +8 -8
  6. package/dist/components/core/Button.vue.d.ts +1 -1
  7. package/dist/components/core/FormField.vue +5 -0
  8. package/dist/components/core/FormField.vue.d.ts +4 -0
  9. package/dist/components/core/Input.vue +59 -5
  10. package/dist/components/core/Input.vue.d.ts +9 -5
  11. package/dist/components/core/Textarea.vue +28 -3
  12. package/dist/components/core/Textarea.vue.d.ts +7 -5
  13. package/dist/components/core/types.d.ts +1 -1
  14. package/dist/components/data/Badge.vue.d.ts +1 -1
  15. package/dist/components/data/DescriptionList.vue.d.ts +1 -1
  16. package/dist/components/data/FilterPills.vue +43 -4
  17. package/dist/components/data/FilterPills.vue.d.ts +4 -1
  18. package/dist/components/data/Pagination.vue +63 -2
  19. package/dist/components/data/Pagination.vue.d.ts +12 -0
  20. package/dist/components/data/Tag.vue.d.ts +1 -1
  21. package/dist/components/data/Tree.vue.d.ts +1 -1
  22. package/dist/components/data/types.d.ts +1 -0
  23. package/dist/components/feedback/Meter.vue.d.ts +1 -1
  24. package/dist/components/feedback/Progress.vue.d.ts +1 -1
  25. package/dist/components/feedback/ProgressRing.vue.d.ts +1 -1
  26. package/dist/components/forms/Calendar.vue +31 -7
  27. package/dist/components/forms/Calendar.vue.d.ts +13 -2
  28. package/dist/components/forms/CheckboxGroup.vue +7 -1
  29. package/dist/components/forms/CheckboxGroup.vue.d.ts +1 -0
  30. package/dist/components/forms/ColorInput.vue.d.ts +5 -5
  31. package/dist/components/forms/Combobox.vue +4 -3
  32. package/dist/components/forms/Combobox.vue.d.ts +1 -0
  33. package/dist/components/forms/DateInput.vue.d.ts +10 -6
  34. package/dist/components/forms/DatePicker.vue.d.ts +1 -1
  35. package/dist/components/forms/FileInput.vue.d.ts +1 -1
  36. package/dist/components/forms/NumberInput.vue.d.ts +5 -5
  37. package/dist/components/forms/PasswordInput.vue.d.ts +9 -5
  38. package/dist/components/forms/PinInput.vue.d.ts +5 -5
  39. package/dist/components/forms/Select.vue +21 -2
  40. package/dist/components/forms/Select.vue.d.ts +4 -4
  41. package/dist/components/forms/Slider.vue.d.ts +5 -5
  42. package/dist/components/forms/Switch.vue.d.ts +1 -1
  43. package/dist/components/forms/TimeInput.vue.d.ts +9 -5
  44. package/dist/components/layout/AppShell.vue +4 -2
  45. package/dist/components/layout/AppShell.vue.d.ts +2 -0
  46. package/dist/components/layout/Card.vue.d.ts +1 -1
  47. package/dist/components/layout/CopyButton.vue.d.ts +1 -1
  48. package/dist/components/layout/PageHeader.vue +15 -3
  49. package/dist/components/layout/PageHeader.vue.d.ts +5 -0
  50. package/dist/components/layout/Separator.vue +17 -4
  51. package/dist/components/layout/Separator.vue.d.ts +7 -1
  52. package/dist/components/layout/Tabs.vue +10 -0
  53. package/dist/components/layout/types.d.ts +3 -0
  54. package/dist/components/overlays/ConfirmDialog.vue.d.ts +8 -4
  55. package/dist/components/overlays/ConfirmHost.vue.d.ts +8 -4
  56. package/dist/components/overlays/ContextMenu.vue.d.ts +4 -2
  57. package/dist/components/overlays/DropdownMenu.vue +5 -1
  58. package/dist/components/overlays/DropdownMenu.vue.d.ts +8 -2
  59. package/dist/components/overlays/Modal.vue +9 -2
  60. package/dist/components/overlays/Modal.vue.d.ts +4 -0
  61. package/dist/components/overlays/types.d.ts +2 -2
  62. package/dist/components/templates/DashboardLayout.vue +2 -0
  63. package/dist/components/templates/DashboardLayout.vue.d.ts +2 -0
  64. package/dist/components/templates/SettingsModal.vue.d.ts +8 -4
  65. package/dist/components/templates/types.d.ts +1 -0
  66. package/dist/composables/useForm.d.ts +24 -2
  67. package/dist/composables/useForm.mjs +51 -8
  68. package/dist/composables/useOverlay.d.ts +1 -1
  69. package/dist/editor/RichTextEditor.vue +108 -10
  70. package/dist/editor/RichTextEditor.vue.d.ts +5 -2
  71. package/dist/editor/rich-text-editor.css +31 -4
  72. package/dist/editor/types.d.ts +2 -1
  73. package/dist/grid/DataGrid.vue +106 -24
  74. package/dist/grid/DataGrid.vue.d.ts +14 -2
  75. package/dist/grid/cells/DateCell.vue +18 -3
  76. package/dist/grid/cells/ImageCell.vue +6 -1
  77. package/dist/grid/data-grid.css +17 -3
  78. package/dist/grid/index.d.ts +2 -1
  79. package/dist/grid/index.mjs +6 -1
  80. package/dist/grid/modules.d.ts +2 -0
  81. package/dist/grid/modules.mjs +32 -0
  82. package/dist/grid/theme.mjs +5 -5
  83. package/dist/grid/types.d.ts +4 -0
  84. package/dist/icons/builtin.d.ts +3 -0
  85. package/dist/icons/builtin.mjs +3 -0
  86. package/dist/index.d.ts +1 -1
  87. package/dist/index.mjs +1 -0
  88. package/dist/internal/core/intl.d.ts +2 -0
  89. package/dist/internal/core/intl.mjs +17 -0
  90. package/dist/internal/forms/counter.d.ts +18 -0
  91. package/dist/internal/forms/counter.mjs +31 -0
  92. package/dist/internal/keys.d.ts +2 -1
  93. package/dist/internal/keys.mjs +1 -0
  94. package/dist/internal/overlays/MenuList.vue +4 -2
  95. package/dist/internal/overlays/MenuList.vue.d.ts +2 -0
  96. package/dist/internal/overlays/OverlayView.vue.d.ts +8 -4
  97. package/dist/locale/de.mjs +21 -3
  98. package/dist/locale/en.mjs +21 -3
  99. package/dist/locale/it.mjs +21 -3
  100. package/dist/locale/types.d.ts +18 -0
  101. package/dist/nuxt/runtime/layouts/dashboard.vue +1 -0
  102. package/dist/styles/base.css +2 -1
  103. package/dist/styles/core/button.css +42 -16
  104. package/dist/styles/core/form-field.css +7 -1
  105. package/dist/styles/core/input.css +31 -2
  106. package/dist/styles/core/kbd.css +1 -1
  107. package/dist/styles/core/textarea.css +10 -0
  108. package/dist/styles/data/avatar.css +1 -1
  109. package/dist/styles/data/badge.css +39 -23
  110. package/dist/styles/data/description-list.css +2 -2
  111. package/dist/styles/data/filter-pills.css +24 -3
  112. package/dist/styles/data/pagination.css +26 -3
  113. package/dist/styles/data/selection-bar.css +1 -1
  114. package/dist/styles/data/stat.css +4 -4
  115. package/dist/styles/data/table.css +4 -4
  116. package/dist/styles/data/tag.css +1 -1
  117. package/dist/styles/data/timeline.css +1 -1
  118. package/dist/styles/data/tree.css +2 -2
  119. package/dist/styles/feedback/empty-state.css +1 -1
  120. package/dist/styles/feedback/loading-host.css +1 -1
  121. package/dist/styles/feedback/loading-overlay.css +1 -1
  122. package/dist/styles/feedback/meter.css +1 -1
  123. package/dist/styles/feedback/progress.css +2 -2
  124. package/dist/styles/forced-colors.css +6 -1
  125. package/dist/styles/forms/calendar.css +30 -3
  126. package/dist/styles/forms/checkbox.css +1 -1
  127. package/dist/styles/forms/choice.css +7 -1
  128. package/dist/styles/forms/combobox.css +2 -2
  129. package/dist/styles/forms/file-input.css +1 -1
  130. package/dist/styles/forms/password-input.css +1 -1
  131. package/dist/styles/forms/pin-input.css +1 -1
  132. package/dist/styles/forms/radio.css +1 -1
  133. package/dist/styles/forms/segmented.css +2 -2
  134. package/dist/styles/forms/select.css +2 -2
  135. package/dist/styles/forms/slider.css +1 -1
  136. package/dist/styles/forms/switch.css +4 -4
  137. package/dist/styles/layout/accordion.css +2 -2
  138. package/dist/styles/layout/app-shell.css +18 -6
  139. package/dist/styles/layout/breadcrumbs.css +3 -3
  140. package/dist/styles/layout/card.css +1 -1
  141. package/dist/styles/layout/nav-link.css +1 -1
  142. package/dist/styles/layout/page-header.css +21 -1
  143. package/dist/styles/layout/separator.css +21 -1
  144. package/dist/styles/layout/sidebar.css +1 -1
  145. package/dist/styles/layout/tabs.css +30 -1
  146. package/dist/styles/layout/theme-switch.css +1 -1
  147. package/dist/styles/overlays/command.css +3 -3
  148. package/dist/styles/overlays/drawer.css +4 -3
  149. package/dist/styles/overlays/floating.css +2 -2
  150. package/dist/styles/overlays/menu.css +5 -1
  151. package/dist/styles/overlays/modal.css +12 -9
  152. package/dist/styles/overlays/toast.css +3 -3
  153. package/dist/styles/overlays/tooltip.css +26 -8
  154. package/dist/styles/shared.css +2 -2
  155. package/dist/styles/templates/account-menu.css +1 -1
  156. package/dist/styles/templates/auth-layout.css +4 -4
  157. package/dist/styles/templates/brand.css +2 -2
  158. package/dist/styles/templates/dashboard-page.css +1 -1
  159. package/dist/styles/templates/empty-page.css +3 -3
  160. package/dist/styles/templates/error-page.css +1 -1
  161. package/dist/styles/templates/save-bar.css +1 -1
  162. package/dist/styles/templates/settings-modal.css +6 -6
  163. package/dist/styles/utilities.css +3 -3
  164. package/package.json +1 -1
@@ -53,16 +53,42 @@
53
53
  </template>
54
54
  </div>
55
55
 
56
- <form v-if="linkOpen" class="k-editor-link" @submit.prevent="applyLink">
56
+ <form
57
+ v-if="linkOpen"
58
+ class="k-editor-link"
59
+ @submit.prevent="applyLink"
60
+ @keydown.esc.stop.prevent="closeLink(true)"
61
+ >
62
+ <span v-if="linkTypes.length > 1" class="k-editor-link-type">
63
+ <select
64
+ v-model="linkType"
65
+ class="k-field k-field-sm"
66
+ :aria-label="text.linkType"
67
+ @change="focusLinkInput"
68
+ >
69
+ <option v-for="type in linkTypes" :key="type" :value="type">
70
+ {{ linkTypeLabels[type] }}
71
+ </option>
72
+ </select>
73
+ <Icon name="chevron-up-down" class="k-editor-link-type-icon" />
74
+ </span>
57
75
  <input
58
76
  ref="linkInputRef"
59
77
  v-model="linkUrl"
60
- class="k-field k-field-sm"
61
- type="url"
62
- inputmode="url"
63
- placeholder="https://"
64
- :aria-label="text.linkUrl"
65
- @keydown.esc.stop.prevent="closeLink(true)"
78
+ class="k-field k-field-sm k-editor-link-target"
79
+ :type="linkInput.type"
80
+ :inputmode="linkInput.inputmode"
81
+ :autocomplete="linkInput.autocomplete"
82
+ :placeholder="linkInput.placeholder"
83
+ :aria-label="linkInput.label"
84
+ />
85
+ <input
86
+ v-if="linkNeedsText"
87
+ v-model="linkText"
88
+ class="k-field k-field-sm k-editor-link-text"
89
+ type="text"
90
+ :placeholder="text.linkText"
91
+ :aria-label="text.linkText"
66
92
  />
67
93
  <Button type="submit" size="xs" :label="text.applyLink" />
68
94
  <Button
@@ -97,6 +123,7 @@ import StarterKit from "@tiptap/starter-kit";
97
123
  import { EditorContent, useEditor } from "@tiptap/vue-3";
98
124
  import { computed, nextTick, onBeforeUnmount, ref, useId, useTemplateRef, watch } from "vue";
99
125
  import Button from "../components/core/Button.vue";
126
+ import Icon from "../components/core/Icon.vue";
100
127
  import { useFieldControl } from "../composables/useField";
101
128
  import { useRovingTabStops } from "../internal/core/roving";
102
129
  import LiveRegion from "../internal/core/LiveRegion.vue";
@@ -125,6 +152,7 @@ const props = defineProps({
125
152
  headingLabel: { type: String, required: false, default: "level" },
126
153
  toolbar: { type: Array, required: false },
127
154
  extensions: { type: Array, required: false, default: () => [] },
155
+ linkTypes: { type: Array, required: false, default: () => ["url"] },
128
156
  messages: { type: Object, required: false }
129
157
  });
130
158
  const model = defineModel({ type: [String, Object, null], ...{ default: null } });
@@ -140,6 +168,40 @@ const toolbarRef = useTemplateRef("toolbarRef");
140
168
  const linkInputRef = useTemplateRef("linkInputRef");
141
169
  const linkOpen = ref(false);
142
170
  const linkUrl = ref("");
171
+ const linkText = ref("");
172
+ const linkType = ref("url");
173
+ const linkNeedsText = ref(false);
174
+ const linkPrefixes = { url: "", email: "mailto:", phone: "tel:" };
175
+ const linkTypeLabels = computed(() => ({
176
+ url: text.value.linkTypeUrl,
177
+ email: text.value.linkTypeEmail,
178
+ phone: text.value.linkTypePhone
179
+ }));
180
+ const linkInput = computed(() => {
181
+ if (linkType.value === "email")
182
+ return {
183
+ type: "email",
184
+ inputmode: "email",
185
+ autocomplete: "email",
186
+ placeholder: "name@example.com",
187
+ label: text.value.linkEmail
188
+ };
189
+ if (linkType.value === "phone")
190
+ return {
191
+ type: "tel",
192
+ inputmode: "tel",
193
+ autocomplete: "tel",
194
+ placeholder: "+",
195
+ label: text.value.linkPhone
196
+ };
197
+ return {
198
+ type: "url",
199
+ inputmode: "url",
200
+ autocomplete: "url",
201
+ placeholder: "https://",
202
+ label: text.value.linkUrl
203
+ };
204
+ });
143
205
  const editable = computed(() => !props.readonly && !control.disabled.value);
144
206
  const showToolbar = computed(() => editable.value);
145
207
  function serialize(current) {
@@ -338,6 +400,16 @@ const defaultToolbar = computed(() => [
338
400
  }
339
401
  ]
340
402
  ]);
403
+ const optionalItems = computed(() => [
404
+ mark(
405
+ "underline",
406
+ text.value.underline,
407
+ "U",
408
+ "is-underline",
409
+ "underline",
410
+ () => editor.value?.chain().focus().toggleUnderline().run()
411
+ )
412
+ ]);
341
413
  function isToolbarGroups(toolbar) {
342
414
  return toolbar.some((entry) => Array.isArray(entry));
343
415
  }
@@ -352,7 +424,9 @@ function splitToolbar(toolbar) {
352
424
  }
353
425
  const groups = computed(() => {
354
426
  if (!props.toolbar) return defaultToolbar.value.filter((group) => group.length);
355
- const byId = new Map(defaultToolbar.value.flat().map((item) => [item.id, item]));
427
+ const byId = new Map(
428
+ [...defaultToolbar.value.flat(), ...optionalItems.value].map((item) => [item.id, item])
429
+ );
356
430
  const headingItems = defaultToolbar.value[1] ?? [];
357
431
  return splitToolbar(props.toolbar).map(
358
432
  (group) => group.flatMap((entry) => {
@@ -379,10 +453,26 @@ function toggleLink() {
379
453
  closeLink();
380
454
  return;
381
455
  }
382
- linkUrl.value = editor.value?.getAttributes("link").href ?? "";
456
+ const current = editor.value;
457
+ const href = current?.getAttributes("link").href ?? "";
458
+ const detected = detectLinkType(href);
459
+ linkType.value = props.linkTypes.includes(detected) ? detected : props.linkTypes[0] ?? "url";
460
+ linkUrl.value = href.slice(linkPrefixes[detected].length);
461
+ linkText.value = "";
462
+ linkNeedsText.value = Boolean(
463
+ current && current.state.selection.empty && !current.isActive("link")
464
+ );
383
465
  linkOpen.value = true;
466
+ focusLinkInput();
467
+ }
468
+ function focusLinkInput() {
384
469
  nextTick(() => linkInputRef.value?.focus());
385
470
  }
471
+ function detectLinkType(href) {
472
+ if (/^mailto:/i.test(href)) return "email";
473
+ if (/^tel:/i.test(href)) return "phone";
474
+ return "url";
475
+ }
386
476
  function closeLink(toButton = false) {
387
477
  linkOpen.value = false;
388
478
  if (!toButton) {
@@ -394,6 +484,8 @@ function closeLink(toButton = false) {
394
484
  function normalizeUrl(value) {
395
485
  const trimmed = value.trim();
396
486
  if (/^(https?:|mailto:|tel:|\/|#)/i.test(trimmed)) return trimmed;
487
+ if (linkType.value === "email") return `mailto:${trimmed}`;
488
+ if (linkType.value === "phone") return `tel:${trimmed.replace(/[^\d+]/g, "")}`;
397
489
  return `https://${trimmed}`;
398
490
  }
399
491
  function applyLink() {
@@ -403,7 +495,13 @@ function applyLink() {
403
495
  removeLink();
404
496
  return;
405
497
  }
406
- current.chain().focus().extendMarkRange("link").setLink({ href: normalizeUrl(linkUrl.value) }).run();
498
+ const href = normalizeUrl(linkUrl.value);
499
+ if (linkNeedsText.value) {
500
+ const label = linkText.value.trim() || linkUrl.value.trim();
501
+ current.chain().focus().insertContent({ type: "text", text: label, marks: [{ type: "link", attrs: { href } }] }).unsetMark("link").run();
502
+ } else {
503
+ current.chain().focus().extendMarkRange("link").setLink({ href }).run();
504
+ }
407
505
  linkOpen.value = false;
408
506
  }
409
507
  function removeLink() {
@@ -1,6 +1,6 @@
1
1
  import type { Editor } from '@tiptap/vue-3';
2
2
  import type { Extensions } from '@tiptap/vue-3';
3
- import type { EditorFormat, EditorHeadingLabel, EditorHeadingLevel, EditorMessages, EditorToolbar, EditorValue } from './types';
3
+ import type { EditorFormat, EditorHeadingLabel, EditorHeadingLevel, EditorLinkType, EditorMessages, EditorToolbar, EditorValue } from './types';
4
4
  declare const __VLS_export: import("vue").DefineComponent<{
5
5
  format?: EditorFormat;
6
6
  placeholder?: string | undefined;
@@ -17,6 +17,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
17
17
  headingLabel?: EditorHeadingLabel;
18
18
  toolbar?: EditorToolbar;
19
19
  extensions?: Extensions;
20
+ linkTypes?: EditorLinkType[];
20
21
  messages?: Partial<EditorMessages>;
21
22
  modelValue?: EditorValue;
22
23
  key?: PropertyKey | undefined;
@@ -231,6 +232,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
231
232
  headingLabel?: EditorHeadingLabel;
232
233
  toolbar?: EditorToolbar;
233
234
  extensions?: Extensions;
235
+ linkTypes?: EditorLinkType[];
234
236
  messages?: Partial<EditorMessages>;
235
237
  modelValue?: EditorValue;
236
238
  key?: PropertyKey | undefined;
@@ -429,10 +431,11 @@ declare const __VLS_export: import("vue").DefineComponent<{
429
431
  }>, {
430
432
  invalid: boolean;
431
433
  format: EditorFormat;
432
- headings: EditorHeadingLevel[];
433
434
  minHeight: string;
435
+ headings: EditorHeadingLevel[];
434
436
  headingLabel: EditorHeadingLabel;
435
437
  extensions: Extensions;
438
+ linkTypes: EditorLinkType[];
436
439
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
437
440
  declare const _default: typeof __VLS_export;
438
441
  export default _default;
@@ -90,7 +90,13 @@
90
90
 
91
91
  .k-editor-glyph-text.is-strike {
92
92
  text-decoration: line-through;
93
- font-weight: 600;
93
+ font-weight: var(--k-font-weight-strong, 600);
94
+ }
95
+
96
+ .k-editor-glyph-text.is-underline {
97
+ text-decoration: underline;
98
+ text-underline-offset: 2px;
99
+ font-weight: var(--k-font-weight-strong, 600);
94
100
  }
95
101
 
96
102
  .k-editor-glyph-text.is-mono {
@@ -100,7 +106,7 @@
100
106
 
101
107
  .k-editor-glyph-text.is-tabular {
102
108
  font-variant-numeric: tabular-nums;
103
- font-weight: 600;
109
+ font-weight: var(--k-font-weight-strong, 600);
104
110
  }
105
111
 
106
112
  .k-editor-glyph-text.is-quote {
@@ -125,6 +131,27 @@
125
131
  width: auto;
126
132
  }
127
133
 
134
+ .k-editor-link-type {
135
+ position: relative;
136
+ display: inline-flex;
137
+ flex: 0 0 auto;
138
+ align-items: center;
139
+ }
140
+
141
+ .k-editor-link .k-editor-link-type > .k-field {
142
+ flex: 0 0 auto;
143
+ padding-right: 1.75rem;
144
+ cursor: pointer;
145
+ }
146
+
147
+ .k-editor-link-type-icon {
148
+ --k-icon-size: 0.875rem;
149
+ position: absolute;
150
+ right: 0.5rem;
151
+ color: var(--k-text-dimmed);
152
+ pointer-events: none;
153
+ }
154
+
128
155
  .k-editor-content {
129
156
  flex: 1;
130
157
  min-width: 0;
@@ -161,7 +188,7 @@
161
188
  .k-editor-prose h3,
162
189
  .k-editor-prose h4 {
163
190
  margin: 1.25em 0 0.5em;
164
- font-weight: 600;
191
+ font-weight: var(--k-font-weight-strong, 600);
165
192
  line-height: 1.25;
166
193
  letter-spacing: -0.015em;
167
194
  color: var(--k-text);
@@ -180,7 +207,7 @@
180
207
  }
181
208
 
182
209
  .k-editor-prose strong {
183
- font-weight: 600;
210
+ font-weight: var(--k-font-weight-strong, 600);
184
211
  color: var(--k-text);
185
212
  }
186
213
 
@@ -5,5 +5,6 @@ export type EditorValue = string | JSONContent | null;
5
5
  export type EditorMessages = KMessages['editor'];
6
6
  export type EditorHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
7
7
  export type EditorHeadingLabel = 'level' | 'relative';
8
- export type EditorToolbarItem = 'undo' | 'redo' | 'headings' | `h${EditorHeadingLevel}` | 'bold' | 'italic' | 'strike' | 'code' | 'bulletList' | 'orderedList' | 'blockquote' | 'link' | '|';
8
+ export type EditorLinkType = 'url' | 'email' | 'phone';
9
+ export type EditorToolbarItem = 'undo' | 'redo' | 'headings' | `h${EditorHeadingLevel}` | 'bold' | 'italic' | 'underline' | 'strike' | 'code' | 'bulletList' | 'orderedList' | 'blockquote' | 'link' | '|';
9
10
  export type EditorToolbar = EditorToolbarItem[] | EditorToolbarItem[][];
@@ -1,7 +1,10 @@
1
1
  <template>
2
2
  <div
3
3
  class="k-data-grid"
4
- :class="[`k-data-grid-${density}`, { 'is-loading': loading, 'is-auto-height': autoHeight }]"
4
+ :class="[
5
+ `k-data-grid-${density}`,
6
+ { 'is-loading': loading, 'is-auto-height': autoHeight, 'is-fill': fill && !autoHeight }
7
+ ]"
5
8
  >
6
9
  <GridToolbar
7
10
  v-if="toolbar"
@@ -23,7 +26,7 @@
23
26
  role="region"
24
27
  :aria-label="label ?? text.label"
25
28
  :aria-busy="loading ? 'true' : void 0"
26
- :style="autoHeight ? void 0 : { height }"
29
+ :style="autoHeight || fill ? void 0 : { height }"
27
30
  >
28
31
  <AgGridVue
29
32
  v-if="mounted"
@@ -31,23 +34,25 @@
31
34
  class="k-data-grid-ag"
32
35
  :theme="theme"
33
36
  :row-data="rows"
34
- :column-defs="columns"
37
+ :column-defs="columnDefs"
35
38
  :default-col-def="defaultColDef"
36
- :get-row-id="props.rowIdKey ? getRowId : void 0"
39
+ :get-row-id="rowIdGetter"
37
40
  :loading="loading"
38
41
  :locale-text="gridLocaleText"
39
- :context="cellContext"
40
- :overlay-no-rows-template="noRowsTemplate"
41
- :overlay-loading-template="loadingTemplate"
42
+ :context="gridContext"
43
+ :overlay-no-rows-template="gridOptions?.overlayNoRowsTemplate ?? noRowsTemplate"
44
+ :overlay-loading-template="gridOptions?.overlayLoadingTemplate ?? loadingTemplate"
42
45
  :quick-filter-text="serverPagination ? void 0 : query"
43
- :row-selection="rowSelection"
46
+ :row-selection="gridRowSelection"
44
47
  :pagination="paginated"
45
48
  :pagination-page-size="pageSize"
46
49
  :pagination-page-size-selector="false"
47
50
  :suppress-pagination-panel="true"
48
- :auto-size-strategy="autoSizeStrategy"
51
+ :auto-size-strategy="gridAutoSizeStrategy"
49
52
  :dom-layout="autoHeight ? 'autoHeight' : 'normal'"
50
- :tooltip-show-delay="600"
53
+ :row-height="rowHeight ?? gridOptions?.rowHeight"
54
+ :header-height="headerHeight ?? gridOptions?.headerHeight"
55
+ :tooltip-show-delay="gridOptions?.tooltipShowDelay ?? 600"
51
56
  :suppress-cell-focus="false"
52
57
  v-on="gridEvents"
53
58
  />
@@ -69,6 +74,9 @@
69
74
  class="k-data-grid-pager"
70
75
  size="sm"
71
76
  compact
77
+ :show-edges="showEdges"
78
+ :show-jump="showJump"
79
+ :loading="loading"
72
80
  :total="totalRows"
73
81
  :page="page"
74
82
  :page-size="pageSize"
@@ -91,7 +99,7 @@
91
99
 
92
100
  <script setup>
93
101
  import { AgGridVue } from "ag-grid-vue3";
94
- import { computed, onMounted, ref, shallowRef, watch } from "vue";
102
+ import { computed, nextTick, onMounted, ref, shallowRef, watch } from "vue";
95
103
  import Pagination from "../components/data/Pagination.vue";
96
104
  import Spinner from "../components/core/Spinner.vue";
97
105
  import { useCopy } from "../composables/useCopy";
@@ -102,18 +110,24 @@ import { useLiveMessage } from "../internal/core/live";
102
110
  import { readStorageJson, removeStorage, writeStorageJson } from "../internal/core/storage";
103
111
  import GridToolbar from "./GridToolbar.vue";
104
112
  import { escapeHtml } from "../internal/core/text";
105
- import { registerGridModules } from "./modules";
113
+ import {
114
+ registerGridEditorModules,
115
+ registerGridFilterModules,
116
+ registerGridModules
117
+ } from "./modules";
106
118
  import { getGridTheme } from "./theme";
107
119
  registerGridModules();
108
120
  const props = defineProps({
109
121
  rows: { type: Array, required: false, default: () => [] },
110
122
  columns: { type: Array, required: true },
111
123
  rowIdKey: { type: String, required: false },
124
+ getRowId: { type: Function, required: false },
112
125
  storageKey: { type: String, required: false },
113
126
  density: { type: String, required: false, default: "normal" },
114
127
  selectable: { type: [Boolean, String], required: false, default: false },
115
128
  loading: { type: Boolean, required: false },
116
129
  height: { type: String, required: false, default: "28rem" },
130
+ fill: { type: Boolean, required: false },
117
131
  autoHeight: { type: Boolean, required: false },
118
132
  label: { type: String, required: false },
119
133
  toolbar: { type: Boolean, required: false, default: false },
@@ -124,17 +138,39 @@ const props = defineProps({
124
138
  searchDebounce: { type: Number, required: false, default: 0 },
125
139
  serverSort: { type: Boolean, required: false },
126
140
  pagination: { type: Boolean, required: false },
141
+ showEdges: { type: Boolean, required: false },
142
+ showJump: { type: Boolean, required: false },
127
143
  serverPagination: { type: Boolean, required: false },
128
144
  total: { type: Number, required: false, default: 0 },
129
145
  pageSizes: { type: Array, required: false, default: () => [10, 25, 50, 100] },
130
146
  messages: { type: Object, required: false },
131
147
  localeText: { type: Object, required: false },
132
- gridOptions: { type: Object, required: false }
148
+ gridOptions: { type: Object, required: false },
149
+ rowHeight: { type: Number, required: false },
150
+ headerHeight: { type: Number, required: false },
151
+ headerText: { type: Function, required: false },
152
+ filterable: { type: Boolean, required: false },
153
+ editable: { type: Boolean, required: false }
133
154
  });
134
- const emit = defineEmits(["rowClick", "selectionChange", "sortChange", "copy"]);
155
+ const emit = defineEmits(["rowClick", "selectionChange", "sortChange", "filterChange", "copy"]);
135
156
  const query = defineModel("query", { type: String, ...{ default: "" } });
136
157
  const page = defineModel("page", { type: Number, ...{ default: 1 } });
137
158
  const pageSize = defineModel("pageSize", { type: Number, ...{ default: 25 } });
159
+ const sort = defineModel("sort", { type: null, ...{ default: void 0 } });
160
+ if (props.filterable) registerGridFilterModules();
161
+ function hasEditableColumn() {
162
+ return props.editable || Boolean(props.gridOptions?.defaultColDef?.editable) || props.columns.some((column) => Boolean(column.editable));
163
+ }
164
+ if (hasEditableColumn()) registerGridEditorModules();
165
+ const columnDefs = computed(() => {
166
+ const headerText = props.headerText;
167
+ if (!headerText) return props.columns;
168
+ return props.columns.map((column) => {
169
+ if (column.headerName !== void 0 || !column.field) return column;
170
+ const headerName = headerText(String(column.field), column);
171
+ return headerName === void 0 ? column : { ...column, headerName };
172
+ });
173
+ });
138
174
  const { locale, t } = useLocale();
139
175
  const text = computed(() => ({
140
176
  ...locale.value.messages.grid,
@@ -163,8 +199,10 @@ const gridLocaleText = computed(() => ({
163
199
  previousPage: text.value.previousPage,
164
200
  noRowsToShow: text.value.noRows,
165
201
  loadingOoo: text.value.loading,
202
+ ...props.gridOptions?.localeText,
166
203
  ...props.localeText
167
204
  }));
205
+ const gridContext = computed(() => ({ ...props.gridOptions?.context, ...cellContext.value }));
168
206
  const live = useLiveMessage();
169
207
  let announcedCount = null;
170
208
  const theme = computed(() => getGridTheme(props.density));
@@ -187,8 +225,8 @@ const noRowsTemplate = computed(
187
225
  const loadingTemplate = computed(
188
226
  () => `<div class="k-data-grid-overlay"><span class="k-spinner k-spinner-md" aria-hidden="true"></span></div>`
189
227
  );
190
- const rowSelection = computed(() => {
191
- if (!props.selectable) return void 0;
228
+ const gridRowSelection = computed(() => {
229
+ if (!props.selectable) return props.gridOptions?.rowSelection;
192
230
  return {
193
231
  mode: props.selectable === "single" ? "singleRow" : "multiRow",
194
232
  enableClickSelection: false
@@ -197,10 +235,12 @@ const rowSelection = computed(() => {
197
235
  const defaultColDef = computed(() => ({
198
236
  sortable: true,
199
237
  resizable: true,
238
+ filter: props.filterable || void 0,
200
239
  minWidth: 80,
201
240
  comparator: props.serverSort ? () => 0 : void 0,
202
241
  valueFormatter: (params) => isEmpty(params.value) ? "-" : String(params.value),
203
- cellRenderer: renderCell
242
+ cellRenderer: renderCell,
243
+ ...props.gridOptions?.defaultColDef
204
244
  }));
205
245
  function isEmpty(value) {
206
246
  return value === null || value === void 0 || value === "";
@@ -223,12 +263,20 @@ function renderCell(params) {
223
263
  }
224
264
  const storageName = computed(() => props.storageKey ? `k-grid:${props.storageKey}` : null);
225
265
  const hasStoredState = ref(false);
226
- const autoSizeStrategy = computed(
227
- () => hasStoredState.value ? void 0 : { type: "fitGridWidth", defaultMinWidth: 100 }
228
- );
229
- function getRowId(params) {
266
+ const gridAutoSizeStrategy = computed(() => {
267
+ if (hasStoredState.value) return void 0;
268
+ if (props.gridOptions && "autoSizeStrategy" in props.gridOptions)
269
+ return props.gridOptions.autoSizeStrategy;
270
+ return { type: "fitGridWidth", defaultMinWidth: 100 };
271
+ });
272
+ function getRowIdByKey(params) {
230
273
  return String(params.data[props.rowIdKey]);
231
274
  }
275
+ const rowIdGetter = computed(() => {
276
+ if (props.getRowId) return props.getRowId;
277
+ if (props.rowIdKey) return getRowIdByKey;
278
+ return props.gridOptions?.getRowId;
279
+ });
232
280
  function readStoredState() {
233
281
  if (!storageName.value) return null;
234
282
  const stored = readStorageJson(storageName.value);
@@ -247,8 +295,28 @@ function onGridReady(event) {
247
295
  api.value = event.api;
248
296
  const stored = readStoredState();
249
297
  if (stored?.length) event.api.applyColumnState({ state: stored, applyOrder: true });
298
+ applySort();
250
299
  syncPagination();
251
300
  }
301
+ function sameSort(a, b) {
302
+ return (a?.column ?? null) === (b?.column ?? null) && (a?.direction ?? null) === (b?.direction ?? null);
303
+ }
304
+ function readSort(current) {
305
+ const sorted = current.getColumnState().filter((column) => column.sort).sort((a, b) => (a.sortIndex ?? 0) - (b.sortIndex ?? 0))[0];
306
+ return sorted?.sort ? { column: sorted.colId, direction: sorted.sort } : null;
307
+ }
308
+ function applySort() {
309
+ const current = api.value;
310
+ if (!current || sort.value === void 0) return;
311
+ if (sameSort(readSort(current), sort.value)) return;
312
+ const target = sort.value;
313
+ current.applyColumnState({
314
+ state: target ? [{ colId: target.column, sort: target.direction }] : [],
315
+ defaultState: { sort: null }
316
+ });
317
+ }
318
+ watch(sort, applySort);
319
+ watch(columnDefs, () => nextTick(applySort));
252
320
  function syncPagination() {
253
321
  const current = api.value;
254
322
  if (!current || props.serverPagination) return;
@@ -281,9 +349,22 @@ function onSelectionChanged(event) {
281
349
  emit("selectionChange", rows);
282
350
  }
283
351
  function onSortChanged(event) {
284
- if (!props.serverSort) return;
285
- const sorted = event.api.getColumnState().find((column) => column.sort);
286
- emit("sortChange", sorted?.sort ? { column: sorted.colId, direction: sorted.sort } : null);
352
+ const change = readSort(event.api);
353
+ if (!sameSort(change, sort.value)) sort.value = change;
354
+ if (props.serverSort) emit("sortChange", change);
355
+ }
356
+ const columnFilterModules = [
357
+ "TextFilterModule",
358
+ "NumberFilterModule",
359
+ "DateFilterModule",
360
+ "BigIntFilterModule",
361
+ "CustomFilterModule",
362
+ "SetFilterModule",
363
+ "MultiFilterModule"
364
+ ];
365
+ function onFilterChanged(event) {
366
+ if (!columnFilterModules.some((name) => event.api.isModuleRegistered(name))) return;
367
+ emit("filterChange", event.api.getFilterModel(), event);
287
368
  }
288
369
  async function onCellContextMenu(event) {
289
370
  if (!event.node || !event.column) return;
@@ -301,6 +382,7 @@ const gridEvents = {
301
382
  rowClicked: (event) => emit("rowClick", event),
302
383
  selectionChanged: onSelectionChanged,
303
384
  sortChanged: onSortChanged,
385
+ filterChanged: onFilterChanged,
304
386
  cellContextMenu: onCellContextMenu,
305
387
  paginationChanged: syncPagination,
306
388
  modelUpdated: onModelUpdated,
@@ -1,15 +1,17 @@
1
- import type { ColDef, GridApi, GridOptions, RowClickedEvent } from 'ag-grid-community';
1
+ import type { FilterChangedEvent, FilterModel, ColDef, GetRowIdParams, GridApi, GridOptions, RowClickedEvent } from 'ag-grid-community';
2
2
  import type { GridDensity, GridMessages, GridSelection, GridSortChange } from './types';
3
3
  declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
4
4
  props: import("vue").PublicProps & __VLS_PrettifyLocal<({
5
5
  rows?: T[];
6
6
  columns: ColDef<T>[];
7
7
  rowIdKey?: keyof T & string;
8
+ getRowId?: (params: GetRowIdParams<T>) => string;
8
9
  storageKey?: string;
9
10
  density?: GridDensity;
10
11
  selectable?: GridSelection;
11
12
  loading?: boolean;
12
13
  height?: string;
14
+ fill?: boolean;
13
15
  autoHeight?: boolean;
14
16
  label?: string;
15
17
  toolbar?: boolean;
@@ -20,24 +22,34 @@ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_se
20
22
  searchDebounce?: number;
21
23
  serverSort?: boolean;
22
24
  pagination?: boolean;
25
+ showEdges?: boolean;
26
+ showJump?: boolean;
23
27
  serverPagination?: boolean;
24
28
  total?: number;
25
29
  pageSizes?: number[];
26
30
  messages?: Partial<GridMessages>;
27
31
  localeText?: Record<string, string>;
28
32
  gridOptions?: GridOptions<T>;
33
+ rowHeight?: number;
34
+ headerHeight?: number;
35
+ headerText?: (field: string, column: ColDef<T>) => string | undefined;
36
+ filterable?: boolean;
37
+ editable?: boolean;
29
38
  } & {
30
39
  query?: string;
31
40
  page?: number;
32
41
  pageSize?: number;
42
+ sort?: GridSortChange | null | undefined;
33
43
  }) & {
34
44
  onCopy?: ((text: string) => any) | undefined;
35
45
  "onUpdate:page"?: ((value: number) => any) | undefined;
36
46
  "onUpdate:pageSize"?: ((value: number) => any) | undefined;
37
47
  onRowClick?: ((event: RowClickedEvent<T, any>) => any) | undefined;
48
+ "onUpdate:sort"?: ((value: GridSortChange | null | undefined) => any) | undefined;
38
49
  "onUpdate:query"?: ((value: string) => any) | undefined;
39
50
  onSelectionChange?: ((rows: T[]) => any) | undefined;
40
51
  onSortChange?: ((change: GridSortChange | null) => any) | undefined;
52
+ onFilterChange?: ((model: FilterModel, event: FilterChangedEvent<T, any>) => any) | undefined;
41
53
  } & Partial<import("vue").HTMLAttributes & import("vue").ReservedProps>> & (typeof globalThis extends {
42
54
  __VLS_PROPS_FALLBACK: infer P;
43
55
  } ? P : {});
@@ -53,7 +65,7 @@ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_se
53
65
  api: GridApi<T> | null;
54
66
  }) => any;
55
67
  };
56
- emit: (((evt: "copy", text: string) => void) & ((evt: "rowClick", event: RowClickedEvent<T, any>) => void) & ((evt: "selectionChange", rows: T[]) => void) & ((evt: "sortChange", change: GridSortChange | null) => void)) & (((event: "update:page", value: number) => void) & ((event: "update:pageSize", value: number) => void) & ((event: "update:query", value: string) => void));
68
+ emit: (((evt: "copy", text: string) => void) & ((evt: "rowClick", event: RowClickedEvent<T, any>) => void) & ((evt: "selectionChange", rows: T[]) => void) & ((evt: "sortChange", change: GridSortChange | null) => void) & ((evt: "filterChange", model: FilterModel, event: FilterChangedEvent<T, any>) => void)) & (((event: "update:page", value: number) => void) & ((event: "update:pageSize", value: number) => void) & ((event: "update:sort", value: GridSortChange | null | undefined) => void) & ((event: "update:query", value: string) => void));
57
69
  }>) => import("vue").VNode & {
58
70
  __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
59
71
  };
@@ -1,12 +1,18 @@
1
1
  <template>
2
- <time v-if="formatted" class="k-data-grid-date k-tabular" :datetime="iso">{{ formatted }}</time>
2
+ <time
3
+ v-if="formatted"
4
+ class="k-data-grid-date k-tabular"
5
+ :datetime="iso"
6
+ :title="params.relative ? absolute : void 0"
7
+ >{{ formatted }}</time
8
+ >
3
9
  <EmptyCell v-else :context="cellContext" />
4
10
  </template>
5
11
 
6
12
  <script setup>
7
13
  import { computed } from "vue";
8
14
  import EmptyCell from "./EmptyCell.vue";
9
- import { dateTimeFormat } from "../../internal/core/intl";
15
+ import { dateTimeFormat, formatRelativeTime } from "../../internal/core/intl";
10
16
  const props = defineProps({
11
17
  params: { type: Object, required: true }
12
18
  });
@@ -19,11 +25,20 @@ const date = computed(() => {
19
25
  const locale = computed(
20
26
  () => props.params.locale ?? props.params.context?.locale
21
27
  );
22
- const formatted = computed(
28
+ const absolute = computed(
23
29
  () => date.value ? dateTimeFormat(locale.value, props.params.options ?? { dateStyle: "medium" }).format(
24
30
  date.value
25
31
  ) : ""
26
32
  );
33
+ const formatted = computed(() => {
34
+ const relative = props.params.relative;
35
+ if (!date.value || !relative) return absolute.value;
36
+ return formatRelativeTime(
37
+ date.value,
38
+ locale.value,
39
+ relative === true ? { numeric: "auto" } : relative
40
+ );
41
+ });
27
42
  const iso = computed(() => date.value?.toISOString());
28
43
  const cellContext = computed(() => props.params.context);
29
44
  </script>