@cfasim-ui/components 0.1.9 → 0.2.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 (76) hide show
  1. package/dist/Box/Box.d.ts +24 -0
  2. package/dist/Box/Box.spec.d.ts +1 -0
  3. package/dist/Box/Box.test.d.ts +1 -0
  4. package/dist/Button/Button.d.ts +29 -0
  5. package/dist/Button/Button.spec.d.ts +1 -0
  6. package/dist/Button/Button.test.d.ts +1 -0
  7. package/dist/Expander/Expander.d.ts +28 -0
  8. package/dist/Expander/Expander.spec.d.ts +1 -0
  9. package/dist/Hint/Hint.d.ts +5 -0
  10. package/dist/Hint/Hint.spec.d.ts +1 -0
  11. package/dist/Hint/Hint.test.d.ts +1 -0
  12. package/dist/Icon/Icon.d.ts +18 -0
  13. package/dist/Icon/Icon.spec.d.ts +1 -0
  14. package/dist/LightDarkToggle/LightDarkToggle.d.ts +2 -0
  15. package/dist/NumberInput/NumberInput.d.ts +21 -0
  16. package/dist/NumberInput/NumberInput.spec.d.ts +1 -0
  17. package/dist/NumberInput/NumberInput.test.d.ts +1 -0
  18. package/dist/SelectBox/SelectBox.d.ts +19 -0
  19. package/dist/SelectBox/SelectBox.spec.d.ts +1 -0
  20. package/dist/SelectBox/SelectBox.test.d.ts +1 -0
  21. package/dist/SidebarLayout/SidebarLayout.d.ts +37 -0
  22. package/dist/SidebarLayout/SidebarLayout.test.d.ts +1 -0
  23. package/dist/Spinner/Spinner.d.ts +9 -0
  24. package/dist/Spinner/Spinner.spec.d.ts +1 -0
  25. package/dist/TextInput/TextInput.d.ts +14 -0
  26. package/dist/TextInput/TextInput.spec.d.ts +1 -0
  27. package/dist/TextInput/TextInput.test.d.ts +1 -0
  28. package/dist/Toggle/Toggle.d.ts +14 -0
  29. package/dist/Toggle/Toggle.spec.d.ts +1 -0
  30. package/dist/Toggle/Toggle.test.d.ts +1 -0
  31. package/dist/index.css +2 -0
  32. package/dist/index.d.ts +15 -0
  33. package/dist/index.js +700 -0
  34. package/package.json +17 -3
  35. package/src/Box/Box.md +0 -41
  36. package/src/Box/Box.spec.ts +0 -13
  37. package/src/Box/Box.test.ts +0 -49
  38. package/src/Box/Box.vue +0 -52
  39. package/src/Button/Button.md +0 -55
  40. package/src/Button/Button.spec.ts +0 -18
  41. package/src/Button/Button.test.ts +0 -36
  42. package/src/Button/Button.vue +0 -81
  43. package/src/Expander/Expander.md +0 -23
  44. package/src/Expander/Expander.spec.ts +0 -14
  45. package/src/Expander/Expander.vue +0 -95
  46. package/src/Hint/Hint.md +0 -24
  47. package/src/Hint/Hint.spec.ts +0 -12
  48. package/src/Hint/Hint.test.ts +0 -34
  49. package/src/Hint/Hint.vue +0 -83
  50. package/src/Icon/Icon.md +0 -55
  51. package/src/Icon/Icon.spec.ts +0 -9
  52. package/src/Icon/Icon.vue +0 -112
  53. package/src/LightDarkToggle/LightDarkToggle.vue +0 -49
  54. package/src/NumberInput/NumberInput.md +0 -187
  55. package/src/NumberInput/NumberInput.spec.ts +0 -10
  56. package/src/NumberInput/NumberInput.test.ts +0 -580
  57. package/src/NumberInput/NumberInput.vue +0 -446
  58. package/src/SelectBox/SelectBox.md +0 -56
  59. package/src/SelectBox/SelectBox.spec.ts +0 -9
  60. package/src/SelectBox/SelectBox.test.ts +0 -42
  61. package/src/SelectBox/SelectBox.vue +0 -190
  62. package/src/SidebarLayout/SidebarLayout.md +0 -104
  63. package/src/SidebarLayout/SidebarLayout.test.ts +0 -86
  64. package/src/SidebarLayout/SidebarLayout.vue +0 -465
  65. package/src/Spinner/Spinner.md +0 -45
  66. package/src/Spinner/Spinner.spec.ts +0 -9
  67. package/src/Spinner/Spinner.vue +0 -55
  68. package/src/TextInput/TextInput.md +0 -41
  69. package/src/TextInput/TextInput.spec.ts +0 -10
  70. package/src/TextInput/TextInput.test.ts +0 -70
  71. package/src/TextInput/TextInput.vue +0 -90
  72. package/src/Toggle/Toggle.md +0 -68
  73. package/src/Toggle/Toggle.spec.ts +0 -13
  74. package/src/Toggle/Toggle.test.ts +0 -35
  75. package/src/Toggle/Toggle.vue +0 -81
  76. package/src/index.ts +0 -15
@@ -1,465 +0,0 @@
1
- <script setup lang="ts">
2
- import {
3
- ref,
4
- computed,
5
- watch,
6
- onMounted,
7
- onUnmounted,
8
- getCurrentInstance,
9
- } from "vue";
10
- import { TabsRoot, TabsList, TabsTrigger, TabsIndicator } from "reka-ui";
11
- import Icon from "../Icon/Icon.vue";
12
- import LightDarkToggle from "../LightDarkToggle/LightDarkToggle.vue";
13
-
14
- // Optional vue-router integration (no hard dependency).
15
- // $router/$route on globalProperties is vue-router's stable public API.
16
- const instance = getCurrentInstance();
17
- const router = instance?.appContext.config.globalProperties.$router;
18
- const route = instance?.appContext.config.globalProperties.$route;
19
-
20
- export interface Tab {
21
- value: string;
22
- label: string;
23
- to?: string;
24
- }
25
-
26
- const props = defineProps<{
27
- hideTopbar?: boolean;
28
- tabs?: Tab[];
29
- }>();
30
-
31
- const tab = defineModel<string>("tab");
32
-
33
- const mql = window.matchMedia("(max-width: 767px)");
34
- const isMobile = ref(mql.matches);
35
- const collapsed = ref(mql.matches);
36
-
37
- function onMediaChange(e: MediaQueryListEvent) {
38
- isMobile.value = e.matches;
39
- collapsed.value = e.matches;
40
- }
41
-
42
- onMounted(() => {
43
- mql.addEventListener("change", onMediaChange);
44
- });
45
-
46
- onUnmounted(() => {
47
- mql.removeEventListener("change", onMediaChange);
48
- });
49
-
50
- function toggle() {
51
- collapsed.value = !collapsed.value;
52
- }
53
-
54
- const isRouterMode = computed(() => !!router && props.tabs?.some((t) => t.to));
55
-
56
- const activeTab = computed({
57
- get() {
58
- return tab.value ?? props.tabs?.[0]?.value;
59
- },
60
- set(value: string | undefined) {
61
- if (!value) return;
62
- tab.value = value;
63
- if (isRouterMode.value && router) {
64
- const target = props.tabs?.find((t) => t.value === value);
65
- if (target?.to) router.push(target.to);
66
- }
67
- },
68
- });
69
-
70
- // Sync active tab from route changes in router mode
71
- if (route) {
72
- watch(
73
- () => route.path,
74
- () => {
75
- if (isRouterMode.value) {
76
- const match = props.tabs?.find((t) => t.to === route.path);
77
- if (match) tab.value = match.value;
78
- }
79
- },
80
- { immediate: true },
81
- );
82
- }
83
- </script>
84
-
85
- <template>
86
- <div class="SidebarLayout" :data-collapsed="collapsed">
87
- <div class="SidebarRail">
88
- <aside class="Sidebar">
89
- <div class="SidebarScroll">
90
- <div class="SidebarHeader">
91
- <button
92
- type="button"
93
- class="Toggle"
94
- aria-label="Collapse sidebar"
95
- title="Collapse sidebar"
96
- @click="toggle"
97
- >
98
- <Icon icon="keyboard_double_arrow_left" size="sm" />
99
- </button>
100
- </div>
101
- <slot name="sidebar" />
102
- </div>
103
- </aside>
104
- <button
105
- type="button"
106
- class="Toggle Toggle--expand"
107
- aria-label="Expand sidebar"
108
- title="Expand sidebar"
109
- @click="toggle"
110
- >
111
- <Icon icon="keyboard_double_arrow_right" size="sm" />
112
- </button>
113
- </div>
114
- <main class="Main">
115
- <TabsRoot
116
- v-if="tabs?.length"
117
- :model-value="activeTab"
118
- class="TabsLayout"
119
- @update:model-value="activeTab = $event as string"
120
- >
121
- <div class="TabsBar">
122
- <button
123
- v-if="isMobile && collapsed"
124
- type="button"
125
- class="Toggle"
126
- aria-label="Expand sidebar"
127
- title="Expand sidebar"
128
- @click="toggle"
129
- >
130
- <Icon icon="keyboard_double_arrow_right" size="sm" />
131
- </button>
132
- <TabsList class="TabsList" aria-label="Tabs">
133
- <TabsTrigger
134
- v-for="t in tabs"
135
- :key="t.value"
136
- :value="t.value"
137
- class="TabsTrigger"
138
- >
139
- {{ t.label }}
140
- </TabsTrigger>
141
- <TabsIndicator
142
- class="TabsIndicator"
143
- :style="{
144
- width: 'var(--reka-tabs-indicator-size)',
145
- left: 'var(--reka-tabs-indicator-position)',
146
- }"
147
- />
148
- </TabsList>
149
- <div class="TabsBarEnd">
150
- <slot name="topbar" />
151
- <LightDarkToggle v-if="!hideTopbar" />
152
- </div>
153
- </div>
154
- <div class="MainScroll">
155
- <div class="MainContent">
156
- <slot />
157
- </div>
158
- </div>
159
- </TabsRoot>
160
- <template v-else>
161
- <div class="Topbar">
162
- <button
163
- v-if="isMobile && collapsed"
164
- type="button"
165
- class="Toggle"
166
- aria-label="Expand sidebar"
167
- title="Expand sidebar"
168
- @click="toggle"
169
- >
170
- <Icon icon="keyboard_double_arrow_right" size="sm" />
171
- </button>
172
- <div class="TopbarEnd">
173
- <slot name="topbar" />
174
- <LightDarkToggle v-if="!hideTopbar" />
175
- </div>
176
- </div>
177
- <div class="MainScroll">
178
- <div class="MainContent">
179
- <slot />
180
- </div>
181
- </div>
182
- </template>
183
- </main>
184
- </div>
185
- </template>
186
-
187
- <style scoped>
188
- .SidebarLayout {
189
- --bar-height: 3rem;
190
- display: flex;
191
- height: 100vh;
192
- height: 100dvh;
193
- background-color: var(--color-bg-0);
194
- color: var(--color-text);
195
- font-family: var(--font-family);
196
- position: relative;
197
- }
198
-
199
- .SidebarRail {
200
- flex-shrink: 0;
201
- width: var(--sidebar-width);
202
- height: 100%;
203
- overflow: hidden;
204
- transition: width var(--transition-normal);
205
- position: relative;
206
- }
207
-
208
- @media (min-width: 768px) {
209
- .SidebarLayout[data-collapsed="true"] .SidebarRail {
210
- width: var(--toggle-size);
211
- background-color: var(--color-bg-1);
212
- border-right: 1px solid var(--color-border);
213
- box-shadow: var(--shadow-sm);
214
- }
215
- }
216
-
217
- .Sidebar {
218
- font-size: var(--font-size-sm);
219
- display: flex;
220
- width: var(--sidebar-width);
221
- height: 100%;
222
- transform: translateX(0);
223
- transition: transform var(--transition-normal);
224
- }
225
-
226
- .SidebarLayout[data-collapsed="true"] .Sidebar {
227
- transform: translateX(-100%);
228
- }
229
-
230
- .SidebarScroll {
231
- flex: 1;
232
- min-width: 0;
233
- overflow-y: auto;
234
- padding: var(--space-4);
235
- background-color: var(--color-bg-1);
236
- border-right: 1px solid var(--color-border);
237
- box-shadow: var(--shadow-sm);
238
- scrollbar-width: thin;
239
- scrollbar-color: transparent transparent;
240
- display: flex;
241
- flex-direction: column;
242
- gap: var(--space-3);
243
- }
244
-
245
- .SidebarScroll:hover {
246
- scrollbar-color: var(--color-border) transparent;
247
- }
248
-
249
- .SidebarScroll :deep(h2) {
250
- font-size: var(--font-size-sm);
251
- font-weight: 600;
252
- text-transform: uppercase;
253
- letter-spacing: 0.05em;
254
- color: var(--color-text-secondary);
255
- margin: var(--space-4) 0 var(--space-2);
256
- }
257
-
258
- .SidebarScroll > :deep(h2:first-child) {
259
- margin-top: 0;
260
- }
261
-
262
- .Toggle {
263
- flex-shrink: 0;
264
- display: flex;
265
- align-items: center;
266
- justify-content: center;
267
- width: var(--toggle-size);
268
- height: var(--toggle-size);
269
- margin: 0;
270
- padding: 0;
271
- border: none;
272
- background: transparent;
273
- color: var(--color-text-secondary);
274
- cursor: pointer;
275
- transition:
276
- color var(--transition-fast),
277
- background-color var(--transition-fast);
278
- }
279
-
280
- .Toggle:hover {
281
- color: var(--color-text);
282
- background-color: var(--color-bg-2);
283
- }
284
-
285
- .Toggle:focus-visible {
286
- outline: none;
287
- box-shadow: var(--shadow-focus);
288
- }
289
-
290
- .SidebarHeader {
291
- display: flex;
292
- justify-content: flex-end;
293
- margin: calc(-1 * var(--space-4)) calc(-1 * var(--space-4))
294
- calc(-1 * var(--space-3));
295
- }
296
-
297
- .Toggle--expand {
298
- position: absolute;
299
- top: 0;
300
- left: 50%;
301
- transform: translateX(-50%);
302
- opacity: 0;
303
- pointer-events: none;
304
- transition: opacity var(--transition-fast);
305
- }
306
-
307
- .SidebarLayout[data-collapsed="true"] .Toggle--expand {
308
- opacity: 1;
309
- pointer-events: auto;
310
- }
311
-
312
- .Main {
313
- flex: 1;
314
- min-width: 0;
315
- display: flex;
316
- flex-direction: column;
317
- overflow: hidden;
318
- background-color: var(--color-bg-0);
319
- font-size: var(--font-size-md);
320
- }
321
-
322
- .Topbar {
323
- display: flex;
324
- align-items: center;
325
- min-height: var(--bar-height);
326
- padding: 0 var(--space-4);
327
- flex-shrink: 0;
328
- }
329
-
330
- .TopbarEnd {
331
- margin-left: auto;
332
- display: flex;
333
- align-items: center;
334
- gap: var(--space-1);
335
- }
336
-
337
- @media (min-width: 768px) {
338
- .Topbar {
339
- padding: 0 var(--space-4) 0 var(--space-20);
340
- }
341
- }
342
-
343
- .MainScroll {
344
- flex: 1;
345
- min-height: 0;
346
- overflow-y: auto;
347
- overflow-x: hidden;
348
- padding: var(--space-6) 0;
349
- scrollbar-width: thin;
350
- scrollbar-color: transparent transparent;
351
- }
352
-
353
- .MainScroll:hover {
354
- scrollbar-color: var(--color-border) transparent;
355
- }
356
-
357
- .MainContent {
358
- max-width: 1024px;
359
- padding: 0 var(--space-4);
360
- }
361
-
362
- @media (min-width: 768px) {
363
- .MainContent {
364
- padding: 0 var(--space-4) 0 var(--space-20);
365
- }
366
- }
367
-
368
- /* Mobile: use transform instead of width resize */
369
- @media (max-width: 767px) {
370
- .SidebarLayout {
371
- transition: transform var(--transition-normal);
372
- }
373
-
374
- .SidebarLayout[data-collapsed="true"] {
375
- transform: translateX(calc(-1 * var(--sidebar-width)));
376
- }
377
-
378
- .SidebarLayout[data-collapsed="true"] .Sidebar {
379
- transform: translateX(0);
380
- }
381
-
382
- .SidebarRail {
383
- min-width: var(--sidebar-width);
384
- }
385
-
386
- .Main {
387
- min-width: 100vw;
388
- }
389
-
390
- .Toggle--expand {
391
- display: none;
392
- }
393
- }
394
-
395
- /* Tabs */
396
- .TabsLayout {
397
- display: flex;
398
- flex-direction: column;
399
- flex: 1;
400
- min-height: 0;
401
- }
402
-
403
- .TabsBar {
404
- flex-shrink: 0;
405
- display: flex;
406
- align-items: center;
407
- min-height: var(--bar-height);
408
- border-bottom: 1px solid var(--color-border);
409
- padding: 0 var(--space-4);
410
- }
411
-
412
- .TabsBarEnd {
413
- margin-left: auto;
414
- display: flex;
415
- align-items: center;
416
- gap: var(--space-1);
417
- }
418
-
419
- .TabsList {
420
- display: flex;
421
- gap: var(--space-1);
422
- position: relative;
423
- align-self: stretch;
424
- }
425
-
426
- .TabsTrigger {
427
- position: relative;
428
- padding: var(--space-2) var(--space-3);
429
- font-size: var(--font-size-sm);
430
- font-weight: 500;
431
- font-family: inherit;
432
- color: var(--color-text-secondary);
433
- background: none;
434
- border: none;
435
- cursor: pointer;
436
- transition:
437
- color var(--transition-fast),
438
- background-color var(--transition-fast);
439
- border-radius: var(--radius-md) var(--radius-md) 0 0;
440
- }
441
-
442
- .TabsTrigger:hover {
443
- color: var(--color-text);
444
- background-color: var(--color-bg-1);
445
- }
446
-
447
- .TabsTrigger[data-state="active"] {
448
- color: var(--color-text);
449
- }
450
-
451
- .TabsTrigger:focus-visible {
452
- outline: none;
453
- box-shadow: var(--shadow-focus);
454
- }
455
-
456
- .TabsIndicator {
457
- position: absolute;
458
- bottom: 0;
459
- height: 2px;
460
- background-color: var(--color-text);
461
- transition:
462
- width var(--transition-fast),
463
- left var(--transition-fast);
464
- }
465
- </style>
@@ -1,45 +0,0 @@
1
- # Spinner
2
-
3
- A loading indicator with accessible labeling.
4
-
5
- ## Examples
6
-
7
- ### Sizes
8
-
9
- <ComponentDemo>
10
- <Spinner size="sm" label="Loading" />
11
- <Spinner size="md" label="Loading" />
12
- <Spinner size="lg" label="Loading" />
13
-
14
- <template #code>
15
-
16
- ```vue
17
- <Spinner size="sm" label="Loading" />
18
- <Spinner size="md" label="Loading" />
19
- <Spinner size="lg" label="Loading" />
20
- ```
21
-
22
- </template>
23
- </ComponentDemo>
24
-
25
- ### In context
26
-
27
- <ComponentDemo>
28
- <div style="display: flex; align-items: center; gap: 8px;">
29
- <Spinner size="sm" label="Running model" />
30
- <span>Running model...</span>
31
- </div>
32
-
33
- <template #code>
34
-
35
- ```vue
36
- <div style="display: flex; align-items: center; gap: 8px;">
37
- <Spinner size="sm" label="Running model" />
38
- <span>Running model...</span>
39
- </div>
40
- ```
41
-
42
- </template>
43
- </ComponentDemo>
44
-
45
- <!--@include: ./_api/spinner.md-->
@@ -1,9 +0,0 @@
1
- import { test, expect } from "@playwright/test";
2
-
3
- test("Spinner page renders demos", async ({ page }) => {
4
- await page.goto("./cfasim-ui/components/spinner");
5
- await expect(page.locator("h1")).toBeVisible();
6
- const demos = page.locator(".demo-preview");
7
- await expect(demos.first()).toBeVisible();
8
- await expect(demos.first().locator('[role="status"]')).toHaveCount(3);
9
- });
@@ -1,55 +0,0 @@
1
- <script setup lang="ts">
2
- export type SpinnerSize = "sm" | "md" | "lg";
3
-
4
- withDefaults(
5
- defineProps<{
6
- size?: SpinnerSize;
7
- label?: string;
8
- }>(),
9
- {
10
- size: "sm",
11
- },
12
- );
13
- </script>
14
-
15
- <template>
16
- <span
17
- class="Spinner"
18
- :data-size="size"
19
- role="status"
20
- :aria-label="label ?? 'Loading'"
21
- />
22
- </template>
23
-
24
- <style>
25
- .Spinner {
26
- display: inline-block;
27
- border: 2px solid currentColor;
28
- border-top-color: transparent;
29
- border-radius: 50%;
30
- animation: cfa-spin 0.6s linear infinite;
31
- vertical-align: middle;
32
- }
33
-
34
- .Spinner[data-size="sm"] {
35
- width: 14px;
36
- height: 14px;
37
- }
38
-
39
- .Spinner[data-size="md"] {
40
- width: 20px;
41
- height: 20px;
42
- }
43
-
44
- .Spinner[data-size="lg"] {
45
- width: 28px;
46
- height: 28px;
47
- border-width: 3px;
48
- }
49
-
50
- @keyframes cfa-spin {
51
- to {
52
- transform: rotate(360deg);
53
- }
54
- }
55
- </style>
@@ -1,41 +0,0 @@
1
- # TextInput
2
-
3
- A text input field with optional label and hint.
4
-
5
- ## Examples
6
-
7
- <script setup>
8
- import { ref } from 'vue'
9
- const name = ref('')
10
- </script>
11
-
12
- <ComponentDemo>
13
- <div style="width: 300px">
14
- <TextInput
15
- v-model="name"
16
- label="Model name"
17
- placeholder="e.g. my-model"
18
- hint="A short identifier used in URLs"
19
- />
20
- </div>
21
-
22
- <template #code>
23
-
24
- ```vue
25
- <script setup>
26
- import { ref } from "vue";
27
- const name = ref("");
28
- </script>
29
-
30
- <TextInput
31
- v-model="name"
32
- label="Model name"
33
- placeholder="e.g. my-model"
34
- hint="A short identifier used in URLs"
35
- />
36
- ```
37
-
38
- </template>
39
- </ComponentDemo>
40
-
41
- <!--@include: ./_api/text-input.md-->
@@ -1,10 +0,0 @@
1
- import { test, expect } from "@playwright/test";
2
-
3
- test("TextInput page renders demos", async ({ page }) => {
4
- await page.goto("./cfasim-ui/components/text-input");
5
- await expect(page.locator("h1")).toBeVisible();
6
- const demos = page.locator(".demo-preview");
7
- await expect(demos.first()).toBeVisible();
8
- await expect(demos.first().getByText("Model name")).toBeVisible();
9
- await expect(demos.first().locator("input")).toBeVisible();
10
- });
@@ -1,70 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { mount } from "@vue/test-utils";
3
- import TextInput from "./TextInput.vue";
4
-
5
- describe("TextInput", () => {
6
- it("renders with label", () => {
7
- const wrapper = mount(TextInput, {
8
- props: { modelValue: "hello", label: "Name" },
9
- });
10
- const label = wrapper.find("label.input-label");
11
- expect(label.exists()).toBe(true);
12
- expect(label.text()).toContain("Name");
13
- });
14
-
15
- it("renders without label", () => {
16
- const wrapper = mount(TextInput, {
17
- props: { modelValue: "hello" },
18
- });
19
- expect(wrapper.find("label").exists()).toBe(false);
20
- expect(wrapper.find("input").exists()).toBe(true);
21
- });
22
-
23
- it("does not emit update on typing, only on blur", async () => {
24
- const wrapper = mount(TextInput, {
25
- props: {
26
- modelValue: "initial",
27
- label: "Name",
28
- "onUpdate:modelValue": (v: string | undefined) =>
29
- wrapper.setProps({ modelValue: v }),
30
- },
31
- });
32
-
33
- const input = wrapper.find("input");
34
- await input.setValue("changed");
35
- expect(wrapper.props("modelValue")).toBe("initial");
36
-
37
- await input.trigger("blur");
38
- expect(wrapper.props("modelValue")).toBe("changed");
39
- });
40
-
41
- it("emits update on Enter keydown", async () => {
42
- const wrapper = mount(TextInput, {
43
- props: {
44
- modelValue: "initial",
45
- label: "Name",
46
- "onUpdate:modelValue": (v: string | undefined) =>
47
- wrapper.setProps({ modelValue: v }),
48
- },
49
- });
50
-
51
- const input = wrapper.find("input");
52
- await input.setValue("entered");
53
- expect(wrapper.props("modelValue")).toBe("initial");
54
-
55
- await input.trigger("keydown.enter");
56
- expect(wrapper.props("modelValue")).toBe("entered");
57
- });
58
-
59
- it("syncs local value when model changes externally", async () => {
60
- const wrapper = mount(TextInput, {
61
- props: { modelValue: "original", label: "Name" },
62
- });
63
-
64
- const input = wrapper.find("input");
65
- expect((input.element as HTMLInputElement).value).toBe("original");
66
-
67
- await wrapper.setProps({ modelValue: "reset" });
68
- expect((input.element as HTMLInputElement).value).toBe("reset");
69
- });
70
- });