@livestorm/ui-vue 2.0.0 → 2.0.2

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/dist/utils.mjs CHANGED
@@ -1,111 +1,146 @@
1
- import { BREAKPOINT_2XL as M, BREAKPOINT_LG as V, BREAKPOINT_MD as b, BREAKPOINT_SM as w, BREAKPOINT_XL as N, BREAKPOINT_XL_PLUS as X } from "./breakpoints.mjs";
1
+ import { BREAKPOINT_2XL as F, BREAKPOINT_LG as N, BREAKPOINT_MD as X, BREAKPOINT_SM as D, BREAKPOINT_XL as Q, BREAKPOINT_XL_PLUS as U } from "./breakpoints.mjs";
2
2
  import "./bus.mjs";
3
- import { computed as m } from "vue";
4
- import { useClipboard as y, usePermission as h } from "@vueuse/core";
3
+ import { computed as h, createApp as _, h as p } from "vue";
4
+ import { useClipboard as g, usePermission as x } from "@vueuse/core";
5
5
  import R from "i18next";
6
- import { createTooltip as g, destroyTooltip as T } from "floating-vue";
7
- import { useResponsive as F } from "./responsive.mjs";
8
- import { useRoute as A, useRouter as _ } from "vue-router";
9
- import { RenderSlot as U, getSlotAsComponent as W, unwrapFragments as j, useSlotsAsComponents as D } from "./slot.mjs";
10
- import { f as z, h as H, a as J, u as Y } from "./a11y-DIdrv5Mn.mjs";
11
- function B() {
12
- const { isSupported: n, copy: u } = y(), s = h("clipboard-write"), c = m(() => n.value && s.value);
13
- async function f(p, o, i = R.t("general_misc_copied-text")) {
14
- if (await u(p), o) {
6
+ import { b as A, _ as C, c as P, a as T } from "./a11y--y7uaY4j.mjs";
7
+ import { f as j, h as G, d as z, u as H } from "./a11y--y7uaY4j.mjs";
8
+ import { useResponsive as Y } from "./responsive.mjs";
9
+ import { useRoute as $, useRouter as S } from "vue-router";
10
+ import { RenderSlot as ee, getSlotAsComponent as te, unwrapFragments as oe, useSlotsAsComponents as ne } from "./slot.mjs";
11
+ let d = null;
12
+ function k(s, u) {
13
+ d && d();
14
+ const t = s.getBoundingClientRect(), r = document.createElement("div");
15
+ document.body.appendChild(r);
16
+ const c = _({
17
+ render: () => p(A, null, () => p(C, { open: !0, delayDuration: 0 }, () => [
18
+ p(P, { asChild: !0 }, () => p("span", {
19
+ "aria-hidden": "true",
20
+ style: {
21
+ position: "fixed",
22
+ left: `${t.left}px`,
23
+ top: `${t.top}px`,
24
+ width: `${t.width}px`,
25
+ height: `${t.height}px`,
26
+ pointerEvents: "none"
27
+ }
28
+ })),
29
+ p(T, { side: "top" }, () => u),
30
+ // The bubble lives in reka's portal where attrs like role are not
31
+ // forwardable; announce the feedback from a visually hidden live
32
+ // region instead so screen readers hear the copy confirmation.
33
+ p("span", {
34
+ role: "status",
35
+ style: {
36
+ position: "absolute",
37
+ width: "1px",
38
+ height: "1px",
39
+ overflow: "hidden",
40
+ clipPath: "inset(50%)"
41
+ }
42
+ }, u)
43
+ ]))
44
+ });
45
+ c.mount(r);
46
+ let a = !1;
47
+ const o = () => {
48
+ a || (a = !0, d === o && (d = null), c.unmount(), r.remove());
49
+ };
50
+ return d = o, o;
51
+ }
52
+ function O() {
53
+ const { isSupported: s, copy: u } = g(), t = x("clipboard-write"), r = h(() => s.value && t.value);
54
+ async function c(a, o, f = R.t("general_misc_copied-text")) {
55
+ if (await u(a), o) {
15
56
  o.classList.add("copied");
16
- const a = g(o, {
17
- triggers: [],
18
- content: i,
19
- delay: 0
20
- });
21
- a.show(), setTimeout(() => {
22
- o.classList.remove("copied"), a.hide(), setTimeout(() => {
23
- T(o);
24
- }, 400);
57
+ const m = k(o, f);
58
+ setTimeout(() => {
59
+ o.classList.remove("copied"), m();
25
60
  }, 600);
26
61
  }
27
62
  }
28
63
  return {
29
- canCopy: c,
30
- copy: f
64
+ canCopy: r,
65
+ copy: c
31
66
  };
32
67
  }
33
- function E(n, u = !1) {
34
- const s = A(), c = _(), f = `${n}-`;
35
- function p() {
36
- const t = [];
37
- for (const e in s.query)
38
- e.startsWith(f) && t.push(e);
39
- return t;
68
+ function q(s, u = !1) {
69
+ const t = $(), r = S(), c = `${s}-`;
70
+ function a() {
71
+ const e = [];
72
+ for (const n in t.query)
73
+ n.startsWith(c) && e.push(n);
74
+ return e;
40
75
  }
41
- function o(t) {
42
- return t === "true" ? !0 : t === "false" ? !1 : t ?? !1;
76
+ function o(e) {
77
+ return e === "true" ? !0 : e === "false" ? !1 : e ?? !1;
43
78
  }
44
- const i = m(() => {
79
+ const f = h(() => {
45
80
  if (u) {
46
- const t = p();
47
- if (t.length === 0)
81
+ const e = a();
82
+ if (e.length === 0)
48
83
  return !1;
49
- const e = {};
50
- for (const r of t)
51
- e[r.substring(f.length)] = o(s.query[r]);
52
- return e;
84
+ const n = {};
85
+ for (const i of e)
86
+ n[i.substring(c.length)] = o(t.query[i]);
87
+ return n;
53
88
  } else
54
- return o(s.query[n]);
55
- }), a = !!i.value;
56
- return m({
57
- get: () => i.value,
58
- set: (t) => {
59
- if (t === i.value) return;
60
- const e = t == null || t === !1;
61
- if (e && !a)
62
- c.back();
89
+ return o(t.query[s]);
90
+ }), m = !!f.value;
91
+ return h({
92
+ get: () => f.value,
93
+ set: (e) => {
94
+ if (e === f.value) return;
95
+ const n = e == null || e === !1;
96
+ if (n && !m)
97
+ r.back();
63
98
  else {
64
- let r;
99
+ let i;
65
100
  if (u)
66
- if (r = {}, e) {
67
- const d = p();
68
- for (const l of d)
69
- r[l] = void 0;
101
+ if (i = {}, n) {
102
+ const l = a();
103
+ for (const y of l)
104
+ i[y] = void 0;
70
105
  } else
71
- for (const d in t)
72
- r[`${n}-${d}`] = t[d];
106
+ for (const l in e)
107
+ i[`${s}-${l}`] = e[l];
73
108
  else
74
- r = {
75
- [n]: e ? void 0 : t
109
+ i = {
110
+ [s]: n ? void 0 : e
76
111
  };
77
- c[e ? "replace" : "push"]({
78
- ...s,
112
+ r[n ? "replace" : "push"]({
113
+ ...t,
79
114
  query: {
80
- ...s.query,
81
- ...r
115
+ ...t.query,
116
+ ...i
82
117
  }
83
118
  });
84
119
  }
85
120
  }
86
121
  });
87
122
  }
88
- let C = 0;
89
- function K() {
90
- return ++C;
123
+ let E = 0;
124
+ function M() {
125
+ return ++E;
91
126
  }
92
127
  export {
93
- M as BREAKPOINT_2XL,
94
- V as BREAKPOINT_LG,
95
- b as BREAKPOINT_MD,
96
- w as BREAKPOINT_SM,
97
- N as BREAKPOINT_XL,
98
- X as BREAKPOINT_XL_PLUS,
99
- U as RenderSlot,
100
- z as focusFirstMenuItem,
101
- W as getSlotAsComponent,
102
- K as getUid,
103
- H as hasStackedComponents,
104
- j as unwrapFragments,
105
- B as useCopyToClipboard,
106
- F as useResponsive,
107
- E as useRouteQueryModel,
108
- D as useSlotsAsComponents,
109
- J as useStackAutofocus,
110
- Y as useStackedComponent
128
+ F as BREAKPOINT_2XL,
129
+ N as BREAKPOINT_LG,
130
+ X as BREAKPOINT_MD,
131
+ D as BREAKPOINT_SM,
132
+ Q as BREAKPOINT_XL,
133
+ U as BREAKPOINT_XL_PLUS,
134
+ ee as RenderSlot,
135
+ j as focusFirstMenuItem,
136
+ te as getSlotAsComponent,
137
+ M as getUid,
138
+ G as hasStackedComponents,
139
+ oe as unwrapFragments,
140
+ O as useCopyToClipboard,
141
+ Y as useResponsive,
142
+ q as useRouteQueryModel,
143
+ ne as useSlotsAsComponents,
144
+ z as useStackAutofocus,
145
+ H as useStackedComponent
111
146
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livestorm/ui-vue",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Livestorm UI components for Vue 3",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -35,14 +35,12 @@
35
35
  "embla-carousel": "^8.6.0",
36
36
  "embla-carousel-autoplay": "^8.6.0",
37
37
  "embla-carousel-vue": "^8.6.0",
38
- "floating-vue": "^5.2.2",
39
38
  "i18next": "^26.3.4",
40
39
  "mitt": "^3.0.1",
41
40
  "reka-ui": "2.9.10",
42
41
  "tailwindcss": "^4.3.2",
43
- "vue-safe-teleport": "^0.1.2",
44
- "@livestorm/ui-icons": "^2.0.0",
45
- "@livestorm/ui-tokens": "^2.0.0"
42
+ "@livestorm/ui-tokens": "^2.0.2",
43
+ "@livestorm/ui-icons": "^2.0.2"
46
44
  },
47
45
  "devDependencies": {
48
46
  "@chromatic-com/storybook": "^5.2.1",
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, getCurrentInstance } from 'vue'
3
3
  import type { ResponsiveGlobalComponentProperty } from '../../utils/responsive.types'
4
+ import { useResponsive } from '../../utils/responsive'
4
5
  import { useToasts, type ToastOptions } from './toastDSV2'
5
6
  import ToastMessage from './ToastMessage.vue'
6
7
 
@@ -8,7 +9,9 @@ const app = getCurrentInstance()
8
9
  if (!app) {
9
10
  throw new Error('No current instance found')
10
11
  }
11
- const responsive = computed(() => app.appContext.config.globalProperties.$responsive as ResponsiveGlobalComponentProperty)
12
+ // Prefer the app-installed $responsive (it may carry custom flags); fall
13
+ // back to the shared singleton so no plugin registration is required.
14
+ const responsive = computed(() => (app.appContext.config.globalProperties.$responsive ?? useResponsive()) as ResponsiveGlobalComponentProperty)
12
15
 
13
16
  const MAX_ITEMS_DISPLAYED = 3
14
17
 
@@ -24,59 +24,60 @@
24
24
  class="button-overlay absolute h-full top-0 left-0 z-10 rounded-md w-full"
25
25
  :class="overlayClasses"
26
26
  />
27
- <div
28
- v-tooltip="tooltip"
29
- class="button-wrapper flex items-center whitespace-nowrap h-full relative z-20"
30
- >
31
- <!-- Loader -->
32
- <UiLoader
33
- v-if="loading"
34
- :size="size"
35
- :class="{ 'mr-2': hasLabel }"
36
- />
37
-
38
- <!-- Content -->
39
- <template
40
- v-else-if="showIconLeft"
27
+ <UiTooltip :content="typeof tooltip === 'string' ? tooltip : tooltip?.content">
28
+ <div
29
+ class="button-wrapper flex items-center whitespace-nowrap h-full relative z-20"
41
30
  >
42
- <div
43
- v-if="iconSlot"
44
- :class="[iconSizeClasses, { 'mr-2': hasLabel }]"
45
- class="shrink-0"
31
+ <!-- Loader -->
32
+ <UiLoader
33
+ v-if="loading"
34
+ :size="size"
35
+ :class="{ 'mr-2': hasLabel }"
36
+ />
37
+
38
+ <!-- Content -->
39
+ <template
40
+ v-else-if="showIconLeft"
46
41
  >
47
- <RenderSlot
48
- :vnodes="iconSlot"
42
+ <div
43
+ v-if="iconSlot"
44
+ :class="[iconSizeClasses, { 'mr-2': hasLabel }]"
45
+ class="shrink-0"
46
+ >
47
+ <RenderSlot
48
+ :vnodes="iconSlot"
49
+ />
50
+ </div>
51
+ <div
52
+ v-else-if="imageSource"
53
+ :class="[iconSizeClasses, { 'mr-2': hasLabel }]"
54
+ class="shrink-0"
55
+ >
56
+ <img
57
+ :src="imageSource"
58
+ :alt="`${labelFinal} button image`"
59
+ >
60
+ </div>
61
+ <div
62
+ v-else-if="icon"
63
+ :class="[icon, iconSizeClasses, { 'mr-2': hasLabel }]"
64
+ class="shrink-0"
49
65
  />
50
- </div>
51
- <div
52
- v-else-if="imageSource"
53
- :class="[iconSizeClasses, { 'mr-2': hasLabel }]"
54
- class="shrink-0"
66
+ </template>
67
+ <span
68
+ class="content w-full"
55
69
  >
56
- <img
57
- :src="imageSource"
58
- :alt="`${labelFinal} button image`"
59
- >
60
- </div>
70
+ <RenderSlot
71
+ :vnodes="defaultSlot"
72
+ />
73
+ </span>
61
74
  <div
62
- v-else-if="icon"
63
- :class="[icon, iconSizeClasses, { 'mr-2': hasLabel }]"
75
+ v-if="iconRight"
76
+ :class="[iconRight, iconSizeClasses, { 'ml-2': hasLabel }]"
64
77
  class="shrink-0"
65
78
  />
66
- </template>
67
- <span
68
- class="content w-full"
69
- >
70
- <RenderSlot
71
- :vnodes="defaultSlot"
72
- />
73
- </span>
74
- <div
75
- v-if="iconRight"
76
- :class="[iconRight, iconSizeClasses, { 'ml-2': hasLabel }]"
77
- class="shrink-0"
78
- />
79
- </div>
79
+ </div>
80
+ </UiTooltip>
80
81
  </component>
81
82
  </template>
82
83
 
@@ -86,6 +87,7 @@ import { useSlotsAsComponents, RenderSlot } from '../../utils/slot'
86
87
  import { SIZES, type Size } from '../../constants'
87
88
  import { useButtonStyles, BUTTON_COLORS, type ButtonVariant, type ButtonColor } from '../../composables/useButtonStyles'
88
89
  import UiLoader from '../UiLoader/UiLoader.vue'
90
+ import UiTooltip from '../UiTooltip/UiTooltip.vue'
89
91
  import type { RouteLocationRaw } from 'vue-router'
90
92
 
91
93
  export type Variant = ButtonVariant
@@ -96,6 +98,7 @@ export default {
96
98
  components: {
97
99
  RenderSlot,
98
100
  UiLoader,
101
+ UiTooltip,
99
102
  },
100
103
 
101
104
  props: {
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
3
  import { Label, useId } from 'reka-ui'
4
+ import UiTooltip from '../UiTooltip/UiTooltip.vue'
4
5
 
5
6
  interface Props {
6
7
  id?: string
@@ -48,13 +49,14 @@ defineExpose({ fieldId })
48
49
  >
49
50
  {{ label }}{{ required ? '*' : '' }}
50
51
  </Label>
51
- <div
52
+ <UiTooltip
52
53
  v-if="labelInfo"
53
- v-tooltip="labelInfo"
54
- class="flex items-center justify-center size-4 text-text-neutral-secondary cursor-help"
54
+ :content="labelInfo"
55
55
  >
56
- <span class="i-ls-info size-4" />
57
- </div>
56
+ <div class="flex items-center justify-center size-4 text-text-neutral-secondary cursor-help">
57
+ <span class="i-ls-info size-4" />
58
+ </div>
59
+ </UiTooltip>
58
60
  </div>
59
61
 
60
62
  <!-- Input -->
@@ -2,6 +2,7 @@
2
2
  import { computed, ref, watch, nextTick } from 'vue'
3
3
  import { Label, useId } from 'reka-ui'
4
4
  import type { CheckboxSize } from './types'
5
+ import UiTooltip from '../UiTooltip/UiTooltip.vue'
5
6
 
6
7
  interface UiInputCheckboxProps {
7
8
  /**
@@ -390,18 +391,21 @@ defineExpose({
390
391
  </Label>
391
392
 
392
393
  <!-- icon -->
393
- <div
394
+ <UiTooltip
394
395
  v-if="labelInfo"
395
- v-tooltip="labelInfo"
396
- class="flex items-center justify-center size-4 text-text-neutral-primary cursor-help shrink-0"
397
- :class="{
398
- 'text-text-neutral-base': !error && !isChecked,
399
- 'text-text-danger-base': error,
400
- 'text-text-primary-base': isChecked && !error,
401
- }"
396
+ :content="labelInfo"
402
397
  >
403
- <span class="i-ls-info-fill size-4" />
404
- </div>
398
+ <div
399
+ class="flex items-center justify-center size-4 text-text-neutral-primary cursor-help shrink-0"
400
+ :class="{
401
+ 'text-text-neutral-base': !error && !isChecked,
402
+ 'text-text-danger-base': error,
403
+ 'text-text-primary-base': isChecked && !error,
404
+ }"
405
+ >
406
+ <span class="i-ls-info-fill size-4" />
407
+ </div>
408
+ </UiTooltip>
405
409
  </div>
406
410
 
407
411
  <span
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { HTMLAttributes } from 'vue'
3
+ import UiTooltip from '../UiTooltip/UiTooltip.vue'
3
4
 
4
5
  interface Props {
5
6
  label?: string | null
@@ -21,14 +22,15 @@ withDefaults(defineProps<Props>(), {
21
22
 
22
23
  <template>
23
24
  <div class="mb-1 flex items-center text-content-legends-bold-md text-text-neutral-tertiary">
24
- <label
25
- v-tooltip="tooltip"
26
- :for="forId"
27
- :class="labelClass"
28
- :style="{
29
- color: labelColor,
30
- }"
31
- v-html="required ? `${label}*` : label"
32
- />
25
+ <UiTooltip :content="tooltip">
26
+ <label
27
+ :for="forId"
28
+ :class="labelClass"
29
+ :style="{
30
+ color: labelColor,
31
+ }"
32
+ v-html="required ? `${label}*` : label"
33
+ />
34
+ </UiTooltip>
33
35
  </div>
34
36
  </template>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, onBeforeUnmount, onMounted, ref, watch, getCurrentInstance, type Slot } from 'vue'
3
3
  import { BREAKPOINT_LG } from '../../utils/breakpoints'
4
+ import { useResponsive } from '../../utils/responsive'
4
5
  import useThemeAndModes from '../../composables/useThemeAndModes'
5
6
  import { useMobileMenu } from '../../composables/useMobileMenu'
6
7
  import UiLoader from '../UiLoader/UiLoader.vue'
@@ -83,7 +84,9 @@ const app = getCurrentInstance()
83
84
  if (!app) {
84
85
  throw new Error('No current instance found')
85
86
  }
86
- const responsive = computed(() => app.appContext.config.globalProperties.$responsive)
87
+ // Prefer the app-installed $responsive (it may carry custom flags); fall
88
+ // back to the shared singleton so no plugin registration is required.
89
+ const responsive = computed(() => app.appContext.config.globalProperties.$responsive ?? useResponsive())
87
90
  const displayLeftSlot = computed(() => responsive.value.width >= BREAKPOINT_LG)
88
91
 
89
92
  const UiPage = ref<HTMLElement | undefined>()
@@ -1,16 +1,68 @@
1
1
  <!-- UiTooltip.vue -->
2
2
  <script setup lang="ts">
3
- import type { TooltipRootEmits, TooltipRootProps } from 'reka-ui'
3
+ import { computed, getCurrentInstance } from 'vue'
4
+ import type { TooltipContentProps, TooltipRootEmits, TooltipRootProps } from 'reka-ui'
4
5
  import { TooltipRoot, useForwardPropsEmits } from 'reka-ui'
6
+ import { reactiveOmit } from '@vueuse/core'
7
+ import UiTooltipProvider from './UiTooltipProvider.vue'
8
+ import UiTooltipTrigger from './UiTooltipTrigger.vue'
9
+ import UiTooltipContent from './UiTooltipContent.vue'
5
10
 
6
- const props = defineProps<TooltipRootProps>()
11
+ export interface UiTooltipProps extends TooltipRootProps {
12
+ /**
13
+ * Simple mode: the tooltip text. When `:content` is bound, UiTooltip is
14
+ * fully self-contained — the default slot becomes the trigger (merged
15
+ * via as-child) and no surrounding UiTooltipProvider is required:
16
+ *
17
+ * <UiTooltip content="Delete this event"><UiButton … /></UiTooltip>
18
+ *
19
+ * A bound-but-empty content renders the slot untouched (no tooltip, no
20
+ * overhead) — so wrappers can always bind an optional string.
21
+ *
22
+ * When `:content` is NOT bound, UiTooltip is the composable root: use it
23
+ * inside a UiTooltipProvider with UiTooltipTrigger/UiTooltipContent
24
+ * children.
25
+ */
26
+ content?: string | null
27
+ /** Simple mode only: which side the tooltip appears on. */
28
+ side?: TooltipContentProps['side']
29
+ }
30
+
31
+ defineOptions({ inheritAttrs: false })
32
+
33
+ const props = defineProps<UiTooltipProps>()
7
34
  const emits = defineEmits<TooltipRootEmits>()
8
35
 
9
- const forwarded = useForwardPropsEmits(props, emits)
36
+ // Simple vs composed mode is decided by the BINDING, not the value:
37
+ // reka's TooltipRoot requires a provider, so the composed branch must only
38
+ // render when the caller actually composes (no `content` bound).
39
+ const hasContentBinding = computed(() => {
40
+ const vnodeProps = getCurrentInstance()?.vnode.props
41
+ return !!vnodeProps && 'content' in vnodeProps
42
+ })
43
+
44
+ // content/side are ours — only forward the reka root props
45
+ const forwarded = useForwardPropsEmits(reactiveOmit(props, 'content', 'side'), emits)
10
46
  </script>
11
47
 
12
48
  <template>
13
- <TooltipRoot v-bind="{ ...forwarded, ...$attrs }">
49
+ <template v-if="hasContentBinding">
50
+ <UiTooltipProvider v-if="content">
51
+ <TooltipRoot v-bind="{ ...forwarded, ...$attrs }">
52
+ <UiTooltipTrigger as-child>
53
+ <slot />
54
+ </UiTooltipTrigger>
55
+ <UiTooltipContent :side="side">
56
+ {{ content }}
57
+ </UiTooltipContent>
58
+ </TooltipRoot>
59
+ </UiTooltipProvider>
60
+ <slot v-else />
61
+ </template>
62
+ <TooltipRoot
63
+ v-else
64
+ v-bind="{ ...forwarded, ...$attrs }"
65
+ >
14
66
  <slot />
15
67
  </TooltipRoot>
16
68
  </template>