@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.
- package/README.md +41 -22
- package/dist/components/core/FormField.vue +5 -0
- package/dist/components/core/FormField.vue.d.ts +4 -0
- package/dist/components/core/Input.vue +59 -5
- package/dist/components/core/Input.vue.d.ts +8 -4
- package/dist/components/core/Textarea.vue +28 -3
- package/dist/components/core/Textarea.vue.d.ts +6 -4
- package/dist/components/data/DescriptionList.vue.d.ts +1 -1
- package/dist/components/data/Pagination.vue +8 -2
- package/dist/components/data/Pagination.vue.d.ts +2 -0
- package/dist/components/forms/CheckboxGroup.vue +7 -1
- package/dist/components/forms/CheckboxGroup.vue.d.ts +1 -0
- package/dist/components/forms/ColorInput.vue.d.ts +4 -4
- package/dist/components/forms/Combobox.vue +4 -3
- package/dist/components/forms/Combobox.vue.d.ts +1 -0
- package/dist/components/forms/DateInput.vue.d.ts +8 -4
- package/dist/components/forms/NumberInput.vue.d.ts +4 -4
- package/dist/components/forms/PasswordInput.vue.d.ts +8 -4
- package/dist/components/forms/PinInput.vue.d.ts +4 -4
- package/dist/components/forms/Select.vue +21 -2
- package/dist/components/forms/Select.vue.d.ts +3 -3
- package/dist/components/forms/Slider.vue.d.ts +4 -4
- package/dist/components/forms/TimeInput.vue.d.ts +8 -4
- package/dist/components/layout/Card.vue.d.ts +1 -1
- package/dist/components/layout/Separator.vue +22 -7
- package/dist/components/layout/Separator.vue.d.ts +14 -4
- package/dist/components/layout/types.d.ts +1 -0
- package/dist/components/overlays/ConfirmDialog.vue.d.ts +4 -4
- package/dist/components/overlays/ConfirmHost.vue.d.ts +4 -4
- package/dist/components/overlays/ContextMenu.vue.d.ts +2 -2
- package/dist/components/overlays/DropdownMenu.vue.d.ts +2 -2
- package/dist/components/overlays/types.d.ts +2 -2
- package/dist/components/templates/SettingsModal.vue.d.ts +4 -4
- package/dist/composables/useForm.d.ts +9 -2
- package/dist/composables/useForm.mjs +75 -13
- package/dist/composables/useOverlay.d.ts +1 -1
- package/dist/editor/RichTextEditor.vue +95 -9
- package/dist/editor/RichTextEditor.vue.d.ts +4 -1
- package/dist/editor/rich-text-editor.css +21 -0
- package/dist/editor/types.d.ts +1 -0
- package/dist/grid/DataGrid.vue +48 -18
- package/dist/grid/DataGrid.vue.d.ts +6 -1
- package/dist/grid/cells/DateCell.vue +7 -2
- package/dist/grid/cells/ImageCell.vue +6 -1
- package/dist/grid/data-grid.css +16 -2
- package/dist/grid/index.d.ts +2 -1
- package/dist/grid/index.mjs +6 -1
- package/dist/grid/modules.d.ts +1 -0
- package/dist/grid/modules.mjs +23 -0
- package/dist/grid/theme.mjs +1 -1
- package/dist/grid/types.d.ts +4 -0
- package/dist/internal/forms/counter.d.ts +18 -0
- package/dist/internal/forms/counter.mjs +31 -0
- package/dist/internal/overlays/OverlayView.vue.d.ts +4 -4
- package/dist/locale/de.mjs +11 -1
- package/dist/locale/en.mjs +11 -1
- package/dist/locale/it.mjs +11 -1
- package/dist/locale/types.d.ts +10 -0
- package/dist/styles/core/button.css +12 -6
- package/dist/styles/core/form-field.css +6 -0
- package/dist/styles/core/input.css +29 -0
- package/dist/styles/core/textarea.css +10 -0
- package/dist/styles/forms/calendar.css +8 -8
- package/dist/styles/forms/choice.css +6 -0
- package/dist/styles/layout/separator.css +49 -0
- package/dist/styles/shared.css +2 -1
- 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
|
|
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) =>
|
|
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(
|
|
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, {
|
|
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
|
|
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="
|
|
62
|
-
inputmode="
|
|
63
|
-
|
|
64
|
-
:
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/editor/types.d.ts
CHANGED
|
@@ -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[][];
|
package/dist/grid/DataGrid.vue
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
class="k-data-grid"
|
|
4
|
-
:class="[
|
|
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="
|
|
39
|
+
:get-row-id="rowIdGetter"
|
|
37
40
|
:loading="loading"
|
|
38
41
|
:locale-text="gridLocaleText"
|
|
39
|
-
:context="
|
|
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="
|
|
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="
|
|
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 {
|
|
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
|
|
208
|
-
if (!props.selectable) return
|
|
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
|
|
245
|
-
(
|
|
246
|
-
)
|
|
247
|
-
|
|
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;
|