@flux-ui/components 3.0.0-next.72 → 3.0.0-next.74
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/dist/component/FluxFormField.vue.d.ts +2 -2
- package/dist/component/FluxKanbanColumn.vue.d.ts +1 -1
- package/dist/component/FluxSegmentedControl.vue.d.ts +18 -8
- package/dist/component/FluxSegmentedControlItem.vue.d.ts +20 -0
- package/dist/component/FluxSpacing.vue.d.ts +2 -1
- package/dist/component/index.d.ts +1 -1
- package/dist/component/primitive/FilterMenuRenderer.d.ts +3 -3
- package/dist/composable/index.d.ts +1 -0
- package/dist/composable/useSegmentedControlInjection.d.ts +2 -0
- package/dist/data/di.d.ts +10 -1
- package/dist/index.css +398 -368
- package/dist/index.js +1264 -1164
- package/dist/index.js.map +1 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/sanitizeUrl.d.ts +7 -0
- package/package.json +9 -9
- package/src/component/FluxBoxedIcon.vue +1 -1
- package/src/component/FluxCalendar.vue +6 -6
- package/src/component/FluxColorPicker.vue +6 -6
- package/src/component/FluxDropZone.vue +0 -1
- package/src/component/FluxDynamicView.vue +2 -2
- package/src/component/FluxExpandableGroup.vue +0 -1
- package/src/component/FluxFilterBase.vue +11 -11
- package/src/component/FluxFormField.vue +4 -2
- package/src/component/FluxFormFieldAddition.vue +2 -2
- package/src/component/FluxInfo.vue +2 -2
- package/src/component/FluxInfoStack.vue +2 -2
- package/src/component/FluxKanbanColumn.vue +3 -5
- package/src/component/FluxLayerPane.vue +1 -1
- package/src/component/FluxPagination.vue +4 -4
- package/src/component/FluxPressable.vue +14 -5
- package/src/component/FluxSegmentedControl.vue +64 -67
- package/src/component/FluxSegmentedControlItem.vue +98 -0
- package/src/component/FluxSpacing.vue +5 -1
- package/src/component/FluxSplitButton.vue +2 -2
- package/src/component/FluxTable.vue +3 -1
- package/src/component/FluxToolbar.vue +1 -1
- package/src/component/index.ts +1 -1
- package/src/component/primitive/DialogLayout.vue +3 -3
- package/src/component/primitive/FilterBadge.vue +11 -2
- package/src/component/primitive/FilterMenuRenderer.ts +4 -4
- package/src/composable/index.ts +1 -0
- package/src/composable/useSegmentedControlInjection.ts +13 -0
- package/src/css/component/Form.module.scss +2 -2
- package/src/css/component/SegmentedControl.module.scss +53 -24
- package/src/css/component/Spinner.module.scss +1 -0
- package/src/css/component/Visual.module.scss +1 -0
- package/src/css/mixin/tree-node.scss +3 -3
- package/src/data/di.ts +13 -1
- package/src/data/iconRegistry.ts +1 -1
- package/src/util/createDialogRenderer.ts +1 -1
- package/src/util/index.ts +1 -0
- package/src/util/sanitizeUrl.ts +40 -0
- package/dist/component/FluxSegmentedView.vue.d.ts +0 -9
- package/src/component/FluxSegmentedView.vue +0 -15
|
@@ -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:
|
|
34
|
+
readonly size: Enumerate<typeof spacings['length']>;
|
|
31
35
|
}>();
|
|
32
36
|
</script>
|
|
@@ -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">
|
package/src/component/index.ts
CHANGED
|
@@ -130,7 +130,7 @@ export { default as FluxScroller } from './FluxScroller.vue';
|
|
|
130
130
|
export { default as FluxSecondaryButton } from './FluxSecondaryButton.vue';
|
|
131
131
|
export { default as FluxSecondaryLinkButton } from './FluxSecondaryLinkButton.vue';
|
|
132
132
|
export { default as FluxSegmentedControl } from './FluxSegmentedControl.vue';
|
|
133
|
-
export { default as
|
|
133
|
+
export { default as FluxSegmentedControlItem } from './FluxSegmentedControlItem.vue';
|
|
134
134
|
export { default as FluxSeparator } from './FluxSeparator.vue';
|
|
135
135
|
export { default as FluxSlideOver } from './FluxSlideOver.vue';
|
|
136
136
|
export { default as FluxSnackbar } from './FluxSnackbar.vue';
|
|
@@ -37,7 +37,16 @@
|
|
|
37
37
|
emit('click', evt);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
watch([() => item, () => value], async () => {
|
|
41
|
-
|
|
40
|
+
watch([() => item, () => value], async ([, nextValue], _prev, onCleanup) => {
|
|
41
|
+
let cancelled = false;
|
|
42
|
+
onCleanup(() => {
|
|
43
|
+
cancelled = true;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const nextLabel = await unref(getValueLabel)(nextValue);
|
|
47
|
+
|
|
48
|
+
if (!cancelled) {
|
|
49
|
+
valueLabel.value = nextLabel ?? undefined;
|
|
50
|
+
}
|
|
42
51
|
}, {deep: true, immediate: true});
|
|
43
52
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FluxFilterDefinition, FluxFilterValue } from '@flux-ui/types';
|
|
2
|
-
import { defineComponent, h, isVNode, type
|
|
2
|
+
import { defineComponent, h, isVNode, type PropType, unref, type VNode } from 'vue';
|
|
3
3
|
import { useFilterInjection } from '~flux/components/composable';
|
|
4
4
|
import FluxMenuGroup from '../FluxMenuGroup.vue';
|
|
5
5
|
import FluxSeparator from '../FluxSeparator.vue';
|
|
@@ -8,7 +8,7 @@ import FilterItem from './FilterItem.vue';
|
|
|
8
8
|
export const FilterMenuRenderer = defineComponent({
|
|
9
9
|
props: {
|
|
10
10
|
menuItems: {required: true, type: Array},
|
|
11
|
-
navigate: {required: true, type: Function}
|
|
11
|
+
navigate: {required: true, type: Function as PropType<(name: string) => void>}
|
|
12
12
|
},
|
|
13
13
|
|
|
14
14
|
setup(props) {
|
|
@@ -18,7 +18,7 @@ export const FilterMenuRenderer = defineComponent({
|
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
function renderFilterGroup(group: (FluxFilterDefinition | VNode)[], index: number, navigate:
|
|
21
|
+
function renderFilterGroup(group: (FluxFilterDefinition | VNode)[], index: number, navigate: (name: string) => void, state: Record<string, FluxFilterValue>): VNode[] {
|
|
22
22
|
const slot: VNode[] = [];
|
|
23
23
|
|
|
24
24
|
if (index > 0) {
|
|
@@ -32,7 +32,7 @@ function renderFilterGroup(group: (FluxFilterDefinition | VNode)[], index: numbe
|
|
|
32
32
|
return slot;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function renderFilterItem(item: FluxFilterDefinition | VNode, navigate:
|
|
35
|
+
function renderFilterItem(item: FluxFilterDefinition | VNode, navigate: (name: string) => void, state: Record<string, FluxFilterValue>): VNode {
|
|
36
36
|
if (isVNode(item)) {
|
|
37
37
|
return item;
|
|
38
38
|
}
|
package/src/composable/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { default as useFilterInjection } from './useFilterInjection';
|
|
|
9
9
|
export { default as useFlyoutInjection } from './useFlyoutInjection';
|
|
10
10
|
export { default as useFormFieldInjection } from './useFormFieldInjection';
|
|
11
11
|
export { default as useKanbanInjection } from './useKanbanInjection';
|
|
12
|
+
export { default as useSegmentedControlInjection } from './useSegmentedControlInjection';
|
|
12
13
|
export { default as useTabBarInjection } from './useTabBarInjection';
|
|
13
14
|
export { default as useTableInjection } from './useTableInjection';
|
|
14
15
|
export { default as useTooltipInjection } from './useTooltipInjection';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FluxSize } from '@flux-ui/types';
|
|
2
|
+
import { inject, ref } from 'vue';
|
|
3
|
+
import { FluxSegmentedControlInjectionKey, type FluxSegmentedControlValue } from '~flux/components/data';
|
|
4
|
+
|
|
5
|
+
export default function () {
|
|
6
|
+
return inject(FluxSegmentedControlInjectionKey, {
|
|
7
|
+
modelValue: ref<FluxSegmentedControlValue | undefined>(undefined),
|
|
8
|
+
size: ref<FluxSize>('medium'),
|
|
9
|
+
select: () => undefined,
|
|
10
|
+
registerItem: () => undefined,
|
|
11
|
+
unregisterItem: () => undefined
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -90,8 +90,8 @@
|
|
|
90
90
|
|
|
91
91
|
.formFieldHeader {
|
|
92
92
|
display: flex;
|
|
93
|
-
margin-bottom: 0;
|
|
94
93
|
width: 100%;
|
|
94
|
+
margin-bottom: 0;
|
|
95
95
|
align-items: center;
|
|
96
96
|
align-self: flex-start;
|
|
97
97
|
gap: 6px;
|
|
@@ -274,9 +274,9 @@
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
&::-webkit-color-swatch-wrapper {
|
|
277
|
+
width: calc(100% + 24px);
|
|
277
278
|
margin: 0 -12px;
|
|
278
279
|
padding: 3px;
|
|
279
|
-
width: calc(100% + 24px);
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
.segmentedControl {
|
|
4
4
|
position: relative;
|
|
5
5
|
align-items: center;
|
|
6
|
-
gap:
|
|
6
|
+
gap: 3px;
|
|
7
|
+
padding: 3px;
|
|
7
8
|
background: var(--gray-50);
|
|
8
9
|
border: 1px solid var(--surface-stroke);
|
|
9
|
-
border-radius: var(--radius);
|
|
10
|
+
border-radius: var(--radius-full);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
.segmentedControlFill {
|
|
@@ -24,60 +25,88 @@
|
|
|
24
25
|
|
|
25
26
|
.segmentedControlHighlight {
|
|
26
27
|
position: absolute;
|
|
27
|
-
top:
|
|
28
|
-
bottom:
|
|
28
|
+
top: 3px;
|
|
29
|
+
bottom: 3px;
|
|
29
30
|
background: var(--surface);
|
|
30
|
-
border-radius:
|
|
31
|
-
box-shadow: var(--shadow-
|
|
31
|
+
border-radius: var(--radius-full);
|
|
32
|
+
box-shadow: var(--shadow-md);
|
|
32
33
|
outline: 1px solid var(--surface-stroke);
|
|
33
34
|
pointer-events: none;
|
|
34
35
|
transition: 300ms var(--swift-out);
|
|
35
36
|
transition-property: left, width;
|
|
36
|
-
translate: -1px 0;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
.segmentedControlItem {
|
|
40
|
+
position: relative;
|
|
40
41
|
display: flex;
|
|
41
|
-
height: 36px;
|
|
42
|
-
padding-left: 12px;
|
|
43
|
-
padding-right: 12px;
|
|
44
42
|
align-items: center;
|
|
45
43
|
flex: 1 1 0;
|
|
46
|
-
gap: 9px;
|
|
47
44
|
justify-content: center;
|
|
48
45
|
background: none;
|
|
49
46
|
border: 0;
|
|
50
|
-
border-radius: var(--radius);
|
|
47
|
+
border-radius: var(--radius-full);
|
|
51
48
|
color: var(--foreground);
|
|
52
49
|
cursor: pointer;
|
|
53
50
|
font-weight: 500;
|
|
54
51
|
text-align: center;
|
|
55
|
-
transition: 300ms var(--swift-out);
|
|
56
|
-
|
|
52
|
+
transition: color 300ms var(--swift-out);
|
|
53
|
+
white-space: nowrap;
|
|
57
54
|
|
|
58
55
|
@include mixin.hover {
|
|
59
|
-
|
|
56
|
+
color: var(--foreground-prominent);
|
|
60
57
|
}
|
|
61
58
|
|
|
59
|
+
@include mixin.focus-ring(3px);
|
|
60
|
+
|
|
62
61
|
&.isActive {
|
|
63
|
-
background: none;
|
|
64
62
|
color: var(--foreground-prominent);
|
|
65
63
|
cursor: default;
|
|
66
64
|
}
|
|
67
65
|
|
|
66
|
+
&:disabled {
|
|
67
|
+
opacity: .5;
|
|
68
|
+
pointer-events: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Houd de inhoud boven de zwevende highlight.
|
|
68
72
|
> * {
|
|
69
73
|
position: relative;
|
|
74
|
+
z-index: 1;
|
|
70
75
|
}
|
|
71
|
-
}
|
|
72
76
|
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
// Verticale divider tussen twee aangrenzende inactieve items.
|
|
78
|
+
& + &::before {
|
|
79
|
+
content: '';
|
|
80
|
+
position: absolute;
|
|
81
|
+
top: 50%;
|
|
82
|
+
left: -2px;
|
|
83
|
+
height: 15px;
|
|
84
|
+
width: 1px;
|
|
85
|
+
background: var(--surface-stroke);
|
|
86
|
+
transition: opacity 300ms var(--swift-out);
|
|
87
|
+
translate: 0 -50%;
|
|
88
|
+
}
|
|
79
89
|
|
|
80
|
-
&.isActive
|
|
90
|
+
&.isActive::before,
|
|
91
|
+
&.isActive + &::before {
|
|
81
92
|
opacity: 0;
|
|
82
93
|
}
|
|
83
94
|
}
|
|
95
|
+
|
|
96
|
+
.isSmall {
|
|
97
|
+
height: 30px;
|
|
98
|
+
padding: 0 12px;
|
|
99
|
+
gap: 9px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.isMedium {
|
|
103
|
+
height: 36px;
|
|
104
|
+
padding: 0 15px;
|
|
105
|
+
gap: 9px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.isLarge {
|
|
109
|
+
height: 48px;
|
|
110
|
+
padding: 0 21px;
|
|
111
|
+
gap: 12px;
|
|
112
|
+
}
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
pointer-events: none;
|
|
16
16
|
|
|
17
17
|
&.hasLine::before {
|
|
18
|
-
content: '';
|
|
19
18
|
position: absolute;
|
|
20
19
|
top: 0;
|
|
21
20
|
bottom: 0;
|
|
22
21
|
left: 9px;
|
|
23
22
|
width: 1px;
|
|
23
|
+
content: '';
|
|
24
24
|
background: var(--surface-stroke);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -35,24 +35,24 @@
|
|
|
35
35
|
|
|
36
36
|
// Non-last siblings: full-height vertical continuation line
|
|
37
37
|
&:not(.isLast)::before {
|
|
38
|
-
content: '';
|
|
39
38
|
position: absolute;
|
|
40
39
|
top: 0;
|
|
41
40
|
bottom: 0;
|
|
42
41
|
left: 9px;
|
|
43
42
|
width: 1px;
|
|
43
|
+
content: '';
|
|
44
44
|
background: var(--surface-stroke);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Rounded elbow: vertical segment from top to midpoint, curves right to expand area
|
|
48
48
|
// Overlaps the ::before vertical line for non-last nodes (same position, same color)
|
|
49
49
|
&::after {
|
|
50
|
-
content: '';
|
|
51
50
|
position: absolute;
|
|
52
51
|
top: 0;
|
|
53
52
|
bottom: 50%;
|
|
54
53
|
left: 9px;
|
|
55
54
|
right: 0;
|
|
55
|
+
content: '';
|
|
56
56
|
border-left: 1px solid var(--surface-stroke);
|
|
57
57
|
border-bottom: 1px solid var(--surface-stroke);
|
|
58
58
|
border-bottom-left-radius: 6px;
|
package/src/data/di.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FluxFilterDefinition, FluxFilterState, FluxFilterValue } from '@flux-ui/types';
|
|
1
|
+
import type { FluxFilterDefinition, FluxFilterState, FluxFilterValue, FluxSize } from '@flux-ui/types';
|
|
2
2
|
import type { DateTime } from 'luxon';
|
|
3
3
|
import type { ComponentInternalInstance, ComputedRef, InjectionKey, Ref, VNode } from 'vue';
|
|
4
4
|
|
|
@@ -10,6 +10,7 @@ export const FluxExpandableGroupInjectionKey: InjectionKey<FluxExpandableGroupIn
|
|
|
10
10
|
export const FluxFlyoutInjectionKey: InjectionKey<FluxFlyoutInjection> = Symbol();
|
|
11
11
|
export const FluxFilterInjectionKey: InjectionKey<FluxFilterInjection> = Symbol();
|
|
12
12
|
export const FluxFormFieldInjectionKey: InjectionKey<FluxFormFieldInjection> = Symbol();
|
|
13
|
+
export const FluxSegmentedControlInjectionKey: InjectionKey<FluxSegmentedControlInjection> = Symbol();
|
|
13
14
|
export const FluxSplitViewInjectionKey: InjectionKey<FluxSplitViewInjection> = Symbol();
|
|
14
15
|
export const FluxTabBarInjectionKey: InjectionKey<FluxTabBarInjection> = Symbol();
|
|
15
16
|
export const FluxTableInjectionKey: InjectionKey<FluxTableInjection> = Symbol();
|
|
@@ -168,6 +169,17 @@ export type FluxSplitViewInjection = {
|
|
|
168
169
|
getPaneIndex(id: number): number;
|
|
169
170
|
};
|
|
170
171
|
|
|
172
|
+
export type FluxSegmentedControlValue = string | number;
|
|
173
|
+
|
|
174
|
+
export type FluxSegmentedControlInjection = {
|
|
175
|
+
readonly modelValue: Ref<FluxSegmentedControlValue | undefined>;
|
|
176
|
+
readonly size: Ref<FluxSize>;
|
|
177
|
+
|
|
178
|
+
select(value: FluxSegmentedControlValue): void;
|
|
179
|
+
registerItem(element: HTMLElement, value: FluxSegmentedControlValue): void;
|
|
180
|
+
unregisterItem(element: HTMLElement): void;
|
|
181
|
+
};
|
|
182
|
+
|
|
171
183
|
export type FluxTabBarInjection = {
|
|
172
184
|
readonly isPills: Ref<boolean>;
|
|
173
185
|
|
package/src/data/iconRegistry.ts
CHANGED
|
@@ -17,7 +17,7 @@ export function fluxRegisterIcons(icons: Icons): void {
|
|
|
17
17
|
acc[iconName as FluxIconName] = icon;
|
|
18
18
|
|
|
19
19
|
if (Array.isArray(icon[2])) {
|
|
20
|
-
icon[2].forEach((
|
|
20
|
+
icon[2].forEach((alias: string) => acc[alias as FluxIconName] = icon);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
return acc;
|
|
@@ -14,7 +14,7 @@ let DIALOG_ID = 0;
|
|
|
14
14
|
|
|
15
15
|
export default function (attrs: object, props: Props, emit: Emit, slots: Slots, className: string, transition: Component): RenderFunction {
|
|
16
16
|
const dialogId = `flux-dialog:${DIALOG_ID++}`;
|
|
17
|
-
let unregister:
|
|
17
|
+
let unregister: VoidFunction | null = null;
|
|
18
18
|
let zIndex = 0;
|
|
19
19
|
|
|
20
20
|
const dialogRef = ref<HTMLElement>();
|
package/src/util/index.ts
CHANGED
|
@@ -2,3 +2,4 @@ export { default as createDialogRenderer } from './createDialogRenderer';
|
|
|
2
2
|
export { default as createLabelForDateRange } from './createLabelForDateRange';
|
|
3
3
|
export { default as defineFilter, type FluxFilterDefinitionFactory } from './defineFilter';
|
|
4
4
|
export { generateMultiOptionsLabel, isFluxFilterOptionHeader, isFluxFilterOptionItem, isResettable, pickFilterCommon } from './filter';
|
|
5
|
+
export { default as sanitizeUrl } from './sanitizeUrl';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const DANGEROUS_PROTOCOL = /^(javascript|vbscript|data):/i;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Removes control characters (and the Unicode line/paragraph separators) that
|
|
5
|
+
* browsers ignore inside URLs but which can be used to obfuscate the scheme,
|
|
6
|
+
* e.g. `java\tscript:alert(1)`.
|
|
7
|
+
*/
|
|
8
|
+
function stripControlChars(value: string): string {
|
|
9
|
+
let out = '';
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < value.length; i++) {
|
|
12
|
+
const code = value.charCodeAt(i);
|
|
13
|
+
|
|
14
|
+
if (code <= 0x20 || (code >= 0x7f && code <= 0x9f) || code === 0x2028 || code === 0x2029) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
out += value[i];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Sanitizes a URL intended for an `href` attribute. Returns `undefined` when the
|
|
26
|
+
* URL uses a dangerous scheme (`javascript:`, `vbscript:`, `data:`) so the attribute
|
|
27
|
+
* is omitted instead of becoming a script-execution or data-injection vector.
|
|
28
|
+
* Safe and relative URLs are returned unchanged.
|
|
29
|
+
*/
|
|
30
|
+
export default function (href?: string): string | undefined {
|
|
31
|
+
if (!href) {
|
|
32
|
+
return href;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (DANGEROUS_PROTOCOL.test(stripControlChars(href))) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return href;
|
|
40
|
+
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { DefineComponent, ExtractPropTypes, VNode, RendererNode, RendererElement, ComponentOptionsMixin, PublicProps, ComponentProvideOptions } from 'vue';
|
|
2
|
-
declare const _default: DefineComponent<ExtractPropTypes<{
|
|
3
|
-
index: NumberConstructor;
|
|
4
|
-
}>, () => VNode<RendererNode, RendererElement, {
|
|
5
|
-
[key: string]: any;
|
|
6
|
-
}>, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly< ExtractPropTypes<{
|
|
7
|
-
index: NumberConstructor;
|
|
8
|
-
}>> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
|
|
9
|
-
export default _default;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { defineComponent } from 'vue';
|
|
3
|
-
|
|
4
|
-
export default defineComponent({
|
|
5
|
-
props: {
|
|
6
|
-
index: Number
|
|
7
|
-
},
|
|
8
|
-
setup(props, {slots}) {
|
|
9
|
-
return () => {
|
|
10
|
-
const items = slots.default!();
|
|
11
|
-
return items[props.index!];
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
</script>
|