@studio-west/component-sw 0.11.36 → 0.12.0

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.
@@ -0,0 +1,218 @@
1
+ <script setup lang="ts">
2
+ import { computed, h, resolveComponent, getCurrentInstance, type VNode, type Component } from 'vue'
3
+
4
+ // Типы для конфигурации компонента
5
+ interface ComponentConfig {
6
+ [key: string]: any
7
+ }
8
+
9
+ interface MenuItemConfig {
10
+ slot?: string | ComponentConfig | Array<any>
11
+ [key: string]: any
12
+ }
13
+
14
+ interface MenuItemsObject {
15
+ [componentName: string]: MenuItemConfig | MenuItemConfig[]
16
+ }
17
+
18
+ type MenuItems = MenuItemsObject | Array<{ [componentName: string]: MenuItemConfig }>
19
+
20
+ interface Props {
21
+ menuItems: MenuItems
22
+ class?: string
23
+ }
24
+
25
+ const props = withDefaults(defineProps<Props>(), {
26
+ class: ''
27
+ })
28
+
29
+ const model = defineModel<boolean[]>()
30
+ const emit = defineEmits<{
31
+ click: [{ index: number; componentName: string; event: Event; value?: boolean }]
32
+ change: [{ index: number; componentName: string; event?: Event; value?: boolean }]
33
+ }>()
34
+
35
+ const instance = getCurrentInstance()
36
+
37
+ // Рекурсивная функция для создания компонента со слотами
38
+ const createComponentWithSlots = (
39
+ componentName: string | Component,
40
+ config: MenuItemConfig,
41
+ index: number | null = null,
42
+ parentIndex: number | null = null
43
+ ): VNode => {
44
+ let component: Component | string
45
+ let resolvedComponent: any = null
46
+
47
+ try {
48
+ if (typeof componentName === 'string') {
49
+ // Сначала ищем в глобально зарегистрированных компонентах
50
+ component = instance?.appContext?.components?.[componentName]
51
+ if (!component) {
52
+ try {
53
+ resolvedComponent = resolveComponent(componentName)
54
+ // Если это async компонент, нужно получить его определение
55
+ if (resolvedComponent && typeof resolvedComponent === 'object') {
56
+ component = resolvedComponent as Component
57
+ }
58
+ } catch (e) {
59
+ console.warn(`Failed to resolve component: ${componentName}`, e)
60
+ }
61
+ }
62
+ if (!component || typeof component === 'string') {
63
+ console.error(`Component "${componentName}" NOT FOUND`)
64
+ return h('div', { class: 'error' }, `Component "${componentName}" not found`)
65
+ }
66
+ } else {
67
+ component = componentName
68
+ }
69
+ } catch (e) {
70
+ console.warn(`Component "${componentName}" not found`, e)
71
+ return h('div', { class: 'error' }, `Component "${componentName}" not found`)
72
+ }
73
+
74
+ if (typeof config === 'object' && config !== null && !Array.isArray(config)) {
75
+ const slots: Record<string, () => any> = {}
76
+ const componentProps: Record<string, any> = {}
77
+
78
+ for (const [key, value] of Object.entries(config)) {
79
+ if (key === 'slot') {
80
+ slots.default = () => {
81
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
82
+ if ('text' in value) {
83
+ return h('span', { textContent: value.text })
84
+ }
85
+ if ('html' in value) {
86
+ return h('span', { innerHTML: value.html })
87
+ }
88
+ return Object.entries(value).map(([compName, compConfig]) => {
89
+ if (Array.isArray(compConfig)) {
90
+ return compConfig.map(item => createComponentWithSlots(compName, item))
91
+ } else {
92
+ return createComponentWithSlots(compName, compConfig)
93
+ }
94
+ }).flat()
95
+ }
96
+ if (Array.isArray(value)) {
97
+ return value.map(item => {
98
+ if (typeof item === 'object' && !Array.isArray(item)) {
99
+ const [nestedComponentName, nestedConfig] = Object.entries(item)[0]
100
+ return createComponentWithSlots(nestedComponentName, nestedConfig)
101
+ }
102
+ return item
103
+ })
104
+ }
105
+ return value
106
+ }
107
+ } else if (key.startsWith('slot.')) {
108
+ const slotName = key.substring(5)
109
+ slots[slotName] = () => {
110
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
111
+ if ('text' in value) {
112
+ return h('span', { textContent: value.text })
113
+ }
114
+ if ('html' in value) {
115
+ return h('span', { innerHTML: value.html })
116
+ }
117
+ return Object.entries(value).map(([compName, compConfig]) => {
118
+ if (Array.isArray(compConfig)) {
119
+ return compConfig.map(item => createComponentWithSlots(compName, item))
120
+ } else {
121
+ return createComponentWithSlots(compName, compConfig)
122
+ }
123
+ }).flat()
124
+ }
125
+ if (Array.isArray(value)) {
126
+ return value.map(item => {
127
+ if (typeof item === 'object' && !Array.isArray(item)) {
128
+ const [nestedComponentName, nestedConfig] = Object.entries(item)[0]
129
+ return createComponentWithSlots(nestedComponentName, nestedConfig)
130
+ }
131
+ return item
132
+ })
133
+ }
134
+ return value
135
+ }
136
+ } else {
137
+ componentProps[key] = value
138
+ }
139
+ }
140
+
141
+ // Назначаем двустороннюю привязку (v-model) всем компонентам первого уровня
142
+ if (index !== null) {
143
+ // Добавляем обработчики событий click и change
144
+ componentProps.onClick = (event: Event) => {
145
+ emit('click', { index, componentName: String(componentName), event, value: model.value?.[index] })
146
+ }
147
+ componentProps.onChange = (event: Event) => {
148
+ emit('change', { index, componentName: String(componentName), event, value: model.value?.[index] })
149
+ }
150
+ componentProps['onUpdate:modelValue'] = (value: boolean) => {
151
+ // Инициализируем model.value если он undefined
152
+ if (!model.value) {
153
+ model.value = []
154
+ }
155
+ const newValues = [...model.value]
156
+ // Расширяем массив если нужно
157
+ while (newValues.length <= index) {
158
+ newValues.push(false)
159
+ }
160
+ newValues[index] = value
161
+ model.value = newValues
162
+ // Эмитим событие change при изменении значения
163
+ emit('change', { index, componentName: String(componentName), value })
164
+ }
165
+ // Используем значение из model или false по умолчанию
166
+ const currentValue = model.value && index < model.value.length ? model.value[index] : false
167
+ componentProps.modelValue = currentValue
168
+ }
169
+
170
+ if (Object.keys(slots).length > 0) {
171
+ return h(component as Component, componentProps, slots)
172
+ }
173
+ return h(component as Component, componentProps)
174
+ }
175
+ return h(component as Component, config)
176
+ }
177
+
178
+ const menuComponents = computed(() => {
179
+ const components: Array<{ render: () => VNode }> = []
180
+
181
+ if (Array.isArray(props.menuItems)) {
182
+ props.menuItems.forEach((item, index) => {
183
+ if (typeof item === 'object' && item !== null) {
184
+ const [componentName, config] = Object.entries(item)[0]
185
+ components.push({
186
+ render: () => createComponentWithSlots(componentName, config as MenuItemConfig, index)
187
+ })
188
+ }
189
+ })
190
+ } else {
191
+ let index = 0
192
+ for (const [componentName, params] of Object.entries(props.menuItems)) {
193
+ if (Array.isArray(params)) {
194
+ params.forEach((param) => {
195
+ components.push({
196
+ render: () => createComponentWithSlots(componentName, param, index++)
197
+ })
198
+ })
199
+ } else if (typeof params === 'object' && params !== null) {
200
+ components.push({
201
+ render: () => createComponentWithSlots(componentName, params, index)
202
+ })
203
+ }
204
+ }
205
+ }
206
+ return components
207
+ })
208
+ </script>
209
+
210
+ <template>
211
+ <nav :class="'sw-menu ' + props.class">
212
+ <component
213
+ v-for="(item, index) in menuComponents"
214
+ :key="index"
215
+ :is="item.render"
216
+ />
217
+ </nav>
218
+ </template>
package/src/index.ts CHANGED
@@ -38,6 +38,7 @@ export type { SwButtonProps } from '@/types'
38
38
  export type { SwInputProps } from '@/types'
39
39
  export type { SwSwitchProps } from '@/types'
40
40
  export type { SwSelectProps } from '@/types'
41
+ export type { SwMenuProps } from '@/types'
41
42
 
42
43
  // Named exports
43
44
  export { components, Alert, Library }
@@ -6,6 +6,7 @@ export type SwButton = DefineComponent<import('./index').SwButtonProps>
6
6
  export type SwInput = DefineComponent<import('./index').SwInputProps>
7
7
  export type SwSwitch = DefineComponent<import('./index').SwSwitchProps>
8
8
  export type SwSelect = DefineComponent<import('./index').SwSelectProps>
9
+ export type SwMenu = DefineComponent<import('./index').SwMenuProps>
9
10
 
10
11
  // Augment Vue's GlobalComponents interface
11
12
  declare module 'vue' {
@@ -33,6 +34,7 @@ declare module 'vue' {
33
34
  SwTableColumn: DefineComponent
34
35
  SwTabs: DefineComponent
35
36
  SwTabsPane: DefineComponent
37
+ SwMenu: SwMenu
36
38
 
37
39
  // kebab-case component names (alternative)
38
40
  'sw-button': SwButton
@@ -57,6 +59,7 @@ declare module 'vue' {
57
59
  'sw-table-column': DefineComponent
58
60
  'sw-tabs': DefineComponent
59
61
  'sw-tabs-pane': DefineComponent
62
+ 'sw-menu': SwMenu
60
63
  }
61
64
  }
62
65
 
package/types/index.d.ts CHANGED
@@ -70,11 +70,17 @@ export interface SwSelectProps {
70
70
  token?: string
71
71
  }
72
72
 
73
+ export interface SwMenuProps {
74
+ menuItems: Record<string, any> | Array<{ [componentName: string]: any }>
75
+ class?: string
76
+ }
77
+
73
78
  // Export component constructor types
74
79
  export type SwButton = DefineComponent<SwButtonProps>
75
80
  export type SwInput = DefineComponent<SwInputProps>
76
81
  export type SwSwitch = DefineComponent<SwSwitchProps>
77
82
  export type SwSelect = DefineComponent<SwSelectProps>
83
+ export type SwMenu = DefineComponent<SwMenuProps>
78
84
 
79
85
  // Global component registration
80
86
  declare module 'vue' {
@@ -102,6 +108,7 @@ declare module 'vue' {
102
108
  SwTableColumn: DefineComponent
103
109
  SwTabs: DefineComponent
104
110
  SwTabsPane: DefineComponent
111
+ SwMenu: SwMenu
105
112
  }
106
113
  }
107
114
 
File without changes