@birdycom/omniaui-vue 0.11.0 → 0.11.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/LICENSE +21 -21
- package/README.md +49 -49
- package/dist/omniaui-vue.js.map +1 -1
- package/package.json +69 -69
- package/src/blocks/OcBlockAboutTimeline.vue +44 -44
- package/src/blocks/OcBlockAppHeader.vue +36 -36
- package/src/blocks/OcBlockAppShell.vue +29 -29
- package/src/blocks/OcBlockAuth.vue +24 -24
- package/src/blocks/OcBlockAuthSplit.vue +47 -47
- package/src/blocks/OcBlockContactGrid.vue +53 -53
- package/src/blocks/OcBlockCta.vue +12 -12
- package/src/blocks/OcBlockDataTable.vue +27 -27
- package/src/blocks/OcBlockFaq.vue +49 -49
- package/src/blocks/OcBlockFeatures.vue +23 -23
- package/src/blocks/OcBlockFooter.vue +25 -25
- package/src/blocks/OcBlockFormPage.vue +24 -24
- package/src/blocks/OcBlockHero.vue +13 -13
- package/src/blocks/OcBlockHeroCode.vue +42 -42
- package/src/blocks/OcBlockHeroOrbit.vue +69 -69
- package/src/blocks/OcBlockHeroSplit.vue +15 -15
- package/src/blocks/OcBlockModuleHeader.vue +21 -21
- package/src/blocks/OcBlockPageHero.vue +13 -13
- package/src/blocks/OcBlockPortfolioGrid.vue +63 -63
- package/src/blocks/OcBlockPricing.vue +39 -39
- package/src/blocks/OcBlockProcess.vue +39 -39
- package/src/blocks/OcBlockProductGrid.vue +41 -41
- package/src/blocks/OcBlockProfileHeader.vue +50 -50
- package/src/blocks/OcBlockShowcase.vue +43 -43
- package/src/blocks/OcBlockSiteFooter.vue +54 -54
- package/src/blocks/OcBlockSiteHeader.vue +34 -34
- package/src/blocks/OcBlockSpotlight.vue +47 -47
- package/src/blocks/OcBlockStats.vue +17 -17
- package/src/blocks/OcBlockStatsCounter.vue +20 -20
- package/src/blocks/OcBlockTeamList.vue +53 -53
- package/src/blocks/OcBlockTwoFactor.vue +61 -61
- package/src/blocks/OcBlockWizardDialog.vue +67 -67
- package/src/components/OcAccordion.vue +12 -12
- package/src/components/OcAccordionAccent.vue +27 -27
- package/src/components/OcAccordionItem.vue +19 -19
- package/src/components/OcAffixInput.vue +40 -40
- package/src/components/OcAlert.vue +17 -17
- package/src/components/OcAlertDialog.vue +66 -66
- package/src/components/OcAlphaTabs.vue +54 -54
- package/src/components/OcAspectRatio.vue +21 -21
- package/src/components/OcAttachment.vue +59 -59
- package/src/components/OcAttachmentImage.vue +13 -13
- package/src/components/OcAttachmentList.vue +31 -31
- package/src/components/OcAttachmentThumb.vue +14 -14
- package/src/components/OcAvatar.vue +18 -18
- package/src/components/OcAvatarGroup.vue +7 -7
- package/src/components/OcBackLink.vue +8 -8
- package/src/components/OcBadge.vue +13 -13
- package/src/components/OcBarcodeInput.vue +51 -51
- package/src/components/OcBreadcrumb.vue +22 -22
- package/src/components/OcBrowserFrame.vue +33 -33
- package/src/components/OcBusyOverlay.vue +32 -32
- package/src/components/OcButton.vue +47 -47
- package/src/components/OcButtonGroup.vue +10 -10
- package/src/components/OcButtonGroupText.vue +7 -7
- package/src/components/OcCard.vue +20 -20
- package/src/components/OcChartLine.vue +80 -80
- package/src/components/OcChatComposer.vue +42 -42
- package/src/components/OcCheckbox.vue +26 -26
- package/src/components/OcChecklist.vue +29 -29
- package/src/components/OcCollapsible.vue +19 -19
- package/src/components/OcColorField.vue +15 -15
- package/src/components/OcColorSelect.vue +33 -33
- package/src/components/OcColumnMenu.vue +50 -50
- package/src/components/OcCommand.vue +119 -119
- package/src/components/OcConfirmDialog.vue +65 -65
- package/src/components/OcCookieConsent.vue +119 -119
- package/src/components/OcCopyField.vue +37 -37
- package/src/components/OcCounter.vue +15 -15
- package/src/components/OcDataTable.vue +144 -144
- package/src/components/OcDefinitionList.vue +35 -35
- package/src/components/OcDropdown.vue +23 -23
- package/src/components/OcDropdownItem.vue +9 -9
- package/src/components/OcDropdownLabel.vue +7 -7
- package/src/components/OcDropdownSeparator.vue +7 -7
- package/src/components/OcDropzone.vue +36 -36
- package/src/components/OcEmojiPicker.vue +45 -45
- package/src/components/OcEmptyState.vue +13 -13
- package/src/components/OcEntityCard.vue +47 -47
- package/src/components/OcFactList.vue +37 -37
- package/src/components/OcFilterCounts.vue +38 -38
- package/src/components/OcFlipCard.vue +36 -36
- package/src/components/OcFormField.vue +36 -36
- package/src/components/OcGreeting.vue +52 -52
- package/src/components/OcHeading.vue +19 -19
- package/src/components/OcHoverCard.vue +25 -25
- package/src/components/OcImageZoom.vue +39 -39
- package/src/components/OcInput.vue +9 -9
- package/src/components/OcInputGroup.vue +12 -12
- package/src/components/OcItem.vue +40 -40
- package/src/components/OcItemGroup.vue +7 -7
- package/src/components/OcItemSeparator.vue +7 -7
- package/src/components/OcKbd.vue +7 -7
- package/src/components/OcKbdGroup.vue +15 -15
- package/src/components/OcLabel.vue +10 -10
- package/src/components/OcLanguageSelect.vue +40 -40
- package/src/components/OcLauncherCard.vue +34 -34
- package/src/components/OcLightbox.vue +63 -63
- package/src/components/OcLinkArrow.vue +25 -25
- package/src/components/OcListToolbar.vue +44 -44
- package/src/components/OcMarquee.vue +36 -36
- package/src/components/OcMediaCard.vue +28 -28
- package/src/components/OcMediaRow.vue +28 -28
- package/src/components/OcMediaThumb.vue +38 -38
- package/src/components/OcMessage.vue +91 -91
- package/src/components/OcMessageList.vue +43 -43
- package/src/components/OcMessageThread.vue +155 -155
- package/src/components/OcModal.vue +42 -42
- package/src/components/OcNavmenu.vue +18 -18
- package/src/components/OcNavmenuList.vue +38 -38
- package/src/components/OcNextTeaser.vue +25 -25
- package/src/components/OcNumberInput.vue +47 -47
- package/src/components/OcOtpInput.vue +34 -34
- package/src/components/OcPageHeader.vue +34 -34
- package/src/components/OcPagination.vue +33 -33
- package/src/components/OcPasswordInput.vue +27 -27
- package/src/components/OcPasswordRequirements.vue +28 -28
- package/src/components/OcPhoneInput.vue +72 -72
- package/src/components/OcPopover.vue +25 -25
- package/src/components/OcPriceCard.vue +69 -69
- package/src/components/OcProductMark.vue +31 -31
- package/src/components/OcProgress.vue +21 -21
- package/src/components/OcProjectCard.vue +36 -36
- package/src/components/OcProse.vue +57 -57
- package/src/components/OcRadio.vue +40 -40
- package/src/components/OcRadioGroup.vue +32 -32
- package/src/components/OcRecoveryCodes.vue +55 -55
- package/src/components/OcScrollArea.vue +15 -15
- package/src/components/OcSegmented.vue +28 -28
- package/src/components/OcSelect.vue +21 -21
- package/src/components/OcSeparator.vue +18 -18
- package/src/components/OcSeverityCard.vue +46 -46
- package/src/components/OcSheet.vue +49 -49
- package/src/components/OcSidebarCollapsible.vue +19 -19
- package/src/components/OcSidebarGroup.vue +11 -11
- package/src/components/OcSidebarMenu.vue +7 -7
- package/src/components/OcSidebarMenuButton.vue +31 -31
- package/src/components/OcSidebarSeparator.vue +7 -7
- package/src/components/OcSkeleton.vue +8 -8
- package/src/components/OcSlider.vue +27 -27
- package/src/components/OcSortableRow.vue +40 -40
- package/src/components/OcSpinner.vue +13 -13
- package/src/components/OcStackCard.vue +24 -24
- package/src/components/OcStackList.vue +31 -31
- package/src/components/OcStatCard.vue +39 -39
- package/src/components/OcStepper.vue +34 -34
- package/src/components/OcSwitch.vue +15 -15
- package/src/components/OcTable.vue +115 -115
- package/src/components/OcTableActions.vue +26 -26
- package/src/components/OcTableCompact.vue +46 -46
- package/src/components/OcTabs.vue +37 -37
- package/src/components/OcTagInput.vue +50 -50
- package/src/components/OcTerminalLog.vue +50 -50
- package/src/components/OcTextarea.vue +9 -9
- package/src/components/OcThemeSwitch.vue +31 -31
- package/src/components/OcTimeInput.vue +50 -50
- package/src/components/OcTimeline.vue +36 -36
- package/src/components/OcToast.vue +31 -31
- package/src/components/OcToastContainer.vue +21 -21
- package/src/components/OcToggle.vue +63 -63
- package/src/components/OcToggleField.vue +28 -28
- package/src/components/OcToggleGroup.vue +40 -40
- package/src/components/OcTooltip.vue +8 -8
- package/src/components/OcTopProgress.vue +54 -54
- package/src/components/OcTree.vue +70 -70
- package/src/components/OcUpgradeBanner.vue +42 -42
- package/src/components/OcUser.vue +25 -25
- package/src/components/OcVersionBanner.vue +34 -34
- package/src/composables/chartLine.ts +28 -28
- package/src/composables/keys.ts +22 -22
- package/src/composables/useAttrsSplit.ts +16 -16
- package/src/composables/useDialog.ts +21 -21
- package/src/composables/useToast.ts +66 -66
- package/src/composables/useUid.ts +12 -12
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
// File: packages/vue/src/composables/chartLine.ts
|
|
2
|
-
// Map: number format -> nice axis maximum -> smooth path (same geometry as the chart-line snippet and the React wrapper)
|
|
3
|
-
|
|
4
|
-
/** Number with at most 2 decimals and no trailing zeros ("75", "37.5", "4.69"). */
|
|
5
|
-
export const n2 = (n: number) => String(parseFloat(n.toFixed(2)))
|
|
6
|
-
|
|
7
|
-
/** Round axis maximum: step = max / rows rounded up to 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8 or 10 x 10^k. */
|
|
8
|
-
export function niceMax(value: number, rows = 4) {
|
|
9
|
-
if (value <= 0) return rows
|
|
10
|
-
const raw = value / rows
|
|
11
|
-
const pow = 10 ** Math.floor(Math.log10(raw))
|
|
12
|
-
return ([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10].find((s) => s * pow >= raw) ?? 10) * pow * rows
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Smooth path through the points (x 0-100, y = 100 - value / max * 100); control points stay between their two ends. */
|
|
16
|
-
export function chartLinePath(values: number[], max: number) {
|
|
17
|
-
const pts = values.map((v, i) => [values.length > 1 ? (i / (values.length - 1)) * 100 : 50, 100 - (v / max) * 100])
|
|
18
|
-
if (!pts.length) return ''
|
|
19
|
-
const clamp = (v: number, a: number, b: number) => Math.min(Math.max(v, Math.min(a, b)), Math.max(a, b))
|
|
20
|
-
let d = `M${n2(pts[0][0])} ${n2(pts[0][1])}`
|
|
21
|
-
for (let i = 0; i < pts.length - 1; i++) {
|
|
22
|
-
const p0 = pts[i - 1] ?? pts[i], p1 = pts[i], p2 = pts[i + 1], p3 = pts[i + 2] ?? p2
|
|
23
|
-
const c1 = [p1[0] + (p2[0] - p0[0]) / 6, clamp(p1[1] + (p2[1] - p0[1]) / 6, p1[1], p2[1])]
|
|
24
|
-
const c2 = [p2[0] - (p3[0] - p1[0]) / 6, clamp(p2[1] - (p3[1] - p1[1]) / 6, p1[1], p2[1])]
|
|
25
|
-
d += ` C${n2(c1[0])} ${n2(c1[1])} ${n2(c2[0])} ${n2(c2[1])} ${n2(p2[0])} ${n2(p2[1])}`
|
|
26
|
-
}
|
|
27
|
-
return d
|
|
28
|
-
}
|
|
1
|
+
// File: packages/vue/src/composables/chartLine.ts
|
|
2
|
+
// Map: number format -> nice axis maximum -> smooth path (same geometry as the chart-line snippet and the React wrapper)
|
|
3
|
+
|
|
4
|
+
/** Number with at most 2 decimals and no trailing zeros ("75", "37.5", "4.69"). */
|
|
5
|
+
export const n2 = (n: number) => String(parseFloat(n.toFixed(2)))
|
|
6
|
+
|
|
7
|
+
/** Round axis maximum: step = max / rows rounded up to 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8 or 10 x 10^k. */
|
|
8
|
+
export function niceMax(value: number, rows = 4) {
|
|
9
|
+
if (value <= 0) return rows
|
|
10
|
+
const raw = value / rows
|
|
11
|
+
const pow = 10 ** Math.floor(Math.log10(raw))
|
|
12
|
+
return ([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10].find((s) => s * pow >= raw) ?? 10) * pow * rows
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Smooth path through the points (x 0-100, y = 100 - value / max * 100); control points stay between their two ends. */
|
|
16
|
+
export function chartLinePath(values: number[], max: number) {
|
|
17
|
+
const pts = values.map((v, i) => [values.length > 1 ? (i / (values.length - 1)) * 100 : 50, 100 - (v / max) * 100])
|
|
18
|
+
if (!pts.length) return ''
|
|
19
|
+
const clamp = (v: number, a: number, b: number) => Math.min(Math.max(v, Math.min(a, b)), Math.max(a, b))
|
|
20
|
+
let d = `M${n2(pts[0][0])} ${n2(pts[0][1])}`
|
|
21
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
22
|
+
const p0 = pts[i - 1] ?? pts[i], p1 = pts[i], p2 = pts[i + 1], p3 = pts[i + 2] ?? p2
|
|
23
|
+
const c1 = [p1[0] + (p2[0] - p0[0]) / 6, clamp(p1[1] + (p2[1] - p0[1]) / 6, p1[1], p2[1])]
|
|
24
|
+
const c2 = [p2[0] - (p3[0] - p1[0]) / 6, clamp(p2[1] - (p3[1] - p1[1]) / 6, p1[1], p2[1])]
|
|
25
|
+
d += ` C${n2(c1[0])} ${n2(c1[1])} ${n2(c2[0])} ${n2(c2[1])} ${n2(p2[0])} ${n2(p2[1])}`
|
|
26
|
+
}
|
|
27
|
+
return d
|
|
28
|
+
}
|
package/src/composables/keys.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
-
|
|
3
|
-
/** Provided by OcRadioGroup, injected by OcRadio. */
|
|
4
|
-
export interface RadioGroupContext {
|
|
5
|
-
model: Ref<string | number | undefined>
|
|
6
|
-
name: Ref<string>
|
|
7
|
-
card: Ref<boolean>
|
|
8
|
-
}
|
|
9
|
-
export const radioGroupKey: InjectionKey<RadioGroupContext> = Symbol('oc-radio-group')
|
|
10
|
-
|
|
11
|
-
/** Provided by OcToggleGroup, injected by OcToggle. */
|
|
12
|
-
export interface ToggleGroupContext {
|
|
13
|
-
/** Currently pressed values. */
|
|
14
|
-
values: Ref<string[]>
|
|
15
|
-
type: Ref<'single' | 'multiple'>
|
|
16
|
-
name: Ref<string>
|
|
17
|
-
toggle: (value: string) => void
|
|
18
|
-
}
|
|
19
|
-
export const toggleGroupKey: InjectionKey<ToggleGroupContext> = Symbol('oc-toggle-group')
|
|
20
|
-
|
|
21
|
-
/** Provided by OcResizable, injected by OcResizablePanel. */
|
|
22
|
-
export const resizableVerticalKey: InjectionKey<Ref<boolean>> = Symbol('oc-resizable-vertical')
|
|
1
|
+
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
/** Provided by OcRadioGroup, injected by OcRadio. */
|
|
4
|
+
export interface RadioGroupContext {
|
|
5
|
+
model: Ref<string | number | undefined>
|
|
6
|
+
name: Ref<string>
|
|
7
|
+
card: Ref<boolean>
|
|
8
|
+
}
|
|
9
|
+
export const radioGroupKey: InjectionKey<RadioGroupContext> = Symbol('oc-radio-group')
|
|
10
|
+
|
|
11
|
+
/** Provided by OcToggleGroup, injected by OcToggle. */
|
|
12
|
+
export interface ToggleGroupContext {
|
|
13
|
+
/** Currently pressed values. */
|
|
14
|
+
values: Ref<string[]>
|
|
15
|
+
type: Ref<'single' | 'multiple'>
|
|
16
|
+
name: Ref<string>
|
|
17
|
+
toggle: (value: string) => void
|
|
18
|
+
}
|
|
19
|
+
export const toggleGroupKey: InjectionKey<ToggleGroupContext> = Symbol('oc-toggle-group')
|
|
20
|
+
|
|
21
|
+
/** Provided by OcResizable, injected by OcResizablePanel. */
|
|
22
|
+
export const resizableVerticalKey: InjectionKey<Ref<boolean>> = Symbol('oc-resizable-vertical')
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { computed, useAttrs } from 'vue'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* For components whose root is a wrapper (label, div) around the real form control:
|
|
5
|
-
* `class` and `style` stay on the wrapper, every other attribute (id, name, disabled,
|
|
6
|
-
* autocomplete, aria-*, listeners) goes to the control. Use with defineOptions({ inheritAttrs: false }).
|
|
7
|
-
*/
|
|
8
|
-
export function useAttrsSplit() {
|
|
9
|
-
const attrs = useAttrs()
|
|
10
|
-
const root = computed(() => ({ class: attrs.class, style: attrs.style }))
|
|
11
|
-
const control = computed(() => {
|
|
12
|
-
const { class: _c, style: _s, ...rest } = attrs
|
|
13
|
-
return rest
|
|
14
|
-
})
|
|
15
|
-
return { root, control }
|
|
16
|
-
}
|
|
1
|
+
import { computed, useAttrs } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* For components whose root is a wrapper (label, div) around the real form control:
|
|
5
|
+
* `class` and `style` stay on the wrapper, every other attribute (id, name, disabled,
|
|
6
|
+
* autocomplete, aria-*, listeners) goes to the control. Use with defineOptions({ inheritAttrs: false }).
|
|
7
|
+
*/
|
|
8
|
+
export function useAttrsSplit() {
|
|
9
|
+
const attrs = useAttrs()
|
|
10
|
+
const root = computed(() => ({ class: attrs.class, style: attrs.style }))
|
|
11
|
+
const control = computed(() => {
|
|
12
|
+
const { class: _c, style: _s, ...rest } = attrs
|
|
13
|
+
return rest
|
|
14
|
+
})
|
|
15
|
+
return { root, control }
|
|
16
|
+
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { onMounted, watch, type Ref } from 'vue'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Keeps a native <dialog> in sync with a boolean `open` model:
|
|
5
|
-
* open -> showModal(), closed -> close(). Native close (Esc, backdrop, form method=dialog)
|
|
6
|
-
* is reported through `onClose` so v-model:open stays correct.
|
|
7
|
-
*/
|
|
8
|
-
export function useDialog(open: Ref<boolean | undefined>, el: Ref<HTMLDialogElement | null>) {
|
|
9
|
-
function sync() {
|
|
10
|
-
const dialog = el.value
|
|
11
|
-
if (!dialog) return
|
|
12
|
-
if (open.value && !dialog.open) dialog.showModal()
|
|
13
|
-
else if (!open.value && dialog.open) dialog.close()
|
|
14
|
-
}
|
|
15
|
-
watch(open, sync, { flush: 'post' })
|
|
16
|
-
onMounted(sync)
|
|
17
|
-
function onClose() {
|
|
18
|
-
if (open.value) open.value = false
|
|
19
|
-
}
|
|
20
|
-
return { onClose }
|
|
21
|
-
}
|
|
1
|
+
import { onMounted, watch, type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keeps a native <dialog> in sync with a boolean `open` model:
|
|
5
|
+
* open -> showModal(), closed -> close(). Native close (Esc, backdrop, form method=dialog)
|
|
6
|
+
* is reported through `onClose` so v-model:open stays correct.
|
|
7
|
+
*/
|
|
8
|
+
export function useDialog(open: Ref<boolean | undefined>, el: Ref<HTMLDialogElement | null>) {
|
|
9
|
+
function sync() {
|
|
10
|
+
const dialog = el.value
|
|
11
|
+
if (!dialog) return
|
|
12
|
+
if (open.value && !dialog.open) dialog.showModal()
|
|
13
|
+
else if (!open.value && dialog.open) dialog.close()
|
|
14
|
+
}
|
|
15
|
+
watch(open, sync, { flush: 'post' })
|
|
16
|
+
onMounted(sync)
|
|
17
|
+
function onClose() {
|
|
18
|
+
if (open.value) open.value = false
|
|
19
|
+
}
|
|
20
|
+
return { onClose }
|
|
21
|
+
}
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import { reactive, readonly } from 'vue'
|
|
2
|
-
|
|
3
|
-
export type ToastVariant = 'success' | 'error' | 'warning' | 'info'
|
|
4
|
-
|
|
5
|
-
export interface ToastOptions {
|
|
6
|
-
title: string
|
|
7
|
-
description?: string
|
|
8
|
-
variant?: ToastVariant
|
|
9
|
-
/** Milliseconds before auto-dismiss. 0 keeps the toast until dismissed. Default 5000. */
|
|
10
|
-
duration?: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ToastItem extends Required<Pick<ToastOptions, 'title' | 'variant'>> {
|
|
14
|
-
id: number
|
|
15
|
-
description?: string
|
|
16
|
-
duration: number
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const state = reactive<{ toasts: ToastItem[] }>({ toasts: [] })
|
|
20
|
-
const timers = new Map<number, ReturnType<typeof setTimeout>>()
|
|
21
|
-
let nextId = 1
|
|
22
|
-
|
|
23
|
-
function dismiss(id: number) {
|
|
24
|
-
const timer = timers.get(id)
|
|
25
|
-
if (timer) clearTimeout(timer)
|
|
26
|
-
timers.delete(id)
|
|
27
|
-
const i = state.toasts.findIndex((t) => t.id === id)
|
|
28
|
-
if (i !== -1) state.toasts.splice(i, 1)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function push(options: ToastOptions): number {
|
|
32
|
-
const id = nextId++
|
|
33
|
-
const item: ToastItem = {
|
|
34
|
-
id,
|
|
35
|
-
title: options.title,
|
|
36
|
-
description: options.description,
|
|
37
|
-
variant: options.variant ?? 'info',
|
|
38
|
-
duration: options.duration ?? 5000,
|
|
39
|
-
}
|
|
40
|
-
state.toasts.push(item)
|
|
41
|
-
if (item.duration > 0) timers.set(id, setTimeout(() => dismiss(id), item.duration))
|
|
42
|
-
return id
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function clear() {
|
|
46
|
-
for (const t of [...state.toasts]) dismiss(t.id)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const shortcut =
|
|
50
|
-
(variant: ToastVariant) =>
|
|
51
|
-
(title: string, description?: string, options: Omit<ToastOptions, 'title' | 'description' | 'variant'> = {}) =>
|
|
52
|
-
push({ ...options, title, description, variant })
|
|
53
|
-
|
|
54
|
-
/** Shared toast queue. Render it once with <OcToastContainer />. */
|
|
55
|
-
export function useToast() {
|
|
56
|
-
return {
|
|
57
|
-
toasts: readonly(state).toasts,
|
|
58
|
-
push,
|
|
59
|
-
dismiss,
|
|
60
|
-
clear,
|
|
61
|
-
success: shortcut('success'),
|
|
62
|
-
error: shortcut('error'),
|
|
63
|
-
warning: shortcut('warning'),
|
|
64
|
-
info: shortcut('info'),
|
|
65
|
-
}
|
|
66
|
-
}
|
|
1
|
+
import { reactive, readonly } from 'vue'
|
|
2
|
+
|
|
3
|
+
export type ToastVariant = 'success' | 'error' | 'warning' | 'info'
|
|
4
|
+
|
|
5
|
+
export interface ToastOptions {
|
|
6
|
+
title: string
|
|
7
|
+
description?: string
|
|
8
|
+
variant?: ToastVariant
|
|
9
|
+
/** Milliseconds before auto-dismiss. 0 keeps the toast until dismissed. Default 5000. */
|
|
10
|
+
duration?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ToastItem extends Required<Pick<ToastOptions, 'title' | 'variant'>> {
|
|
14
|
+
id: number
|
|
15
|
+
description?: string
|
|
16
|
+
duration: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const state = reactive<{ toasts: ToastItem[] }>({ toasts: [] })
|
|
20
|
+
const timers = new Map<number, ReturnType<typeof setTimeout>>()
|
|
21
|
+
let nextId = 1
|
|
22
|
+
|
|
23
|
+
function dismiss(id: number) {
|
|
24
|
+
const timer = timers.get(id)
|
|
25
|
+
if (timer) clearTimeout(timer)
|
|
26
|
+
timers.delete(id)
|
|
27
|
+
const i = state.toasts.findIndex((t) => t.id === id)
|
|
28
|
+
if (i !== -1) state.toasts.splice(i, 1)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function push(options: ToastOptions): number {
|
|
32
|
+
const id = nextId++
|
|
33
|
+
const item: ToastItem = {
|
|
34
|
+
id,
|
|
35
|
+
title: options.title,
|
|
36
|
+
description: options.description,
|
|
37
|
+
variant: options.variant ?? 'info',
|
|
38
|
+
duration: options.duration ?? 5000,
|
|
39
|
+
}
|
|
40
|
+
state.toasts.push(item)
|
|
41
|
+
if (item.duration > 0) timers.set(id, setTimeout(() => dismiss(id), item.duration))
|
|
42
|
+
return id
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function clear() {
|
|
46
|
+
for (const t of [...state.toasts]) dismiss(t.id)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const shortcut =
|
|
50
|
+
(variant: ToastVariant) =>
|
|
51
|
+
(title: string, description?: string, options: Omit<ToastOptions, 'title' | 'description' | 'variant'> = {}) =>
|
|
52
|
+
push({ ...options, title, description, variant })
|
|
53
|
+
|
|
54
|
+
/** Shared toast queue. Render it once with <OcToastContainer />. */
|
|
55
|
+
export function useToast() {
|
|
56
|
+
return {
|
|
57
|
+
toasts: readonly(state).toasts,
|
|
58
|
+
push,
|
|
59
|
+
dismiss,
|
|
60
|
+
clear,
|
|
61
|
+
success: shortcut('success'),
|
|
62
|
+
error: shortcut('error'),
|
|
63
|
+
warning: shortcut('warning'),
|
|
64
|
+
info: shortcut('info'),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import * as Vue from 'vue'
|
|
2
|
-
|
|
3
|
-
let counter = 0
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Stable unique id for aria-labelledby / popovertarget / label-for wiring.
|
|
7
|
-
* Uses Vue's built-in useId() when available (Vue >= 3.5, SSR safe), otherwise a counter.
|
|
8
|
-
*/
|
|
9
|
-
export function useUid(prefix = 'oc'): string {
|
|
10
|
-
const useId = (Vue as Record<string, unknown>)['useId'] as (() => string) | undefined
|
|
11
|
-
return `${prefix}-${useId ? useId() : ++counter}`
|
|
12
|
-
}
|
|
1
|
+
import * as Vue from 'vue'
|
|
2
|
+
|
|
3
|
+
let counter = 0
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Stable unique id for aria-labelledby / popovertarget / label-for wiring.
|
|
7
|
+
* Uses Vue's built-in useId() when available (Vue >= 3.5, SSR safe), otherwise a counter.
|
|
8
|
+
*/
|
|
9
|
+
export function useUid(prefix = 'oc'): string {
|
|
10
|
+
const useId = (Vue as Record<string, unknown>)['useId'] as (() => string) | undefined
|
|
11
|
+
return `${prefix}-${useId ? useId() : ++counter}`
|
|
12
|
+
}
|