@toife/vue 3.1.5 → 3.1.6

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 (48) hide show
  1. package/package.json +4 -4
  2. package/src/components/action/action.scss +1 -1
  3. package/src/components/app/app.scss +1 -1
  4. package/src/components/app/app.type.ts +0 -6
  5. package/src/components/app/app.vue +1 -7
  6. package/src/components/avatar/avatar.scss +1 -1
  7. package/src/components/button/button.scss +16 -8
  8. package/src/components/button/button.type.ts +2 -4
  9. package/src/components/card/card/card.scss +1 -1
  10. package/src/components/card/card-body/card-body.scss +1 -1
  11. package/src/components/card/card-footer/card-footer.scss +1 -1
  12. package/src/components/card/card-header/card-header.scss +1 -1
  13. package/src/components/checkbox/checkbox.scss +1 -1
  14. package/src/components/checkbox/checkbox.type.ts +1 -3
  15. package/src/components/collapse/collapse.scss +2 -5
  16. package/src/components/decision-modal/decision-modal.scss +2 -2
  17. package/src/components/divider/divider.scss +1 -1
  18. package/src/components/dropdown/dropdown.scss +1 -1
  19. package/src/components/dropdown/dropdown.type.ts +2 -2
  20. package/src/components/field/field.html +28 -9
  21. package/src/components/field/{outline/outline.scss → field.scss} +20 -118
  22. package/src/components/field/field.type.ts +2 -2
  23. package/src/components/field/field.vue +83 -46
  24. package/src/components/field/index.ts +1 -1
  25. package/src/components/gesture-indicator/gesture-indicator.scss +1 -1
  26. package/src/components/modal/modal.scss +1 -1
  27. package/src/components/present/present.scss +1 -1
  28. package/src/components/present/present.vue +1 -1
  29. package/src/components/radio/radio/radio.scss +1 -1
  30. package/src/components/radio/radio/radio.type.ts +1 -3
  31. package/src/components/route/route-navigator/route-navigator.html +1 -1
  32. package/src/components/route/route-navigator/route-navigator.scss +1 -1
  33. package/src/components/route/route-navigator/route-navigator.vue +1 -1
  34. package/src/components/segmented-field/segmented-field.scss +1 -1
  35. package/src/components/select/select.scss +1 -1
  36. package/src/components/skeleton/skeleton.scss +1 -1
  37. package/src/components/slide-range/slide-range.scss +7 -4
  38. package/src/components/slide-range/slide-range.vue +1 -0
  39. package/src/components/switch/switch.scss +1 -1
  40. package/src/components/switch/switch.type.ts +1 -3
  41. package/src/components/tabs/tabs/tabs.scss +1 -5
  42. package/src/components/tabs/tabs/tabs.type.ts +1 -2
  43. package/src/components/toast/toast/toast.scss +1 -1
  44. package/src/components/toast/toast-content/toast-content.scss +1 -1
  45. package/src/components/toolbar/toolbar.scss +1 -1
  46. package/src/components/field/outline/index.ts +0 -1
  47. package/src/components/field/outline/outline.html +0 -36
  48. package/src/components/field/outline/outline.vue +0 -294
@@ -1,294 +0,0 @@
1
- <style lang="scss" src="./outline.scss" scoped></style>
2
- <template src="./outline.html"></template>
3
- <script lang="ts" setup>
4
- import { computed, nextTick, onMounted, ref, watch } from "vue";
5
- import type { FieldProps, FieldEmit } from "../field.type";
6
- import { cssProperty, cssPrefix } from "../../../utils";
7
-
8
- // Component setup (props, emits, injects)
9
- // ----------------------------------------------------------------------------
10
- const props = withDefaults(defineProps<FieldProps>(), {
11
- modelValue: "",
12
- type: "text",
13
- size: "standard",
14
- disabled: false,
15
- readonly: false,
16
- message: "",
17
- help: "",
18
- variant: "fill",
19
- placeholder: "",
20
- shadow: undefined,
21
- direction: undefined,
22
- });
23
- const emit = defineEmits<FieldEmit>();
24
-
25
- // Reactive state
26
- // ----------------------------------------------------------------------------
27
- const isFocus = ref(false);
28
- const contentRef = ref<HTMLElement>();
29
- const caret = ref(0);
30
- const isComposing = ref(false);
31
-
32
- // Computed properties
33
- // ----------------------------------------------------------------------------
34
- const content = computed(() => {
35
- if (props.value !== undefined && props.value !== null) {
36
- return props.value;
37
- }
38
- return props.modelValue ?? "";
39
- });
40
-
41
- const isContentEmpty = computed(() => {
42
- const v = content.value;
43
- return v === "" || v === undefined || v === null;
44
- });
45
-
46
- const fieldAttrs = computed(() => {
47
- return {
48
- class: [
49
- cssPrefix(["layer", "field"]),
50
- cssPrefix(["role", props.role || ""]),
51
- cssPrefix(["shape", props.shape || ""]),
52
- cssPrefix("field"),
53
- cssPrefix(["size", props.size]),
54
- cssPrefix(["direction", props.direction || "left"]),
55
- props.variant,
56
- props.type,
57
- {
58
- disabled: props.disabled,
59
- focus: isFocus.value,
60
- shadow: props.shadow,
61
- empty: isContentEmpty.value,
62
- readonly: props.readonly,
63
- typing: isComposing.value,
64
- },
65
- ],
66
- style: {
67
- [cssProperty(["field", "line"])]: props.line,
68
- [cssProperty(["field", "max-line"])]: props.maxLine || props.line,
69
- },
70
- };
71
- });
72
-
73
- const fieldContentAttrs = computed(() => ({
74
- class: [cssPrefix("field-content")],
75
- }));
76
-
77
- /** Outer shell: layout, placeholder pseudo-element (must stay off the contenteditable node on iOS). */
78
- const fieldInputShellAttrs = computed(() => ({
79
- class: [cssPrefix("field-input")],
80
- placeholder: props.placeholder,
81
- }));
82
-
83
- /** contenteditable as the literal string "true"|"false" improves mobile WebKit behavior vs boolean. */
84
- const fieldEditableAttrs = computed(() => ({
85
- class: [cssPrefix("field-input-editable")],
86
- name: props.name,
87
- id: props.id,
88
- contenteditable:
89
- props.type === "password" ? undefined : props.disabled || props.readonly ? "false" : "true",
90
- autocomplete: props.autocomplete,
91
- tabindex: props.readonly ? 0 : props.disabled ? -1 : props.tabindex,
92
- }));
93
-
94
- const fieldPasswordInputAttrs = computed(() => ({
95
- class: [cssPrefix("field-input")],
96
- name: props.name,
97
- id: props.id,
98
- placeholder: props.placeholder,
99
- autocomplete: props.autocomplete,
100
- tabindex: props.readonly ? 0 : props.disabled ? -1 : props.tabindex,
101
- readonly: props.readonly,
102
- disabled: props.disabled,
103
- }));
104
-
105
- const fieldMessageAttrs = computed(() => ({
106
- class: [cssPrefix("field-message")],
107
- }));
108
-
109
- const fieldHelpAttrs = computed(() => ({
110
- class: [cssPrefix("field-help")],
111
- }));
112
-
113
- // Methods
114
- // ----------------------------------------------------------------------------
115
- const isBr = (node: Node): node is HTMLBRElement =>
116
- node.nodeType === 1 && (node as HTMLElement).tagName === "BR";
117
-
118
- const isBlockLineBreak = (node: Node, parent: Node | null): node is HTMLElement =>
119
- parent != null && node.nodeType === 1 && ["DIV", "P"].includes((node as HTMLElement).tagName);
120
-
121
- const getCaretOffset = (el: HTMLElement) => {
122
- const selection = window.getSelection();
123
- if (!selection || selection.rangeCount === 0) return 0;
124
-
125
- const range = selection.getRangeAt(0);
126
- const endContainer = range.endContainer;
127
- const endOffset = range.endOffset;
128
- let offset = 0;
129
-
130
- function walk(node: Node, parent: Node | null): boolean {
131
- if (node === endContainer) {
132
- if (node.nodeType === 3) {
133
- offset += endOffset;
134
- } else if (isBr(node)) {
135
- offset += endOffset > 0 ? 1 : 0;
136
- } else if (isBlockLineBreak(node, parent)) {
137
- offset += 1;
138
- } else if (node === el) {
139
- for (let i = 0; i < endOffset; i++) {
140
- const child = node.childNodes[i];
141
- if (!child) break;
142
- if (child.nodeType === 3) offset += child.textContent?.length ?? 0;
143
- else if (isBr(child)) offset += 1;
144
- else if (isBlockLineBreak(child, el)) offset += 1;
145
- }
146
- }
147
- return true;
148
- }
149
- if (node.nodeType === 3) {
150
- offset += node.textContent?.length ?? 0;
151
- } else if (isBr(node)) {
152
- offset += 1;
153
- } else if (isBlockLineBreak(node, parent)) {
154
- offset += 1;
155
- }
156
- for (const child of Array.from(node.childNodes)) {
157
- if (walk(child, node)) return true;
158
- }
159
- return false;
160
- }
161
- walk(el, null);
162
- return offset;
163
- };
164
-
165
- const setCaretOffset = (el: HTMLElement, offset: number) => {
166
- const sel = window.getSelection();
167
- if (!sel) return;
168
- let current = 0;
169
- const range = document.createRange();
170
-
171
- function walk(node: Node): boolean {
172
- if (node.nodeType === 3) {
173
- const len = node.textContent?.length ?? 0;
174
- const next = current + len;
175
- if (offset <= next) {
176
- range.setStart(node, Math.min(offset - current, len));
177
- range.collapse(true);
178
- sel!.removeAllRanges();
179
- sel!.addRange(range);
180
- return true;
181
- }
182
- current = next;
183
- } else if (isBr(node)) {
184
- if (offset <= current) {
185
- range.setStartBefore(node);
186
- range.collapse(true);
187
- sel!.removeAllRanges();
188
- sel!.addRange(range);
189
- return true;
190
- }
191
- if (offset <= current + 1) {
192
- range.setStartAfter(node);
193
- range.collapse(true);
194
- sel!.removeAllRanges();
195
- sel!.addRange(range);
196
- return true;
197
- }
198
- current += 1;
199
- }
200
-
201
- for (const child of Array.from(node.childNodes)) {
202
- if (walk(child)) return true;
203
- }
204
- return false;
205
- }
206
-
207
- walk(el);
208
- };
209
-
210
- const onInput = (ev: Event) => {
211
- if (props.disabled || props.readonly || isComposing.value) return;
212
-
213
- caret.value = getCaretOffset(ev.target as HTMLElement);
214
- emit("input", ev);
215
- };
216
-
217
- const onCompositionStart = () => {
218
- isComposing.value = true;
219
- };
220
-
221
- const onCompositionEnd = (ev: CompositionEvent) => {
222
- const el = ev.target as HTMLElement;
223
- isComposing.value = false;
224
- nextTick(() => {
225
- caret.value = getCaretOffset(el);
226
- const inputEv = new InputEvent("input", { bubbles: true });
227
- el.dispatchEvent(inputEv);
228
- emit("input", inputEv);
229
- });
230
- };
231
-
232
- const normalizeText = (s: string) => s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
233
-
234
- const ensureEditableCanReceiveCaret = (el: HTMLElement) => {
235
- // iOS Safari often refuses to show caret / accept input in an empty contenteditable.
236
- if (!el.textContent?.length && el.childNodes.length === 0) {
237
- el.appendChild(document.createElement("br"));
238
- }
239
- };
240
-
241
- const syncContentFromModel = async () => {
242
- const el = contentRef.value;
243
- if (!el || props.type === "password") return;
244
- if (isComposing.value) return;
245
-
246
- const next = normalizeText(content.value.toString() ?? "");
247
- const current = normalizeText(el.innerText);
248
- if (current === next) return;
249
-
250
- if (isFocus.value) {
251
- const saved = caret.value;
252
- el.innerText = next;
253
- await nextTick();
254
- el.focus();
255
- setCaretOffset(el, Math.min(saved, next.length));
256
- } else {
257
- el.innerText = next;
258
- }
259
- };
260
-
261
- const onFocus = (ev: FocusEvent) => {
262
- if (props.disabled) return;
263
- isFocus.value = true;
264
- const el = contentRef.value;
265
- if (el && props.type !== "password") {
266
- ensureEditableCanReceiveCaret(el);
267
- }
268
- emit("focus", ev);
269
- };
270
-
271
- const onBlur = (ev: FocusEvent) => {
272
- if (props.disabled) return;
273
- isFocus.value = false;
274
- emit("blur", ev);
275
- };
276
-
277
- const onBeforeinput = (ev: Event) => {
278
- if (props.disabled || props.readonly) {
279
- ev.preventDefault();
280
- return;
281
- }
282
- emit("beforeinput", ev);
283
- };
284
-
285
- // Lifecycle
286
- // ----------------------------------------------------------------------------
287
- watch(
288
- () => content.value,
289
- () => void syncContentFromModel(),
290
- { flush: "post" }
291
- );
292
-
293
- onMounted(() => void syncContentFromModel());
294
- </script>