@sewadah/selaras 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/agent/catalog.json +1 -1
- package/dist/module.json +1 -1
- package/dist/runtime/components/Autocomplete.vue +6 -0
- package/dist/runtime/components/Checkbox.d.vue.ts +6 -4
- package/dist/runtime/components/Checkbox.vue +30 -22
- package/dist/runtime/components/Checkbox.vue.d.ts +6 -4
- package/dist/runtime/components/DatePicker.d.vue.ts +27 -27
- package/dist/runtime/components/DatePicker.vue +58 -99
- package/dist/runtime/components/DatePicker.vue.d.ts +27 -27
- package/dist/runtime/components/Editable.d.vue.ts +91 -0
- package/dist/runtime/components/Editable.vue +257 -0
- package/dist/runtime/components/Editable.vue.d.ts +91 -0
- package/dist/runtime/components/Input.d.vue.ts +7 -4
- package/dist/runtime/components/Input.vue +41 -25
- package/dist/runtime/components/Input.vue.d.ts +7 -4
- package/dist/runtime/components/Select.vue +6 -0
- package/dist/runtime/components/Textarea.d.vue.ts +7 -4
- package/dist/runtime/components/Textarea.vue +42 -28
- package/dist/runtime/components/Textarea.vue.d.ts +7 -4
- package/dist/runtime/components/Toolbar.d.vue.ts +25 -0
- package/dist/runtime/components/Toolbar.vue +35 -0
- package/dist/runtime/components/Toolbar.vue.d.ts +25 -0
- package/dist/runtime/components/ToolbarButton.d.vue.ts +20 -0
- package/dist/runtime/components/ToolbarButton.vue +46 -0
- package/dist/runtime/components/ToolbarButton.vue.d.ts +20 -0
- package/dist/runtime/components/ToolbarSeparator.d.vue.ts +10 -0
- package/dist/runtime/components/ToolbarSeparator.vue +22 -0
- package/dist/runtime/components/ToolbarSeparator.vue.d.ts +10 -0
- package/dist/runtime/components/ToolbarToggleGroup.d.vue.ts +37 -0
- package/dist/runtime/components/ToolbarToggleGroup.vue +44 -0
- package/dist/runtime/components/ToolbarToggleGroup.vue.d.ts +37 -0
- package/dist/runtime/internal/ComboboxSelectBase.d.vue.ts +7 -63
- package/dist/runtime/internal/ComboboxSelectBase.vue +58 -54
- package/dist/runtime/internal/ComboboxSelectBase.vue.d.ts +7 -63
- package/dist/runtime/internal/FieldClearButton.d.vue.ts +21 -0
- package/dist/runtime/internal/FieldClearButton.vue +30 -0
- package/dist/runtime/internal/FieldClearButton.vue.d.ts +21 -0
- package/dist/runtime/internal/ListboxOption.d.vue.ts +2 -1
- package/dist/runtime/internal/ListboxOption.vue +3 -2
- package/dist/runtime/internal/ListboxOption.vue.d.ts +2 -1
- package/dist/runtime/theme/date-picker.d.ts +27 -0
- package/dist/runtime/theme/date-picker.js +6 -3
- package/dist/runtime/theme/editable.d.ts +155 -0
- package/dist/runtime/theme/editable.js +25 -0
- package/dist/runtime/theme/field-clear-button.d.ts +56 -0
- package/dist/runtime/theme/field-clear-button.js +15 -0
- package/dist/runtime/theme/input.d.ts +6 -9
- package/dist/runtime/theme/input.js +7 -12
- package/dist/runtime/theme/listbox.js +1 -1
- package/dist/runtime/theme/select.d.ts +15 -0
- package/dist/runtime/theme/select.js +13 -10
- package/dist/runtime/theme/textarea.d.ts +6 -9
- package/dist/runtime/theme/textarea.js +6 -4
- package/dist/runtime/theme/toolbar-button.d.ts +65 -0
- package/dist/runtime/theme/toolbar-button.js +21 -0
- package/dist/runtime/theme/toolbar-separator.d.ts +35 -0
- package/dist/runtime/theme/toolbar-separator.js +8 -0
- package/dist/runtime/theme/toolbar-toggle-group.d.ts +74 -0
- package/dist/runtime/theme/toolbar-toggle-group.js +13 -0
- package/dist/runtime/theme/toolbar.d.ts +35 -0
- package/dist/runtime/theme/toolbar.js +11 -0
- package/dist/runtime/theme-config.d.ts +36 -0
- package/dist/runtime/types/index.d.ts +6 -0
- package/dist/runtime/utils/field-adornments.d.ts +14 -0
- package/dist/runtime/utils/field-adornments.js +36 -0
- package/dist/runtime/utils/field-clear.d.ts +9 -0
- package/dist/runtime/utils/field-clear.js +7 -0
- package/dist/runtime/utils/icons.d.ts +1 -0
- package/dist/runtime/utils/icons.js +1 -0
- package/dist/runtime/utils/messages.d.ts +4 -0
- package/dist/runtime/utils/messages.js +4 -0
- package/dist/runtime/utils/select-contracts.d.ts +3 -0
- package/dist/runtime/utils/toolbar.d.ts +9 -0
- package/dist/runtime/utils/toolbar.js +1 -0
- package/package.json +13 -5
- package/skills/selaras/SKILL.md +11 -7
|
@@ -69,7 +69,7 @@ export interface DatePickerProps {
|
|
|
69
69
|
isYearDisabled?: (year: number) => boolean;
|
|
70
70
|
invalid?: boolean;
|
|
71
71
|
clearable?: boolean;
|
|
72
|
-
/** Chrome color
|
|
72
|
+
/** Chrome color for trigger buttons and calendar navigation. Clear controls remain neutral. */
|
|
73
73
|
color?: ColorRole;
|
|
74
74
|
/** The color of whatever's currently "active" - the selected day/month/year cell and the Done button. */
|
|
75
75
|
activeColor?: ColorRole;
|
|
@@ -90,7 +90,7 @@ export interface DatePickerEmits {
|
|
|
90
90
|
'update:modelValue': [value: DateValue | DateRange | Time | undefined];
|
|
91
91
|
'update:view': [view: 'date' | 'month' | 'year'];
|
|
92
92
|
}
|
|
93
|
-
declare var __VLS_43: {},
|
|
93
|
+
declare var __VLS_43: {}, __VLS_58: {}, __VLS_80: {}, __VLS_96: {}, __VLS_111: {
|
|
94
94
|
date: DateValue;
|
|
95
95
|
dayValue: string;
|
|
96
96
|
selected: boolean;
|
|
@@ -98,7 +98,7 @@ declare var __VLS_43: {}, __VLS_63: {}, __VLS_83: {}, __VLS_99: {}, __VLS_119: {
|
|
|
98
98
|
selectionStart: boolean;
|
|
99
99
|
selectionEnd: boolean;
|
|
100
100
|
highlighted: boolean;
|
|
101
|
-
},
|
|
101
|
+
}, __VLS_114: {}, __VLS_137: {
|
|
102
102
|
date: DateValue;
|
|
103
103
|
dayValue: string;
|
|
104
104
|
selected: boolean;
|
|
@@ -106,61 +106,61 @@ declare var __VLS_43: {}, __VLS_63: {}, __VLS_83: {}, __VLS_99: {}, __VLS_119: {
|
|
|
106
106
|
selectionStart: boolean;
|
|
107
107
|
selectionEnd: boolean;
|
|
108
108
|
highlighted: boolean;
|
|
109
|
-
},
|
|
109
|
+
}, __VLS_140: {}, __VLS_179: {}, __VLS_194: {}, __VLS_216: {}, __VLS_232: {}, __VLS_253: {}, __VLS_276: {}, __VLS_314: {}, __VLS_329: {}, __VLS_351: {}, __VLS_367: {}, __VLS_382: {
|
|
110
110
|
date: DateValue;
|
|
111
111
|
dayValue: string;
|
|
112
112
|
selected: boolean;
|
|
113
113
|
disabled: boolean;
|
|
114
|
-
},
|
|
114
|
+
}, __VLS_385: {}, __VLS_408: {
|
|
115
115
|
date: DateValue;
|
|
116
116
|
dayValue: string;
|
|
117
117
|
selected: boolean;
|
|
118
118
|
disabled: boolean;
|
|
119
|
-
},
|
|
119
|
+
}, __VLS_411: {};
|
|
120
120
|
type __VLS_Slots = {} & {
|
|
121
121
|
'clear-icon'?: (props: typeof __VLS_43) => any;
|
|
122
122
|
} & {
|
|
123
|
-
'trigger-icon'?: (props: typeof
|
|
123
|
+
'trigger-icon'?: (props: typeof __VLS_58) => any;
|
|
124
124
|
} & {
|
|
125
|
-
'trigger-icon'?: (props: typeof
|
|
125
|
+
'trigger-icon'?: (props: typeof __VLS_80) => any;
|
|
126
126
|
} & {
|
|
127
|
-
'clear-icon'?: (props: typeof
|
|
127
|
+
'clear-icon'?: (props: typeof __VLS_96) => any;
|
|
128
128
|
} & {
|
|
129
|
-
day?: (props: typeof
|
|
129
|
+
day?: (props: typeof __VLS_111) => any;
|
|
130
130
|
} & {
|
|
131
|
-
footer?: (props: typeof
|
|
131
|
+
footer?: (props: typeof __VLS_114) => any;
|
|
132
132
|
} & {
|
|
133
|
-
day?: (props: typeof
|
|
133
|
+
day?: (props: typeof __VLS_137) => any;
|
|
134
134
|
} & {
|
|
135
|
-
footer?: (props: typeof
|
|
135
|
+
footer?: (props: typeof __VLS_140) => any;
|
|
136
136
|
} & {
|
|
137
|
-
'clear-icon'?: (props: typeof
|
|
137
|
+
'clear-icon'?: (props: typeof __VLS_179) => any;
|
|
138
138
|
} & {
|
|
139
|
-
'trigger-icon'?: (props: typeof
|
|
139
|
+
'trigger-icon'?: (props: typeof __VLS_194) => any;
|
|
140
140
|
} & {
|
|
141
|
-
'trigger-icon'?: (props: typeof
|
|
141
|
+
'trigger-icon'?: (props: typeof __VLS_216) => any;
|
|
142
142
|
} & {
|
|
143
|
-
'clear-icon'?: (props: typeof
|
|
143
|
+
'clear-icon'?: (props: typeof __VLS_232) => any;
|
|
144
144
|
} & {
|
|
145
|
-
footer?: (props: typeof
|
|
145
|
+
footer?: (props: typeof __VLS_253) => any;
|
|
146
146
|
} & {
|
|
147
|
-
footer?: (props: typeof
|
|
147
|
+
footer?: (props: typeof __VLS_276) => any;
|
|
148
148
|
} & {
|
|
149
|
-
'clear-icon'?: (props: typeof
|
|
149
|
+
'clear-icon'?: (props: typeof __VLS_314) => any;
|
|
150
150
|
} & {
|
|
151
|
-
'trigger-icon'?: (props: typeof
|
|
151
|
+
'trigger-icon'?: (props: typeof __VLS_329) => any;
|
|
152
152
|
} & {
|
|
153
|
-
'trigger-icon'?: (props: typeof
|
|
153
|
+
'trigger-icon'?: (props: typeof __VLS_351) => any;
|
|
154
154
|
} & {
|
|
155
|
-
'clear-icon'?: (props: typeof
|
|
155
|
+
'clear-icon'?: (props: typeof __VLS_367) => any;
|
|
156
156
|
} & {
|
|
157
|
-
day?: (props: typeof
|
|
157
|
+
day?: (props: typeof __VLS_382) => any;
|
|
158
158
|
} & {
|
|
159
|
-
footer?: (props: typeof
|
|
159
|
+
footer?: (props: typeof __VLS_385) => any;
|
|
160
160
|
} & {
|
|
161
|
-
day?: (props: typeof
|
|
161
|
+
day?: (props: typeof __VLS_408) => any;
|
|
162
162
|
} & {
|
|
163
|
-
footer?: (props: typeof
|
|
163
|
+
footer?: (props: typeof __VLS_411) => any;
|
|
164
164
|
};
|
|
165
165
|
declare const __VLS_base: import("vue").DefineComponent<DatePickerProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
166
166
|
"update:modelValue": (value: DateValue | DateRange | Time | undefined) => any;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { VariantProps } from 'tailwind-variants';
|
|
2
|
+
import type { EditableThemeSlots } from '../theme/editable.js';
|
|
3
|
+
import type { ColorRole } from '../utils/color-registry.js';
|
|
4
|
+
import type { UiProp } from '../utils/ui.js';
|
|
5
|
+
import { editableTheme } from '../theme/editable.js';
|
|
6
|
+
type EditableVariants = VariantProps<typeof editableTheme>;
|
|
7
|
+
export type EditableActivationMode = 'focus' | 'dblclick' | 'none';
|
|
8
|
+
export type EditableSubmitMode = 'enter' | 'blur' | 'both' | 'none';
|
|
9
|
+
export interface EditableProps {
|
|
10
|
+
modelValue?: string;
|
|
11
|
+
defaultValue?: string;
|
|
12
|
+
editing?: boolean;
|
|
13
|
+
defaultEditing?: boolean;
|
|
14
|
+
activationMode?: EditableActivationMode;
|
|
15
|
+
submitMode?: EditableSubmitMode;
|
|
16
|
+
controls?: boolean;
|
|
17
|
+
multiline?: boolean;
|
|
18
|
+
rows?: number;
|
|
19
|
+
placeholder?: string | {
|
|
20
|
+
preview: string;
|
|
21
|
+
edit: string;
|
|
22
|
+
};
|
|
23
|
+
selectOnFocus?: boolean;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
readonly?: boolean;
|
|
26
|
+
loading?: boolean;
|
|
27
|
+
invalid?: boolean;
|
|
28
|
+
required?: boolean;
|
|
29
|
+
maxLength?: number;
|
|
30
|
+
id?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
size?: EditableVariants['size'];
|
|
33
|
+
color?: ColorRole;
|
|
34
|
+
editIcon?: string;
|
|
35
|
+
submitIcon?: string;
|
|
36
|
+
cancelIcon?: string;
|
|
37
|
+
editLabel?: string;
|
|
38
|
+
submitLabel?: string;
|
|
39
|
+
cancelLabel?: string;
|
|
40
|
+
ui?: UiProp<EditableThemeSlots>;
|
|
41
|
+
}
|
|
42
|
+
export interface EditableEmits {
|
|
43
|
+
'update:modelValue': [value: string];
|
|
44
|
+
'update:editing': [editing: boolean];
|
|
45
|
+
'edit': [];
|
|
46
|
+
'submit': [value: string];
|
|
47
|
+
'cancel': [value: string];
|
|
48
|
+
}
|
|
49
|
+
export interface EditableSlotProps {
|
|
50
|
+
value: string;
|
|
51
|
+
draft: string;
|
|
52
|
+
editing: boolean;
|
|
53
|
+
disabled: boolean;
|
|
54
|
+
edit: () => void;
|
|
55
|
+
submit: () => boolean;
|
|
56
|
+
cancel: () => void;
|
|
57
|
+
}
|
|
58
|
+
export interface EditableSlots {
|
|
59
|
+
default?: (props: EditableSlotProps) => any;
|
|
60
|
+
preview?: (props: EditableSlotProps) => any;
|
|
61
|
+
controls?: (props: EditableSlotProps) => any;
|
|
62
|
+
}
|
|
63
|
+
type __VLS_Slots = EditableSlots;
|
|
64
|
+
declare function edit(): void;
|
|
65
|
+
declare function cancel(restore?: boolean): void;
|
|
66
|
+
declare function submit(restore?: boolean): boolean;
|
|
67
|
+
declare const __VLS_base: import("vue").DefineComponent<EditableProps, {
|
|
68
|
+
edit: typeof edit;
|
|
69
|
+
submit: typeof submit;
|
|
70
|
+
cancel: typeof cancel;
|
|
71
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
72
|
+
edit: () => any;
|
|
73
|
+
submit: (value: string) => any;
|
|
74
|
+
cancel: (value: string) => any;
|
|
75
|
+
"update:modelValue": (value: string) => any;
|
|
76
|
+
"update:editing": (editing: boolean) => any;
|
|
77
|
+
}, string, import("vue").PublicProps, Readonly<EditableProps> & Readonly<{
|
|
78
|
+
onEdit?: (() => any) | undefined;
|
|
79
|
+
onSubmit?: ((value: string) => any) | undefined;
|
|
80
|
+
onCancel?: ((value: string) => any) | undefined;
|
|
81
|
+
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
82
|
+
"onUpdate:editing"?: ((editing: boolean) => any) | undefined;
|
|
83
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
84
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
85
|
+
declare const _default: typeof __VLS_export;
|
|
86
|
+
export default _default;
|
|
87
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
88
|
+
new (): {
|
|
89
|
+
$slots: S;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { Primitive } from "reka-ui";
|
|
3
|
+
import { computed, mergeProps, nextTick, onMounted, ref, useAttrs, useId, watch } from "vue";
|
|
4
|
+
import { useFormField } from "../composables/use-form-field";
|
|
5
|
+
import { useIcons } from "../composables/use-icons";
|
|
6
|
+
import { useMessages } from "../composables/use-messages";
|
|
7
|
+
import { editableTheme } from "../theme/editable";
|
|
8
|
+
import { isNativeInputA11yAttr, isNativeInputAttr } from "../utils/native-input";
|
|
9
|
+
import { resolveRegisteredColorRole } from "../utils/registered-colors";
|
|
10
|
+
import { applyClassPrefix, resolveSlot, useComponentTheme, useFallthroughAttrs, useRootProps, useThemeProps } from "../utils/ui";
|
|
11
|
+
import Button from "./Button.vue";
|
|
12
|
+
defineOptions({ inheritAttrs: false });
|
|
13
|
+
const props = defineProps({
|
|
14
|
+
modelValue: { type: String, required: false },
|
|
15
|
+
defaultValue: { type: String, required: false },
|
|
16
|
+
editing: { type: Boolean, required: false, default: void 0 },
|
|
17
|
+
defaultEditing: { type: Boolean, required: false },
|
|
18
|
+
activationMode: { type: String, required: false, default: "focus" },
|
|
19
|
+
submitMode: { type: String, required: false, default: "both" },
|
|
20
|
+
controls: { type: Boolean, required: false, default: true },
|
|
21
|
+
multiline: { type: Boolean, required: false },
|
|
22
|
+
rows: { type: Number, required: false },
|
|
23
|
+
placeholder: { type: [String, Object], required: false },
|
|
24
|
+
selectOnFocus: { type: Boolean, required: false, default: true },
|
|
25
|
+
disabled: { type: Boolean, required: false },
|
|
26
|
+
readonly: { type: Boolean, required: false },
|
|
27
|
+
loading: { type: Boolean, required: false },
|
|
28
|
+
invalid: { type: Boolean, required: false },
|
|
29
|
+
required: { type: Boolean, required: false },
|
|
30
|
+
maxLength: { type: Number, required: false },
|
|
31
|
+
id: { type: String, required: false },
|
|
32
|
+
name: { type: String, required: false },
|
|
33
|
+
size: { type: null, required: false },
|
|
34
|
+
color: { type: null, required: false },
|
|
35
|
+
editIcon: { type: String, required: false },
|
|
36
|
+
submitIcon: { type: String, required: false },
|
|
37
|
+
cancelIcon: { type: String, required: false },
|
|
38
|
+
editLabel: { type: String, required: false },
|
|
39
|
+
submitLabel: { type: String, required: false },
|
|
40
|
+
cancelLabel: { type: String, required: false },
|
|
41
|
+
ui: { type: Object, required: false }
|
|
42
|
+
});
|
|
43
|
+
const emit = defineEmits(["update:modelValue", "update:editing", "edit", "submit", "cancel"]);
|
|
44
|
+
defineSlots();
|
|
45
|
+
const field = useFormField();
|
|
46
|
+
const attrs = useAttrs();
|
|
47
|
+
const icons = useIcons();
|
|
48
|
+
const messages = useMessages();
|
|
49
|
+
const defaults = useThemeProps("editable");
|
|
50
|
+
const innerValue = ref(props.defaultValue ?? "");
|
|
51
|
+
const value = computed(() => props.modelValue ?? innerValue.value);
|
|
52
|
+
const draft = ref(value.value);
|
|
53
|
+
const innerEditing = ref(!!props.defaultEditing);
|
|
54
|
+
const isEditing = computed(() => (props.editing ?? innerEditing.value) && !props.disabled && !props.readonly);
|
|
55
|
+
const blocked = computed(() => !!(props.disabled || props.readonly || props.loading));
|
|
56
|
+
const root = ref();
|
|
57
|
+
const input = ref();
|
|
58
|
+
const preview = ref();
|
|
59
|
+
const inputId = props.id ?? field?.id ?? useId();
|
|
60
|
+
const fieldName = computed(() => props.name ?? field?.name);
|
|
61
|
+
const formId = computed(() => attrs.form);
|
|
62
|
+
const invalid = computed(() => !!props.invalid || !!field?.invalid.value);
|
|
63
|
+
const size = computed(() => props.size ?? field?.size ?? defaults.value.size ?? "md");
|
|
64
|
+
const color = computed(() => resolveRegisteredColorRole(props.color ?? defaults.value.color ?? "primary", "primary"));
|
|
65
|
+
const buttonSize = computed(() => size.value === "lg" ? "md" : "sm");
|
|
66
|
+
const theme = useComponentTheme("editable", editableTheme);
|
|
67
|
+
const ui = computed(() => theme.value({ size: size.value, color: color.value, invalid: invalid.value, multiline: props.multiline, empty: !value.value }));
|
|
68
|
+
const placeholder = computed(() => typeof props.placeholder === "string" ? { preview: props.placeholder, edit: props.placeholder } : props.placeholder ?? { preview: messages.value.editablePlaceholder, edit: "" });
|
|
69
|
+
const isInputAttr = (key) => isNativeInputAttr(key) || isNativeInputA11yAttr(key);
|
|
70
|
+
const rootProps = useRootProps(() => ui.value.root, () => props.ui?.root, { exclude: isInputAttr });
|
|
71
|
+
const nativeAttrs = useFallthroughAttrs(isInputAttr);
|
|
72
|
+
const labelledBy = computed(() => attrs["aria-labelledby"] ?? field?.labelId);
|
|
73
|
+
const describedBy = computed(() => [attrs["aria-describedby"], field?.describedBy.value].filter(Boolean).join(" ") || void 0);
|
|
74
|
+
const label = computed(() => attrs["aria-label"] ?? (labelledBy.value ? void 0 : messages.value.editableInput));
|
|
75
|
+
const inputProps = computed(() => mergeProps(resolveSlot(ui.value.input, props.ui?.input), nativeAttrs.value));
|
|
76
|
+
const scope = computed(() => ({ value: value.value, draft: draft.value, editing: isEditing.value, disabled: blocked.value, edit, submit, cancel }));
|
|
77
|
+
let restoreTarget;
|
|
78
|
+
let restoringFocus = false;
|
|
79
|
+
let restoreOnClose = true;
|
|
80
|
+
let closeReason;
|
|
81
|
+
const composing = ref(false);
|
|
82
|
+
function setEditing(editing) {
|
|
83
|
+
innerEditing.value = editing;
|
|
84
|
+
emit("update:editing", editing);
|
|
85
|
+
}
|
|
86
|
+
async function focusInput() {
|
|
87
|
+
await nextTick();
|
|
88
|
+
if (!isEditing.value)
|
|
89
|
+
return;
|
|
90
|
+
input.value?.focus({ preventScroll: true });
|
|
91
|
+
if (props.selectOnFocus)
|
|
92
|
+
input.value?.select();
|
|
93
|
+
}
|
|
94
|
+
function edit() {
|
|
95
|
+
if (!blocked.value && !isEditing.value)
|
|
96
|
+
setEditing(true);
|
|
97
|
+
}
|
|
98
|
+
function cancel(restore = true) {
|
|
99
|
+
if (!isEditing.value)
|
|
100
|
+
return;
|
|
101
|
+
restoreOnClose = restore;
|
|
102
|
+
setEditing(false);
|
|
103
|
+
}
|
|
104
|
+
function submit(restore = true) {
|
|
105
|
+
if (!isEditing.value || blocked.value || closeReason)
|
|
106
|
+
return false;
|
|
107
|
+
if (input.value && !input.value.checkValidity()) {
|
|
108
|
+
input.value.reportValidity();
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
closeReason = "submit";
|
|
112
|
+
restoreOnClose = restore;
|
|
113
|
+
innerValue.value = draft.value;
|
|
114
|
+
emit("update:modelValue", draft.value);
|
|
115
|
+
setEditing(false);
|
|
116
|
+
emit("submit", draft.value);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
watch(value, (next) => draft.value = next);
|
|
120
|
+
watch(isEditing, async (editing, previous) => {
|
|
121
|
+
if (editing) {
|
|
122
|
+
restoreTarget = document.activeElement instanceof HTMLElement ? document.activeElement : void 0;
|
|
123
|
+
draft.value = value.value;
|
|
124
|
+
closeReason = void 0;
|
|
125
|
+
restoreOnClose = true;
|
|
126
|
+
emit("edit");
|
|
127
|
+
await focusInput();
|
|
128
|
+
} else if (previous) {
|
|
129
|
+
if (closeReason !== "submit")
|
|
130
|
+
emit("cancel", value.value);
|
|
131
|
+
draft.value = value.value;
|
|
132
|
+
closeReason = void 0;
|
|
133
|
+
await nextTick();
|
|
134
|
+
if (restoreOnClose) {
|
|
135
|
+
restoringFocus = true;
|
|
136
|
+
const target = restoreTarget?.isConnected ? restoreTarget : preview.value?.$el;
|
|
137
|
+
target?.focus({ preventScroll: true });
|
|
138
|
+
restoringFocus = false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
watch(() => props.disabled || props.readonly, (blocked2) => {
|
|
143
|
+
if (blocked2) {
|
|
144
|
+
innerEditing.value = false;
|
|
145
|
+
if (props.editing)
|
|
146
|
+
emit("update:editing", false);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
onMounted(() => {
|
|
150
|
+
if (isEditing.value)
|
|
151
|
+
focusInput();
|
|
152
|
+
});
|
|
153
|
+
async function onFocusOut(event) {
|
|
154
|
+
if (event.relatedTarget instanceof Node && root.value?.contains(event.relatedTarget))
|
|
155
|
+
return;
|
|
156
|
+
await nextTick();
|
|
157
|
+
if (!isEditing.value || props.loading || root.value?.contains(document.activeElement))
|
|
158
|
+
return;
|
|
159
|
+
if (props.submitMode === "blur" || props.submitMode === "both")
|
|
160
|
+
submit(false);
|
|
161
|
+
else if (props.submitMode === "enter")
|
|
162
|
+
cancel(false);
|
|
163
|
+
}
|
|
164
|
+
function onKeydown(event) {
|
|
165
|
+
if (event.defaultPrevented || event.isComposing || composing.value || event.keyCode === 229)
|
|
166
|
+
return;
|
|
167
|
+
if (event.key === "Escape") {
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
event.stopPropagation();
|
|
170
|
+
cancel();
|
|
171
|
+
} else if (event.key === "Enter" && (!props.multiline || event.ctrlKey || event.metaKey)) {
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
event.stopPropagation();
|
|
174
|
+
if (props.submitMode === "enter" || props.submitMode === "both")
|
|
175
|
+
submit();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function onPreviewFocus() {
|
|
179
|
+
if (!restoringFocus && props.activationMode === "focus")
|
|
180
|
+
edit();
|
|
181
|
+
}
|
|
182
|
+
function onPreviewKeydown(event) {
|
|
183
|
+
if (props.activationMode !== "none" && (event.key === "Enter" || event.key === " ")) {
|
|
184
|
+
event.preventDefault();
|
|
185
|
+
edit();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function onInvalid(event) {
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
edit();
|
|
191
|
+
await nextTick();
|
|
192
|
+
input.value?.reportValidity();
|
|
193
|
+
}
|
|
194
|
+
defineExpose({ edit, submit, cancel });
|
|
195
|
+
</script>
|
|
196
|
+
|
|
197
|
+
<template>
|
|
198
|
+
<div ref="root" :data-selaras-color="invalid ? 'danger' : color" :data-editing="isEditing ? '' : void 0" :aria-busy="loading || void 0" :data-selaras-field-filled="value ? '' : void 0" :data-selaras-field-active="isEditing ? '' : void 0" v-bind="rootProps" @focusout="onFocusOut">
|
|
199
|
+
<div v-bind="resolveSlot(ui.area, props.ui?.area)">
|
|
200
|
+
<component
|
|
201
|
+
:is="multiline ? 'textarea' : 'input'"
|
|
202
|
+
v-if="isEditing"
|
|
203
|
+
:id="inputId"
|
|
204
|
+
ref="input"
|
|
205
|
+
:value="draft"
|
|
206
|
+
:rows="multiline ? rows ?? 3 : void 0"
|
|
207
|
+
:placeholder="placeholder.edit"
|
|
208
|
+
:disabled="disabled || loading"
|
|
209
|
+
:required="required"
|
|
210
|
+
:maxlength="maxLength"
|
|
211
|
+
:aria-invalid="invalid || void 0"
|
|
212
|
+
v-bind="inputProps"
|
|
213
|
+
:aria-label="label"
|
|
214
|
+
:aria-labelledby="labelledBy"
|
|
215
|
+
:aria-describedby="describedBy"
|
|
216
|
+
@input="draft = $event.target.value"
|
|
217
|
+
@keydown="onKeydown"
|
|
218
|
+
@compositionstart="composing = true"
|
|
219
|
+
@compositionend="composing = false"
|
|
220
|
+
/>
|
|
221
|
+
<Primitive
|
|
222
|
+
v-else
|
|
223
|
+
:id="inputId"
|
|
224
|
+
ref="preview"
|
|
225
|
+
:as="activationMode === 'none' || readonly ? 'span' : 'button'"
|
|
226
|
+
:type="activationMode !== 'none' && !readonly ? 'button' : void 0"
|
|
227
|
+
:tabindex="activationMode === 'none' || readonly ? -1 : void 0"
|
|
228
|
+
:disabled="disabled || loading || void 0"
|
|
229
|
+
:aria-disabled="disabled || loading || void 0"
|
|
230
|
+
:aria-label="attrs['aria-label']"
|
|
231
|
+
:aria-labelledby="labelledBy"
|
|
232
|
+
:aria-describedby="describedBy"
|
|
233
|
+
v-bind="resolveSlot(ui.preview, props.ui?.preview)"
|
|
234
|
+
@focus="onPreviewFocus"
|
|
235
|
+
@click="activationMode === 'focus' && edit()"
|
|
236
|
+
@dblclick="activationMode === 'dblclick' && edit()"
|
|
237
|
+
@keydown="onPreviewKeydown"
|
|
238
|
+
>
|
|
239
|
+
<slot name="preview" v-bind="scope">
|
|
240
|
+
<slot v-bind="scope">
|
|
241
|
+
{{ value || placeholder.preview }}
|
|
242
|
+
</slot>
|
|
243
|
+
</slot>
|
|
244
|
+
</Primitive>
|
|
245
|
+
</div>
|
|
246
|
+
<div v-if="controls || $slots.controls" v-bind="resolveSlot(ui.controls, props.ui?.controls)">
|
|
247
|
+
<slot name="controls" v-bind="scope">
|
|
248
|
+
<template v-if="isEditing">
|
|
249
|
+
<Button type="button" :size="buttonSize" :color="color" variant="soft" square :disabled="blocked" :loading="loading" :icon="submitIcon ?? icons.check" :aria-label="submitLabel ?? messages.save" :title="submitLabel ?? messages.save" v-bind="resolveSlot(ui.submit, props.ui?.submit)" @click="submit()" />
|
|
250
|
+
<Button type="button" :size="buttonSize" color="neutral" variant="ghost" square :disabled="blocked" :icon="cancelIcon ?? icons.close" :aria-label="cancelLabel ?? messages.cancel" :title="cancelLabel ?? messages.cancel" v-bind="resolveSlot(ui.cancel, props.ui?.cancel)" @click="cancel()" />
|
|
251
|
+
</template>
|
|
252
|
+
<Button v-else type="button" :size="buttonSize" color="neutral" variant="ghost" square :disabled="blocked" :loading="loading" :icon="editIcon ?? icons.edit" :aria-label="editLabel ?? messages.edit" :title="editLabel ?? messages.edit" v-bind="resolveSlot(ui.edit, props.ui?.edit)" @click="edit" />
|
|
253
|
+
</slot>
|
|
254
|
+
</div>
|
|
255
|
+
<component :is="multiline ? 'textarea' : 'input'" v-if="fieldName" :class="applyClassPrefix('sr-only')" :type="multiline ? void 0 : 'text'" tabindex="-1" aria-hidden="true" :name="fieldName" :form="formId" :value="value" :required="required" :disabled="disabled" :readonly="readonly" @invalid="onInvalid" />
|
|
256
|
+
</div>
|
|
257
|
+
</template>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { VariantProps } from 'tailwind-variants';
|
|
2
|
+
import type { EditableThemeSlots } from '../theme/editable.js';
|
|
3
|
+
import type { ColorRole } from '../utils/color-registry.js';
|
|
4
|
+
import type { UiProp } from '../utils/ui.js';
|
|
5
|
+
import { editableTheme } from '../theme/editable.js';
|
|
6
|
+
type EditableVariants = VariantProps<typeof editableTheme>;
|
|
7
|
+
export type EditableActivationMode = 'focus' | 'dblclick' | 'none';
|
|
8
|
+
export type EditableSubmitMode = 'enter' | 'blur' | 'both' | 'none';
|
|
9
|
+
export interface EditableProps {
|
|
10
|
+
modelValue?: string;
|
|
11
|
+
defaultValue?: string;
|
|
12
|
+
editing?: boolean;
|
|
13
|
+
defaultEditing?: boolean;
|
|
14
|
+
activationMode?: EditableActivationMode;
|
|
15
|
+
submitMode?: EditableSubmitMode;
|
|
16
|
+
controls?: boolean;
|
|
17
|
+
multiline?: boolean;
|
|
18
|
+
rows?: number;
|
|
19
|
+
placeholder?: string | {
|
|
20
|
+
preview: string;
|
|
21
|
+
edit: string;
|
|
22
|
+
};
|
|
23
|
+
selectOnFocus?: boolean;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
readonly?: boolean;
|
|
26
|
+
loading?: boolean;
|
|
27
|
+
invalid?: boolean;
|
|
28
|
+
required?: boolean;
|
|
29
|
+
maxLength?: number;
|
|
30
|
+
id?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
size?: EditableVariants['size'];
|
|
33
|
+
color?: ColorRole;
|
|
34
|
+
editIcon?: string;
|
|
35
|
+
submitIcon?: string;
|
|
36
|
+
cancelIcon?: string;
|
|
37
|
+
editLabel?: string;
|
|
38
|
+
submitLabel?: string;
|
|
39
|
+
cancelLabel?: string;
|
|
40
|
+
ui?: UiProp<EditableThemeSlots>;
|
|
41
|
+
}
|
|
42
|
+
export interface EditableEmits {
|
|
43
|
+
'update:modelValue': [value: string];
|
|
44
|
+
'update:editing': [editing: boolean];
|
|
45
|
+
'edit': [];
|
|
46
|
+
'submit': [value: string];
|
|
47
|
+
'cancel': [value: string];
|
|
48
|
+
}
|
|
49
|
+
export interface EditableSlotProps {
|
|
50
|
+
value: string;
|
|
51
|
+
draft: string;
|
|
52
|
+
editing: boolean;
|
|
53
|
+
disabled: boolean;
|
|
54
|
+
edit: () => void;
|
|
55
|
+
submit: () => boolean;
|
|
56
|
+
cancel: () => void;
|
|
57
|
+
}
|
|
58
|
+
export interface EditableSlots {
|
|
59
|
+
default?: (props: EditableSlotProps) => any;
|
|
60
|
+
preview?: (props: EditableSlotProps) => any;
|
|
61
|
+
controls?: (props: EditableSlotProps) => any;
|
|
62
|
+
}
|
|
63
|
+
type __VLS_Slots = EditableSlots;
|
|
64
|
+
declare function edit(): void;
|
|
65
|
+
declare function cancel(restore?: boolean): void;
|
|
66
|
+
declare function submit(restore?: boolean): boolean;
|
|
67
|
+
declare const __VLS_base: import("vue").DefineComponent<EditableProps, {
|
|
68
|
+
edit: typeof edit;
|
|
69
|
+
submit: typeof submit;
|
|
70
|
+
cancel: typeof cancel;
|
|
71
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
72
|
+
edit: () => any;
|
|
73
|
+
submit: (value: string) => any;
|
|
74
|
+
cancel: (value: string) => any;
|
|
75
|
+
"update:modelValue": (value: string) => any;
|
|
76
|
+
"update:editing": (editing: boolean) => any;
|
|
77
|
+
}, string, import("vue").PublicProps, Readonly<EditableProps> & Readonly<{
|
|
78
|
+
onEdit?: (() => any) | undefined;
|
|
79
|
+
onSubmit?: ((value: string) => any) | undefined;
|
|
80
|
+
onCancel?: ((value: string) => any) | undefined;
|
|
81
|
+
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
82
|
+
"onUpdate:editing"?: ((editing: boolean) => any) | undefined;
|
|
83
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
84
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
85
|
+
declare const _default: typeof __VLS_export;
|
|
86
|
+
export default _default;
|
|
87
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
88
|
+
new (): {
|
|
89
|
+
$slots: S;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { VariantProps } from 'tailwind-variants';
|
|
2
2
|
import type { InputThemeSlots } from '../theme/input.js';
|
|
3
3
|
import type { ColorRole } from '../utils/color-registry.js';
|
|
4
|
+
import type { FieldAdornmentSlotProps } from '../utils/field-adornments.js';
|
|
4
5
|
import type { UiProp } from '../utils/ui.js';
|
|
5
6
|
import { inputTheme } from '../theme/input.js';
|
|
6
7
|
type InputVariants = VariantProps<typeof inputTheme>;
|
|
8
|
+
type __VLS_Slots = InputSlots;
|
|
7
9
|
export interface InputProps {
|
|
8
10
|
id?: string;
|
|
9
11
|
name?: string;
|
|
@@ -23,10 +25,11 @@ export interface InputProps {
|
|
|
23
25
|
export interface InputEmits {
|
|
24
26
|
'update:modelValue': [value: string];
|
|
25
27
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
'
|
|
29
|
-
|
|
28
|
+
export interface InputSlots {
|
|
29
|
+
'leading'?: (props: FieldAdornmentSlotProps) => any;
|
|
30
|
+
'trailing'?: (props: FieldAdornmentSlotProps) => any;
|
|
31
|
+
'clear-icon'?: () => any;
|
|
32
|
+
}
|
|
30
33
|
declare const __VLS_base: import("vue").DefineComponent<InputProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
34
|
"update:modelValue": (value: string) => any;
|
|
32
35
|
}, string, import("vue").PublicProps, Readonly<InputProps> & Readonly<{
|