@ziyarago/ui 0.14.0 → 0.15.1
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 +5 -5
- package/dist/llms-full.txt +8 -4
- package/dist/llms.txt +2 -2
- package/dist/manifest.json +343 -4
- package/dist/style.css +13 -13
- package/docs/INTEGRATION.md +12 -0
- package/package.json +12 -25
- package/src/core/components.js +11 -15
- package/src/core/components2.js +2 -72
- package/src/core/components3.js +2 -44
- package/src/core/components4.js +0 -38
- package/src/core/components6.js +0 -50
- package/src/core/components7.js +0 -34
- package/src/core/index.js +2 -2
- package/src/core/pages.js +2 -36
- package/src/i18n/components/index.js +0 -72
- package/src/icons/builtin.js +99 -0
- package/src/internal.js +10 -0
- package/src/meta/forms.js +5 -1
- package/src/meta/index.js +2 -0
- package/src/meta/pages.js +1 -1
- package/src/react/index.js +1 -107
- package/src/styles/components.css +8 -8
- package/src/styles/components2.css +2 -2
- package/src/styles/components7.css +1 -1
- package/src/styles/components8.css +1 -1
- package/src/styles/extras.css +1 -1
- package/src/vue/index.js +2 -2
- package/types/core.d.ts +1 -1
- package/types/internal.d.ts +30 -0
- package/types/meta.d.ts +5 -0
- package/types/react.d.ts +154 -4141
- package/types/shared.d.ts +2 -0
- package/types/vue-global.d.ts +0 -106
- package/types/vue.d.ts +168 -3601
- package/docs/MIGRATION.md +0 -455
- package/src/compat/index.js +0 -469
- package/src/compat/map.js +0 -282
- package/src/compat/resolver.js +0 -22
- package/src/compat/vite.js +0 -29
- package/src/i18n/components/ActionCard.js +0 -5
- package/src/i18n/components/Actionsheet.js +0 -5
- package/src/i18n/components/CallCard.js +0 -5
- package/src/i18n/components/CallRow.js +0 -5
- package/src/i18n/components/Carousel.js +0 -5
- package/src/i18n/components/ChartCard.js +0 -5
- package/src/i18n/components/ChatAi.js +0 -5
- package/src/i18n/components/ChipInput.js +0 -5
- package/src/i18n/components/ContactPanel.js +0 -5
- package/src/i18n/components/DataGroup.js +0 -5
- package/src/i18n/components/DealCard.js +0 -5
- package/src/i18n/components/FullscreenDialog.js +0 -5
- package/src/i18n/components/Gallery.js +0 -5
- package/src/i18n/components/GuestCard.js +0 -5
- package/src/i18n/components/ImageUpload.js +0 -5
- package/src/i18n/components/ImageViewer.js +0 -5
- package/src/i18n/components/IncomingCall.js +0 -5
- package/src/i18n/components/InlineEdit.js +0 -5
- package/src/i18n/components/IntegrationCard.js +0 -5
- package/src/i18n/components/Map.js +0 -5
- package/src/i18n/components/NotificationBell.js +0 -5
- package/src/i18n/components/PricingCard.js +0 -5
- package/src/i18n/components/QiblaCompass.js +0 -5
- package/src/i18n/components/RamadanTracker.js +0 -5
- package/src/i18n/components/RangeCalendar.js +0 -5
- package/src/i18n/components/RangeSlider.js +0 -5
- package/src/i18n/components/RichEditor.js +0 -5
- package/src/i18n/components/Screensaver.js +0 -5
- package/src/i18n/components/Sortable.js +0 -5
- package/src/i18n/components/StepDialog.js +0 -5
- package/src/i18n/components/StepForm.js +0 -5
- package/src/i18n/components/TabTiles.js +0 -5
- package/src/i18n/components/Tour.js +0 -5
- package/src/i18n/components/TourCard.js +0 -5
- package/src/i18n/components/TourPopover.js +0 -5
- package/src/i18n/components/TreeSelect.js +0 -5
- package/types/compat-resolver.d.ts +0 -12
- package/types/compat-vite.d.ts +0 -11
- package/types/compat.d.ts +0 -176
package/src/core/components3.js
CHANGED
|
@@ -1,36 +1,15 @@
|
|
|
1
1
|
// @ziyarago/ui · components, part 3 — card patterns, tonal variants, shells for auth/profile/status pages.
|
|
2
|
-
import { icon, cx
|
|
2
|
+
import { icon, cx } from './components.js';
|
|
3
3
|
import { t } from '../i18n/index.js';
|
|
4
|
-
import { rove, tabIdx
|
|
4
|
+
import { rove, tabIdx } from './keys.js';
|
|
5
5
|
const TONES = ['brand', 'sky', 'success', 'warning', 'danger', 'neutral'];
|
|
6
6
|
const tc = t => 'zg-t-' + (TONES.includes(t) ? t : 'brand');
|
|
7
|
-
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;
|
|
8
|
-
|
|
9
7
|
export const more3 = {
|
|
10
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': t('Chip', 'remove'), onClick: p.onClose || p.onRemove }, icon(h, 'x', 12)) : null) },
|
|
11
9
|
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))) },
|
|
12
10
|
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))) },
|
|
13
|
-
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)) },
|
|
14
|
-
StatRow: { render: ({ C, h, props: p }) => h('div', { class: 'zg-srow' }, h(C.IconTile, { icon: p.icon, tone: p.tone, size: { xs: 28, sm: 34, md: 40, lg: 48, xl: 56 }[p.size] || p.size || 40 }), h('div', { style: { flex: 1, minWidth: 0 } }, h('b', { class: 'zg-srow__t', style: { fontSize: '15px' } }, p.title), p.sub ? h('div', { class: 'zg-hint' }, p.sub) : null), h('div', { style: { textAlign: 'end' } }, h('div', { class: 'zg-num zg-srow__v', style: { fontSize: '17px', fontWeight: 600 } }, p.value), delta(h, p.delta))) },
|
|
15
|
-
CountryRow: { render: ({ h, props: p }) => h('div', { class: 'zg-srow' }, h('span', { class: 'zg-tile zg-tile--circle zg-t-neutral', style: { '--s': '38px', font: '700 12px var(--zg-font)' } }, p.code), h('div', { style: { flex: 1, minWidth: 0 } }, h('b', { class: 'zg-num', style: { fontSize: '16px' } }, p.amount), h('div', { class: 'zg-hint' }, p.name)), delta(h, p.pct)) },
|
|
16
|
-
StatLine: { render: ({ C, h, props: p }) => h('div', { class: cx('zg-card zg-glass zg-statline', tc(p.tone)) }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '12px' } }, h(C.IconTile, { icon: p.icon, tone: p.tone, size: { xs: 28, sm: 34, md: 40, lg: 48, xl: 56 }[p.size] || p.size || 40 }), h('span', { class: 'zg-num zg-statline__v', style: { fontSize: '26px', fontWeight: 400 } }, p.value)), h('span', { class: 'zg-hint' }, p.label), p.delta ? h('span', null, delta(h, p.delta)) : null, h('i', { class: 'zg-statline__bar' })) },
|
|
17
|
-
StatStrip: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-strip' }, ...(p.items || []).map((it, i) => h('div', { key: i, class: 'zg-strip__i' }, h(C.IconTile, { icon: it.icon, tone: it.tone, size: { xs: 30, sm: 36, md: 44, lg: 52, xl: 60 }[p.size] || p.size || 44 }), h('div', null, h('div', { class: 'zg-num zg-strip__v', style: { fontSize: '22px', fontWeight: 400 } }, it.value), h('div', { class: 'zg-hint' }, it.label))))) },
|
|
18
|
-
VersusBar: { render: ({ h, props: p }) => { const a = { ...p.left }, b = { ...p.right }; a.value = Math.max(0, Number(a.value) || 0); b.value = Math.max(0, Number(b.value) || 0); const t = (a.value + b.value) || 1;
|
|
19
|
-
return h('div', { class: 'zg-vs' }, h('div', { class: 'zg-vs__r' }, h('div', null, h('div', { class: 'zg-hint' }, a.label), h('b', { class: 'zg-num', style: { fontSize: '20px' } }, fmt.num(a.value))), h('span', { class: 'zg-tile zg-tile--circle zg-t-neutral', style: { '--s': '32px', font: '700 11px var(--zg-font)' } }, 'VS'), h('div', { style: { textAlign: 'end' } }, h('div', { class: 'zg-hint' }, b.label), h('b', { class: 'zg-num', style: { fontSize: '20px' } }, fmt.num(b.value)))),
|
|
20
|
-
h('div', { class: 'zg-vs__bar' }, h('i', { style: { width: (a.value / t * 100) + '%', background: 'var(--zg-brand)' } }), h('i', { style: { flex: 1, background: 'var(--zg-sky)' } }))); } },
|
|
21
|
-
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)'];
|
|
22
|
-
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)))); } },
|
|
23
|
-
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) },
|
|
24
|
-
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) },
|
|
25
11
|
PillTabs: { render: ({ h, props: p }) => h('div', { class: cx('zg-pt', p.vertical && 'zg-pt--v'), role: 'tablist', 'aria-orientation': p.vertical ? 'vertical' : undefined, onKeydown: rove('[role=tab]', { vertical: !!p.vertical }) }, ...(p.items || []).map((it, i, all) => h('button', { key: it.value, type: 'button', role: 'tab', tabIndex: tabIdx(all, x => x.value === p.value)(i), disabled: it.disabled, 'aria-selected': String(it.value === p.value), onClick: () => p.onChange && p.onChange(it.value) }, it.icon ? icon(h, it.icon, 16) : null, it.label))) },
|
|
26
|
-
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 }), onKeydown: openKeys(open, () => set({ open: true })) }, (opts.find(o => o.value === v) || {}).label || p.placeholder, icon(h, 'chevron-down', 14)), open ? h('div', { class: 'zg-menu', role: 'listbox', 'aria-label': p.placeholder || (opts.find(o => o.value === v) || {}).label, 'data-zg-dd': '', ref: ddRef(() => set({ open: false }), { focus: true }), onKeydown: listKeys('[role=option]', { onTab: e => { const t = trigger(e.currentTarget); t && t.focus(); set({ open: false }); } }) }, ...opts.map((o, i) => h('button', { key: o.value, type: 'button', role: 'option', tabIndex: tabIdx(opts, x => x.value === v)(i), class: 'zg-menu__i', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === v), onClick: e => { toTrigger(e); set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label))) : null); } },
|
|
27
|
-
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),
|
|
28
|
-
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) },
|
|
29
|
-
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(); } }))),
|
|
30
|
-
col ? null : h('div', null, ...children), busy ? h('div', { class: 'zg-lo__o' }, h(C.Spinner, { size: 28 })) : null); } },
|
|
31
12
|
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) },
|
|
32
|
-
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'))))) },
|
|
33
|
-
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)) },
|
|
34
13
|
SocialAuth: { render: ({ C, h, props: p }) => {
|
|
35
14
|
const list = (p.providers || ['google', 'apple']).filter(k => k === 'google' || k === 'apple');
|
|
36
15
|
const icon = k => k === 'apple'
|
|
@@ -46,27 +25,6 @@ export const more3 = {
|
|
|
46
25
|
p.divider === false ? null : h('div', { class: 'zg-social__or', role: 'separator' }, h('span', null, t('SocialAuth', 'or'))),
|
|
47
26
|
h('div', { class: 'zg-social__row' }, ...list.map(k => h(C.Button, { key: k, variant: 'secondary', size: 'lg', block: stack, iconOnly: !stack, title: t('SocialAuth', 'continueWith', { provider: name(k) }), 'aria-label': t('SocialAuth', 'continueWith', { provider: name(k) }), onClick: () => p.onSelect && p.onSelect(k) }, icon(k), stack ? t('SocialAuth', 'continueWith', { provider: name(k) }) : null))));
|
|
48
27
|
} },
|
|
49
|
-
AuthShowcase: { render: ({ C, h, props: p, children }) => {
|
|
50
|
-
const v = p.variant === 'light' || p.variant === 'tint' ? p.variant : 'photo';
|
|
51
|
-
const people = p.people || [];
|
|
52
|
-
const stack = size => h('span', { class: 'zg-ash__stack' }, ...people.slice(0, 4).map((u, i) => h(C.Avatar, { key: i, name: u.name, src: u.src, size })));
|
|
53
|
-
const head = h('div', { class: 'zg-ash__head' }, p.logo || null, h('h1', { class: 'zg-ash__title' }, p.title), p.subtitle ? h('p', { class: 'zg-ash__sub' }, p.subtitle) : null);
|
|
54
|
-
const form = h('div', { class: 'zg-ash__form' }, head, h('div', { class: 'zg-ash__fields' }, ...children), p.footer ? h('div', { class: 'zg-ash__foot' }, p.footer) : null);
|
|
55
|
-
const art = { backgroundImage: p.image ? 'url(' + p.image + ')' : undefined };
|
|
56
|
-
if (v === 'photo') {
|
|
57
|
-
const spots = ['a', 'b', 'c'];
|
|
58
|
-
return h('div', { class: 'zg-root zg-ash zg-ash--photo' }, h('div', { class: 'zg-ash__frame' },
|
|
59
|
-
h('section', { class: 'zg-ash__media', style: art }, h('div', { class: 'zg-ash__copy' }, p.headline ? h('h2', null, p.headline) : null, p.caption ? h('p', null, p.caption) : null),
|
|
60
|
-
...people.slice(0, 3).map((u, i) => h('span', { key: i, class: 'zg-ash__float zg-ash__float--' + spots[i] }, h(C.Avatar, { name: u.name, src: u.src, size: i === 1 ? 'lg' : 'md' }))),
|
|
61
|
-
p.quote ? h('small', { class: 'zg-ash__quote' }, p.quote) : null),
|
|
62
|
-
h('main', { class: 'zg-ash__side' }, form)));
|
|
63
|
-
}
|
|
64
|
-
return h('div', { class: 'zg-root zg-ash zg-ash--' + v }, h('div', { class: 'zg-ash__grid' },
|
|
65
|
-
h('div', { class: 'zg-ash__col' }, h('main', { class: 'zg-ash__card' }, form),
|
|
66
|
-
p.proof ? h('div', { class: 'zg-ash__proof' }, stack('sm'), h('div', { class: 'zg-ash__proofT' }, h('b', null, p.proof), p.proofHint ? h('span', null, p.proofHint) : null), h(C.IconButton, { icon: 'arrow-up-right', label: p.proof, ghost: true })) : null),
|
|
67
|
-
h('section', { class: 'zg-ash__media', style: art }, v === 'tint' && p.headline ? h('h2', { class: 'zg-ash__mh' }, p.headline) : null,
|
|
68
|
-
h('div', { class: 'zg-ash__glass' }, (p.chips || []).length ? h('div', { class: 'zg-ash__chips' }, ...p.chips.map((c, i) => h('span', { key: i, class: 'zg-ash__chip' }, c))) : null, p.caption ? h('p', null, p.caption) : null))));
|
|
69
|
-
} },
|
|
70
28
|
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))) },
|
|
71
29
|
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'];
|
|
72
30
|
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); } }
|
package/src/core/components4.js
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
// @ziyarago/ui · components, part 4 — gaps closed after the Vuexy audit.
|
|
2
2
|
import { t } from '../i18n/index.js';
|
|
3
|
-
import { icon, cx, fmt } from './components.js';
|
|
4
|
-
import { trap } from './dialog.js';
|
|
5
3
|
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
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; };
|
|
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(' '));
|
|
8
|
-
const sanitizeHtml = html => { if (typeof document === 'undefined') return html; const t = document.createElement('template'); t.innerHTML = html;
|
|
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; }
|
|
10
|
-
for (const a of [...c.attributes]) { const n = a.name.toLowerCase(), v = a.value.replace(/[\s\u0000-\u001f]/g, '').toLowerCase(); if (!SAN_ATTR.has(n) || n.startsWith('on') || (/^(javascript|vbscript|data):/.test(v) && !(n === 'src' && /^data:image\//.test(v))) || (n === 'style' && /expression\(|javascript:|url\(|@import/.test(v))) c.removeAttribute(a.name); }
|
|
11
|
-
if (tag === 'a' && c.getAttribute('target') === '_blank') c.setAttribute('rel', 'noopener noreferrer'); } };
|
|
12
|
-
walk(t.content); return t.innerHTML; };
|
|
13
|
-
|
|
14
4
|
export const rules = {
|
|
15
5
|
required: () => v => (v == null || String(v).trim() === '') ? t('Validation', 'required') : true,
|
|
16
6
|
email: () => v => !v || /^\S+@\S+\.\S+$/.test(v) || t('Validation', 'email'),
|
|
@@ -27,33 +17,5 @@ export const rules = {
|
|
|
27
17
|
};
|
|
28
18
|
|
|
29
19
|
export const more4 = {
|
|
30
|
-
ValidatedField: { state: { v: undefined, touched: false }, render: ({ C, h, props: p, state, set }) => {
|
|
31
|
-
const val = state.v ?? p.modelValue ?? '', rs = p.rules || [];
|
|
32
|
-
let err = null; if (state.touched) for (const r of rs) { const x = r(val); if (x !== true) { err = x; break; } }
|
|
33
|
-
const Comp = p.type === 'password' ? C.PasswordInput : C.TextField;
|
|
34
|
-
return h(Comp, { ...p, modelValue: val, error: p.error || err, onChange: v => { set({ v, touched: true }); p.onChange && p.onChange(v); } }); } },
|
|
35
20
|
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) },
|
|
36
|
-
DataGroup: { state: { open: {} , grp: {} }, render: ({ C, h, props: p, state, set }) => { const cols = p.columns || [];
|
|
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]; };
|
|
38
|
-
return h('div', { class: 'zg-table-wrap', tabIndex: 0 }, 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)))),
|
|
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))]; })))); } },
|
|
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)) },
|
|
41
|
-
OptionCards: { render: ({ C, h, props: p }) => { const cur = p.multiple ? (p.modelValue || []) : p.modelValue;
|
|
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) })))); } },
|
|
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(); };
|
|
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)); } },
|
|
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);
|
|
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]); } });
|
|
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))); } },
|
|
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();
|
|
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); }))); } },
|
|
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() },
|
|
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)))),
|
|
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)) }
|
|
59
21
|
};
|
package/src/core/components6.js
CHANGED
|
@@ -1,59 +1,9 @@
|
|
|
1
1
|
// @ziyarago/ui · components, part 6 — last catalog gaps: TreeSelect, ContextMenu, Sortable, Map, Screensaver, ImageUpload, Tour.
|
|
2
|
-
import { t } from '../i18n/index.js';
|
|
3
2
|
import { icon, cx, ddRef } from './components.js';
|
|
4
|
-
import { trap, life } from './dialog.js';
|
|
5
3
|
import { listKeys, tabIdx } from './keys.js';
|
|
6
|
-
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
7
|
-
|
|
8
4
|
export const more6 = {
|
|
9
|
-
TreeSelect: { state: { open: false, q: '', ex: {} }, render: ({ h, props: p, state, set }) => {
|
|
10
|
-
const flat = (ns, d = 0) => ns.flatMap(n => [{ ...n, d }, ...(n.children ? flat(n.children, d + 1) : [])]), all = flat(p.nodes || []), cur = all.find(n => n.id === p.modelValue), q = state.q.toLowerCase();
|
|
11
|
-
const node = (n, d) => { const has = n.children && n.children.length, o = q ? true : (state.ex[n.id] ?? d < 1);
|
|
12
|
-
if (q && !all.filter(x => x.label.toLowerCase().includes(q)).some(x => x.id === n.id || (n.children || []).some(function f(c) { return c.id === x.id || (c.children || []).some(f); }))) return null;
|
|
13
|
-
return h('div', { key: n.id }, h('button', { type: 'button', role: 'treeitem', disabled: n.disabled, 'aria-disabled': n.disabled ? 'true' : undefined, class: cx('zg-tree__n', n.id === p.modelValue && 'zg-tree__n--on'), style: { paddingInlineStart: 8 + d * 18 + 'px', width: '100%' }, onClick: () => { if (has && !q) set({ ex: { ...state.ex, [n.id]: !o } }); if (!has || p.selectGroups) { set({ open: false, q: '' }); p.onChange && p.onChange(n.id); } } }, has ? icon(h, o ? 'chevron-down' : 'chevron-right', 14) : h('span', { style: { width: '14px' } }), n.label), has && o ? n.children.map(c => node(c, d + 1)) : null); };
|
|
14
|
-
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': 'tree', '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)),
|
|
15
|
-
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'tree', 'data-zg-dd': '', ref: ddRef(() => set({ open: false, q: '' })), style: { padding: '8px' } }, h('span', { class: 'zg-input', style: { '--h': '36px', marginBottom: '6px' } }, icon(h, 'search', 14), h('input', { value: state.q, placeholder: t('TreeSelect', 'search'), onInput: e => set({ q: e.target.value }) })), ...(p.nodes || []).map(n => node(n, 0))) : null)); } },
|
|
16
5
|
ContextMenu: { state: { x: 0, y: 0, open: false }, render: ({ h, props: p, state, set, children }) => {
|
|
17
6
|
const close = () => set({ open: false });
|
|
18
7
|
return h('div', { class: 'zg-ctx', onContextmenu: e => { e.preventDefault(); set({ open: true, x: e.clientX, y: e.clientY }); } }, ...children,
|
|
19
8
|
state.open ? h('div', { class: 'zg-ctx__scrim', onClick: close, onContextmenu: e => { e.preventDefault(); close(); } }, h('div', { class: 'zg-menu', role: 'menu', 'data-zg-dd': '', ref: ddRef(close, { self: true, focus: true }), onKeydown: listKeys('[role=menuitem]', { wrap: true, onTab: close }), style: { position: 'fixed', left: Math.min(state.x, innerWidth - 240) + 'px', top: Math.min(state.y, innerHeight - (p.items || []).length * 42 - 20) + 'px', insetInlineStart: 'auto' }, onClick: e => e.stopPropagation() }, ...(p.items || []).map((it, i, all) => it.separator ? h('div', { key: i, class: 'zg-menu__sep', role: 'separator' }) : h('button', { key: i, type: 'button', role: 'menuitem', tabIndex: tabIdx(all.map(x => ({ disabled: x.separator || x.disabled })), () => false)(i), class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick: () => { close(); 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); } },
|
|
20
|
-
Sortable: { state: { drag: null, over: null }, render: ({ h, props: p, state, set }) => { const items = p.items || [];
|
|
21
|
-
const drop = (to, f) => { const from = f === undefined ? state.drag : f; set({ drag: null, over: null }); if (from == null || from === to) return; const a = items.slice(); const [m] = a.splice(from, 1); a.splice(to, 0, m); p.onChange && p.onChange(a); };
|
|
22
|
-
return h('div', { class: 'zg-sort', role: 'list' }, ...items.map((it, i) => h('div', { key: it.id ?? i, role: 'listitem', class: cx('zg-sort__i', state.over === i && state.drag !== i && 'is-over', state.drag === i && 'is-drag'), draggable: 'true', onDragstart: e => { e.dataTransfer.effectAllowed = 'move'; try { e.dataTransfer.setData('text/plain', String(i)); } catch (x) {} set({ drag: i }); }, onDragover: e => { e.preventDefault(); if (state.over !== i) set({ over: i }); }, onDrop: e => { e.preventDefault(); drop(i); }, onDragend: () => set({ drag: null, over: null }) },
|
|
23
|
-
h('span', { class: 'zg-sort__h', 'aria-hidden': 'true' }, icon(h, 'grip-vertical', 16)), h('div', { style: { flex: 1, minWidth: 0 } }, p.render ? p.render(it, i) : h('b', null, it.label)),
|
|
24
|
-
h('span', { style: { display: 'flex', gap: '2px' } }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Sortable', 'moveUp'), disabled: i === 0, onClick: () => drop(i - 1, i) }, icon(h, 'chevron-up', 14)), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Sortable', 'moveDown'), disabled: i === items.length - 1, onClick: () => drop(i + 1, i) }, icon(h, 'chevron-down', 14)))))); } },
|
|
25
|
-
Map: { state: { lon: null, lat: null, z: null, dk: null, w: null }, render: ({ h, props: p, state, set }) => {
|
|
26
|
-
const W = state.w || p.width || 640, H = p.height || 360, z = state.z ?? p.zoom ?? 14, lat0 = state.lat ?? p.lat ?? 21.4225, lon0 = state.lon ?? p.lon ?? 39.8262, n = 2 ** z;
|
|
27
|
-
const mpx = (lat, lon, nn) => [(lon + 180) / 360 * nn * 256, (1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * nn * 256], px = (lat, lon) => mpx(lat, lon, n);
|
|
28
|
-
const [cx0, cy0] = mpx(lat0, lon0, n), x0 = cx0 - W / 2, y0 = cy0 - H / 2, dark = p.theme ? p.theme === 'dark' : state.dk === true;
|
|
29
|
-
const tiles = []; for (let tx = Math.floor(x0 / 256); tx <= Math.floor((x0 + W) / 256); tx++) for (let ty = Math.floor(y0 / 256); ty <= Math.floor((y0 + H) / 256); ty++) if (ty >= 0 && ty < n) tiles.push(h('img', { key: tx + '_' + ty, alt: '', draggable: 'false', src: `https://${'abc'[(tx + ty) & 1]}.basemaps.cartocdn.com/${p.style || (dark ? 'dark_all' : 'light_all')}/${z}/${((tx % n) + n) % n}/${ty}.png`, style: { position: 'absolute', left: (tx * 256 - x0) + 'px', top: (ty * 256 - y0) + 'px', width: '256px', height: '256px' } }));
|
|
30
|
-
const unpx = (x, y, nn) => [Math.atan(Math.sinh(Math.PI * (1 - 2 * y / (nn * 256)))) * 180 / Math.PI, x / (nn * 256) * 360 - 180];
|
|
31
|
-
const emit = (la, lo, zz) => { p.onMove && p.onMove({ lat: la, lon: lo, zoom: zz }); if (zz !== z) p.onZoom && p.onZoom(zz); };
|
|
32
|
-
const zoomAt = (dz, ox, oy) => { const nz = clamp(z + dz, 3, 18); if (nz === z) return; const s = 2 ** (nz - z), nn = 2 ** nz, [la, lo] = unpx((cx0 + ox) * s - ox, (cy0 + oy) * s - oy, nn); set({ z: nz, lat: la, lon: lo }); emit(la, lo, nz); };
|
|
33
|
-
const rel = (e, el) => { const q = el.getBoundingClientRect(); return [e.clientX - q.left - q.width / 2, e.clientY - q.top - q.height / 2]; };
|
|
34
|
-
const pan = e => { if (e.button > 0 || !e.isPrimary) return; const el = e.currentTarget, sx = e.clientX, sy = e.clientY, pid = e.pointerId; let cap = false;
|
|
35
|
-
const mv = ev => { if (ev.pointerId !== pid) return; const dx = ev.clientX - sx, dy = ev.clientY - sy; if (!cap) { if (Math.abs(dx) + Math.abs(dy) < 4) return; cap = true; try { el.setPointerCapture(pid); } catch (x) {} } const [la, lo] = unpx(cx0 - dx, cy0 - dy, n); set({ lat: la, lon: lo }); emit(la, lo, z); };
|
|
36
|
-
const up = ev => { if (ev.pointerId !== pid) return; el.removeEventListener('pointermove', mv); el.removeEventListener('pointerup', up); el.removeEventListener('pointercancel', up); };
|
|
37
|
-
el.addEventListener('pointermove', mv); el.addEventListener('pointerup', up); el.addEventListener('pointercancel', up); };
|
|
38
|
-
const mk = (p.markers || []).map((m, i) => { const [mx, my] = px(m.lat, m.lon); return h('button', { key: 'm' + i, type: 'button', class: cx('zg-pin', m.tone && 'zg-t-' + m.tone), title: m.label, style: { left: (mx - x0) + 'px', top: (my - y0) + 'px' }, onClick: () => p.onSelect && p.onSelect(m) }, icon(h, m.icon || 'map-pin', 16)); });
|
|
39
|
-
const stop = e => e.stopPropagation(), zb = (dz, k, d, ic) => h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm', 'aria-label': (p.labels && p.labels[k]) || d, onPointerdown: stop, onDblclick: stop, onClick: () => zoomAt(dz, 0, 0) }, icon(h, ic, 16));
|
|
40
|
-
return h('div', { class: 'zg-map', role: 'application', 'aria-label': p.label || t('Map', 'label'), style: { height: H + 'px' }, onPointerdown: pan, onDblclick: e => { const [ox, oy] = rel(e, e.currentTarget); zoomAt(1, ox, oy); },
|
|
41
|
-
ref: el => { if (!el) return; el.__zgw = e => { e.preventDefault(); const t = Date.now(); if (t - (el.__zgt || 0) < 180) return; el.__zgt = t; const [ox, oy] = rel(e, el); zoomAt(e.deltaY < 0 ? 1 : -1, ox, oy); };
|
|
42
|
-
if (!el.__zgi) { el.__zgi = 1; el.addEventListener('wheel', e => el.__zgw(e), { passive: false }); if (typeof ResizeObserver !== 'undefined') new ResizeObserver(() => { const w = Math.round(el.clientWidth); if (w && w !== el.__zgl) { el.__zgl = w; set({ w }); } }).observe(el); }
|
|
43
|
-
if (p.theme == null && state.dk == null) set({ dk: !!el.closest('[data-theme="dark"]') }); } }, ...tiles, ...mk,
|
|
44
|
-
h('div', { class: 'zg-map__z' }, zb(1, 'zoomIn', t('Map', 'zoomIn'), 'plus'), zb(-1, 'zoomOut', t('Map', 'zoomOut'), 'minus')),
|
|
45
|
-
h('span', { class: 'zg-map__c' }, '© OpenStreetMap · CARTO')); } },
|
|
46
|
-
ImageUpload: { state: { src: null }, render: ({ h, props: p, state, set }) => { const src = state.src || p.modelValue, round = p.shape === 'circle';
|
|
47
|
-
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('label', { class: 'zg-imgup', style: { aspectRatio: p.aspect || '1/1', maxWidth: (({ xs: 112, sm: 128, md: 160, lg: 192, xl: 224 })[p.size] || p.size || 160) + 'px', borderRadius: round ? '50%' : '24px', backgroundImage: src ? `url(${src})` : undefined } }, src ? null : h('span', { class: 'zg-imgup__e' }, icon(h, 'image-up', 24), h('span', { class: 'zg-hint' }, p.hint || t('ImageUpload', 'upload'))), h('span', { class: 'zg-imgup__o' }, icon(h, 'camera', 18)),
|
|
48
|
-
h('input', { type: 'file', accept: 'image/*', style: { display: 'none' }, onChange: e => { const f = e.target.files[0]; if (!f) return; const r = new FileReader(); r.onload = () => { set({ src: r.result }); p.onChange && p.onChange(r.result, f); }; r.readAsDataURL(f); } }))); } },
|
|
49
|
-
Screensaver: { state: { t: 0 }, render: ({ h, props: p, state, set }) => { if (!p.open) return p.idleMs > 0 ? h('span', { hidden: true, 'aria-hidden': 'true', ref: life({ idleMs: p.idleMs, onIdle: p.onIdle, mount: (el, get) => { let tm; const arm = () => { clearTimeout(tm); tm = setTimeout(() => { const o = get(); o.onIdle && o.onIdle(); }, get().idleMs); }, evs = ['pointermove', 'pointerdown', 'keydown', 'wheel', 'touchstart']; evs.forEach(e => document.addEventListener(e, arm, { passive: true })); arm(); return () => { clearTimeout(tm); evs.forEach(e => document.removeEventListener(e, arm)); }; } }) }) : null;
|
|
50
|
-
const d = new Date(state.t || 0), pad = n => String(n).padStart(2, '0'), ph = (d.getSeconds() % 40) / 40;
|
|
51
|
-
return h('div', { class: 'zg-saver', role: 'dialog', 'aria-modal': 'true', 'aria-label': t('Screensaver', 'label'), tabIndex: 0, ref: trap({ onClose: p.onClose, onKey: e => { if (e.key === 'Tab') return false; p.onClose && p.onClose(); return true; }, mount: el => { const id = setInterval(() => set({ t: Date.now() }), 1000); set({ t: Date.now() }); return () => clearInterval(id); } }), onClick: p.onClose }, h('span', { class: 'zg-saver__g', style: { left: (10 + ph * 60) + '%', top: (20 + Math.sin(ph * 6.28) * 18) + '%' } }),
|
|
52
|
-
h('div', { class: 'zg-saver__c' }, h('span', { class: 'zg-saver__m', style: { backgroundImage: 'url(assets/brand/ziyarago-mark.svg)' } }), h('b', { class: 'zg-num', style: { fontSize: 'clamp(64px,12vw,160px)', fontWeight: 200, letterSpacing: '-.04em', lineHeight: 1 } }, state.t ? pad(d.getHours()) + ':' + pad(d.getMinutes()) : '--:--'), h('span', { style: { color: 'var(--zg-text-2)', fontSize: '18px' } }, state.t ? new Intl.DateTimeFormat(p.locale || 'en-GB', { weekday: 'long', day: 'numeric', month: 'long' }).format(d) : '\u00a0'), p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null)); } },
|
|
53
|
-
Tour: { state: { i: 0, r: null }, render: ({ C, h, props: p, state, set }) => { if (!p.open) return null; const st = p.steps || [], i = clamp(p.step != null ? p.step : state.i, 0, Math.max(0, st.length - 1)), s = st[i] || {};
|
|
54
|
-
const measure = () => { if (state.r || state.busy || !s.target) return; const el0 = document.querySelector(s.target); if (!el0) return; state.busy = true; setTimeout(() => { const el = document.querySelector(s.target) || el0, q = el.getBoundingClientRect(); if (q.top < 80 || q.bottom > innerHeight - 220) { let a = el.parentElement; while (a && !(/(auto|scroll)/.test(getComputedStyle(a).overflowY) && a.scrollHeight > a.clientHeight + 4)) a = a.parentElement; const dy = q.top - innerHeight / 3; if (a) a.scrollTop += dy; else window.scrollTo(0, window.scrollY + dy); } setTimeout(() => { state.busy = false; set({ r: (document.querySelector(s.target) || el).getBoundingClientRect() }); }, 160); }, 0); };
|
|
55
|
-
const r = state.r, pad = 8, below = r ? r.bottom + 12 + 190 < innerHeight : true;
|
|
56
|
-
const go = d => { const n = clamp(i + d, 0, st.length - 1); if (n === i) return; set({ i: n, r: null }); p.onStep && p.onStep(n); }, done = () => { set({ i: 0, r: null }); p.onClose && p.onClose(); };
|
|
57
|
-
return h('div', { class: 'zg-tourov', role: 'dialog', 'aria-label': t('Tour', 'label'), ref: el => { if (el) measure(); trap({ focus: false, onClose: done, mount: () => () => set({ i: 0, r: null }), onKey: e => { const rtl = document.documentElement.dir === 'rtl', k = e.key === 'ArrowRight' ? (rtl ? -1 : 1) : e.key === 'ArrowLeft' ? (rtl ? 1 : -1) : 0; if (k) { e.preventDefault(); go(k); return true; } } })(el); } }, r ? h('span', { class: 'zg-tourov__cut', style: { left: (r.left - pad) + 'px', top: (r.top - pad) + 'px', width: (r.width + pad * 2) + 'px', height: (r.height + pad * 2) + 'px' } }) : h('span', { class: 'zg-tourov__full' }),
|
|
58
|
-
h('div', { style: { position: 'fixed', zIndex: 2, left: r ? clamp(r.left, 12, innerWidth - 392) + 'px' : '50%', top: r ? (below ? r.bottom + 12 : Math.max(12, r.top - 12 - 190)) + 'px' : '40%', transform: r ? 'none' : 'translateX(-50%)' } }, h(C.TourPopover, { title: s.title, text: s.text, step: (i + 1) + ' / ' + st.length, last: i === st.length - 1, onBack: i ? () => go(-1) : null, onNext: () => i === st.length - 1 ? done() : go(1), onClose: done }))); } }
|
|
59
9
|
};
|
package/src/core/components7.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// @ziyarago/ui · components, part 7 — gluestack parity: typography, layout primitives, Link, Pressable, Icon, Portal, sheets, ImageViewer, ChatAi, LiquidGlass.
|
|
2
|
-
import { t } from '../i18n/index.js';
|
|
3
2
|
import { icon, cx } from './components.js';
|
|
4
3
|
import { trap } from './dialog.js';
|
|
5
4
|
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -7,19 +6,6 @@ const sp = v => (typeof v === 'number' ? v + 'px' : v);
|
|
|
7
6
|
const stack = (dir) => ({ render: ({ h, props: p, children }) => h(p.as || 'div', { class: 'zg-stack', style: { flexDirection: dir, gap: sp(p.gap ?? 12), alignItems: p.align, justifyContent: p.justify, flexWrap: p.wrap ? 'wrap' : undefined, ...(p.style || {}) } }, ...children) });
|
|
8
7
|
|
|
9
8
|
export const more7 = {
|
|
10
|
-
QiblaCompass: { render: ({ h, props: p }) => {
|
|
11
|
-
const px = { xs: 120, sm: 160, md: 200, lg: 250, xl: 300 }[p.size] || (Number(p.size) > 0 ? Number(p.size) : 200), norm = v => ((Number(v) || 0) % 360 + 360) % 360, b = norm(p.bearing), hd = norm(p.heading), deg = Math.round(b), ticks = [];
|
|
12
|
-
for (let i = 0; i < 72; i++) { const major = i % 9 === 0, a = i * 5 * Math.PI / 180, r1 = 92, r2 = major ? 80 : i % 3 ? 87 : 84; ticks.push(h('line', { key: i, class: major ? 'zg-qb__tk zg-qb__tk--m' : 'zg-qb__tk', x1: +(100 + Math.sin(a) * r1).toFixed(2), y1: +(100 - Math.cos(a) * r1).toFixed(2), x2: +(100 + Math.sin(a) * r2).toFixed(2), y2: +(100 - Math.cos(a) * r2).toFixed(2) })); }
|
|
13
|
-
return h('div', { class: 'zg-qb zg-dm', role: 'img', 'aria-label': t('QiblaCompass', 'label', { deg }), style: { width: px + 'px' } },
|
|
14
|
-
h('svg', { class: 'zg-qb__svg', viewBox: '0 0 200 200', 'aria-hidden': 'true', focusable: 'false' },
|
|
15
|
-
h('circle', { class: 'zg-qb__ring', cx: 100, cy: 100, r: 98 }),
|
|
16
|
-
h('g', { class: 'zg-qb__dial', transform: `rotate(${-hd} 100 100)` }, ...ticks, h('path', { class: 'zg-qb__n', d: 'M100 6 L106 20 L94 20 Z' })),
|
|
17
|
-
h('g', { class: 'zg-qb__ptr', transform: `rotate(${b - hd} 100 100)` },
|
|
18
|
-
h('line', { class: 'zg-qb__ln', x1: 100, y1: 100, x2: 100, y2: 46 }),
|
|
19
|
-
h('rect', { class: 'zg-qb__kb', x: 88, y: 24, width: 24, height: 24, rx: 5 }),
|
|
20
|
-
h('rect', { class: 'zg-qb__kg', x: 88, y: 31, width: 24, height: 4 })),
|
|
21
|
-
h('circle', { class: 'zg-qb__hub', cx: 100, cy: 100, r: 5 })),
|
|
22
|
-
h('div', { class: 'zg-qb__txt' }, h('b', { class: 'zg-num' }, deg + '°'), h('span', null, t('QiblaCompass', 'qibla')), p.distance ? h('small', null, p.distance) : null)); } },
|
|
23
9
|
Heading: { render: ({ h, props: p, children }) => h('h' + clamp(p.level || 2, 1, 6), { class: cx('zg-heading', 'zg-heading--' + (p.size || 'lg'), p.brand && 'zg-heading--brand') }, ...children) },
|
|
24
10
|
Text: { render: ({ h, props: p, children }) => h(p.as || 'p', { class: cx('zg-text', 'zg-text--' + (p.size || 'md'), p.tone && 'zg-text--' + p.tone, p.bold && 'zg-text--bold', p.truncate && 'zg-text--truncate') }, ...children) },
|
|
25
11
|
Box: { render: ({ h, props: p, children }) => h(p.as || 'div', { class: cx('zg-box', p.glass && 'zg-glass'), style: { padding: sp(p.p), ...(p.style || {}) } }, ...children) },
|
|
@@ -31,26 +17,6 @@ export const more7 = {
|
|
|
31
17
|
Pressable: { render: ({ h, props: p, children }) => h(p.as || 'button', { type: (p.as || 'button') === 'button' ? 'button' : undefined, class: 'zg-press', disabled: p.disabled, 'aria-label': p.label, onClick: p.onClick }, ...children) },
|
|
32
18
|
Icon: { render: ({ h, props: p }) => icon(h, p.name || 'circle', { xs: 14, sm: 16, md: 20, lg: 24, xl: 32 }[p.size] || p.size || 20) },
|
|
33
19
|
Portal: { render: ({ props: p, children, portal }) => (portal ? portal(children, p.to) : null) },
|
|
34
|
-
LiquidGlass: { render: ({ h, props: p, children }) => h('div', { class: 'zg-liquid', style: { '--zg-liquid-r': sp(p.radius ?? 24), '--zg-liquid-b': sp(p.blur ?? 18) } }, ...children) },
|
|
35
20
|
BottomSheet: { render: ({ h, props: p, children, slots }) => !p.open ? null : h('div', { class: 'zg-modal-scrim zg-sheet-scrim', onClick: p.onClose }, h('div', { class: 'zg-sheet', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }), style: { maxHeight: sp(p.maxHeight || '85vh') }, onClick: e => e.stopPropagation() },
|
|
36
21
|
h('span', { class: 'zg-sheet__grab', 'aria-hidden': 'true' }), p.title ? h('h3', { class: 'zg-sheet__t' }, p.title) : null, h('div', { class: 'zg-sheet__b' }, ...children), (slots.footer || p.footer) ? h('div', { class: 'zg-sheet__f' }, slots.footer || p.footer) : null)) },
|
|
37
|
-
Actionsheet: { render: ({ C, h, props: p }) => h(C.BottomSheet, { open: p.open, size: p.size, onClose: p.onClose, title: p.title },
|
|
38
|
-
h('div', { class: 'zg-as' }, ...(p.items || []).map((it, i) => h('button', { key: i, type: 'button', class: cx('zg-as__i', it.tone && 'zg-as__i--' + it.tone), disabled: it.disabled, onClick: () => { it.onClick && it.onClick(); p.onSelect && p.onSelect(it); p.onClose && p.onClose(); } }, it.icon ? icon(h, it.icon, 18) : null, h('span', null, it.label)))),
|
|
39
|
-
h('button', { type: 'button', class: 'zg-as__i zg-as__i--cancel', onClick: p.onClose }, p.cancelLabel || t('Actionsheet', 'cancel'))) },
|
|
40
|
-
ImageViewer: { state: { i: null }, render: ({ h, props: p, state, set }) => {
|
|
41
|
-
if (!p.open) return null;
|
|
42
|
-
const im = p.images || [], i = clamp(state.i ?? p.index ?? 0, 0, Math.max(0, im.length - 1)), go = d => set({ i: (i + d + im.length) % im.length });
|
|
43
|
-
return h('div', { class: 'zg-iv', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.alt || t('ImageViewer', 'label'), onClick: p.onClose },
|
|
44
|
-
h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-iv__x', 'aria-label': t('ImageViewer', 'close'), onClick: p.onClose }, icon(h, 'x', 20)),
|
|
45
|
-
im.length > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-iv__p', 'aria-label': t('ImageViewer', 'previous'), onClick: e => { e.stopPropagation(); go(-1); } }, icon(h, 'chevron-left', 22)) : null,
|
|
46
|
-
h('img', { class: 'zg-iv__img', src: im[i], alt: p.alt || '', onClick: e => e.stopPropagation() }),
|
|
47
|
-
im.length > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-iv__n', 'aria-label': t('ImageViewer', 'next'), onClick: e => { e.stopPropagation(); go(1); } }, icon(h, 'chevron-right', 22)) : null,
|
|
48
|
-
h('span', { class: 'zg-badge zg-iv__c' }, `${i + 1} / ${im.length}`)); } },
|
|
49
|
-
ChatAi: { state: { q: '' }, render: ({ h, props: p, state, set }) => {
|
|
50
|
-
const send = () => { const t = state.q.trim(); if (!t || p.loading) return; set({ q: '' }); p.onSend && p.onSend(t); };
|
|
51
|
-
return h('section', { class: 'zg-chat zg-glass', 'aria-label': p.title || t('ChatAi', 'assistant') },
|
|
52
|
-
p.title ? h('div', { class: 'zg-chat__h' }, h('b', null, p.title), p.subtitle ? h('span', { class: 'zg-hint' }, p.subtitle) : null) : null,
|
|
53
|
-
h('div', { class: 'zg-chat__m', role: 'log', 'aria-live': 'polite' }, ...(p.messages || []).map((m, i) => h('div', { key: i, class: cx('zg-chat__b', 'zg-chat__b--' + (m.role === 'user' ? 'user' : 'ai')) }, m.text)), p.loading ? h('div', { class: 'zg-chat__b zg-chat__b--ai zg-chat__dots', 'aria-label': t('ChatAi', 'typing') }, h('i'), h('i'), h('i')) : null),
|
|
54
|
-
h('div', { class: 'zg-chat__f' }, h('input', { class: 'zg-chat__in', value: state.q, placeholder: p.placeholder || t('ChatAi', 'placeholder'), 'aria-label': t('ChatAi', 'message'), onInput: e => set({ q: e.target.value }), onKeydown: e => { if (e.key === 'Enter') { e.preventDefault(); send(); } } }),
|
|
55
|
-
h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--brand', 'aria-label': t('ChatAi', 'send'), disabled: p.loading || !state.q.trim(), onClick: send }, icon(h, 'send', 18)))); } },
|
|
56
22
|
};
|
package/src/core/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { components as base, fmt, setIconBase } from './components.js';
|
|
1
|
+
import { components as base, fmt, setIconBase, registerIcons } from './components.js';
|
|
2
2
|
import { more } from './components2.js';
|
|
3
3
|
import { more3 } from './components3.js';
|
|
4
4
|
import { more4, rules } from './components4.js';
|
|
@@ -6,7 +6,7 @@ import { pages } from './pages.js';
|
|
|
6
6
|
import { more6 } from './components6.js';
|
|
7
7
|
import { more7 } from './components7.js';
|
|
8
8
|
export const components = { ...base, ...more, ...more3, ...more4, ...more6, ...more7, ...pages };
|
|
9
|
-
export { fmt, setIconBase, rules };
|
|
9
|
+
export { fmt, setIconBase, registerIcons, rules };
|
|
10
10
|
/** Extension API: adds definitions to the shared registry (used by add-on packages such as @ziyarago/ui-pro). Registering the very same definition twice is a no-op (Vue and React adapters can both load one plugin). */
|
|
11
11
|
export function registerComponents(defs, { override = false } = {}) {
|
|
12
12
|
for (const name of Object.keys(defs)) if (!override && name in components && components[name] !== defs[name]) throw new Error(`@ziyarago/ui: component "${name}" is already registered (pass { override: true } to replace it)`);
|
package/src/core/pages.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// @ziyarago/ui · page templates — real slot contracts. Put inside <UiAppShell>, fill only the slots you need.
|
|
2
2
|
// Vue: <UiPageDashboard title="Dashboard"><template #kpis>…</template></UiPageDashboard>
|
|
3
3
|
// React: <UiPageDashboard title="Dashboard" kpis={<…/>} />
|
|
4
|
-
import { cx } from './components.js';
|
|
5
4
|
|
|
6
5
|
const region = (h, id, kids, stick) => (kids == null || (Array.isArray(kids) && !kids.length)) ? null : h('div', { key: id, class: stick ? 'zg-pg__r zg-pg__stick' : 'zg-pg__r', 'data-slot': id }, kids);
|
|
7
6
|
|
|
@@ -15,7 +14,7 @@ const draw = (h, node, ctx) => {
|
|
|
15
14
|
return null;
|
|
16
15
|
};
|
|
17
16
|
|
|
18
|
-
const page = (name, slots, tree) => [name, {
|
|
17
|
+
export const page = (name, slots, tree) => [name, {
|
|
19
18
|
slots,
|
|
20
19
|
render: ({ C, h, props: p, slots: s }) => {
|
|
21
20
|
const header = () => s.header ? region(h, 'header', s.header) : (p.title ? h('div', { class: 'zg-pg__r', 'data-slot': 'header' }, h('div', { class: 'zg-page-head' }, h('div', { style: { flex: 1, minWidth: 0 } }, p.crumbs ? h(C.Breadcrumbs, { items: p.crumbs }) : null, h('h1', null, p.title), p.subtitle ? h('p', null, p.subtitle) : null), s.actions || null)) : (s.actions ? h('div', { class: 'zg-pg__r', 'data-slot': 'header' }, h('div', { class: 'zg-page-head', style: { justifyContent: 'flex-end' } }, s.actions)) : null));
|
|
@@ -25,47 +24,14 @@ const page = (name, slots, tree) => [name, {
|
|
|
25
24
|
|
|
26
25
|
const T = Object.fromEntries([
|
|
27
26
|
page('PageDashboard', ['header', 'hero', 'kpis', 'occupancy', 'funnel', 'top', 'activity'], ['col', 'header', ['auto', 340, 'hero', 'kpis'], ['auto', 360, 'occupancy', 'funnel'], ['auto', 360, 'top', 'activity']]),
|
|
28
|
-
page('PagePatterns', ['header', 'hero', 'strip', 'statlines', 'chartcard', 'tiles', 'listrows', 'countries', 'versus', 'segbar', 'activity', 'actioncard'], ['col', 'header', ['auto', 340, 'hero', 'strip'], 'statlines', ['auto', 360, 'chartcard', 'tiles'], ['auto', 320, 'listrows', 'countries', 'versus'], 'segbar', ['auto', 360, 'activity', 'actioncard']]),
|
|
29
|
-
page('PageCrm', ['header', 'summary', 'filters', 'board', 'calls'], ['col', 'header', 'summary', 'filters', 'board', 'calls']),
|
|
30
|
-
page('PageBookings', ['header', 'stats', 'toolbar', 'bulk', 'table', 'pager'], ['col', 'header', 'stats', 'toolbar', 'bulk', 'table', 'pager']),
|
|
31
|
-
page('PageCatalog', ['header', 'filters', 'toolbar', 'grid', 'pager'], ['col', 'header', ['wrap', ['filters'], ['toolbar', 'grid', 'pager'], 260]]),
|
|
32
|
-
page('PageDetail', ['header', 'gallery', 'summary', 'tabs', 'history', 'booking'], ['col', 'header', ['two', ['gallery', 'summary', 'tabs', 'history'], ['booking']]]),
|
|
33
27
|
page('PageSettings', ['header', 'subnav', 'profile', 'notifications', 'integrations', 'danger'], ['col', 'header', ['wrap', ['subnav'], ['profile', 'notifications', 'integrations', 'danger'], 220]]),
|
|
34
28
|
page('PageUserList', ['header', 'stats', 'filters', 'toolbar', 'table', 'drawer'], ['col', 'header', 'stats', ['two', ['filters', 'toolbar', 'table'], ['drawer']]]),
|
|
35
|
-
page('PageProfile', ['header', 'profile', 'tabs', 'about', 'activity', 'plan'], ['col', 'header', 'profile', 'tabs', ['wrap', ['about'], ['activity', 'plan'], 320]]),
|
|
36
|
-
page('PageForm', ['header', 'main', 'media', 'side', 'actions'], ['col', 'header', ['two', ['main', 'media'], ['side']]]),
|
|
37
|
-
page('PageOrder', ['header', 'status', 'items', 'shipping', 'side'], ['col', 'header', 'status', ['two', ['items', 'shipping'], ['side']]]),
|
|
38
|
-
page('PageAccount', ['header', 'tabs', 'panel'], ['col', 'header', 'tabs', 'panel']),
|
|
39
29
|
page('PagePricing', ['header', 'toggle', 'plans', 'faq'], ['col', 'header', 'toggle', 'plans', 'faq']),
|
|
40
|
-
page('PageFaq', ['header', 'search', 'cats', 'answers', 'contact'], ['col', 'header', 'search', ['wrap', ['cats'], ['answers'], 200], 'contact']),
|
|
41
|
-
page('PageLanding', ['hero', 'logos', 'features', 'proof', 'pricing', 'cta', 'footer'], ['col', 'hero', 'logos', 'features', 'proof', 'pricing', 'cta', 'footer']),
|
|
42
|
-
page('PageOnboarding', ['header', 'progress', 'steps', 'checklist', 'tips'], ['col', 'header', 'progress', ['two', ['steps', 'checklist'], ['tips']]]),
|
|
43
|
-
page('PageCheckout', ['header', 'steps', 'items', 'address', 'payment', 'summary'], ['col', 'header', 'steps', ['two', ['items', 'address', 'payment'], ['summary']]]),
|
|
44
|
-
page('PageInbox', ['header', 'filters', 'list', 'thread', 'composer', 'customer'], ['col', 'header', ['wrap', ['filters', 'list'], [['two', ['thread', 'composer'], ['customer']]], 320]]),
|
|
45
|
-
page('PageNotifications', ['header', 'filters', 'feed', 'empty'], ['col', 'header', 'filters', 'feed', 'empty']),
|
|
46
|
-
page('PageAnalytics', ['header', 'period', 'kpis', 'trend', 'breakdown', 'table'], ['col', 'header', 'period', 'kpis', ['auto', 420, 'trend', 'breakdown'], 'table']),
|
|
47
|
-
page('PageCalendar', ['header', 'toolbar', 'grid', 'mini', 'details'], ['col', 'header', 'toolbar', ['two', ['grid'], ['mini', 'details']]]),
|
|
48
|
-
page('PageKanban', ['header', 'filters', 'board', 'detail'], ['col', 'header', 'filters', ['two', ['board'], ['detail']]]),
|
|
49
|
-
page('PageBilling', ['header', 'plan', 'usage', 'invoices', 'methods', 'upgrade'], ['col', 'header', ['two', ['plan', 'usage', 'invoices'], ['methods', 'upgrade']]]),
|
|
50
|
-
page('PageTeam', ['header', 'stats', 'members', 'roles', 'invite'], ['col', 'header', 'stats', ['two', ['members', 'roles'], ['invite']]]),
|
|
51
|
-
page('PageIntegrations', ['header', 'filters', 'connections', 'keys', 'webhooks'], ['col', 'header', 'filters', 'connections', ['auto', 420, 'keys', 'webhooks']]),
|
|
52
|
-
page('PageSearch', ['header', 'search', 'filters', 'results', 'empty'], ['col', 'header', 'search', ['wrap', ['filters'], ['results', 'empty'], 260]]),
|
|
53
|
-
page('PageArticle', ['header', 'cover', 'body', 'toc', 'share', 'related'], ['col', 'header', 'cover', ['two', ['body', 'share', 'related'], ['toc'], 'sticky:toc']]),
|
|
54
|
-
page('PageChangelog', ['header', 'filters', 'timeline', 'subscribe'], ['col', 'header', ['two', ['filters', 'timeline'], ['subscribe'], 'sticky:subscribe']]),
|
|
55
|
-
page('PageHelp', ['header', 'search', 'categories', 'popular', 'contact'], ['col', 'header', 'search', 'categories', ['auto', 360, 'popular', 'contact']]),
|
|
56
|
-
page('PageStates', ['header', 'empty', 'noresults', 'forbidden', 'offline', 'error', 'loading', 'success'], ['col', 'header', ['auto', 300, 'empty', 'noresults', 'forbidden'], ['auto', 300, 'offline', 'error', 'loading'], 'success']),
|
|
57
|
-
page('PageLegal', ['header', 'meta', 'toc', 'document', 'contact'], ['col', 'header', 'meta', ['wrap', ['toc'], ['document', 'contact'], 260, 'sticky:toc']]),
|
|
58
|
-
page('PageLocations', ['header', 'filters', 'list', 'map'], ['col', 'header', 'filters', ['wrap', ['list'], ['map'], 380, 'sticky:map']]),
|
|
59
|
-
page('PageBookingFlow', ['header', 'steps', 'content', 'nav', 'summary'], ['col', 'header', 'steps', ['two', ['content', 'nav'], ['summary'], 'sticky:summary']]),
|
|
60
|
-
page('PageItinerary', ['header', 'days', 'timeline', 'transport', 'hotel', 'summary'], ['col', 'header', 'days', ['two', ['timeline', 'transport', 'hotel'], ['summary'], 'sticky:summary']]),
|
|
61
|
-
page('PagePrayer', ['header', 'location', 'today', 'month', 'qibla'], ['col', 'header', 'location', ['two', ['today', 'month'], ['qibla'], 'sticky:qibla']]),
|
|
62
|
-
page('PageGroupManager', ['header', 'trip', 'toolbar', 'bulk', 'pilgrims', 'rooms', 'payments'], ['col', 'header', 'trip', ['two', ['toolbar', 'bulk', 'pilgrims', 'rooms'], ['payments'], 'sticky:payments']]),
|
|
63
|
-
page('PageTourPublic', ['header', 'gallery', 'highlights', 'itinerary', 'included', 'reviews', 'booking'], ['col', 'header', 'gallery', ['two', ['highlights', 'itinerary', 'included', 'reviews'], ['booking'], 'sticky:booking']])
|
|
64
30
|
]);
|
|
65
31
|
|
|
66
32
|
T.PageAuth = { slots: ['background', 'logo', 'aside', 'form', 'footer'], render: ({ C, h, props: p, slots: s }) => {
|
|
67
33
|
const body = [s.form || null, s.footer ? h('div', { class: 'zg-pg__r', 'data-slot': 'footer' }, s.footer) : null];
|
|
68
|
-
if (['photo', 'light', 'tint'].includes(p.layout)) return h(C.AuthShowcase, { variant: p.layout, title: p.title, subtitle: p.subtitle, logo: s.logo, footer: s.footer, image: p.image, headline: p.headline, caption: p.caption, quote: p.quote, chips: p.chips, people: p.people, proof: p.proof, proofHint: p.proofHint }, s.form || null);
|
|
34
|
+
if (C.AuthShowcase && ['photo', 'light', 'tint'].includes(p.layout)) return h(C.AuthShowcase, { variant: p.layout, title: p.title, subtitle: p.subtitle, logo: s.logo, footer: s.footer, image: p.image, headline: p.headline, caption: p.caption, quote: p.quote, chips: p.chips, people: p.people, proof: p.proof, proofHint: p.proofHint }, s.form || null);
|
|
69
35
|
return p.layout === 'split'
|
|
70
36
|
? h(C.AuthSplit, { title: p.title, subtitle: p.subtitle, logo: s.logo, aside: s.aside }, ...body)
|
|
71
37
|
: h('div', { class: 'zg-pgauth' }, s.background ? h('div', { class: 'zg-pg__r zg-pgauth__bg', 'data-slot': 'background' }, s.background) : null, h('div', { class: 'zg-pgauth__c' }, h(C.AuthLayout, { title: p.title, subtitle: p.subtitle, logo: s.logo }, ...body)));
|
|
@@ -1,141 +1,69 @@
|
|
|
1
1
|
// generated by scripts/i18n-index.mjs: do not edit
|
|
2
2
|
import { defineMessages } from '../index.js';
|
|
3
|
-
import ActionCard from './ActionCard.js';
|
|
4
|
-
import Actionsheet from './Actionsheet.js';
|
|
5
3
|
import Alert from './Alert.js';
|
|
6
4
|
import Breadcrumbs from './Breadcrumbs.js';
|
|
7
5
|
import Calendar from './Calendar.js';
|
|
8
|
-
import CallCard from './CallCard.js';
|
|
9
|
-
import CallRow from './CallRow.js';
|
|
10
|
-
import Carousel from './Carousel.js';
|
|
11
|
-
import ChartCard from './ChartCard.js';
|
|
12
|
-
import ChatAi from './ChatAi.js';
|
|
13
6
|
import Chip from './Chip.js';
|
|
14
|
-
import ChipInput from './ChipInput.js';
|
|
15
7
|
import CodeInput from './CodeInput.js';
|
|
16
8
|
import Combobox from './Combobox.js';
|
|
17
9
|
import CommandPalette from './CommandPalette.js';
|
|
18
10
|
import ConfirmDialog from './ConfirmDialog.js';
|
|
19
|
-
import ContactPanel from './ContactPanel.js';
|
|
20
11
|
import CopyButton from './CopyButton.js';
|
|
21
|
-
import DataGroup from './DataGroup.js';
|
|
22
12
|
import DataState from './DataState.js';
|
|
23
13
|
import DataTable from './DataTable.js';
|
|
24
14
|
import DatePicker from './DatePicker.js';
|
|
25
|
-
import DealCard from './DealCard.js';
|
|
26
15
|
import Drawer from './Drawer.js';
|
|
27
16
|
import ErrorPage from './ErrorPage.js';
|
|
28
17
|
import FileUpload from './FileUpload.js';
|
|
29
|
-
import FullscreenDialog from './FullscreenDialog.js';
|
|
30
|
-
import Gallery from './Gallery.js';
|
|
31
|
-
import GuestCard from './GuestCard.js';
|
|
32
|
-
import ImageUpload from './ImageUpload.js';
|
|
33
|
-
import ImageViewer from './ImageViewer.js';
|
|
34
|
-
import IncomingCall from './IncomingCall.js';
|
|
35
|
-
import InlineEdit from './InlineEdit.js';
|
|
36
|
-
import IntegrationCard from './IntegrationCard.js';
|
|
37
18
|
import LoginForm from './LoginForm.js';
|
|
38
|
-
import Map from './Map.js';
|
|
39
19
|
import Modal from './Modal.js';
|
|
40
20
|
import MultiSelect from './MultiSelect.js';
|
|
41
|
-
import NotificationBell from './NotificationBell.js';
|
|
42
21
|
import NumberInput from './NumberInput.js';
|
|
43
22
|
import Pagination from './Pagination.js';
|
|
44
23
|
import PasswordInput from './PasswordInput.js';
|
|
45
24
|
import Popconfirm from './Popconfirm.js';
|
|
46
|
-
import PricingCard from './PricingCard.js';
|
|
47
25
|
import Progress from './Progress.js';
|
|
48
|
-
import QiblaCompass from './QiblaCompass.js';
|
|
49
|
-
import RamadanTracker from './RamadanTracker.js';
|
|
50
|
-
import RangeCalendar from './RangeCalendar.js';
|
|
51
|
-
import RangeSlider from './RangeSlider.js';
|
|
52
26
|
import Rating from './Rating.js';
|
|
53
|
-
import RichEditor from './RichEditor.js';
|
|
54
|
-
import Screensaver from './Screensaver.js';
|
|
55
27
|
import SearchInput from './SearchInput.js';
|
|
56
28
|
import SideNav from './SideNav.js';
|
|
57
29
|
import Snackbar from './Snackbar.js';
|
|
58
30
|
import SocialAuth from './SocialAuth.js';
|
|
59
|
-
import Sortable from './Sortable.js';
|
|
60
31
|
import Spinner from './Spinner.js';
|
|
61
32
|
import StatusPage from './StatusPage.js';
|
|
62
|
-
import StepDialog from './StepDialog.js';
|
|
63
|
-
import StepForm from './StepForm.js';
|
|
64
|
-
import TabTiles from './TabTiles.js';
|
|
65
33
|
import Tag from './Tag.js';
|
|
66
34
|
import TopNav from './TopNav.js';
|
|
67
|
-
import Tour from './Tour.js';
|
|
68
|
-
import TourCard from './TourCard.js';
|
|
69
|
-
import TourPopover from './TourPopover.js';
|
|
70
|
-
import TreeSelect from './TreeSelect.js';
|
|
71
35
|
import Validation from './Validation.js';
|
|
72
36
|
|
|
73
|
-
defineMessages('ActionCard', ActionCard);
|
|
74
|
-
defineMessages('Actionsheet', Actionsheet);
|
|
75
37
|
defineMessages('Alert', Alert);
|
|
76
38
|
defineMessages('Breadcrumbs', Breadcrumbs);
|
|
77
39
|
defineMessages('Calendar', Calendar);
|
|
78
|
-
defineMessages('CallCard', CallCard);
|
|
79
|
-
defineMessages('CallRow', CallRow);
|
|
80
|
-
defineMessages('Carousel', Carousel);
|
|
81
|
-
defineMessages('ChartCard', ChartCard);
|
|
82
|
-
defineMessages('ChatAi', ChatAi);
|
|
83
40
|
defineMessages('Chip', Chip);
|
|
84
|
-
defineMessages('ChipInput', ChipInput);
|
|
85
41
|
defineMessages('CodeInput', CodeInput);
|
|
86
42
|
defineMessages('Combobox', Combobox);
|
|
87
43
|
defineMessages('CommandPalette', CommandPalette);
|
|
88
44
|
defineMessages('ConfirmDialog', ConfirmDialog);
|
|
89
|
-
defineMessages('ContactPanel', ContactPanel);
|
|
90
45
|
defineMessages('CopyButton', CopyButton);
|
|
91
|
-
defineMessages('DataGroup', DataGroup);
|
|
92
46
|
defineMessages('DataState', DataState);
|
|
93
47
|
defineMessages('DataTable', DataTable);
|
|
94
48
|
defineMessages('DatePicker', DatePicker);
|
|
95
|
-
defineMessages('DealCard', DealCard);
|
|
96
49
|
defineMessages('Drawer', Drawer);
|
|
97
50
|
defineMessages('ErrorPage', ErrorPage);
|
|
98
51
|
defineMessages('FileUpload', FileUpload);
|
|
99
|
-
defineMessages('FullscreenDialog', FullscreenDialog);
|
|
100
|
-
defineMessages('Gallery', Gallery);
|
|
101
|
-
defineMessages('GuestCard', GuestCard);
|
|
102
|
-
defineMessages('ImageUpload', ImageUpload);
|
|
103
|
-
defineMessages('ImageViewer', ImageViewer);
|
|
104
|
-
defineMessages('IncomingCall', IncomingCall);
|
|
105
|
-
defineMessages('InlineEdit', InlineEdit);
|
|
106
|
-
defineMessages('IntegrationCard', IntegrationCard);
|
|
107
52
|
defineMessages('LoginForm', LoginForm);
|
|
108
|
-
defineMessages('Map', Map);
|
|
109
53
|
defineMessages('Modal', Modal);
|
|
110
54
|
defineMessages('MultiSelect', MultiSelect);
|
|
111
|
-
defineMessages('NotificationBell', NotificationBell);
|
|
112
55
|
defineMessages('NumberInput', NumberInput);
|
|
113
56
|
defineMessages('Pagination', Pagination);
|
|
114
57
|
defineMessages('PasswordInput', PasswordInput);
|
|
115
58
|
defineMessages('Popconfirm', Popconfirm);
|
|
116
|
-
defineMessages('PricingCard', PricingCard);
|
|
117
59
|
defineMessages('Progress', Progress);
|
|
118
|
-
defineMessages('QiblaCompass', QiblaCompass);
|
|
119
|
-
defineMessages('RamadanTracker', RamadanTracker);
|
|
120
|
-
defineMessages('RangeCalendar', RangeCalendar);
|
|
121
|
-
defineMessages('RangeSlider', RangeSlider);
|
|
122
60
|
defineMessages('Rating', Rating);
|
|
123
|
-
defineMessages('RichEditor', RichEditor);
|
|
124
|
-
defineMessages('Screensaver', Screensaver);
|
|
125
61
|
defineMessages('SearchInput', SearchInput);
|
|
126
62
|
defineMessages('SideNav', SideNav);
|
|
127
63
|
defineMessages('Snackbar', Snackbar);
|
|
128
64
|
defineMessages('SocialAuth', SocialAuth);
|
|
129
|
-
defineMessages('Sortable', Sortable);
|
|
130
65
|
defineMessages('Spinner', Spinner);
|
|
131
66
|
defineMessages('StatusPage', StatusPage);
|
|
132
|
-
defineMessages('StepDialog', StepDialog);
|
|
133
|
-
defineMessages('StepForm', StepForm);
|
|
134
|
-
defineMessages('TabTiles', TabTiles);
|
|
135
67
|
defineMessages('Tag', Tag);
|
|
136
68
|
defineMessages('TopNav', TopNav);
|
|
137
|
-
defineMessages('Tour', Tour);
|
|
138
|
-
defineMessages('TourCard', TourCard);
|
|
139
|
-
defineMessages('TourPopover', TourPopover);
|
|
140
|
-
defineMessages('TreeSelect', TreeSelect);
|
|
141
69
|
defineMessages('Validation', Validation);
|