@xleddyl/443-components-vue 0.8.2 → 0.8.4

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 (67) hide show
  1. package/README.md +41 -22
  2. package/dist/components/core/FormField.vue +5 -0
  3. package/dist/components/core/FormField.vue.d.ts +4 -0
  4. package/dist/components/core/Input.vue +59 -5
  5. package/dist/components/core/Input.vue.d.ts +8 -4
  6. package/dist/components/core/Textarea.vue +28 -3
  7. package/dist/components/core/Textarea.vue.d.ts +6 -4
  8. package/dist/components/data/DescriptionList.vue.d.ts +1 -1
  9. package/dist/components/data/Pagination.vue +8 -2
  10. package/dist/components/data/Pagination.vue.d.ts +2 -0
  11. package/dist/components/forms/CheckboxGroup.vue +7 -1
  12. package/dist/components/forms/CheckboxGroup.vue.d.ts +1 -0
  13. package/dist/components/forms/ColorInput.vue.d.ts +4 -4
  14. package/dist/components/forms/Combobox.vue +4 -3
  15. package/dist/components/forms/Combobox.vue.d.ts +1 -0
  16. package/dist/components/forms/DateInput.vue.d.ts +8 -4
  17. package/dist/components/forms/NumberInput.vue.d.ts +4 -4
  18. package/dist/components/forms/PasswordInput.vue.d.ts +8 -4
  19. package/dist/components/forms/PinInput.vue.d.ts +4 -4
  20. package/dist/components/forms/Select.vue +21 -2
  21. package/dist/components/forms/Select.vue.d.ts +3 -3
  22. package/dist/components/forms/Slider.vue.d.ts +4 -4
  23. package/dist/components/forms/TimeInput.vue.d.ts +8 -4
  24. package/dist/components/layout/Card.vue.d.ts +1 -1
  25. package/dist/components/layout/Separator.vue +22 -7
  26. package/dist/components/layout/Separator.vue.d.ts +14 -4
  27. package/dist/components/layout/types.d.ts +1 -0
  28. package/dist/components/overlays/ConfirmDialog.vue.d.ts +4 -4
  29. package/dist/components/overlays/ConfirmHost.vue.d.ts +4 -4
  30. package/dist/components/overlays/ContextMenu.vue.d.ts +2 -2
  31. package/dist/components/overlays/DropdownMenu.vue.d.ts +2 -2
  32. package/dist/components/overlays/types.d.ts +2 -2
  33. package/dist/components/templates/SettingsModal.vue.d.ts +4 -4
  34. package/dist/composables/useForm.d.ts +9 -2
  35. package/dist/composables/useForm.mjs +75 -13
  36. package/dist/composables/useOverlay.d.ts +1 -1
  37. package/dist/editor/RichTextEditor.vue +95 -9
  38. package/dist/editor/RichTextEditor.vue.d.ts +4 -1
  39. package/dist/editor/rich-text-editor.css +21 -0
  40. package/dist/editor/types.d.ts +1 -0
  41. package/dist/grid/DataGrid.vue +48 -18
  42. package/dist/grid/DataGrid.vue.d.ts +6 -1
  43. package/dist/grid/cells/DateCell.vue +7 -2
  44. package/dist/grid/cells/ImageCell.vue +6 -1
  45. package/dist/grid/data-grid.css +16 -2
  46. package/dist/grid/index.d.ts +2 -1
  47. package/dist/grid/index.mjs +6 -1
  48. package/dist/grid/modules.d.ts +1 -0
  49. package/dist/grid/modules.mjs +23 -0
  50. package/dist/grid/theme.mjs +1 -1
  51. package/dist/grid/types.d.ts +4 -0
  52. package/dist/internal/forms/counter.d.ts +18 -0
  53. package/dist/internal/forms/counter.mjs +31 -0
  54. package/dist/internal/overlays/OverlayView.vue.d.ts +4 -4
  55. package/dist/locale/de.mjs +11 -1
  56. package/dist/locale/en.mjs +11 -1
  57. package/dist/locale/it.mjs +11 -1
  58. package/dist/locale/types.d.ts +10 -0
  59. package/dist/styles/core/button.css +12 -6
  60. package/dist/styles/core/form-field.css +6 -0
  61. package/dist/styles/core/input.css +29 -0
  62. package/dist/styles/core/textarea.css +10 -0
  63. package/dist/styles/forms/calendar.css +8 -8
  64. package/dist/styles/forms/choice.css +6 -0
  65. package/dist/styles/layout/separator.css +49 -0
  66. package/dist/styles/shared.css +2 -1
  67. package/package.json +1 -1
@@ -1,7 +1,12 @@
1
1
  import type { App, ComputedRef, MaybeRefOrGetter, Ref, UnwrapRef } from 'vue';
2
2
  import type { IssueKeyResolver, StandardSchemaIssue, StandardSchemaV1 } from '../internal/forms/standard-schema';
3
+ import type { MessageParams } from '../locale/types';
3
4
  export type { IssuePath as FormIssuePath, StandardSchemaIssue, StandardSchemaResult, StandardSchemaV1, } from '../internal/forms/standard-schema';
4
- export type FieldValidationResult = string | undefined | null | boolean;
5
+ export interface FieldValidationMessage {
6
+ message: string;
7
+ params?: MessageParams;
8
+ }
9
+ export type FieldValidationResult = string | FieldValidationMessage | undefined | null | boolean;
5
10
  export interface FieldDefinition<T = unknown> {
6
11
  initial?: T | (() => T);
7
12
  label?: string;
@@ -11,6 +16,7 @@ export interface FieldDefinition<T = unknown> {
11
16
  minLength?: number;
12
17
  maxLength?: number;
13
18
  pattern?: RegExp;
19
+ isEmpty?: (value: T) => boolean;
14
20
  schema?: StandardSchemaV1;
15
21
  validate?: (value: T, values: Record<string, unknown>) => FieldValidationResult | Promise<FieldValidationResult>;
16
22
  messages?: Partial<FormMessages>;
@@ -40,6 +46,7 @@ export interface FormTranslateContext {
40
46
  name?: string;
41
47
  label?: string;
42
48
  kind: 'error' | 'warning';
49
+ params?: MessageParams;
43
50
  }
44
51
  export type FormTranslateMessage = (message: string, context: FormTranslateContext) => string;
45
52
  export interface FormConfig {
@@ -54,7 +61,7 @@ export type FormWarningsFunction<V> = (values: V) => FormWarningsResult | Promis
54
61
  export type FormWarnings<V> = StandardSchemaV1 | Readonly<Ref<StandardSchemaV1 | undefined>> | FormWarningsFunction<V>;
55
62
  export type FormPath<V> = (keyof V & string) | (string & {});
56
63
  export interface UseFormOptions<S extends FormSchema = FormSchema, V extends object = FormValues<S>> {
57
- schema?: S;
64
+ schema?: MaybeRefOrGetter<S>;
58
65
  values?: MaybeRefOrGetter<V>;
59
66
  validator?: MaybeRefOrGetter<StandardSchemaV1 | undefined>;
60
67
  warnings?: FormWarnings<V>;
@@ -20,6 +20,7 @@ import {
20
20
  schemaIssues
21
21
  } from "../internal/forms/standard-schema.mjs";
22
22
  import { FORM_CONFIG_KEY, FORM_KEY } from "../internal/keys.mjs";
23
+ import { formatLocaleMessage } from "../locale/index.mjs";
23
24
  import { useLocale } from "./useLocale.mjs";
24
25
  function localeFormMessages(t) {
25
26
  return {
@@ -32,11 +33,17 @@ function localeFormMessages(t) {
32
33
  invalid: () => t("form.invalid")
33
34
  };
34
35
  }
36
+ function isPlainObject(value) {
37
+ if (value === null || typeof value !== "object") return false;
38
+ const prototype = Object.getPrototypeOf(value);
39
+ return prototype === Object.prototype || prototype === null;
40
+ }
35
41
  export function isEmptyValue(value) {
36
42
  if (value === null || value === void 0) return true;
37
43
  if (typeof value === "string") return value.trim() === "";
38
44
  if (Array.isArray(value)) return value.length === 0;
39
45
  if (typeof value === "number") return Number.isNaN(value);
46
+ if (isPlainObject(value)) return Object.values(value).every(isEmptyValue);
40
47
  return false;
41
48
  }
42
49
  function cloneValue(value) {
@@ -98,10 +105,10 @@ function replaceEntries(target, next, inScope = () => true) {
98
105
  }
99
106
  }
100
107
  export function useForm(options) {
101
- const schema = options.schema ?? {};
102
- const schemaNames = Object.keys(schema);
108
+ const schema = computed(() => toValue(options.schema) ?? {});
109
+ const schemaNames = computed(() => Object.keys(schema.value));
103
110
  const external = options.values !== void 0;
104
- const { t } = useLocale();
111
+ const { t, locale } = useLocale();
105
112
  const config = hasInjectionContext() && inject(FORM_CONFIG_KEY, null) || null;
106
113
  const messages = computed(() => ({
107
114
  ...localeFormMessages(t),
@@ -109,22 +116,28 @@ export function useForm(options) {
109
116
  ...options.messages
110
117
  }));
111
118
  const translator = options.translateMessage ?? config?.translateMessage;
112
- const translate = (message, context) => translator && message ? translator(message, context) : message;
119
+ const translate = (message, context) => {
120
+ if (!message) return message;
121
+ if (translator) return translator(message, context);
122
+ return context.params ? formatLocaleMessage(locale.value.code, message, context.params) : message;
123
+ };
113
124
  const translateIssues = (issues, kind) => issues.map((issue) => {
114
125
  const name = issue.path.map(String).join(".") || void 0;
115
- const label = name ? schema[name]?.label : void 0;
126
+ const label = name ? schema.value[name]?.label : void 0;
116
127
  return { ...issue, message: translate(issue.message, { name, label, kind }) };
117
128
  });
118
129
  const validateOn = options.validateOn ?? "submit";
119
130
  const issueKey = options.issueKey ?? defaultIssueKey;
120
- const initialValues = () => Object.fromEntries(schemaNames.map((name) => [name, resolveInitial(schema[name])]));
131
+ const initialValues = () => Object.fromEntries(
132
+ schemaNames.value.map((name) => [name, resolveInitial(schema.value[name])])
133
+ );
121
134
  const internalValues = external ? void 0 : reactive(initialValues());
122
135
  const target = () => external ? toValue(options.values) : internalValues;
123
136
  let baseline = cloneValue(target());
124
137
  const baselineVersion = ref(0);
125
138
  const names = computed(() => {
126
- if (!external) return schemaNames;
127
- return [.../* @__PURE__ */ new Set([...schemaNames, ...Object.keys(target())])];
139
+ if (!external) return schemaNames.value;
140
+ return [.../* @__PURE__ */ new Set([...schemaNames.value, ...Object.keys(target())])];
128
141
  });
129
142
  const errors = reactive({});
130
143
  const warnings = reactive({});
@@ -173,11 +186,16 @@ export function useForm(options) {
173
186
  return evaluateValidator();
174
187
  }
175
188
  function evaluateRules(name) {
176
- const definition = schema[name];
189
+ const definition = schema.value[name];
177
190
  if (!definition) return void 0;
178
191
  const value = readValue(name);
179
192
  const fieldMessages = { ...messages.value, ...definition.messages };
180
- const own = (message) => translate(message, { name, label: definition.label, kind: "error" });
193
+ const own = (message, params) => translate(message, {
194
+ name,
195
+ label: definition.label,
196
+ kind: "error",
197
+ ...params ? { params } : {}
198
+ });
181
199
  const context = {
182
200
  label: definition.label,
183
201
  value,
@@ -186,7 +204,7 @@ export function useForm(options) {
186
204
  minLength: definition.minLength,
187
205
  maxLength: definition.maxLength
188
206
  };
189
- if (isEmptyValue(value)) {
207
+ if (definition.isEmpty ? definition.isEmpty(value) : isEmptyValue(value)) {
190
208
  if (definition.required) return fieldMessages.required(context);
191
209
  } else {
192
210
  if (typeof value === "number") {
@@ -217,6 +235,8 @@ export function useForm(options) {
217
235
  if (!definition.validate) return runSchema();
218
236
  return chain(definition.validate(value, target()), (result) => {
219
237
  if (typeof result === "string" && result) return own(result);
238
+ if (result && typeof result === "object" && result.message)
239
+ return own(result.message, result.params);
220
240
  if (result === false) return fieldMessages.invalid(context);
221
241
  return runSchema();
222
242
  });
@@ -376,6 +396,46 @@ export function useForm(options) {
376
396
  },
377
397
  { deep: true }
378
398
  );
399
+ function dropErrorsWithin(name) {
400
+ for (const source of [ruleErrors, manualErrors, issueErrors, touched]) {
401
+ for (const key of Object.keys(source)) {
402
+ if (isWithin(key, name)) delete source[key];
403
+ }
404
+ }
405
+ }
406
+ watch(
407
+ schemaNames,
408
+ (next, previous) => {
409
+ const added = next.filter((name) => !previous.includes(name));
410
+ const removed = previous.filter((name) => !next.includes(name));
411
+ if (!added.length && !removed.length) return;
412
+ if (internalValues) {
413
+ for (const name of removed) delete internalValues[name];
414
+ for (const name of added) internalValues[name] = resolveInitial(schema.value[name]);
415
+ }
416
+ for (const name of removed) {
417
+ if (names.value.includes(name)) continue;
418
+ if (!external) delete baseline[name];
419
+ snapshots.delete(name);
420
+ fieldRuns.delete(name);
421
+ dropErrorsWithin(name);
422
+ }
423
+ for (const name of added) {
424
+ if (!external) baseline[name] = cloneValue(readValue(name));
425
+ snapshots.set(name, cloneValue(readValue(name)));
426
+ }
427
+ baselineVersion.value++;
428
+ syncErrors();
429
+ },
430
+ { flush: "sync" }
431
+ );
432
+ watch(
433
+ schema,
434
+ () => {
435
+ if (submitCount.value > 0) void validate();
436
+ },
437
+ { deep: 2 }
438
+ );
379
439
  watch(
380
440
  () => toValue(options.validator),
381
441
  () => {
@@ -407,7 +467,9 @@ export function useForm(options) {
407
467
  });
408
468
  }
409
469
  return {
410
- schema,
470
+ get schema() {
471
+ return schema.value;
472
+ },
411
473
  get values() {
412
474
  return target();
413
475
  },
@@ -429,7 +491,7 @@ export function useForm(options) {
429
491
  setErrors,
430
492
  clearErrors,
431
493
  touch,
432
- isRequired: (name) => Boolean(schema[name]?.required),
494
+ isRequired: (name) => Boolean(schema.value[name]?.required),
433
495
  reset,
434
496
  submit
435
497
  };
@@ -1,6 +1,6 @@
1
1
  import type { AllowedComponentProps, Component, VNodeProps } from 'vue';
2
2
  import type { OverlayContext, OverlayEntry, OverlayKind, OverlayOptions } from '../internal/overlays/stack';
3
- export type { OverlayContext, OverlayKind, OverlayOptions };
3
+ export type { OverlayContext, OverlayEntry, OverlayKind, OverlayOptions };
4
4
  export type OverlayComponentProps<C> = C extends new (...args: never[]) => {
5
5
  $props: infer P;
6
6
  } ? Omit<P, keyof VNodeProps | keyof AllowedComponentProps> : Record<string, unknown>;
@@ -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) {
@@ -391,10 +453,26 @@ function toggleLink() {
391
453
  closeLink();
392
454
  return;
393
455
  }
394
- 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
+ );
395
465
  linkOpen.value = true;
466
+ focusLinkInput();
467
+ }
468
+ function focusLinkInput() {
396
469
  nextTick(() => linkInputRef.value?.focus());
397
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
+ }
398
476
  function closeLink(toButton = false) {
399
477
  linkOpen.value = false;
400
478
  if (!toButton) {
@@ -406,6 +484,8 @@ function closeLink(toButton = false) {
406
484
  function normalizeUrl(value) {
407
485
  const trimmed = value.trim();
408
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, "")}`;
409
489
  return `https://${trimmed}`;
410
490
  }
411
491
  function applyLink() {
@@ -415,7 +495,13 @@ function applyLink() {
415
495
  removeLink();
416
496
  return;
417
497
  }
418
- 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
+ }
419
505
  linkOpen.value = false;
420
506
  }
421
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;
@@ -433,6 +435,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
433
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;
@@ -131,6 +131,27 @@
131
131
  width: auto;
132
132
  }
133
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
+
134
155
  .k-editor-content {
135
156
  flex: 1;
136
157
  min-width: 0;
@@ -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 EditorLinkType = 'url' | 'email' | 'phone';
8
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"
@@ -33,23 +36,23 @@
33
36
  :row-data="rows"
34
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
53
  :row-height="rowHeight ?? gridOptions?.rowHeight"
51
54
  :header-height="headerHeight ?? gridOptions?.headerHeight"
52
- :tooltip-show-delay="600"
55
+ :tooltip-show-delay="gridOptions?.tooltipShowDelay ?? 600"
53
56
  :suppress-cell-focus="false"
54
57
  v-on="gridEvents"
55
58
  />
@@ -71,6 +74,9 @@
71
74
  class="k-data-grid-pager"
72
75
  size="sm"
73
76
  compact
77
+ :show-edges="showEdges"
78
+ :show-jump="showJump"
79
+ :loading="loading"
74
80
  :total="totalRows"
75
81
  :page="page"
76
82
  :page-size="pageSize"
@@ -104,18 +110,24 @@ import { useLiveMessage } from "../internal/core/live";
104
110
  import { readStorageJson, removeStorage, writeStorageJson } from "../internal/core/storage";
105
111
  import GridToolbar from "./GridToolbar.vue";
106
112
  import { escapeHtml } from "../internal/core/text";
107
- import { registerGridFilterModules, registerGridModules } from "./modules";
113
+ import {
114
+ registerGridEditorModules,
115
+ registerGridFilterModules,
116
+ registerGridModules
117
+ } from "./modules";
108
118
  import { getGridTheme } from "./theme";
109
119
  registerGridModules();
110
120
  const props = defineProps({
111
121
  rows: { type: Array, required: false, default: () => [] },
112
122
  columns: { type: Array, required: true },
113
123
  rowIdKey: { type: String, required: false },
124
+ getRowId: { type: Function, required: false },
114
125
  storageKey: { type: String, required: false },
115
126
  density: { type: String, required: false, default: "normal" },
116
127
  selectable: { type: [Boolean, String], required: false, default: false },
117
128
  loading: { type: Boolean, required: false },
118
129
  height: { type: String, required: false, default: "28rem" },
130
+ fill: { type: Boolean, required: false },
119
131
  autoHeight: { type: Boolean, required: false },
120
132
  label: { type: String, required: false },
121
133
  toolbar: { type: Boolean, required: false, default: false },
@@ -126,6 +138,8 @@ const props = defineProps({
126
138
  searchDebounce: { type: Number, required: false, default: 0 },
127
139
  serverSort: { type: Boolean, required: false },
128
140
  pagination: { type: Boolean, required: false },
141
+ showEdges: { type: Boolean, required: false },
142
+ showJump: { type: Boolean, required: false },
129
143
  serverPagination: { type: Boolean, required: false },
130
144
  total: { type: Number, required: false, default: 0 },
131
145
  pageSizes: { type: Array, required: false, default: () => [10, 25, 50, 100] },
@@ -135,7 +149,8 @@ const props = defineProps({
135
149
  rowHeight: { type: Number, required: false },
136
150
  headerHeight: { type: Number, required: false },
137
151
  headerText: { type: Function, required: false },
138
- filterable: { type: Boolean, required: false }
152
+ filterable: { type: Boolean, required: false },
153
+ editable: { type: Boolean, required: false }
139
154
  });
140
155
  const emit = defineEmits(["rowClick", "selectionChange", "sortChange", "filterChange", "copy"]);
141
156
  const query = defineModel("query", { type: String, ...{ default: "" } });
@@ -143,6 +158,10 @@ const page = defineModel("page", { type: Number, ...{ default: 1 } });
143
158
  const pageSize = defineModel("pageSize", { type: Number, ...{ default: 25 } });
144
159
  const sort = defineModel("sort", { type: null, ...{ default: void 0 } });
145
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();
146
165
  const columnDefs = computed(() => {
147
166
  const headerText = props.headerText;
148
167
  if (!headerText) return props.columns;
@@ -180,8 +199,10 @@ const gridLocaleText = computed(() => ({
180
199
  previousPage: text.value.previousPage,
181
200
  noRowsToShow: text.value.noRows,
182
201
  loadingOoo: text.value.loading,
202
+ ...props.gridOptions?.localeText,
183
203
  ...props.localeText
184
204
  }));
205
+ const gridContext = computed(() => ({ ...props.gridOptions?.context, ...cellContext.value }));
185
206
  const live = useLiveMessage();
186
207
  let announcedCount = null;
187
208
  const theme = computed(() => getGridTheme(props.density));
@@ -204,8 +225,8 @@ const noRowsTemplate = computed(
204
225
  const loadingTemplate = computed(
205
226
  () => `<div class="k-data-grid-overlay"><span class="k-spinner k-spinner-md" aria-hidden="true"></span></div>`
206
227
  );
207
- const rowSelection = computed(() => {
208
- if (!props.selectable) return void 0;
228
+ const gridRowSelection = computed(() => {
229
+ if (!props.selectable) return props.gridOptions?.rowSelection;
209
230
  return {
210
231
  mode: props.selectable === "single" ? "singleRow" : "multiRow",
211
232
  enableClickSelection: false
@@ -218,7 +239,8 @@ const defaultColDef = computed(() => ({
218
239
  minWidth: 80,
219
240
  comparator: props.serverSort ? () => 0 : void 0,
220
241
  valueFormatter: (params) => isEmpty(params.value) ? "-" : String(params.value),
221
- cellRenderer: renderCell
242
+ cellRenderer: renderCell,
243
+ ...props.gridOptions?.defaultColDef
222
244
  }));
223
245
  function isEmpty(value) {
224
246
  return value === null || value === void 0 || value === "";
@@ -241,12 +263,20 @@ function renderCell(params) {
241
263
  }
242
264
  const storageName = computed(() => props.storageKey ? `k-grid:${props.storageKey}` : null);
243
265
  const hasStoredState = ref(false);
244
- const autoSizeStrategy = computed(
245
- () => hasStoredState.value ? void 0 : { type: "fitGridWidth", defaultMinWidth: 100 }
246
- );
247
- 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) {
248
273
  return String(params.data[props.rowIdKey]);
249
274
  }
275
+ const rowIdGetter = computed(() => {
276
+ if (props.getRowId) return props.getRowId;
277
+ if (props.rowIdKey) return getRowIdByKey;
278
+ return props.gridOptions?.getRowId;
279
+ });
250
280
  function readStoredState() {
251
281
  if (!storageName.value) return null;
252
282
  const stored = readStorageJson(storageName.value);
@@ -1,15 +1,17 @@
1
- import type { FilterChangedEvent, FilterModel, 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,6 +22,8 @@ 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[];
@@ -30,6 +34,7 @@ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_se
30
34
  headerHeight?: number;
31
35
  headerText?: (field: string, column: ColDef<T>) => string | undefined;
32
36
  filterable?: boolean;
37
+ editable?: boolean;
33
38
  } & {
34
39
  query?: string;
35
40
  page?: number;