@sewadah/selaras 0.9.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.
Files changed (66) hide show
  1. package/README.md +11 -0
  2. package/dist/agent/catalog.json +1 -1
  3. package/dist/module.json +1 -1
  4. package/dist/runtime/components/Checkbox.d.vue.ts +6 -4
  5. package/dist/runtime/components/Checkbox.vue +30 -22
  6. package/dist/runtime/components/Checkbox.vue.d.ts +6 -4
  7. package/dist/runtime/components/DatePicker.d.vue.ts +27 -27
  8. package/dist/runtime/components/DatePicker.vue +58 -99
  9. package/dist/runtime/components/DatePicker.vue.d.ts +27 -27
  10. package/dist/runtime/components/Editable.d.vue.ts +91 -0
  11. package/dist/runtime/components/Editable.vue +257 -0
  12. package/dist/runtime/components/Editable.vue.d.ts +91 -0
  13. package/dist/runtime/components/Input.vue +21 -22
  14. package/dist/runtime/components/Textarea.vue +20 -23
  15. package/dist/runtime/components/Toolbar.d.vue.ts +25 -0
  16. package/dist/runtime/components/Toolbar.vue +35 -0
  17. package/dist/runtime/components/Toolbar.vue.d.ts +25 -0
  18. package/dist/runtime/components/ToolbarButton.d.vue.ts +20 -0
  19. package/dist/runtime/components/ToolbarButton.vue +46 -0
  20. package/dist/runtime/components/ToolbarButton.vue.d.ts +20 -0
  21. package/dist/runtime/components/ToolbarSeparator.d.vue.ts +10 -0
  22. package/dist/runtime/components/ToolbarSeparator.vue +22 -0
  23. package/dist/runtime/components/ToolbarSeparator.vue.d.ts +10 -0
  24. package/dist/runtime/components/ToolbarToggleGroup.d.vue.ts +37 -0
  25. package/dist/runtime/components/ToolbarToggleGroup.vue +44 -0
  26. package/dist/runtime/components/ToolbarToggleGroup.vue.d.ts +37 -0
  27. package/dist/runtime/internal/ComboboxSelectBase.vue +41 -50
  28. package/dist/runtime/internal/FieldClearButton.d.vue.ts +21 -0
  29. package/dist/runtime/internal/FieldClearButton.vue +30 -0
  30. package/dist/runtime/internal/FieldClearButton.vue.d.ts +21 -0
  31. package/dist/runtime/internal/ListboxOption.d.vue.ts +2 -1
  32. package/dist/runtime/internal/ListboxOption.vue +3 -2
  33. package/dist/runtime/internal/ListboxOption.vue.d.ts +2 -1
  34. package/dist/runtime/theme/date-picker.d.ts +27 -0
  35. package/dist/runtime/theme/date-picker.js +6 -3
  36. package/dist/runtime/theme/editable.d.ts +155 -0
  37. package/dist/runtime/theme/editable.js +25 -0
  38. package/dist/runtime/theme/field-clear-button.d.ts +56 -0
  39. package/dist/runtime/theme/field-clear-button.js +15 -0
  40. package/dist/runtime/theme/input.d.ts +0 -9
  41. package/dist/runtime/theme/input.js +5 -12
  42. package/dist/runtime/theme/listbox.js +1 -1
  43. package/dist/runtime/theme/select.d.ts +9 -0
  44. package/dist/runtime/theme/select.js +11 -10
  45. package/dist/runtime/theme/textarea.d.ts +0 -9
  46. package/dist/runtime/theme/textarea.js +4 -4
  47. package/dist/runtime/theme/toolbar-button.d.ts +65 -0
  48. package/dist/runtime/theme/toolbar-button.js +21 -0
  49. package/dist/runtime/theme/toolbar-separator.d.ts +35 -0
  50. package/dist/runtime/theme/toolbar-separator.js +8 -0
  51. package/dist/runtime/theme/toolbar-toggle-group.d.ts +74 -0
  52. package/dist/runtime/theme/toolbar-toggle-group.js +13 -0
  53. package/dist/runtime/theme/toolbar.d.ts +35 -0
  54. package/dist/runtime/theme/toolbar.js +11 -0
  55. package/dist/runtime/theme-config.d.ts +36 -0
  56. package/dist/runtime/types/index.d.ts +5 -0
  57. package/dist/runtime/utils/field-clear.d.ts +9 -0
  58. package/dist/runtime/utils/field-clear.js +7 -0
  59. package/dist/runtime/utils/icons.d.ts +1 -0
  60. package/dist/runtime/utils/icons.js +1 -0
  61. package/dist/runtime/utils/messages.d.ts +4 -0
  62. package/dist/runtime/utils/messages.js +4 -0
  63. package/dist/runtime/utils/toolbar.d.ts +9 -0
  64. package/dist/runtime/utils/toolbar.js +1 -0
  65. package/package.json +9 -2
  66. package/skills/selaras/SKILL.md +4 -0
@@ -69,7 +69,7 @@ export interface DatePickerProps {
69
69
  isYearDisabled?: (year: number) => boolean;
70
70
  invalid?: boolean;
71
71
  clearable?: boolean;
72
- /** Chrome color - the clear/trigger buttons and prev/next month nav. Only the color changes; each button keeps its own deliberately-different variant (icon-only buttons use `text`, nav arrows use `ghost`). */
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: {}, __VLS_63: {}, __VLS_83: {}, __VLS_99: {}, __VLS_119: {
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
- }, __VLS_122: {}, __VLS_145: {
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
- }, __VLS_148: {}, __VLS_187: {}, __VLS_207: {}, __VLS_227: {}, __VLS_243: {}, __VLS_269: {}, __VLS_292: {}, __VLS_330: {}, __VLS_350: {}, __VLS_370: {}, __VLS_386: {}, __VLS_406: {
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
- }, __VLS_409: {}, __VLS_432: {
114
+ }, __VLS_385: {}, __VLS_408: {
115
115
  date: DateValue;
116
116
  dayValue: string;
117
117
  selected: boolean;
118
118
  disabled: boolean;
119
- }, __VLS_435: {};
119
+ }, __VLS_411: {};
120
120
  type __VLS_Slots = {} & {
121
121
  'clear-icon'?: (props: typeof __VLS_43) => any;
122
122
  } & {
123
- 'trigger-icon'?: (props: typeof __VLS_63) => any;
123
+ 'trigger-icon'?: (props: typeof __VLS_58) => any;
124
124
  } & {
125
- 'trigger-icon'?: (props: typeof __VLS_83) => any;
125
+ 'trigger-icon'?: (props: typeof __VLS_80) => any;
126
126
  } & {
127
- 'clear-icon'?: (props: typeof __VLS_99) => any;
127
+ 'clear-icon'?: (props: typeof __VLS_96) => any;
128
128
  } & {
129
- day?: (props: typeof __VLS_119) => any;
129
+ day?: (props: typeof __VLS_111) => any;
130
130
  } & {
131
- footer?: (props: typeof __VLS_122) => any;
131
+ footer?: (props: typeof __VLS_114) => any;
132
132
  } & {
133
- day?: (props: typeof __VLS_145) => any;
133
+ day?: (props: typeof __VLS_137) => any;
134
134
  } & {
135
- footer?: (props: typeof __VLS_148) => any;
135
+ footer?: (props: typeof __VLS_140) => any;
136
136
  } & {
137
- 'clear-icon'?: (props: typeof __VLS_187) => any;
137
+ 'clear-icon'?: (props: typeof __VLS_179) => any;
138
138
  } & {
139
- 'trigger-icon'?: (props: typeof __VLS_207) => any;
139
+ 'trigger-icon'?: (props: typeof __VLS_194) => any;
140
140
  } & {
141
- 'trigger-icon'?: (props: typeof __VLS_227) => any;
141
+ 'trigger-icon'?: (props: typeof __VLS_216) => any;
142
142
  } & {
143
- 'clear-icon'?: (props: typeof __VLS_243) => any;
143
+ 'clear-icon'?: (props: typeof __VLS_232) => any;
144
144
  } & {
145
- footer?: (props: typeof __VLS_269) => any;
145
+ footer?: (props: typeof __VLS_253) => any;
146
146
  } & {
147
- footer?: (props: typeof __VLS_292) => any;
147
+ footer?: (props: typeof __VLS_276) => any;
148
148
  } & {
149
- 'clear-icon'?: (props: typeof __VLS_330) => any;
149
+ 'clear-icon'?: (props: typeof __VLS_314) => any;
150
150
  } & {
151
- 'trigger-icon'?: (props: typeof __VLS_350) => any;
151
+ 'trigger-icon'?: (props: typeof __VLS_329) => any;
152
152
  } & {
153
- 'trigger-icon'?: (props: typeof __VLS_370) => any;
153
+ 'trigger-icon'?: (props: typeof __VLS_351) => any;
154
154
  } & {
155
- 'clear-icon'?: (props: typeof __VLS_386) => any;
155
+ 'clear-icon'?: (props: typeof __VLS_367) => any;
156
156
  } & {
157
- day?: (props: typeof __VLS_406) => any;
157
+ day?: (props: typeof __VLS_382) => any;
158
158
  } & {
159
- footer?: (props: typeof __VLS_409) => any;
159
+ footer?: (props: typeof __VLS_385) => any;
160
160
  } & {
161
- day?: (props: typeof __VLS_432) => any;
161
+ day?: (props: typeof __VLS_408) => any;
162
162
  } & {
163
- footer?: (props: typeof __VLS_435) => any;
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,14 +1,13 @@
1
1
  <script setup>
2
- import { computed, mergeProps } from "vue";
2
+ import { computed, mergeProps, nextTick, ref, useAttrs } from "vue";
3
3
  import { useFormField } from "../composables/use-form-field";
4
- import { useIcons } from "../composables/use-icons";
5
- import { useMessages } from "../composables/use-messages";
4
+ import FieldClearButton from "../internal/FieldClearButton.vue";
6
5
  import { inputTheme } from "../theme/input";
7
6
  import { useFieldAdornments } from "../utils/field-adornments";
7
+ import { canClearField, fieldClearWidths, isFieldReadonly } from "../utils/field-clear";
8
8
  import { isNativeInputA11yAttr, isNativeInputAttr } from "../utils/native-input";
9
9
  import { resolveRegisteredColorRole } from "../utils/registered-colors";
10
10
  import { resolveSlot, useComponentTheme, useFallthroughAttrs, useRootProps, useThemeProps } from "../utils/ui";
11
- import Button from "./Button.vue";
12
11
  import Icon from "./Icon.vue";
13
12
  defineOptions({ inheritAttrs: false });
14
13
  const props = defineProps({
@@ -30,22 +29,26 @@ const emit = defineEmits(["update:modelValue"]);
30
29
  const slots = defineSlots();
31
30
  const { leading, trailing, padding, clearOffset } = useFieldAdornments();
32
31
  const field = useFormField();
32
+ const attrs = useAttrs();
33
+ const inputRef = ref();
33
34
  const effectivePlaceholder = computed(() => props.placeholder || (field?.floatingLabel.value ? " " : void 0));
34
35
  const inputId = computed(() => props.id ?? field?.id);
35
36
  const inputInvalid = computed(() => props.invalid || (field?.invalid.value ?? false));
36
37
  const describedBy = computed(() => field?.describedBy.value);
37
- const hasValue = computed(() => props.modelValue !== void 0 && props.modelValue !== "");
38
- const showClear = computed(() => !!props.clearable && !props.disabled && hasValue.value);
39
- function clear() {
38
+ const hasValue = computed(() => props.modelValue != null && props.modelValue !== "");
39
+ const showClear = computed(() => canClearField(props.clearable, hasValue.value, props.disabled, isFieldReadonly(attrs.readonly)));
40
+ async function clear() {
41
+ if (!showClear.value)
42
+ return;
40
43
  emit("update:modelValue", "");
44
+ await nextTick();
45
+ inputRef.value?.focus();
41
46
  }
42
47
  const themeProps = useThemeProps("input");
43
48
  const effectiveSize = computed(() => props.size ?? field?.size ?? themeProps.value.size ?? "md");
44
- const clearSize = computed(() => ({ sm: "sm", md: "sm", lg: "md" })[effectiveSize.value]);
45
- const adornmentScope = computed(() => ({ disabled: !!props.disabled, size: effectiveSize.value, buttonSize: clearSize.value }));
46
- const clearWidth = computed(() => ({ sm: "1.75rem", md: "2.25rem", lg: "2.5rem" })[effectiveSize.value]);
47
- const icons = useIcons();
48
- const messages = useMessages();
49
+ const adornmentButtonSize = computed(() => ({ sm: "sm", md: "sm", lg: "md" })[effectiveSize.value]);
50
+ const adornmentScope = computed(() => ({ disabled: !!props.disabled, size: effectiveSize.value, buttonSize: adornmentButtonSize.value }));
51
+ const clearWidth = computed(() => fieldClearWidths[effectiveSize.value]);
49
52
  const theme = useComponentTheme("input", inputTheme);
50
53
  const effectiveColor = computed(() => resolveRegisteredColorRole(props.color ?? themeProps.value.color ?? "primary", "primary"));
51
54
  const ui = computed(() => theme.value({
@@ -76,6 +79,7 @@ function clearProps() {
76
79
  <Icon v-else-if="icon" :name="icon" v-bind="resolveSlot(ui.leadingIcon, props.ui?.leadingIcon)" />
77
80
  <input
78
81
  :id="inputId"
82
+ ref="inputRef"
79
83
  data-ui-group-item
80
84
  :value="modelValue"
81
85
  :type="type"
@@ -87,21 +91,16 @@ function clearProps() {
87
91
  v-bind="adornedInputProps()"
88
92
  @input="$emit('update:modelValue', $event.target.value)"
89
93
  >
90
- <Button
94
+ <FieldClearButton
91
95
  v-if="showClear"
92
- :size="clearSize"
93
- variant="text"
94
- color="neutral"
95
- :aria-label="messages.clear"
96
+ :size="effectiveSize"
96
97
  v-bind="clearProps()"
97
98
  @click="clear"
98
99
  >
99
- <template #icon="{ class: iconClass }">
100
- <slot name="clear-icon">
101
- <Icon :name="icons.close" :class="iconClass" />
102
- </slot>
100
+ <template v-if="slots['clear-icon']" #default>
101
+ <slot name="clear-icon" />
103
102
  </template>
104
- </Button>
103
+ </FieldClearButton>
105
104
  <Icon v-else-if="trailingIcon && !slots.trailing" :name="trailingIcon" v-bind="resolveSlot(ui.trailingIcon, props.ui?.trailingIcon)" />
106
105
  <fieldset v-if="slots.trailing" ref="trailing" :disabled="disabled" :inert="disabled || void 0" v-bind="resolveSlot(ui.trailing, props.ui?.trailing)">
107
106
  <slot name="trailing" v-bind="adornmentScope" />