@flux-ui/components 3.1.2 → 3.1.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/dist/component/FluxAvatarGroup.vue.d.ts +17 -0
- package/dist/component/FluxButton.vue.d.ts +2 -0
- package/dist/component/FluxContextMenu.vue.d.ts +26 -0
- package/dist/component/FluxDataTable.vue.d.ts +20 -10
- package/dist/component/FluxDescriptionItem.vue.d.ts +19 -0
- package/dist/component/FluxDescriptionList.vue.d.ts +17 -0
- package/dist/component/FluxFlyout.vue.d.ts +9 -2
- package/dist/component/FluxFormCombobox.vue.d.ts +20 -0
- package/dist/component/FluxFormRating.vue.d.ts +21 -0
- package/dist/component/FluxFormTagsInput.vue.d.ts +27 -0
- package/dist/component/FluxFormTextArea.vue.d.ts +6 -1
- package/dist/component/FluxInlineEdit.vue.d.ts +41 -0
- package/dist/component/FluxMenu.vue.d.ts +1 -0
- package/dist/component/FluxMenuFlyout.vue.d.ts +22 -0
- package/dist/component/FluxTableCell.vue.d.ts +1 -0
- package/dist/component/FluxTour.vue.d.ts +35 -0
- package/dist/component/FluxTourItem.vue.d.ts +18 -0
- package/dist/component/FluxVirtualScroller.vue.d.ts +27 -0
- package/dist/component/index.d.ts +12 -0
- package/dist/component/primitive/AnchorPopup.vue.d.ts +7 -1
- package/dist/component/primitive/SelectBase.vue.d.ts +3 -0
- package/dist/composable/private/index.d.ts +1 -0
- package/dist/composable/private/useMenuFlyout.d.ts +42 -0
- package/dist/data/di.d.ts +35 -0
- package/dist/data/i18n.d.ts +7 -0
- package/dist/index.css +449 -5
- package/dist/index.js +2156 -408
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/component/FluxAvatarGroup.vue +52 -0
- package/src/component/FluxButton.vue +3 -0
- package/src/component/FluxContextMenu.vue +134 -0
- package/src/component/FluxDataTable.vue +113 -32
- package/src/component/FluxDescriptionItem.vue +43 -0
- package/src/component/FluxDescriptionList.vue +37 -0
- package/src/component/FluxDestructiveButton.vue +2 -1
- package/src/component/FluxFlyout.vue +16 -3
- package/src/component/FluxFormCombobox.vue +98 -0
- package/src/component/FluxFormRating.vue +172 -0
- package/src/component/FluxFormTagsInput.vue +249 -0
- package/src/component/FluxFormTextArea.vue +16 -1
- package/src/component/FluxInlineEdit.vue +176 -0
- package/src/component/FluxMenu.vue +13 -3
- package/src/component/FluxMenuFlyout.vue +118 -0
- package/src/component/FluxPrimaryButton.vue +2 -1
- package/src/component/FluxPrimaryLinkButton.vue +2 -1
- package/src/component/FluxPublishButton.vue +2 -1
- package/src/component/FluxSecondaryButton.vue +2 -1
- package/src/component/FluxSecondaryLinkButton.vue +2 -1
- package/src/component/FluxTableCell.vue +2 -0
- package/src/component/FluxTour.vue +332 -0
- package/src/component/FluxTourItem.vue +27 -0
- package/src/component/FluxVirtualScroller.vue +96 -0
- package/src/component/index.ts +12 -0
- package/src/component/primitive/AnchorPopup.vue +27 -0
- package/src/component/primitive/SelectBase.vue +37 -2
- package/src/composable/private/index.ts +1 -0
- package/src/composable/private/useMenuFlyout.ts +417 -0
- package/src/css/component/AvatarGroup.module.scss +22 -0
- package/src/css/component/ContextMenu.module.scss +17 -0
- package/src/css/component/DescriptionList.module.scss +98 -0
- package/src/css/component/Form.module.scss +51 -0
- package/src/css/component/FormRating.module.scss +47 -0
- package/src/css/component/InlineEdit.module.scss +45 -0
- package/src/css/component/Menu.module.scss +4 -1
- package/src/css/component/MenuFlyout.module.scss +38 -0
- package/src/css/component/Table.module.scss +16 -0
- package/src/css/component/Tour.module.scss +108 -0
- package/src/css/component/VirtualScroller.module.scss +17 -0
- package/src/css/mixin/button-active.scss +3 -1
- package/src/data/di.ts +40 -0
- package/src/data/i18n.ts +7 -0
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.1.
|
|
4
|
+
"version": "3.1.4",
|
|
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.
|
|
59
|
-
"@basmilius/utils": "^3.
|
|
60
|
-
"@flux-ui/internals": "3.1.
|
|
61
|
-
"@flux-ui/types": "3.1.
|
|
58
|
+
"@basmilius/common": "^3.43.0",
|
|
59
|
+
"@basmilius/utils": "^3.43.0",
|
|
60
|
+
"@flux-ui/internals": "3.1.4",
|
|
61
|
+
"@flux-ui/types": "3.1.4",
|
|
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.
|
|
69
|
+
"vue": "^3.6.0-beta.16"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@basmilius/vite-preset": "^3.
|
|
72
|
+
"@basmilius/vite-preset": "^3.43.0",
|
|
73
73
|
"@types/lodash-es": "^4.17.12",
|
|
74
74
|
"@types/luxon": "^3.7.1",
|
|
75
75
|
"@types/node": "^25.9.3",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="$style.avatarGroup"
|
|
4
|
+
:style="{
|
|
5
|
+
fontSize: `${size}px`,
|
|
6
|
+
'--overlap': overlap
|
|
7
|
+
}">
|
|
8
|
+
<component :is="renderVisible"/>
|
|
9
|
+
|
|
10
|
+
<span
|
|
11
|
+
v-if="overflowCount > 0"
|
|
12
|
+
:class="$style.avatarGroupItem">
|
|
13
|
+
<FluxAvatar
|
|
14
|
+
:alt="overflowLabel"
|
|
15
|
+
fallback="neutral"
|
|
16
|
+
:fallback-initials="`+${overflowCount}`"/>
|
|
17
|
+
</span>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script
|
|
22
|
+
lang="ts"
|
|
23
|
+
setup>
|
|
24
|
+
import { flattenVNodeTree } from '@flux-ui/internals';
|
|
25
|
+
import { computed, h, type VNode } from 'vue';
|
|
26
|
+
import { useTranslate } from '~flux/components/composable/private';
|
|
27
|
+
import FluxAvatar from './FluxAvatar.vue';
|
|
28
|
+
import $style from '~flux/components/css/component/AvatarGroup.module.scss';
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
max,
|
|
32
|
+
overlap = 0.3,
|
|
33
|
+
size = 32
|
|
34
|
+
} = defineProps<{
|
|
35
|
+
readonly max?: number;
|
|
36
|
+
readonly overlap?: number;
|
|
37
|
+
readonly size?: number;
|
|
38
|
+
}>();
|
|
39
|
+
|
|
40
|
+
const slots = defineSlots<{
|
|
41
|
+
default(): VNode[];
|
|
42
|
+
}>();
|
|
43
|
+
|
|
44
|
+
const translate = useTranslate();
|
|
45
|
+
|
|
46
|
+
const children = computed(() => flattenVNodeTree(slots.default?.() ?? []).filter(vnode => typeof vnode.type !== 'symbol'));
|
|
47
|
+
const visibleNodes = computed(() => max !== undefined && children.value.length > max ? children.value.slice(0, max) : children.value);
|
|
48
|
+
const overflowCount = computed(() => max !== undefined ? Math.max(0, children.value.length - max) : 0);
|
|
49
|
+
const overflowLabel = computed(() => translate('flux.andNMore', {n: overflowCount.value}));
|
|
50
|
+
|
|
51
|
+
const renderVisible = () => visibleNodes.value.map(vnode => h('span', {class: $style.avatarGroupItem}, [vnode]));
|
|
52
|
+
</script>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
:component-type="type"
|
|
4
4
|
:class="clsx(
|
|
5
5
|
cssClass,
|
|
6
|
+
isActive && cssClassActive,
|
|
6
7
|
isFilled && $style.isFilled,
|
|
7
8
|
size === 'small' && $style.isSmall,
|
|
8
9
|
size === 'medium' && $style.isMedium,
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
)"
|
|
12
13
|
:type="isSubmit ? 'submit' : 'button'"
|
|
13
14
|
:aria-disabled="disabled ? true : undefined"
|
|
15
|
+
:aria-pressed="isActive && type === 'button' ? true : undefined"
|
|
14
16
|
:disabled="disabled ? true : undefined"
|
|
15
17
|
:tabindex="disabled ? -1 : tabindex"
|
|
16
18
|
:href="href"
|
|
@@ -82,6 +84,7 @@
|
|
|
82
84
|
type = 'button'
|
|
83
85
|
} = defineProps<FluxButtonProps & {
|
|
84
86
|
readonly cssClass: string;
|
|
87
|
+
readonly cssClassActive?: string;
|
|
85
88
|
readonly cssClassIcon: string;
|
|
86
89
|
readonly cssClassLabel: string;
|
|
87
90
|
}>();
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="$style.contextMenu"
|
|
4
|
+
@contextmenu="onContextMenu">
|
|
5
|
+
<slot/>
|
|
6
|
+
|
|
7
|
+
<Teleport to="body">
|
|
8
|
+
<FluxFadeTransition>
|
|
9
|
+
<AnchorPopup
|
|
10
|
+
v-if="isOpen"
|
|
11
|
+
ref="popup"
|
|
12
|
+
:anchor="virtualAnchor"
|
|
13
|
+
:class="$style.contextMenuPopup"
|
|
14
|
+
clamp-to-viewport
|
|
15
|
+
:margin="2"
|
|
16
|
+
:position="position"
|
|
17
|
+
role="menu"
|
|
18
|
+
@click="close()">
|
|
19
|
+
<slot
|
|
20
|
+
name="menu"
|
|
21
|
+
v-bind="{close}"/>
|
|
22
|
+
</AnchorPopup>
|
|
23
|
+
</FluxFadeTransition>
|
|
24
|
+
</Teleport>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script
|
|
29
|
+
lang="ts"
|
|
30
|
+
setup>
|
|
31
|
+
import { isSSR, useEventListener, useFocusTrap } from '@flux-ui/internals';
|
|
32
|
+
import { computed, type ComponentPublicInstance, reactive, ref, toRef, useTemplateRef, type VNode } from 'vue';
|
|
33
|
+
import { useDisabled } from '~flux/components/composable';
|
|
34
|
+
import { useMenuFlyoutProvider } from '~flux/components/composable/private';
|
|
35
|
+
import { FluxFadeTransition } from '~flux/components/transition';
|
|
36
|
+
import { AnchorPopup } from '~flux/components/component/primitive';
|
|
37
|
+
import $style from '~flux/components/css/component/ContextMenu.module.scss';
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
debugCone = false,
|
|
41
|
+
disabled: componentDisabled,
|
|
42
|
+
position = 'bottom-left'
|
|
43
|
+
} = defineProps<{
|
|
44
|
+
readonly debugCone?: boolean;
|
|
45
|
+
readonly disabled?: boolean;
|
|
46
|
+
readonly position?:
|
|
47
|
+
| 'top' | 'top-left' | 'top-right'
|
|
48
|
+
| 'left' | 'left-top' | 'left-bottom'
|
|
49
|
+
| 'right' | 'right-top' | 'right-bottom'
|
|
50
|
+
| 'bottom' | 'bottom-left' | 'bottom-right';
|
|
51
|
+
}>();
|
|
52
|
+
|
|
53
|
+
const emit = defineEmits<{
|
|
54
|
+
open: [MouseEvent];
|
|
55
|
+
close: [];
|
|
56
|
+
}>();
|
|
57
|
+
|
|
58
|
+
defineSlots<{
|
|
59
|
+
default(): VNode[];
|
|
60
|
+
menu(props: {close(): void}): VNode[];
|
|
61
|
+
}>();
|
|
62
|
+
|
|
63
|
+
const disabled = useDisabled(toRef(() => componentDisabled));
|
|
64
|
+
const popupRef = useTemplateRef<ComponentPublicInstance>('popup');
|
|
65
|
+
|
|
66
|
+
const isOpen = ref(false);
|
|
67
|
+
const cursor = reactive({x: 0, y: 0});
|
|
68
|
+
const virtualAnchor = {
|
|
69
|
+
$el: {
|
|
70
|
+
getBoundingClientRect: () => new DOMRect(cursor.x, cursor.y, 0, 0)
|
|
71
|
+
}
|
|
72
|
+
} as unknown as ComponentPublicInstance;
|
|
73
|
+
|
|
74
|
+
const menuFlyout = useMenuFlyoutProvider({
|
|
75
|
+
debugCone: toRef(() => debugCone),
|
|
76
|
+
onCloseAll: () => close()
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
useFocusTrap(popupRef, {
|
|
80
|
+
disable: computed(() => menuFlyout.keyboardStack.value.length > 0)
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function onContextMenu(evt: MouseEvent): void {
|
|
84
|
+
if (disabled.value) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
evt.preventDefault();
|
|
89
|
+
|
|
90
|
+
cursor.x = evt.clientX;
|
|
91
|
+
cursor.y = evt.clientY;
|
|
92
|
+
isOpen.value = true;
|
|
93
|
+
|
|
94
|
+
emit('open', evt);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function close(): void {
|
|
98
|
+
if (!isOpen.value) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
isOpen.value = false;
|
|
103
|
+
emit('close');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!isSSR) {
|
|
107
|
+
useEventListener(ref(window), 'pointerdown', (evt: PointerEvent) => {
|
|
108
|
+
if (!isOpen.value) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const target = evt.target as Node | null;
|
|
113
|
+
const root = (popupRef.value?.$el ?? null) as HTMLElement | null;
|
|
114
|
+
|
|
115
|
+
if ((root && target && root.contains(target)) || menuFlyout.isInsidePopups(target)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
close();
|
|
120
|
+
}, {capture: true});
|
|
121
|
+
|
|
122
|
+
useEventListener(ref(window), 'keydown', (evt: KeyboardEvent) => {
|
|
123
|
+
if (isOpen.value && evt.key === 'Escape') {
|
|
124
|
+
close();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
useEventListener(ref(window), 'scroll', () => {
|
|
129
|
+
if (isOpen.value) {
|
|
130
|
+
close();
|
|
131
|
+
}
|
|
132
|
+
}, {capture: true});
|
|
133
|
+
}
|
|
134
|
+
</script>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
</template>
|
|
15
15
|
|
|
16
16
|
<template
|
|
17
|
-
v-if="'header' in slots || selectionMode"
|
|
17
|
+
v-if="'header' in slots || selectionMode || hasExpandable"
|
|
18
18
|
#header>
|
|
19
19
|
<slot
|
|
20
20
|
name="filter"
|
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
@update:model-value="onSelectAll"/>
|
|
32
32
|
</FluxTableHeader>
|
|
33
33
|
|
|
34
|
+
<FluxTableHeader
|
|
35
|
+
v-if="hasExpandable"
|
|
36
|
+
is-shrinking
|
|
37
|
+
:class="$style.tableCellExpand"/>
|
|
38
|
+
|
|
34
39
|
<slot
|
|
35
40
|
name="header"
|
|
36
41
|
v-bind="{page, perPage, items: limitedItems, total}"/>
|
|
@@ -63,27 +68,54 @@
|
|
|
63
68
|
</slot>
|
|
64
69
|
</template>
|
|
65
70
|
|
|
66
|
-
<
|
|
71
|
+
<template
|
|
67
72
|
v-for="(item, index) of limitedItems"
|
|
68
|
-
:key="uniqueKey ? item[uniqueKey] : index"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
v-if="
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
:key="uniqueKey ? item[uniqueKey] : index">
|
|
74
|
+
<FluxTableRow
|
|
75
|
+
:class="selectionMode && !treeDisabled && $style.isSelectableRow"
|
|
76
|
+
:is-selected="selectionMode ? isItemSelected(item) : false"
|
|
77
|
+
@click="onRowClick(item, $event)">
|
|
78
|
+
<FluxTableCell
|
|
79
|
+
v-if="selectionMode"
|
|
80
|
+
:class="$style.tableCellSelection">
|
|
81
|
+
<FluxFormCheckbox
|
|
82
|
+
:model-value="isItemSelected(item)"
|
|
83
|
+
@update:model-value="onSelectRow(item)"/>
|
|
84
|
+
</FluxTableCell>
|
|
85
|
+
|
|
86
|
+
<FluxTableCell
|
|
87
|
+
v-if="hasExpandable"
|
|
88
|
+
:class="$style.tableCellExpand">
|
|
89
|
+
<FluxAction
|
|
90
|
+
:class="clsx($style.tableExpandToggle, isItemExpanded(item) && $style.isExpanded)"
|
|
91
|
+
icon="chevron-right"
|
|
92
|
+
:aria-expanded="isItemExpanded(item)"
|
|
93
|
+
:aria-label="isItemExpanded(item) ? translate('flux.collapseRow') : translate('flux.expandRow')"
|
|
94
|
+
@click="toggleExpand(item)"/>
|
|
95
|
+
</FluxTableCell>
|
|
96
|
+
|
|
97
|
+
<template v-for="(_, name) of slots">
|
|
98
|
+
<slot
|
|
99
|
+
v-if="!IGNORED_SLOTS.includes(name as string)"
|
|
100
|
+
v-bind="{index, item, items: limitedItems, page, perPage, total, isSelected: isItemSelected(item)}"
|
|
101
|
+
:name="name"/>
|
|
102
|
+
</template>
|
|
103
|
+
</FluxTableRow>
|
|
104
|
+
|
|
105
|
+
<FluxTableRow
|
|
106
|
+
v-if="hasExpandable && isItemExpanded(item)"
|
|
107
|
+
:class="$style.tableExpandRow">
|
|
108
|
+
<FluxTableCell :colspan="columnCount">
|
|
109
|
+
<template #content>
|
|
110
|
+
<div :class="$style.tableExpandContent">
|
|
111
|
+
<slot
|
|
112
|
+
name="expandable"
|
|
113
|
+
v-bind="{index, item, isExpanded: true, toggle: () => toggleExpand(item)}"/>
|
|
114
|
+
</div>
|
|
115
|
+
</template>
|
|
116
|
+
</FluxTableCell>
|
|
117
|
+
</FluxTableRow>
|
|
118
|
+
</template>
|
|
87
119
|
</FluxTable>
|
|
88
120
|
</template>
|
|
89
121
|
|
|
@@ -91,8 +123,11 @@
|
|
|
91
123
|
lang="ts"
|
|
92
124
|
setup
|
|
93
125
|
generic="T extends Record<string, any>">
|
|
126
|
+
import { clsx } from 'clsx';
|
|
94
127
|
import { computed, unref, useTemplateRef, type VNode, watch } from 'vue';
|
|
95
128
|
import { useDisabledInjection } from '~flux/components/composable';
|
|
129
|
+
import { useTranslate } from '~flux/components/composable/private';
|
|
130
|
+
import FluxAction from './FluxAction.vue';
|
|
96
131
|
import FluxFormCheckbox from './FluxFormCheckbox.vue';
|
|
97
132
|
import FluxPaginationBar from './FluxPaginationBar.vue';
|
|
98
133
|
import FluxTable from './FluxTable.vue';
|
|
@@ -104,7 +139,7 @@
|
|
|
104
139
|
type SelectionId = string | number;
|
|
105
140
|
type SelectionValue = SelectionId | null | SelectionId[];
|
|
106
141
|
|
|
107
|
-
const IGNORED_SLOTS: string[] = ['filter', 'header', 'footer', 'colgroups', 'pagination'];
|
|
142
|
+
const IGNORED_SLOTS: string[] = ['filter', 'header', 'footer', 'colgroups', 'pagination', 'expandable'];
|
|
108
143
|
|
|
109
144
|
const emit = defineEmits<{
|
|
110
145
|
limit: [number];
|
|
@@ -112,8 +147,12 @@
|
|
|
112
147
|
}>();
|
|
113
148
|
|
|
114
149
|
const selected = defineModel<SelectionValue>('selected');
|
|
150
|
+
const expanded = defineModel<SelectionId[]>('expanded', {
|
|
151
|
+
default: () => []
|
|
152
|
+
});
|
|
115
153
|
|
|
116
154
|
const {
|
|
155
|
+
expandMode = 'multiple',
|
|
117
156
|
isBordered = true,
|
|
118
157
|
isHoverable = false,
|
|
119
158
|
isLoading = false,
|
|
@@ -124,6 +163,7 @@
|
|
|
124
163
|
selectionMode,
|
|
125
164
|
uniqueKey
|
|
126
165
|
} = defineProps<{
|
|
166
|
+
readonly expandMode?: 'single' | 'multiple';
|
|
127
167
|
readonly fillColumns?: number;
|
|
128
168
|
readonly isBordered?: boolean;
|
|
129
169
|
readonly isHoverable?: boolean;
|
|
@@ -140,16 +180,6 @@
|
|
|
140
180
|
}>();
|
|
141
181
|
|
|
142
182
|
const slots = defineSlots<{
|
|
143
|
-
[key: string]: (props: {
|
|
144
|
-
readonly index: number;
|
|
145
|
-
readonly page: number;
|
|
146
|
-
readonly perPage: number;
|
|
147
|
-
readonly item: T;
|
|
148
|
-
readonly items: T[];
|
|
149
|
-
readonly total: number;
|
|
150
|
-
readonly isSelected: boolean;
|
|
151
|
-
}) => VNode;
|
|
152
|
-
|
|
153
183
|
filter(props: {
|
|
154
184
|
readonly page: number;
|
|
155
185
|
readonly perPage: number;
|
|
@@ -179,13 +209,38 @@
|
|
|
179
209
|
}): VNode;
|
|
180
210
|
|
|
181
211
|
colgroups(): VNode;
|
|
212
|
+
|
|
213
|
+
expandable(props: {
|
|
214
|
+
readonly index: number;
|
|
215
|
+
readonly item: T;
|
|
216
|
+
readonly isExpanded: boolean;
|
|
217
|
+
|
|
218
|
+
toggle(): void;
|
|
219
|
+
}): VNode;
|
|
220
|
+
} & {
|
|
221
|
+
[key: string]: (props: {
|
|
222
|
+
readonly index: number;
|
|
223
|
+
readonly page: number;
|
|
224
|
+
readonly perPage: number;
|
|
225
|
+
readonly item: T;
|
|
226
|
+
readonly items: T[];
|
|
227
|
+
readonly total: number;
|
|
228
|
+
readonly isSelected: boolean;
|
|
229
|
+
}) => VNode;
|
|
182
230
|
}>();
|
|
183
231
|
|
|
184
232
|
const table = useTemplateRef('table');
|
|
185
233
|
const treeDisabled = useDisabledInjection();
|
|
234
|
+
const translate = useTranslate();
|
|
186
235
|
|
|
187
236
|
const limitedItems = computed(() => items.slice(0, perPage));
|
|
188
237
|
|
|
238
|
+
const hasExpandable = computed(() => 'expandable' in slots);
|
|
239
|
+
const columnCount = computed(() => {
|
|
240
|
+
const userColumns = Object.keys(slots).filter(name => !IGNORED_SLOTS.includes(name)).length;
|
|
241
|
+
return userColumns + (selectionMode ? 1 : 0) + (unref(hasExpandable) ? 1 : 0);
|
|
242
|
+
});
|
|
243
|
+
|
|
189
244
|
const currentPageIds = computed<SelectionId[]>(() => {
|
|
190
245
|
if (!uniqueKey) {
|
|
191
246
|
return [];
|
|
@@ -294,10 +349,36 @@
|
|
|
294
349
|
selected.value = current.filter(id => !ids.includes(id));
|
|
295
350
|
}
|
|
296
351
|
|
|
352
|
+
function isItemExpanded(item: T): boolean {
|
|
353
|
+
const id = getItemId(item);
|
|
354
|
+
return id !== undefined && unref(expanded).includes(id);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function toggleExpand(item: T): void {
|
|
358
|
+
const id = getItemId(item);
|
|
359
|
+
|
|
360
|
+
if (id === undefined) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const current = unref(expanded);
|
|
365
|
+
|
|
366
|
+
if (current.includes(id)) {
|
|
367
|
+
expanded.value = current.filter(v => v !== id);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
expanded.value = expandMode === 'single' ? [id] : [...current, id];
|
|
372
|
+
}
|
|
373
|
+
|
|
297
374
|
if (import.meta.env.DEV && selectionMode && !uniqueKey) {
|
|
298
375
|
console.warn('[FluxDataTable] `uniqueKey` is required when `selectionMode` is set, otherwise rows cannot be tracked across renders.');
|
|
299
376
|
}
|
|
300
377
|
|
|
378
|
+
if (import.meta.env.DEV && unref(hasExpandable) && !uniqueKey) {
|
|
379
|
+
console.warn('[FluxDataTable] `uniqueKey` is required when the `expandable` slot is used, otherwise rows cannot be tracked across renders.');
|
|
380
|
+
}
|
|
381
|
+
|
|
301
382
|
watch(() => items, () => {
|
|
302
383
|
unref(table)?.$el.scrollTo(0, 0);
|
|
303
384
|
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="clsx(
|
|
4
|
+
$style.descriptionItem,
|
|
5
|
+
isStacked && $style.isStacked
|
|
6
|
+
)">
|
|
7
|
+
<dt :class="$style.descriptionItemTerm">
|
|
8
|
+
<FluxIcon
|
|
9
|
+
v-if="icon"
|
|
10
|
+
:class="$style.descriptionItemIcon"
|
|
11
|
+
:name="icon"/>
|
|
12
|
+
|
|
13
|
+
<span :class="$style.descriptionItemLabel">
|
|
14
|
+
<slot name="label">{{ label }}</slot>
|
|
15
|
+
</span>
|
|
16
|
+
</dt>
|
|
17
|
+
|
|
18
|
+
<dd :class="$style.descriptionItemValue">
|
|
19
|
+
<slot/>
|
|
20
|
+
</dd>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script
|
|
25
|
+
lang="ts"
|
|
26
|
+
setup>
|
|
27
|
+
import type { FluxIconName } from '@flux-ui/types';
|
|
28
|
+
import { clsx } from 'clsx';
|
|
29
|
+
import type { VNode } from 'vue';
|
|
30
|
+
import FluxIcon from './FluxIcon.vue';
|
|
31
|
+
import $style from '~flux/components/css/component/DescriptionList.module.scss';
|
|
32
|
+
|
|
33
|
+
defineProps<{
|
|
34
|
+
readonly icon?: FluxIconName;
|
|
35
|
+
readonly isStacked?: boolean;
|
|
36
|
+
readonly label?: string;
|
|
37
|
+
}>();
|
|
38
|
+
|
|
39
|
+
defineSlots<{
|
|
40
|
+
default(): VNode[];
|
|
41
|
+
label(): VNode[];
|
|
42
|
+
}>();
|
|
43
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="$style.descriptionList">
|
|
3
|
+
<div
|
|
4
|
+
v-if="title || slots.header"
|
|
5
|
+
:class="$style.descriptionListHeader">
|
|
6
|
+
<slot name="header">{{ title }}</slot>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<dl
|
|
10
|
+
:class="clsx(
|
|
11
|
+
$style.descriptionListItems,
|
|
12
|
+
direction === 'horizontal' && $style.isHorizontal
|
|
13
|
+
)">
|
|
14
|
+
<slot/>
|
|
15
|
+
</dl>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script
|
|
20
|
+
lang="ts"
|
|
21
|
+
setup>
|
|
22
|
+
import { clsx } from 'clsx';
|
|
23
|
+
import type { VNode } from 'vue';
|
|
24
|
+
import $style from '~flux/components/css/component/DescriptionList.module.scss';
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
direction = 'vertical'
|
|
28
|
+
} = defineProps<{
|
|
29
|
+
readonly direction?: 'horizontal' | 'vertical';
|
|
30
|
+
readonly title?: string;
|
|
31
|
+
}>();
|
|
32
|
+
|
|
33
|
+
const slots = defineSlots<{
|
|
34
|
+
default(): VNode[];
|
|
35
|
+
header(): VNode[];
|
|
36
|
+
}>();
|
|
37
|
+
</script>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<FluxButton
|
|
3
|
-
:="{type, disabled, iconLeading, iconTrailing, isFilled, isLoading, isSubmit, label, size, tabindex, href, rel, target, to}"
|
|
3
|
+
:="{type, disabled, iconLeading, iconTrailing, isActive, isFilled, isLoading, isSubmit, label, size, tabindex, href, rel, target, to}"
|
|
4
4
|
:css-class="$style.destructiveButton"
|
|
5
|
+
:css-class-active="$style.isActive"
|
|
5
6
|
:css-class-icon="$style.destructiveButtonIcon"
|
|
6
7
|
:css-class-label="$style.destructiveButtonLabel"
|
|
7
8
|
@click="$emit('click', $event)"
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
<dialog
|
|
17
17
|
ref="dialog"
|
|
18
18
|
:class="$style.flyoutDialog"
|
|
19
|
-
@click="onDialogBackdropClick"
|
|
20
|
-
@keydown.prevent.esc="close">
|
|
19
|
+
@click="onDialogBackdropClick">
|
|
21
20
|
<FluxPane
|
|
22
21
|
v-if="isOpen"
|
|
23
22
|
ref="pane"
|
|
@@ -46,6 +45,12 @@
|
|
|
46
45
|
import { FluxFlyoutInjectionKey } from '~flux/components/data';
|
|
47
46
|
import FluxPane from './FluxPane.vue';
|
|
48
47
|
import $style from '~flux/components/css/component/Flyout.module.scss';
|
|
48
|
+
import { useHotKey } from '@basmilius/common';
|
|
49
|
+
|
|
50
|
+
const emit = defineEmits<{
|
|
51
|
+
close: [];
|
|
52
|
+
open: [];
|
|
53
|
+
}>();
|
|
49
54
|
|
|
50
55
|
const {
|
|
51
56
|
direction = 'vertical',
|
|
@@ -93,6 +98,11 @@
|
|
|
93
98
|
!isSSR && useEventListener(ref(window), 'resize', () => unref(isOpen) && reposition());
|
|
94
99
|
useFocusTrap(paneRef);
|
|
95
100
|
|
|
101
|
+
useHotKey('esc', () => close(), {
|
|
102
|
+
enabled: isOpen,
|
|
103
|
+
target: dialogRef
|
|
104
|
+
});
|
|
105
|
+
|
|
96
106
|
let closeAnimationEndListener: ((evt: AnimationEvent) => void) | null = null;
|
|
97
107
|
let openAnimationEndListener: ((evt: AnimationEvent) => void) | null = null;
|
|
98
108
|
|
|
@@ -233,11 +243,13 @@
|
|
|
233
243
|
|
|
234
244
|
if (isOpen && !dialog.open) {
|
|
235
245
|
dialog.showModal();
|
|
246
|
+
emit('open');
|
|
236
247
|
|
|
237
248
|
window.addEventListener('scroll', reposition, {passive: true});
|
|
238
249
|
onCleanup(() => window.removeEventListener('scroll', reposition));
|
|
239
250
|
} else if (!isOpen && dialog.open) {
|
|
240
251
|
dialog.close();
|
|
252
|
+
emit('close');
|
|
241
253
|
}
|
|
242
254
|
});
|
|
243
255
|
|
|
@@ -250,6 +262,7 @@
|
|
|
250
262
|
defineExpose({
|
|
251
263
|
close,
|
|
252
264
|
open,
|
|
253
|
-
toggle
|
|
265
|
+
toggle,
|
|
266
|
+
isOpen
|
|
254
267
|
});
|
|
255
268
|
</script>
|