@ziyarago/ui 0.2.0 → 0.4.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 +12 -0
- package/README.md +24 -2
- package/dist/llms-full.txt +101 -9
- package/dist/llms.txt +2 -2
- package/dist/manifest.json +412 -20
- package/dist/style.css +73 -4
- package/docs/INTEGRATION.md +153 -0
- package/docs/MIGRATION.md +453 -0
- package/package.json +88 -8
- package/src/compat/index.js +468 -0
- package/src/compat/map.js +282 -0
- package/src/compat/resolver.js +22 -0
- package/src/compat/vite.js +29 -0
- package/src/core/bools.js +4 -0
- package/src/core/components.js +40 -31
- package/src/core/components2.js +52 -47
- package/src/core/components3.js +8 -7
- package/src/core/components4.js +25 -24
- package/src/core/components6.js +30 -20
- package/src/core/components7.js +10 -9
- package/src/core/index.js +2 -0
- package/src/i18n/components/ActionCard.js +5 -0
- package/src/i18n/components/Actionsheet.js +5 -0
- package/src/i18n/components/Alert.js +5 -0
- package/src/i18n/components/Breadcrumbs.js +5 -0
- package/src/i18n/components/Calendar.js +5 -0
- package/src/i18n/components/CallCard.js +5 -0
- package/src/i18n/components/CallRow.js +5 -0
- package/src/i18n/components/Carousel.js +5 -0
- package/src/i18n/components/ChartCard.js +5 -0
- package/src/i18n/components/ChatAi.js +5 -0
- package/src/i18n/components/Chip.js +5 -0
- package/src/i18n/components/ChipInput.js +5 -0
- package/src/i18n/components/CodeInput.js +5 -0
- package/src/i18n/components/Combobox.js +5 -0
- package/src/i18n/components/CommandPalette.js +5 -0
- package/src/i18n/components/ConfirmDialog.js +5 -0
- package/src/i18n/components/ContactPanel.js +5 -0
- package/src/i18n/components/CopyButton.js +5 -0
- package/src/i18n/components/DataGroup.js +5 -0
- package/src/i18n/components/DataState.js +5 -0
- package/src/i18n/components/DataTable.js +5 -0
- package/src/i18n/components/DatePicker.js +5 -0
- package/src/i18n/components/DealCard.js +5 -0
- package/src/i18n/components/Drawer.js +5 -0
- package/src/i18n/components/ErrorPage.js +5 -0
- package/src/i18n/components/FileUpload.js +5 -0
- package/src/i18n/components/FullscreenDialog.js +5 -0
- package/src/i18n/components/Gallery.js +5 -0
- package/src/i18n/components/GuestCard.js +5 -0
- package/src/i18n/components/ImageUpload.js +5 -0
- package/src/i18n/components/ImageViewer.js +5 -0
- package/src/i18n/components/IncomingCall.js +5 -0
- package/src/i18n/components/InlineEdit.js +5 -0
- package/src/i18n/components/IntegrationCard.js +5 -0
- package/src/i18n/components/LoginForm.js +5 -0
- package/src/i18n/components/Map.js +5 -0
- package/src/i18n/components/Modal.js +5 -0
- package/src/i18n/components/MultiSelect.js +5 -0
- package/src/i18n/components/NotificationBell.js +5 -0
- package/src/i18n/components/NumberInput.js +5 -0
- package/src/i18n/components/Pagination.js +5 -0
- package/src/i18n/components/PasswordInput.js +5 -0
- package/src/i18n/components/Popconfirm.js +5 -0
- package/src/i18n/components/PricingCard.js +5 -0
- package/src/i18n/components/RamadanTracker.js +5 -0
- package/src/i18n/components/RangeCalendar.js +5 -0
- package/src/i18n/components/RangeSlider.js +5 -0
- package/src/i18n/components/Rating.js +5 -0
- package/src/i18n/components/RichEditor.js +5 -0
- package/src/i18n/components/Screensaver.js +5 -0
- package/src/i18n/components/SearchInput.js +5 -0
- package/src/i18n/components/SideNav.js +5 -0
- package/src/i18n/components/Snackbar.js +5 -0
- package/src/i18n/components/Sortable.js +5 -0
- package/src/i18n/components/Spinner.js +5 -0
- package/src/i18n/components/StatusPage.js +5 -0
- package/src/i18n/components/StepDialog.js +5 -0
- package/src/i18n/components/StepForm.js +5 -0
- package/src/i18n/components/TabTiles.js +5 -0
- package/src/i18n/components/Tag.js +5 -0
- package/src/i18n/components/TopNav.js +5 -0
- package/src/i18n/components/Tour.js +5 -0
- package/src/i18n/components/TourCard.js +5 -0
- package/src/i18n/components/TourPopover.js +5 -0
- package/src/i18n/components/TreeSelect.js +5 -0
- package/src/i18n/components/Validation.js +5 -0
- package/src/i18n/components/index.js +135 -0
- package/src/i18n/index.js +50 -0
- package/src/meta/actions.js +10 -3
- package/src/meta/data.js +8 -3
- package/src/meta/feedback.js +2 -1
- package/src/meta/forms.js +44 -4
- package/src/meta/media.js +7 -2
- package/src/meta/navigation.js +3 -0
- package/src/meta/overlay.js +8 -2
- package/src/nuxt/module.mjs +2 -2
- package/src/nuxt/plugin.mjs +6 -5
- package/src/react/index.js +202 -8
- package/src/resolver.js +33 -0
- package/src/styles/components.css +7 -0
- package/src/styles/components6.css +2 -0
- package/src/styles/effects.css +6 -4
- package/src/styles/extras.css +55 -0
- package/src/styles/index.css +1 -0
- package/src/theme.js +16 -4
- package/src/vue/global.js +2 -0
- package/src/vue/index.js +13 -5
- package/types/compat-resolver.d.ts +12 -0
- package/types/compat-vite.d.ts +11 -0
- package/types/compat.d.ts +176 -0
- package/types/i18n.d.ts +14 -0
- package/types/meta.d.ts +4 -0
- package/types/nuxt.d.ts +1 -1
- package/types/react.d.ts +6282 -186
- package/types/resolver.d.ts +14 -0
- package/types/shared.d.ts +7 -7
- package/types/theme.d.ts +20 -1
- package/types/vue-global.d.ts +193 -0
- package/types/vue.d.ts +6178 -186
package/src/core/components2.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// @ziyarago/ui · components, part 2 — Forms, Surfaces, Actions, Data, Navigation, Layout, CRM, Hotels.
|
|
2
|
-
import { icon, cx, fmt, dzp, dzf, dzb, ddRef } from './components.js';
|
|
2
|
+
import { icon, cx, fmt, dzp, dzf, dzb, ddRef, pick } from './components.js';
|
|
3
3
|
import { trap } from './dialog.js';
|
|
4
|
+
import { t, getUiLocale, intlLocale } from '../i18n/index.js';
|
|
5
|
+
const dloc = p => p.locale || (getUiLocale() === 'en' ? 'en-GB' : intlLocale());
|
|
4
6
|
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
5
7
|
const num = v => Number.isFinite(+v) && v !== null && v !== '' ? +v : 0;
|
|
6
8
|
const arr = v => Array.isArray(v) ? v : [];
|
|
@@ -24,106 +26,109 @@ let dragging = null;
|
|
|
24
26
|
const ROOM_TONES = { occupied: 'brand', clean: 'success', dirty: 'warning', blocked: 'danger' };
|
|
25
27
|
const TONE_OK = ['brand', 'sky', 'success', 'warning', 'danger', 'neutral'];
|
|
26
28
|
const CALL_TONES = { answered: 'success', completed: 'success', missed: 'danger', declined: 'danger', rejected: 'danger', failed: 'danger', voicemail: 'warning', busy: 'warning', 'no answer': 'warning', ringing: 'info', 'in progress': 'info' };
|
|
27
|
-
const pd = v => { if (v == null || v === '') return null; if (v instanceof Date) return isNaN(v) ? null : v; if (typeof v === 'string') { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v.trim()); if (m) return new Date(+m[1], +m[2] - 1, +m[3]); } const d = new Date(v); return isNaN(d) ? null : d; };
|
|
28
|
-
const lab =(h, p, body) => h('label', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, body, p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null);
|
|
29
|
+
export const pd = v => { if (v == null || v === '') return null; if (v instanceof Date) return isNaN(v) ? null : v; if (typeof v === 'string') { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v.trim()); if (m) return new Date(+m[1], +m[2] - 1, +m[3]); } const d = new Date(v); return isNaN(d) ? null : d; };
|
|
30
|
+
const lab =(h, p, body) => h('label', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label, p.required ? h('span', { class: 'zg-req', 'aria-hidden': 'true' }, ' *') : null) : null, body, p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null);
|
|
29
31
|
const field = (h, p, pre, input, suf) => h('span', { class: cx('zg-input', p.error && 'zg-input--error', p.disabled && 'zg-input--disabled') }, pre || null, input, suf || null);
|
|
30
32
|
const TONE = { success: 'check', danger: 'circle-alert', warning: 'triangle-alert' };
|
|
31
33
|
const sel = (h, o, on) => ({ 'aria-selected': String(on), role: 'option' });
|
|
32
34
|
|
|
33
35
|
export const more = {
|
|
34
36
|
/* ── Surfaces ── */
|
|
35
|
-
Alert: { render: ({ h, props: p, children }) => h('div', { class: cx('zg-alert', 'zg-alert--' + (p.tone || 'info'), p.banner && 'zg-banner'), role: 'alert' }, h('span', { class: 'zg-alert__i' }, icon(h, TONE[p.tone] || 'info', 18)), h('div', { class: 'zg-alert__b' }, p.title ? h('b', null, p.title) : null, h('span', null, ...children)), p.onClose ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': '
|
|
37
|
+
Alert: { render: ({ h, props: p, children, slots }) => h('div', { class: cx('zg-alert', 'zg-alert--' + (p.tone || 'info'), p.banner && 'zg-banner'), role: 'alert' }, p.hideIcon ? null : h('span', { class: 'zg-alert__i' }, slots.icon || icon(h, TONE[p.tone] || 'info', 18)), h('div', { class: 'zg-alert__b' }, (slots.title || p.title) ? h('b', null, slots.title || p.title) : null, h('span', null, ...children), slots.actions ? h('div', { class: 'zg-alert__act' }, slots.actions) : null), p.onClose ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Alert', 'close'), onClick: p.onClose }, icon(h, 'x')) : null) },
|
|
36
38
|
Banner: { render: ({ C, h, props: p, children }) => h(C.Alert, { ...p, banner: true }, ...children) },
|
|
37
39
|
Accordion: { state: { open: null }, render: ({ h, props: p, state, set }) => h('div', { class: 'zg-acc' }, ...(p.items || []).map(it => { const o = state.open === it.id; return h('div', { key: it.id, class: 'zg-acc__i', 'data-open': String(o) }, h('button', { type: 'button', 'aria-expanded': String(o), class: 'zg-acc__h', onClick: () => set({ open: o ? null : it.id }) }, it.title, icon(h, 'chevron-down', 16)), o ? h('div', { class: 'zg-acc__b' }, it.body) : null); })) },
|
|
38
40
|
Skeleton: { render: ({ h, props: p }) => h('div', { class: 'zg-skelw' }, ...Array.from({ length: p.lines || 1 }, (_, i) => h('span', { key: i, class: 'zg-skel zg-skel--' + (p.variant || 'text'), style: { width: p.variant === 'circle' ? ({ xs: 24, sm: 32, md: 40, lg: 56, xl: 72 }[p.size] || p.size || 40) + 'px' : (i === (p.lines || 1) - 1 && (p.lines || 1) > 1 ? '60%' : p.width || '100%'), height: p.height || (p.variant === 'circle' ? ({ xs: 24, sm: 32, md: 40, lg: 56, xl: 72 }[p.size] || p.size || 40) + 'px' : undefined) } }))) },
|
|
39
|
-
Spinner: { render: ({ h, props: p }) => h('span', { class: 'zg-spin', role: 'status', 'aria-label': '
|
|
41
|
+
Spinner: { render: ({ h, props: p }) => h('span', { class: 'zg-spin', role: 'status', 'aria-label': t('Spinner', 'loading'), style: { fontSize: ({ xs: 14, sm: 16, md: 20, lg: 28, xl: 36 }[p.size] || p.size || 20) + 'px' } }) },
|
|
40
42
|
Divider: { render: ({ h, props: p }) => h('div', { class: 'zg-div', role: 'separator' }, p.label ? h('span', null, p.label) : null) },
|
|
41
43
|
EmptyState: { render: ({ h, props: p, children }) => h('div', { class: 'zg-es' }, p.image ? h('span', { class: 'zg-es__img', style: { backgroundImage: `url(${p.image})` } }) : h('span', { class: 'zg-es__ico' }, icon(h, p.icon || 'inbox', 28)), h('b', null, p.title), p.description ? h('span', null, p.description) : null, children.length ? h('div', { class: 'zg-es__a' }, ...children) : null) },
|
|
42
|
-
ErrorPage: { render: ({ C, h, props: p, children }) => h('div', { class: 'zg-es zg-es--page' }, h('span', { class: 'zg-num', style: { fontSize: 'clamp(80px,14vw,160px)', lineHeight: 1, color: 'var(--zg-brand-text)' } }, p.code || '404'), h('b', { style: { fontSize: '24px' } }, p.title || '
|
|
43
|
-
DataState: { render: ({ C, h, props: p, children }) => ({ ready: () => h('div', null, ...children), loading: () => h('div', { class: 'zg-es' }, h(C.Spinner, { size: 28 }), h('b', null, p.title || '
|
|
44
|
+
ErrorPage: { render: ({ C, h, props: p, children }) => h('div', { class: 'zg-es zg-es--page' }, h('span', { class: 'zg-num', style: { fontSize: 'clamp(80px,14vw,160px)', lineHeight: 1, color: 'var(--zg-brand-text)' } }, p.code || '404'), h('b', { style: { fontSize: '24px' } }, p.title || t('ErrorPage', 'title')), h('span', null, p.description || t('ErrorPage', 'description')), h('div', { class: 'zg-es__a' }, ...children)) },
|
|
45
|
+
DataState: { render: ({ C, h, props: p, children }) => ({ ready: () => h('div', null, ...children), loading: () => h('div', { class: 'zg-es' }, h(C.Spinner, { size: 28 }), h('b', null, p.title || t('DataState', 'loading'))), empty: () => h(C.EmptyState, { title: p.title || t('DataState', 'empty'), description: p.description, icon: 'inbox' }), error: () => h(C.EmptyState, { title: p.title || t('DataState', 'error'), description: p.description, icon: 'circle-alert' }, h(C.Button, { onClick: p.onRetry }, p.retryLabel || t('DataState', 'retry'))), offline: () => h(C.EmptyState, { title: p.title || t('DataState', 'offline'), description: p.description || t('DataState', 'offlineDesc'), icon: 'wifi-off' }, h(C.Button, { onClick: p.onRetry }, p.retryLabel || t('DataState', 'tryAgain'))) }[p.state || 'ready'])() },
|
|
44
46
|
LoadingOverlay: { render: ({ C, h, props: p, children }) => h('div', { class: 'zg-lo' }, ...children, p.loading ? h('div', { class: 'zg-lo__o' }, h(C.Spinner, { size: 28 }), p.label ? h('span', null, p.label) : null) : null) },
|
|
45
|
-
ConfirmDialog: { render: ({ C, h, props: p }) => h(C.Modal, { open: p.open, size: p.size, title: p.title, onClose: p.onCancel, footer: [h(C.Button, { key: 'c', variant: 'ghost', onClick: p.onCancel }, p.cancelLabel || '
|
|
46
|
-
Popconfirm: { state: { open: false }, render: ({ C, h, props: p, state, set }) => h('span', { class: 'zg-dd' }, h(C.Button, { size: p.size || 'sm', variant: p.variant || 'danger', onClick: () => set({ open: !state.open }) }, p.label), state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, h('b', { style: { fontSize: '14px' } }, p.title), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: 'xs', variant: 'ghost', onClick: () => set({ open: false }) }, p.cancelLabel || '
|
|
47
|
+
ConfirmDialog: { render: ({ C, h, props: p, children }) => h(C.Modal, { open: p.open, size: p.size, title: p.title, onClose: p.onCancel, footer: [h(C.Button, { key: 'c', variant: 'ghost', onClick: p.onCancel }, p.cancelLabel || t('ConfirmDialog', 'cancel')), h(C.Button, { key: 'k', variant: p.tone === 'danger' ? 'danger' : 'primary', loading: p.loading, onClick: p.onConfirm }, p.confirmLabel || t('ConfirmDialog', 'confirm'))] }, h('span', { style: { color: 'var(--zg-text-2)', lineHeight: 1.6 } }, p.description, ...children)) },
|
|
48
|
+
Popconfirm: { state: { open: false }, render: ({ C, h, props: p, state, set }) => h('span', { class: 'zg-dd' }, h(C.Button, { size: p.size || 'sm', variant: p.variant || 'danger', onClick: () => set({ open: !state.open }) }, p.label), state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, h('b', { style: { fontSize: '14px' } }, p.title), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: 'xs', variant: 'ghost', onClick: () => set({ open: false }) }, p.cancelLabel || t('Popconfirm', 'no')), h(C.Button, { size: 'xs', variant: 'primary', onClick: () => { set({ open: false }); p.onConfirm && p.onConfirm(); } }, p.confirmLabel || t('Popconfirm', 'yes')))) : null) },
|
|
47
49
|
Carousel: { state: { i: 0 }, render: ({ h, props: p, state, set }) => { const items = arr(p.items).map(x => x || {}), n = items.length, i = idx(p, state, n), it = items[i] || {}, L = p.labels || {};
|
|
48
50
|
const to = k => { if (!n) return; k = ((k % n) + n) % n; if (k === i) return; if (p.index === undefined || p.index === null) set({ i: k }); p.onChange && p.onChange(k); };
|
|
49
51
|
const ms = p.autoplay === true ? 5000 : n > 1 && num(p.autoplay) > 0 ? num(p.autoplay) : 0;
|
|
50
|
-
return h('div', { class: 'zg-car', role: 'region', 'aria-roledescription': 'carousel', 'aria-label': p.label || undefined, ref: el => autoplay(el, n > 1 ? ms : 0, () => to(i + 1)), onKeyDown: e => { if (e.key === 'ArrowLeft') to(i - 1); else if (e.key === 'ArrowRight') to(i + 1); } }, h('div', { class: 'zg-car__s', 'aria-live': ms ? 'off' : 'polite', style: { backgroundImage: it.image ? `url(${it.image})` : undefined } }, h('div', { class: 'zg-car__c' }, it.title ? h('b', null, it.title) : null, it.sub ? h('span', null, it.sub) : null)), n > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-car__p', 'aria-label': L.prev || '
|
|
52
|
+
return h('div', { class: 'zg-car', role: 'region', 'aria-roledescription': 'carousel', 'aria-label': p.label || undefined, ref: el => autoplay(el, n > 1 ? ms : 0, () => to(i + 1)), onKeyDown: e => { if (e.key === 'ArrowLeft') to(i - 1); else if (e.key === 'ArrowRight') to(i + 1); } }, h('div', { class: 'zg-car__s', 'aria-live': ms ? 'off' : 'polite', style: { backgroundImage: it.image ? `url(${it.image})` : undefined } }, h('div', { class: 'zg-car__c' }, it.title ? h('b', null, it.title) : null, it.sub ? h('span', null, it.sub) : null)), n > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-car__p', 'aria-label': L.prev || t('Carousel', 'prev'), onClick: () => to(i - 1) }, icon(h, 'chevron-left')) : null, n > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-car__n', 'aria-label': L.next || t('Carousel', 'next'), onClick: () => to(i + 1) }, icon(h, 'chevron-right')) : null, n > 1 ? h('div', { class: 'zg-car__d' }, ...items.map((_, k) => h('button', { key: k, type: 'button', 'aria-label': (L.slide || t('Carousel', 'slide')) + ' ' + (k + 1), 'aria-current': String(k === i), onClick: () => to(k) }))) : null); } },
|
|
51
53
|
Gallery: { state: { i: 0 }, render: ({ h, props: p, state, set }) => { const im = arr(p.images).filter(Boolean), n = im.length, i = idx(p, state, n), L = p.labels || {};
|
|
52
54
|
const to = k => { if (k === i) return; if (p.index === undefined || p.index === null) set({ i: k }); p.onChange && p.onChange(k); };
|
|
53
|
-
return h('div', { class: 'zg-gal' }, h('div', { class: 'zg-gal__m', style: { backgroundImage: n ? `url(${im[i]})` : undefined }, role: 'img', 'aria-label': p.alt || '' }, n ? h('span', { class: 'zg-badge zg-gal__n' }, `${i + 1} / ${n}`) : null), n ? h('div', { class: 'zg-gal__t' }, ...im.map((u, k) => h('button', { key: k, type: 'button', 'aria-label': (L.photo || '
|
|
55
|
+
return h('div', { class: 'zg-gal' }, h('div', { class: 'zg-gal__m', style: { backgroundImage: n ? `url(${im[i]})` : undefined }, role: 'img', 'aria-label': p.alt || '' }, n ? h('span', { class: 'zg-badge zg-gal__n' }, `${i + 1} / ${n}`) : null), n ? h('div', { class: 'zg-gal__t' }, ...im.map((u, k) => h('button', { key: k, type: 'button', 'aria-label': (L.photo || t('Gallery', 'photo')) + ' ' + (k + 1), 'aria-current': String(k === i), style: { backgroundImage: `url(${u})` }, onClick: () => to(k) }))) : null); } },
|
|
54
56
|
Code: { render: ({ C, h, props: p }) => h('div', { class: 'zg-code-b' }, h('div', { class: 'zg-code-b__h' }, h('span', null, p.title || p.language || 'code'), h(C.CopyButton, { value: p.code, size: 'xs' })), h('pre', { class: 'zg-mono' }, p.code)) },
|
|
55
|
-
PricingCard: { render: ({ C, h, props: p }) => h('div', { class: cx('zg-card', 'zg-glass', p.featured && 'zg-price--on') }, h('div', { class: 'zg-card__head' }, h('h3', { class: 'zg-card__title' }, p.name), p.badge ? h(C.Badge, { tone: 'brand' }, p.badge) : null), h('div', { style: { display: 'flex', alignItems: 'baseline', gap: '6px' } }, h('span', { class: 'zg-num', style: { fontSize: '44px' } }, p.price), h('span', { class: 'zg-hint' }, p.period || '')), h('ul', { class: 'zg-feat' }, ...(p.features || []).map((f, i) => h('li', { key: i }, icon(h, 'check', 16), f))), h(C.Button, { variant: p.featured ? 'primary' : 'secondary', block: true, onClick: p.onSelect }, p.cta || '
|
|
56
|
-
IntegrationCard: { render: ({ C, h, props: p }) => { const st = ['connected', 'error'].includes(p.status) ? p.status : 'off', tone = { connected: 'success', error: 'danger' }[st], L = { connected: '
|
|
57
|
+
PricingCard: { render: ({ C, h, props: p }) => h('div', { class: cx('zg-card', 'zg-glass', p.featured && 'zg-price--on') }, h('div', { class: 'zg-card__head' }, h('h3', { class: 'zg-card__title' }, p.name), p.badge ? h(C.Badge, { tone: 'brand' }, p.badge) : null), h('div', { style: { display: 'flex', alignItems: 'baseline', gap: '6px' } }, h('span', { class: 'zg-num', style: { fontSize: '44px' } }, p.price), h('span', { class: 'zg-hint' }, p.period || '')), h('ul', { class: 'zg-feat' }, ...(p.features || []).map((f, i) => h('li', { key: i }, icon(h, 'check', 16), f))), h(C.Button, { variant: p.featured ? 'primary' : 'secondary', block: true, onClick: p.onSelect }, p.cta || t('PricingCard', 'choose'))) },
|
|
58
|
+
IntegrationCard: { render: ({ C, h, props: p }) => { const st = ['connected', 'error'].includes(p.status) ? p.status : 'off', tone = { connected: 'success', error: 'danger' }[st], L = { connected: t('IntegrationCard', 'connected'), error: t('IntegrationCard', 'error'), off: t('IntegrationCard', 'off'), manage: t('IntegrationCard', 'manage'), reconnect: t('IntegrationCard', 'reconnect'), link: t('IntegrationCard', 'link'), ...(p.labels || {}) };
|
|
57
59
|
return h('div', { class: 'zg-card zg-glass' }, h('div', { class: 'zg-card__head' }, h('span', { class: 'zg-tl__dot', style: { margin: '8px 4px 0' } }), h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.name), h('div', { class: 'zg-card__sub' }, p.description)), h(C.Badge, { tone, dot: true }, L[st])), h(C.Button, { variant: st === 'connected' ? 'secondary' : 'primary', size: 'sm', onClick: p.onToggle }, st === 'connected' ? L.manage : st === 'error' ? L.reconnect : L.link)); } },
|
|
58
|
-
IncomingCall: { render: ({ C, h, props: p }) => h('div', { class: 'zg-inc zg-glass zg-dm' }, h(C.Avatar, { name: p.name, size: dzp(p, 48) }), h('div', { style: { flex: 1, minWidth: 0 } }, h('div', { class: 'zg-hint' }, p.label || '
|
|
59
|
-
CallCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 36) }), h('div', { style: { flex: 1, minWidth: 0 } }, h('b', { style: { fontSize: dzf(14) } }, p.name), h('div', { class: 'zg-hint zg-mono' }, p.phone)), p.status ? h(C.Badge, { tone: { ...CALL_TONES, ...(p.statusTones || {}) }[String(p.status).toLowerCase()] || (p.statusTones || {})[p.status], dot: true }, p.status) : null), h('div', { class: 'zg-lead__row' }, h(C.IconButton, { icon: 'play', label: (p.labels && p.labels.play) || '
|
|
60
|
+
IncomingCall: { render: ({ C, h, props: p }) => h('div', { class: 'zg-inc zg-glass zg-dm' }, h(C.Avatar, { name: p.name, size: dzp(p, 48) }), h('div', { style: { flex: 1, minWidth: 0 } }, h('div', { class: 'zg-hint' }, p.label || t('IncomingCall', 'incoming')), h('b', { style: { fontSize: dzf(16) } }, p.name), h('div', { class: 'zg-mono zg-hint' }, p.phone)), h(C.IconButton, { icon: 'phone-off', label: t('IncomingCall', 'decline'), size: p.size ? p.size : undefined, onClick: p.onDecline }), h('button', { type: 'button', class: 'zg-ibtn zg-inc__ok', 'aria-label': t('IncomingCall', 'answer'), onClick: p.onAnswer }, icon(h, 'phone'))) },
|
|
61
|
+
CallCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 36) }), h('div', { style: { flex: 1, minWidth: 0 } }, h('b', { style: { fontSize: dzf(14) } }, p.name), h('div', { class: 'zg-hint zg-mono' }, p.phone)), p.status ? h(C.Badge, { tone: { ...CALL_TONES, ...(p.statusTones || {}) }[String(p.status).toLowerCase()] || (p.statusTones || {})[p.status], dot: true }, p.status) : null), h('div', { class: 'zg-lead__row' }, h(C.IconButton, { icon: 'play', label: (p.labels && p.labels.play) || t('CallCard', 'play'), size: dzb(p), onClick: p.onPlay }), h(C.Progress, { value: num(p.progress) }), h('span', { class: 'zg-mono zg-hint' }, fmt.dur(p.duration, p.durUnits)))) },
|
|
60
62
|
/* ── Actions ── */
|
|
61
63
|
Dropdown: { state: { open: false }, render: ({ h, props: p, state, set }) => h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: cx('zg-btn', p.size && 'zg-btn--' + p.size), 'aria-haspopup': 'menu', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, onClick: () => set({ open: !state.open }) }, p.icon ? icon(h, p.icon, 16) : null, p.label, icon(h, 'chevron-down', 14)), state.open && !p.disabled ? h('div', { class: 'zg-menu', role: 'menu', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.items || []).map((it, i) => it.separator ? h('div', { key: i, class: 'zg-menu__sep' }) : h('button', { key: i, type: 'button', role: 'menuitem', disabled: it.disabled, 'aria-disabled': it.disabled ? 'true' : undefined, class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick: () => { set({ open: false }); it.onClick && it.onClick(it); p.onSelect && p.onSelect(it.value); } }, it.icon ? icon(h, it.icon, 16) : null, it.label, it.shortcut ? h('span', { class: 'zg-kbd' }, it.shortcut) : null))) : null) },
|
|
62
64
|
Menu: { render: ({ h, props: p }) => p.open === false ? null : h('div', { class: 'zg-menu zg-menu--static', role: 'menu', 'data-zg-dd': '', ref: p.open === true && p.onClose ? ddRef(p.onClose, { self: true }) : undefined }, ...(p.items || []).map((it, i) => it.separator ? h('div', { key: i, class: 'zg-menu__sep' }) : h('button', { key: i, type: 'button', role: 'menuitem', disabled: it.disabled, 'aria-disabled': it.disabled ? 'true' : undefined, class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick: () => { it.onClick && it.onClick(it); p.onSelect && p.onSelect(it.value); } }, it.icon ? icon(h, it.icon, 16) : null, it.label, it.shortcut ? h('span', { class: 'zg-kbd' }, it.shortcut) : null))) },
|
|
63
|
-
Tooltip: { render: ({ h, props: p, children }) => h('span', { class: 'zg-tip', 'data-tip': p.text, tabIndex: 0 }, ...children) },
|
|
65
|
+
Tooltip: { render: ({ h, props: p, children }) => h('span', { class: cx('zg-tip', p.placement && p.placement !== 'top' && 'zg-tip--' + p.placement), 'data-tip': p.text, tabIndex: 0 }, ...children) },
|
|
64
66
|
HoverCard: { render: ({ h, props: p, children }) => h('span', { class: 'zg-hc', tabIndex: 0 }, h('span', null, p.trigger), h('div', { class: 'zg-hc__c zg-glass' }, ...children)) },
|
|
65
67
|
Popover: { state: { open: false }, render: ({ C, h, props: p, state, set, children }) => h('span', { class: 'zg-dd' }, h(C.Button, { size: p.size, onClick: () => set({ open: !state.open }) }, p.label), state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...children) : null) },
|
|
66
68
|
CopyButton: { state: { done: false, err: false }, render: ({ C, h, props: p, state, set }) => { const reset = () => setTimeout(() => set({ done: false, err: false }), 1500), fail = e => { set({ done: false, err: true }); p.onError && p.onError(e); reset(); }, ok = () => { set({ done: true, err: false }); p.onCopy && p.onCopy(p.value); reset(); };
|
|
67
|
-
const msg = state.err ? '
|
|
68
|
-
return h('span', { style: { display: 'inline-flex' } }, h(C.Button, { size: p.size || 'sm', variant: state.err ? 'danger' : p.variant, icon: state.err ? 'triangle-alert' : state.done ? 'check' : 'copy', onClick: () => { try { const cb = typeof navigator !== 'undefined' && navigator.clipboard; if (!cb || !cb.writeText) return fail(new Error('Clipboard unavailable')); Promise.resolve(cb.writeText(p.value)).then(ok, fail); } catch (e) { fail(e); } } }, p.label || msg || '
|
|
69
|
+
const msg = state.err ? t('CopyButton', 'failed') : state.done ? t('CopyButton', 'copied') : '';
|
|
70
|
+
return h('span', { style: { display: 'inline-flex' } }, h(C.Button, { size: p.size || 'sm', variant: state.err ? 'danger' : p.variant, icon: state.err ? 'triangle-alert' : state.done ? 'check' : 'copy', onClick: () => { try { const cb = typeof navigator !== 'undefined' && navigator.clipboard; if (!cb || !cb.writeText) return fail(new Error('Clipboard unavailable')); Promise.resolve(cb.writeText(p.value)).then(ok, fail); } catch (e) { fail(e); } } }, p.label || msg || t('CopyButton', 'copy')),
|
|
69
71
|
h('span', { role: 'status', 'aria-live': 'polite', style: { position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap' } }, msg)); } },
|
|
70
72
|
CommandBar: { render: ({ h, props: p }) => h('div', { class: cx('zg-cb', 'zg-glass') }, ...(p.items || []).map((it, i) => it.separator ? h('span', { key: i, class: 'zg-cb__s' }) : h('button', { key: i, type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-ibtn--sm', title: it.label, 'aria-label': it.label, disabled: it.disabled, onClick: () => p.onAction && p.onAction(it.id) }, icon(h, it.icon || 'circle')))) },
|
|
71
73
|
CommandPalette: { state: { q: '', a: 0 }, render: ({ h, props: p, state, set }) => { if (!p.open) return null; const f = (p.commands || []).filter(c => !state.q || (c.label + ' ' + (c.keywords || '')).toLowerCase().includes(state.q.toLowerCase())); const ai = f.length ? clamp(state.a, 0, f.length - 1) : -1; const run = c => { if (!c) return; p.onRun && p.onRun(c.id); p.onClose && p.onClose(); };
|
|
72
|
-
return h('div', { class: 'zg-modal-scrim', style: { alignItems: 'flex-start', paddingTop: '12vh' }, onClick: p.onClose }, h('div', { class: 'zg-modal', style: { maxWidth: '560px' }, role: 'dialog', 'aria-modal': 'true', 'aria-label': '
|
|
73
|
-
h('div', { class: 'zg-modal__head' }, icon(h, 'search', 18), h('input', { value: state.q, autoFocus: true, 'data-autofocus': '', role: 'combobox', 'aria-expanded': 'true', 'aria-controls': 'zg-cp-list', 'aria-activedescendant': ai >= 0 ? 'zg-cp-opt-' + ai : undefined, 'aria-label': p.placeholder || '
|
|
74
|
-
h('div', { class: 'zg-modal__body', id: 'zg-cp-list', role: 'listbox', style: { padding: '8px', gap: '2px' } }, ...(f.length ? f.map((c, i) => h('button', { key: c.id, type: 'button', id: 'zg-cp-opt-' + i, role: 'option', tabIndex: -1, 'aria-selected': String(i === ai), class: cx('zg-menu__i', i === ai && 'is-active'), ref: i === ai ? (el => { if (el && el.scrollIntoView) el.scrollIntoView({ block: 'nearest' }); }) : undefined, onMouseMove: () => { if (i !== ai) set({ a: i }); }, onClick: () => run(c) }, c.icon ? icon(h, c.icon, 16) : null, c.label, c.shortcut ? h('span', { class: 'zg-kbd' }, c.shortcut) : null)) : [h('div', { class: 'zg-empty' }, '
|
|
74
|
+
return h('div', { class: 'zg-modal-scrim', style: { alignItems: 'flex-start', paddingTop: '12vh' }, onClick: p.onClose }, h('div', { class: 'zg-modal', style: { maxWidth: '560px' }, role: 'dialog', 'aria-modal': 'true', 'aria-label': t('CommandPalette', 'label'), ref: trap({ onClose: p.onClose }), onClick: e => e.stopPropagation() },
|
|
75
|
+
h('div', { class: 'zg-modal__head' }, icon(h, 'search', 18), h('input', { value: state.q, autoFocus: true, 'data-autofocus': '', role: 'combobox', 'aria-expanded': 'true', 'aria-controls': 'zg-cp-list', 'aria-activedescendant': ai >= 0 ? 'zg-cp-opt-' + ai : undefined, 'aria-label': p.placeholder || t('CommandPalette', 'placeholder'), placeholder: p.placeholder || t('CommandPalette', 'placeholder'), onInput: e => set({ q: e.target.value, a: 0 }), onKeyDown: e => { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); if (f.length) set({ a: (ai + (e.key === 'ArrowDown' ? 1 : -1) + f.length) % f.length }); } else if (e.key === 'Enter') { e.preventDefault(); run(f[ai]); } }, style: { flex: 1, border: 0, outline: 0, background: 'none', color: 'var(--zg-text-1)', font: '500 17px var(--zg-font)' } }), h('span', { class: 'zg-kbd' }, 'Esc')),
|
|
76
|
+
h('div', { class: 'zg-modal__body', id: 'zg-cp-list', role: 'listbox', style: { padding: '8px', gap: '2px' } }, ...(f.length ? f.map((c, i) => h('button', { key: c.id, type: 'button', id: 'zg-cp-opt-' + i, role: 'option', tabIndex: -1, 'aria-selected': String(i === ai), class: cx('zg-menu__i', i === ai && 'is-active'), ref: i === ai ? (el => { if (el && el.scrollIntoView) el.scrollIntoView({ block: 'nearest' }); }) : undefined, onMouseMove: () => { if (i !== ai) set({ a: i }); }, onClick: () => run(c) }, c.icon ? icon(h, c.icon, 16) : null, c.label, c.shortcut ? h('span', { class: 'zg-kbd' }, c.shortcut) : null)) : [h('div', { class: 'zg-empty' }, t('CommandPalette', 'empty'))])))); } },
|
|
75
77
|
NotificationBell: { state: { open: false, seen: {}, all: false }, render: ({ C, h, props: p, state, set }) => { const ctl = !!(p.onRead || p.onMarkAllRead), isRead = i => !!i.read || (!ctl && (state.all || !!state.seen[i.id])), un = (p.items || []).filter(i => !isRead(i)).length;
|
|
76
|
-
return h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-ibtn', 'aria-label': '
|
|
77
|
-
state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })), style: { width: '320px', padding: '8px' } }, un ? h('button', { key: '_all', type: 'button', class: 'zg-menu__i', style: { justifyContent: 'flex-end', minHeight: '32px', color: 'var(--zg-brand-text)' }, onClick: () => { if (!ctl) set({ all: true }); p.onMarkAllRead && p.onMarkAllRead(); } }, '
|
|
78
|
+
return h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-ibtn', 'aria-label': t('NotificationBell', 'label'), 'aria-expanded': String(state.open), onClick: () => set({ open: !state.open }) }, icon(h, 'bell'), un ? h('span', { class: 'zg-bell__n' }, un) : null),
|
|
79
|
+
state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })), style: { width: '320px', padding: '8px' } }, un ? h('button', { key: '_all', type: 'button', class: 'zg-menu__i', style: { justifyContent: 'flex-end', minHeight: '32px', color: 'var(--zg-brand-text)' }, onClick: () => { if (!ctl) set({ all: true }); p.onMarkAllRead && p.onMarkAllRead(); } }, t('NotificationBell', 'markAllRead')) : null, ...(p.items || []).map(it => h('div', { key: it.id, class: 'zg-menu__i', role: 'button', tabIndex: 0, style: { alignItems: 'flex-start', opacity: isRead(it) ? .6 : 1 }, onClick: () => { if (!isRead(it)) { if (!ctl) set({ seen: { ...state.seen, [it.id]: true } }); p.onRead && p.onRead(it.id); } p.onSelect && p.onSelect(it); }, onKeyDown: e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.currentTarget.click(); } } }, h('span', { style: { display: 'flex', flexDirection: 'column', gap: '2px', flex: 1 } }, h('b', { style: { fontSize: '14px' } }, it.title), h('span', { class: 'zg-hint' }, it.message), h('span', { class: 'zg-hint' }, it.time)), !isRead(it) ? h('span', { class: 'zg-tl__dot', style: { margin: '6px 4px' } }) : null))) : null); } },
|
|
78
80
|
ToggleGroup: { render: ({ h, props: p }) => { const cur = p.multiple ? (p.modelValue || []) : [p.modelValue]; return h('div', { class: 'zg-seg', role: 'group' }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', 'aria-pressed': String(cur.includes(o.value)), title: o.label, onClick: () => p.onChange && p.onChange(p.multiple ? (cur.includes(o.value) ? cur.filter(v => v !== o.value) : [...cur, o.value]) : (p.allowEmpty && cur.includes(o.value) ? null : o.value)) }, o.icon ? icon(h, o.icon, 16) : null, p.iconOnly ? null : o.label))); } },
|
|
79
81
|
Fab: { render: ({ h, props: p }) => h('button', { type: 'button', class: 'zg-fab', 'aria-label': p.label || p.icon || 'plus', onClick: p.onClick }, icon(h, p.icon || 'plus', 20), p.label ? h('span', null, p.label) : null) },
|
|
80
82
|
/* ── Forms ── */
|
|
81
|
-
Textarea: { render: ({ h, props: p }) => lab(h, p, h('textarea', { class: cx('zg-ta', p.error && 'zg-input--error'), rows: p.rows || 4, value: p.modelValue ?? '', placeholder: p.placeholder, maxLength: p.maxlength, disabled: p.disabled, onInput: e => p.onChange && p.onChange(e.target.value)
|
|
83
|
+
Textarea: { render: ({ h, props: p }) => lab(h, p, [h('textarea', { ...pick(p, ['name', 'required', 'autocomplete', 'spellcheck', 'autofocus', 'minlength', 'onBlur', 'onFocus', 'onKeydown', 'onKeyDown', 'onKeyup', 'onKeyUp']), class: cx('zg-ta', p.error && 'zg-input--error', p.resize === false && 'zg-ta--fixed', p.autoResize && 'zg-ta--auto'), rows: p.rows || 4, value: p.modelValue ?? '', placeholder: p.placeholder, maxLength: p.maxlength, disabled: p.disabled, readOnly: p.readonly, onInput: e => { p.onChange && p.onChange(e.target.value); p.onInput && p.onInput(e); } }),
|
|
84
|
+
p.showCount ? h('span', { class: 'zg-hint zg-ta__count' }, String((p.modelValue ?? '').length) + (p.maxlength ? ' / ' + p.maxlength : '')) : null]) },
|
|
82
85
|
NumberInput: { state: { draft: null }, render: ({ C, h, props: p, state, set: put }) => { const v = p.modelValue ?? null, st = p.step || 1, lo = p.min ?? -Infinity, hi = p.max ?? Infinity, parse = s => { const t = String(s).trim().replace(',', '.'); if (t === '') return null; const n = Number(t); return Number.isFinite(n) ? n : NaN; }, emit = n => p.onChange && p.onChange(n), bump = d => { put({ draft: null }); emit(clamp(Math.round(((v ?? 0) + d) * 1e10) / 1e10, lo, hi)); };
|
|
83
|
-
return lab(h, p, h('span', { class: 'zg-input zg-num-in' }, h(C.IconButton, { icon: 'minus', label: '
|
|
84
|
-
PasswordInput: { state: { show: false }, render: ({ C, h, props: p, state, set }) => lab(h, p, field(h, p, icon(h, 'lock-keyhole', 18), h('input', { type: state.show ? 'text' : 'password', value: p.modelValue ?? '', placeholder: p.placeholder, autoComplete: p.autocomplete || 'current-password', onInput: e => p.onChange && p.onChange(e.target.value) }), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': state.show ? '
|
|
85
|
-
SearchInput: { render: ({ h, props: p }) => h('span', { class: 'zg-input', style: { '--h': 'var(--zg-h-md)' } }, icon(h, 'search', 16), h('input', { type: 'search', value: p.modelValue ?? '', placeholder: p.placeholder || '
|
|
86
|
-
RadioGroup: { render: ({ h, props: p }) => h('div', { class: 'zg-field', role: 'radiogroup', 'aria-disabled': p.disabled ? 'true' : undefined, style: { gap: '10px' } }, p.label ? h('span', { class: 'zg-label' }, p.label) : null,
|
|
86
|
+
return lab(h, p, h('span', { class: 'zg-input zg-num-in' }, h(C.IconButton, { icon: 'minus', label: t('NumberInput', 'decrease'), size: 'sm', onClick: () => bump(-st) }), h('input', { value: state.draft ?? (v ?? ''), inputMode: 'decimal', placeholder: p.placeholder, style: { textAlign: 'center' }, onInput: e => { const raw = e.target.value, n = parse(raw); put({ draft: raw }); if (n === null || !Number.isNaN(n)) emit(n); }, onBlur: e => { const n = parse(e.target.value); put({ draft: null }); if (n === null) { if (v !== null) emit(null); } else if (!Number.isNaN(n)) { const c = clamp(n, lo, hi); if (c !== v) emit(c); } } }), h(C.IconButton, { icon: 'plus', label: t('NumberInput', 'increase'), size: 'sm', onClick: () => bump(st) }))); } },
|
|
87
|
+
PasswordInput: { state: { show: false }, render: ({ C, h, props: p, state, set }) => lab(h, p, field(h, p, icon(h, 'lock-keyhole', 18), h('input', { type: state.show ? 'text' : 'password', value: p.modelValue ?? '', placeholder: p.placeholder, autoComplete: p.autocomplete || 'current-password', onInput: e => p.onChange && p.onChange(e.target.value) }), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': state.show ? t('PasswordInput', 'hide') : t('PasswordInput', 'show'), onClick: () => set({ show: !state.show }) }, icon(h, state.show ? 'eye-off' : 'eye', 16)))) },
|
|
88
|
+
SearchInput: { render: ({ h, props: p }) => h('span', { class: 'zg-input', style: { '--h': 'var(--zg-h-md)' } }, icon(h, 'search', 16), h('input', { type: 'search', value: p.modelValue ?? '', placeholder: p.placeholder || t('SearchInput', 'placeholder'), onInput: e => p.onChange && p.onChange(e.target.value), onKeyDown: e => e.key === 'Enter' && p.onSearch && p.onSearch(p.modelValue) }), p.modelValue ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('SearchInput', 'clear'), onClick: () => p.onChange && p.onChange('') }, icon(h, 'x', 14)) : null) },
|
|
89
|
+
RadioGroup: { render: ({ h, props: p }) => h('div', { class: 'zg-field', role: 'radiogroup', 'aria-disabled': p.disabled ? 'true' : undefined, style: { gap: '10px' } }, p.label ? h('span', { class: 'zg-label' }, p.label) : null,
|
|
90
|
+
h('div', { class: cx('zg-rg', p.orientation === 'horizontal' && 'zg-rg--row') }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', role: 'radio', class: 'zg-check', 'aria-checked': String(o.value === p.modelValue), disabled: p.disabled || o.disabled, 'aria-disabled': p.disabled || o.disabled ? 'true' : undefined, onClick: () => !(p.disabled || o.disabled) && p.onChange && p.onChange(o.value) }, h('span', { class: 'zg-check__box zg-radio' }, o.value === p.modelValue ? h('i') : null), h('span', null, o.label, o.hint ? h('span', { class: 'zg-hint', style: { display: 'block' } }, o.hint) : null)))),
|
|
91
|
+
p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null) },
|
|
87
92
|
Slider: { render: ({ h, props: p }) => { const mn = p.min ?? 0, mx = p.max ?? 100, v = p.modelValue ?? mn, pct = (v - mn) / (mx - mn) * 100;
|
|
88
93
|
return lab(h, p, h('span', { style: { display: 'flex', alignItems: 'center', gap: '12px' } }, h('input', { type: 'range', class: 'zg-range', min: mn, max: mx, step: p.step || 1, value: v, style: { '--p': pct + '%' }, onInput: e => p.onChange && p.onChange(Number(e.target.value)) }), p.showValue !== false ? h('span', { class: 'zg-mono', style: { minWidth: '48px', textAlign: 'end' } }, p.formatValue ? p.formatValue(v) : v) : null)); } },
|
|
89
|
-
Rating: { render: ({ h, props: p }) => h('div', { class: 'zg-rate', role: 'radiogroup', 'aria-label': p.label || 'Rating' }, ...Array.from({ length: p.max || 5 }, (_, i) => h('button', { key: i, type: 'button', disabled: p.readonly, 'aria-label': (i + 1)
|
|
94
|
+
Rating: { render: ({ h, props: p }) => h('div', { class: 'zg-rate', role: 'radiogroup', 'aria-label': p.label || t('Rating', 'label') }, ...Array.from({ length: p.max || 5 }, (_, i) => h('button', { key: i, type: 'button', disabled: p.readonly, 'aria-label': t('Rating', 'stars', { n: i + 1 }), 'data-on': String(i < (p.modelValue || 0)), onClick: () => p.onChange && p.onChange(i + 1) }, icon(h, 'star', ({ xs: 14, sm: 18, md: 20, lg: 26, xl: 32 })[p.size] || p.size || 20)))) },
|
|
90
95
|
ChipInput: { state: { q: '' }, render: ({ h, props: p, state, set }) => { const v = p.modelValue || [];
|
|
91
96
|
const add = t => { t = t.trim(); if (t && !v.includes(t) && (!p.maxItems || v.length < p.maxItems)) p.onChange && p.onChange([...v, t]); set({ q: '' }); };
|
|
92
|
-
return lab(h, p, h('span', { class: 'zg-input zg-chipin' }, ...v.map(
|
|
93
|
-
InlineEdit: { state: { edit: false, d: '' }, render: ({ C, h, props: p, state, set }) => state.edit ? h('span', { class: 'zg-input', style: { '--h': 'var(--zg-h-md)' } }, h('input', { value: state.d, autoFocus: true, onInput: e => set({ d: e.target.value }), onKeyDown: e => { if (e.key === 'Enter') { p.onChange && p.onChange(state.d); set({ edit: false }); } if (e.key === 'Escape') set({ edit: false }); } }), h(C.IconButton, { icon: 'check', label: '
|
|
97
|
+
return lab(h, p, h('span', { class: 'zg-input zg-chipin' }, ...v.map(tg => h('span', { key: tg, class: 'zg-tag' }, tg, h('button', { type: 'button', 'aria-label': t('ChipInput', 'remove', { name: tg }), onClick: () => p.onChange && p.onChange(v.filter(x => x !== tg)) }, icon(h, 'x', 12)))), h('input', { value: state.q, placeholder: v.length ? '' : p.placeholder, onInput: e => set({ q: e.target.value }), onKeyDown: e => { if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); add(state.q); } else if (e.key === 'Backspace' && !state.q && v.length) p.onChange && p.onChange(v.slice(0, -1)); } }))); } },
|
|
98
|
+
InlineEdit: { state: { edit: false, d: '' }, render: ({ C, h, props: p, state, set }) => state.edit ? h('span', { class: 'zg-input', style: { '--h': 'var(--zg-h-md)' } }, h('input', { value: state.d, autoFocus: true, onInput: e => set({ d: e.target.value }), onKeyDown: e => { if (e.key === 'Enter') { p.onChange && p.onChange(state.d); set({ edit: false }); } if (e.key === 'Escape') set({ edit: false }); } }), h(C.IconButton, { icon: 'check', label: t('InlineEdit', 'save'), size: 'sm', onClick: () => { p.onChange && p.onChange(state.d); set({ edit: false }); } })) : h('button', { type: 'button', class: 'zg-inline', onClick: () => set({ edit: true, d: p.modelValue || '' }) }, p.modelValue || h('span', { class: 'zg-hint' }, p.emptyText || t('InlineEdit', 'edit')), icon(h, 'pencil', 14)) },
|
|
94
99
|
ColorPicker: { render: ({ h, props: p }) => h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-chips' }, ...(p.presets || ['#BC926A', '#5FAACC', '#3FD13F', '#E26E5F', '#E8B964', '#A98BD6', '#1B1919']).map(c => h('button', { key: c, type: 'button', 'aria-label': c, 'aria-pressed': String(c === p.modelValue), class: 'zg-swatch', style: { background: c }, onClick: () => p.onChange && p.onChange(c) })))) },
|
|
95
100
|
FileUpload: { state: { drag: false }, render: ({ h, props: p, state, set }) => { const ac = String(p.accept || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean), okType = f => !ac.length || ac.some(a => a[0] === '.' ? f.name.toLowerCase().endsWith(a) : a.endsWith('/*') ? (f.type || '').toLowerCase().startsWith(a.slice(0, -1)) : (f.type || '').toLowerCase() === a), rej = (f, r) => p.onReject && p.onReject(f, r),
|
|
96
101
|
take = list => { const ok = []; for (const f of list) { if (!okType(f)) rej(f, 'type'); else if (p.maxSize && f.size > p.maxSize) rej(f, 'size'); else ok.push(f); } if (!p.multiple) ok.slice(1).forEach(f => rej(f, 'count')); const out = p.multiple ? ok : ok.slice(0, 1); if (out.length && p.onChange) p.onChange(out); }, pick = e => { const i = e.currentTarget.querySelector('input'); if (i) i.click(); };
|
|
97
|
-
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-fdrop', role: 'button', tabIndex: 0, 'aria-label': p.title || '
|
|
98
|
-
Combobox: { state: { open: false, q: '' }, render: ({ h, props: p, state, set }) => { const cur = (p.options || []).find(o => o.value === p.modelValue), f = (p.options || []).filter(o => !state.q || o.label.toLowerCase().includes(state.q.toLowerCase()));
|
|
99
|
-
return lab(h, p, h('div', { class: 'zg-select' }, field(h, p, icon(h, 'search', 16), h('input', { value: state.open && !p.disabled ? state.q : (cur ? cur.label : ''), disabled: p.disabled, placeholder: p.placeholder || '
|
|
102
|
+
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-fdrop', role: 'button', tabIndex: 0, 'aria-label': p.title || t('FileUpload', 'title'), 'data-drag': state.drag ? '' : undefined, onClick: pick, onKeydown: e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); pick(e); } }, onDragenter: e => { e.preventDefault(); if (!state.drag) set({ drag: true }); }, onDragover: e => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; if (!state.drag) set({ drag: true }); }, onDragleave: e => { if (!e.currentTarget.contains(e.relatedTarget)) set({ drag: false }); }, onDrop: e => { e.preventDefault(); set({ drag: false }); take([...(e.dataTransfer ? e.dataTransfer.files : [])]); } }, icon(h, p.image ? 'image-up' : 'upload', 24), h('b', null, p.title || t('FileUpload', 'title')), h('span', { class: 'zg-hint' }, p.hint || t('FileUpload', 'hint')), h('input', { type: 'file', multiple: p.multiple, accept: p.accept, tabIndex: -1, 'aria-hidden': 'true', style: { display: 'none' }, onClick: e => e.stopPropagation(), onChange: e => { take([...e.target.files]); e.target.value = ''; } })), p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : null); } },
|
|
103
|
+
Combobox: { state: { open: false, q: '' }, render: ({ h, props: p, state, set }) => { const cur = (p.options || []).find(o => o.value === p.modelValue), f = (p.options || []).filter(o => !state.q || (p.filter ? p.filter(o, state.q) : o.label.toLowerCase().includes(state.q.toLowerCase())));
|
|
104
|
+
return lab(h, p, h('div', { class: 'zg-select' }, field(h, p, icon(h, 'search', 16), h('input', { value: state.open && !p.disabled ? state.q : (cur ? cur.label : ''), disabled: p.disabled, placeholder: p.placeholder || t('Combobox', 'placeholder'), onFocus: () => set({ open: true, q: '' }), onInput: e => set({ q: e.target.value, open: true }) }), [p.clearable && cur && !p.disabled ? h('button', { key: 'x', type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Combobox', 'clear'), onMouseDown: e => e.preventDefault(), onClick: () => { set({ open: false, q: '' }); p.onChange && p.onChange(null); } }, icon(h, 'x', 14)) : null, icon(h, 'chevron-down', 16)]),
|
|
100
105
|
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'data-zg-dd': '', ref: ddRef(() => set({ open: false, q: '' })) }, ...(f.length ? f.map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onMouseDown: e => e.preventDefault(), onClick: () => { set({ open: false, q: '' }); p.onChange && p.onChange(o.value); } }, o.label)) : [h('div', { key: 'e', class: 'zg-empty' }, p.emptyText || '—')])) : null)); } },
|
|
101
106
|
MultiSelect: { state: { open: false }, render: ({ h, props: p, state, set }) => { const v = p.modelValue || [], tog = x => p.onChange && p.onChange(v.includes(x) ? v.filter(i => i !== x) : [...v, x]);
|
|
102
|
-
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'listbox', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', null, v.length ?
|
|
107
|
+
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'listbox', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', null, v.length ? t('MultiSelect', 'selected', { n: v.length }) : (p.placeholder || '—')), icon(h, 'chevron-down', 16)),
|
|
103
108
|
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'aria-multiselectable': 'true', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(v.includes(o.value)), onClick: () => tog(o.value) }, h('span', { class: 'zg-check__box', style: { width: '18px', height: '18px', borderRadius: '6px', background: v.includes(o.value) ? 'var(--zg-brand)' : 'transparent' } }, v.includes(o.value) ? icon(h, 'check', 12) : null), h('span', { style: { flex: 1, marginInlineStart: '10px' } }, o.label)))) : null)); } },
|
|
104
109
|
GroupedSelect: { state: { open: false }, render: ({ h, props: p, state, set }) => { const all = (p.groups || []).flatMap(g => g.options), cur = all.find(o => o.value === p.modelValue);
|
|
105
110
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'listbox', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', null, cur ? cur.label : (p.placeholder || '—')), icon(h, 'chevron-down', 16)),
|
|
106
111
|
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.groups || []).flatMap(g => [h('div', { key: g.label, class: 'zg-sidenav__group', style: { padding: '8px 14px 2px' } }, g.label), ...g.options.map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onClick: () => { set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label))])) : null)); } },
|
|
107
112
|
PhoneInput: { render: ({ C, h, props: p }) => { const CC = p.countries || [{ value: '+966', label: 'SA +966' }, { value: '+998', label: 'UZ +998' }, { value: '+7', label: 'RU +7' }, { value: '+90', label: 'TR +90' }, { value: '+20', label: 'EG +20' }], v = p.modelValue || {};
|
|
108
113
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { style: { display: 'grid', gridTemplateColumns: '130px minmax(0,1fr)', gap: '8px', alignItems: 'start' } }, h(C.Select, { options: CC, modelValue: v.code || CC[0].value, onChange: c => p.onChange && p.onChange({ ...v, code: c }) }), field(h, p, icon(h, 'phone', 16), h('input', { type: 'tel', inputMode: 'tel', value: v.number || '', placeholder: p.placeholder ?? '90 123 45 67', onInput: e => p.onChange && p.onChange({ ...v, number: e.target.value }) }))), typeof p.error === 'string' && p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null); } },
|
|
109
|
-
Calendar: { state: { ym: null }, render: ({ h, props: p, state, set }) => { const s = pd(p.modelValue), b = state.ym ? new Date(state.ym) : (s || new Date()), y = b.getFullYear(), m = b.getMonth(), loc = p
|
|
114
|
+
Calendar: { state: { ym: null }, render: ({ h, props: p, state, set }) => { const s = pd(p.modelValue), b = state.ym ? new Date(state.ym) : (s || new Date()), y = b.getFullYear(), m = b.getMonth(), loc = dloc(p), ws = p.weekStartsOn ?? 1, off = (new Date(y, m, 1).getDay() - ws + 7) % 7, dn = new Date(y, m + 1, 0).getDate(), today = new Date();
|
|
110
115
|
const wd = Array.from({ length: 7 }, (_, i) => new Intl.DateTimeFormat(loc, { weekday: 'narrow' }).format(new Date(2023, 0, 1 + ((i + ws) % 7))));
|
|
111
|
-
const same = (a, d) => a && a.getFullYear() === y && a.getMonth() === m && a.getDate() === d, ev = p.events || {};
|
|
112
|
-
return h('div', { class: 'zg-cal' }, h('div', { class: 'zg-cal__h' }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': '
|
|
113
|
-
h('div', { class: 'zg-cal__g' }, ...wd.map((d, i) => h('span', { key: 'w' + i, class: 'zg-cal__w' }, d)), ...Array.from({ length: off }, (_, i) => h('span', { key: 'o' + i })), ...Array.from({ length: dn }, (_, i) => { const d = i + 1, key = `${y}-${String(m + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`; return h('button', { key: d, type: 'button', class: 'zg-cal__d', 'aria-pressed': String(same(s, d)), 'data-today': String(same(today, d)), 'data-ev': ev[key] ? '' : undefined, title: ev[key] || undefined, onClick: () => p.onChange && p.onChange(new Date(y, m, d)) }, d); }))); } },
|
|
114
|
-
DatePicker: { state: { open: false }, render: ({ C, h, props: p, state, set }) => lab(h, p, h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'dialog', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', { style: { display: 'flex', gap: '10px', alignItems: 'center' } }, icon(h, 'calendar', 16), p.modelValue ? new Intl.DateTimeFormat(p
|
|
115
|
-
state.open && !p.disabled ? h('div', { class: 'zg-select__list', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })), style: { padding: '12px' } }, h(C.Calendar, { modelValue: p.modelValue, locale: p.locale, weekStartsOn: p.weekStartsOn, onChange: d => { set({ open: false }); p.onChange && p.onChange(d); } })) : null)) },
|
|
116
|
+
const same = (a, d) => a && a.getFullYear() === y && a.getMonth() === m && a.getDate() === d, ev = p.events || {}, lo = pd(p.min), hi = pd(p.max), day0 = x => new Date(x.getFullYear(), x.getMonth(), x.getDate()), blocked = d => { const t = new Date(y, m, d); return !!((lo && t < day0(lo)) || (hi && t > day0(hi)) || (p.disabledDate && p.disabledDate(t))); };
|
|
117
|
+
return h('div', { class: 'zg-cal' }, h('div', { class: 'zg-cal__h' }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Calendar', 'prev'), onClick: () => set({ ym: new Date(y, m - 1, 1).getTime() }) }, icon(h, 'chevron-left', 16)), h('b', null, new Intl.DateTimeFormat(loc, { month: 'long', year: 'numeric' }).format(b)), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Calendar', 'next'), onClick: () => set({ ym: new Date(y, m + 1, 1).getTime() }) }, icon(h, 'chevron-right', 16))),
|
|
118
|
+
h('div', { class: 'zg-cal__g' }, ...wd.map((d, i) => h('span', { key: 'w' + i, class: 'zg-cal__w' }, d)), ...Array.from({ length: off }, (_, i) => h('span', { key: 'o' + i })), ...Array.from({ length: dn }, (_, i) => { const d = i + 1, key = `${y}-${String(m + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`; return h('button', { key: d, type: 'button', class: 'zg-cal__d', 'aria-pressed': String(same(s, d)), 'data-today': String(same(today, d)), 'data-ev': ev[key] ? '' : undefined, title: ev[key] || undefined, disabled: blocked(d) || undefined, onClick: () => !blocked(d) && p.onChange && p.onChange(new Date(y, m, d)) }, d); }))); } },
|
|
119
|
+
DatePicker: { state: { open: false }, render: ({ C, h, props: p, state, set }) => lab(h, p, h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'dialog', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', { style: { display: 'flex', gap: '10px', alignItems: 'center' } }, icon(h, 'calendar', 16), p.modelValue ? new Intl.DateTimeFormat(dloc(p), { dateStyle: 'medium' }).format(pd(p.modelValue) || new Date()) : (p.placeholder || t('DatePicker', 'placeholder'))), h('span', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, p.clearable && p.modelValue && !p.disabled ? h('span', { role: 'button', tabIndex: 0, class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('DatePicker', 'clear'), onClick: e => { e.stopPropagation(); p.onChange && p.onChange(null); }, onKeydown: e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); p.onChange && p.onChange(null); } } }, icon(h, 'x', 14)) : null, icon(h, 'chevron-down', 16))),
|
|
120
|
+
state.open && !p.disabled ? h('div', { class: 'zg-select__list', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })), style: { padding: '12px' } }, h(C.Calendar, { modelValue: p.modelValue, locale: p.locale, weekStartsOn: p.weekStartsOn, min: p.min, max: p.max, disabledDate: p.disabledDate, onChange: d => { set({ open: false }); p.onChange && p.onChange(d); } })) : null)) },
|
|
116
121
|
TimePicker: { render: ({ C, h, props: p }) => { const [H0, M0] = (p.modelValue || '09:00').split(':'), H = String(H0 ?? '00').padStart(2, '0'), M = String(M0 ?? '00').padStart(2, '0'); const hs = Array.from({ length: 24 }, (_, i) => { const v = String(i).padStart(2, '0'); return { value: v, label: v }; }), st = Math.max(1, Math.min(60, Math.floor(+p.minuteStep) || 15)), mv = Array.from({ length: Math.ceil(60 / st) }, (_, i) => String(i * st).padStart(2, '0')); if (!mv.includes(M)) { mv.push(M); mv.sort(); } const ms = mv.map(v => ({ value: v, label: v }));
|
|
117
122
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } }, h('div', { style: { flex: 1 } }, h(C.Select, { options: hs, disabled: p.disabled, modelValue: H, onChange: v => p.onChange && p.onChange(v + ':' + M) })), h('b', null, ':'), h('div', { style: { flex: 1 } }, h(C.Select, { options: ms, disabled: p.disabled, modelValue: M, onChange: v => p.onChange && p.onChange(H + ':' + v) })))); } },
|
|
118
123
|
FormField: { render: ({ h, props: p, children }) => h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label, p.required ? h('span', { style: { color: 'var(--zg-red)' } }, ' *') : null) : null, ...children, p.error ? h('span', { class: 'zg-hint zg-hint--error' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null) },
|
|
119
|
-
LoginForm: { state: { e: '', pw: '', rm: true }, render: ({ C, h, props: p, state, set }) => { const L = { email: '
|
|
124
|
+
LoginForm: { state: { e: '', pw: '', rm: true }, render: ({ C, h, props: p, state, set }) => { const L = { email: t('LoginForm', 'email'), password: t('LoginForm', 'password'), remember: t('LoginForm', 'remember'), forgot: t('LoginForm', 'forgot'), submit: t('LoginForm', 'submit'), ...(p.labels || {}) }; return h('form', { class: 'zg-field', style: { gap: '14px' }, onSubmit: ev => { ev.preventDefault(); p.onSubmit && p.onSubmit({ email: state.e, password: state.pw, remember: state.rm }); } },
|
|
120
125
|
p.error ? h(C.Alert, { tone: 'danger' }, p.error) : null, h(C.TextField, { label: L.email, icon: 'user-round', modelValue: state.e, onChange: v => set({ e: v }) }), h(C.PasswordInput, { label: L.password, modelValue: state.pw, onChange: v => set({ pw: v }) }),
|
|
121
126
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, h(C.Checkbox, { label: L.remember, modelValue: state.rm, onChange: v => set({ rm: v }) }), p.showForgot !== false ? h('a', { href: '#', onClick: e => { e.preventDefault(); p.onForgot && p.onForgot(); }, style: { color: 'var(--zg-brand-text)', fontWeight: 600, fontSize: '14px' } }, L.forgot) : null),
|
|
122
127
|
h('button', { type: 'submit', class: 'zg-btn zg-btn--primary zg-btn--lg zg-btn--block', disabled: p.loading }, p.loading ? h('span', { class: 'zg-spin' }) : null, p.submitLabel || L.submit)); } },
|
|
123
128
|
StepForm: { state: { i: 0 }, render: ({ C, h, props: p, state, set }) => { const st = p.steps || [], i = clamp(state.i, 0, st.length - 1);
|
|
124
|
-
return h('div', { class: 'zg-field', style: { gap: '18px' } }, h(C.Stepper, { steps: st.map(s => s.label), current: i }), h('div', null, st[i] && st[i].content), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: p.size, variant: 'ghost', disabled: i === 0, onClick: () => set({ i: i - 1 }) }, '
|
|
129
|
+
return h('div', { class: 'zg-field', style: { gap: '18px' } }, h(C.Stepper, { steps: st.map(s => s.label), current: i }), h('div', null, st[i] && st[i].content), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: p.size, variant: 'ghost', disabled: i === 0, onClick: () => set({ i: i - 1 }) }, t('StepForm', 'back')), h(C.Button, { size: p.size, variant: 'primary', onClick: () => i === st.length - 1 ? p.onFinish && p.onFinish() : set({ i: i + 1 }) }, i === st.length - 1 ? (p.finishLabel || t('StepForm', 'finish')) : t('StepForm', 'next')))); } },
|
|
125
130
|
/* ── Data ── */
|
|
126
|
-
Tag: { render: ({ h, props: p, children }) => h('span', { class: 'zg-tag' }, ...children, (p.onRemove || p.onClose) ? h('button', { type: 'button', 'aria-label': '
|
|
131
|
+
Tag: { render: ({ h, props: p, children }) => h('span', { class: 'zg-tag' }, ...children, (p.onRemove || p.onClose) ? h('button', { type: 'button', 'aria-label': t('Tag', 'remove'), onClick: p.onRemove || p.onClose }, icon(h, 'x', 12)) : null) },
|
|
127
132
|
Kbd: { render: ({ h, children }) => h('kbd', { class: 'zg-kbd' }, ...children) },
|
|
128
133
|
StatsCard: { render: ({ h, props: p }) => h('div', { class: 'zg-kpi' }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '10px' } }, p.icon ? h('span', { class: 'zg-toast__icon' }, icon(h, p.icon, 18)) : null, h('span', { class: 'zg-kpi__label' }, p.label)), h('span', { class: 'zg-kpi__value zg-num' }, p.value), p.delta ? h('span', { class: cx('zg-delta', p.trend === 'down' ? 'zg-delta--down' : 'zg-delta--up') }, (p.trend === 'down' ? '▼ ' : '▲ ') + p.delta) : null) },
|
|
129
134
|
KpiInline: { render: ({ h, props: p }) => h('span', { class: 'zg-kpii' }, h('span', { class: 'zg-hint' }, p.label), h('b', { class: 'zg-num zg-kpii__v', style: { fontSize: '20px', fontWeight: 300 } }, p.value), p.delta ? h('span', { class: cx('zg-delta', String(p.delta).startsWith('-') ? 'zg-delta--down' : 'zg-delta--up') }, p.delta) : null) },
|
|
@@ -144,19 +149,19 @@ export const more = {
|
|
|
144
149
|
SettingsSection: { render: ({ h, props: p, children }) => h('section', { class: 'zg-setsec' }, h('div', null, h('b', { style: { fontSize: '16px' } }, p.title), p.description ? h('p', { class: 'zg-hint', style: { margin: '4px 0 0', fontSize: '13px' } }, p.description) : null), h('div', { class: 'zg-field', style: { gap: '14px' } }, ...children)) },
|
|
145
150
|
ScrollArea: { render: ({ h, props: p, children }) => h('div', { class: 'zg-scroll', style: { maxHeight: p.maxHeight || '240px' } }, ...children) },
|
|
146
151
|
/* ── CRM ── */
|
|
147
|
-
DealCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-lead zg-dm' }, h('div', { class: 'zg-lead__row' }, h('b', { style: { fontSize: dzf(14), flex: 1 } }, p.title), p.stage ? h(C.Badge, { tone: p.tone || 'info' }, p.stage) : null), h('div', { class: 'zg-num', style: { fontSize: dzf(26), fontWeight: 300 } }, p.amount), h(C.Progress, { value: p.probability ?? 0, label: '
|
|
152
|
+
DealCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-lead zg-dm' }, h('div', { class: 'zg-lead__row' }, h('b', { style: { fontSize: dzf(14), flex: 1 } }, p.title), p.stage ? h(C.Badge, { tone: p.tone || 'info' }, p.stage) : null), h('div', { class: 'zg-num', style: { fontSize: dzf(26), fontWeight: 300 } }, p.amount), h(C.Progress, { value: p.probability ?? 0, label: t('DealCard', 'probability'), meta: (p.probability ?? 0) + '%' }), h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.owner, size: dzp(p, 24) }), h('span', { class: 'zg-hint' }, p.owner), h('span', { class: 'zg-hint', style: { marginInlineStart: 'auto' } }, p.due))) },
|
|
148
153
|
PipelineBoard: { state: { over: null }, render: ({ C, h, props: p, state, set }) => { const mv = p.onMove, stages = arr(p.stages).filter(Boolean);
|
|
149
154
|
const drop = (e, to) => { e.preventDefault(); const d = dragging; dragging = null; if (state.over !== null) set({ over: null }); if (d && d.from !== to) mv({ id: d.id, from: d.from, to }); };
|
|
150
155
|
return h('div', { style: { display: 'flex', gap: '14px', overflowX: 'auto', paddingBottom: '4px' } }, ...stages.map((s, si) => { const cards = arr(s.cards).filter(Boolean), sid = s.id ?? si;
|
|
151
156
|
const col = h(C.KanbanColumn, { key: sid, title: s.title, count: cards.length, size: p.size }, ...cards.map((c, i) => { const deal = h(C.DealCard, { key: c.id ?? i, size: p.size, ...c });
|
|
152
157
|
return mv ? h('div', { key: c.id ?? i, class: 'zg-drag', draggable: 'true', onDragStart: e => { dragging = { id: c.id ?? i, from: sid }; if (e.dataTransfer) { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', String(c.id ?? i)); } }, onDragEnd: () => { dragging = null; if (state.over !== null) set({ over: null }); } }, deal) : deal; }));
|
|
153
158
|
return mv ? h('div', { key: sid, class: 'zg-drop', 'data-over': String(state.over === sid && !!dragging && dragging.from !== sid), onDragOver: e => { if (!dragging) return; e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'; if (state.over !== sid) set({ over: sid }); }, onDragLeave: e => { if (!e.currentTarget.contains(e.relatedTarget) && state.over === sid) set({ over: null }); }, onDrop: e => drop(e, sid) }, col) : col; })); } },
|
|
154
|
-
ContactPanel: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 56) }), h('div', null, h('b', { style: { fontSize: dzf(18) } }, p.name), h('div', { class: 'zg-hint' }, p.role))), h(C.Descriptions, { items: [{ label: '
|
|
159
|
+
ContactPanel: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 56) }), h('div', null, h('b', { style: { fontSize: dzf(18) } }, p.name), h('div', { class: 'zg-hint' }, p.role))), h(C.Descriptions, { items: [{ label: t('ContactPanel', 'phone'), value: p.phone }, { label: t('ContactPanel', 'telegram'), value: p.telegram }, { label: t('ContactPanel', 'email'), value: p.email }, { label: t('ContactPanel', 'city'), value: p.city }] }), h('div', { style: { display: 'flex', gap: '8px' } }, h(C.Button, { variant: 'primary', size: 'sm', icon: 'phone', onClick: () => p.onCall && p.onCall() }, (p.labels && p.labels.call) || t('ContactPanel', 'call')), h(C.Button, { size: 'sm', icon: 'send', onClick: () => p.onMessage && p.onMessage() }, (p.labels && p.labels.message) || t('ContactPanel', 'message')))) },
|
|
155
160
|
SalesFunnel: { render: ({ h, props: p }) => { const st = arr(p.steps).map(s => s || {}), mx = Math.max(...st.map(s => num(s.value)), 1);
|
|
156
161
|
return h('div', { class: 'zg-field', style: { gap: '8px' } }, ...st.map((s, i) => h('div', { key: i, style: { display: 'grid', gridTemplateColumns: '120px minmax(0,1fr) 56px', gap: '12px', alignItems: 'center', fontSize: '14px' } }, h('span', null, s.label), h('span', { style: { display: 'flex', height: '32px', borderRadius: '12px', background: 'var(--zg-s2)' } }, h('span', { style: { width: (Math.max(0, num(s.value)) / mx * 100) + '%', borderRadius: '12px', background: i === st.length - 1 ? 'var(--zg-brand)' : 'color-mix(in oklch,var(--zg-sky) 40%,var(--zg-s2))' } })), h('b', { class: 'zg-mono', style: { textAlign: 'end' } }, fmt.num(s.value))))); } },
|
|
157
162
|
/* ── Hotels ── */
|
|
158
163
|
BookingCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-lead zg-dm' }, h('div', { class: 'zg-lead__row' }, h('b', { class: 'zg-mono', style: { fontSize: dzf(14), flex: 1 } }, p.code), h(C.Badge, { tone: { Confirmed: 'success', Pending: 'warning', Cancelled: 'danger' }[p.status], dot: true }, p.status)), h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.guest, size: dzp(p, 32) }), h('div', null, h('b', { style: { fontSize: dzf(14) } }, p.guest), h('div', { class: 'zg-hint' }, p.hotel))), h('div', { class: 'zg-lead__row zg-hint' }, icon(h, 'calendar', dzp(p, 14)), p.dates, h('span', { class: 'zg-num', style: { marginInlineStart: 'auto', fontSize: dzf(16), fontWeight: 600, color: 'var(--zg-text-1)' } }, fmt.sar(p.amount)))) },
|
|
159
|
-
GuestCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 48) }), h('div', { style: { flex: 1 } }, h('b', { style: { fontSize: dzf(16) } }, p.name), h('div', { class: 'zg-hint' }, p.country, ' · ',
|
|
164
|
+
GuestCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 48) }), h('div', { style: { flex: 1 } }, h('b', { style: { fontSize: dzf(16) } }, p.name), h('div', { class: 'zg-hint' }, p.country, ' · ', t('GuestCard', 'nights', { n: p.nights }))), p.vip ? h(C.Badge, { tone: 'brand' }, 'VIP') : null), h('div', { class: 'zg-chips' }, ...(p.tags || []).map(t => h('span', { key: t, class: 'zg-tag' }, t)))) },
|
|
160
165
|
RoomGrid: { render: ({ h, props: p }) => { const T = { ...ROOM_TONES, ...(p.statusTones || {}) }; return h('div', { class: 'zg-rooms zg-dm' }, ...arr(p.rooms).filter(Boolean).map((r, i) => { const tone = TONE_OK.includes(T[r.status]) ? T[r.status] : undefined; return h('button', { key: r.no ?? i, type: 'button', class: cx('zg-room', tone && 'zg-t-' + tone), 'data-s': r.status, 'data-tone': tone, onClick: () => p.onSelect && p.onSelect(r.no) }, h('b', { class: 'zg-mono' }, r.no), h('span', null, r.type)); })); } },
|
|
161
166
|
RoomCalendar: { render: ({ h, props: p }) => { const D = p.days || 14; return h('div', { class: 'zg-rc zg-dm', style: { '--d': D } }, h('span'), ...Array.from({ length: D }, (_, i) => h('span', { key: 'd' + i, class: 'zg-rc__h' }, i + 1)), ...arr(p.rooms).filter(Boolean).flatMap(r => [h('b', { key: r.no, class: 'zg-mono' }, r.no), ...Array.from({ length: D }, (_, i) => { const b = arr(r.bookings).find(x => i >= x.from && i < x.from + x.nights); return h('i', { key: r.no + '_' + i, class: 'zg-rc__c', 'data-b': b ? (i === b.from ? 'start' : 'mid') : undefined, title: b ? b.guest : undefined }); })])); } },
|
|
162
167
|
OccupancyChart: { render: ({ h, props: p }) => h('div', { class: 'zg-occ' }, ...arr(p.values).map((v, i) => { v = clamp(num(v), 0, 100); return h('div', { key: i, class: 'zg-occ__c' }, h('span', { class: 'zg-hint zg-mono' }, v + '%'), h('i', { style: { height: v + '%', background: v > 90 ? 'var(--zg-brand)' : 'color-mix(in oklch,var(--zg-sky) 55%,var(--zg-s3))' } }), h('span', { class: 'zg-hint' }, arr(p.labels)[i] || '')); })) },
|
package/src/core/components3.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// @ziyarago/ui · components, part 3 — card patterns, tonal variants, shells for auth/profile/status pages.
|
|
2
2
|
import { icon, cx, fmt, ddRef } from './components.js';
|
|
3
|
+
import { t } from '../i18n/index.js';
|
|
3
4
|
const TONES = ['brand', 'sky', 'success', 'warning', 'danger', 'neutral'];
|
|
4
5
|
const tc = t => 'zg-t-' + (TONES.includes(t) ? t : 'brand');
|
|
5
6
|
const delta = (h, d) => d ? h('span', { class: cx('zg-delta', String(d).trim().startsWith('-') ? 'zg-delta--down' : 'zg-delta--up') }, (String(d).trim().startsWith('-') ? '▼ ' : '▲ ') + String(d).replace(/^[+-]/, '')) : null;
|
|
6
7
|
|
|
7
8
|
export const more3 = {
|
|
8
|
-
Chip: { render: ({ h, props: p, children }) => h('span', { class: cx('zg-chip2', tc(p.tone), 'zg-chip2--' + (p.variant || 'tonal'), p.size && 'zg-chip2--' + p.size, p.pill && 'zg-chip2--pill') }, p.icon ? icon(h, p.icon, 14) : null, ...children, (p.onClose || p.onRemove) ? h('button', { type: 'button', 'aria-label': '
|
|
9
|
+
Chip: { render: ({ h, props: p, children }) => h('span', { class: cx('zg-chip2', tc(p.tone), 'zg-chip2--' + (p.variant || 'tonal'), p.size && 'zg-chip2--' + p.size, p.pill && 'zg-chip2--pill') }, p.icon ? icon(h, p.icon, 14) : null, ...children, (p.onClose || p.onRemove) ? h('button', { type: 'button', 'aria-label': t('Chip', 'remove'), onClick: p.onClose || p.onRemove }, icon(h, 'x', 12)) : null) },
|
|
9
10
|
IconTile: { render: ({ h, props: p }) => h('span', { class: cx('zg-tile', tc(p.tone), p.shape === 'circle' && 'zg-tile--circle'), style: { '--s': ({ xs: 28, sm: 32, md: 40, lg: 48, xl: 56 }[p.size] || p.size || 40) + 'px' } }, icon(h, p.icon || 'circle', Math.round(({ xs: 28, sm: 32, md: 40, lg: 48, xl: 56 }[p.size] || p.size || 40) * .5))) },
|
|
10
11
|
StatusIcon: { render: ({ h, props: p }) => h('span', { class: cx('zg-tile zg-tile--circle zg-tip', tc(p.tone)), 'data-tip': p.tip, tabIndex: 0, style: { '--s': ({ xs: 24, sm: 30, md: 36, lg: 44, xl: 52 }[p.size] || p.size || 36) + 'px' } }, icon(h, p.icon || 'circle', Math.round(({ xs: 24, sm: 30, md: 36, lg: 44, xl: 52 }[p.size] || p.size || 36) * .44))) },
|
|
11
12
|
UserCell: { render: ({ C, h, props: p }) => h('span', { class: 'zg-usercell' }, h(C.Avatar, { name: p.name, src: p.src, size: { xs: 24, sm: 30, md: 36, lg: 44, xl: 56 }[p.size] || p.size || 36 }), h('span', null, h('b', null, p.name), p.sub ? h('span', { class: 'zg-hint' }, p.sub) : null)) },
|
|
@@ -19,17 +20,17 @@ export const more3 = {
|
|
|
19
20
|
SegBar: { render: ({ h, props: p }) => { const s = (Array.isArray(p.segments) ? p.segments : []).filter(Boolean).map(x => ({ ...x, value: Math.max(0, Number(x.value) || 0) })), t = s.reduce((a, x) => a + x.value, 0) || 1, col = ['var(--zg-brand)', 'var(--zg-sky)', 'var(--zg-green)', 'var(--zg-amber)', 'var(--zg-red)'];
|
|
20
21
|
return h('div', { class: 'zg-field', style: { gap: '12px' } }, h('div', { class: 'zg-segbar' }, ...s.map((x, i) => h('i', { key: i, title: x.label, style: { width: (x.value / t * 100) + '%', background: x.color || col[i % 5] } }))), h('div', { class: 'zg-chips' }, ...s.map((x, i) => h('span', { key: i, class: 'zg-hint', style: { display: 'inline-flex', alignItems: 'center', gap: '6px' } }, h('i', { style: { width: '8px', height: '8px', borderRadius: '50%', background: x.color || col[i % 5] } }), x.label + ' · ' + x.value)))); } },
|
|
21
22
|
HeroBanner: { render: ({ C, h, props: p, children }) => h('section', { class: 'zg-hero zg-glass' }, h('div', { class: 'zg-hero__t' }, h('b', { style: { fontSize: '20px', fontWeight: 500 } }, p.title), p.text ? h('span', { style: { color: 'var(--zg-text-2)', lineHeight: 1.5 } }, p.text) : null, p.value ? h('span', { class: 'zg-num', style: { fontSize: '36px', fontWeight: 300 } }, p.value) : null, ...children, p.cta ? h(C.Button, { variant: 'primary', size: 'sm', onClick: p.onCta }, p.cta) : null), p.image ? h('span', { class: 'zg-hero__img', style: { backgroundImage: `url(${p.image})` } }) : null) },
|
|
22
|
-
TabTiles: { render: ({ h, props: p }) => h('div', { class: 'zg-tt' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', class: 'zg-tt__i', 'aria-pressed': String(it.value === p.value), onClick: () => p.onChange && p.onChange(it.value) }, icon(h, it.icon || 'circle', 20), h('span', null, it.label))), p.addable ? h('button', { type: 'button', class: 'zg-tt__i zg-tt__add', 'aria-label': '
|
|
23
|
+
TabTiles: { render: ({ h, props: p }) => h('div', { class: 'zg-tt' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', class: 'zg-tt__i', 'aria-pressed': String(it.value === p.value), onClick: () => p.onChange && p.onChange(it.value) }, icon(h, it.icon || 'circle', 20), h('span', null, it.label))), p.addable ? h('button', { type: 'button', class: 'zg-tt__i zg-tt__add', 'aria-label': t('TabTiles', 'add'), onClick: p.onAdd }, icon(h, 'plus', 20)) : null) },
|
|
23
24
|
PillTabs: { render: ({ h, props: p }) => h('div', { class: cx('zg-pt', p.vertical && 'zg-pt--v'), role: 'tablist' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', role: 'tab', 'aria-selected': String(it.value === p.value), onClick: () => p.onChange && p.onChange(it.value) }, it.icon ? icon(h, it.icon, 16) : null, it.label))) },
|
|
24
25
|
PillSelect: { state: { open: false }, render: ({ h, props: p, state, set }) => { const opts = p.options || [], v = p.modelValue !== undefined ? p.modelValue : p.value, open = state.open && !p.disabled; return h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-btn zg-btn--tonal zg-btn--sm', 'aria-haspopup': 'listbox', 'aria-expanded': String(open), disabled: p.disabled, onClick: () => set({ open: !state.open }) }, (opts.find(o => o.value === v) || {}).label || p.placeholder, icon(h, 'chevron-down', 14)), open ? h('div', { class: 'zg-menu', role: 'listbox', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...opts.map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-menu__i', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === v), onClick: () => { set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label))) : null); } },
|
|
25
26
|
ChartCard: { state: { open: false }, render: ({ h, props: p, state, set, children }) => h('section', { class: 'zg-card zg-glass' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1, minWidth: 0 } }, h('h3', { class: 'zg-card__title' }, p.title), p.subtitle ? h('div', { class: 'zg-card__sub' }, p.subtitle) : null),
|
|
26
|
-
p.menu ? h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': '
|
|
27
|
-
ActionCard: { state: { collapsed: false, loading: false, removed: false }, render: ({ C, h, props: p, state, set, children }) => { const col = p.collapsed !== undefined ? !!p.collapsed && p.collapsed !== 'false' : state.collapsed, busy = p.loading !== undefined ? !!p.loading && p.loading !== 'false' : state.loading; return state.removed ? null : h('section', { class: 'zg-card zg-glass zg-lo' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.title), p.subtitle ? h('div', { class: 'zg-card__sub' }, p.subtitle) : null), h('div', { style: { display: 'flex', gap: '2px' } }, h(C.IconButton, { icon: col ? 'chevron-down' : 'chevron-up', label: col ? '
|
|
27
|
+
p.menu ? h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('ChartCard', 'more'), 'aria-haspopup': 'menu', onClick: () => set({ open: !state.open }) }, icon(h, 'ellipsis-vertical')), state.open ? h('div', { class: 'zg-menu', role: 'menu', style: { insetInlineStart: 'auto', insetInlineEnd: 0 } }, ...p.menu.map(m => h('button', { key: m, type: 'button', role: 'menuitem', class: cx('zg-menu__i', m === 'Delete' && 'zg-menu__i--danger'), onClick: () => { set({ open: false }); p.onMenu && p.onMenu(m); } }, m))) : null) : null), ...children) },
|
|
28
|
+
ActionCard: { state: { collapsed: false, loading: false, removed: false }, render: ({ C, h, props: p, state, set, children }) => { const col = p.collapsed !== undefined ? !!p.collapsed && p.collapsed !== 'false' : state.collapsed, busy = p.loading !== undefined ? !!p.loading && p.loading !== 'false' : state.loading; return state.removed ? null : h('section', { class: 'zg-card zg-glass zg-lo' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.title), p.subtitle ? h('div', { class: 'zg-card__sub' }, p.subtitle) : null), h('div', { style: { display: 'flex', gap: '2px' } }, h(C.IconButton, { icon: col ? 'chevron-down' : 'chevron-up', label: col ? t('ActionCard', 'expand') : t('ActionCard', 'collapse'), size: 'sm', ghost: true, onClick: () => { set({ collapsed: !col }); p.onCollapse && p.onCollapse(!col); } }), h(C.IconButton, { icon: 'refresh-cw', label: t('ActionCard', 'refresh'), size: 'sm', ghost: true, onClick: () => { if (p.loading === undefined) { set({ loading: true }); setTimeout(() => set({ loading: false }), 1800); } p.onRefresh && p.onRefresh(); } }), h(C.IconButton, { icon: 'x', label: t('ActionCard', 'remove'), size: 'sm', ghost: true, onClick: () => { set({ removed: true }); p.onRemove && p.onRemove(); } }))),
|
|
28
29
|
col ? null : h('div', null, ...children), busy ? h('div', { class: 'zg-lo__o' }, h(C.Spinner, { size: 28 })) : null); } },
|
|
29
|
-
Snackbar: { render: ({ h, props: p, children }) => h('div', { class: cx('zg-snack', p.variant === 'tonal' && 'zg-snack--tonal', tc(p.tone)), role: 'status' }, h('span', { style: { flex: 1 } }, ...children), p.action ? h('button', { type: 'button', onClick: p.onAction, style: { color: p.tone === 'success' ? 'var(--zg-green)' : 'var(--zg-red)' } }, p.action) : null, p.onClose ? h('button', { type: 'button', onClick: p.onClose, 'aria-label': '
|
|
30
|
-
TourPopover: { render: ({ C, h, props: p }) => h('div', { class: 'zg-tour' }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, h('b', { style: { flex: 1, fontSize: '16px' } }, p.title), h('button', { type: 'button', 'aria-label': '
|
|
30
|
+
Snackbar: { render: ({ h, props: p, children }) => h('div', { class: cx('zg-snack', p.variant === 'tonal' && 'zg-snack--tonal', tc(p.tone)), role: 'status' }, h('span', { style: { flex: 1 } }, ...children), p.action ? h('button', { type: 'button', onClick: p.onAction, style: { color: p.tone === 'success' ? 'var(--zg-green)' : 'var(--zg-red)' } }, p.action) : null, p.onClose ? h('button', { type: 'button', onClick: p.onClose, 'aria-label': t('Snackbar', 'close') }, icon(h, 'x', 14)) : null) },
|
|
31
|
+
TourPopover: { render: ({ C, h, props: p }) => h('div', { class: 'zg-tour' }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, h('b', { style: { flex: 1, fontSize: '16px' } }, p.title), h('button', { type: 'button', 'aria-label': t('TourPopover', 'close'), class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', onClick: p.onClose }, icon(h, 'x', 14))), h('span', { style: { fontSize: '14px', lineHeight: 1.5 } }, p.text), h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, h('span', { class: 'zg-hint zg-mono' }, p.step || ''), h('div', { style: { display: 'flex', gap: '8px' } }, p.onBack ? h(C.Button, { size: 'sm', onClick: p.onBack }, t('TourPopover', 'back')) : null, h(C.Button, { size: 'sm', variant: 'primary', onClick: p.onNext }, p.last ? t('TourPopover', 'finish') : t('TourPopover', 'next'))))) },
|
|
31
32
|
ProfileHeader: { render: ({ C, h, props: p, children }) => h('section', { class: 'zg-card zg-glass zg-prof' }, h('div', { class: 'zg-prof__cover', style: { backgroundImage: p.cover ? `url(${p.cover})` : undefined } }), h('div', { class: 'zg-prof__row' }, h('span', { class: 'zg-prof__av' }, h(C.Avatar, { name: p.name, src: p.src, size: 104 })), h('div', { style: { flex: 1, minWidth: '200px', display: 'flex', flexDirection: 'column', gap: '8px' } }, h('b', { style: { fontSize: '22px', fontWeight: 500 } }, p.name), h('div', { class: 'zg-chips', style: { color: 'var(--zg-text-3)', fontSize: '13px' } }, ...(p.meta || []).map(m => h('span', { key: m.label, style: { display: 'inline-flex', gap: '6px', alignItems: 'center' } }, icon(h, m.icon, 14), m.label)))), ...children)) },
|
|
32
33
|
AuthSplit: { render: ({ h, props: p, children }) => h('div', { class: 'zg-root zg-split2' }, h('aside', { class: 'zg-split2__a' }, p.aside), h('main', { class: 'zg-split2__m' }, h('div', { style: { width: '100%', maxWidth: '420px', display: 'flex', flexDirection: 'column', gap: '20px' } }, p.logo || null, h('div', null, h('h1', { style: { margin: 0, font: '300 28px var(--zg-font)', letterSpacing: '-.03em' } }, p.title), p.subtitle ? h('p', { style: { margin: '6px 0 0', color: 'var(--zg-text-2)', lineHeight: 1.5 } }, p.subtitle) : null), ...children))) },
|
|
33
|
-
StatusPage: { state: { email: '' }, render: ({ C, h, props: p, children, state, set }) => { const K = { 404: ['404', '
|
|
34
|
+
StatusPage: { state: { email: '' }, render: ({ C, h, props: p, children, state, set }) => { const K = { 404: ['404', t('StatusPage', 'nfTitle'), t('StatusPage', 'nfDesc'), t('StatusPage', 'home')], 401: ['401', t('StatusPage', 'authTitle'), t('StatusPage', 'authDesc'), t('StatusPage', 'home')], maintenance: ['', t('StatusPage', 'mtTitle'), t('StatusPage', 'mtDesc'), t('StatusPage', 'home')], soon: ['', t('StatusPage', 'soonTitle'), t('StatusPage', 'soonDesc'), t('StatusPage', 'notify')] }[p.kind || '404'];
|
|
34
35
|
return h('div', { class: 'zg-es zg-es--page' }, p.codeNode || (K[0] ? h('span', { class: 'zg-num', style: { fontSize: 'clamp(80px,14vw,150px)', lineHeight: 1, color: 'var(--zg-brand-text)' } }, K[0]) : h('span', { class: 'zg-es__ico' }, icon(h, p.kind === 'soon' ? 'rocket' : 'wrench', 28))), p.messageNode || [h('b', { key: 't', style: { fontSize: '24px' } }, p.title || K[1]), h('span', { key: 'd' }, p.description || K[2])], p.actionNode || (p.kind === 'soon' ? h('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap', justifyContent: 'center', width: '100%', maxWidth: '420px' } }, h('div', { style: { flex: '1 1 200px' } }, h(C.TextField, { placeholder: p.emailLabel || 'name@email.com', icon: 'mail', modelValue: state.email, onChange: v => set({ email: v }) })), h(C.Button, { variant: 'primary', onClick: () => p.onNotify && p.onNotify(state.email) }, p.notifyLabel || K[3])) : h(C.Button, { variant: 'primary', onClick: p.onAction }, p.actionLabel || K[3])), ...children); } }
|
|
35
36
|
};
|
package/src/core/components4.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// @ziyarago/ui · components, part 4 — gaps closed after the Vuexy audit.
|
|
2
|
+
import { t } from '../i18n/index.js';
|
|
2
3
|
import { icon, cx, fmt } from './components.js';
|
|
3
4
|
import { trap } from './dialog.js';
|
|
4
5
|
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
5
|
-
const pd = v => { if (v == null || v === '') return null; if (v instanceof Date) return isNaN(v) ? null : v; if (typeof v === 'string') { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v.trim()); if (m) return new Date(+m[1], +m[2] - 1, +m[3]); } const d = new Date(v); return isNaN(d) ? null : d; };
|
|
6
|
+
export const pd = v => { if (v == null || v === '') return null; if (v instanceof Date) return isNaN(v) ? null : v; if (typeof v === 'string') { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v.trim()); if (m) return new Date(+m[1], +m[2] - 1, +m[3]); } const d = new Date(v); return isNaN(d) ? null : d; };
|
|
6
7
|
const SAN_TAGS = new Set('a b i u s em strong strike del ins sub sup p div span br hr ul ol li blockquote pre code h1 h2 h3 h4 h5 h6 img table thead tbody tr td th font small mark'.split(' ')), SAN_DROP = new Set('script style iframe object embed link meta base form input button textarea select svg math noscript template frame frameset applet'.split(' ')), SAN_ATTR = new Set('href src alt title target rel class style colspan rowspan align color size width height'.split(' '));
|
|
7
8
|
const sanitizeHtml = html => { if (typeof document === 'undefined') return html; const t = document.createElement('template'); t.innerHTML = html;
|
|
8
9
|
const walk = node => { for (const c of [...node.childNodes]) { if (c.nodeType !== 1) { if (c.nodeType !== 3) c.remove(); continue; } const tag = c.tagName.toLowerCase(); if (SAN_DROP.has(tag)) { c.remove(); continue; } walk(c); if (!SAN_TAGS.has(tag)) { c.replaceWith(...c.childNodes); continue; }
|
|
@@ -11,18 +12,18 @@ const sanitizeHtml = html => { if (typeof document === 'undefined') return html;
|
|
|
11
12
|
walk(t.content); return t.innerHTML; };
|
|
12
13
|
|
|
13
14
|
export const rules = {
|
|
14
|
-
required: () => v => (v == null || String(v).trim() === '') ? '
|
|
15
|
-
email: () => v => !v || /^\S+@\S+\.\S+$/.test(v) || '
|
|
16
|
-
url: () => v => !v || /^https?:\/\/\S+\.\S+/.test(v) || '
|
|
17
|
-
digits: n => v => !v || new RegExp(`^\\d{${n}}$`).test(v) ||
|
|
18
|
-
alpha: () => v => !v || /^[\p{L}\s]+$/u.test(v) || '
|
|
19
|
-
alphaDash: () => v => !v || /^[\p{L}\d_-]+$/u.test(v) || '
|
|
20
|
-
minLength: n => v => !v || String(v).length >= n ||
|
|
21
|
-
maxLength: n => v => !v || String(v).length <= n ||
|
|
22
|
-
range: (a, b) => v =>
|
|
23
|
-
regex: (re, msg) => v => !v || re.test(v) || msg || '
|
|
24
|
-
confirmed: get => v => v === get() || '
|
|
25
|
-
password: () => v => !v || (/\d/.test(v) && /[A-ZА-Я]/.test(v) && String(v).length >= 8) || '
|
|
15
|
+
required: () => v => (v == null || String(v).trim() === '') ? t('Validation', 'required') : true,
|
|
16
|
+
email: () => v => !v || /^\S+@\S+\.\S+$/.test(v) || t('Validation', 'email'),
|
|
17
|
+
url: () => v => !v || /^https?:\/\/\S+\.\S+/.test(v) || t('Validation', 'url'),
|
|
18
|
+
digits: n => v => !v || new RegExp(`^\\d{${n}}$`).test(v) || t('Validation', 'digits', { n }),
|
|
19
|
+
alpha: () => v => !v || /^[\p{L}\s]+$/u.test(v) || t('Validation', 'alpha'),
|
|
20
|
+
alphaDash: () => v => !v || /^[\p{L}\d_-]+$/u.test(v) || t('Validation', 'alphaDash'),
|
|
21
|
+
minLength: n => v => !v || String(v).length >= n || t('Validation', 'minLength', { n }),
|
|
22
|
+
maxLength: n => v => !v || String(v).length <= n || t('Validation', 'maxLength', { n }),
|
|
23
|
+
range: (a, b) => v => v == null || v === '' || (Number(v) >= a && Number(v) <= b) || t('Validation', 'range', { a, b }),
|
|
24
|
+
regex: (re, msg) => v => !v || re.test(v) || msg || t('Validation', 'invalid'),
|
|
25
|
+
confirmed: get => v => v === get() || t('Validation', 'confirmed'),
|
|
26
|
+
password: () => v => !v || (/\d/.test(v) && /[A-ZА-Я]/.test(v) && String(v).length >= 8) || t('Validation', 'password')
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
export const more4 = {
|
|
@@ -33,26 +34,26 @@ export const more4 = {
|
|
|
33
34
|
return h(Comp, { ...p, modelValue: val, error: p.error || err, onChange: v => { set({ v, touched: true }); p.onChange && p.onChange(v); } }); } },
|
|
34
35
|
Footer: { render: ({ h, props: p, children }) => h('footer', { class: 'zg-foot' }, h('span', { style: { color: 'var(--zg-text-3)', fontSize: '13px' } }, p.copyright || '© 2026 ZiyaraGo'), h('nav', { style: { display: 'flex', gap: '16px', flexWrap: 'wrap' } }, ...(p.links || []).map(l => h('a', { key: l.label, href: l.href || '#', style: { color: 'var(--zg-brand-text)', fontSize: '13px', fontWeight: 600 } }, l.label))), ...children) },
|
|
35
36
|
DataGroup: { state: { open: {} , grp: {} }, render: ({ C, h, props: p, state, set }) => { const cols = p.columns || [];
|
|
36
|
-
const row = r => { const o = !!state.open[r.id]; return [h('tr', { key: r.id, 'aria-selected': o ? 'true' : undefined }, p.expand ? h('td', { style: { width: '44px' } }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-expanded': String(o), 'aria-label': '
|
|
37
|
+
const row = r => { const o = !!state.open[r.id]; return [h('tr', { key: r.id, 'aria-selected': o ? 'true' : undefined }, p.expand ? h('td', { style: { width: '44px' } }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-expanded': String(o), 'aria-label': t('DataGroup', 'expand'), onClick: () => set({ open: { ...state.open, [r.id]: !o } }) }, icon(h, o ? 'chevron-down' : 'chevron-right', 16))) : null, ...cols.map(c => h('td', { key: c.key, class: c.num ? 'num' : undefined }, c.render ? c.render(r[c.key], r) : r[c.key]))), o && p.expand ? h('tr', { key: r.id + 'x' }, h('td', { colSpan: cols.length + 1, style: { background: 'var(--zg-s1)', padding: '16px 20px' } }, p.expand(r))) : null]; };
|
|
37
38
|
return h('div', { class: 'zg-table-wrap' }, h('table', { class: 'zg-table' }, h('thead', null, h('tr', null, p.expand ? h('th') : null, ...cols.map(c => h('th', { key: c.key, class: c.num ? 'num' : undefined }, c.label)))),
|
|
38
39
|
h('tbody', null, ...(p.groups || []).flatMap(g => { const c = !!state.grp[g.label]; return [h('tr', { key: 'g' + g.label }, h('td', { colSpan: cols.length + 1, style: { padding: 0 } }, h('button', { type: 'button', class: 'zg-dg__g', onClick: () => set({ grp: { ...state.grp, [g.label]: !c } }), style: { all: 'unset', boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: '8px', width: '100%', padding: '10px 16px', cursor: 'pointer', fontWeight: 700, fontSize: '13px', background: 'var(--zg-s1)' } }, icon(h, c ? 'chevron-right' : 'chevron-down', 14), g.label + ' (' + g.rows.length + ')'))), ...(c ? [] : g.rows.flatMap(row))]; })))); } },
|
|
39
40
|
OptionCard: { render: ({ h, props: p }) => h('button', { type: 'button', role: p.multiple ? 'checkbox' : 'radio', 'aria-checked': String(!!p.checked), class: 'zg-opt', onClick: p.onClick }, p.image ? h('span', { class: 'zg-opt__img', style: { backgroundImage: `url(${p.image})` } }) : p.icon ? h('span', { class: 'zg-tile zg-t-brand', style: { '--s': '40px' } }, icon(h, p.icon, 20)) : null, h('span', { style: { display: 'flex', flexDirection: 'column', gap: '2px', flex: 1, textAlign: 'start' } }, h('b', { style: { fontSize: '15px' } }, p.title), p.desc ? h('span', { class: 'zg-hint' }, p.desc) : null), p.price ? h('b', { class: 'zg-num', style: { fontSize: '16px' } }, p.price) : null, h('span', { class: cx('zg-check__box', !p.multiple && 'zg-radio'), style: { background: p.checked ? 'var(--zg-brand)' : 'transparent' } }, p.checked ? (p.multiple ? icon(h, 'check', 12) : h('i')) : null)) },
|
|
40
41
|
OptionCards: { render: ({ C, h, props: p }) => { const cur = p.multiple ? (p.modelValue || []) : p.modelValue;
|
|
41
42
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { style: { display: 'grid', gridTemplateColumns: `repeat(auto-fit,minmax(min(100%,${p.min || 220}px),1fr))`, gap: '10px' } }, ...(p.options || []).map(o => h(C.OptionCard, { ...o, multiple: p.multiple, checked: p.multiple ? cur.includes(o.value) : cur === o.value, onClick: () => p.onChange && p.onChange(p.multiple ? (cur.includes(o.value) ? cur.filter(x => x !== o.value) : [...cur, o.value]) : o.value) })))); } },
|
|
42
43
|
RichEditor: { render: ({ h, props: p }) => { let ed; const san = x => p.sanitize ? sanitizeHtml(x) : x, emit = () => { if (!ed) return; const x = san(ed.innerHTML); ed.__zg = x; p.onChange && p.onChange(x); }; const cmd = (c, v) => () => { if (!ed) return; ed.focus(); try { document.execCommand(c, false, v); } catch (e) {} emit(); };
|
|
43
|
-
const B = [['bold', 'bold', '
|
|
44
|
-
const area = h('div', { class: 'zg-rte__c', contentEditable: 'true', role: 'textbox', 'aria-multiline': 'true', 'aria-label': p.label || '
|
|
45
|
-
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-rte' }, h('div', { class: 'zg-rte__t' }, ...B.map((b, i) => b ? h('button', { key: b[0], type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', title: b[2], 'aria-label': b[2], onMouseDown: e => e.preventDefault(), onClick: cmd(b[0], b[3]) }, icon(h, b[1], 16)) : h('span', { key: 's' + i, class: 'zg-cb__s' }))), area)); } },
|
|
44
|
+
const B = [['bold', 'bold', t('RichEditor', 'bold')], ['italic', 'italic', t('RichEditor', 'italic')], ['underline', 'underline', t('RichEditor', 'underline')], ['strikeThrough', 'strikethrough', t('RichEditor', 'strike')], null, ['justifyLeft', 'align-left', t('RichEditor', 'left')], ['justifyCenter', 'align-center', t('RichEditor', 'center')], ['justifyRight', 'align-right', t('RichEditor', 'right')], null, ['insertUnorderedList', 'list', t('RichEditor', 'bullets')], ['insertOrderedList', 'list-ordered', t('RichEditor', 'numbers')], null, ['formatBlock', 'quote', t('RichEditor', 'quote'), 'blockquote'], ['formatBlock', 'code', t('RichEditor', 'codeBlock'), 'pre']];
|
|
45
|
+
const area = h('div', { class: 'zg-rte__c', contentEditable: 'true', role: 'textbox', 'aria-multiline': 'true', 'aria-label': p.label || t('RichEditor', 'editor'), 'data-ph': p.placeholder || t('RichEditor', 'placeholder'), ref: el => { if (!el) return; ed = el; const want = p.modelValue ?? ''; if (el.__zg !== want && el.innerHTML !== want) el.innerHTML = san(want); el.__zg = want; }, onFocus: e => { ed = e.currentTarget; }, onInput: e => { ed = e.currentTarget; emit(); } });
|
|
46
|
+
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-rte' }, h('div', { class: 'zg-rte__t' }, ...B.map((b, i) => b ? h('button', { key: b[0] + i, type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', title: b[2], 'aria-label': b[2], onMouseDown: e => e.preventDefault(), onClick: cmd(b[0], b[3]) }, icon(h, b[1], 16)) : h('span', { key: 's' + i, class: 'zg-cb__s' }))), area)); } },
|
|
46
47
|
RangeSlider: { render: ({ h, props: p }) => { const mn = p.min ?? 0, mx = p.max ?? 100, [a, b] = p.modelValue || [mn, mx], pa = (a - mn) / (mx - mn) * 100, pb = (b - mn) / (mx - mn) * 100, f = p.formatValue || (v => v);
|
|
47
|
-
const inp = (v, k) => h('input', { type: 'range', class: 'zg-range2', min: mn, max: mx, step: p.step || 1, value: v, 'aria-label': k ? '
|
|
48
|
+
const inp = (v, k) => h('input', { type: 'range', class: 'zg-range2', min: mn, max: mx, step: p.step || 1, value: v, 'aria-label': k ? t('RangeSlider', 'max') : t('RangeSlider', 'min'), onInput: e => { const x = Number(e.target.value); p.onChange && p.onChange(k ? [a, Math.max(x, a)] : [Math.min(x, b), b]); } });
|
|
48
49
|
return h('div', { class: 'zg-field' }, h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, p.label ? h('span', { class: 'zg-label' }, p.label) : h('span'), h('span', { class: 'zg-mono', style: { fontSize: '13px' } }, f(a) + ' – ' + f(b))), h('div', { class: 'zg-range2w', style: { '--a': pa + '%', '--b': pb + '%' } }, h('i'), inp(a, 0), inp(b, 1))); } },
|
|
49
50
|
RangeCalendar: { state: { from: null, to: null, hover: null, ym: null }, render: ({ h, props: p, state, set }) => { const loc = p.locale || 'en-GB', ctl = p.modelValue !== undefined, mv = ctl ? (p.modelValue || []) : [], day = v => { const d = pd(v); return d ? new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime() : null; }, f = ctl ? day(mv[0]) : state.from, tt = ctl ? day(mv[1]) : state.to, b = state.ym ? new Date(state.ym) : (f != null ? new Date(f) : (pd(p.start) || new Date())), y = b.getFullYear(), m = b.getMonth(), ws = p.weekStartsOn ?? 1, off = (new Date(y, m, 1).getDay() - ws + 7) % 7, dn = new Date(y, m + 1, 0).getDate();
|
|
50
|
-
const wd = Array.from({ length: 7 }, (_, i) => new Intl.DateTimeFormat(loc, { weekday: 'narrow' }).format(new Date(2023, 0, 1 + ((i + ws) % 7)))),
|
|
51
|
-
const pick = d => { const x =
|
|
52
|
-
return h('div', { class: 'zg-cal' }, h('div', { class: 'zg-cal__h' }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': '
|
|
53
|
-
h('div', { class: 'zg-cal__g' }, ...wd.map((d, i) => h('span', { key: 'w' + i, class: 'zg-cal__w' }, d)), ...Array.from({ length: off }, (_, i) => h('span', { key: 'o' + i })), ...Array.from({ length: dn }, (_, i) => { const d = i + 1, x =
|
|
51
|
+
const wd = Array.from({ length: 7 }, (_, i) => new Intl.DateTimeFormat(loc, { weekday: 'narrow' }).format(new Date(2023, 0, 1 + ((i + ws) % 7)))), td = d => new Date(y, m, d).getTime(), e = tt ?? (f && state.hover && state.hover > f ? state.hover : null);
|
|
52
|
+
const pick = d => { const x = td(d); if (f == null || tt != null || x < f) { if (ctl) { set({ hover: null }); p.onChange && p.onChange([new Date(x), null]); } else set({ from: x, to: null, hover: null }); } else { if (!ctl) set({ to: x }); p.onChange && p.onChange([new Date(f), new Date(x)]); } };
|
|
53
|
+
return h('div', { class: 'zg-cal' }, h('div', { class: 'zg-cal__h' }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('RangeCalendar', 'prev'), onClick: () => set({ ym: new Date(y, m - 1, 1).getTime() }) }, icon(h, 'chevron-left', 16)), h('b', null, new Intl.DateTimeFormat(loc, { month: 'long', year: 'numeric' }).format(b)), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('RangeCalendar', 'next'), onClick: () => set({ ym: new Date(y, m + 1, 1).getTime() }) }, icon(h, 'chevron-right', 16))),
|
|
54
|
+
h('div', { class: 'zg-cal__g' }, ...wd.map((d, i) => h('span', { key: 'w' + i, class: 'zg-cal__w' }, d)), ...Array.from({ length: off }, (_, i) => h('span', { key: 'o' + i })), ...Array.from({ length: dn }, (_, i) => { const d = i + 1, x = td(d), edge = x === f || x === e, mid = f && e && x > f && x < e; return h('button', { key: d, type: 'button', class: 'zg-cal__d', 'aria-pressed': String(!!edge), 'data-mid': mid ? '' : undefined, onMouseEnter: () => f != null && tt == null && set({ hover: x }), onClick: () => pick(d) }, d); }))); } },
|
|
54
55
|
StepDialog: { render: ({ C, h, props: p, children, slots }) => !p.open ? null : h('div', { class: 'zg-modal-scrim', onClick: p.onClose }, h('div', { class: 'zg-modal zg-stepdlg', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }), onClick: e => e.stopPropagation() },
|
|
55
56
|
h('aside', { class: 'zg-stepdlg__s' }, ...(p.steps || []).map((s, i) => h('div', { key: i, class: cx('zg-stepdlg__i', i < p.current && 'is-done', i === p.current && 'is-cur') }, h('span', { class: 'zg-step__n' }, i < p.current ? icon(h, 'check', 14) : i + 1), h('span', { style: { display: 'flex', flexDirection: 'column' } }, h('b', { style: { fontSize: '14px' } }, s.title), s.sub ? h('span', { class: 'zg-hint' }, s.sub) : null)))),
|
|
56
|
-
h('div', { style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' } }, h('div', { class: 'zg-modal__head' }, h('h3', null, p.title), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': '
|
|
57
|
-
FullscreenDialog: { render: ({ C, h, props: p, children }) => !p.open ? null : h('div', { class: 'zg-fsd', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }) }, h('div', { class: 'zg-fsd__h' }, h(C.IconButton, { icon: 'x', label: '
|
|
57
|
+
h('div', { style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' } }, h('div', { class: 'zg-modal__head' }, h('h3', null, p.title), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('StepDialog', 'close'), onClick: p.onClose }, icon(h, 'x'))), h('div', { class: 'zg-modal__body', style: { flex: 1 } }, ...children), h('div', { class: 'zg-modal__foot', style: { justifyContent: 'space-between' } }, h(C.Button, { disabled: p.current === 0, onClick: p.onBack }, t('StepDialog', 'previous')), h(C.Button, { variant: 'primary', onClick: p.onNext }, p.current === (p.steps || []).length - 1 ? t('StepDialog', 'submit') : t('StepDialog', 'next')))))) },
|
|
58
|
+
FullscreenDialog: { render: ({ C, h, props: p, children }) => !p.open ? null : h('div', { class: 'zg-fsd', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }) }, h('div', { class: 'zg-fsd__h' }, h(C.IconButton, { icon: 'x', label: t('FullscreenDialog', 'close'), onClick: p.onClose }), h('b', { style: { flex: 1, fontSize: '18px' } }, p.title), h(C.Button, { variant: 'inverse', onClick: p.onSave }, p.saveLabel || t('FullscreenDialog', 'save'))), h('div', { class: 'zg-fsd__b' }, ...children)) }
|
|
58
59
|
};
|