@ziyarago/ui 0.14.0 → 0.15.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +5 -5
  3. package/dist/llms-full.txt +3 -3
  4. package/dist/llms.txt +2 -2
  5. package/dist/manifest.json +316 -2
  6. package/docs/INTEGRATION.md +3 -0
  7. package/package.json +10 -25
  8. package/src/core/components.js +0 -11
  9. package/src/core/components2.js +1 -71
  10. package/src/core/components3.js +2 -44
  11. package/src/core/components4.js +0 -38
  12. package/src/core/components6.js +0 -50
  13. package/src/core/components7.js +0 -34
  14. package/src/core/pages.js +2 -36
  15. package/src/i18n/components/index.js +0 -72
  16. package/src/internal.js +10 -0
  17. package/src/meta/index.js +2 -0
  18. package/src/meta/pages.js +1 -1
  19. package/src/react/index.js +0 -106
  20. package/src/vue/index.js +1 -1
  21. package/types/internal.d.ts +30 -0
  22. package/types/meta.d.ts +5 -0
  23. package/types/react.d.ts +136 -4143
  24. package/types/vue-global.d.ts +0 -106
  25. package/types/vue.d.ts +145 -3598
  26. package/docs/MIGRATION.md +0 -455
  27. package/src/compat/index.js +0 -469
  28. package/src/compat/map.js +0 -282
  29. package/src/compat/resolver.js +0 -22
  30. package/src/compat/vite.js +0 -29
  31. package/src/i18n/components/ActionCard.js +0 -5
  32. package/src/i18n/components/Actionsheet.js +0 -5
  33. package/src/i18n/components/CallCard.js +0 -5
  34. package/src/i18n/components/CallRow.js +0 -5
  35. package/src/i18n/components/Carousel.js +0 -5
  36. package/src/i18n/components/ChartCard.js +0 -5
  37. package/src/i18n/components/ChatAi.js +0 -5
  38. package/src/i18n/components/ChipInput.js +0 -5
  39. package/src/i18n/components/ContactPanel.js +0 -5
  40. package/src/i18n/components/DataGroup.js +0 -5
  41. package/src/i18n/components/DealCard.js +0 -5
  42. package/src/i18n/components/FullscreenDialog.js +0 -5
  43. package/src/i18n/components/Gallery.js +0 -5
  44. package/src/i18n/components/GuestCard.js +0 -5
  45. package/src/i18n/components/ImageUpload.js +0 -5
  46. package/src/i18n/components/ImageViewer.js +0 -5
  47. package/src/i18n/components/IncomingCall.js +0 -5
  48. package/src/i18n/components/InlineEdit.js +0 -5
  49. package/src/i18n/components/IntegrationCard.js +0 -5
  50. package/src/i18n/components/Map.js +0 -5
  51. package/src/i18n/components/NotificationBell.js +0 -5
  52. package/src/i18n/components/PricingCard.js +0 -5
  53. package/src/i18n/components/QiblaCompass.js +0 -5
  54. package/src/i18n/components/RamadanTracker.js +0 -5
  55. package/src/i18n/components/RangeCalendar.js +0 -5
  56. package/src/i18n/components/RangeSlider.js +0 -5
  57. package/src/i18n/components/RichEditor.js +0 -5
  58. package/src/i18n/components/Screensaver.js +0 -5
  59. package/src/i18n/components/Sortable.js +0 -5
  60. package/src/i18n/components/StepDialog.js +0 -5
  61. package/src/i18n/components/StepForm.js +0 -5
  62. package/src/i18n/components/TabTiles.js +0 -5
  63. package/src/i18n/components/Tour.js +0 -5
  64. package/src/i18n/components/TourCard.js +0 -5
  65. package/src/i18n/components/TourPopover.js +0 -5
  66. package/src/i18n/components/TreeSelect.js +0 -5
  67. package/types/compat-resolver.d.ts +0 -12
  68. package/types/compat-vite.d.ts +0 -11
  69. package/types/compat.d.ts +0 -176
@@ -1,469 +0,0 @@
1
- // @ziyarago/ui/compat · the OLD glass-ui-kit component names (UiInput, UiTable, UiSidebar, ...) as thin Vue wrappers over the new components.
2
- // Switch the imports first, refactor to the new names later. See docs/MIGRATION.md. Vue 3 only (the old kit never had a React build).
3
- import { h, defineComponent, Teleport, ref, readonly, computed, provide, inject, watch, getCurrentInstance, onMounted, onBeforeUnmount, Fragment, Comment } from 'vue';
4
- import * as K from '../vue/index.js';
5
- import { useUiTheme } from '../theme.js';
6
- import { ddRef } from '../core/components.js';
7
- import { config, configureCompat, SPECS, translate, emitsOf, buildHandlers, translateSlots, warnOnce, iconName, mapSize, twWidth, sortRows, camelizeKeys } from './map.js';
8
-
9
- export { configureCompat, iconName };
10
- export { COMPAT_NAMES, COMPAT_COMPONENTS, SPECS, translate } from './map.js';
11
-
12
- const N = n => K['Ui' + n];
13
- const cx = (...a) => a.filter(Boolean).join(' ');
14
- const truthy = v => v === '' || !!v;
15
-
16
- /** (key, fallback) => string. Order: configureCompat({ t }), then the host app's vue-i18n ($t, only when the key exists), then the English fallback. */
17
- function useT() {
18
- const g = (getCurrentInstance() || { appContext: { config: { globalProperties: {} } } }).appContext.config.globalProperties;
19
- return (key, fb) => {
20
- if (config.t) return config.t(key, fb);
21
- if (g.$t && (!g.$te || g.$te(key))) return g.$t(key);
22
- return fb;
23
- };
24
- }
25
-
26
- /** () => BCP 47 locale of the host vue-i18n (or undefined). Date pickers use it when no locale prop is passed, like the old kit did. */
27
- function useLocale() {
28
- const g = (getCurrentInstance() || { appContext: { config: { globalProperties: {} } } }).appContext.config.globalProperties;
29
- return () => (g.$i18n && typeof g.$i18n.locale === 'string' ? g.$i18n.locale : undefined);
30
- }
31
-
32
- const vtext = vnodes => [].concat(vnodes || []).map(v => (typeof v === 'string' || typeof v === 'number' ? String(v) : v && v.children ? vtext(typeof v.children === 'function' ? v.children() : v.children) : '')).join('');
33
-
34
- // ───────────────────────── table-driven wrappers ─────────────────────────
35
- // pre(attrs, slots) -> { attrs?, slots?, render? } lets a wrapper adjust input or bypass the new component entirely.
36
- const PRE = {
37
- Calendar: (attrs, _s, ctx) => (attrs.locale === undefined && ctx.locale() ? { attrs: { ...attrs, locale: ctx.locale() } } : undefined),
38
- Skeleton: attrs => (/(^|\s)h-/.test(String(attrs.class || '')) && attrs.variant !== 'text' && attrs.variant !== 'circle' ? { attrs: { ...attrs, height: '100%' } } : undefined),
39
- };
40
-
41
- function makeCompat(name) {
42
- return defineComponent({
43
- name: 'Ui' + name, inheritAttrs: false, emits: emitsOf(name),
44
- setup(_, { attrs, slots, emit }) {
45
- const t = useT(), locale = useLocale();
46
- return () => {
47
- let a = attrs, s = slots;
48
- const pre = PRE[name] && PRE[name](a, s, { t, locale });
49
- if (pre && pre.render) return pre.render();
50
- if (pre) { a = pre.attrs || a; s = pre.slots || s; }
51
- const r = translate(name, a, { ...config, t });
52
- warnOnce(name, r.dropped);
53
- return h(N(r.component), { ...r.props, ...buildHandlers(name, r.src, emit) }, translateSlots(name, s, r.src));
54
- };
55
- },
56
- });
57
- }
58
-
59
- const generated = {};
60
- for (const name of Object.keys(SPECS)) generated[name] = makeCompat(name);
61
-
62
- // ───────────────────────── Tabs (compound) ─────────────────────────
63
- const TabsKey = Symbol('UiTabs');
64
- export const UiTabs = defineComponent({
65
- name: 'UiTabs', props: { modelValue: { type: [String, Number], default: undefined }, variant: { type: String, default: 'pill' } }, emits: ['update:modelValue'],
66
- setup(props, { slots, emit }) {
67
- provide(TabsKey, { modelValue: computed(() => props.modelValue), variant: computed(() => props.variant), setValue: v => emit('update:modelValue', v) });
68
- return () => h('div', null, slots.default && slots.default());
69
- },
70
- });
71
- export const UiTabsList = defineComponent({
72
- name: 'UiTabsList',
73
- setup(_, { slots }) { const ctx = inject(TabsKey); return () => h('div', { class: ctx && ctx.variant.value === 'underline' ? 'zg-tabs' : 'zg-pt', role: 'tablist' }, slots.default && slots.default()); },
74
- });
75
- export const UiTab = defineComponent({
76
- name: 'UiTab', props: { value: { type: [String, Number], required: true }, disabled: Boolean },
77
- setup(props, { slots }) {
78
- const ctx = inject(TabsKey);
79
- return () => h('button', { type: 'button', role: 'tab', 'aria-selected': String(ctx.modelValue.value === props.value), disabled: props.disabled, onClick: () => !props.disabled && ctx.setValue(props.value) }, slots.default && slots.default());
80
- },
81
- });
82
- export const UiTabPanel = defineComponent({
83
- name: 'UiTabPanel', props: { value: { type: [String, Number], required: true } },
84
- setup(props, { slots }) { const ctx = inject(TabsKey); return () => (ctx.modelValue.value === props.value ? h('div', { role: 'tabpanel', tabindex: 0 }, slots.default && slots.default()) : null); },
85
- });
86
-
87
- // ───────────────────────── Radio / RadioGroup ─────────────────────────
88
- const RadioItem = defineComponent({ name: 'UiRadio', inheritAttrs: false, props: { value: null, label: String, hint: String, disabled: Boolean, modelValue: null, size: String, name: String, id: String }, emits: ['update:modelValue'],
89
- setup(props, { emit, attrs }) {
90
- return () => h(N('RadioGroup'), { ...attrs, modelValue: props.modelValue, size: props.size, options: [{ value: props.value, label: props.label, hint: props.hint, disabled: props.disabled }], onChange: v => emit('update:modelValue', v) });
91
- } });
92
- export const UiRadio = RadioItem;
93
- function collectRadios(vnodes, out = []) {
94
- for (const v of vnodes || []) {
95
- if (!v || v.type === Comment) continue;
96
- if (v.type === Fragment) collectRadios(v.children, out);
97
- else if (v.type === RadioItem) { const p = camelizeKeys(v.props || {}); out.push({ value: p.value, label: p.label, hint: p.hint, disabled: truthy(p.disabled) }); }
98
- }
99
- return out;
100
- }
101
- export const UiRadioGroup = defineComponent({
102
- name: 'UiRadioGroup', inheritAttrs: false, emits: ['update:modelValue'],
103
- setup(_, { attrs, slots, emit }) {
104
- return () => {
105
- const a = camelizeKeys(attrs), { modelValue, label, hint, error, size, disabled, orientation, name, ...rest } = a;
106
- return h(N('RadioGroup'), { ...rest, modelValue, label, hint, error: error === true ? ' ' : error || undefined, size, disabled: truthy(disabled), orientation, options: collectRadios(slots.default && slots.default()), onChange: v => emit('update:modelValue', v) });
107
- };
108
- },
109
- });
110
-
111
- // ───────────────────────── Dropdown / Menu / MenuItem / MenuSeparator ─────────────────────────
112
- const DropdownKey = Symbol('UiDropdown');
113
- export const UiDropdown = defineComponent({
114
- name: 'UiDropdown', inheritAttrs: false, props: { open: { type: Boolean, default: undefined }, placement: { type: String, default: 'bottom-start' }, offset: { type: Number, default: 8 }, closeOnOutsideClick: { type: Boolean, default: true }, closeOnEsc: { type: Boolean, default: true } }, emits: ['update:open'],
115
- setup(props, { slots, emit }) {
116
- const inner = ref(false), trig = ref(null), panel = ref(null), pos = ref({ position: 'fixed', top: '0px', left: '0px', visibility: 'hidden' });
117
- const isOpen = computed(() => (props.open !== undefined ? props.open : inner.value));
118
- const setOpen = v => { inner.value = v; emit('update:open', v); };
119
- const close = () => { if (isOpen.value) setOpen(false); }, toggle = () => setOpen(!isOpen.value);
120
- provide(DropdownKey, { close });
121
- // Panel is teleported to <body> and positioned with fixed coordinates (flip + clamp to the viewport), so overflow:hidden parents (tables, cards) never clip it.
122
- const place = () => {
123
- const t = trig.value, f = panel.value; if (!t || !f) return;
124
- const r = t.getBoundingClientRect(), fw = f.offsetWidth, fh = f.offsetHeight, pad = 8, off = props.offset;
125
- let [side, align = 'start'] = props.placement.split('-'), x, y;
126
- if (side === 'bottom' && r.bottom + off + fh > innerHeight - pad && r.top - off - fh >= pad) side = 'top'; else if (side === 'top' && r.top - off - fh < pad && r.bottom + off + fh <= innerHeight - pad) side = 'bottom';
127
- if (side === 'top' || side === 'bottom') { y = side === 'bottom' ? r.bottom + off : r.top - off - fh; x = align === 'end' ? r.right - fw : align === 'center' ? r.left + (r.width - fw) / 2 : r.left; }
128
- else { x = side === 'right' ? r.right + off : r.left - off - fw; y = align === 'end' ? r.bottom - fh : r.top; }
129
- pos.value = { position: 'fixed', top: Math.max(pad, Math.min(y, innerHeight - fh - pad)) + 'px', left: Math.max(pad, Math.min(x, innerWidth - fw - pad)) + 'px', visibility: 'visible' };
130
- };
131
- const onDown = e => { if (isOpen.value && props.closeOnOutsideClick && !(trig.value && trig.value.contains(e.target)) && !(panel.value && panel.value.contains(e.target))) close(); };
132
- const onKey = e => { if (e.key === 'Escape' && isOpen.value && props.closeOnEsc) { e.stopPropagation(); close(); const b = trig.value && trig.value.querySelector('button,[href],[tabindex]'); if (b) b.focus(); } };
133
- onMounted(() => { document.addEventListener('mousedown', onDown); document.addEventListener('keydown', onKey); addEventListener('resize', place); addEventListener('scroll', place, true); });
134
- onBeforeUnmount(() => { document.removeEventListener('mousedown', onDown); document.removeEventListener('keydown', onKey); removeEventListener('resize', place); removeEventListener('scroll', place, true); });
135
- watch(isOpen, o => { if (o) { pos.value = { ...pos.value, visibility: 'hidden' }; Promise.resolve().then(() => requestAnimationFrame(place)); } });
136
- return () => [
137
- h('span', { ref: trig, class: 'zg-dd-trig', onClick: toggle }, slots.trigger ? slots.trigger({ isOpen: isOpen.value, toggle }) : null),
138
- isOpen.value ? h(Teleport, { to: 'body' }, h('div', { ref: panel, class: 'zg-dd__panel', style: pos.value }, slots.default && slots.default())) : null,
139
- ];
140
- },
141
- });
142
- export const UiMenu = defineComponent({
143
- name: 'UiMenu', props: { minWidth: { type: String, default: '12rem' }, autofocus: { type: Boolean, default: true } },
144
- setup(props, { slots }) {
145
- const el = ref(null);
146
- const items = () => (el.value ? [...el.value.querySelectorAll("[role='menuitem']:not([aria-disabled='true'])")] : []);
147
- onMounted(() => { if (props.autofocus) Promise.resolve().then(() => { const i = items()[0]; if (i) i.focus(); }); });
148
- const onKeydown = e => {
149
- const list = items(); if (!list.length) return;
150
- const i = list.indexOf(document.activeElement), go = n => { e.preventDefault(); list[n].focus(); };
151
- if (e.key === 'ArrowDown') go((i + 1) % list.length); else if (e.key === 'ArrowUp') go(i <= 0 ? list.length - 1 : i - 1); else if (e.key === 'Home') go(0); else if (e.key === 'End') go(list.length - 1);
152
- };
153
- return () => h('div', { ref: el, class: 'zg-menu zg-menu--static', role: 'menu', style: { minWidth: props.minWidth }, onKeydown }, slots.default && slots.default());
154
- },
155
- });
156
- export const UiMenuItem = defineComponent({
157
- name: 'UiMenuItem', props: { disabled: Boolean, tone: { type: String, default: 'default' }, closeOnClick: { type: Boolean, default: true }, loading: Boolean }, emits: ['click'],
158
- setup(props, { slots, emit }) {
159
- const ctx = inject(DropdownKey, null);
160
- return () => h('button', {
161
- type: 'button', role: 'menuitem', disabled: props.disabled, 'aria-disabled': props.disabled ? 'true' : undefined, class: cx('zg-menu__i', props.tone === 'danger' && 'zg-menu__i--danger'),
162
- onClick: e => { if (props.disabled) return; emit('click', e); if (props.closeOnClick && ctx) ctx.close(); },
163
- }, slots['icon-left'] && slots['icon-left'](), h('span', { style: { flex: 1, textAlign: 'start' } }, slots.default && slots.default()), slots.shortcut ? h('span', { class: 'zg-kbd' }, slots.shortcut()) : null, slots['icon-right'] && slots['icon-right']());
164
- },
165
- });
166
- export const UiMenuSeparator = defineComponent({
167
- name: 'UiMenuSeparator', props: { label: String },
168
- setup(props) { return () => (props.label ? h('div', { class: 'zg-menu__lbl', role: 'separator' }, props.label) : h('div', { class: 'zg-menu__sep', role: 'separator' })); },
169
- });
170
-
171
- // ───────────────────────── Table ─────────────────────────
172
- export const UiTable = defineComponent({
173
- name: 'UiTable', inheritAttrs: false, emits: ['rowClick'],
174
- setup(_, { attrs, slots, emit }) {
175
- const t = useT();
176
- const sortKey = ref(null), sortDir = ref('asc');
177
- const value = (row, col) => (col.accessor ? col.accessor(row) : row[col.key]);
178
- return () => {
179
- const a = camelizeKeys(attrs), cols = a.columns || [], rows = a.rows || [];
180
- const sorted = sortKey.value && cols.find(c => c.key === sortKey.value) ? sortRows(rows, cols.find(c => c.key === sortKey.value), sortDir.value, value) : rows;
181
- const loading = truthy(a.loading), sk = a.skeletonRows || 5;
182
- const newCols = cols.map(c => ({
183
- key: c.key, sortable: c.sortable, align: c.align, width: twWidth(c.width),
184
- label: slots['header-' + c.key] ? slots['header-' + c.key]({ column: c }) : c.label,
185
- render: (_v, nr) => (nr.__sk ? h(N('Skeleton'), { lines: 1 }) : slots['cell-' + c.key] ? slots['cell-' + c.key]({ row: nr.__row, value: value(nr.__row, c), index: nr.__i }) : (value(nr.__row, c) ?? '')),
186
- }));
187
- const data = loading ? Array.from({ length: sk }, (_x, i) => ({ id: 'sk' + i, __sk: true })) : sorted.map((row, i) => ({ id: a.rowKey ? a.rowKey(row, i) : i, __row: row, __i: i }));
188
- const root = {};
189
- for (const k of Object.keys(attrs)) if (/^(aria|data)-|^(class|style|id)$/.test(k)) root[k] = attrs[k];
190
- return h(N('DataTable'), {
191
- ...root, columns: newCols, rows: data, size: a.density === 'compact' ? 'sm' : 'md', sortKey: sortKey.value, sortDir: sortDir.value,
192
- empty: t('common.noData', 'No data to display'),
193
- onSort: (key, dir) => { sortKey.value = key; sortDir.value = dir; },
194
- onRowClick: (nr) => { if (!nr.__sk) emit('rowClick', nr.__row, nr.__i); },
195
- }, slots.empty ? { empty: () => slots.empty() } : undefined);
196
- };
197
- },
198
- });
199
-
200
- // ───────────────────────── Sidebar / Navbar / SubNav ─────────────────────────
201
- const SidebarKey = Symbol('UiSidebar');
202
- export const UiSidebar = defineComponent({
203
- name: 'UiSidebar', props: { collapsed: { type: Boolean, default: undefined }, defaultCollapsed: Boolean, open: { type: Boolean, default: undefined }, storageKey: String, widthExpanded: String, widthCollapsed: String }, emits: ['update:collapsed', 'update:open'],
204
- setup(props, { slots, emit }) {
205
- const innerCollapsed = ref(props.defaultCollapsed), innerOpen = ref(false), desktop = ref(true);
206
- onMounted(() => {
207
- if (props.storageKey) { try { const v = localStorage.getItem(props.storageKey); if ((v === 'true' || v === 'false') && props.collapsed === undefined) innerCollapsed.value = v === 'true'; } catch { /* storage blocked */ } }
208
- if (typeof matchMedia === 'function') { const mq = matchMedia('(min-width:1024px)'); desktop.value = mq.matches; const f = () => { desktop.value = mq.matches; }; mq.addEventListener('change', f); onBeforeUnmount(() => mq.removeEventListener('change', f)); }
209
- });
210
- watch(() => props.collapsed, v => { if (v !== undefined && props.storageKey) { try { localStorage.setItem(props.storageKey, String(v)); } catch { /* storage blocked */ } } });
211
- const collapsed = computed(() => (props.collapsed !== undefined ? props.collapsed : innerCollapsed.value));
212
- const isOpen = computed(() => (props.open !== undefined ? props.open : innerOpen.value));
213
- const eff = computed(() => collapsed.value && desktop.value);
214
- const closeMobile = () => { innerOpen.value = false; emit('update:open', false); };
215
- provide(SidebarKey, { collapsed: eff, closeMobile });
216
- return () => [
217
- isOpen.value ? h('div', { class: 'zg-sidenav-scrim', 'aria-hidden': 'true', onClick: closeMobile }) : null,
218
- h('aside', { class: cx('zg-sidenav zg-sidenav--compat', eff.value && 'zg-sidenav--collapsed', isOpen.value && 'is-open'), 'data-theme': 'dark' },
219
- h('button', { type: 'button', class: 'zg-sidenav__close zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': 'Close menu', onClick: closeMobile }, h(N('Icon'), { name: 'x', size: 16 })),
220
- slots.header ? h('div', { class: 'zg-sidenav__brand' }, slots.header({ collapsed: eff.value })) : null,
221
- h('nav', { class: 'zg-sidenav__nav' }, slots.default && slots.default()),
222
- slots.footer ? h('div', { class: 'zg-sidenav__foot' }, slots.footer({ collapsed: eff.value })) : null),
223
- ];
224
- },
225
- });
226
- export const UiSidebarSection = defineComponent({
227
- name: 'UiSidebarSection', props: { label: String },
228
- setup(props, { slots }) { const ctx = inject(SidebarKey, null); return () => h('div', { class: 'zg-sidenav__sec' }, props.label && !(ctx && ctx.collapsed.value) ? h('div', { class: 'zg-sidenav__group' }, props.label) : null, slots.default && slots.default()); },
229
- });
230
- export const UiSidebarItem = defineComponent({
231
- name: 'UiSidebarItem', props: { label: { type: String, required: true }, icon: null, active: Boolean, badge: [Number, String], href: String, disabled: Boolean }, emits: ['click'],
232
- setup(props, { emit }) {
233
- const ctx = inject(SidebarKey, null);
234
- return () => h(props.href ? 'a' : 'button', {
235
- class: 'zg-navitem', 'aria-current': props.active ? 'page' : undefined, title: ctx && ctx.collapsed.value ? props.label : undefined, href: props.href, type: props.href ? undefined : 'button', disabled: props.href ? undefined : props.disabled, 'aria-disabled': props.disabled ? 'true' : undefined,
236
- onClick: e => { if (props.disabled) return; emit('click', e); if (ctx) ctx.closeMobile(); },
237
- }, props.icon ? h(props.icon, { size: 18, class: 'zg-navitem__ic' }) : null, h('span', null, props.label), props.badge !== undefined && props.badge !== null && props.badge !== '' ? h('span', { class: 'zg-navitem__count' }, props.badge) : null);
238
- },
239
- });
240
- export const UiNavbar = defineComponent({
241
- name: 'UiNavbar', props: { variant: String, size: String, sticky: Boolean },
242
- setup(props, { slots }) {
243
- return () => h('header', { class: cx('zg-topnav zg-topnav--compat', props.size === 'md' && 'zg-topnav--md'), style: props.sticky ? undefined : { position: 'static' } },
244
- h('div', { class: 'zg-topnav__brand' }, slots.left && slots.left()), h('div', { class: 'zg-topnav__mid' }, slots.center && slots.center()), h('div', { class: 'zg-topnav__actions' }, slots.right && slots.right()));
245
- },
246
- });
247
- export const UiSubNav = defineComponent({
248
- name: 'UiSubNav', props: { items: { type: Array, default: () => [] }, title: String, titleIcon: null, matchExact: { type: Boolean, default: true } },
249
- setup(props) {
250
- const g = getCurrentInstance().appContext.config.globalProperties;
251
- const path = () => (g.$route ? g.$route.path : typeof location !== 'undefined' ? location.pathname : '');
252
- const active = p => (props.matchExact ? path() === p : path() === p || path().startsWith(p + '/'));
253
- return () => h('nav', { class: 'zg-subnav zg-subnav--compat', 'aria-label': props.title },
254
- props.title ? h('div', { class: 'zg-sidenav__group' }, props.titleIcon ? h(props.titleIcon, { size: 16 }) : null, props.title) : null,
255
- ...props.items.map(it => h('a', { key: it.path, href: it.path, class: 'zg-navitem', 'aria-current': active(it.path) ? 'page' : undefined, onClick: e => { if (g.$router && !(e.metaKey || e.ctrlKey || e.shiftKey || e.button)) { e.preventDefault(); g.$router.push(it.path); } } },
256
- it.icon ? h(it.icon, { size: 16 }) : null, h('span', { style: { flex: 1, textAlign: 'start' } }, it.label), it.badge !== undefined && it.badge !== '' ? h('span', { class: 'zg-navitem__count' }, it.badge) : null)));
257
- },
258
- });
259
-
260
- // ───────────────────────── Toast ─────────────────────────
261
- const toasts = ref([]);
262
- let toastId = 0;
263
- export function useToast() {
264
- const dismiss = id => { const i = toasts.value.findIndex(t => t.id === id); if (i !== -1) toasts.value.splice(i, 1); };
265
- const push = input => {
266
- const id = `toast-${Date.now()}-${++toastId}`;
267
- const toast = { id, tone: input.tone || 'default', duration: input.duration ?? 5000, title: input.title, message: input.message, action: input.action };
268
- toasts.value.push(toast);
269
- if (toast.duration > 0 && typeof window !== 'undefined') window.setTimeout(() => dismiss(id), toast.duration);
270
- return id;
271
- };
272
- const tone = t => (message, options) => push({ tone: t, message, ...options });
273
- return { toasts, push, dismiss, clear: () => { toasts.value = []; }, success: tone('success'), info: tone('info'), warning: tone('warning'), danger: tone('danger') };
274
- }
275
- export const UiToastContainer = defineComponent({
276
- name: 'UiToastContainer', props: { position: { type: String, default: 'top-right' } },
277
- setup(props) {
278
- const { toasts: list, dismiss } = useToast();
279
- return () => h('div', { class: cx('zg-toasts', 'zg-toasts--' + props.position), 'aria-live': 'polite' }, list.value.map(t =>
280
- h('div', { class: 'zg-toasts__i', key: t.id },
281
- h(N('Toast'), { tone: t.tone === 'default' ? 'info' : t.tone, title: t.title }, () => [t.message, t.action ? h('div', { class: 'zg-toasts__act' }, h(N('Button'), { size: 'xs', variant: 'ghost', onClick: () => { t.action.onClick(); dismiss(t.id); } }, () => t.action.label)) : null]),
282
- h('button', { type: 'button', class: 'zg-toasts__x zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': 'Dismiss', onClick: () => dismiss(t.id) }, h(N('Icon'), { name: 'x', size: 14 })))));
283
- },
284
- });
285
-
286
- // ───────────────────────── Command palette ─────────────────────────
287
- const commands = ref([]), paletteOpen = ref(false);
288
- export function useCommandPalette() {
289
- return {
290
- commands, isOpen: paletteOpen,
291
- register: c => { const i = commands.value.findIndex(x => x.id === c.id); if (i !== -1) commands.value[i] = c; else commands.value.push(c); },
292
- unregister: id => { commands.value = commands.value.filter(c => c.id !== id); },
293
- clear: () => { commands.value = []; },
294
- open: () => { paletteOpen.value = true; }, close: () => { paletteOpen.value = false; }, toggle: () => { paletteOpen.value = !paletteOpen.value; },
295
- };
296
- }
297
- export const UiCommandPalette = defineComponent({
298
- name: 'UiCommandPalette', props: { shortcutKey: { type: String, default: 'k' }, disableShortcut: Boolean, emptyText: String, placeholder: String },
299
- setup(props) {
300
- const pal = useCommandPalette();
301
- const onKey = e => { if (!props.disableShortcut && (e.metaKey || e.ctrlKey) && e.key.toLowerCase() === props.shortcutKey) { e.preventDefault(); pal.toggle(); } };
302
- onMounted(() => document.addEventListener('keydown', onKey));
303
- onBeforeUnmount(() => document.removeEventListener('keydown', onKey));
304
- return () => h(N('CommandPalette'), {
305
- open: pal.isOpen.value, placeholder: props.placeholder,
306
- commands: pal.commands.value.map(c => ({ id: c.id, label: c.label, icon: iconName(c.icon), shortcut: c.shortcut, keywords: [c.description, c.group, ...(c.keywords || [])].filter(Boolean).join(' ') })),
307
- onRun: id => { const c = pal.commands.value.find(x => x.id === id); pal.close(); if (c) c.action(); },
308
- onClose: pal.close,
309
- });
310
- },
311
- });
312
-
313
- // ───────────────────────── FileUpload (adds the file list the old component had) ─────────────────────────
314
- const fileSize = b => (b < 1024 ? b + ' B' : b < 1048576 ? (b / 1024).toFixed(1) + ' KB' : (b / 1048576).toFixed(1) + ' MB');
315
- export const UiFileUpload = defineComponent({
316
- name: 'UiFileUpload', inheritAttrs: false, emits: ['update:modelValue', 'reject'],
317
- setup(_, { attrs, slots, emit }) {
318
- const t = useT(), inner = ref([]);
319
- return () => {
320
- const a = camelizeKeys(attrs), files = a.modelValue || inner.value, multiple = truthy(a.multiple);
321
- const set = list => { inner.value = list; emit('update:modelValue', list); };
322
- const root = {}; for (const k of Object.keys(attrs)) if (/^(aria|data)-|^(class|style|id)$/.test(k)) root[k] = attrs[k];
323
- return h('div', { ...root, style: [{ display: 'flex', flexDirection: 'column', gap: '10px' }, root.style] },
324
- h(N('FileUpload'), {
325
- label: a.label, hint: a.hint, error: a.error === true ? ' ' : a.error || undefined, accept: a.accept, maxSize: a.maxSize, multiple,
326
- title: slots.title ? vtext(slots.title()) : t('common.dropFilesOrBrowse', 'Drop files here or browse'),
327
- onChange: list => set(multiple ? [...files, ...list] : list.slice(0, 1)),
328
- onReject: (file, reason) => emit('reject', { file, cause: reason }),
329
- }),
330
- files.length ? h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '8px' } }, files.map((f, i) => h(N('Chip'), { key: f.name + i, size: 'sm', icon: 'file', onClose: () => set(files.filter((_x, j) => j !== i)) }, () => `${f.name} · ${fileSize(f.size)}`))) : null);
331
- };
332
- },
333
- });
334
-
335
- // ───────────────────────── DatePicker (adds range) and DateTimePicker ─────────────────────────
336
- const pad = n => String(n).padStart(2, '0');
337
- const RangePicker = defineComponent({
338
- name: 'UiRangePicker', props: { value: Object, label: String, hint: String, error: String, placeholder: String, disabled: Boolean, locale: String, weekStartsOn: Number, size: String }, emits: ['pick'],
339
- setup(props, { emit }) {
340
- const open = ref(false);
341
- const fmt = d => new Intl.DateTimeFormat(props.locale || 'en-GB', { dateStyle: 'medium' }).format(d);
342
- return () => {
343
- const v = props.value || {}, text = v.start ? fmt(v.start) + (v.end ? ' – ' + fmt(v.end) : ' – …') : props.placeholder || 'Select dates';
344
- return h('div', { class: 'zg-field', 'data-size': props.size }, props.label ? h('span', { class: 'zg-label' }, props.label) : null,
345
- h('div', { class: 'zg-select' }, h('button', { type: 'button', class: cx('zg-btn zg-select__trigger', props.error && 'zg-select__trigger--error'), disabled: props.disabled, 'aria-expanded': String(open.value), onClick: () => { open.value = !open.value; } }, h('span', null, text), h(N('Icon'), { name: 'calendar', size: 16 })),
346
- open.value ? h('div', { class: 'zg-select__list', 'data-zg-dd': '', style: { padding: '12px' }, ref: ddRef(() => { open.value = false; }) },
347
- h(N('RangeCalendar'), { modelValue: v.start ? [v.start, v.end || null] : [], locale: props.locale, weekStartsOn: props.weekStartsOn, onChange: r => { emit('pick', { start: r[0] || null, end: r[1] || null }); if (r[0] && r[1]) open.value = false; } })) : null),
348
- props.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, props.error) : props.hint ? h('span', { class: 'zg-hint' }, props.hint) : null);
349
- };
350
- },
351
- });
352
- const BaseDatePicker = generated.DatePicker;
353
- export const UiDatePicker = defineComponent({
354
- name: 'UiDatePicker', inheritAttrs: false, emits: ['update:modelValue'],
355
- setup(_, { attrs, emit }) {
356
- const t = useT(), locale = useLocale();
357
- return () => {
358
- const a = camelizeKeys(attrs);
359
- if (!truthy(a.range)) return h(BaseDatePicker, { ...attrs, locale: attrs.locale === undefined ? locale() : attrs.locale, 'onUpdate:modelValue': v => emit('update:modelValue', v) });
360
- return h(RangePicker, { value: a.modelValue, label: a.label, hint: a.hint, error: typeof a.error === 'string' ? a.error : undefined, placeholder: a.placeholder || t('common.selectDates', 'Select dates'), disabled: truthy(a.disabled), locale: a.locale === undefined ? locale() : a.locale, weekStartsOn: a.weekStartsOn, size: mapSize('control', a.size || 'lg'), onPick: r => emit('update:modelValue', r) });
361
- };
362
- },
363
- });
364
- export const UiDateTimePicker = defineComponent({
365
- name: 'UiDateTimePicker', inheritAttrs: false, emits: ['update:modelValue'],
366
- setup(_, { attrs, emit }) {
367
- const t = useT(), locale = useLocale(), buffer = ref('');
368
- const combine = (d, time) => { const m = /^(\d{1,2}):(\d{1,2})$/.exec(time || ''); return new Date(d.getFullYear(), d.getMonth(), d.getDate(), m ? Math.min(23, +m[1]) : 0, m ? Math.min(59, +m[2]) : 0).toISOString(); };
369
- return () => {
370
- const a = camelizeKeys(attrs), d = a.modelValue ? new Date(a.modelValue) : null, valid = d && !Number.isNaN(d.getTime());
371
- const date = valid ? d : null, time = valid ? `${pad(d.getHours())}:${pad(d.getMinutes())}` : buffer.value;
372
- const defaultTime = a.defaultTime === undefined ? '12:00' : a.defaultTime;
373
- const err = a.error === true ? ' ' : a.error || undefined;
374
- const root = {}; for (const k of Object.keys(attrs)) if (/^(aria|data)-|^(class|style|id)$/.test(k)) root[k] = attrs[k];
375
- return h('div', { ...root, class: cx('zg-field', root.class) },
376
- a.label ? h('span', { class: 'zg-label' }, a.label) : null,
377
- h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 140px', gap: '8px' } },
378
- h(N('DatePicker'), { modelValue: date, size: 'xl', clearable: true, locale: locale(), disabled: truthy(a.disabled), error: err ? ' ' : undefined, placeholder: a.placeholder || t('common.datePlaceholder', 'dd/mm/yyyy'), onChange: nd => emit('update:modelValue', nd ? combine(nd, time || defaultTime || '00:00') : null) }),
379
- h(N('TextField'), {
380
- modelValue: time, size: 'xl', icon: 'clock', placeholder: '23:59', maxlength: 5, inputmode: 'numeric', disabled: truthy(a.disabled), error: err ? ' ' : undefined,
381
- onChange: v => { let val = v; if (/^\d{2}$/.test(val) && !val.includes(':')) val += ':'; buffer.value = val; if (date && /^\d{1,2}:\d{1,2}$/.test(val)) emit('update:modelValue', combine(date, val)); },
382
- onBlur: () => { const dg = buffer.value.replace(/\D/g, '').slice(0, 4); if (dg.length >= 3) { buffer.value = `${dg.slice(0, 2)}:${dg.slice(2).padEnd(2, '0')}`; if (date) emit('update:modelValue', combine(date, buffer.value)); } },
383
- })),
384
- err && err.trim() ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, err) : a.hint ? h('span', { class: 'zg-hint' }, a.hint) : null);
385
- };
386
- },
387
- });
388
-
389
- // ───────────────────────── Tooltip (teleported, so overflow:hidden parents never clip it) ─────────────────────────
390
- export const UiTooltip = defineComponent({
391
- name: 'UiTooltip', props: { text: String, placement: { type: String, default: 'top' }, tone: { type: String, default: 'ink' }, delay: { type: Number, default: 0 }, disabled: Boolean, anchorClass: { type: String, default: 'inline-flex' } },
392
- setup(props, { slots }) {
393
- const anchor = ref(null), tip = ref(null), shown = ref(false), pos = ref({ top: '0px', left: '0px', visibility: 'hidden' });
394
- let timer = 0;
395
- const place = () => {
396
- const a = anchor.value, f = tip.value; if (!a || !f) return;
397
- const r = a.getBoundingClientRect(), w = f.offsetWidth, hh = f.offsetHeight, gap = 8, pad = 4;
398
- const fit = { top: r.top - gap - hh >= pad, bottom: r.bottom + gap + hh <= innerHeight - pad, left: r.left - gap - w >= pad, right: r.right + gap + w <= innerWidth - pad };
399
- let side = props.placement; if (!fit[side]) { const o = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }[side]; if (o && fit[o]) side = o; }
400
- const cx0 = r.left + r.width / 2 - w / 2, cy0 = r.top + r.height / 2 - hh / 2;
401
- const [x, y] = side === 'top' ? [cx0, r.top - gap - hh] : side === 'bottom' ? [cx0, r.bottom + gap] : side === 'left' ? [r.left - gap - w, cy0] : [r.right + gap, cy0];
402
- pos.value = { top: Math.max(pad, Math.min(y, innerHeight - hh - pad)) + 'px', left: Math.max(pad, Math.min(x, innerWidth - w - pad)) + 'px', visibility: 'visible' };
403
- };
404
- const show = () => { if (props.disabled) return; clearTimeout(timer); timer = setTimeout(() => { shown.value = true; Promise.resolve().then(() => requestAnimationFrame(place)); }, props.delay); };
405
- const hide = () => { clearTimeout(timer); shown.value = false; pos.value = { ...pos.value, visibility: 'hidden' }; };
406
- onBeforeUnmount(() => clearTimeout(timer));
407
- return () => [
408
- h('span', { ref: anchor, class: props.anchorClass, onMouseenter: show, onMouseleave: hide, onFocusin: show, onFocusout: hide }, slots.default && slots.default()),
409
- shown.value && !props.disabled && (slots.content || props.text) ? h(Teleport, { to: 'body' }, h('div', { ref: tip, role: 'tooltip', class: cx('zg-tipbox', props.tone === 'glass' && 'zg-tipbox--glass'), style: pos.value }, slots.content ? slots.content() : props.text)) : null,
410
- ];
411
- },
412
- });
413
-
414
- // ───────────────────────── small ones ─────────────────────────
415
- export const UiAvatarGroup = defineComponent({
416
- name: 'UiAvatarGroup', props: { spacing: { type: String, default: 'normal' }, reverse: Boolean },
417
- setup(props, { slots }) { return () => h('div', { class: cx('zg-avgroup', 'zg-avgroup--' + props.spacing, props.reverse && 'zg-avgroup--rev') }, slots.default && slots.default()); },
418
- });
419
- export const UiForm = defineComponent({
420
- name: 'UiForm', props: { novalidate: Boolean }, emits: ['submit'],
421
- setup(props, { slots, emit }) { return () => h('form', { novalidate: props.novalidate, onSubmit: e => { e.preventDefault(); emit('submit', e); } }, slots.default && slots.default()); },
422
- });
423
- const LANGS = [['ru', 'RU'], ['uz', 'UZ'], ['en', 'EN']];
424
- export const UiMultilangInput = defineComponent({
425
- name: 'UiMultilangInput', props: { modelValue: { type: Object, default: () => ({}) }, label: String, placeholder: String, required: Boolean, error: [String, Boolean], disabled: Boolean }, emits: ['update:modelValue'],
426
- setup(props, { emit }) {
427
- const active = ref('ru');
428
- return () => {
429
- const v = props.modelValue || {}, invalid = props.required && !LANGS.some(([c]) => (v[c] || '').trim());
430
- return h('div', { class: 'zg-field' },
431
- h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' } }, props.label ? h('span', { class: 'zg-label' }, props.label, props.required ? h('span', { class: 'zg-req' }, ' *') : null) : h('span'),
432
- h(N('SegmentedControl'), { size: 'sm', modelValue: active.value, items: LANGS.map(([c, l]) => ({ value: c, label: (v[c] || '').trim() && c !== active.value ? l + ' •' : l })), onChange: c => { if (!props.disabled) active.value = c; } })),
433
- h(N('TextField'), { modelValue: v[active.value] || '', placeholder: props.placeholder, disabled: props.disabled, size: 'lg', error: props.error === true || invalid ? ' ' : props.error || undefined, onChange: s => emit('update:modelValue', { ...v, [active.value]: s }) }),
434
- typeof props.error === 'string' && props.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, props.error) : null);
435
- };
436
- },
437
- });
438
-
439
- // ───────────────────────── theme ─────────────────────────
440
- const LEGACY_KEY = 'glass-ui-theme', themeRef = ref('light');
441
- // Read the storage BEFORE the kit's theme controller initialises: it writes its defaults (dark) to "zg-ui" on first use, which would hide the old choice.
442
- const boot = (() => { try { return typeof localStorage === 'undefined' ? {} : { legacy: localStorage.getItem(LEGACY_KEY), hasNew: !!JSON.parse(localStorage.getItem('zg-ui') || '{}').theme }; } catch { return {}; } })();
443
- let themeInit = false;
444
- /** Drop-in for the old useTheme(): { theme, setTheme, toggleTheme }. Also toggles the .dark class (for Tailwind dark:) next to the new data-theme attribute. Reads the old localStorage key once. */
445
- export function useTheme() {
446
- const ui = useUiTheme();
447
- if (!themeInit && typeof window !== 'undefined') {
448
- themeInit = true;
449
- const sync = tm => { themeRef.value = tm; document.documentElement.classList.toggle('dark', tm === 'dark'); try { localStorage.setItem(LEGACY_KEY, tm); } catch { /* storage blocked */ } };
450
- const legacy = boot.legacy, hasNew = boot.hasNew;
451
- if (!hasNew) ui.setTheme(legacy === 'light' || legacy === 'dark' ? legacy : (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'));
452
- sync(ui.theme);
453
- ui.subscribe(s => sync(s.theme));
454
- }
455
- return { theme: readonly(themeRef), setTheme: tm => ui.setTheme(tm), toggleTheme: () => ui.toggleTheme() };
456
- }
457
-
458
- // ───────────────────────── exports ─────────────────────────
459
- const all = {
460
- ...Object.fromEntries(Object.entries(generated).map(([k, v]) => ['Ui' + k, v])),
461
- UiDatePicker, UiTabs, UiTabsList, UiTab, UiTabPanel, UiRadio, UiRadioGroup, UiDropdown, UiMenu, UiMenuItem, UiMenuSeparator, UiTable, UiSidebar, UiSidebarSection, UiSidebarItem,
462
- UiNavbar, UiSubNav, UiToastContainer, UiCommandPalette, UiFileUpload, UiDateTimePicker, UiAvatarGroup, UiForm, UiMultilangInput, UiTooltip,
463
- };
464
- export const { UiButton, UiInput, UiTextarea, UiSelect, UiCombobox, UiCheckbox, UiSwitch, UiBadge, UiAlert, UiSkeleton, UiModal, UiConfirmModal, UiCard, UiProgress, UiBreadcrumbs, UiPagination, UiStatsCard, UiSegmentedControl, UiAvatar, UiCalendar, UiSpinner, UiDivider, UiBanner, UiEmptyState, UiDrawer, UiTag, UiOtpInput, UiKpiCard, UiFormField } = all;
465
- export const MultilangInput = UiMultilangInput;
466
- export const compatComponents = all;
467
- export const install = app => { for (const [n, c] of Object.entries(all)) app.component(n, c); };
468
- export default { install };
469
- export { mapSize };