@flux-ui/components 3.0.0-next.73 → 3.0.0-next.75

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 (56) hide show
  1. package/dist/component/FluxKanbanColumn.vue.d.ts +1 -1
  2. package/dist/component/FluxSegmentedControl.vue.d.ts +18 -8
  3. package/dist/component/FluxSegmentedControlItem.vue.d.ts +20 -0
  4. package/dist/component/FluxSpacing.vue.d.ts +2 -1
  5. package/dist/component/index.d.ts +1 -4
  6. package/dist/component/primitive/FilterMenuRenderer.d.ts +3 -3
  7. package/dist/composable/index.d.ts +1 -0
  8. package/dist/composable/useSegmentedControlInjection.d.ts +2 -0
  9. package/dist/data/di.d.ts +10 -1
  10. package/dist/index.css +57 -158
  11. package/dist/index.js +321 -405
  12. package/dist/index.js.map +1 -1
  13. package/package.json +9 -9
  14. package/src/component/FluxBoxedIcon.vue +1 -1
  15. package/src/component/FluxCalendar.vue +6 -6
  16. package/src/component/FluxColorPicker.vue +5 -5
  17. package/src/component/FluxDropZone.vue +0 -1
  18. package/src/component/FluxDynamicView.vue +2 -2
  19. package/src/component/FluxExpandableGroup.vue +0 -1
  20. package/src/component/FluxFilterBase.vue +11 -11
  21. package/src/component/FluxFormFieldAddition.vue +2 -2
  22. package/src/component/FluxInfo.vue +2 -2
  23. package/src/component/FluxInfoStack.vue +2 -2
  24. package/src/component/FluxKanbanColumn.vue +2 -4
  25. package/src/component/FluxLayerPane.vue +1 -1
  26. package/src/component/FluxPagination.vue +4 -4
  27. package/src/component/FluxSegmentedControl.vue +64 -67
  28. package/src/component/FluxSegmentedControlItem.vue +98 -0
  29. package/src/component/FluxSpacing.vue +5 -1
  30. package/src/component/FluxSplitButton.vue +2 -2
  31. package/src/component/FluxTable.vue +3 -1
  32. package/src/component/FluxToolbar.vue +1 -1
  33. package/src/component/index.ts +1 -4
  34. package/src/component/primitive/FilterBadge.vue +11 -2
  35. package/src/component/primitive/FilterMenuRenderer.ts +4 -4
  36. package/src/composable/index.ts +1 -0
  37. package/src/composable/useSegmentedControlInjection.ts +13 -0
  38. package/src/css/component/Form.module.scss +2 -2
  39. package/src/css/component/SegmentedControl.module.scss +51 -23
  40. package/src/css/component/Spinner.module.scss +1 -0
  41. package/src/css/component/Visual.module.scss +1 -0
  42. package/src/css/mixin/tree-node.scss +3 -3
  43. package/src/data/di.ts +13 -1
  44. package/src/data/iconRegistry.ts +1 -1
  45. package/src/util/createDialogRenderer.ts +1 -1
  46. package/dist/component/FluxLegend.vue.d.ts +0 -8
  47. package/dist/component/FluxPercentageBar.vue.d.ts +0 -8
  48. package/dist/component/FluxSegmentedView.vue.d.ts +0 -9
  49. package/dist/component/FluxStatistic.vue.d.ts +0 -17
  50. package/src/component/FluxLegend.vue +0 -27
  51. package/src/component/FluxPercentageBar.vue +0 -47
  52. package/src/component/FluxSegmentedView.vue +0 -15
  53. package/src/component/FluxStatistic.vue +0 -82
  54. package/src/css/component/Legend.module.scss +0 -29
  55. package/src/css/component/PercentageBar.module.scss +0 -31
  56. package/src/css/component/Statistic.module.scss +0 -91
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flux-ui/components",
3
3
  "description": "A set of opiniated UI components.",
4
- "version": "3.0.0-next.73",
4
+ "version": "3.0.0-next.75",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/basmilius",
@@ -55,10 +55,10 @@
55
55
  "**/dist/index.css"
56
56
  ],
57
57
  "dependencies": {
58
- "@basmilius/common": "^3.37.0",
59
- "@basmilius/utils": "^3.37.0",
60
- "@flux-ui/internals": "3.0.0-next.73",
61
- "@flux-ui/types": "3.0.0-next.73",
58
+ "@basmilius/common": "^3.40.0",
59
+ "@basmilius/utils": "^3.40.0",
60
+ "@flux-ui/internals": "3.0.0-next.75",
61
+ "@flux-ui/types": "3.0.0-next.75",
62
62
  "@fortawesome/fontawesome-common-types": "^7.2.0",
63
63
  "clsx": "^2.1.1",
64
64
  "imask": "^7.6.1",
@@ -66,10 +66,10 @@
66
66
  },
67
67
  "peerDependencies": {
68
68
  "luxon": "^3.7.2",
69
- "vue": "^3.6.0-beta.12"
69
+ "vue": "^3.6.0-beta.13"
70
70
  },
71
71
  "devDependencies": {
72
- "@basmilius/vite-preset": "^3.37.0",
72
+ "@basmilius/vite-preset": "^3.40.0",
73
73
  "@types/lodash-es": "^4.17.12",
74
74
  "@types/luxon": "^3.7.1",
75
75
  "@types/node": "^25.9.1",
@@ -77,7 +77,7 @@
77
77
  "@vue/tsconfig": "^0.9.1",
78
78
  "sass-embedded": "^1.100.0",
79
79
  "typescript": "^6.0.3",
80
- "vite": "^8.0.14",
81
- "vue-tsc": "^3.3.2"
80
+ "vite": "^8.0.16",
81
+ "vue-tsc": "^3.3.3"
82
82
  }
83
83
  }
@@ -7,7 +7,7 @@
7
7
  color === 'danger' && $style.iconBoxedDanger,
8
8
  color === 'info' && $style.iconBoxedInfo,
9
9
  color === 'success' && $style.iconBoxedSuccess,
10
- color === 'warning' && $style.iconBoxedWarning,
10
+ color === 'warning' && $style.iconBoxedWarning
11
11
  )"
12
12
  :style="{
13
13
  fontSize: size && `${size}px`
@@ -265,13 +265,13 @@
265
265
  });
266
266
 
267
267
  const timeGridDayCount = computed<1 | 2 | 7>(() => {
268
- const v = unref(resolvedView);
268
+ const resolved = unref(resolvedView);
269
269
 
270
- if (v === 'week') {
270
+ if (resolved === 'week') {
271
271
  return 7;
272
272
  }
273
273
 
274
- if (v === 'two-days') {
274
+ if (resolved === 'two-days') {
275
275
  return 2;
276
276
  }
277
277
 
@@ -521,13 +521,13 @@
521
521
  return;
522
522
  }
523
523
 
524
- const v = unref(resolvedView);
525
- const delta = (v === 'month' ? MONTH_KEY_DELTAS : TIME_GRID_KEY_DELTAS)[direction];
524
+ const resolved = unref(resolvedView);
525
+ const delta = (resolved === 'month' ? MONTH_KEY_DELTAS : TIME_GRID_KEY_DELTAS)[direction];
526
526
  const newDate = currentDate.plus(delta);
527
527
 
528
528
  emit('reschedule', {id, fromDate: currentDate, toDate: newDate});
529
529
 
530
- if (v === 'month') {
530
+ if (resolved === 'month') {
531
531
  monthFocusedDate.value = newDate.startOf('day');
532
532
 
533
533
  if (newDate.month !== unref(monthViewDate).month) {
@@ -36,8 +36,8 @@
36
36
  <div
37
37
  :class="$style.colorPickerPreview"
38
38
  :style="{
39
- '--color': rgb
40
- }"
39
+ '--color': rgb
40
+ }"
41
41
  aria-hidden="true"/>
42
42
 
43
43
  <FluxFormField
@@ -180,9 +180,9 @@
180
180
 
181
181
  const saturationValue = computed({
182
182
  get: (): [number, number] => [unref(saturation), unref(value)],
183
- set: ([s, v]: [number, number]) => {
184
- saturation.value = s;
185
- value.value = v;
183
+ set: ([nextSaturation, nextValue]: [number, number]) => {
184
+ saturation.value = nextSaturation;
185
+ value.value = nextValue;
186
186
  }
187
187
  });
188
188
 
@@ -213,4 +213,3 @@
213
213
  pathLength.value = roundStep(width * 2 + height * 2, 6);
214
214
  }, {immediate: true});
215
215
  </script>
216
-
@@ -5,8 +5,8 @@
5
5
  </template>
6
6
 
7
7
  <script
8
- setup
9
- lang="ts">
8
+ lang="ts"
9
+ setup>
10
10
  import type { VNode } from 'vue';
11
11
  import { VNodeRenderer } from './primitive';
12
12
 
@@ -18,7 +18,6 @@
18
18
  readonly isControlled?: boolean;
19
19
  }>();
20
20
 
21
-
22
21
  defineSlots<{
23
22
  default(): VNode[];
24
23
  }>();
@@ -41,7 +41,7 @@
41
41
  const flattenedFilters = computed(() => flattenVNodeTree(slots.filters?.() ?? []));
42
42
 
43
43
  const buttons = computed(() => {
44
- const buttons: Record<string, FluxFilterDefinition> = {};
44
+ const result: Record<string, FluxFilterDefinition> = {};
45
45
  const items = unref(flattenedFilters);
46
46
 
47
47
  for (const item of items) {
@@ -51,14 +51,14 @@
51
51
  continue;
52
52
  }
53
53
 
54
- buttons[definition.name] = definition;
54
+ result[definition.name] = definition;
55
55
  }
56
56
 
57
- return buttons;
57
+ return result;
58
58
  });
59
59
 
60
60
  const filters = computed<Record<string, VNode>>(() => {
61
- const filters: { [key: string]: VNode; } = {};
61
+ const result: { [key: string]: VNode; } = {};
62
62
  const items = unref(flattenedFilters);
63
63
 
64
64
  for (const item of items) {
@@ -68,33 +68,33 @@
68
68
  continue;
69
69
  }
70
70
 
71
- filters[definition.name] = item;
71
+ result[definition.name] = item;
72
72
  }
73
73
 
74
- return filters;
74
+ return result;
75
75
  });
76
76
 
77
77
  const menuItems = computed<(FluxFilterDefinition | VNode)[][]>(() => {
78
- const menuItems: (FluxFilterDefinition | VNode)[][] = [[]];
78
+ const result: (FluxFilterDefinition | VNode)[][] = [[]];
79
79
  const items = unref(flattenedFilters);
80
80
 
81
81
  for (const item of items) {
82
82
  if (getComponentName(item) === 'FluxSeparator') {
83
- menuItems.push([]);
83
+ result.push([]);
84
84
  continue;
85
85
  }
86
86
 
87
87
  const definition = resolveDefinition(item);
88
88
 
89
89
  if (definition) {
90
- menuItems[menuItems.length - 1].push(definition);
90
+ result[result.length - 1].push(definition);
91
91
  continue;
92
92
  }
93
93
 
94
- menuItems[menuItems.length - 1].push(item);
94
+ result[result.length - 1].push(item);
95
95
  }
96
96
 
97
- return menuItems;
97
+ return result;
98
98
  });
99
99
 
100
100
  watchEffect(() => {
@@ -20,8 +20,8 @@
20
20
  </template>
21
21
 
22
22
  <script
23
- setup
24
- lang="ts">
23
+ lang="ts"
24
+ setup>
25
25
  import type { FluxIconName } from '@flux-ui/types';
26
26
  import { clsx } from 'clsx';
27
27
  import FluxIcon from './FluxIcon.vue';
@@ -12,8 +12,8 @@
12
12
  </template>
13
13
 
14
14
  <script
15
- setup
16
- lang="ts">
15
+ lang="ts"
16
+ setup>
17
17
  import type { FluxIconName } from '@flux-ui/types';
18
18
  import type { VNode } from 'vue';
19
19
  import FluxIcon from './FluxIcon.vue';
@@ -7,8 +7,8 @@
7
7
  </template>
8
8
 
9
9
  <script
10
- setup
11
- lang="ts">
10
+ lang="ts"
11
+ setup>
12
12
  import type { VNode } from 'vue';
13
13
  import FluxFlex from './FluxFlex.vue';
14
14
 
@@ -65,9 +65,7 @@
65
65
  <footer
66
66
  v-if="hasFooter"
67
67
  :class="$style.kanbanColumnFooter">
68
- <slot
69
- v-if="hasFooter"
70
- name="footer"/>
68
+ <slot name="footer"/>
71
69
  </footer>
72
70
  </div>
73
71
  </template>
@@ -90,7 +88,7 @@
90
88
  label
91
89
  } = defineProps<{
92
90
  readonly columnId: string | number;
93
- readonly count: string | number;
91
+ readonly count?: string | number;
94
92
  readonly disabled?: boolean;
95
93
  readonly icon?: FluxIconName;
96
94
  readonly label: string;
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div
3
3
  :class="clsx(
4
- color === 'gray' && $style.layerPaneGray,
4
+ color === 'gray' && $style.layerPaneGray,
5
5
  color === 'primary' && $style.layerPanePrimary,
6
6
  color === 'danger' && $style.layerPaneDanger,
7
7
  color === 'info' && $style.layerPaneInfo,
@@ -35,7 +35,7 @@
35
35
 
36
36
  <template v-else>
37
37
  <FluxPaginationButton
38
- :class="$style.paginationCurrentZZ"
38
+ is-current
39
39
  @click="prompt"
40
40
  #before>
41
41
  <strong>{{ page }}</strong>
@@ -140,12 +140,12 @@
140
140
  fieldLabel: translate('flux.paginationNavigatePage')
141
141
  });
142
142
 
143
- const page = Number(pageStr);
143
+ const target = Number(pageStr);
144
144
 
145
- if (isNaN(page) || page > unref(pages) || page <= 0) {
145
+ if (isNaN(target) || target > unref(pages) || target <= 0) {
146
146
  return;
147
147
  }
148
148
 
149
- navigate(page);
149
+ navigate(target);
150
150
  }
151
151
  </script>
@@ -13,98 +13,90 @@
13
13
  width: `${activeItemWidth}px`
14
14
  }"/>
15
15
 
16
- <template v-for="(item, index) of items">
17
- <div
18
- v-if="index > 0"
19
- :class="clsx(
20
- $style.segmentedControlSeparator,
21
- (index === modelValue || index === modelValue + 1) && $style.isActive
22
- )"
23
- role="separator"/>
24
-
25
- <button
26
- ref="items"
27
- :class="clsx(
28
- $style.segmentedControlItem,
29
- index === modelValue && $style.isActive
30
- )"
31
- role="radio"
32
- :aria-checked="index === modelValue"
33
- :aria-label="item.label"
34
- :tabindex="index === modelValue ? 0 : -1"
35
- type="button"
36
- @click="activate(index)">
37
- <FluxIcon
38
- v-if="item.icon"
39
- :name="item.icon"
40
- :size="15"/>
41
-
42
- <span v-if="item.label">{{ item.label }}</span>
43
- </button>
44
- </template>
16
+ <slot/>
45
17
  </div>
46
18
  </template>
47
19
 
48
20
  <script
49
21
  lang="ts"
50
22
  setup>
51
- import { useResizeObserver } from '@basmilius/common';
52
- import type { FluxSegmentedControlItemObject } from '@flux-ui/types';
53
- import { clsx } from 'clsx';
54
- import { onBeforeUnmount, ref, unref, useTemplateRef, watchEffect } from 'vue';
55
- import FluxIcon from './FluxIcon.vue';
23
+ import { useMutationObserver, useResizeObserver } from '@basmilius/common';
24
+ import type { FluxSize } from '@flux-ui/types';
25
+ import { onMounted, provide, ref, toRef, useTemplateRef, watch, type VNode } from 'vue';
26
+ import { FluxSegmentedControlInjectionKey, type FluxSegmentedControlValue } from '~flux/components/data';
56
27
  import $style from '~flux/components/css/component/SegmentedControl.module.scss';
57
28
 
58
- const modelValue = defineModel<number>({
59
- default: 0
60
- });
29
+ const modelValue = defineModel<FluxSegmentedControlValue>();
61
30
 
62
- defineProps<{
31
+ const { size = 'medium' } = defineProps<{
63
32
  readonly ariaLabel?: string;
64
33
  readonly isFill?: boolean;
65
- readonly items: FluxSegmentedControlItemObject[];
34
+ readonly size?: FluxSize;
35
+ }>();
36
+
37
+ defineSlots<{
38
+ default(): VNode[];
66
39
  }>();
67
40
 
68
41
  const controlRef = useTemplateRef<HTMLElement>('control');
69
- const itemRefs = useTemplateRef<HTMLButtonElement[]>('items');
70
42
 
71
43
  const activeItemX = ref(0);
72
44
  const activeItemWidth = ref(0);
73
- const isAlive = ref(true);
74
45
 
75
- onBeforeUnmount(() => {
76
- isAlive.value = false;
46
+ const items = new Map<HTMLElement, FluxSegmentedControlValue>();
47
+
48
+ provide(FluxSegmentedControlInjectionKey, {
49
+ modelValue,
50
+ size: toRef(() => size),
51
+ select,
52
+ registerItem(element, value) {
53
+ items.set(element, value);
54
+ updateHighlight();
55
+ },
56
+ unregisterItem(element) {
57
+ items.delete(element);
58
+ updateHighlight();
59
+ }
77
60
  });
78
61
 
79
- watchEffect(() => updateHighlight(unref(modelValue)), {flush: 'post'});
62
+ onMounted(() => updateHighlight());
80
63
 
81
- useResizeObserver(controlRef, () => updateHighlight(unref(modelValue)));
64
+ watch(modelValue, () => updateHighlight(), {flush: 'post'});
82
65
 
83
- function activate(index: number): void {
84
- modelValue.value = index;
66
+ useMutationObserver(controlRef, () => updateHighlight(), {childList: true, subtree: true});
67
+ useResizeObserver(controlRef, () => updateHighlight());
85
68
 
86
- const itemRef = itemRefs.value?.[index];
87
- itemRef?.focus();
69
+ function select(value: FluxSegmentedControlValue): void {
70
+ modelValue.value = value;
88
71
  }
89
72
 
90
73
  function onKeyDown(evt: KeyboardEvent): void {
91
- const items = itemRefs.value;
74
+ const control = controlRef.value;
75
+
76
+ if (!control) {
77
+ return;
78
+ }
79
+
80
+ const radios = Array.from(control.querySelectorAll<HTMLButtonElement>('[role=radio]:not([disabled])'));
92
81
 
93
- if (!items) {
82
+ if (radios.length === 0) {
94
83
  return;
95
84
  }
96
85
 
97
- let newIndex: number | null = null;
86
+ const activeElement = control.querySelector<HTMLButtonElement>('[role=radio][aria-checked=true]');
87
+ const currentIndex = activeElement ? radios.indexOf(activeElement) : -1;
88
+
89
+ let newIndex: number;
98
90
 
99
91
  switch (evt.key) {
100
92
  case 'ArrowLeft':
101
93
  case 'ArrowUp':
102
- newIndex = Math.max(0, unref(modelValue) - 1);
94
+ newIndex = Math.max(0, currentIndex - 1);
103
95
  break;
104
96
 
105
97
  case 'ArrowRight':
106
98
  case 'ArrowDown':
107
- newIndex = Math.min(items.length - 1, unref(modelValue) + 1);
99
+ newIndex = Math.min(radios.length - 1, currentIndex + 1);
108
100
  break;
109
101
 
110
102
  case 'Home':
@@ -112,40 +104,45 @@
112
104
  break;
113
105
 
114
106
  case 'End':
115
- newIndex = items.length - 1;
107
+ newIndex = radios.length - 1;
116
108
  break;
117
109
 
118
110
  default:
119
111
  return;
120
112
  }
121
113
 
122
- activate(newIndex);
114
+ const target = radios[newIndex];
115
+ const value = items.get(target);
116
+
117
+ if (value !== undefined) {
118
+ select(value);
119
+ }
120
+
121
+ target.focus();
123
122
  evt.preventDefault();
124
123
  }
125
124
 
126
- function updateHighlight(index: number): void {
127
- if (!isAlive.value) {
125
+ function updateHighlight(): void {
126
+ const control = controlRef.value;
127
+
128
+ if (!control) {
128
129
  return;
129
130
  }
130
131
 
131
- const itemRef = itemRefs.value?.[index];
132
- const control = controlRef.value;
132
+ const activeElement = control.querySelector<HTMLElement>('[role=radio][aria-checked=true]');
133
133
 
134
- if (!itemRef || !control) {
134
+ if (!activeElement) {
135
+ activeItemWidth.value = 0;
135
136
  return;
136
137
  }
137
138
 
138
- const width = itemRef.offsetWidth;
139
+ const width = activeElement.offsetWidth;
139
140
 
140
141
  if (width === 0) {
141
142
  return;
142
143
  }
143
144
 
144
- const controlRect = control.getBoundingClientRect();
145
- const itemRect = itemRef.getBoundingClientRect();
146
- const scaleX = control.offsetWidth > 0 ? controlRect.width / control.offsetWidth : 1;
147
-
148
- activeItemX.value = (itemRect.left - controlRect.left) / scaleX;
145
+ activeItemX.value = activeElement.offsetLeft;
149
146
  activeItemWidth.value = width;
150
147
  }
151
148
  </script>
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <button
3
+ ref="item"
4
+ :class="itemClass"
5
+ role="radio"
6
+ :aria-checked="isActive"
7
+ :aria-disabled="disabled ? true : undefined"
8
+ :disabled="disabled ? true : undefined"
9
+ :tabindex="isActive ? 0 : -1"
10
+ type="button"
11
+ @click="onClick">
12
+ <slot>
13
+ <FluxIcon
14
+ v-if="icon"
15
+ :name="icon"
16
+ :size="iconSize"/>
17
+
18
+ <span v-if="label">{{ label }}</span>
19
+ </slot>
20
+ </button>
21
+ </template>
22
+
23
+ <script
24
+ lang="ts"
25
+ setup>
26
+ import type { FluxIconName } from '@flux-ui/types';
27
+ import { clsx } from 'clsx';
28
+ import { computed, onBeforeUnmount, onMounted, toRef, unref, useTemplateRef } from 'vue';
29
+ import { useDisabled, useSegmentedControlInjection } from '~flux/components/composable';
30
+ import type { FluxSegmentedControlValue } from '~flux/components/data';
31
+ import FluxIcon from './FluxIcon.vue';
32
+ import $style from '~flux/components/css/component/SegmentedControl.module.scss';
33
+
34
+ const { value, disabled: componentDisabled } = defineProps<{
35
+ readonly value: FluxSegmentedControlValue;
36
+ readonly disabled?: boolean;
37
+ readonly icon?: FluxIconName;
38
+ readonly label?: string;
39
+ }>();
40
+
41
+ defineSlots<{
42
+ default(): any;
43
+ }>();
44
+
45
+ const control = useSegmentedControlInjection();
46
+ const disabled = useDisabled(toRef(() => componentDisabled));
47
+ const itemRef = useTemplateRef<HTMLButtonElement>('item');
48
+
49
+ const sizeClasses = {
50
+ small: $style.isSmall,
51
+ medium: $style.isMedium,
52
+ large: $style.isLarge
53
+ };
54
+ const iconSizes = {
55
+ small: 14,
56
+ medium: 16,
57
+ large: 18
58
+ };
59
+
60
+ const isActive = computed(() => control.modelValue.value === value);
61
+ const iconSize = computed(() => iconSizes[unref(control.size)]);
62
+ const itemClass = computed(() => clsx(
63
+ $style.segmentedControlItem,
64
+ sizeClasses[unref(control.size)],
65
+ isActive.value && $style.isActive
66
+ ));
67
+
68
+ onMounted(() => {
69
+ const element = unref(itemRef);
70
+
71
+ if (!element) {
72
+ return;
73
+ }
74
+
75
+ control.registerItem(element, value);
76
+
77
+ if (control.modelValue.value === undefined && !unref(disabled)) {
78
+ control.select(value);
79
+ }
80
+ });
81
+
82
+ onBeforeUnmount(() => {
83
+ const element = unref(itemRef);
84
+
85
+ if (element) {
86
+ control.unregisterItem(element);
87
+ }
88
+ });
89
+
90
+ function onClick(evt: MouseEvent): void {
91
+ if (unref(disabled)) {
92
+ evt.preventDefault();
93
+ return;
94
+ }
95
+
96
+ control.select(value);
97
+ }
98
+ </script>
@@ -26,7 +26,11 @@
26
26
  120
27
27
  ] as const;
28
28
 
29
+ type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
30
+ ? Acc[number]
31
+ : Enumerate<N, [...Acc, Acc['length']]>;
32
+
29
33
  defineProps<{
30
- readonly size: keyof typeof spacings;
34
+ readonly size: Enumerate<typeof spacings['length']>;
31
35
  }>();
32
36
  </script>
@@ -25,8 +25,8 @@
25
25
  </template>
26
26
 
27
27
  <script
28
- setup
29
- lang="ts">
28
+ lang="ts"
29
+ setup>
30
30
  import type { FluxDirection, FluxIconName } from '@flux-ui/types';
31
31
  import type { VNode } from 'vue';
32
32
  import FluxFlyout from './FluxFlyout.vue';
@@ -18,7 +18,9 @@
18
18
  <FluxTableRow
19
19
  v-if="fillColumns"
20
20
  :class="$style.tableFill">
21
- <FluxTableCell v-for="_ of fillColumns"/>
21
+ <FluxTableCell
22
+ v-for="n of fillColumns"
23
+ :key="n"/>
22
24
  </FluxTableRow>
23
25
  </tbody>
24
26
 
@@ -6,7 +6,7 @@
6
6
  floatingMode === 'top-end' && $style.isTopEnd,
7
7
  floatingMode === 'top-start' && $style.isTopStart,
8
8
  floatingMode === 'bottom-end' && $style.isBottomEnd,
9
- floatingMode === 'bottom-start' && $style.isBottomStart,
9
+ floatingMode === 'bottom-start' && $style.isBottomStart
10
10
  )"
11
11
  :gap="6"
12
12
  tag="nav">