adata-ui 2.1.40-beta → 2.1.40-beta.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.
@@ -0,0 +1,84 @@
1
+ // Active-state detection for the products mega-menu.
2
+ //
3
+ // Two levels of "active":
4
+ // 1. activeModule — the current host matches one of the module's hosts
5
+ // (host ≠ module key: e.g. the `fines` module lives on avto.*.kz,
6
+ // and `tenders` spans both zakupki.*.kz and tender.*.kz).
7
+ // 2. activeService — within the active module, the current page matches a
8
+ // concrete item link (`/`, `/unload`, `/foreign`, …).
9
+
10
+ const LOCALE_SEGMENTS = new Set(['kk', 'en'])
11
+
12
+ interface NavTarget { to: string }
13
+ interface NavModule { link: string, items: NavTarget[] }
14
+ interface NormalizedUrl { host: string, key: string }
15
+
16
+ function safeUrl(href: string): URL | null {
17
+ try {
18
+ return new URL(href)
19
+ }
20
+ catch {
21
+ return null
22
+ }
23
+ }
24
+
25
+ // Reduce a URL to a comparable identity: host + locale-stripped path + query.
26
+ // The query is kept because it can be significant (fines main is `/?check_fines=true`).
27
+ function normalize(href: string): NormalizedUrl | null {
28
+ const url = safeUrl(href)
29
+ if (!url) return null
30
+
31
+ const segments = url.pathname
32
+ .split('/')
33
+ .filter(segment => segment && !LOCALE_SEGMENTS.has(segment))
34
+
35
+ const path = `/${segments.join('/')}`
36
+ return { host: url.host, key: path + url.search }
37
+ }
38
+
39
+ // A "root" target has no path segments (e.g. `/` or `/?check_fines=true`).
40
+ // Roots must match exactly, otherwise their prefix would light up every sub-page.
41
+ function isRoot(key: string): boolean {
42
+ return key === '/' || key.startsWith('/?')
43
+ }
44
+
45
+ export function useActiveNavigation() {
46
+ const route = useRoute()
47
+
48
+ // Recompute on every SPA navigation. Host stays constant within an app, so we
49
+ // read it from the request URL (SSR-safe); the path/query come from the route.
50
+ const current = computed<NormalizedUrl | null>(() => {
51
+ const host = useRequestURL().host
52
+ return normalize(`https://${host}${route.fullPath}`)
53
+ })
54
+
55
+ function isActiveModule(module: NavModule): boolean {
56
+ if (!current.value) return false
57
+
58
+ const hosts = new Set<string>()
59
+ const link = normalize(module.link)
60
+ if (link) hosts.add(link.host)
61
+ for (const item of module.items) {
62
+ const item_url = normalize(item.to)
63
+ if (item_url) hosts.add(item_url.host)
64
+ }
65
+
66
+ return hosts.has(current.value.host)
67
+ }
68
+
69
+ function isActiveService(itemTo: string): boolean {
70
+ const cur = current.value
71
+ const item = normalize(itemTo)
72
+ if (!cur || !item || cur.host !== item.host) return false
73
+
74
+ // The car-check page redirects to a `car-result` URL; keep its item lit.
75
+ if (item.key.includes('check-car') && cur.key.includes('car-result')) return true
76
+
77
+ if (cur.key === item.key) return true
78
+ if (isRoot(item.key)) return false
79
+
80
+ return cur.key.startsWith(item.key)
81
+ }
82
+
83
+ return { isActiveModule, isActiveService }
84
+ }
@@ -0,0 +1,82 @@
1
+ import type { ComputedRef, Ref } from 'vue'
2
+
3
+ interface UseChipOverflowOptions {
4
+ container: Ref<HTMLElement | null>
5
+ measure: Ref<HTMLElement | null>
6
+ count: Ref<number>
7
+ gap?: number
8
+ }
9
+
10
+ interface UseChipOverflow {
11
+ visibleCount: Ref<number>
12
+ hiddenCount: ComputedRef<number>
13
+ }
14
+
15
+ export function useChipOverflow(options: UseChipOverflowOptions): UseChipOverflow {
16
+ const { container, measure, count, gap = 6 } = options
17
+
18
+ const availableWidth = ref(0)
19
+ const visibleCount = ref(0)
20
+
21
+ function recompute() {
22
+ const total = count.value
23
+ if (total <= 0) {
24
+ visibleCount.value = 0
25
+ return
26
+ }
27
+
28
+ const avail = availableWidth.value
29
+ const root = measure.value
30
+
31
+ if (!avail || !root) {
32
+ visibleCount.value = total
33
+ return
34
+ }
35
+
36
+ const chips = (Array.from(root.children) as HTMLElement[]).slice(0, total)
37
+
38
+ let used = 0
39
+ let fit = 0
40
+ for (let i = 0; i < chips.length; i++) {
41
+ const width = chips[i].offsetWidth + (i > 0 ? gap : 0)
42
+ if (used + width > avail) break
43
+ used += width
44
+ fit++
45
+ }
46
+
47
+ if (fit >= total) {
48
+ visibleCount.value = total
49
+ return
50
+ }
51
+
52
+ const badge = root.children[total] as HTMLElement | undefined
53
+ const badgeWidth = (badge?.offsetWidth ?? 0) + gap
54
+ while (fit > 1 && used + badgeWidth > avail) {
55
+ used -= chips[fit - 1].offsetWidth + gap
56
+ fit--
57
+ }
58
+
59
+ visibleCount.value = Math.max(1, fit)
60
+ }
61
+
62
+ let resizeObserver: ResizeObserver | null = null
63
+
64
+ onMounted(() => {
65
+ if (container.value) {
66
+ availableWidth.value = container.value.clientWidth
67
+ resizeObserver = new ResizeObserver((entries) => {
68
+ availableWidth.value = entries[0]?.contentRect.width ?? 0
69
+ })
70
+ resizeObserver.observe(container.value)
71
+ }
72
+ recompute()
73
+ })
74
+
75
+ onUnmounted(() => resizeObserver?.disconnect())
76
+
77
+ watch([availableWidth, count], () => nextTick(recompute))
78
+
79
+ const hiddenCount = computed(() => Math.max(0, count.value - visibleCount.value))
80
+
81
+ return { visibleCount, hiddenCount }
82
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adata-ui",
3
3
  "type": "module",
4
- "version": "2.1.40-beta",
4
+ "version": "2.1.40-beta.2",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground",