@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.
- package/CHANGELOG.md +6 -0
- package/README.md +5 -5
- package/dist/llms-full.txt +3 -3
- package/dist/llms.txt +2 -2
- package/dist/manifest.json +316 -2
- package/docs/INTEGRATION.md +3 -0
- package/package.json +10 -25
- package/src/core/components.js +0 -11
- package/src/core/components2.js +1 -71
- package/src/core/components3.js +2 -44
- package/src/core/components4.js +0 -38
- package/src/core/components6.js +0 -50
- package/src/core/components7.js +0 -34
- package/src/core/pages.js +2 -36
- package/src/i18n/components/index.js +0 -72
- package/src/internal.js +10 -0
- package/src/meta/index.js +2 -0
- package/src/meta/pages.js +1 -1
- package/src/react/index.js +0 -106
- package/src/vue/index.js +1 -1
- package/types/internal.d.ts +30 -0
- package/types/meta.d.ts +5 -0
- package/types/react.d.ts +136 -4143
- package/types/vue-global.d.ts +0 -106
- package/types/vue.d.ts +145 -3598
- package/docs/MIGRATION.md +0 -455
- package/src/compat/index.js +0 -469
- package/src/compat/map.js +0 -282
- package/src/compat/resolver.js +0 -22
- package/src/compat/vite.js +0 -29
- package/src/i18n/components/ActionCard.js +0 -5
- package/src/i18n/components/Actionsheet.js +0 -5
- package/src/i18n/components/CallCard.js +0 -5
- package/src/i18n/components/CallRow.js +0 -5
- package/src/i18n/components/Carousel.js +0 -5
- package/src/i18n/components/ChartCard.js +0 -5
- package/src/i18n/components/ChatAi.js +0 -5
- package/src/i18n/components/ChipInput.js +0 -5
- package/src/i18n/components/ContactPanel.js +0 -5
- package/src/i18n/components/DataGroup.js +0 -5
- package/src/i18n/components/DealCard.js +0 -5
- package/src/i18n/components/FullscreenDialog.js +0 -5
- package/src/i18n/components/Gallery.js +0 -5
- package/src/i18n/components/GuestCard.js +0 -5
- package/src/i18n/components/ImageUpload.js +0 -5
- package/src/i18n/components/ImageViewer.js +0 -5
- package/src/i18n/components/IncomingCall.js +0 -5
- package/src/i18n/components/InlineEdit.js +0 -5
- package/src/i18n/components/IntegrationCard.js +0 -5
- package/src/i18n/components/Map.js +0 -5
- package/src/i18n/components/NotificationBell.js +0 -5
- package/src/i18n/components/PricingCard.js +0 -5
- package/src/i18n/components/QiblaCompass.js +0 -5
- package/src/i18n/components/RamadanTracker.js +0 -5
- package/src/i18n/components/RangeCalendar.js +0 -5
- package/src/i18n/components/RangeSlider.js +0 -5
- package/src/i18n/components/RichEditor.js +0 -5
- package/src/i18n/components/Screensaver.js +0 -5
- package/src/i18n/components/Sortable.js +0 -5
- package/src/i18n/components/StepDialog.js +0 -5
- package/src/i18n/components/StepForm.js +0 -5
- package/src/i18n/components/TabTiles.js +0 -5
- package/src/i18n/components/Tour.js +0 -5
- package/src/i18n/components/TourCard.js +0 -5
- package/src/i18n/components/TourPopover.js +0 -5
- package/src/i18n/components/TreeSelect.js +0 -5
- package/types/compat-resolver.d.ts +0 -12
- package/types/compat-vite.d.ts +0 -11
- package/types/compat.d.ts +0 -176
package/src/compat/index.js
DELETED
|
@@ -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 };
|