@agnos-ui/core 0.0.1-alpha.2 → 0.0.1-alpha.4
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.
- package/accordion.d.ts +10 -0
- package/accordion.js +7 -2
- package/config.d.ts +5 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +3 -2
- package/services/index.d.ts +1 -0
- package/services/index.js +1 -0
- package/services/isFocusable.d.ts +9 -0
- package/services/isFocusable.js +35 -0
- package/slider.d.ts +193 -0
- package/slider.js +289 -0
package/accordion.d.ts
CHANGED
|
@@ -126,6 +126,12 @@ export interface AccordionProps extends WidgetsCommonPropsAndState {
|
|
|
126
126
|
* It is a prop of the accordion-item.
|
|
127
127
|
*/
|
|
128
128
|
itemBodyClass: string;
|
|
129
|
+
/**
|
|
130
|
+
* The html tag to use for the accordion-item-header.
|
|
131
|
+
*
|
|
132
|
+
* It is a prop of the accordion-item.
|
|
133
|
+
*/
|
|
134
|
+
itemHeadingTag: string;
|
|
129
135
|
}
|
|
130
136
|
export interface AccordionState extends WidgetsCommonPropsAndState {
|
|
131
137
|
/**
|
|
@@ -263,6 +269,10 @@ export interface AccordionItemCommonPropsAndState {
|
|
|
263
269
|
* Classes to add on the accordion-item body DOM element.
|
|
264
270
|
*/
|
|
265
271
|
itemBodyClass: string;
|
|
272
|
+
/**
|
|
273
|
+
* The html tag to use for the accordion-item-header.
|
|
274
|
+
*/
|
|
275
|
+
itemHeadingTag: string;
|
|
266
276
|
}
|
|
267
277
|
export interface AccordionItemProps extends AccordionItemCommonPropsAndState {
|
|
268
278
|
/**
|
package/accordion.js
CHANGED
|
@@ -35,11 +35,12 @@ const defaultAccordionConfig = {
|
|
|
35
35
|
onHidden: noop,
|
|
36
36
|
className: '',
|
|
37
37
|
itemId: '',
|
|
38
|
-
itemDestroyOnHide:
|
|
38
|
+
itemDestroyOnHide: true,
|
|
39
39
|
itemDisabled: false,
|
|
40
40
|
itemVisible: false,
|
|
41
41
|
itemAnimation: true,
|
|
42
42
|
itemTransition: collapseVerticalTransition,
|
|
43
|
+
itemHeadingTag: '',
|
|
43
44
|
onItemShown: noop,
|
|
44
45
|
onItemHidden: noop,
|
|
45
46
|
onItemVisibleChange: noop,
|
|
@@ -70,6 +71,7 @@ const defaultItemConfig = {
|
|
|
70
71
|
itemButtonClass: defaultAccordionConfig.itemButtonClass,
|
|
71
72
|
itemCollapseClass: defaultAccordionConfig.itemCollapseClass,
|
|
72
73
|
itemBodyClass: defaultAccordionConfig.itemBodyClass,
|
|
74
|
+
itemHeadingTag: defaultAccordionConfig.itemHeadingTag,
|
|
73
75
|
};
|
|
74
76
|
const accordionItemProps = Object.keys(defaultItemConfig);
|
|
75
77
|
/**
|
|
@@ -97,6 +99,7 @@ const configAccordionValidator = {
|
|
|
97
99
|
itemButtonClass: typeString,
|
|
98
100
|
itemCollapseClass: typeString,
|
|
99
101
|
itemBodyClass: typeString,
|
|
102
|
+
itemHeadingTag: typeString,
|
|
100
103
|
};
|
|
101
104
|
const configItemValidator = {
|
|
102
105
|
itemId: typeString,
|
|
@@ -113,6 +116,7 @@ const configItemValidator = {
|
|
|
113
116
|
itemButtonClass: typeString,
|
|
114
117
|
itemCollapseClass: typeString,
|
|
115
118
|
itemBodyClass: typeString,
|
|
119
|
+
itemHeadingTag: typeString,
|
|
116
120
|
};
|
|
117
121
|
function createAccordionItem(accordionOnShown, accordionOnHidden, config) {
|
|
118
122
|
const [{ itemAnimation$, itemTransition$, itemDestroyOnHide$, onItemShown$, onItemHidden$, onItemVisibleChange$, itemVisible$, itemId$: _dirtyItemId$, itemDisabled$, ...stateProps }, patch,] = writablesForProps(defaultItemConfig, config, configItemValidator);
|
|
@@ -178,7 +182,7 @@ function createAccordionItem(accordionOnShown, accordionOnHidden, config) {
|
|
|
178
182
|
* @returns a new accordion widget instance
|
|
179
183
|
*/
|
|
180
184
|
export function createAccordion(config) {
|
|
181
|
-
const [{ closeOthers$, onShown$, onHidden$, itemId$, itemAnimation$, itemClass$, itemDisabled$, itemVisible$, itemTransition$, itemDestroyOnHide$, itemBodyClass$, itemButtonClass$, itemCollapseClass$, itemHeaderClass$, onItemVisibleChange$, onItemHidden$, onItemShown$, slotItemStructure$, slotItemBody$, slotItemHeader$, ...stateProps }, patch,] = writablesForProps(defaultAccordionConfig, config, configAccordionValidator);
|
|
185
|
+
const [{ closeOthers$, onShown$, onHidden$, itemId$, itemAnimation$, itemClass$, itemDisabled$, itemVisible$, itemTransition$, itemDestroyOnHide$, itemBodyClass$, itemButtonClass$, itemCollapseClass$, itemHeaderClass$, itemHeadingTag$, onItemVisibleChange$, onItemHidden$, onItemShown$, slotItemStructure$, slotItemBody$, slotItemHeader$, ...stateProps }, patch,] = writablesForProps(defaultAccordionConfig, config, configAccordionValidator);
|
|
182
186
|
const accordionItemConfig = {
|
|
183
187
|
itemId: itemId$,
|
|
184
188
|
itemClass: itemClass$,
|
|
@@ -191,6 +195,7 @@ export function createAccordion(config) {
|
|
|
191
195
|
itemButtonClass: itemButtonClass$,
|
|
192
196
|
itemCollapseClass: itemCollapseClass$,
|
|
193
197
|
itemHeaderClass: itemHeaderClass$,
|
|
198
|
+
itemHeadingTag: itemHeadingTag$,
|
|
194
199
|
onItemVisibleChange: onItemVisibleChange$,
|
|
195
200
|
onItemHidden: onItemHidden$,
|
|
196
201
|
onItemShown: onItemShown$,
|
package/config.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { RatingProps } from './rating';
|
|
|
6
6
|
import type { SelectProps } from './select';
|
|
7
7
|
import type { AccordionProps } from './accordion';
|
|
8
8
|
import type { ProgressbarProps } from './progressbar';
|
|
9
|
+
import type { SliderProps } from './slider';
|
|
9
10
|
export type Partial2Levels<T> = Partial<{
|
|
10
11
|
[Level1 in keyof T]: Partial<T[Level1]>;
|
|
11
12
|
}>;
|
|
@@ -73,4 +74,8 @@ export interface WidgetsConfig {
|
|
|
73
74
|
* the progress bar widget config
|
|
74
75
|
*/
|
|
75
76
|
progressbar: ProgressbarProps;
|
|
77
|
+
/**
|
|
78
|
+
* slider widget config
|
|
79
|
+
*/
|
|
80
|
+
slider: SliderProps;
|
|
76
81
|
}
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"alert",
|
|
11
11
|
"modal",
|
|
12
12
|
"pagination",
|
|
13
|
-
"rating"
|
|
13
|
+
"rating",
|
|
14
|
+
"slider"
|
|
14
15
|
],
|
|
15
16
|
"type": "module",
|
|
16
17
|
"main": "./index.js",
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
"@amadeus-it-group/tansu": "0.0.23"
|
|
27
28
|
},
|
|
28
29
|
"sideEffects": false,
|
|
29
|
-
"version": "
|
|
30
|
+
"version": "0.0.1-alpha.4",
|
|
30
31
|
"homepage": "https://amadeusitgroup.github.io/AgnosUI/latest/",
|
|
31
32
|
"bugs": "https://github.com/AmadeusITGroup/AgnosUI/issues",
|
|
32
33
|
"license": "MIT",
|
package/services/index.d.ts
CHANGED
package/services/index.js
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true if the given HTML element is programmatically focusable.
|
|
3
|
+
* Warning: this is a best-effort approximation of whether the element is really focusable.
|
|
4
|
+
* It may not handle all use cases accurately.
|
|
5
|
+
*
|
|
6
|
+
* @param element - element to test
|
|
7
|
+
* @returns true if the element is programmatically focusable.
|
|
8
|
+
*/
|
|
9
|
+
export declare const isFocusable: (element: HTMLElement) => boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const isInertOrInvisible = (element) => {
|
|
2
|
+
let curElement = element;
|
|
3
|
+
while (curElement) {
|
|
4
|
+
const style = getComputedStyle(curElement);
|
|
5
|
+
if (curElement.inert || curElement.hidden || style.display === 'none' || style.visibility === 'hidden') {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
curElement = curElement.parentElement;
|
|
9
|
+
}
|
|
10
|
+
return false;
|
|
11
|
+
};
|
|
12
|
+
const checkNotDisabled = (element) => {
|
|
13
|
+
if (element.disabled) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const parentFieldset = element.parentElement?.closest('fieldset');
|
|
17
|
+
return parentFieldset ? checkNotDisabled(parentFieldset) : true;
|
|
18
|
+
};
|
|
19
|
+
const isFocusableOtherTags = (element) => element.isContentEditable || !!element.hasAttribute('tabindex');
|
|
20
|
+
const isFocusableByTagName = {
|
|
21
|
+
INPUT: (element) => element.type !== 'hidden' && checkNotDisabled(element),
|
|
22
|
+
SELECT: checkNotDisabled,
|
|
23
|
+
TEXTAREA: checkNotDisabled,
|
|
24
|
+
BUTTON: checkNotDisabled,
|
|
25
|
+
A: (element) => !!element.href || isFocusableOtherTags(element),
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Returns true if the given HTML element is programmatically focusable.
|
|
29
|
+
* Warning: this is a best-effort approximation of whether the element is really focusable.
|
|
30
|
+
* It may not handle all use cases accurately.
|
|
31
|
+
*
|
|
32
|
+
* @param element - element to test
|
|
33
|
+
* @returns true if the element is programmatically focusable.
|
|
34
|
+
*/
|
|
35
|
+
export const isFocusable = (element) => !isInertOrInvisible(element) && (isFocusableByTagName[element.tagName] ?? isFocusableOtherTags)(element);
|
package/slider.d.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { WidgetsCommonPropsAndState } from './commonProps';
|
|
2
|
+
import type { PropsConfig } from './services';
|
|
3
|
+
import type { Directive, Widget } from './types';
|
|
4
|
+
export interface ProgressDisplayOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Left offset of the progress in %
|
|
7
|
+
*/
|
|
8
|
+
left: number;
|
|
9
|
+
/**
|
|
10
|
+
* Bottom offset of the progresss in %
|
|
11
|
+
*/
|
|
12
|
+
bottom: number;
|
|
13
|
+
/**
|
|
14
|
+
* Width of the progress in %
|
|
15
|
+
*/
|
|
16
|
+
width: number;
|
|
17
|
+
/**
|
|
18
|
+
* Height of hte progress in %
|
|
19
|
+
*/
|
|
20
|
+
height: number;
|
|
21
|
+
}
|
|
22
|
+
export interface HandleDisplayOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Left offset of the handle in %
|
|
25
|
+
*/
|
|
26
|
+
left: number;
|
|
27
|
+
/**
|
|
28
|
+
* Top offset of the handle in %
|
|
29
|
+
*/
|
|
30
|
+
top: number;
|
|
31
|
+
}
|
|
32
|
+
export interface SliderCommonPropsAndState extends WidgetsCommonPropsAndState {
|
|
33
|
+
/**
|
|
34
|
+
* Minimum value that can be assigned to the slider
|
|
35
|
+
*/
|
|
36
|
+
min: number;
|
|
37
|
+
/**
|
|
38
|
+
* Maximum value that can be assigned to the slider
|
|
39
|
+
*/
|
|
40
|
+
max: number;
|
|
41
|
+
/**
|
|
42
|
+
* Unit value between slider steps
|
|
43
|
+
*/
|
|
44
|
+
stepSize: number;
|
|
45
|
+
/**
|
|
46
|
+
* If `true` slider value cannot be changed but the slider is still focusable
|
|
47
|
+
*/
|
|
48
|
+
readonly: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* If `true` slider value cannot be changed and the slider cannot be focused
|
|
51
|
+
*/
|
|
52
|
+
disabled: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* If `true` is vertically positioned otherwise it is horizontal
|
|
55
|
+
*/
|
|
56
|
+
vertical: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Current slider values
|
|
59
|
+
*/
|
|
60
|
+
values: number[];
|
|
61
|
+
}
|
|
62
|
+
export interface SliderState extends SliderCommonPropsAndState {
|
|
63
|
+
/**
|
|
64
|
+
* Sorted slider values
|
|
65
|
+
*/
|
|
66
|
+
sortedValues: number[];
|
|
67
|
+
/**
|
|
68
|
+
* Combined label left offset in %
|
|
69
|
+
*/
|
|
70
|
+
combinedLabelPositionLeft: number;
|
|
71
|
+
/**
|
|
72
|
+
* Combined label top offset in %
|
|
73
|
+
*/
|
|
74
|
+
combinedLabelPositionTop: number;
|
|
75
|
+
/**
|
|
76
|
+
* If `visible` the minimum label will be displayed
|
|
77
|
+
*/
|
|
78
|
+
minValueLabelDisplay: string;
|
|
79
|
+
/**
|
|
80
|
+
* If `visible` the maximum label will be displayed
|
|
81
|
+
*/
|
|
82
|
+
maxValueLabelDisplay: string;
|
|
83
|
+
/**
|
|
84
|
+
* If `visible` the label when the handles are close is displayed
|
|
85
|
+
*/
|
|
86
|
+
combinedLabelDisplay: string;
|
|
87
|
+
/**
|
|
88
|
+
* Array of the sorted handles to display
|
|
89
|
+
*/
|
|
90
|
+
sortedHandles: {
|
|
91
|
+
value: number;
|
|
92
|
+
id: number;
|
|
93
|
+
}[];
|
|
94
|
+
/**
|
|
95
|
+
* Array of objects representing progress display options
|
|
96
|
+
*/
|
|
97
|
+
progressDisplayOptions: ProgressDisplayOptions[];
|
|
98
|
+
/**
|
|
99
|
+
* Array of objects representing handle display options
|
|
100
|
+
*/
|
|
101
|
+
handleDisplayOptions: HandleDisplayOptions[];
|
|
102
|
+
}
|
|
103
|
+
export interface SliderProps extends SliderCommonPropsAndState {
|
|
104
|
+
/**
|
|
105
|
+
* An event emitted when slider values are changed
|
|
106
|
+
*
|
|
107
|
+
* Event payload equals to the updated slider values
|
|
108
|
+
*/
|
|
109
|
+
onValuesChange: (values: number[]) => void;
|
|
110
|
+
}
|
|
111
|
+
export interface SliderApi {
|
|
112
|
+
}
|
|
113
|
+
export interface SliderDirectives {
|
|
114
|
+
/**
|
|
115
|
+
* Directive to get the slider component elementRef
|
|
116
|
+
*/
|
|
117
|
+
sliderDirective: Directive;
|
|
118
|
+
/**
|
|
119
|
+
* Directive to get the minLabel elementRef
|
|
120
|
+
*/
|
|
121
|
+
minLabelDirective: Directive;
|
|
122
|
+
/**
|
|
123
|
+
* Directive to get the maxLabel elementRef
|
|
124
|
+
*/
|
|
125
|
+
maxLabelDirective: Directive;
|
|
126
|
+
}
|
|
127
|
+
export interface SliderActions {
|
|
128
|
+
/**
|
|
129
|
+
* Method to handle click on the slider
|
|
130
|
+
* @param event - mouse event
|
|
131
|
+
*/
|
|
132
|
+
click(event: MouseEvent): void;
|
|
133
|
+
/**
|
|
134
|
+
* Method to process the keyboard event
|
|
135
|
+
* @param event - keyboard event object
|
|
136
|
+
* @param handleNumber - id of the modified handle
|
|
137
|
+
*/
|
|
138
|
+
keydown(event: KeyboardEvent, handleNumber: number): void;
|
|
139
|
+
/**
|
|
140
|
+
* Method describing the behavior of the slider handle on mouse down event
|
|
141
|
+
* @param event - mouse event
|
|
142
|
+
* @param handleId - numeric id of the handle
|
|
143
|
+
*/
|
|
144
|
+
mouseDown(event: MouseEvent, handleId: number): void;
|
|
145
|
+
}
|
|
146
|
+
export type SliderWidget = Widget<SliderProps, SliderState, SliderApi, SliderActions, SliderDirectives>;
|
|
147
|
+
/**
|
|
148
|
+
* Returns a shallow copy of the default slider config.
|
|
149
|
+
* @returns a copy of the default config
|
|
150
|
+
*/
|
|
151
|
+
export declare function getSliderDefaultConfig(): {
|
|
152
|
+
/**
|
|
153
|
+
* An event emitted when slider values are changed
|
|
154
|
+
*
|
|
155
|
+
* Event payload equals to the updated slider values
|
|
156
|
+
*/
|
|
157
|
+
onValuesChange: (values: number[]) => void;
|
|
158
|
+
/**
|
|
159
|
+
* Minimum value that can be assigned to the slider
|
|
160
|
+
*/
|
|
161
|
+
min: number;
|
|
162
|
+
/**
|
|
163
|
+
* Maximum value that can be assigned to the slider
|
|
164
|
+
*/
|
|
165
|
+
max: number;
|
|
166
|
+
/**
|
|
167
|
+
* Unit value between slider steps
|
|
168
|
+
*/
|
|
169
|
+
stepSize: number;
|
|
170
|
+
/**
|
|
171
|
+
* If `true` slider value cannot be changed but the slider is still focusable
|
|
172
|
+
*/
|
|
173
|
+
readonly: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* If `true` slider value cannot be changed and the slider cannot be focused
|
|
176
|
+
*/
|
|
177
|
+
disabled: boolean;
|
|
178
|
+
/**
|
|
179
|
+
* If `true` is vertically positioned otherwise it is horizontal
|
|
180
|
+
*/
|
|
181
|
+
vertical: boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Current slider values
|
|
184
|
+
*/
|
|
185
|
+
values: number[];
|
|
186
|
+
className: string;
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Create a slider widget with given config props
|
|
190
|
+
* @param config - an optional slider config
|
|
191
|
+
* @returns a SliderWidget
|
|
192
|
+
*/
|
|
193
|
+
export declare function createSlider(config?: PropsConfig<SliderProps>): SliderWidget;
|
package/slider.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { computed, derived, writable } from '@amadeus-it-group/tansu';
|
|
2
|
+
import { bindableDerived, createStoreDirective, writablesForProps } from './services';
|
|
3
|
+
import { stateStores } from './services/stores';
|
|
4
|
+
import { typeArray, typeBoolean, typeFunction, typeNumber } from './services/writables';
|
|
5
|
+
import { noop } from './utils';
|
|
6
|
+
const defaultSliderConfig = {
|
|
7
|
+
min: 0,
|
|
8
|
+
max: 100,
|
|
9
|
+
stepSize: 1,
|
|
10
|
+
readonly: false,
|
|
11
|
+
disabled: false,
|
|
12
|
+
vertical: false,
|
|
13
|
+
className: '',
|
|
14
|
+
onValuesChange: noop,
|
|
15
|
+
values: [0],
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Returns a shallow copy of the default slider config.
|
|
19
|
+
* @returns a copy of the default config
|
|
20
|
+
*/
|
|
21
|
+
export function getSliderDefaultConfig() {
|
|
22
|
+
return { ...defaultSliderConfig };
|
|
23
|
+
}
|
|
24
|
+
const configValidator = {
|
|
25
|
+
min: typeNumber,
|
|
26
|
+
max: typeNumber,
|
|
27
|
+
stepSize: typeNumber,
|
|
28
|
+
readonly: typeBoolean,
|
|
29
|
+
disabled: typeBoolean,
|
|
30
|
+
vertical: typeBoolean,
|
|
31
|
+
onValuesChange: typeFunction,
|
|
32
|
+
values: typeArray,
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Create a slider widget with given config props
|
|
36
|
+
* @param config - an optional slider config
|
|
37
|
+
* @returns a SliderWidget
|
|
38
|
+
*/
|
|
39
|
+
export function createSlider(config) {
|
|
40
|
+
const [{
|
|
41
|
+
// dirty inputs that need adjustment:
|
|
42
|
+
min$: _dirtyMinimum$, max$: _dirtyMaximum$, stepSize$: _dirtyStepSize$, values$: _dirtyValues$, onValuesChange$, ...stateProps }, patch,] = writablesForProps(defaultSliderConfig, config, configValidator);
|
|
43
|
+
const { vertical$, disabled$, readonly$ } = stateProps;
|
|
44
|
+
let _prevCoordinate = -1;
|
|
45
|
+
// clean inputs adjustment
|
|
46
|
+
const min$ = computed(() => {
|
|
47
|
+
if (_dirtyMinimum$() === _dirtyMaximum$())
|
|
48
|
+
return defaultSliderConfig.min;
|
|
49
|
+
return Math.min(_dirtyMinimum$(), _dirtyMaximum$());
|
|
50
|
+
});
|
|
51
|
+
const max$ = computed(() => {
|
|
52
|
+
if (_dirtyMinimum$() === _dirtyMaximum$())
|
|
53
|
+
return defaultSliderConfig.max;
|
|
54
|
+
return Math.max(_dirtyMinimum$(), _dirtyMaximum$());
|
|
55
|
+
});
|
|
56
|
+
const stepSize$ = computed(() => _dirtyStepSize$() || defaultSliderConfig.stepSize);
|
|
57
|
+
const values$ = bindableDerived(onValuesChange$, [_dirtyValues$], ([dirtyValues]) => dirtyValues.map((dv) => computeCleanValue(dv)), (a, b) => a.every((val, index) => val === b[index]));
|
|
58
|
+
// computed
|
|
59
|
+
const { directive: sliderDirective, element$: sliderDom$ } = createStoreDirective();
|
|
60
|
+
const { directive: minLabelDirective, element$: minLabelDom$ } = createStoreDirective();
|
|
61
|
+
const { directive: maxLabelDirective, element$: maxLabelDom$ } = createStoreDirective();
|
|
62
|
+
const sliderResized$ = derived(sliderDom$, (sliderDom, set) => {
|
|
63
|
+
if (!sliderDom) {
|
|
64
|
+
set({});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
68
|
+
set({});
|
|
69
|
+
});
|
|
70
|
+
resizeObserver.observe(sliderDom);
|
|
71
|
+
return () => resizeObserver.disconnect();
|
|
72
|
+
}, {});
|
|
73
|
+
const updateSliderSize$ = writable({});
|
|
74
|
+
const sliderDomRect$ = computed(() => {
|
|
75
|
+
sliderResized$();
|
|
76
|
+
updateSliderSize$();
|
|
77
|
+
return sliderDom$()?.getBoundingClientRect() ?? {};
|
|
78
|
+
}, {
|
|
79
|
+
equal: Object.is,
|
|
80
|
+
});
|
|
81
|
+
const minLabelDomRect$ = computed(() => {
|
|
82
|
+
sliderResized$();
|
|
83
|
+
updateSliderSize$();
|
|
84
|
+
return minLabelDom$()?.getBoundingClientRect() ?? {};
|
|
85
|
+
}, {
|
|
86
|
+
equal: (a, b) => Object.is(a, b),
|
|
87
|
+
});
|
|
88
|
+
const maxLabelDomRect$ = computed(() => {
|
|
89
|
+
sliderResized$();
|
|
90
|
+
updateSliderSize$();
|
|
91
|
+
return maxLabelDom$()?.getBoundingClientRect() ?? {};
|
|
92
|
+
}, {
|
|
93
|
+
equal: (a, b) => Object.is(a, b),
|
|
94
|
+
});
|
|
95
|
+
const sliderDomRectOffset$ = computed(() => (vertical$() ? sliderDomRect$().top : sliderDomRect$().left));
|
|
96
|
+
const sliderDomRectSize$ = computed(() => (vertical$() ? sliderDomRect$().height : sliderDomRect$().width));
|
|
97
|
+
const sortedValues$ = computed(() => [...values$()].sort((a, b) => a - b));
|
|
98
|
+
const sortedHandles$ = computed(() => values$()
|
|
99
|
+
.map((val, index) => {
|
|
100
|
+
return { id: index, value: val };
|
|
101
|
+
})
|
|
102
|
+
.sort((a, b) => a.value - b.value));
|
|
103
|
+
const valuesPercent$ = computed(() => values$().map((val) => percentCompute(val)));
|
|
104
|
+
const sortedValuesPercent$ = computed(() => [...valuesPercent$()].sort((a, b) => a - b));
|
|
105
|
+
const minLabelWidth$ = computed(() => (minLabelDomRect$().width / sliderDomRectSize$()) * 100);
|
|
106
|
+
const maxLabelWidth$ = computed(() => (maxLabelDomRect$().width / sliderDomRectSize$()) * 100);
|
|
107
|
+
const minValueLabelDisplay$ = computed(() => (valuesPercent$().some((percent) => percent < minLabelWidth$() + 1) ? 'hidden' : 'visible'));
|
|
108
|
+
const maxValueLabelDisplay$ = computed(() => (valuesPercent$().some((percent) => percent > 100 - maxLabelWidth$() - 1) ? 'hidden' : 'visible'));
|
|
109
|
+
// TODO define the intersection value
|
|
110
|
+
const combinedLabelDisplay$ = computed(() => (values$().length == 2 && Math.abs(values$()[0] - values$()[1]) < 10 ? 'visible' : 'hidden'));
|
|
111
|
+
const isInteractable$ = computed(() => !disabled$() && !readonly$());
|
|
112
|
+
const combinedLabelPositionLeft$ = computed(() => vertical$() || sortedValuesPercent$().length != 2 ? 0 : (sortedValuesPercent$()[0] + sortedValuesPercent$()[1]) / 2);
|
|
113
|
+
const combinedLabelPositionTop$ = computed(() => vertical$() && sortedValuesPercent$().length == 2 ? 100 - (sortedValuesPercent$()[0] + sortedValuesPercent$()[1]) / 2 : 0);
|
|
114
|
+
// const handleTooltipLeft$ = computed(() => (vertical$() ? Array(valuesPercent$().length).fill(0) : valuesPercent$()));
|
|
115
|
+
// const handleTooltipTop$ = computed(() => (vertical$() ? valuesPercent$().map((vp) => 100 - vp) : Array(valuesPercent$().length).fill(0)));
|
|
116
|
+
const handleDisplayOptions$ = computed(() => valuesPercent$().map((vp) => {
|
|
117
|
+
return {
|
|
118
|
+
left: vertical$() ? 0 : vp,
|
|
119
|
+
top: vertical$() ? 100 - vp : 0,
|
|
120
|
+
};
|
|
121
|
+
}));
|
|
122
|
+
const progressDisplayOptions$ = computed(() => {
|
|
123
|
+
if (sortedValuesPercent$().length === 1) {
|
|
124
|
+
return [
|
|
125
|
+
{
|
|
126
|
+
left: 0,
|
|
127
|
+
bottom: 0,
|
|
128
|
+
width: vertical$() ? 100 : sortedValuesPercent$()[0],
|
|
129
|
+
height: vertical$() ? sortedValuesPercent$()[0] : 100,
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
return sortedValuesPercent$()
|
|
135
|
+
.map((svp, index, array) => {
|
|
136
|
+
return {
|
|
137
|
+
left: vertical$() ? 0 : svp,
|
|
138
|
+
bottom: vertical$() ? svp : 0,
|
|
139
|
+
width: vertical$() ? 100 : index === array.length - 1 ? svp : array[index + 1] - svp,
|
|
140
|
+
height: vertical$() ? (index === array.length - 1 ? svp : array[index + 1] - svp) : 100,
|
|
141
|
+
};
|
|
142
|
+
})
|
|
143
|
+
.slice(0, sortedValuesPercent$().length - 1);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
// functions
|
|
147
|
+
const computeCleanValue = (value) => {
|
|
148
|
+
if (value >= max$()) {
|
|
149
|
+
return max$();
|
|
150
|
+
}
|
|
151
|
+
else if (value <= min$()) {
|
|
152
|
+
return min$();
|
|
153
|
+
}
|
|
154
|
+
const indexMin = Math.floor(value / stepSize$());
|
|
155
|
+
return value % stepSize$() < stepSize$() / 2 ? indexMin * stepSize$() : (indexMin + 1) * stepSize$();
|
|
156
|
+
};
|
|
157
|
+
const percentCompute = (value) => {
|
|
158
|
+
return ((value - min$()) * 100) / (max$() - min$());
|
|
159
|
+
};
|
|
160
|
+
const getClosestSliderHandle = (clickedPercent) => {
|
|
161
|
+
if (values$().length === 1) {
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
const closestBigger = sortedValues$().find((sv) => sv > clickedPercent * 100);
|
|
165
|
+
const closestBiggerIndex = closestBigger ? sortedValues$().indexOf(closestBigger) : sortedValues$().length - 1;
|
|
166
|
+
const midPoint = sortedValues$()[closestBiggerIndex - 1] + (sortedValues$()[closestBiggerIndex] - sortedValues$()[closestBiggerIndex - 1]) / 2;
|
|
167
|
+
let closestValue;
|
|
168
|
+
if (clickedPercent * 100 <= midPoint) {
|
|
169
|
+
closestValue = sortedValues$()[closestBiggerIndex - 1];
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
closestValue = sortedValues$()[closestBiggerIndex];
|
|
173
|
+
}
|
|
174
|
+
return values$().indexOf(closestValue);
|
|
175
|
+
};
|
|
176
|
+
const adjustCoordinate = (clickedCoordinate, handleNumber) => {
|
|
177
|
+
if (isInteractable$()) {
|
|
178
|
+
const clickedPercent = vertical$()
|
|
179
|
+
? (sliderDomRectSize$() - clickedCoordinate + sliderDomRectOffset$()) / sliderDomRectSize$()
|
|
180
|
+
: (clickedCoordinate - sliderDomRectOffset$()) / sliderDomRectSize$();
|
|
181
|
+
const derivedHandleIndex = handleNumber ?? getClosestSliderHandle(clickedPercent);
|
|
182
|
+
const newValue = clickedPercent * (max$() - min$()) + min$();
|
|
183
|
+
_dirtyValues$.update((dh) => {
|
|
184
|
+
dh = [...dh];
|
|
185
|
+
dh[derivedHandleIndex] = newValue;
|
|
186
|
+
return dh;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
...stateStores({
|
|
192
|
+
min$,
|
|
193
|
+
max$,
|
|
194
|
+
stepSize$,
|
|
195
|
+
values$,
|
|
196
|
+
sortedValues$,
|
|
197
|
+
sortedHandles$,
|
|
198
|
+
minValueLabelDisplay$,
|
|
199
|
+
maxValueLabelDisplay$,
|
|
200
|
+
combinedLabelDisplay$,
|
|
201
|
+
isInteractable$,
|
|
202
|
+
combinedLabelPositionLeft$,
|
|
203
|
+
combinedLabelPositionTop$,
|
|
204
|
+
progressDisplayOptions$,
|
|
205
|
+
handleDisplayOptions$,
|
|
206
|
+
...stateProps,
|
|
207
|
+
}),
|
|
208
|
+
patch,
|
|
209
|
+
api: {},
|
|
210
|
+
directives: {
|
|
211
|
+
sliderDirective,
|
|
212
|
+
minLabelDirective,
|
|
213
|
+
maxLabelDirective,
|
|
214
|
+
},
|
|
215
|
+
actions: {
|
|
216
|
+
click(event) {
|
|
217
|
+
updateSliderSize$.set({});
|
|
218
|
+
adjustCoordinate(vertical$() ? event.clientY : event.clientX);
|
|
219
|
+
},
|
|
220
|
+
keydown(event, handleIndex) {
|
|
221
|
+
const { key } = event;
|
|
222
|
+
if (isInteractable$()) {
|
|
223
|
+
switch (key) {
|
|
224
|
+
case 'ArrowDown':
|
|
225
|
+
case 'ArrowLeft':
|
|
226
|
+
_dirtyValues$.update((value) => {
|
|
227
|
+
value = [...value];
|
|
228
|
+
value[handleIndex] = values$()[handleIndex] - stepSize$();
|
|
229
|
+
return value;
|
|
230
|
+
});
|
|
231
|
+
break;
|
|
232
|
+
case 'ArrowUp':
|
|
233
|
+
case 'ArrowRight':
|
|
234
|
+
_dirtyValues$.update((value) => {
|
|
235
|
+
value = [...value];
|
|
236
|
+
value[handleIndex] = values$()[handleIndex] + stepSize$();
|
|
237
|
+
return value;
|
|
238
|
+
});
|
|
239
|
+
break;
|
|
240
|
+
case 'Home':
|
|
241
|
+
_dirtyValues$.update((value) => {
|
|
242
|
+
value = [...value];
|
|
243
|
+
value[handleIndex] = min$();
|
|
244
|
+
return value;
|
|
245
|
+
});
|
|
246
|
+
break;
|
|
247
|
+
case 'End':
|
|
248
|
+
_dirtyValues$.update((value) => {
|
|
249
|
+
value = [...value];
|
|
250
|
+
value[handleIndex] = max$();
|
|
251
|
+
return value;
|
|
252
|
+
});
|
|
253
|
+
break;
|
|
254
|
+
case 'PageUp':
|
|
255
|
+
// TODO it is optional in accessibility guidelines, so define the skip value for steps and write unit test
|
|
256
|
+
break;
|
|
257
|
+
case 'PageDown':
|
|
258
|
+
// TODO it is optional in accessibility guidelines, so define the skip value for steps and write unit test
|
|
259
|
+
break;
|
|
260
|
+
default:
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
event.preventDefault();
|
|
264
|
+
event.stopPropagation();
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
mouseDown(event, handleId) {
|
|
268
|
+
event.preventDefault();
|
|
269
|
+
const handleDrag = (e) => {
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
const newCoord = vertical$() ? e.clientY : e.clientX;
|
|
272
|
+
event.target.focus();
|
|
273
|
+
if (_prevCoordinate !== newCoord) {
|
|
274
|
+
_prevCoordinate = newCoord;
|
|
275
|
+
adjustCoordinate(newCoord, handleId);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
if (isInteractable$()) {
|
|
279
|
+
event.target.focus();
|
|
280
|
+
document.addEventListener('mousemove', handleDrag);
|
|
281
|
+
// TODO mouse up doesn't work outside the handle area
|
|
282
|
+
document.addEventListener('mouseup', () => {
|
|
283
|
+
document.removeEventListener('mousemove', handleDrag);
|
|
284
|
+
}, { once: true });
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|