@bagelink/vue 1.15.63 → 1.15.65

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.
Files changed (136) hide show
  1. package/dist/components/AccordionItem.vue.d.ts.map +1 -1
  2. package/dist/components/Avatar.vue.d.ts +6 -1
  3. package/dist/components/Avatar.vue.d.ts.map +1 -1
  4. package/dist/components/Badge.vue.d.ts.map +1 -1
  5. package/dist/components/Card.vue.d.ts +7 -0
  6. package/dist/components/Card.vue.d.ts.map +1 -1
  7. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  8. package/dist/components/EmptyState.vue.d.ts +43 -0
  9. package/dist/components/EmptyState.vue.d.ts.map +1 -0
  10. package/dist/components/Icon/Icon.vue.d.ts +13 -0
  11. package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
  12. package/dist/components/Image.vue.d.ts +26 -1
  13. package/dist/components/Image.vue.d.ts.map +1 -1
  14. package/dist/components/ListItem.vue.d.ts +9 -9
  15. package/dist/components/ListItem.vue.d.ts.map +1 -1
  16. package/dist/components/Menu.vue.d.ts.map +1 -1
  17. package/dist/components/Swiper.vue.d.ts +3 -3
  18. package/dist/components/calendar/CalendarPopover.vue.d.ts +10 -0
  19. package/dist/components/calendar/CalendarPopover.vue.d.ts.map +1 -1
  20. package/dist/components/charts/BarChart.vue.d.ts +34 -0
  21. package/dist/components/charts/BarChart.vue.d.ts.map +1 -0
  22. package/dist/components/charts/ChartTooltip.vue.d.ts +33 -0
  23. package/dist/components/charts/ChartTooltip.vue.d.ts.map +1 -0
  24. package/dist/components/charts/Donut.vue.d.ts +53 -0
  25. package/dist/components/charts/Donut.vue.d.ts.map +1 -0
  26. package/dist/components/charts/Funnel.vue.d.ts +53 -0
  27. package/dist/components/charts/Funnel.vue.d.ts.map +1 -0
  28. package/dist/components/charts/Gauge.vue.d.ts +28 -0
  29. package/dist/components/charts/Gauge.vue.d.ts.map +1 -0
  30. package/dist/components/charts/LineChart.vue.d.ts +37 -0
  31. package/dist/components/charts/LineChart.vue.d.ts.map +1 -0
  32. package/dist/components/charts/RadialBars.vue.d.ts +34 -0
  33. package/dist/components/charts/RadialBars.vue.d.ts.map +1 -0
  34. package/dist/components/charts/RankBars.vue.d.ts +27 -0
  35. package/dist/components/charts/RankBars.vue.d.ts.map +1 -0
  36. package/dist/components/charts/Sparkline.vue.d.ts +25 -0
  37. package/dist/components/charts/Sparkline.vue.d.ts.map +1 -0
  38. package/dist/components/charts/StatCard.vue.d.ts +28 -0
  39. package/dist/components/charts/StatCard.vue.d.ts.map +1 -0
  40. package/dist/components/charts/core/data.d.ts +46 -0
  41. package/dist/components/charts/core/data.d.ts.map +1 -0
  42. package/dist/components/charts/core/format.d.ts +13 -0
  43. package/dist/components/charts/core/format.d.ts.map +1 -0
  44. package/dist/components/charts/core/palette.d.ts +19 -0
  45. package/dist/components/charts/core/palette.d.ts.map +1 -0
  46. package/dist/components/charts/core/uid.d.ts +2 -0
  47. package/dist/components/charts/core/uid.d.ts.map +1 -0
  48. package/dist/components/charts/core/useChartAnim.d.ts +11 -0
  49. package/dist/components/charts/core/useChartAnim.d.ts.map +1 -0
  50. package/dist/components/charts/core/useChartFrame.d.ts +21 -0
  51. package/dist/components/charts/core/useChartFrame.d.ts.map +1 -0
  52. package/dist/components/charts/core/useScale.d.ts +16 -0
  53. package/dist/components/charts/core/useScale.d.ts.map +1 -0
  54. package/dist/components/charts/index.d.ts +12 -0
  55. package/dist/components/charts/index.d.ts.map +1 -0
  56. package/dist/components/form/inputs/RadioGroup.vue.d.ts +1 -0
  57. package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
  58. package/dist/components/form/inputs/RangeInput.vue.d.ts +13 -4
  59. package/dist/components/form/inputs/RangeInput.vue.d.ts.map +1 -1
  60. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  61. package/dist/components/index.d.ts +3 -1
  62. package/dist/components/index.d.ts.map +1 -1
  63. package/dist/components/layout/Layout.vue.d.ts +1 -1
  64. package/dist/components/layout/Layout.vue.d.ts.map +1 -1
  65. package/dist/components/layout/Panel.vue.d.ts +1 -1
  66. package/dist/components/layout/Panel.vue.d.ts.map +1 -1
  67. package/dist/components/layout/Timeline.types.d.ts +9 -0
  68. package/dist/components/layout/Timeline.types.d.ts.map +1 -0
  69. package/dist/components/layout/Timeline.vue.d.ts +42 -0
  70. package/dist/components/layout/Timeline.vue.d.ts.map +1 -0
  71. package/dist/components/layout/TimelineItem.vue.d.ts +37 -0
  72. package/dist/components/layout/TimelineItem.vue.d.ts.map +1 -0
  73. package/dist/components/layout/index.d.ts +3 -0
  74. package/dist/components/layout/index.d.ts.map +1 -1
  75. package/dist/dialog/Dialog.vue.d.ts +4 -0
  76. package/dist/dialog/Dialog.vue.d.ts.map +1 -1
  77. package/dist/index.cjs +110 -116
  78. package/dist/index.mjs +38059 -37009
  79. package/dist/style.css +1 -1
  80. package/package.json +2 -1
  81. package/src/components/AccordionItem.vue +24 -22
  82. package/src/components/Avatar.vue +49 -11
  83. package/src/components/Badge.vue +4 -7
  84. package/src/components/Card.vue +32 -2
  85. package/src/components/Dropdown.vue +14 -3
  86. package/src/components/EmptyState.vue +91 -0
  87. package/src/components/Icon/Icon.vue +118 -25
  88. package/src/components/Image.vue +70 -3
  89. package/src/components/ListItem.vue +43 -22
  90. package/src/components/Menu.vue +10 -2
  91. package/src/components/charts/BarChart.vue +197 -0
  92. package/src/components/charts/ChartTooltip.vue +74 -0
  93. package/src/components/charts/Donut.vue +219 -0
  94. package/src/components/charts/Funnel.vue +377 -0
  95. package/src/components/charts/Gauge.vue +90 -0
  96. package/src/components/charts/LineChart.vue +255 -0
  97. package/src/components/charts/RadialBars.vue +99 -0
  98. package/src/components/charts/RankBars.vue +72 -0
  99. package/src/components/charts/Sparkline.vue +90 -0
  100. package/src/components/charts/StatCard.vue +84 -0
  101. package/src/components/charts/core/data.ts +95 -0
  102. package/src/components/charts/core/format.ts +64 -0
  103. package/src/components/charts/core/palette.ts +52 -0
  104. package/src/components/charts/core/uid.ts +6 -0
  105. package/src/components/charts/core/useChartAnim.ts +60 -0
  106. package/src/components/charts/core/useChartFrame.ts +49 -0
  107. package/src/components/charts/core/useScale.ts +39 -0
  108. package/src/components/charts/index.ts +12 -0
  109. package/src/components/form/inputs/RadioGroup.vue +2 -1
  110. package/src/components/form/inputs/RangeInput.vue +43 -15
  111. package/src/components/form/inputs/SelectInput.vue +1 -19
  112. package/src/components/index.ts +3 -1
  113. package/src/components/layout/Timeline.types.ts +9 -0
  114. package/src/components/layout/Timeline.vue +54 -0
  115. package/src/components/layout/TimelineItem.vue +93 -0
  116. package/src/components/layout/index.ts +3 -0
  117. package/src/dialog/Dialog.vue +29 -1
  118. package/src/styles/bagel.css +1 -0
  119. package/src/styles/gradients.css +181 -0
  120. package/src/styles/layout.css +9 -0
  121. package/src/styles/theme.css +1 -1
  122. package/dist/components/analytics/BarChart.vue.d.ts +0 -47
  123. package/dist/components/analytics/BarChart.vue.d.ts.map +0 -1
  124. package/dist/components/analytics/KpiCard.vue.d.ts +0 -24
  125. package/dist/components/analytics/KpiCard.vue.d.ts.map +0 -1
  126. package/dist/components/analytics/LineChart.vue.d.ts +0 -35
  127. package/dist/components/analytics/LineChart.vue.d.ts.map +0 -1
  128. package/dist/components/analytics/PieChart.vue.d.ts +0 -53
  129. package/dist/components/analytics/PieChart.vue.d.ts.map +0 -1
  130. package/dist/components/analytics/index.d.ts +0 -5
  131. package/dist/components/analytics/index.d.ts.map +0 -1
  132. package/src/components/analytics/BarChart.vue +0 -262
  133. package/src/components/analytics/KpiCard.vue +0 -84
  134. package/src/components/analytics/LineChart.vue +0 -357
  135. package/src/components/analytics/PieChart.vue +0 -544
  136. package/src/components/analytics/index.ts +0 -4
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Ergonomic data normalization for the chart family.
3
+ *
4
+ * Accepts the simplest thing that works and progressively enhances:
5
+ * :data="[5, 8, 6, 12]" → one series, x = index
6
+ * :data="[{ x: 'Jan', y: 5 }, …]" → one series, labelled
7
+ * :series="[{ name, color, data: number[] }]" → many series
8
+ * :labels="['Jan','Feb',…]" → shared x labels
9
+ *
10
+ * A server can return `{ labels, series }` and drop it straight in.
11
+ */
12
+ import type { ThemeType } from '../../../types'
13
+
14
+ /** A single value the way callers pass it. */
15
+ export type RawPoint = number | { x?: string | number; y: number; label?: string }
16
+
17
+ /** A caller-supplied series. */
18
+ export interface RawSeries {
19
+ name?: string
20
+ /** Tone name (`primary`, `blue`, …) or any CSS color. */
21
+ color?: ThemeType | string
22
+ data: RawPoint[]
23
+ }
24
+
25
+ /** Normalized point used internally by every chart. */
26
+ export interface Point {
27
+ /** Numeric x for scaling (index if labels are categorical). */
28
+ x: number
29
+ y: number
30
+ /** Display label for the x value. */
31
+ label: string
32
+ }
33
+
34
+ /** Normalized series used internally by every chart. */
35
+ export interface Series {
36
+ name: string
37
+ /** Resolved CSS color (already `var(--bgl-…)` or a raw color). */
38
+ color?: ThemeType | string
39
+ points: Point[]
40
+ }
41
+
42
+ export interface NormalizeOptions {
43
+ /** Shared x labels; overrides per-point labels when provided. */
44
+ labels?: (string | number)[]
45
+ /** Fallback color (tone name or CSS) for series that don't set their own.
46
+ Lets a single-series chart honor a top-level `color` prop. */
47
+ defaultColor?: ThemeType | string
48
+ }
49
+
50
+ function toPoints(data: RawPoint[], labels?: (string | number)[]): Point[] {
51
+ return data.map((d, i) => {
52
+ if (typeof d === 'number') {
53
+ return { x: i, y: d, label: String(labels?.[i] ?? i) }
54
+ }
55
+ const label = d.label ?? labels?.[i] ?? d.x ?? i
56
+ return { x: typeof d.x === 'number' ? d.x : i, y: d.y, label: String(label) }
57
+ })
58
+ }
59
+
60
+ /**
61
+ * Resolve `data` / `series` props into a uniform `Series[]`.
62
+ * Exactly one of `data` or `series` is expected; `series` wins if both given.
63
+ */
64
+ export function normalizeSeries(
65
+ data: RawPoint[] | undefined,
66
+ series: RawSeries[] | undefined,
67
+ opts: NormalizeOptions = {},
68
+ ): Series[] {
69
+ if (series?.length) {
70
+ // Only apply the chart-level default to a lone series; multi-series keep
71
+ // their automatic per-index tone sequence unless each sets its own color.
72
+ const useDefault = series.length === 1
73
+ return series.map((s, i) => ({
74
+ name: s.name ?? `Series ${i + 1}`,
75
+ color: s.color ?? (useDefault ? opts.defaultColor : undefined),
76
+ points: toPoints(s.data ?? [], opts.labels),
77
+ }))
78
+ }
79
+ if (data?.length) {
80
+ return [{ name: 'Series 1', color: opts.defaultColor, points: toPoints(data, opts.labels) }]
81
+ }
82
+ return []
83
+ }
84
+
85
+ /** Flatten all y-values across series (for domain calc). */
86
+ export function allValues(series: Series[]): number[] {
87
+ return series.flatMap(s => s.points.map(p => p.y))
88
+ }
89
+
90
+ /** The x labels of the first/longest series (charts share an x axis). */
91
+ export function sharedLabels(series: Series[]): string[] {
92
+ let longest: Point[] = []
93
+ for (const s of series) if (s.points.length > longest.length) longest = s.points
94
+ return longest.map(p => p.label)
95
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Locale-aware value formatting for charts. Uses the app i18n locale (via
3
+ * `resolveI18n`) instead of a hardcoded locale, with graceful fallbacks.
4
+ */
5
+ import { getI18n } from '../../../i18n'
6
+
7
+ function currentLocale(): string {
8
+ try {
9
+ const loc = getI18n().global.locale as unknown
10
+ const v = (loc as { value?: string })?.value ?? loc
11
+ if (typeof v === 'string' && v) return v
12
+ } catch { /* i18n not initialized — fall through */ }
13
+ if (typeof document !== 'undefined') {
14
+ return document.documentElement.lang || navigator?.language || 'en'
15
+ }
16
+ return 'en'
17
+ }
18
+
19
+ export interface ValueFormat {
20
+ /** ISO currency code (e.g. 'USD', 'ILS'). When set, formats as currency. */
21
+ currency?: string
22
+ prefix?: string
23
+ suffix?: string
24
+ /** Compact large numbers (1.2K, 3.4M). Default true for axis ticks. */
25
+ compact?: boolean
26
+ maximumFractionDigits?: number
27
+ }
28
+
29
+ export function formatValue(value: number, fmt: ValueFormat = {}): string {
30
+ const locale = currentLocale()
31
+ let out: string
32
+ try {
33
+ if (fmt.currency) {
34
+ out = new Intl.NumberFormat(locale, {
35
+ style: 'currency',
36
+ currency: fmt.currency,
37
+ notation: fmt.compact ? 'compact' : 'standard',
38
+ maximumFractionDigits: fmt.maximumFractionDigits ?? (fmt.compact ? 1 : 0),
39
+ }).format(value)
40
+ } else {
41
+ out = new Intl.NumberFormat(locale, {
42
+ notation: fmt.compact ? 'compact' : 'standard',
43
+ maximumFractionDigits: fmt.maximumFractionDigits ?? (fmt.compact ? 1 : 2),
44
+ }).format(value)
45
+ }
46
+ } catch {
47
+ out = String(value)
48
+ }
49
+ return `${fmt.prefix ?? ''}${out}${fmt.suffix ?? ''}`
50
+ }
51
+
52
+ /** Format an x label that may be an ISO date; otherwise pass through. */
53
+ export function formatLabel(label: string): string {
54
+ // Only treat as a date if it parses AND looks date-ish (has - or /).
55
+ if (/[-/]/.test(label)) {
56
+ const d = new Date(label)
57
+ if (!Number.isNaN(d.getTime())) {
58
+ try {
59
+ return d.toLocaleDateString(currentLocale(), { month: 'short', day: 'numeric' })
60
+ } catch { /* noop */ }
61
+ }
62
+ }
63
+ return label
64
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Color resolution for charts. Tone names map to theme CSS vars so charts are
3
+ * automatically theme + dark-mode aware; any raw CSS color passes through.
4
+ */
5
+ import type { ThemeType } from '../../../types'
6
+
7
+ const TONES = [
8
+ 'blue', 'green', 'red', 'yellow', 'purple', 'brown',
9
+ 'orange', 'turquoise', 'gray', 'black', 'pink', 'primary', 'white',
10
+ ]
11
+
12
+ /** A pleasant default multi-series sequence (theme tokens). */
13
+ export const SERIES_TONES: ThemeType[] = [
14
+ 'primary', 'purple', 'turquoise', 'orange', 'pink', 'green', 'blue', 'yellow', 'red',
15
+ ]
16
+
17
+ /** Is this string one of the known theme tones (incl. -light/-tint suffixes)? */
18
+ function isTone(c: string): boolean {
19
+ const base = c.replace(/-(light|tint|dark|\d+)$/, '')
20
+ return TONES.includes(base)
21
+ }
22
+
23
+ /**
24
+ * Resolve a caller color into a paintable CSS value.
25
+ * - tone name → `var(--bgl-<tone>)`
26
+ * - anything else (hex/rgb/var(...)) → returned as-is
27
+ * - undefined → falls back to the series-index tone
28
+ */
29
+ export function resolveColor(color: ThemeType | string | undefined, index = 0): string {
30
+ if (!color) return `var(--bgl-${SERIES_TONES[index % SERIES_TONES.length]})`
31
+ if (typeof color === 'string' && isTone(color)) return `var(--bgl-${color})`
32
+ return color
33
+ }
34
+
35
+ /** A translucent version of a resolved color (for area fills / hovers). */
36
+ export function alpha(cssColor: string, pct: number): string {
37
+ return `color-mix(in srgb, ${cssColor} ${pct}%, transparent)`
38
+ }
39
+
40
+ /**
41
+ * A monochrome ramp: `count` shades of one base color, from full strength to a
42
+ * lighter tint. Reads premium for donut/pie slices and stacked categories.
43
+ * `index 0` is the strongest; later slices fade toward white.
44
+ */
45
+ export function ramp(base: ThemeType | string | undefined, count: number, index = 0): string {
46
+ const color = resolveColor(base ?? 'primary')
47
+ if (count <= 1) return color
48
+ // Spread mix from 100% (strongest) down to ~40% (lightest) of the base.
49
+ const minPct = 42
50
+ const pct = 100 - (index / (count - 1)) * (100 - minPct)
51
+ return `color-mix(in srgb, ${color} ${pct}%, var(--bgl-box-bg))`
52
+ }
@@ -0,0 +1,6 @@
1
+ /** Stable per-instance id for SVG defs (gradients, clips, masks). */
2
+ let n = 0
3
+ export function chartUid(prefix = 'bglc'): string {
4
+ n += 1
5
+ return `${prefix}-${n}`
6
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Reveal animation: eases 0→1 once the chart scrolls into view. Respects
3
+ * `prefers-reduced-motion` (snaps to 1) and cleans up after itself.
4
+ */
5
+ import { onMounted, onUnmounted, ref, type Ref } from 'vue'
6
+
7
+ export interface AnimOptions {
8
+ el: Ref<HTMLElement | undefined>
9
+ enabled?: boolean
10
+ duration?: number
11
+ delay?: number
12
+ }
13
+
14
+ function easeOutCubic(t: number): number {
15
+ return 1 - (1 - t) ** 3
16
+ }
17
+
18
+ function prefersReduced(): boolean {
19
+ return typeof window !== 'undefined'
20
+ && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
21
+ }
22
+
23
+ export function useChartAnim(opts: AnimOptions) {
24
+ const progress = ref(opts.enabled === false || prefersReduced() ? 1 : 0)
25
+ let observer: IntersectionObserver | null = null
26
+ let raf = 0
27
+ let started = false
28
+
29
+ function run() {
30
+ if (started) return
31
+ started = true
32
+ const duration = opts.duration ?? 700
33
+ const start = performance.now() + (opts.delay ?? 0)
34
+ const tick = (now: number) => {
35
+ const elapsed = now - start
36
+ if (elapsed < 0) { raf = requestAnimationFrame(tick); return }
37
+ const t = Math.min(elapsed / duration, 1)
38
+ progress.value = easeOutCubic(t)
39
+ if (t < 1) raf = requestAnimationFrame(tick)
40
+ }
41
+ raf = requestAnimationFrame(tick)
42
+ }
43
+
44
+ onMounted(() => {
45
+ if (progress.value === 1) return
46
+ if (typeof IntersectionObserver === 'undefined') { run(); return }
47
+ observer = new IntersectionObserver((entries) => {
48
+ for (const e of entries) if (e.isIntersecting) { run(); observer?.disconnect() }
49
+ }, { threshold: 0.25 })
50
+ if (opts.el.value) observer.observe(opts.el.value)
51
+ else run()
52
+ })
53
+
54
+ onUnmounted(() => {
55
+ observer?.disconnect()
56
+ cancelAnimationFrame(raf)
57
+ })
58
+
59
+ return { progress }
60
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Responsive chart frame: tracks the container's width via ResizeObserver,
3
+ * exposes inner plot dimensions, and detects RTL once (charts flip x in RTL).
4
+ */
5
+ import { computed, onMounted, onUnmounted, ref, type Ref } from 'vue'
6
+
7
+ export interface Padding { top: number; right: number; bottom: number; left: number }
8
+
9
+ export interface FrameOptions {
10
+ height: Ref<number> | number
11
+ padding?: Partial<Padding>
12
+ }
13
+
14
+ const DEFAULT_PAD: Padding = { top: 8, right: 8, bottom: 22, left: 36 }
15
+
16
+ export function useChartFrame(el: Ref<HTMLElement | undefined>, opts: FrameOptions) {
17
+ const width = ref(0)
18
+ const height = computed(() => (typeof opts.height === 'number' ? opts.height : opts.height.value))
19
+ const pad = computed<Padding>(() => ({ ...DEFAULT_PAD, ...opts.padding }))
20
+
21
+ const innerWidth = computed(() => Math.max(0, width.value - pad.value.left - pad.value.right))
22
+ const innerHeight = computed(() => Math.max(0, height.value - pad.value.top - pad.value.bottom))
23
+
24
+ const isRTL = ref(false)
25
+ function detectRTL() {
26
+ if (typeof document === 'undefined') return
27
+ const node = el.value
28
+ const dir = node?.closest('[dir]')?.getAttribute('dir') || document.documentElement.dir
29
+ isRTL.value = dir === 'rtl' || document.documentElement.lang === 'he'
30
+ }
31
+
32
+ let ro: ResizeObserver | null = null
33
+ onMounted(() => {
34
+ detectRTL()
35
+ if (el.value) {
36
+ width.value = el.value.clientWidth || 0
37
+ ro = new ResizeObserver((entries) => {
38
+ for (const e of entries) width.value = e.contentRect.width
39
+ })
40
+ ro.observe(el.value)
41
+ }
42
+ })
43
+ onUnmounted(() => ro?.disconnect())
44
+
45
+ /** Map a plot-local x (0..innerWidth) to the painted x, flipping in RTL. */
46
+ const flipX = (x: number) => (isRTL.value ? innerWidth.value - x : x)
47
+
48
+ return { width, height, pad, innerWidth, innerHeight, isRTL, flipX }
49
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Minimal scale + tick helpers. No d3 — just the bits dashboards need.
3
+ */
4
+
5
+ /** "Nice" rounded domain max so axis ticks land on clean numbers. */
6
+ export function niceMax(max: number, ticks = 4): number {
7
+ if (max <= 0) return ticks
8
+ const rough = max / ticks
9
+ const mag = 10 ** Math.floor(Math.log10(rough))
10
+ const norm = rough / mag
11
+ const step = norm >= 5 ? 10 : norm >= 2 ? 5 : norm >= 1 ? 2 : 1
12
+ const niceStep = step * mag
13
+ return Math.ceil(max / niceStep) * niceStep
14
+ }
15
+
16
+ /** Evenly spaced tick values from min..max (inclusive). */
17
+ export function ticks(min: number, max: number, count = 4): number[] {
18
+ const out: number[] = []
19
+ const step = (max - min) / count
20
+ for (let i = 0; i <= count; i++) out.push(min + step * i)
21
+ return out
22
+ }
23
+
24
+ /** Linear scale factory: maps a value in [d0,d1] → pixel in [r0,r1]. */
25
+ export function linear(d0: number, d1: number, r0: number, r1: number) {
26
+ const span = d1 - d0 || 1
27
+ return (v: number) => r0 + ((v - d0) / span) * (r1 - r0)
28
+ }
29
+
30
+ /** Band scale for categorical x: returns center x + band width. */
31
+ export function band(count: number, range: number, padding = 0.2) {
32
+ const step = range / Math.max(1, count)
33
+ const bandWidth = step * (1 - padding)
34
+ return {
35
+ bandWidth,
36
+ center: (i: number) => step * i + step / 2,
37
+ start: (i: number) => step * i + (step - bandWidth) / 2,
38
+ }
39
+ }
@@ -0,0 +1,12 @@
1
+ export { default as Sparkline } from './Sparkline.vue'
2
+ export { default as LineChart } from './LineChart.vue'
3
+ export { default as BarChart } from './BarChart.vue'
4
+ export { default as Donut } from './Donut.vue'
5
+ export { default as Funnel } from './Funnel.vue'
6
+ export { default as Gauge } from './Gauge.vue'
7
+ export { default as RadialBars } from './RadialBars.vue'
8
+ export { default as RankBars } from './RankBars.vue'
9
+ export { default as StatCard } from './StatCard.vue'
10
+
11
+ export type { RawPoint, RawSeries, NormalizeOptions } from './core/data'
12
+ export type { ValueFormat } from './core/format'
@@ -42,6 +42,7 @@ const props = withDefaults(
42
42
  borderColor?: string
43
43
  textColor?: string
44
44
  textAlign?: 'left' | 'center' | 'right'
45
+ wrapperClass?: string
45
46
  } & BagelInputShellProps>(),
46
47
  {
47
48
  align: 'center'
@@ -112,7 +113,7 @@ function handleChange() {
112
113
  <p v-if="label" class="group-label">
113
114
  {{ resolveI18n(label) }} <span v-if="required">*</span>
114
115
  </p>
115
- <div class="radio-group-wrap">
116
+ <div class="radio-group-wrap" :class="wrapperClass">
116
117
  <label v-for="(opt, index) in visibleOptions" :key="opt.id || `${name}-${index}`" class="border rounded flex active-list-item hover mb-05" :for="opt.id || `${name}-${index}`"
117
118
  :class="{ 'p-05 gap-025': thin, 'py-1 gap-075': !thin, 'ps-05': !hideRadio, 'bg-gray-light': !bgColor && !flat, 'align-items-start': align === 'start' || align === 'top', 'align-items-center': align === 'center', 'align-items-end': align === 'end' || align === 'bottom', invertedActive }"
118
119
  :style="{ backgroundColor: bgColor, borderColor }">
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { resolveI18n } from '@bagelink/vue'
3
- import { computed, ref, watch } from 'vue'
3
+ import { computed, onMounted, ref, watch } from 'vue'
4
4
  import type { BagelInputShellProps } from './bagelInputShell'
5
5
 
6
6
  export interface RangeInputProps extends BagelInputShellProps {
@@ -12,13 +12,18 @@ export interface RangeInputProps extends BagelInputShellProps {
12
12
  label?: string
13
13
  disabled?: boolean
14
14
  id?: string
15
+ /** Force direction. Omit to inherit the page/parent `dir` (recommended). */
15
16
  rtl?: boolean
16
17
  multiRange?: boolean
17
18
  formatValue?: (value: number) => string
18
- /** צבע הרקע של הפס (track) */
19
+ /** Track (background) color. */
19
20
  trackColor?: string
20
- /** צבע הפס הפעיל והעיגולברירת מחדל: labelActiveColor או primary */
21
+ /** Active range + thumb color defaults to labelActiveColor or primary. */
21
22
  activeColor?: string
23
+ /** Slim variant for bare sliders / media scrubbers (smaller thumb + track). */
24
+ size?: 'sm' | 'md'
25
+ /** Hide the min/max footer labels (e.g. for a media scrubber). */
26
+ hideMinMax?: boolean
22
27
  }
23
28
 
24
29
  const props = defineProps<RangeInputProps>()
@@ -32,6 +37,17 @@ const {
32
37
  formatValue = (value: number) => value.toString()
33
38
  } = props
34
39
 
40
+ // Resolve direction: explicit `rtl` prop wins; otherwise inherit the element's
41
+ // computed `dir` so a slider in an RTL page fills from the start edge natively.
42
+ const rootEl = ref<HTMLElement | null>(null)
43
+ const inheritedRtl = ref(false)
44
+ onMounted(() => {
45
+ if (props.rtl === undefined && rootEl.value) {
46
+ inheritedRtl.value = getComputedStyle(rootEl.value).direction === 'rtl'
47
+ }
48
+ })
49
+ const isRtl = computed(() => props.rtl ?? inheritedRtl.value)
50
+
35
51
  const from = ref<number>(Array.isArray(props.modelValue) ? props.modelValue[0] : (props.modelValue ?? min))
36
52
  const to = ref<number>(Array.isArray(props.modelValue) ? props.modelValue[1] : max)
37
53
 
@@ -60,16 +76,16 @@ function handleInput(value: number, isFromInput: boolean) {
60
76
  emit('update:modelValue', multiRange ? [validFrom.value, validTo.value] : validFrom.value)
61
77
  }
62
78
 
79
+ // Position the active range with logical properties so the browser handles RTL.
63
80
  const rangeStyle = computed(() => {
64
81
  if (multiRange) {
65
82
  return {
66
- left: `${fromPercentage.value}%`,
83
+ insetInlineStart: `${fromPercentage.value}%`,
67
84
  width: `${toPercentage.value - fromPercentage.value}%`
68
85
  }
69
86
  }
70
- return props.rtl
71
- ? { left: `${100 - fromPercentage.value}%`, width: `${fromPercentage.value}%` }
72
- : { left: '0', width: `${fromPercentage.value}%` }
87
+ // Single value: always fills from the inline-start edge (start = min).
88
+ return { insetInlineStart: '0', width: `${fromPercentage.value}%` }
73
89
  })
74
90
 
75
91
  const displayFrom = computed(() => formatValue(validFrom.value))
@@ -78,11 +94,14 @@ const displayTo = computed(() => formatValue(validTo.value))
78
94
 
79
95
  <template>
80
96
  <div
81
- :dir="rtl ? 'rtl' : 'ltr'"
97
+ ref="rootEl"
98
+ :dir="rtl === undefined ? undefined : (rtl ? 'rtl' : 'ltr')"
99
+ :class="{ 'range-sm': size === 'sm' }"
82
100
  :style="[
83
101
  minWidth ? { minWidth } : {},
84
102
  maxWidth ? { maxWidth } : {},
85
103
  activeColor ? { '--bgl-range-thumb-color': activeColor } : {},
104
+ activeColor ? { '--bgl-input-label-active-color': activeColor } : {},
86
105
  labelActiveColor ? { '--bgl-input-label-active-color': labelActiveColor } : {},
87
106
  ]"
88
107
  >
@@ -145,7 +164,7 @@ const displayTo = computed(() => formatValue(validTo.value))
145
164
  </slot>
146
165
  </p>
147
166
  </div>
148
- <p class="txt-center txt-14 user-select-none range-slider-txt flex space-between opacity-4 mx-05">
167
+ <p v-if="!hideMinMax" class="txt-center txt-14 user-select-none range-slider-txt flex space-between opacity-4 mx-05">
149
168
  <slot name="min" :max="formatValue(max)" :min="formatValue(min)" :from="displayFrom" :to="displayTo" :progress="displayFrom">
150
169
  <span>{{ formatValue(min) }}</span>
151
170
  </slot>
@@ -160,23 +179,33 @@ const displayTo = computed(() => formatValue(validTo.value))
160
179
 
161
180
  .range-slider-position-txt{
162
181
  margin-inline-start: calc((var(--progress) * 1%) - (var(--bgl-range-thumb-size) * var(--progress) / 100));
163
- top: calc(var(--bgl-range-thumb-size) / 2 ) ;
164
- transition: transform 0.1s, opacity 0.5s, top 0.5s;
182
+ /* Float the value bubble ABOVE the thumb so the thumb never covers it. */
183
+ bottom: calc(100% + 0.25rem);
184
+ transition: transform 0.1s, opacity 0.3s, bottom 0.3s;
165
185
  transform: scale(0.8);
186
+ transform-origin: bottom center;
187
+ opacity: 0;
166
188
  width: var(--bgl-range-thumb-size);
167
189
  }
168
- .range-slider:hover .range-slider-position-txt{
190
+ .range-slider:hover .range-slider-position-txt,
191
+ .range-slider:focus-within .range-slider-position-txt{
169
192
  opacity: 1;
170
193
  transform: scale(1);
171
- top: calc(var(--bgl-range-thumb-size) / 2);
172
194
  }
173
195
 
174
196
  .range-slider {
175
- height: var(--bgl-range-track-size);
197
+ height: var(--bgl-range-thumb-size);
176
198
  display: flex;
177
199
  align-items: center;
178
200
  margin-top: calc(var(--bgl-range-thumb-size) / 2 + 0.5rem) ;
179
201
  }
202
+
203
+ /* Slim variant — smaller thumb + thinner track for bare sliders / scrubbers. */
204
+ .range-sm {
205
+ --bgl-range-thumb-size: 14px;
206
+ --bgl-range-track-height: 4px;
207
+ }
208
+ .range-sm .range-slider { margin-top: 0; }
180
209
  .range-slider-txt{
181
210
  margin-top: calc(var(--bgl-range-thumb-size) / 2) !important;
182
211
  }
@@ -230,7 +259,6 @@ cursor: grabbing;
230
259
  .range-slider-position-txt{
231
260
  opacity: 1;
232
261
  transform: scale(1);
233
- top: calc(var(--bgl-range-thumb-size) / 1.8) ;
234
262
  }
235
263
  }
236
264
 
@@ -303,10 +303,7 @@ onMounted(() => {
303
303
  <div v-if="clearable && selectedItemCount > 0" class="ms-auto ps-05 me-05">
304
304
  <Btn flat thin icon="clear" class="color-gray" @click="selectedItems = []; emitUpdate()" />
305
305
  </div>
306
- <Transition v-if="!disabled" name="icon-swap" mode="out-in">
307
- <Icon v-if="open" key="open" thin :icon="underlined ? 'expand_less' : 'unfold_less'" />
308
- <Icon v-else key="closed" thin :icon="underlined ? 'expand_more' : 'unfold_more'" />
309
- </Transition>
306
+ <Icon v-if="!disabled" thin transition="rotate" :icon="open ? (underlined ? 'expand_less' : 'unfold_less') : (underlined ? 'expand_more' : 'unfold_more')" />
310
307
  </button>
311
308
  <input
312
309
  v-if="required && !underlined" tabindex="-1"
@@ -393,21 +390,6 @@ background: var(--bgl-gray-tint);
393
390
  opacity: 0.3;
394
391
  }
395
392
 
396
- .icon-swap-enter-active,
397
- .icon-swap-leave-active {
398
- transition: opacity 0.12s ease, transform 0.12s ease;
399
- }
400
-
401
- .icon-swap-enter-from {
402
- opacity: 0;
403
- transform: translateY(-4px);
404
- }
405
-
406
- .icon-swap-leave-to {
407
- opacity: 0;
408
- transform: translateY(4px);
409
- }
410
-
411
393
  .selected {
412
394
  color: var(--bgl-input-label-active-color, var(--bgl-primary));
413
395
  background: var(--bgl-selected);
@@ -3,7 +3,8 @@ export { default as AccordionItem } from './AccordionItem.vue'
3
3
  export { default as AddressSearch } from './AddressSearch.vue'
4
4
  export { default as AddToCalendar } from './AddToCalendar.vue'
5
5
  export { default as Alert } from './Alert.vue'
6
- export * from './analytics'
6
+ export { BarChart, Donut, Funnel, Gauge, LineChart, RadialBars, RankBars, Sparkline, StatCard } from './charts'
7
+ export type { NormalizeOptions, RawPoint, RawSeries, ValueFormat } from './charts'
7
8
  export { default as Avatar } from './Avatar.vue'
8
9
  export { default as Badge } from './Badge.vue'
9
10
  /** @deprecated Renamed to Badge. Pill is an alias that will be removed in a future version. */
@@ -19,6 +20,7 @@ export { default as TableSchema } from './dataTable/DataTable.vue'
19
20
  export { Draggable, useDraggable, vDraggable } from './draggable'
20
21
  export { default as DragOver } from './DragOver.vue'
21
22
  export { default as Dropdown } from './Dropdown.vue'
23
+ export { default as EmptyState } from './EmptyState.vue'
22
24
  export { default as FieldSetVue } from './FieldSetVue.vue'
23
25
  export { default as FilterQuery } from './FilterQuery.vue'
24
26
  export type { FilterField, QueryOption } from './FilterQuery.types'
@@ -0,0 +1,9 @@
1
+ import type { IconType, ThemeType } from '@bagelink/vue'
2
+
3
+ export interface TimelineEntry {
4
+ title?: string
5
+ meta?: string
6
+ icon?: IconType
7
+ color?: ThemeType
8
+ [key: string]: any
9
+ }
@@ -0,0 +1,54 @@
1
+ <script lang="ts" setup>
2
+ defineOptions({ name: 'BglTimeline' })
3
+ import { useSlots } from 'vue'
4
+ import type { TimelineEntry } from './Timeline.types'
5
+ import TimelineItem from './TimelineItem.vue'
6
+
7
+ /**
8
+ * Vertical activity / history feed. Two ways to use it:
9
+ *
10
+ * 1. Data-driven — pass `items`; each maps to a <TimelineItem>. Use the default
11
+ * scoped slot to render custom body content per entry:
12
+ * <Timeline :items="activity">
13
+ * <template #default="{ item }">{{ item.title }}</template>
14
+ * </Timeline>
15
+ *
16
+ * 2. Composition — drop <TimelineItem> children directly (omit `items`). Mark the
17
+ * last one with `last` to drop its connector, or let data-driven mode do it.
18
+ */
19
+ const { items = [] } = defineProps<{
20
+ items?: TimelineEntry[]
21
+ }>()
22
+
23
+ const slots = useSlots()
24
+ </script>
25
+
26
+ <template>
27
+ <div class="bgl-timeline grid">
28
+ <template v-if="items.length">
29
+ <TimelineItem
30
+ v-for="(item, i) in items"
31
+ :key="i"
32
+ :title="item.title"
33
+ :meta="item.meta"
34
+ :icon="item.icon"
35
+ :color="item.color"
36
+ :last="i === items.length - 1"
37
+ >
38
+ <template v-if="slots.default" #default>
39
+ <slot :item="item" :index="i" />
40
+ </template>
41
+ <template v-if="slots.meta" #meta>
42
+ <slot name="meta" :item="item" :index="i" />
43
+ </template>
44
+ </TimelineItem>
45
+ </template>
46
+ <slot v-else name="items" />
47
+ </div>
48
+ </template>
49
+
50
+ <style scoped>
51
+ .bgl-timeline {
52
+ align-content: start;
53
+ }
54
+ </style>