@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.
Files changed (72) hide show
  1. package/dist/component/FluxAvatarGroup.vue.d.ts +17 -0
  2. package/dist/component/FluxButton.vue.d.ts +2 -0
  3. package/dist/component/FluxContextMenu.vue.d.ts +26 -0
  4. package/dist/component/FluxDataTable.vue.d.ts +20 -10
  5. package/dist/component/FluxDescriptionItem.vue.d.ts +19 -0
  6. package/dist/component/FluxDescriptionList.vue.d.ts +17 -0
  7. package/dist/component/FluxFlyout.vue.d.ts +9 -2
  8. package/dist/component/FluxFormCombobox.vue.d.ts +20 -0
  9. package/dist/component/FluxFormRating.vue.d.ts +21 -0
  10. package/dist/component/FluxFormTagsInput.vue.d.ts +27 -0
  11. package/dist/component/FluxFormTextArea.vue.d.ts +6 -1
  12. package/dist/component/FluxInlineEdit.vue.d.ts +41 -0
  13. package/dist/component/FluxMenu.vue.d.ts +1 -0
  14. package/dist/component/FluxMenuFlyout.vue.d.ts +22 -0
  15. package/dist/component/FluxTableCell.vue.d.ts +1 -0
  16. package/dist/component/FluxTour.vue.d.ts +35 -0
  17. package/dist/component/FluxTourItem.vue.d.ts +18 -0
  18. package/dist/component/FluxVirtualScroller.vue.d.ts +27 -0
  19. package/dist/component/index.d.ts +12 -0
  20. package/dist/component/primitive/AnchorPopup.vue.d.ts +7 -1
  21. package/dist/component/primitive/SelectBase.vue.d.ts +3 -0
  22. package/dist/composable/private/index.d.ts +1 -0
  23. package/dist/composable/private/useMenuFlyout.d.ts +42 -0
  24. package/dist/data/di.d.ts +35 -0
  25. package/dist/data/i18n.d.ts +7 -0
  26. package/dist/index.css +449 -5
  27. package/dist/index.js +2156 -408
  28. package/dist/index.js.map +1 -1
  29. package/package.json +7 -7
  30. package/src/component/FluxAvatarGroup.vue +52 -0
  31. package/src/component/FluxButton.vue +3 -0
  32. package/src/component/FluxContextMenu.vue +134 -0
  33. package/src/component/FluxDataTable.vue +113 -32
  34. package/src/component/FluxDescriptionItem.vue +43 -0
  35. package/src/component/FluxDescriptionList.vue +37 -0
  36. package/src/component/FluxDestructiveButton.vue +2 -1
  37. package/src/component/FluxFlyout.vue +16 -3
  38. package/src/component/FluxFormCombobox.vue +98 -0
  39. package/src/component/FluxFormRating.vue +172 -0
  40. package/src/component/FluxFormTagsInput.vue +249 -0
  41. package/src/component/FluxFormTextArea.vue +16 -1
  42. package/src/component/FluxInlineEdit.vue +176 -0
  43. package/src/component/FluxMenu.vue +13 -3
  44. package/src/component/FluxMenuFlyout.vue +118 -0
  45. package/src/component/FluxPrimaryButton.vue +2 -1
  46. package/src/component/FluxPrimaryLinkButton.vue +2 -1
  47. package/src/component/FluxPublishButton.vue +2 -1
  48. package/src/component/FluxSecondaryButton.vue +2 -1
  49. package/src/component/FluxSecondaryLinkButton.vue +2 -1
  50. package/src/component/FluxTableCell.vue +2 -0
  51. package/src/component/FluxTour.vue +332 -0
  52. package/src/component/FluxTourItem.vue +27 -0
  53. package/src/component/FluxVirtualScroller.vue +96 -0
  54. package/src/component/index.ts +12 -0
  55. package/src/component/primitive/AnchorPopup.vue +27 -0
  56. package/src/component/primitive/SelectBase.vue +37 -2
  57. package/src/composable/private/index.ts +1 -0
  58. package/src/composable/private/useMenuFlyout.ts +417 -0
  59. package/src/css/component/AvatarGroup.module.scss +22 -0
  60. package/src/css/component/ContextMenu.module.scss +17 -0
  61. package/src/css/component/DescriptionList.module.scss +98 -0
  62. package/src/css/component/Form.module.scss +51 -0
  63. package/src/css/component/FormRating.module.scss +47 -0
  64. package/src/css/component/InlineEdit.module.scss +45 -0
  65. package/src/css/component/Menu.module.scss +4 -1
  66. package/src/css/component/MenuFlyout.module.scss +38 -0
  67. package/src/css/component/Table.module.scss +16 -0
  68. package/src/css/component/Tour.module.scss +108 -0
  69. package/src/css/component/VirtualScroller.module.scss +17 -0
  70. package/src/css/mixin/button-active.scss +3 -1
  71. package/src/data/di.ts +40 -0
  72. package/src/data/i18n.ts +7 -0
@@ -0,0 +1,176 @@
1
+ <template>
2
+ <div :class="$style.inlineEdit">
3
+ <template v-if="isEditing">
4
+ <FluxFormTextArea
5
+ v-if="multiline"
6
+ v-model="draft"
7
+ ref="field"
8
+ :class="$style.inlineEditField"
9
+ :error="error"
10
+ :placeholder="placeholder"
11
+ @blur="onBlur"
12
+ @keydown="onKeyDown"/>
13
+
14
+ <FluxFormInput
15
+ v-else
16
+ v-model="draft"
17
+ ref="field"
18
+ :class="$style.inlineEditField"
19
+ :error="error"
20
+ :placeholder="placeholder"
21
+ @blur="onBlur"
22
+ @keydown="onKeyDown"/>
23
+
24
+ <div
25
+ :class="$style.inlineEditActions"
26
+ @mousedown.prevent>
27
+ <slot
28
+ name="actions"
29
+ :save="save"
30
+ :cancel="cancel">
31
+ <FluxSecondaryButton
32
+ icon-leading="check"
33
+ @click="save"/>
34
+
35
+ <FluxSecondaryButton
36
+ icon-leading="xmark"
37
+ @click="cancel"/>
38
+ </slot>
39
+ </div>
40
+ </template>
41
+
42
+ <div
43
+ v-else
44
+ ref="display"
45
+ :class="clsx(
46
+ $style.inlineEditDisplay,
47
+ isInteractive && $style.isInteractive,
48
+ !modelValue && $style.isPlaceholder
49
+ )"
50
+ :role="isInteractive ? 'button' : undefined"
51
+ :tabindex="isInteractive ? 0 : undefined"
52
+ @click="edit"
53
+ @keydown="onDisplayKeyDown">
54
+ <slot
55
+ :value="modelValue"
56
+ :edit="edit">
57
+ {{ modelValue || placeholder }}
58
+ </slot>
59
+ </div>
60
+ </div>
61
+ </template>
62
+
63
+ <script
64
+ lang="ts"
65
+ setup>
66
+ import { clsx } from 'clsx';
67
+ import { computed, nextTick, ref, toRef, useTemplateRef, type VNode } from 'vue';
68
+ import { useDisabled } from '~flux/components/composable';
69
+ import FluxFormInput from './FluxFormInput.vue';
70
+ import FluxFormTextArea from './FluxFormTextArea.vue';
71
+ import FluxSecondaryButton from './FluxSecondaryButton.vue';
72
+ import $style from '~flux/components/css/component/InlineEdit.module.scss';
73
+
74
+ const modelValue = defineModel<string>({
75
+ default: ''
76
+ });
77
+
78
+ const {
79
+ disabled: componentDisabled,
80
+ isReadonly,
81
+ multiline = false,
82
+ saveOnBlur = true
83
+ } = defineProps<{
84
+ readonly disabled?: boolean;
85
+ readonly error?: string | null;
86
+ readonly isReadonly?: boolean;
87
+ readonly multiline?: boolean;
88
+ readonly placeholder?: string;
89
+ readonly saveOnBlur?: boolean;
90
+ }>();
91
+
92
+ const emit = defineEmits<{
93
+ cancel: [];
94
+ edit: [];
95
+ save: [string];
96
+ }>();
97
+
98
+ defineSlots<{
99
+ actions?(props: {save(): void; cancel(): void}): VNode[];
100
+ default?(props: {value: string; edit(): void}): VNode[];
101
+ }>();
102
+
103
+ const disabled = useDisabled(toRef(() => componentDisabled));
104
+ const displayRef = useTemplateRef<HTMLElement>('display');
105
+ const fieldRef = useTemplateRef<{focus(): void}>('field');
106
+
107
+ const isEditing = ref(false);
108
+ const draft = ref('');
109
+
110
+ const isInteractive = computed(() => !disabled.value && !isReadonly);
111
+
112
+ function edit(): void {
113
+ if (!isInteractive.value) {
114
+ return;
115
+ }
116
+
117
+ draft.value = modelValue.value;
118
+ isEditing.value = true;
119
+ emit('edit');
120
+
121
+ nextTick(() => fieldRef.value?.focus());
122
+ }
123
+
124
+ function close(): void {
125
+ isEditing.value = false;
126
+ nextTick(() => displayRef.value?.focus());
127
+ }
128
+
129
+ function save(): void {
130
+ modelValue.value = draft.value;
131
+ emit('save', draft.value);
132
+ close();
133
+ }
134
+
135
+ function cancel(): void {
136
+ emit('cancel');
137
+ close();
138
+ }
139
+
140
+ function onBlur(): void {
141
+ if (saveOnBlur && isEditing.value) {
142
+ save();
143
+ }
144
+ }
145
+
146
+ function onKeyDown(evt: KeyboardEvent): void {
147
+ if (evt.key === 'Escape') {
148
+ evt.preventDefault();
149
+ cancel();
150
+ return;
151
+ }
152
+
153
+ if (evt.key === 'Enter') {
154
+ if (multiline) {
155
+ if (evt.metaKey || evt.ctrlKey) {
156
+ evt.preventDefault();
157
+ save();
158
+ }
159
+ } else {
160
+ evt.preventDefault();
161
+ save();
162
+ }
163
+ }
164
+ }
165
+
166
+ function onDisplayKeyDown(evt: KeyboardEvent): void {
167
+ if (!isInteractive.value) {
168
+ return;
169
+ }
170
+
171
+ if (evt.key === 'Enter' || evt.key === ' ') {
172
+ evt.preventDefault();
173
+ edit();
174
+ }
175
+ }
176
+ </script>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <nav
3
3
  ref="element"
4
- :class="isLarge ? $style.menuLarge : $style.menuNormal"
4
+ :class="[isLarge ? $style.menuLarge : $style.menuNormal, coneActive && $style.menuConeActive]"
5
5
  role="menu"
6
6
  aria-orientation="vertical">
7
7
  <slot/>
@@ -12,10 +12,14 @@
12
12
  lang="ts"
13
13
  setup>
14
14
  import { useFocusZone } from '@flux-ui/internals';
15
- import { useTemplateRef, type VNode } from 'vue';
15
+ import { computed, toRef, useTemplateRef, type VNode } from 'vue';
16
+ import { useMenuFlyoutContext } from '~flux/components/composable/private';
16
17
  import $style from '~flux/components/css/component/Menu.module.scss';
17
18
 
18
- defineProps<{
19
+ const {
20
+ debugCone = false
21
+ } = defineProps<{
22
+ readonly debugCone?: boolean;
19
23
  readonly isLarge?: boolean;
20
24
  }>();
21
25
 
@@ -28,4 +32,10 @@
28
32
  useFocusZone(elementRef, {
29
33
  direction: 'vertical'
30
34
  });
35
+
36
+ const menuFlyout = useMenuFlyoutContext({
37
+ debugCone: toRef(() => debugCone)
38
+ });
39
+
40
+ const coneActive = computed(() => !!menuFlyout.activeCone.value);
31
41
  </script>
@@ -0,0 +1,118 @@
1
+ <template>
2
+ <FluxMenuItem
3
+ ref="trigger"
4
+ :class="isOpen && $style.menuFlyoutTriggerOpen"
5
+ command-icon="angle-right"
6
+ :disabled="disabled"
7
+ :icon-leading="icon"
8
+ :is-active="isActive"
9
+ :is-destructive="isDestructive"
10
+ :label="label"
11
+ aria-haspopup="menu"
12
+ :aria-expanded="isOpen ? 'true' : 'false'"
13
+ @click="onTriggerClick"
14
+ @keydown="onTriggerKeydown">
15
+ <template
16
+ v-if="$slots.trigger"
17
+ #label>
18
+ <slot name="trigger"/>
19
+ </template>
20
+ </FluxMenuItem>
21
+
22
+ <Teleport to="body">
23
+ <AnchorPopup
24
+ v-if="isOpen"
25
+ ref="popup"
26
+ :anchor="popupAnchor"
27
+ :class="$style.menuFlyoutPopup"
28
+ clamp-to-viewport
29
+ :margin="2"
30
+ :position="position"
31
+ role="menu"
32
+ :aria-label="translate('flux.submenu')"
33
+ @keydown="onPopupKeydown">
34
+ <slot/>
35
+ </AnchorPopup>
36
+
37
+ <svg
38
+ v-if="showDebugCone"
39
+ :class="$style.menuFlyoutConeDebug">
40
+ <polygon :points="conePoints"/>
41
+ <circle
42
+ :cx="coneApex.x"
43
+ :cy="coneApex.y"
44
+ r="4"/>
45
+ </svg>
46
+ </Teleport>
47
+ </template>
48
+
49
+ <script
50
+ lang="ts"
51
+ setup>
52
+ import type { FluxIconName } from '@flux-ui/types';
53
+ import { computed, type ComponentPublicInstance, toRef, useTemplateRef, type VNode } from 'vue';
54
+ import { useMenuFlyout, useTranslate } from '~flux/components/composable/private';
55
+ import { AnchorPopup } from '~flux/components/component/primitive';
56
+ import FluxMenuItem from './FluxMenuItem.vue';
57
+ import $style from '~flux/components/css/component/MenuFlyout.module.scss';
58
+
59
+ const {
60
+ disabled,
61
+ position = 'right-top'
62
+ } = defineProps<{
63
+ readonly disabled?: boolean;
64
+ readonly icon?: FluxIconName;
65
+ readonly isActive?: boolean;
66
+ readonly isDestructive?: boolean;
67
+ readonly label?: string;
68
+ readonly position?:
69
+ | 'top' | 'top-left' | 'top-right'
70
+ | 'left' | 'left-top' | 'left-bottom'
71
+ | 'right' | 'right-top' | 'right-bottom'
72
+ | 'bottom' | 'bottom-left' | 'bottom-right';
73
+ }>();
74
+
75
+ defineSlots<{
76
+ default(): VNode[];
77
+ trigger(): VNode[];
78
+ }>();
79
+
80
+ // The top chrome of a menu inside a pane: 1px pane border + 9px menu margin
81
+ // (see `.basePane > .menu` in Menu.module.scss). Subtracting it aligns the first
82
+ // submenu item with the opener item instead of dropping it lower.
83
+ const MENU_CHROME_TOP = 10;
84
+
85
+ const translate = useTranslate();
86
+
87
+ const triggerRef = useTemplateRef<ComponentPublicInstance>('trigger');
88
+ const popupRef = useTemplateRef<ComponentPublicInstance>('popup');
89
+
90
+ const popupAnchor = {
91
+ $el: {
92
+ getBoundingClientRect(): DOMRect {
93
+ const element = triggerRef.value?.$el as HTMLElement | undefined;
94
+
95
+ if (!element) {
96
+ return new DOMRect();
97
+ }
98
+
99
+ const rect = element.getBoundingClientRect();
100
+
101
+ return new DOMRect(rect.x, rect.y - MENU_CHROME_TOP, rect.width, rect.height);
102
+ }
103
+ }
104
+ } as unknown as ComponentPublicInstance;
105
+
106
+ const {
107
+ context,
108
+ cone,
109
+ isOpen,
110
+ onPopupKeydown,
111
+ onTriggerClick,
112
+ onTriggerKeydown
113
+ } = useMenuFlyout({triggerRef, popupRef, disabled: toRef(() => disabled)});
114
+
115
+ const showDebugCone = computed(() => !!context && context.debugCone.value && isOpen.value && !!cone.value);
116
+ const conePoints = computed(() => cone.value ? `${cone.value.ax},${cone.value.ay} ${cone.value.bx},${cone.value.by} ${cone.value.cx},${cone.value.cy}` : '');
117
+ const coneApex = computed(() => ({x: cone.value?.ax ?? 0, y: cone.value?.ay ?? 0}));
118
+ </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.primaryButton"
5
+ :css-class-active="$style.isActive"
5
6
  :css-class-icon="$style.primaryButtonIcon"
6
7
  :css-class-label="$style.primaryButtonLabel"
7
8
  @click="$emit('click', $event)"
@@ -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.primaryLinkButton"
5
+ :css-class-active="$style.isActive"
5
6
  :css-class-icon="$style.primaryLinkButtonIcon"
6
7
  :css-class-label="$style.primaryLinkButtonLabel"
7
8
  @click="$emit('click', $event)"
@@ -1,12 +1,13 @@
1
1
  <template>
2
2
  <FluxButton
3
- :="{type, disabled, iconTrailing, isFilled, isLoading, isSubmit, label, size, tabindex, href, rel, target, to}"
3
+ :="{type, disabled, iconTrailing, isActive, isFilled, isLoading, isSubmit, label, size, tabindex, href, rel, target, to}"
4
4
  :class="clsx(
5
5
  !isDone && !isLoading && $style.isIdle,
6
6
  isDone && $style.isDone,
7
7
  isLoading && $style.isLoading
8
8
  )"
9
9
  :css-class="$style.publishButton"
10
+ :css-class-active="$style.isActive"
10
11
  :css-class-icon="$style.publishButtonIcon"
11
12
  :css-class-label="$style.publishButtonLabel"
12
13
  @click="$emit('click', $event)"
@@ -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.secondaryButton"
5
+ :css-class-active="$style.isActive"
5
6
  :css-class-icon="$style.secondaryButtonIcon"
6
7
  :css-class-label="$style.secondaryButtonLabel"
7
8
  @click="$emit('click', $event)"
@@ -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.secondaryLinkButton"
5
+ :css-class-active="$style.isActive"
5
6
  :css-class-icon="$style.secondaryLinkButtonIcon"
6
7
  :css-class-label="$style.secondaryLinkButtonLabel"
7
8
  @click="$emit('click', $event)"
@@ -7,6 +7,7 @@
7
7
  isSeparated && $style.isSeparated,
8
8
  isStriped && $style.isStriped
9
9
  )"
10
+ :colspan="colspan"
10
11
  role="cell">
11
12
  <slot name="content">
12
13
  <div
@@ -32,6 +33,7 @@
32
33
  const {
33
34
  contentDirection = 'row'
34
35
  } = defineProps<{
36
+ readonly colspan?: number;
35
37
  readonly contentDirection?: 'column' | 'row';
36
38
  readonly contentGap?: number;
37
39
  }>();