@baklavue/ui 1.0.0-preview.2

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 (83) hide show
  1. package/.releaserc.json +14 -0
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +15 -0
  4. package/index.ts +1 -0
  5. package/package.json +45 -0
  6. package/src/accordion/Accordion.vue +206 -0
  7. package/src/accordion/accordion.types.ts +109 -0
  8. package/src/accordion/index.ts +3 -0
  9. package/src/alert/Alert.vue +199 -0
  10. package/src/alert/alert.types.ts +58 -0
  11. package/src/alert/index.ts +2 -0
  12. package/src/badge/Badge.vue +20 -0
  13. package/src/badge/badge.types.ts +7 -0
  14. package/src/badge/index.ts +2 -0
  15. package/src/button/Button.vue +45 -0
  16. package/src/button/button.types.ts +30 -0
  17. package/src/button/index.ts +3 -0
  18. package/src/checkbox/Checkbox.vue +148 -0
  19. package/src/checkbox/checkbox.types.ts +108 -0
  20. package/src/checkbox/index.ts +2 -0
  21. package/src/datepicker/Datepicker.vue +172 -0
  22. package/src/datepicker/datepicker.types.ts +39 -0
  23. package/src/datepicker/index.ts +2 -0
  24. package/src/dialog/Dialog.vue +178 -0
  25. package/src/dialog/dialog.types.ts +17 -0
  26. package/src/dialog/index.ts +2 -0
  27. package/src/drawer/Drawer.vue +162 -0
  28. package/src/drawer/drawer.types.ts +17 -0
  29. package/src/drawer/index.ts +2 -0
  30. package/src/dropdown/Dropdown.vue +231 -0
  31. package/src/dropdown/dropdown.types.ts +110 -0
  32. package/src/dropdown/index.ts +2 -0
  33. package/src/icon/Icon.vue +102 -0
  34. package/src/icon/icon.types.ts +25 -0
  35. package/src/icon/index.ts +2 -0
  36. package/src/index.ts +37 -0
  37. package/src/input/Input.vue +148 -0
  38. package/src/input/index.ts +3 -0
  39. package/src/input/input.types.ts +156 -0
  40. package/src/link/Link.vue +133 -0
  41. package/src/link/index.ts +2 -0
  42. package/src/link/link.types.ts +42 -0
  43. package/src/notification/Notification.vue +57 -0
  44. package/src/notification/index.ts +2 -0
  45. package/src/notification/notification.types.ts +25 -0
  46. package/src/pagination/Pagination.vue +137 -0
  47. package/src/pagination/index.ts +2 -0
  48. package/src/pagination/pagination.types.ts +61 -0
  49. package/src/radio/Radio.vue +205 -0
  50. package/src/radio/index.ts +2 -0
  51. package/src/radio/radio.types.ts +95 -0
  52. package/src/select/Select.vue +147 -0
  53. package/src/select/index.ts +2 -0
  54. package/src/select/select.types.ts +53 -0
  55. package/src/spinner/Spinner.vue +49 -0
  56. package/src/spinner/index.ts +2 -0
  57. package/src/spinner/spinner.types.ts +11 -0
  58. package/src/split-button/SplitButton.vue +73 -0
  59. package/src/split-button/index.ts +2 -0
  60. package/src/split-button/split-button.types.ts +19 -0
  61. package/src/stepper/Stepper.vue +100 -0
  62. package/src/stepper/index.ts +2 -0
  63. package/src/stepper/stepper.types.ts +29 -0
  64. package/src/switch/Switch.vue +80 -0
  65. package/src/switch/index.ts +2 -0
  66. package/src/switch/switch.types.ts +13 -0
  67. package/src/tab/Tab.vue +99 -0
  68. package/src/tab/index.ts +2 -0
  69. package/src/tab/tab.types.ts +17 -0
  70. package/src/table/Table.vue +264 -0
  71. package/src/table/index.ts +7 -0
  72. package/src/table/table.types.ts +62 -0
  73. package/src/tag/Tag.vue +83 -0
  74. package/src/tag/index.ts +2 -0
  75. package/src/tag/tag.types.ts +24 -0
  76. package/src/textarea/Textarea.vue +84 -0
  77. package/src/textarea/index.ts +2 -0
  78. package/src/textarea/textarea.types.ts +37 -0
  79. package/src/tooltip/Tooltip.vue +81 -0
  80. package/src/tooltip/index.ts +3 -0
  81. package/src/tooltip/tooltip.types.ts +29 -0
  82. package/src/utils/loadBaklavaResources.ts +24 -0
  83. package/tsconfig.json +28 -0
@@ -0,0 +1,178 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Dialog Component
4
+ *
5
+ * A Vue UI kit component for Baklava's `bl-dialog` web component.
6
+ * Provides a modal overlay for confirmations, forms, and important content
7
+ * with support for header and footer slots, controllable visibility, and programmatic open/close.
8
+ *
9
+ * @component
10
+ * @example
11
+ * ```vue
12
+ * <!-- Basic usage with v-model -->
13
+ * <template>
14
+ * <BvButton @click="showDialog = true">Open Dialog</BvButton>
15
+ * <BvDialog v-model:open="showDialog" caption="Dialog Title">
16
+ * <p>Dialog content goes here.</p>
17
+ * </BvDialog>
18
+ * </template>
19
+ * ```
20
+ *
21
+ * @example
22
+ * ```vue
23
+ * <!-- With header and footer slots -->
24
+ * <template>
25
+ * <BvDialog v-model:open="showDialog" caption="Confirm Action">
26
+ * <p>Are you sure you want to proceed?</p>
27
+ * <template #footer>
28
+ * <BvButton variant="tertiary" @click="showDialog = false">Cancel</BvButton>
29
+ * <BvButton variant="primary" @click="showDialog = false">Confirm</BvButton>
30
+ * </template>
31
+ * </BvDialog>
32
+ * </template>
33
+ * ```
34
+ *
35
+ * @example
36
+ * ```vue
37
+ * <!-- Closable with backdrop control -->
38
+ * <template>
39
+ * <BvDialog v-model:open="showDialog" caption="Closable" :closable="true" :backdrop="true">
40
+ * <p>This dialog has a close button and backdrop click.</p>
41
+ * </BvDialog>
42
+ * </template>
43
+ * ```
44
+ *
45
+ * @example
46
+ * ```vue
47
+ * <!-- Programmatic control via ref -->
48
+ * <template>
49
+ * <BvButton @click="dialogRef?.open()">Open</BvButton>
50
+ * <BvButton @click="dialogRef?.close()">Close</BvButton>
51
+ * <BvDialog ref="dialogRef" caption="Programmatic">
52
+ * <p>Opened and closed via ref methods.</p>
53
+ * </BvDialog>
54
+ * </template>
55
+ * ```
56
+ */
57
+ import { computed, onMounted, ref, watch } from "vue";
58
+ import { loadBaklavaResources } from "../utils/loadBaklavaResources";
59
+ import type { DialogProps } from "./dialog.types";
60
+
61
+ const SIZE_TO_WIDTH: Record<string, string> = {
62
+ small: "320px",
63
+ medium: "424px",
64
+ large: "560px",
65
+ };
66
+
67
+ const props = withDefaults(defineProps<DialogProps>(), {
68
+ open: false,
69
+ caption: undefined,
70
+ closable: undefined,
71
+ backdrop: undefined,
72
+ size: undefined,
73
+ });
74
+
75
+ const emit = defineEmits<{
76
+ /** Emitted when visibility changes. Use for two-way binding. */
77
+ "update:open": [open: boolean];
78
+ /** Emitted when the dialog is opened. */
79
+ open: [];
80
+ /** Emitted when the dialog is closed. */
81
+ close: [];
82
+ }>();
83
+
84
+ /** Reference to the underlying bl-dialog element. */
85
+ const dialogRef = ref<HTMLElement | null>(null);
86
+
87
+ /** Computed style for dialog width based on size prop. */
88
+ const dialogStyle = computed(() => {
89
+ if (!props.size) return undefined;
90
+ const width = SIZE_TO_WIDTH[props.size.toLowerCase()] ?? props.size;
91
+ return { "--bl-dialog-width": width };
92
+ });
93
+
94
+ /** Handles bl-dialog-open event. Syncs state and emits. */
95
+ const handleOpen = () => {
96
+ emit("update:open", true);
97
+ emit("open");
98
+ };
99
+
100
+ /** Handles bl-dialog-close event. Syncs state and emits. */
101
+ const handleClose = () => {
102
+ emit("update:open", false);
103
+ emit("close");
104
+ };
105
+
106
+ /**
107
+ * Handles bl-dialog-request-close event. Prevents closing when closable or backdrop
108
+ * is explicitly set to false for the given close source.
109
+ */
110
+ const handleRequestClose = (
111
+ e: CustomEvent<{ source: "close-button" | "keyboard" | "backdrop" }>,
112
+ ) => {
113
+ if (props.closable === false && e.detail.source === "close-button") {
114
+ e.preventDefault();
115
+ }
116
+ if (props.backdrop === false && e.detail.source === "backdrop") {
117
+ e.preventDefault();
118
+ }
119
+ };
120
+
121
+ /** Syncs props.open to the bl-dialog element's open property. */
122
+ function getBlDialog(el: HTMLElement | null): { open: boolean } | null {
123
+ return el as unknown as { open: boolean } | null;
124
+ }
125
+
126
+ watch(
127
+ () => props.open,
128
+ (newValue) => {
129
+ const blDialog = getBlDialog(dialogRef.value);
130
+ if (blDialog && blDialog.open !== newValue) {
131
+ blDialog.open = newValue;
132
+ }
133
+ },
134
+ { immediate: true },
135
+ );
136
+
137
+ onMounted(() => {
138
+ loadBaklavaResources();
139
+
140
+ const blDialog = getBlDialog(dialogRef.value);
141
+ if (blDialog && blDialog.open !== props.open) {
142
+ blDialog.open = props.open;
143
+ }
144
+ });
145
+
146
+ defineExpose({
147
+ /** Opens the dialog programmatically. */
148
+ open: () => {
149
+ const blDialog = getBlDialog(dialogRef.value);
150
+ if (blDialog) blDialog.open = true;
151
+ },
152
+ /** Closes the dialog programmatically. */
153
+ close: () => {
154
+ const blDialog = getBlDialog(dialogRef.value);
155
+ if (blDialog) blDialog.open = false;
156
+ },
157
+ });
158
+ </script>
159
+
160
+ <template>
161
+ <bl-dialog
162
+ ref="dialogRef"
163
+ :open="open"
164
+ :caption="caption"
165
+ :style="dialogStyle"
166
+ @bl-dialog-open="handleOpen"
167
+ @bl-dialog-close="handleClose"
168
+ @bl-dialog-request-close="handleRequestClose"
169
+ >
170
+ <div>
171
+ <slot v-if="$slots['header']" name="header" />
172
+ <slot />
173
+ </div>
174
+ <div v-if="$slots['footer']" slot="secondary-action">
175
+ <slot name="footer" />
176
+ </div>
177
+ </bl-dialog>
178
+ </template>
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Props for the Dialog component.
3
+ *
4
+ * @interface DialogProps
5
+ */
6
+ export interface DialogProps {
7
+ /** Whether the dialog is visible. */
8
+ open?: boolean;
9
+ /** Optional dialog title. Maps to bl-dialog's caption attribute. */
10
+ caption?: string;
11
+ /** Whether to show the close button in the header. When false, clicking the X is prevented. */
12
+ closable?: boolean;
13
+ /** Whether clicking the backdrop closes the dialog. When false, click-outside is prevented. */
14
+ backdrop?: boolean;
15
+ /** Dialog width. Accepts "small", "medium", "large" or a CSS value (e.g. "424px"). */
16
+ size?: string;
17
+ }
@@ -0,0 +1,2 @@
1
+ export { default as BvDialog } from "./Dialog.vue";
2
+ export type { DialogProps } from "./dialog.types";
@@ -0,0 +1,162 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Drawer Component
4
+ *
5
+ * A Vue UI kit component for Baklava's `bl-drawer` web component.
6
+ * Provides a side drawer for supplemental content with support for caption,
7
+ * embedded iframe content, external link, and programmable width.
8
+ *
9
+ * @component
10
+ * @example
11
+ * ```vue
12
+ * <!-- Basic usage -->
13
+ * <template>
14
+ * <BvButton @click="showDrawer = true">Open Drawer</BvButton>
15
+ * <BvDrawer v-model:open="showDrawer">
16
+ * <p>Drawer content goes here.</p>
17
+ * </BvDrawer>
18
+ * </template>
19
+ * ```
20
+ *
21
+ * @example
22
+ * ```vue
23
+ * <!-- With caption -->
24
+ * <template>
25
+ * <BvDrawer v-model:open="showDrawer" caption="Drawer Title">
26
+ * <p>Content with title.</p>
27
+ * </BvDrawer>
28
+ * </template>
29
+ * ```
30
+ *
31
+ * @example
32
+ * ```vue
33
+ * <!-- With embed URL (iframe) -->
34
+ * <template>
35
+ * <BvDrawer v-model:open="showDrawer" embed-url="https://example.com" />
36
+ * </template>
37
+ * ```
38
+ *
39
+ * @example
40
+ * ```vue
41
+ * <!-- With width -->
42
+ * <template>
43
+ * <BvDrawer v-model:open="showDrawer" width="large" caption="Wide Drawer">
44
+ * <p>Wider drawer content.</p>
45
+ * </BvDrawer>
46
+ * </template>
47
+ * ```
48
+ *
49
+ * @example
50
+ * ```vue
51
+ * <!-- Programmatic control via ref -->
52
+ * <template>
53
+ * <BvButton @click="drawerRef?.open()">Open</BvButton>
54
+ * <BvButton @click="drawerRef?.close()">Close</BvButton>
55
+ * <BvDrawer ref="drawerRef" caption="Programmatic">
56
+ * <p>Opened and closed via ref methods.</p>
57
+ * </BvDrawer>
58
+ * </template>
59
+ * ```
60
+ */
61
+ import { computed, onMounted, ref, watch } from "vue";
62
+ import { loadBaklavaResources } from "../utils/loadBaklavaResources";
63
+ import type { DrawerProps } from "./drawer.types";
64
+
65
+ const WIDTH_TO_PX: Record<string, string> = {
66
+ small: "320px",
67
+ medium: "424px",
68
+ large: "560px",
69
+ };
70
+
71
+ const props = withDefaults(defineProps<DrawerProps>(), {
72
+ open: false,
73
+ caption: undefined,
74
+ embedUrl: undefined,
75
+ externalLink: undefined,
76
+ width: undefined,
77
+ });
78
+
79
+ const emit = defineEmits<{
80
+ /** Emitted when visibility changes. Use for two-way binding. */
81
+ "update:open": [open: boolean];
82
+ /** Emitted when the drawer is opened. */
83
+ open: [];
84
+ /** Emitted when the drawer is closed. */
85
+ close: [];
86
+ }>();
87
+
88
+ /** Reference to the underlying bl-drawer element. */
89
+ const drawerRef = ref<HTMLElement | null>(null);
90
+
91
+ /** Computed width for bl-drawer. Maps small/medium/large to px or passes CSS value. */
92
+ const drawerWidth = computed(() => {
93
+ if (!props.width) return undefined;
94
+ const key = props.width.toLowerCase();
95
+ return WIDTH_TO_PX[key] ?? props.width;
96
+ });
97
+
98
+ /** Handles bl-drawer-open event. Syncs state and emits. */
99
+ const handleOpen = () => {
100
+ emit("update:open", true);
101
+ emit("open");
102
+ };
103
+
104
+ /** Handles bl-drawer-close event. Syncs state and emits. */
105
+ const handleClose = () => {
106
+ emit("update:open", false);
107
+ emit("close");
108
+ };
109
+
110
+ /** Syncs props.open to the bl-drawer element's open property. */
111
+ function getBlDrawer(el: HTMLElement | null): { open: boolean } | null {
112
+ return el as unknown as { open: boolean } | null;
113
+ }
114
+
115
+ watch(
116
+ () => props.open,
117
+ (newValue) => {
118
+ const blDrawer = getBlDrawer(drawerRef.value);
119
+ if (blDrawer && blDrawer.open !== newValue) {
120
+ blDrawer.open = newValue;
121
+ }
122
+ },
123
+ { immediate: true },
124
+ );
125
+
126
+ onMounted(() => {
127
+ loadBaklavaResources();
128
+
129
+ const blDrawer = getBlDrawer(drawerRef.value);
130
+ if (blDrawer && blDrawer.open !== props.open) {
131
+ blDrawer.open = props.open;
132
+ }
133
+ });
134
+
135
+ defineExpose({
136
+ /** Opens the drawer programmatically. */
137
+ open: () => {
138
+ const blDrawer = getBlDrawer(drawerRef.value);
139
+ if (blDrawer) blDrawer.open = true;
140
+ },
141
+ /** Closes the drawer programmatically. */
142
+ close: () => {
143
+ const blDrawer = getBlDrawer(drawerRef.value);
144
+ if (blDrawer) blDrawer.open = false;
145
+ },
146
+ });
147
+ </script>
148
+
149
+ <template>
150
+ <bl-drawer
151
+ ref="drawerRef"
152
+ :open="open"
153
+ :caption="caption"
154
+ :embed-url="embedUrl"
155
+ :external-link="externalLink"
156
+ :width="drawerWidth"
157
+ @bl-drawer-open="handleOpen"
158
+ @bl-drawer-close="handleClose"
159
+ >
160
+ <slot />
161
+ </bl-drawer>
162
+ </template>
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Props for the Drawer component.
3
+ *
4
+ * @interface DrawerProps
5
+ */
6
+ export interface DrawerProps {
7
+ /** Whether the drawer is visible. */
8
+ open?: boolean;
9
+ /** Drawer title. Maps to bl-drawer's caption attribute. */
10
+ caption?: string;
11
+ /** Iframe URL for embedded content. When set, drawer shows iframe instead of slot. */
12
+ embedUrl?: string;
13
+ /** External link URL - adds a button in the header. */
14
+ externalLink?: string;
15
+ /** Drawer width. Accepts "small", "medium", "large" or CSS value (e.g. "424px"). */
16
+ width?: string;
17
+ }
@@ -0,0 +1,2 @@
1
+ export { default as BvDrawer } from "./Drawer.vue";
2
+ export type { DrawerProps } from "./drawer.types";
@@ -0,0 +1,231 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Dropdown Component
4
+ *
5
+ * A Vue UI kit component for Baklava's `bl-dropdown`, `bl-dropdown-group`, and `bl-dropdown-item`
6
+ * web components. Can be used in two modes: slot mode (custom content via slots) or items
7
+ * mode (declarative menu items with optional grouping).
8
+ *
9
+ * @component
10
+ * @example
11
+ * ```vue
12
+ * <!-- Basic usage with label and items -->
13
+ * <template>
14
+ * <BvDropdown label="Actions" :items="menuItems">
15
+ * <template #item="{ item }">
16
+ * {{ item.caption }}
17
+ * </template>
18
+ * </BvDropdown>
19
+ * </template>
20
+ * ```
21
+ *
22
+ * @example
23
+ * ```vue
24
+ * <!-- Items mode with groups -->
25
+ * <template>
26
+ * <BvDropdown label="Menu" :items="groupedItems">
27
+ * <template #item="{ item }">
28
+ * {{ item.caption }}
29
+ * </template>
30
+ * </BvDropdown>
31
+ * </template>
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```vue
36
+ * <!-- Programmatic control via ref -->
37
+ * <template>
38
+ * <BvButton @click="dropdownRef?.open()">Open</BvButton>
39
+ * <BvButton @click="dropdownRef?.close()">Close</BvButton>
40
+ * <BvDropdown ref="dropdownRef" label="Menu" :items="items">
41
+ * <template #item="{ item }">{{ item.caption }}</template>
42
+ * </BvDropdown>
43
+ * </template>
44
+ * ```
45
+ */
46
+ import { computed, onMounted, ref, watch } from "vue";
47
+ import { loadBaklavaResources } from "../utils/loadBaklavaResources";
48
+ import type { DropdownItem, DropdownProps } from "./dropdown.types";
49
+
50
+ const props = withDefaults(defineProps<DropdownProps>(), {
51
+ open: false,
52
+ placement: undefined,
53
+ disabled: false,
54
+ trigger: undefined,
55
+ label: "Menu",
56
+ variant: undefined,
57
+ kind: undefined,
58
+ size: undefined,
59
+ icon: undefined,
60
+ items: undefined,
61
+ });
62
+
63
+ const emit = defineEmits<{
64
+ /** Emitted when visibility changes. Use for two-way binding. */
65
+ "update:open": [open: boolean];
66
+ /** Emitted when the dropdown is opened. */
67
+ open: [];
68
+ /** Emitted when the dropdown is closed. */
69
+ close: [];
70
+ /** Emitted when a dropdown item is clicked (from bl-dropdown-item-click). */
71
+ select: [event: CustomEvent];
72
+ }>();
73
+
74
+ /** Reference to the underlying bl-dropdown element. */
75
+ const dropdownRef = ref<HTMLElement | null>(null);
76
+
77
+ /** Determines if the component is in items mode (using items prop). */
78
+ const isItemsMode = computed(
79
+ () => Array.isArray(props.items) && props.items.length > 0,
80
+ );
81
+
82
+ /** Groups items by groupCaption for rendering bl-dropdown-group. */
83
+ const groupedItems = computed(() => {
84
+ if (!props.items) return [];
85
+ const groups = new Map<
86
+ string | undefined,
87
+ { item: DropdownItem; index: number }[]
88
+ >();
89
+ props.items.forEach((item, index) => {
90
+ const key = item.groupCaption ?? undefined;
91
+ const list = groups.get(key) ?? [];
92
+ list.push({ item, index });
93
+ groups.set(key, list);
94
+ });
95
+ return Array.from(groups.entries()).map(([groupCaption, entries]) => ({
96
+ groupCaption: groupCaption || undefined,
97
+ entries,
98
+ }));
99
+ });
100
+
101
+ /** Handles bl-dropdown-open event. Syncs state and emits. */
102
+ const handleOpen = () => {
103
+ emit("update:open", true);
104
+ emit("open");
105
+ };
106
+
107
+ /** Handles bl-dropdown-close event. Syncs state and emits. */
108
+ const handleClose = () => {
109
+ emit("update:open", false);
110
+ emit("close");
111
+ };
112
+
113
+ /** Handles bl-dropdown-item-click from items. Bubbles as select event. */
114
+ const handleItemClick = (event: CustomEvent) => {
115
+ emit("select", event);
116
+ };
117
+
118
+ type BlDropdownElement = {
119
+ opened?: boolean;
120
+ open: () => void;
121
+ close: () => void;
122
+ };
123
+
124
+ function getBlDropdown(el: HTMLElement | null): BlDropdownElement | null {
125
+ return el as unknown as BlDropdownElement | null;
126
+ }
127
+
128
+ /** Syncs props.open to the bl-dropdown element. */
129
+ watch(
130
+ () => props.open,
131
+ (newValue) => {
132
+ const blDropdown = getBlDropdown(dropdownRef.value);
133
+ if (blDropdown) {
134
+ if (newValue && !blDropdown.opened) {
135
+ blDropdown.open();
136
+ } else if (!newValue && blDropdown.opened) {
137
+ blDropdown.close();
138
+ }
139
+ }
140
+ },
141
+ { immediate: true },
142
+ );
143
+
144
+ onMounted(() => {
145
+ loadBaklavaResources();
146
+
147
+ const blDropdown = getBlDropdown(dropdownRef.value);
148
+ if (blDropdown && blDropdown.opened !== props.open) {
149
+ if (props.open) {
150
+ blDropdown.open();
151
+ } else {
152
+ blDropdown.close();
153
+ }
154
+ }
155
+ });
156
+
157
+ defineExpose({
158
+ /** Opens the dropdown programmatically. */
159
+ open: () => {
160
+ getBlDropdown(dropdownRef.value)?.open();
161
+ },
162
+ /** Closes the dropdown programmatically. */
163
+ close: () => {
164
+ getBlDropdown(dropdownRef.value)?.close();
165
+ },
166
+ /** Toggles the dropdown open/closed state. */
167
+ toggle: () => {
168
+ const blDropdown = getBlDropdown(dropdownRef.value);
169
+ if (blDropdown) {
170
+ if (blDropdown.opened) {
171
+ blDropdown.close();
172
+ } else {
173
+ blDropdown.open();
174
+ }
175
+ }
176
+ },
177
+ });
178
+ </script>
179
+
180
+ <template>
181
+ <bl-dropdown
182
+ ref="dropdownRef"
183
+ v-bind="{
184
+ label: props.label,
185
+ variant: props.variant,
186
+ kind: props.kind,
187
+ size: props.size,
188
+ icon: props.icon,
189
+ disabled: props.disabled === true ? true : undefined,
190
+ }"
191
+ @bl-dropdown-open="handleOpen"
192
+ @bl-dropdown-close="handleClose"
193
+ >
194
+ <!-- Items mode: render bl-dropdown-group and bl-dropdown-item -->
195
+ <template v-if="isItemsMode">
196
+ <template v-for="(group, gi) in groupedItems" :key="gi">
197
+ <bl-dropdown-group
198
+ v-if="group.groupCaption"
199
+ :caption="group.groupCaption"
200
+ >
201
+ <bl-dropdown-item
202
+ v-for="(entry, ii) in group.entries"
203
+ :key="`${gi}-${ii}`"
204
+ :icon="entry.item.icon"
205
+ :disabled="entry.item.disabled === true"
206
+ @bl-dropdown-item-click="handleItemClick"
207
+ >
208
+ <slot name="item" :item="entry.item" :index="entry.index">
209
+ {{ entry.item.caption }}
210
+ </slot>
211
+ </bl-dropdown-item>
212
+ </bl-dropdown-group>
213
+ <template v-else>
214
+ <bl-dropdown-item
215
+ v-for="(entry, ii) in group.entries"
216
+ :key="`${gi}-${ii}`"
217
+ :icon="entry.item.icon"
218
+ :disabled="entry.item.disabled === true"
219
+ @bl-dropdown-item-click="handleItemClick"
220
+ >
221
+ <slot name="item" :item="entry.item" :index="entry.index">
222
+ {{ entry.item.caption }}
223
+ </slot>
224
+ </bl-dropdown-item>
225
+ </template>
226
+ </template>
227
+ </template>
228
+ <!-- Slot mode: default slot for custom content -->
229
+ <slot v-else />
230
+ </bl-dropdown>
231
+ </template>