@flux-ui/components 3.0.0-next.61 → 3.0.0-next.63

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 (123) hide show
  1. package/dist/component/FluxActionStack.vue.d.ts +25 -19
  2. package/dist/component/FluxAspectRatio.vue.d.ts +4 -3
  3. package/dist/component/FluxBorderShine.vue.d.ts +1 -1
  4. package/dist/component/FluxCalendar.vue.d.ts +2 -6
  5. package/dist/component/FluxContainer.vue.d.ts +3 -2
  6. package/dist/component/FluxFilter.vue.d.ts +6 -7
  7. package/dist/component/FluxFilterBar.vue.d.ts +5 -4
  8. package/dist/component/FluxFilterBase.vue.d.ts +14 -11
  9. package/dist/component/FluxFilterDate.vue.d.ts +3 -6
  10. package/dist/component/FluxFilterDateRange.vue.d.ts +3 -6
  11. package/dist/component/FluxFilterOption.vue.d.ts +3 -6
  12. package/dist/component/FluxFilterOptionAsync.vue.d.ts +3 -6
  13. package/dist/component/FluxFilterOptions.vue.d.ts +3 -6
  14. package/dist/component/FluxFilterOptionsAsync.vue.d.ts +3 -6
  15. package/dist/component/FluxFilterRange.vue.d.ts +3 -7
  16. package/dist/component/FluxFilterWindow.vue.d.ts +3 -8
  17. package/dist/component/FluxFlex.vue.d.ts +30 -0
  18. package/dist/component/{FluxRow.vue.d.ts → FluxFlexItem.vue.d.ts} +5 -3
  19. package/dist/component/FluxGrid.vue.d.ts +3 -2
  20. package/dist/component/FluxGridColumn.vue.d.ts +3 -2
  21. package/dist/component/FluxKanbanColumn.vue.d.ts +3 -0
  22. package/dist/component/FluxScroller.vue.d.ts +32 -0
  23. package/dist/component/{FluxStack.vue.d.ts → FluxSplitView.vue.d.ts} +7 -6
  24. package/dist/component/{FluxColumn.vue.d.ts → FluxSplitViewPane.vue.d.ts} +4 -1
  25. package/dist/component/FluxSticky.vue.d.ts +34 -0
  26. package/dist/component/index.d.ts +6 -3
  27. package/dist/component/primitive/FilterBadge.vue.d.ts +2 -2
  28. package/dist/component/primitive/FilterItem.vue.d.ts +3 -2
  29. package/dist/component/primitive/SelectBase.vue.d.ts +4 -4
  30. package/dist/composable/private/index.d.ts +1 -0
  31. package/dist/composable/private/useSplitView.d.ts +23 -0
  32. package/dist/data/di.d.ts +19 -2
  33. package/dist/data/index.d.ts +0 -1
  34. package/dist/index.css +583 -331
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +8755 -8109
  37. package/dist/index.js.map +1 -1
  38. package/dist/util/defineFilter.d.ts +3 -0
  39. package/dist/util/filter.d.ts +7 -0
  40. package/dist/util/index.d.ts +2 -0
  41. package/dist/vite/defineFilterMacro.d.ts +3 -0
  42. package/dist/vite/index.d.ts +1 -0
  43. package/dist/vite.js +217 -0
  44. package/dist/vite.js.map +1 -0
  45. package/package.json +11 -7
  46. package/src/component/FluxActionBar.vue +3 -4
  47. package/src/component/FluxActionStack.vue +3 -3
  48. package/src/component/FluxAspectRatio.vue +5 -3
  49. package/src/component/FluxBadgeStack.vue +4 -4
  50. package/src/component/FluxButtonStack.vue +6 -4
  51. package/src/component/FluxCalendar.vue +160 -157
  52. package/src/component/FluxContainer.vue +4 -2
  53. package/src/component/FluxFilter.vue +10 -11
  54. package/src/component/FluxFilterBar.vue +71 -15
  55. package/src/component/FluxFilterBase.vue +65 -51
  56. package/src/component/FluxFilterDate.vue +24 -8
  57. package/src/component/FluxFilterDateRange.vue +27 -9
  58. package/src/component/FluxFilterOption.vue +20 -10
  59. package/src/component/FluxFilterOptionAsync.vue +19 -11
  60. package/src/component/FluxFilterOptions.vue +26 -11
  61. package/src/component/FluxFilterOptionsAsync.vue +28 -12
  62. package/src/component/FluxFilterRange.vue +25 -11
  63. package/src/component/FluxFilterWindow.vue +25 -11
  64. package/src/component/FluxFlex.vue +53 -0
  65. package/src/component/FluxFlexItem.vue +40 -0
  66. package/src/component/FluxFormDateTimeInput.vue +3 -4
  67. package/src/component/FluxGrid.vue +4 -2
  68. package/src/component/FluxGridColumn.vue +4 -2
  69. package/src/component/FluxInfoStack.vue +3 -3
  70. package/src/component/FluxItemStack.vue +4 -4
  71. package/src/component/FluxKanbanColumn.vue +16 -3
  72. package/src/component/FluxNoticeStack.vue +3 -3
  73. package/src/component/FluxProgressBar.vue +4 -3
  74. package/src/component/FluxScroller.vue +63 -0
  75. package/src/component/FluxSplitView.vue +101 -0
  76. package/src/component/FluxSplitViewPane.vue +23 -0
  77. package/src/component/FluxSticky.vue +67 -0
  78. package/src/component/FluxTagStack.vue +4 -4
  79. package/src/component/FluxToolbar.vue +3 -4
  80. package/src/component/FluxToolbarGroup.vue +3 -4
  81. package/src/component/FluxTooltipProvider.vue +56 -25
  82. package/src/component/index.ts +6 -3
  83. package/src/component/primitive/FilterBadge.vue +2 -2
  84. package/src/component/primitive/FilterItem.vue +4 -2
  85. package/src/component/primitive/FilterMenuRenderer.ts +10 -5
  86. package/src/component/primitive/FilterOptionBase.vue +1 -1
  87. package/src/composable/private/index.ts +1 -0
  88. package/src/composable/private/useAsyncFilterOptions.ts +1 -1
  89. package/src/composable/private/useFilterOption.ts +1 -1
  90. package/src/composable/private/useSplitView.ts +249 -0
  91. package/src/composable/useFilterInjection.ts +3 -1
  92. package/src/css/component/Calendar.module.scss +11 -17
  93. package/src/css/component/Comment.module.scss +3 -11
  94. package/src/css/component/Filter.module.scss +6 -2
  95. package/src/css/component/Flex.module.scss +84 -0
  96. package/src/css/component/Flyout.module.scss +1 -0
  97. package/src/css/component/Kanban.module.scss +31 -28
  98. package/src/css/component/LayerPane.module.scss +5 -0
  99. package/src/css/component/Layout.module.scss +0 -41
  100. package/src/css/component/Legend.module.scss +3 -4
  101. package/src/css/component/Menu.module.scss +1 -0
  102. package/src/css/component/Pagination.module.scss +1 -1
  103. package/src/css/component/Pane.module.scss +1 -1
  104. package/src/css/component/Progress.module.scss +2 -2
  105. package/src/css/component/Scroller.module.scss +109 -0
  106. package/src/css/component/SplitView.module.scss +78 -0
  107. package/src/css/component/Sticky.module.scss +35 -0
  108. package/src/css/component/Tab.module.scss +1 -0
  109. package/src/css/component/Table.module.scss +1 -0
  110. package/src/css/component/Tooltip.module.scss +14 -0
  111. package/src/data/di.ts +22 -2
  112. package/src/data/index.ts +0 -1
  113. package/src/index.ts +11 -0
  114. package/src/util/defineFilter.ts +10 -0
  115. package/src/util/filter.ts +63 -0
  116. package/src/util/index.ts +2 -0
  117. package/src/vite/defineFilterMacro.ts +335 -0
  118. package/src/vite/index.ts +1 -0
  119. package/dist/data/filter.d.ts +0 -7
  120. package/src/component/FluxColumn.vue +0 -24
  121. package/src/component/FluxRow.vue +0 -24
  122. package/src/component/FluxStack.vue +0 -41
  123. package/src/data/filter.ts +0 -165
@@ -1,109 +1,93 @@
1
1
  <template>
2
- <slot v-bind="{buttons, filters, menuItems}"/>
2
+ <slot v-bind="{buttons, filters, menuItems, clear, reset}"/>
3
3
  </template>
4
4
 
5
5
  <script
6
6
  lang="ts"
7
7
  setup>
8
8
  import { flattenVNodeTree, getComponentName, getComponentProps } from '@flux-ui/internals';
9
- import type { FluxFilterItem, FluxFilterOptionItem, FluxFilterSpecMap, FluxFilterState } from '@flux-ui/types';
10
- import { camelCase } from 'lodash-es';
11
- import { computed, provide, unref, type VNode } from 'vue';
12
- import { filterParsers, FluxFilterInjectionKey } from '~flux/components/data';
13
-
14
- type FluxFilterType = keyof FluxFilterSpecMap;
15
-
16
- function applyParser<K extends FluxFilterType>(type: K, spec: FluxFilterSpecMap[K]): FluxFilterItem {
17
- const parser = filterParsers[type] as (spec: FluxFilterSpecMap[K]) => FluxFilterItem;
18
-
19
- return parser(spec);
20
- }
9
+ import type { FluxFilterDefinition, FluxFilterState, FluxFilterValue } from '@flux-ui/types';
10
+ import { computed, provide, unref, watchEffect, type VNode } from 'vue';
11
+ import { FluxFilterInjectionKey } from '~flux/components/data';
21
12
 
22
13
  const emit = defineEmits<{
23
14
  back: [];
24
- reset: [string]
15
+ clear: [string];
16
+ reset: [string];
25
17
  }>();
26
18
 
27
19
  const modelValue = defineModel<FluxFilterState>({
28
20
  required: true
29
21
  });
30
22
 
31
- defineProps<{
32
- readonly resettable?: string[];
33
- }>();
34
-
35
23
  const slots = defineSlots<{
36
24
  default(props: {
37
- readonly buttons: Record<string, FluxFilterItem>;
25
+ readonly buttons: Record<string, FluxFilterDefinition>;
38
26
  readonly filters: Record<string, VNode>;
39
- readonly menuItems: (FluxFilterItem | VNode)[][];
27
+ readonly menuItems: (FluxFilterDefinition | VNode)[][];
28
+ clear(name: string): void;
29
+ reset(name: string): void;
40
30
  }): VNode[];
41
31
 
42
32
  filters(): VNode[];
43
33
  }>();
44
34
 
35
+ function resolveDefinition(vnode: VNode): FluxFilterDefinition | null {
36
+ const factory = (vnode.type as { __filterDefinitionFactory?: (props: unknown) => FluxFilterDefinition })?.__filterDefinitionFactory;
37
+
38
+ return typeof factory === 'function' ? factory(getComponentProps(vnode)) : null;
39
+ }
40
+
41
+ const flattenedFilters = computed(() => flattenVNodeTree(slots.filters?.() ?? []));
42
+
45
43
  const buttons = computed(() => {
46
- const buttons: Record<string, FluxFilterItem> = {};
44
+ const buttons: Record<string, FluxFilterDefinition> = {};
47
45
  const items = unref(flattenedFilters);
48
46
 
49
47
  for (const item of items) {
50
- const name = getComponentName(item);
48
+ const definition = resolveDefinition(item);
51
49
 
52
- if (!name.startsWith('FluxFilter')) {
50
+ if (!definition) {
53
51
  continue;
54
52
  }
55
53
 
56
- const type = camelCase(name.substring(10)) as FluxFilterType;
57
- const props = getComponentProps<FluxFilterSpecMap[FluxFilterType]>(item);
58
-
59
- buttons[props.name] = applyParser(type, props);
54
+ buttons[definition.name] = definition;
60
55
  }
61
56
 
62
57
  return buttons;
63
58
  });
64
59
 
65
- const flattenedFilters = computed(() => flattenVNodeTree(slots.filters?.() ?? []));
66
-
67
60
  const filters = computed<Record<string, VNode>>(() => {
68
61
  const filters: { [key: string]: VNode; } = {};
69
62
  const items = unref(flattenedFilters);
70
63
 
71
64
  for (const item of items) {
72
- const name = getComponentName(item);
65
+ const definition = resolveDefinition(item);
73
66
 
74
- if (!name.startsWith('FluxFilter')) {
67
+ if (!definition) {
75
68
  continue;
76
69
  }
77
70
 
78
- const props = getComponentProps<{ name: string; }>(item);
79
-
80
- if (!props.name) {
81
- continue;
82
- }
83
-
84
- filters[props.name] = item;
71
+ filters[definition.name] = item;
85
72
  }
86
73
 
87
74
  return filters;
88
75
  });
89
76
 
90
- const menuItems = computed<(FluxFilterItem | VNode)[][]>(() => {
91
- const menuItems: (FluxFilterItem | VNode)[][] = [[]];
77
+ const menuItems = computed<(FluxFilterDefinition | VNode)[][]>(() => {
78
+ const menuItems: (FluxFilterDefinition | VNode)[][] = [[]];
92
79
  const items = unref(flattenedFilters);
93
80
 
94
81
  for (const item of items) {
95
- const name = getComponentName(item);
96
-
97
- if (name === 'FluxSeparator') {
82
+ if (getComponentName(item) === 'FluxSeparator') {
98
83
  menuItems.push([]);
99
84
  continue;
100
85
  }
101
86
 
102
- if (name.startsWith('FluxFilter')) {
103
- const type = camelCase(name.substring(10)) as FluxFilterType;
104
- const props = getComponentProps<FluxFilterSpecMap[FluxFilterType]>(item);
87
+ const definition = resolveDefinition(item);
105
88
 
106
- menuItems[menuItems.length - 1].push(applyParser(type, props));
89
+ if (definition) {
90
+ menuItems[menuItems.length - 1].push(definition);
107
91
  continue;
108
92
  }
109
93
 
@@ -113,37 +97,67 @@
113
97
  return menuItems;
114
98
  });
115
99
 
100
+ watchEffect(() => {
101
+ const state = unref(modelValue);
102
+
103
+ for (const definition of Object.values(unref(buttons))) {
104
+ if (definition.defaultValue !== undefined && state[definition.name] === undefined) {
105
+ modelValue.value[definition.name] = definition.defaultValue as FluxFilterValue;
106
+ }
107
+ }
108
+ });
109
+
116
110
  function back(): void {
117
111
  emit('back');
118
112
  }
119
113
 
114
+ function clear(name: string): void {
115
+ const definition = unref(buttons)[name];
116
+
117
+ back();
118
+ modelValue.value[name] = null;
119
+ definition?.onClear?.();
120
+ emit('clear', name);
121
+ }
122
+
120
123
  function reset(name: string): void {
124
+ const definition = unref(buttons)[name];
125
+
121
126
  back();
127
+ modelValue.value[name] = (definition?.defaultValue ?? null) as FluxFilterValue;
128
+ definition?.onClear?.();
122
129
  emit('reset', name);
123
130
  }
124
131
 
125
- function getValue(name: string): FluxFilterOptionItem['value'] | undefined {
132
+ function getDefinition(name: string): FluxFilterDefinition | undefined {
133
+ return unref(buttons)[name];
134
+ }
135
+
136
+ function getValue(name: string): FluxFilterValue | undefined {
126
137
  if (!hasValue(name)) {
127
138
  return undefined;
128
139
  }
129
140
 
130
- return unref(modelValue)[name] as FluxFilterOptionItem['value'];
141
+ return unref(modelValue)[name] as FluxFilterValue;
131
142
  }
132
143
 
133
144
  function hasValue(name: string): boolean {
134
145
  return name in unref(modelValue);
135
146
  }
136
147
 
137
- function setValue(name: string, value: FluxFilterOptionItem['value']): void {
148
+ function setValue(name: string, value: FluxFilterValue): void {
138
149
  modelValue.value[name] = value;
150
+ unref(buttons)[name]?.onChange?.(value);
139
151
  }
140
152
 
141
153
  provide(FluxFilterInjectionKey, {
142
154
  state: modelValue,
143
155
  back,
144
- reset,
156
+ clear,
157
+ getDefinition,
145
158
  getValue,
146
159
  hasValue,
160
+ reset,
147
161
  setValue
148
162
  });
149
163
  </script>
@@ -10,22 +10,38 @@
10
10
  <script
11
11
  lang="ts"
12
12
  setup>
13
- import type { FluxIconName } from '@flux-ui/types';
13
+ import type { FluxFilterDateSpec } from '@flux-ui/types';
14
14
  import { DateTime } from 'luxon';
15
15
  import { computed, unref } from 'vue';
16
16
  import { useFilterInjection } from '~flux/components/composable';
17
+ import { defineFilter, pickFilterCommon } from '~flux/components/util';
17
18
  import FluxDatePicker from './FluxDatePicker.vue';
18
19
  import $style from '~flux/components/css/component/Filter.module.scss';
19
20
 
20
- const {
21
- name
22
- } = defineProps<{
23
- readonly icon?: FluxIconName;
24
- readonly label: string;
21
+ type Props = FluxFilterDateSpec & {
25
22
  readonly max?: DateTime;
26
23
  readonly min?: DateTime;
27
- readonly name: string;
28
- }>();
24
+ };
25
+
26
+ defineFilter<Props>(p => ({
27
+ ...pickFilterCommon(p),
28
+ type: 'date',
29
+ async getValueLabel(value) {
30
+ if (!DateTime.isDateTime(value)) {
31
+ return null;
32
+ }
33
+
34
+ return value.toLocaleString({
35
+ day: 'numeric',
36
+ month: 'short',
37
+ year: 'numeric'
38
+ });
39
+ }
40
+ }));
41
+
42
+ const {
43
+ name
44
+ } = defineProps<Props>();
29
45
 
30
46
  const {back, state, setValue} = useFilterInjection();
31
47
 
@@ -11,24 +11,42 @@
11
11
  <script
12
12
  lang="ts"
13
13
  setup>
14
- import type { FluxIconName } from '@flux-ui/types';
14
+ import type { FluxFilterDateRangeSpec } from '@flux-ui/types';
15
15
  import { DateTime } from 'luxon';
16
16
  import { computed, unref } from 'vue';
17
17
  import { useFilterInjection } from '~flux/components/composable';
18
+ import { createLabelForDateRange, defineFilter, pickFilterCommon } from '~flux/components/util';
18
19
  import FluxDatePicker from './FluxDatePicker.vue';
19
20
  import $style from '~flux/components/css/component/Filter.module.scss';
20
21
 
21
- const {
22
- name,
23
- rangeMode = 'range'
24
- } = defineProps<{
25
- readonly icon?: FluxIconName;
26
- readonly label: string;
22
+ type Props = FluxFilterDateRangeSpec & {
27
23
  readonly max?: DateTime;
28
24
  readonly min?: DateTime;
29
- readonly name: string;
30
25
  readonly rangeMode?: 'range' | 'week' | 'month';
31
- }>();
26
+ };
27
+
28
+ defineFilter<Props>(p => ({
29
+ ...pickFilterCommon(p),
30
+ type: 'dateRange',
31
+ async getValueLabel(value) {
32
+ if (!Array.isArray(value) || value.length !== 2) {
33
+ return null;
34
+ }
35
+
36
+ const [start, end] = value;
37
+
38
+ if (!DateTime.isDateTime(start) || !DateTime.isDateTime(end)) {
39
+ return null;
40
+ }
41
+
42
+ return createLabelForDateRange(start, end);
43
+ }
44
+ }));
45
+
46
+ const {
47
+ name,
48
+ rangeMode = 'range'
49
+ } = defineProps<Props>();
32
50
 
33
51
  const {back, state, setValue} = useFilterInjection();
34
52
 
@@ -11,25 +11,35 @@
11
11
  <script
12
12
  lang="ts"
13
13
  setup>
14
- import type { FluxFilterOptionRow, FluxIconName } from '@flux-ui/types';
14
+ import type { FluxFilterOptionSpec } from '@flux-ui/types';
15
15
  import { computed, unref } from 'vue';
16
16
  import { useFilterOptionSingle } from '~flux/components/composable/private';
17
- import { isFluxFilterOptionHeader } from '~flux/components/data';
17
+ import { defineFilter, isFluxFilterOptionHeader, isFluxFilterOptionItem, pickFilterCommon } from '~flux/components/util';
18
18
  import { FilterOptionBase } from './primitive';
19
19
 
20
+ type Props = FluxFilterOptionSpec & {
21
+ readonly isSearchable?: boolean;
22
+ readonly searchPlaceholder?: string;
23
+ };
24
+
25
+ defineFilter<Props>(p => {
26
+ const items = p.options.filter(isFluxFilterOptionItem);
27
+
28
+ return {
29
+ ...pickFilterCommon(p),
30
+ type: 'option',
31
+ async getValueLabel(value) {
32
+ return items.find(o => o.value === value)?.label ?? null;
33
+ }
34
+ };
35
+ });
36
+
20
37
  const modelSearch = defineModel<string>('searchQuery', {default: ''});
21
38
 
22
39
  const {
23
40
  name,
24
41
  options
25
- } = defineProps<{
26
- readonly icon?: FluxIconName;
27
- readonly isSearchable?: boolean;
28
- readonly label: string;
29
- readonly name: string;
30
- readonly options: FluxFilterOptionRow[];
31
- readonly searchPlaceholder?: string;
32
- }>();
42
+ } = defineProps<Props>();
33
43
 
34
44
  const {currentValue, onSelect} = useFilterOptionSingle(name);
35
45
 
@@ -12,11 +12,28 @@
12
12
  <script
13
13
  lang="ts"
14
14
  setup>
15
- import type { FluxFilterOptionRow, FluxFilterValue, FluxIconName } from '@flux-ui/types';
15
+ import type { FluxFilterOptionAsyncSpec, FluxFilterOptionRow, FluxFilterValue } from '@flux-ui/types';
16
16
  import { computed, unref } from 'vue';
17
17
  import { useAsyncFilterOptions, useFilterOptionSingle } from '~flux/components/composable/private';
18
+ import { defineFilter, isFluxFilterOptionItem, pickFilterCommon } from '~flux/components/util';
18
19
  import { FilterOptionBase } from './primitive';
19
20
 
21
+ type Props = FluxFilterOptionAsyncSpec & {
22
+ fetchRelevant(): Promise<FluxFilterOptionRow[]>;
23
+ fetchSearch(searchQuery: string): Promise<FluxFilterOptionRow[]>;
24
+ readonly searchPlaceholder?: string;
25
+ };
26
+
27
+ defineFilter<Props>(p => ({
28
+ ...pickFilterCommon(p),
29
+ type: 'option',
30
+ async getValueLabel(value) {
31
+ const items = (await p.fetchOptions([value])).filter(isFluxFilterOptionItem);
32
+
33
+ return items.find(o => o.value === value)?.label ?? null;
34
+ }
35
+ }));
36
+
20
37
  const modelSearch = defineModel<string>('searchQuery', {
21
38
  default: ''
22
39
  });
@@ -26,16 +43,7 @@
26
43
  fetchRelevant: fetchRelevantProp,
27
44
  fetchSearch: fetchSearchProp,
28
45
  name
29
- } = defineProps<{
30
- fetchOptions(ids: FluxFilterValue[]): Promise<FluxFilterOptionRow[]>;
31
- fetchRelevant(): Promise<FluxFilterOptionRow[]>;
32
- fetchSearch(searchQuery: string): Promise<FluxFilterOptionRow[]>;
33
-
34
- readonly icon?: FluxIconName;
35
- readonly label: string;
36
- readonly name: string;
37
- readonly searchPlaceholder?: string;
38
- }>();
46
+ } = defineProps<Props>();
39
47
 
40
48
  const {currentValue, onSelect} = useFilterOptionSingle(name);
41
49
 
@@ -11,12 +11,34 @@
11
11
  <script
12
12
  lang="ts"
13
13
  setup>
14
- import type { FluxFilterOptionRow, FluxIconName } from '@flux-ui/types';
14
+ import type { FluxFilterOptionsSpec } from '@flux-ui/types';
15
15
  import { computed, unref } from 'vue';
16
- import { useFilterOptionMulti } from '~flux/components/composable/private';
17
- import { isFluxFilterOptionHeader } from '~flux/components/data';
16
+ import { useFilterOptionMulti, useTranslate } from '~flux/components/composable/private';
17
+ import { defineFilter, generateMultiOptionsLabel, isFluxFilterOptionHeader, isFluxFilterOptionItem, pickFilterCommon } from '~flux/components/util';
18
18
  import { FilterOptionBase } from './primitive';
19
19
 
20
+ type Props = FluxFilterOptionsSpec & {
21
+ readonly isSearchable?: boolean;
22
+ readonly searchPlaceholder?: string;
23
+ };
24
+
25
+ defineFilter<Props>(p => {
26
+ const items = p.options.filter(isFluxFilterOptionItem);
27
+ const translate = useTranslate();
28
+
29
+ return {
30
+ ...pickFilterCommon(p),
31
+ type: 'options',
32
+ async getValueLabel(value) {
33
+ if (!Array.isArray(value)) {
34
+ return null;
35
+ }
36
+
37
+ return generateMultiOptionsLabel(translate, items, value);
38
+ }
39
+ };
40
+ });
41
+
20
42
  const modelSearch = defineModel<string>('searchQuery', {
21
43
  default: ''
22
44
  });
@@ -24,14 +46,7 @@
24
46
  const {
25
47
  name,
26
48
  options
27
- } = defineProps<{
28
- readonly icon?: FluxIconName;
29
- readonly isSearchable?: boolean;
30
- readonly label: string;
31
- readonly name: string;
32
- readonly options: FluxFilterOptionRow[];
33
- readonly searchPlaceholder?: string;
34
- }>();
49
+ } = defineProps<Props>();
35
50
 
36
51
  const {currentValue, onSelect} = useFilterOptionMulti(name);
37
52
 
@@ -12,10 +12,35 @@
12
12
  <script
13
13
  lang="ts"
14
14
  setup>
15
- import type { FluxFilterOptionRow, FluxFilterValue, FluxIconName } from '@flux-ui/types';
16
- import { useAsyncFilterOptions, useFilterOptionMulti } from '~flux/components/composable/private';
15
+ import type { FluxFilterOptionRow, FluxFilterOptionsAsyncSpec, FluxFilterValue } from '@flux-ui/types';
16
+ import { useAsyncFilterOptions, useFilterOptionMulti, useTranslate } from '~flux/components/composable/private';
17
+ import { defineFilter, generateMultiOptionsLabel, isFluxFilterOptionItem, pickFilterCommon } from '~flux/components/util';
17
18
  import { FilterOptionBase } from './primitive';
18
19
 
20
+ type Props = FluxFilterOptionsAsyncSpec & {
21
+ fetchRelevant(): Promise<FluxFilterOptionRow[]>;
22
+ fetchSearch(searchQuery: string): Promise<FluxFilterOptionRow[]>;
23
+ readonly searchPlaceholder?: string;
24
+ };
25
+
26
+ defineFilter<Props>(p => {
27
+ const translate = useTranslate();
28
+
29
+ return {
30
+ ...pickFilterCommon(p),
31
+ type: 'options',
32
+ async getValueLabel(value) {
33
+ if (!Array.isArray(value)) {
34
+ return null;
35
+ }
36
+
37
+ const items = (await p.fetchOptions(value)).filter(isFluxFilterOptionItem);
38
+
39
+ return generateMultiOptionsLabel(translate, items, value);
40
+ }
41
+ };
42
+ });
43
+
19
44
  const modelSearch = defineModel<string>('searchQuery', {
20
45
  default: ''
21
46
  });
@@ -25,16 +50,7 @@
25
50
  fetchRelevant: fetchRelevantProp,
26
51
  fetchSearch: fetchSearchProp,
27
52
  name
28
- } = defineProps<{
29
- fetchOptions(ids: FluxFilterValue[]): Promise<FluxFilterOptionRow[]>;
30
- fetchRelevant(): Promise<FluxFilterOptionRow[]>;
31
- fetchSearch(searchQuery: string): Promise<FluxFilterOptionRow[]>;
32
-
33
- readonly icon?: FluxIconName;
34
- readonly label: string;
35
- readonly name: string;
36
- readonly searchPlaceholder?: string;
37
- }>();
53
+ } = defineProps<Props>();
38
54
 
39
55
  const {currentValue, onSelect} = useFilterOptionMulti(name);
40
56
 
@@ -38,31 +38,45 @@
38
38
  lang="ts"
39
39
  setup>
40
40
  import { formatNumber } from '@basmilius/utils';
41
- import type { FluxIconName } from '@flux-ui/types';
41
+ import type { FluxFilterRangeSpec } from '@flux-ui/types';
42
42
  import { computed, unref } from 'vue';
43
43
  import { useFilterInjection } from '~flux/components/composable';
44
44
  import { useTranslate } from '~flux/components/composable/private';
45
+ import { defineFilter, pickFilterCommon } from '~flux/components/util';
45
46
  import FluxFormColumn from './FluxFormColumn.vue';
46
47
  import FluxFormField from './FluxFormField.vue';
47
48
  import FluxFormSlider from './FluxFormSlider.vue';
48
49
  import FluxPaneBody from './FluxPaneBody.vue';
49
50
 
51
+ type Props = FluxFilterRangeSpec & {
52
+ readonly isTicksVisible?: boolean;
53
+ readonly max: number;
54
+ readonly min: number;
55
+ readonly step?: number;
56
+ };
57
+
58
+ defineFilter<Props>(p => ({
59
+ ...pickFilterCommon(p),
60
+ type: 'range',
61
+ async getValueLabel(value) {
62
+ if (!value || !Array.isArray(value) || value.length !== 2) {
63
+ return null;
64
+ }
65
+
66
+ const [lower, upper] = value as number[];
67
+ const format = p.formatter ?? formatNumber;
68
+
69
+ return `${format(lower!)} – ${format(upper!)}`;
70
+ }
71
+ }));
72
+
50
73
  const {
51
74
  formatter = formatNumber,
52
75
  max,
53
76
  min,
54
77
  name,
55
78
  step = 1
56
- } = defineProps<{
57
- readonly icon?: FluxIconName;
58
- readonly isTicksVisible?: boolean;
59
- readonly label: string;
60
- readonly name: string;
61
- readonly max: number;
62
- readonly min: number;
63
- readonly step?: number;
64
- readonly formatter?: (value: number) => string;
65
- }>();
79
+ } = defineProps<Props>();
66
80
 
67
81
  const {state, setValue} = useFilterInjection();
68
82
  const translate = useTranslate();
@@ -25,11 +25,17 @@
25
25
  @click="back()"/>
26
26
 
27
27
  <FluxMenuItem
28
- v-if="resettable?.includes(name)"
29
- :class="$style.filterReset"
28
+ v-if="canReset(name)"
29
+ :class="$style.filterAction"
30
+ icon-leading="rotate-left"
31
+ @click="reset(name)"
32
+ style="flex-grow: 0"/>
33
+
34
+ <FluxMenuItem
35
+ :class="$style.filterAction"
30
36
  icon-leading="trash"
31
37
  is-destructive
32
- @click="reset(name)"
38
+ @click="clear(name)"
33
39
  style="flex-grow: 0"/>
34
40
  </FluxMenuGroup>
35
41
 
@@ -44,9 +50,11 @@
44
50
  lang="ts"
45
51
  setup>
46
52
  import { vHeightTransition } from '@flux-ui/internals';
47
- import type { FluxFilterItem } from '@flux-ui/types';
53
+ import type { FluxFilterDefinition } from '@flux-ui/types';
48
54
  import { unref, useTemplateRef, type VNode } from 'vue';
55
+ import { useFilterInjection } from '~flux/components/composable';
49
56
  import { useTranslate } from '~flux/components/composable/private';
57
+ import { isResettable } from '~flux/components/util';
50
58
  import FluxMenu from './FluxMenu.vue';
51
59
  import FluxMenuGroup from './FluxMenuGroup.vue';
52
60
  import FluxMenuItem from './FluxMenuItem.vue';
@@ -54,20 +62,17 @@
54
62
  import { FilterMenuRenderer, VNodeRenderer } from './primitive';
55
63
  import $style from '~flux/components/css/component/Filter.module.scss';
56
64
 
57
- const emit = defineEmits<{
58
- reset: [string]
59
- }>();
60
-
61
65
  defineProps<{
62
66
  readonly filters: Record<string, VNode>;
63
- readonly menuItems: (FluxFilterItem | VNode)[][];
64
- readonly resettable?: string[];
67
+ readonly menuItems: (FluxFilterDefinition | VNode)[][];
65
68
  }>();
66
69
 
67
70
  defineSlots<{
68
71
  default(): VNode[];
69
72
  }>();
70
73
 
74
+ const {clear: clearFilter, getDefinition, getValue, reset: resetFilter} = useFilterInjection();
75
+
71
76
  const translate = useTranslate();
72
77
 
73
78
  const windowRef = useTemplateRef<{ back(to: string): void; }>('window');
@@ -76,8 +81,17 @@
76
81
  unref(windowRef)?.back('default');
77
82
  }
78
83
 
84
+ function clear(name: string): void {
85
+ back();
86
+ clearFilter(name);
87
+ }
88
+
79
89
  function reset(name: string): void {
80
90
  back();
81
- emit('reset', name);
91
+ resetFilter(name);
92
+ }
93
+
94
+ function canReset(name: string): boolean {
95
+ return isResettable(getDefinition(name), getValue(name));
82
96
  }
83
97
  </script>