@treeui/vue 0.11.0 → 0.13.0

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 (56) hide show
  1. package/README.md +5 -1
  2. package/dist/components/TAppShell.cjs +2 -0
  3. package/dist/components/TAppShell.cjs.map +1 -0
  4. package/dist/components/TAppShell.js +7 -0
  5. package/dist/components/TAppShell.js.map +1 -0
  6. package/dist/components/TAppShell.vue.d.ts +90 -0
  7. package/dist/components/TAppShell.vue.d.ts.map +1 -0
  8. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.cjs +2 -0
  9. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.cjs.map +1 -0
  10. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.js +244 -0
  11. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.js.map +1 -0
  12. package/dist/components/TChart.cjs +2 -0
  13. package/dist/components/TChart.cjs.map +1 -0
  14. package/dist/components/TChart.js +7 -0
  15. package/dist/components/TChart.js.map +1 -0
  16. package/dist/components/TChart.vue.d.ts +123 -0
  17. package/dist/components/TChart.vue.d.ts.map +1 -0
  18. package/dist/components/TChart.vue_vue_type_script_setup_true_lang.cjs +2 -0
  19. package/dist/components/TChart.vue_vue_type_script_setup_true_lang.cjs.map +1 -0
  20. package/dist/components/TChart.vue_vue_type_script_setup_true_lang.js +370 -0
  21. package/dist/components/TChart.vue_vue_type_script_setup_true_lang.js.map +1 -0
  22. package/dist/components/TDonutChart.cjs +2 -0
  23. package/dist/components/TDonutChart.cjs.map +1 -0
  24. package/dist/components/TDonutChart.js +7 -0
  25. package/dist/components/TDonutChart.js.map +1 -0
  26. package/dist/components/TDonutChart.vue.d.ts +75 -0
  27. package/dist/components/TDonutChart.vue.d.ts.map +1 -0
  28. package/dist/components/TDonutChart.vue_vue_type_script_setup_true_lang.cjs +2 -0
  29. package/dist/components/TDonutChart.vue_vue_type_script_setup_true_lang.cjs.map +1 -0
  30. package/dist/components/TDonutChart.vue_vue_type_script_setup_true_lang.js +168 -0
  31. package/dist/components/TDonutChart.vue_vue_type_script_setup_true_lang.js.map +1 -0
  32. package/dist/components/TGrid.vue.d.ts +1 -1
  33. package/dist/components/TGrid.vue.d.ts.map +1 -1
  34. package/dist/components/TSparkline.cjs +2 -0
  35. package/dist/components/TSparkline.cjs.map +1 -0
  36. package/dist/components/TSparkline.js +7 -0
  37. package/dist/components/TSparkline.js.map +1 -0
  38. package/dist/components/TSparkline.vue.d.ts +40 -0
  39. package/dist/components/TSparkline.vue.d.ts.map +1 -0
  40. package/dist/components/TSparkline.vue_vue_type_script_setup_true_lang.cjs +2 -0
  41. package/dist/components/TSparkline.vue_vue_type_script_setup_true_lang.cjs.map +1 -0
  42. package/dist/components/TSparkline.vue_vue_type_script_setup_true_lang.js +107 -0
  43. package/dist/components/TSparkline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  44. package/dist/components/TStack.vue.d.ts +1 -1
  45. package/dist/components/TStack.vue.d.ts.map +1 -1
  46. package/dist/components/index.d.ts +7 -0
  47. package/dist/components/index.d.ts.map +1 -1
  48. package/dist/index.cjs +1 -1
  49. package/dist/index.js +39 -35
  50. package/dist/plugin.cjs +1 -1
  51. package/dist/plugin.cjs.map +1 -1
  52. package/dist/plugin.d.ts.map +1 -1
  53. package/dist/plugin.js +128 -120
  54. package/dist/plugin.js.map +1 -1
  55. package/dist/style.css +1 -1
  56. package/package.json +4 -4
package/README.md CHANGED
@@ -55,6 +55,10 @@ import '@treeui/vue/style.css'
55
55
 
56
56
  `TBadge` · `TAlert` · `TAvatar` · `TEmptyState` · `TTag` · `TTimeline` · `TSpinner` · `TProgress` · `TSkeleton` · `TStat`
57
57
 
58
+ ### Charts
59
+
60
+ `TChart` (line / area / bar) · `TSparkline` · `TDonutChart` — a lightweight, dependency-free data-viz set sharing a framework-agnostic geometry engine in `@treeui/utils` and the `--tree-color-chart-*` palette.
61
+
58
62
  ### Overlay & Interaction
59
63
 
60
64
  `TButton` · `TModal` · `TConfirmDialog` · `TDrawer` · `TDropdown` · `TPopover` · `TTooltip` · `TContextMenu` · `TPagination` · `TToast` · `TToastProvider`
@@ -124,7 +128,7 @@ Some repeated app UI intentionally stays documented as composition guidance inst
124
128
  - Color fields: `TFormField` + native `input[type="color"]`
125
129
  - Tag inputs: `TFormField` + `TInput` + removable `TTag`
126
130
  - Router-backed tabs: `TTabs` + app route state
127
- - Charts: `TCard` + `TStack` + your preferred chart library
131
+ - Charts: `TChart` / `TSparkline` / `TDonutChart` (native, no chart library) — frame them in `TCard` + `TStack` when a titled surface is wanted
128
132
 
129
133
  ## TypeScript
130
134
 
@@ -0,0 +1,2 @@
1
+ var e=require("./TAppShell.vue_vue_type_script_setup_true_lang.cjs").default;exports.default=e;
2
+ //# sourceMappingURL=TAppShell.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TAppShell.cjs","names":[],"sources":["../../src/components/TAppShell.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n createId,\n focusFirst,\n focusLast,\n getFocusableElements,\n isEscapeKey,\n} from '@treeui/utils';\nimport { getTreeIcon, treeIconDefaults } from '@treeui/icons';\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n provide,\n ref,\n toRef,\n useAttrs,\n watch,\n} from 'vue';\nimport { useControllableOpen } from '../composables/useControllableOpen';\nimport type { TSidebarSide } from './TSidebar.vue';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nconst CloseIcon = getTreeIcon('x');\n\nconst props = withDefaults(\n defineProps<{\n as?: string;\n side?: TSidebarSide;\n sidebarWidth?: string;\n collapsedWidth?: string;\n collapsible?: boolean;\n collapsed?: boolean;\n defaultCollapsed?: boolean;\n mobile?: boolean;\n breakpoint?: string;\n sidebarOpen?: boolean;\n defaultSidebarOpen?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n }>(),\n {\n as: 'div',\n side: 'left',\n sidebarWidth: '17.5rem',\n collapsedWidth: '4.75rem',\n collapsible: false,\n collapsed: undefined,\n defaultCollapsed: false,\n mobile: undefined,\n breakpoint: '768px',\n sidebarOpen: undefined,\n defaultSidebarOpen: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n },\n);\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean];\n 'collapse-change': [value: boolean];\n 'update:sidebarOpen': [value: boolean];\n 'sidebar-open-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n default?: (props: Pick<AppShellSlotProps, 'mobile' | 'collapsed'>) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst sidebarId = createId('t-app-shell-sidebar');\n\nconst { value: isCollapsed, setValue: setCollapsed } = useControllableOpen(\n toRef(props, 'collapsed'),\n props.defaultCollapsed,\n (value) => {\n emit('update:collapsed', value);\n emit('collapse-change', value);\n },\n);\n\nconst { value: isSidebarOpen, setValue: setSidebarOpen } = useControllableOpen(\n toRef(props, 'sidebarOpen'),\n props.defaultSidebarOpen,\n (value) => {\n emit('update:sidebarOpen', value);\n emit('sidebar-open-change', value);\n },\n);\n\n// Auto responsive detection when `mobile` is not explicitly controlled.\nconst autoMobile = ref(false);\nlet mediaQuery: MediaQueryList | null = null;\n\nconst onMediaChange = (event: MediaQueryListEvent | MediaQueryList) => {\n autoMobile.value = event.matches;\n};\n\nconst setupMediaQuery = () => {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return;\n }\n\n teardownMediaQuery();\n mediaQuery = window.matchMedia(`(max-width: ${props.breakpoint})`);\n autoMobile.value = mediaQuery.matches;\n mediaQuery.addEventListener?.('change', onMediaChange);\n};\n\nconst teardownMediaQuery = () => {\n mediaQuery?.removeEventListener?.('change', onMediaChange);\n mediaQuery = null;\n};\n\nconst isMobile = computed(() => props.mobile ?? autoMobile.value);\n\n// Collapse is a desktop-only concept; the mobile drawer always shows the\n// expanded sidebar, so descendants and slots see `collapsed: false` there.\nconst effectiveCollapsed = computed(() =>\n isMobile.value ? false : isCollapsed.value,\n);\n\nconst surfaceRef = ref<HTMLElement | null>(null);\nconst previousFocusedElement = ref<HTMLElement | null>(null);\nlet previousBodyOverflow = '';\nlet bodyLocked = false;\n\nconst lockBodyScroll = () => {\n if (typeof document === 'undefined' || bodyLocked) {\n return;\n }\n\n previousBodyOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n bodyLocked = true;\n};\n\nconst unlockBodyScroll = () => {\n if (typeof document === 'undefined' || !bodyLocked) {\n return;\n }\n\n document.body.style.overflow = previousBodyOverflow;\n bodyLocked = false;\n};\n\nconst focusSurface = () => {\n nextTick(() => {\n if (!surfaceRef.value) {\n return;\n }\n\n const focused = focusFirst(surfaceRef.value);\n if (!focused) {\n surfaceRef.value.focus();\n }\n });\n};\n\nconst openSidebar = () => {\n previousFocusedElement.value =\n typeof document === 'undefined'\n ? null\n : (document.activeElement as HTMLElement | null);\n setSidebarOpen(true);\n};\n\nconst closeSidebar = () => {\n setSidebarOpen(false);\n};\n\nconst toggleSidebar = () => {\n if (isSidebarOpen.value) {\n closeSidebar();\n } else {\n openSidebar();\n }\n};\n\nconst toggleCollapsed = () => {\n if (!props.collapsible) {\n return;\n }\n\n setCollapsed(!isCollapsed.value);\n};\n\nconst onOverlayClick = () => {\n if (props.closeOnOverlay) {\n closeSidebar();\n }\n};\n\nconst onKeydown = (event: KeyboardEvent) => {\n if (isEscapeKey(event) && props.closeOnEscape) {\n event.preventDefault();\n closeSidebar();\n return;\n }\n\n if (event.key !== 'Tab' || !surfaceRef.value) {\n return;\n }\n\n const focusable = getFocusableElements(surfaceRef.value);\n\n if (!focusable.length) {\n event.preventDefault();\n surfaceRef.value.focus();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const activeElement = document.activeElement as HTMLElement | null;\n\n if (event.shiftKey && activeElement === first) {\n event.preventDefault();\n focusLast(surfaceRef.value);\n }\n\n if (!event.shiftKey && activeElement === last) {\n event.preventDefault();\n focusFirst(surfaceRef.value);\n }\n};\n\n// The off-canvas drawer only exists in mobile mode; keep body-scroll lock and\n// focus handling in sync with both the open flag and the responsive mode.\nconst syncOverlayState = (open: boolean, wasOpen: boolean) => {\n if (open && isMobile.value) {\n lockBodyScroll();\n focusSurface();\n return;\n }\n\n if (wasOpen) {\n unlockBodyScroll();\n nextTick(() => {\n previousFocusedElement.value?.focus();\n });\n }\n};\n\nwatch(isSidebarOpen, (value, previousValue) => {\n syncOverlayState(value, Boolean(previousValue));\n});\n\nwatch(isMobile, (mobile) => {\n if (!isSidebarOpen.value) {\n return;\n }\n\n // Leaving mobile removes the drawer from the DOM, so release the lock and\n // restore focus; re-entering mobile while \"open\" re-arms the overlay.\n if (mobile) {\n lockBodyScroll();\n focusSurface();\n } else {\n unlockBodyScroll();\n }\n});\n\nwatch(\n () => props.breakpoint,\n () => {\n if (typeof window !== 'undefined') {\n setupMediaQuery();\n }\n },\n);\n\nonMounted(() => {\n setupMediaQuery();\n});\n\nonBeforeUnmount(() => {\n teardownMediaQuery();\n unlockBodyScroll();\n});\n\nprovide(treeSidebarInjectionKey, {\n collapsed: effectiveCollapsed,\n});\n\nconst rootClasses = computed(() => [\n 't-app-shell',\n `t-app-shell--${props.side}`,\n {\n 'is-mobile': isMobile.value,\n 'is-collapsed':\n !isMobile.value && props.collapsible && isCollapsed.value,\n 'is-sidebar-open': isMobile.value && isSidebarOpen.value,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => [\n {\n '--tree-app-shell-sidebar-width': props.sidebarWidth,\n '--tree-app-shell-collapsed-width': props.collapsedWidth,\n },\n attrs.style,\n]);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst slotProps = computed<AppShellSlotProps>(() => ({\n mobile: isMobile.value,\n collapsed: effectiveCollapsed.value,\n sidebarOpen: isSidebarOpen.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n}));\n</script>\n\n<template>\n <component\n :is=\"as\"\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n :data-mobile=\"isMobile ? 'true' : 'false'\"\n >\n <header class=\"t-app-shell__header\">\n <button\n v-if=\"isMobile && showMenuButton\"\n type=\"button\"\n class=\"t-app-shell__menu-button\"\n :aria-label=\"menuLabel\"\n :aria-expanded=\"isSidebarOpen\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleSidebar\"\n >\n <slot\n name=\"menu-icon\"\n :sidebar-open=\"isSidebarOpen\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"6\"\n y2=\"6\"\n />\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"12\"\n y2=\"12\"\n />\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"18\"\n y2=\"18\"\n />\n </svg>\n </slot>\n </button>\n\n <div class=\"t-app-shell__header-content\">\n <slot\n name=\"header\"\n v-bind=\"slotProps\"\n />\n </div>\n </header>\n\n <aside\n v-if=\"!isMobile\"\n :id=\"sidebarId\"\n class=\"t-app-shell__sidebar\"\n :aria-label=\"sidebarLabel\"\n >\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen\"\n class=\"t-app-shell__overlay\"\n >\n <div\n class=\"t-app-shell__backdrop\"\n aria-hidden=\"true\"\n @click=\"onOverlayClick\"\n />\n <aside\n :id=\"sidebarId\"\n ref=\"surfaceRef\"\n class=\"t-app-shell__drawer\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"sidebarLabel\"\n tabindex=\"-1\"\n @keydown=\"onKeydown\"\n >\n <div class=\"t-app-shell__drawer-topbar\">\n <button\n type=\"button\"\n class=\"t-app-shell__drawer-close\"\n :aria-label=\"closeLabel\"\n @click=\"closeSidebar\"\n >\n <CloseIcon v-bind=\"treeIconDefaults\" />\n </button>\n </div>\n <div class=\"t-app-shell__drawer-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import e from "./TAppShell.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/TAppShell.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
6
+
7
+ //# sourceMappingURL=TAppShell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TAppShell.js","names":[],"sources":["../../src/components/TAppShell.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n createId,\n focusFirst,\n focusLast,\n getFocusableElements,\n isEscapeKey,\n} from '@treeui/utils';\nimport { getTreeIcon, treeIconDefaults } from '@treeui/icons';\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n provide,\n ref,\n toRef,\n useAttrs,\n watch,\n} from 'vue';\nimport { useControllableOpen } from '../composables/useControllableOpen';\nimport type { TSidebarSide } from './TSidebar.vue';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nconst CloseIcon = getTreeIcon('x');\n\nconst props = withDefaults(\n defineProps<{\n as?: string;\n side?: TSidebarSide;\n sidebarWidth?: string;\n collapsedWidth?: string;\n collapsible?: boolean;\n collapsed?: boolean;\n defaultCollapsed?: boolean;\n mobile?: boolean;\n breakpoint?: string;\n sidebarOpen?: boolean;\n defaultSidebarOpen?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n }>(),\n {\n as: 'div',\n side: 'left',\n sidebarWidth: '17.5rem',\n collapsedWidth: '4.75rem',\n collapsible: false,\n collapsed: undefined,\n defaultCollapsed: false,\n mobile: undefined,\n breakpoint: '768px',\n sidebarOpen: undefined,\n defaultSidebarOpen: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n },\n);\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean];\n 'collapse-change': [value: boolean];\n 'update:sidebarOpen': [value: boolean];\n 'sidebar-open-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n default?: (props: Pick<AppShellSlotProps, 'mobile' | 'collapsed'>) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst sidebarId = createId('t-app-shell-sidebar');\n\nconst { value: isCollapsed, setValue: setCollapsed } = useControllableOpen(\n toRef(props, 'collapsed'),\n props.defaultCollapsed,\n (value) => {\n emit('update:collapsed', value);\n emit('collapse-change', value);\n },\n);\n\nconst { value: isSidebarOpen, setValue: setSidebarOpen } = useControllableOpen(\n toRef(props, 'sidebarOpen'),\n props.defaultSidebarOpen,\n (value) => {\n emit('update:sidebarOpen', value);\n emit('sidebar-open-change', value);\n },\n);\n\n// Auto responsive detection when `mobile` is not explicitly controlled.\nconst autoMobile = ref(false);\nlet mediaQuery: MediaQueryList | null = null;\n\nconst onMediaChange = (event: MediaQueryListEvent | MediaQueryList) => {\n autoMobile.value = event.matches;\n};\n\nconst setupMediaQuery = () => {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return;\n }\n\n teardownMediaQuery();\n mediaQuery = window.matchMedia(`(max-width: ${props.breakpoint})`);\n autoMobile.value = mediaQuery.matches;\n mediaQuery.addEventListener?.('change', onMediaChange);\n};\n\nconst teardownMediaQuery = () => {\n mediaQuery?.removeEventListener?.('change', onMediaChange);\n mediaQuery = null;\n};\n\nconst isMobile = computed(() => props.mobile ?? autoMobile.value);\n\n// Collapse is a desktop-only concept; the mobile drawer always shows the\n// expanded sidebar, so descendants and slots see `collapsed: false` there.\nconst effectiveCollapsed = computed(() =>\n isMobile.value ? false : isCollapsed.value,\n);\n\nconst surfaceRef = ref<HTMLElement | null>(null);\nconst previousFocusedElement = ref<HTMLElement | null>(null);\nlet previousBodyOverflow = '';\nlet bodyLocked = false;\n\nconst lockBodyScroll = () => {\n if (typeof document === 'undefined' || bodyLocked) {\n return;\n }\n\n previousBodyOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n bodyLocked = true;\n};\n\nconst unlockBodyScroll = () => {\n if (typeof document === 'undefined' || !bodyLocked) {\n return;\n }\n\n document.body.style.overflow = previousBodyOverflow;\n bodyLocked = false;\n};\n\nconst focusSurface = () => {\n nextTick(() => {\n if (!surfaceRef.value) {\n return;\n }\n\n const focused = focusFirst(surfaceRef.value);\n if (!focused) {\n surfaceRef.value.focus();\n }\n });\n};\n\nconst openSidebar = () => {\n previousFocusedElement.value =\n typeof document === 'undefined'\n ? null\n : (document.activeElement as HTMLElement | null);\n setSidebarOpen(true);\n};\n\nconst closeSidebar = () => {\n setSidebarOpen(false);\n};\n\nconst toggleSidebar = () => {\n if (isSidebarOpen.value) {\n closeSidebar();\n } else {\n openSidebar();\n }\n};\n\nconst toggleCollapsed = () => {\n if (!props.collapsible) {\n return;\n }\n\n setCollapsed(!isCollapsed.value);\n};\n\nconst onOverlayClick = () => {\n if (props.closeOnOverlay) {\n closeSidebar();\n }\n};\n\nconst onKeydown = (event: KeyboardEvent) => {\n if (isEscapeKey(event) && props.closeOnEscape) {\n event.preventDefault();\n closeSidebar();\n return;\n }\n\n if (event.key !== 'Tab' || !surfaceRef.value) {\n return;\n }\n\n const focusable = getFocusableElements(surfaceRef.value);\n\n if (!focusable.length) {\n event.preventDefault();\n surfaceRef.value.focus();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const activeElement = document.activeElement as HTMLElement | null;\n\n if (event.shiftKey && activeElement === first) {\n event.preventDefault();\n focusLast(surfaceRef.value);\n }\n\n if (!event.shiftKey && activeElement === last) {\n event.preventDefault();\n focusFirst(surfaceRef.value);\n }\n};\n\n// The off-canvas drawer only exists in mobile mode; keep body-scroll lock and\n// focus handling in sync with both the open flag and the responsive mode.\nconst syncOverlayState = (open: boolean, wasOpen: boolean) => {\n if (open && isMobile.value) {\n lockBodyScroll();\n focusSurface();\n return;\n }\n\n if (wasOpen) {\n unlockBodyScroll();\n nextTick(() => {\n previousFocusedElement.value?.focus();\n });\n }\n};\n\nwatch(isSidebarOpen, (value, previousValue) => {\n syncOverlayState(value, Boolean(previousValue));\n});\n\nwatch(isMobile, (mobile) => {\n if (!isSidebarOpen.value) {\n return;\n }\n\n // Leaving mobile removes the drawer from the DOM, so release the lock and\n // restore focus; re-entering mobile while \"open\" re-arms the overlay.\n if (mobile) {\n lockBodyScroll();\n focusSurface();\n } else {\n unlockBodyScroll();\n }\n});\n\nwatch(\n () => props.breakpoint,\n () => {\n if (typeof window !== 'undefined') {\n setupMediaQuery();\n }\n },\n);\n\nonMounted(() => {\n setupMediaQuery();\n});\n\nonBeforeUnmount(() => {\n teardownMediaQuery();\n unlockBodyScroll();\n});\n\nprovide(treeSidebarInjectionKey, {\n collapsed: effectiveCollapsed,\n});\n\nconst rootClasses = computed(() => [\n 't-app-shell',\n `t-app-shell--${props.side}`,\n {\n 'is-mobile': isMobile.value,\n 'is-collapsed':\n !isMobile.value && props.collapsible && isCollapsed.value,\n 'is-sidebar-open': isMobile.value && isSidebarOpen.value,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => [\n {\n '--tree-app-shell-sidebar-width': props.sidebarWidth,\n '--tree-app-shell-collapsed-width': props.collapsedWidth,\n },\n attrs.style,\n]);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst slotProps = computed<AppShellSlotProps>(() => ({\n mobile: isMobile.value,\n collapsed: effectiveCollapsed.value,\n sidebarOpen: isSidebarOpen.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n}));\n</script>\n\n<template>\n <component\n :is=\"as\"\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n :data-mobile=\"isMobile ? 'true' : 'false'\"\n >\n <header class=\"t-app-shell__header\">\n <button\n v-if=\"isMobile && showMenuButton\"\n type=\"button\"\n class=\"t-app-shell__menu-button\"\n :aria-label=\"menuLabel\"\n :aria-expanded=\"isSidebarOpen\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleSidebar\"\n >\n <slot\n name=\"menu-icon\"\n :sidebar-open=\"isSidebarOpen\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"6\"\n y2=\"6\"\n />\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"12\"\n y2=\"12\"\n />\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"18\"\n y2=\"18\"\n />\n </svg>\n </slot>\n </button>\n\n <div class=\"t-app-shell__header-content\">\n <slot\n name=\"header\"\n v-bind=\"slotProps\"\n />\n </div>\n </header>\n\n <aside\n v-if=\"!isMobile\"\n :id=\"sidebarId\"\n class=\"t-app-shell__sidebar\"\n :aria-label=\"sidebarLabel\"\n >\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen\"\n class=\"t-app-shell__overlay\"\n >\n <div\n class=\"t-app-shell__backdrop\"\n aria-hidden=\"true\"\n @click=\"onOverlayClick\"\n />\n <aside\n :id=\"sidebarId\"\n ref=\"surfaceRef\"\n class=\"t-app-shell__drawer\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"sidebarLabel\"\n tabindex=\"-1\"\n @keydown=\"onKeydown\"\n >\n <div class=\"t-app-shell__drawer-topbar\">\n <button\n type=\"button\"\n class=\"t-app-shell__drawer-close\"\n :aria-label=\"closeLabel\"\n @click=\"closeSidebar\"\n >\n <CloseIcon v-bind=\"treeIconDefaults\" />\n </button>\n </div>\n <div class=\"t-app-shell__drawer-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":""}
@@ -0,0 +1,90 @@
1
+ import { TSidebarSide } from './TSidebar.vue';
2
+ type __VLS_Props = {
3
+ as?: string;
4
+ side?: TSidebarSide;
5
+ sidebarWidth?: string;
6
+ collapsedWidth?: string;
7
+ collapsible?: boolean;
8
+ collapsed?: boolean;
9
+ defaultCollapsed?: boolean;
10
+ mobile?: boolean;
11
+ breakpoint?: string;
12
+ sidebarOpen?: boolean;
13
+ defaultSidebarOpen?: boolean;
14
+ closeOnEscape?: boolean;
15
+ closeOnOverlay?: boolean;
16
+ showMenuButton?: boolean;
17
+ menuLabel?: string;
18
+ closeLabel?: string;
19
+ sidebarLabel?: string;
20
+ };
21
+ export interface AppShellSlotProps {
22
+ mobile: boolean;
23
+ collapsed: boolean;
24
+ sidebarOpen: boolean;
25
+ toggleSidebar: () => void;
26
+ closeSidebar: () => void;
27
+ toggleCollapsed: () => void;
28
+ }
29
+ declare function __VLS_template(): {
30
+ attrs: Partial<{}>;
31
+ slots: Readonly<{
32
+ header?: (props: AppShellSlotProps) => unknown;
33
+ sidebar?: (props: AppShellSlotProps) => unknown;
34
+ default?: (props: Pick<AppShellSlotProps, "mobile" | "collapsed">) => unknown;
35
+ 'menu-icon'?: (props: {
36
+ sidebarOpen: boolean;
37
+ }) => unknown;
38
+ }> & {
39
+ header?: (props: AppShellSlotProps) => unknown;
40
+ sidebar?: (props: AppShellSlotProps) => unknown;
41
+ default?: (props: Pick<AppShellSlotProps, "mobile" | "collapsed">) => unknown;
42
+ 'menu-icon'?: (props: {
43
+ sidebarOpen: boolean;
44
+ }) => unknown;
45
+ };
46
+ refs: {
47
+ surfaceRef: HTMLElement;
48
+ };
49
+ rootEl: any;
50
+ };
51
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
52
+ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
53
+ "update:collapsed": (value: boolean) => any;
54
+ "collapse-change": (value: boolean) => any;
55
+ "update:sidebarOpen": (value: boolean) => any;
56
+ "sidebar-open-change": (value: boolean) => any;
57
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
58
+ "onUpdate:collapsed"?: ((value: boolean) => any) | undefined;
59
+ "onCollapse-change"?: ((value: boolean) => any) | undefined;
60
+ "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
61
+ "onSidebar-open-change"?: ((value: boolean) => any) | undefined;
62
+ }>, {
63
+ collapsible: boolean;
64
+ as: string;
65
+ mobile: boolean;
66
+ closeOnEscape: boolean;
67
+ closeOnOverlay: boolean;
68
+ closeLabel: string;
69
+ side: TSidebarSide;
70
+ collapsed: boolean;
71
+ defaultCollapsed: boolean;
72
+ collapsedWidth: string;
73
+ sidebarWidth: string;
74
+ breakpoint: string;
75
+ sidebarOpen: boolean;
76
+ defaultSidebarOpen: boolean;
77
+ showMenuButton: boolean;
78
+ menuLabel: string;
79
+ sidebarLabel: string;
80
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
81
+ surfaceRef: HTMLElement;
82
+ }, any>;
83
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
84
+ export default _default;
85
+ type __VLS_WithTemplateSlots<T, S> = T & {
86
+ new (): {
87
+ $slots: S;
88
+ };
89
+ };
90
+ //# sourceMappingURL=TAppShell.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TAppShell.vue.d.ts","sourceRoot":"","sources":["../../src/components/TAppShell.vue"],"names":[],"mappings":"AA2eA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AASnD,KAAK,WAAW,GAAG;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAgCJ,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B;AAwRD,iBAAS,cAAc;WAgKT,OAAO,IAA6B;;iBArbvC,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO;kBACpC,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO;kBACrC,CAAC,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,WAAW,CAAC,KAAK,OAAO;sBAC/D,CAAC,KAAK,EAAE;YAAE,WAAW,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO;;iBAHjD,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO;kBACpC,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO;kBACrC,CAAC,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,WAAW,CAAC,KAAK,OAAO;sBAC/D,CAAC,KAAK,EAAE;YAAE,WAAW,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO;;;;;;EAub3D;AAyBD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;;;;;iBA3gBH,OAAO;QAJhB,MAAM;YAOF,OAAO;mBAIA,OAAO;oBACN,OAAO;gBAGX,MAAM;UAdZ,YAAY;eAIP,OAAO;sBACA,OAAO;oBAHT,MAAM;kBADR,MAAM;gBAMR,MAAM;iBACL,OAAO;wBACA,OAAO;oBAGX,OAAO;eACZ,MAAM;kBAEH,MAAM;;;OAwgBvB,CAAC;wBACkB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAAnG,wBAAoG;AAapG,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
@@ -0,0 +1,2 @@
1
+ const e=require("../composables/useControllableOpen.cjs"),t=require("./sidebar.cjs");let n=require("vue"),r=require("@treeui/utils"),i=require("@treeui/icons");var a={class:`t-app-shell__header`},o=[`aria-label`,`aria-expanded`,`aria-controls`],s={class:`t-app-shell__header-content`},c=[`id`,`aria-label`],l={class:`t-app-shell__main`},u={key:0,class:`t-app-shell__overlay`},d=[`id`,`aria-label`],f={class:`t-app-shell__drawer-topbar`},p=[`aria-label`],m={class:`t-app-shell__drawer-body`},h=(0,n.defineComponent)({inheritAttrs:!1,__name:`TAppShell`,props:{as:{default:`div`},side:{default:`left`},sidebarWidth:{default:`17.5rem`},collapsedWidth:{default:`4.75rem`},collapsible:{type:Boolean,default:!1},collapsed:{type:Boolean,default:void 0},defaultCollapsed:{type:Boolean,default:!1},mobile:{type:Boolean,default:void 0},breakpoint:{default:`768px`},sidebarOpen:{type:Boolean,default:void 0},defaultSidebarOpen:{type:Boolean,default:!1},closeOnEscape:{type:Boolean,default:!0},closeOnOverlay:{type:Boolean,default:!0},showMenuButton:{type:Boolean,default:!0},menuLabel:{default:`Open menu`},closeLabel:{default:`Close menu`},sidebarLabel:{default:`Sidebar`}},emits:[`update:collapsed`,`collapse-change`,`update:sidebarOpen`,`sidebar-open-change`],setup(h,{emit:g}){let _=(0,i.getTreeIcon)(`x`),v=h,y=g,b=(0,n.useAttrs)(),x=(0,r.createId)(`t-app-shell-sidebar`),{value:S,setValue:C}=e.useControllableOpen((0,n.toRef)(v,`collapsed`),v.defaultCollapsed,e=>{y(`update:collapsed`,e),y(`collapse-change`,e)}),{value:w,setValue:T}=e.useControllableOpen((0,n.toRef)(v,`sidebarOpen`),v.defaultSidebarOpen,e=>{y(`update:sidebarOpen`,e),y(`sidebar-open-change`,e)}),E=(0,n.ref)(!1),D=null,O=e=>{E.value=e.matches},k=()=>{typeof window>`u`||typeof window.matchMedia!=`function`||(A(),D=window.matchMedia(`(max-width: ${v.breakpoint})`),E.value=D.matches,D.addEventListener?.(`change`,O))},A=()=>{D?.removeEventListener?.(`change`,O),D=null},j=(0,n.computed)(()=>v.mobile??E.value),M=(0,n.computed)(()=>!j.value&&S.value),N=(0,n.ref)(null),P=(0,n.ref)(null),F=``,I=!1,L=()=>{typeof document>`u`||I||(F=document.body.style.overflow,document.body.style.overflow=`hidden`,I=!0)},R=()=>{typeof document>`u`||!I||(document.body.style.overflow=F,I=!1)},z=()=>{(0,n.nextTick)(()=>{N.value&&((0,r.focusFirst)(N.value)||N.value.focus())})},B=()=>{P.value=typeof document>`u`?null:document.activeElement,T(!0)},V=()=>{T(!1)},H=()=>{w.value?V():B()},U=()=>{v.collapsible&&C(!S.value)},W=()=>{v.closeOnOverlay&&V()},G=e=>{if((0,r.isEscapeKey)(e)&&v.closeOnEscape){e.preventDefault(),V();return}if(e.key!==`Tab`||!N.value)return;let t=(0,r.getFocusableElements)(N.value);if(!t.length){e.preventDefault(),N.value.focus();return}let n=t[0],i=t[t.length-1],a=document.activeElement;e.shiftKey&&a===n&&(e.preventDefault(),(0,r.focusLast)(N.value)),!e.shiftKey&&a===i&&(e.preventDefault(),(0,r.focusFirst)(N.value))},K=(e,t)=>{if(e&&j.value){L(),z();return}t&&(R(),(0,n.nextTick)(()=>{P.value?.focus()}))};(0,n.watch)(w,(e,t)=>{K(e,!!t)}),(0,n.watch)(j,e=>{w.value&&(e?(L(),z()):R())}),(0,n.watch)(()=>v.breakpoint,()=>{typeof window<`u`&&k()}),(0,n.onMounted)(()=>{k()}),(0,n.onBeforeUnmount)(()=>{A(),R()}),(0,n.provide)(t.treeSidebarInjectionKey,{collapsed:M});let q=(0,n.computed)(()=>[`t-app-shell`,`t-app-shell--${v.side}`,{"is-mobile":j.value,"is-collapsed":!j.value&&v.collapsible&&S.value,"is-sidebar-open":j.value&&w.value},b.class]),J=(0,n.computed)(()=>[{"--tree-app-shell-sidebar-width":v.sidebarWidth,"--tree-app-shell-collapsed-width":v.collapsedWidth},b.style]),Y=(0,n.computed)(()=>{let{class:e,style:t,...n}=b;return n}),X=(0,n.computed)(()=>({mobile:j.value,collapsed:M.value,sidebarOpen:w.value,toggleSidebar:H,closeSidebar:V,toggleCollapsed:U}));return(e,t)=>((0,n.openBlock)(),(0,n.createBlock)((0,n.resolveDynamicComponent)(h.as),(0,n.mergeProps)(Y.value,{class:q.value,style:J.value,"data-mobile":j.value?`true`:`false`}),{default:(0,n.withCtx)(()=>[(0,n.createElementVNode)(`header`,a,[j.value&&h.showMenuButton?((0,n.openBlock)(),(0,n.createElementBlock)(`button`,{key:0,type:`button`,class:`t-app-shell__menu-button`,"aria-label":h.menuLabel,"aria-expanded":(0,n.unref)(w),"aria-controls":(0,n.unref)(x),onClick:H},[(0,n.renderSlot)(e.$slots,`menu-icon`,{sidebarOpen:(0,n.unref)(w)},()=>[t[0]||=(0,n.createElementVNode)(`svg`,{xmlns:`http://www.w3.org/2000/svg`,width:`24`,height:`24`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[(0,n.createElementVNode)(`line`,{x1:`4`,x2:`20`,y1:`6`,y2:`6`}),(0,n.createElementVNode)(`line`,{x1:`4`,x2:`20`,y1:`12`,y2:`12`}),(0,n.createElementVNode)(`line`,{x1:`4`,x2:`20`,y1:`18`,y2:`18`})],-1)])],8,o)):(0,n.createCommentVNode)(``,!0),(0,n.createElementVNode)(`div`,s,[(0,n.renderSlot)(e.$slots,`header`,(0,n.normalizeProps)((0,n.guardReactiveProps)(X.value)))])]),j.value?(0,n.createCommentVNode)(``,!0):((0,n.openBlock)(),(0,n.createElementBlock)(`aside`,{key:0,id:(0,n.unref)(x),class:`t-app-shell__sidebar`,"aria-label":h.sidebarLabel},[(0,n.renderSlot)(e.$slots,`sidebar`,(0,n.normalizeProps)((0,n.guardReactiveProps)(X.value)))],8,c)),(0,n.createElementVNode)(`main`,l,[(0,n.renderSlot)(e.$slots,`default`,{mobile:j.value,collapsed:M.value})]),(0,n.createVNode)(n.Transition,{name:`t-app-shell-fade`},{default:(0,n.withCtx)(()=>[j.value&&(0,n.unref)(w)?((0,n.openBlock)(),(0,n.createElementBlock)(`div`,u,[(0,n.createElementVNode)(`div`,{class:`t-app-shell__backdrop`,"aria-hidden":`true`,onClick:W}),(0,n.createElementVNode)(`aside`,{id:(0,n.unref)(x),ref_key:`surfaceRef`,ref:N,class:`t-app-shell__drawer`,role:`dialog`,"aria-modal":`true`,"aria-label":h.sidebarLabel,tabindex:`-1`,onKeydown:G},[(0,n.createElementVNode)(`div`,f,[(0,n.createElementVNode)(`button`,{type:`button`,class:`t-app-shell__drawer-close`,"aria-label":h.closeLabel,onClick:V},[(0,n.createVNode)((0,n.unref)(_),(0,n.normalizeProps)((0,n.guardReactiveProps)((0,n.unref)(i.treeIconDefaults))),null,16)],8,p)]),(0,n.createElementVNode)(`div`,m,[(0,n.renderSlot)(e.$slots,`sidebar`,(0,n.normalizeProps)((0,n.guardReactiveProps)(X.value)))])],40,d)])):(0,n.createCommentVNode)(``,!0)]),_:3})]),_:3},16,[`class`,`style`,`data-mobile`]))}});exports.default=h;
2
+ //# sourceMappingURL=TAppShell.vue_vue_type_script_setup_true_lang.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TAppShell.vue_vue_type_script_setup_true_lang.cjs","names":[],"sources":["../../src/components/TAppShell.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n createId,\n focusFirst,\n focusLast,\n getFocusableElements,\n isEscapeKey,\n} from '@treeui/utils';\nimport { getTreeIcon, treeIconDefaults } from '@treeui/icons';\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n provide,\n ref,\n toRef,\n useAttrs,\n watch,\n} from 'vue';\nimport { useControllableOpen } from '../composables/useControllableOpen';\nimport type { TSidebarSide } from './TSidebar.vue';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nconst CloseIcon = getTreeIcon('x');\n\nconst props = withDefaults(\n defineProps<{\n as?: string;\n side?: TSidebarSide;\n sidebarWidth?: string;\n collapsedWidth?: string;\n collapsible?: boolean;\n collapsed?: boolean;\n defaultCollapsed?: boolean;\n mobile?: boolean;\n breakpoint?: string;\n sidebarOpen?: boolean;\n defaultSidebarOpen?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n }>(),\n {\n as: 'div',\n side: 'left',\n sidebarWidth: '17.5rem',\n collapsedWidth: '4.75rem',\n collapsible: false,\n collapsed: undefined,\n defaultCollapsed: false,\n mobile: undefined,\n breakpoint: '768px',\n sidebarOpen: undefined,\n defaultSidebarOpen: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n },\n);\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean];\n 'collapse-change': [value: boolean];\n 'update:sidebarOpen': [value: boolean];\n 'sidebar-open-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n default?: (props: Pick<AppShellSlotProps, 'mobile' | 'collapsed'>) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst sidebarId = createId('t-app-shell-sidebar');\n\nconst { value: isCollapsed, setValue: setCollapsed } = useControllableOpen(\n toRef(props, 'collapsed'),\n props.defaultCollapsed,\n (value) => {\n emit('update:collapsed', value);\n emit('collapse-change', value);\n },\n);\n\nconst { value: isSidebarOpen, setValue: setSidebarOpen } = useControllableOpen(\n toRef(props, 'sidebarOpen'),\n props.defaultSidebarOpen,\n (value) => {\n emit('update:sidebarOpen', value);\n emit('sidebar-open-change', value);\n },\n);\n\n// Auto responsive detection when `mobile` is not explicitly controlled.\nconst autoMobile = ref(false);\nlet mediaQuery: MediaQueryList | null = null;\n\nconst onMediaChange = (event: MediaQueryListEvent | MediaQueryList) => {\n autoMobile.value = event.matches;\n};\n\nconst setupMediaQuery = () => {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return;\n }\n\n teardownMediaQuery();\n mediaQuery = window.matchMedia(`(max-width: ${props.breakpoint})`);\n autoMobile.value = mediaQuery.matches;\n mediaQuery.addEventListener?.('change', onMediaChange);\n};\n\nconst teardownMediaQuery = () => {\n mediaQuery?.removeEventListener?.('change', onMediaChange);\n mediaQuery = null;\n};\n\nconst isMobile = computed(() => props.mobile ?? autoMobile.value);\n\n// Collapse is a desktop-only concept; the mobile drawer always shows the\n// expanded sidebar, so descendants and slots see `collapsed: false` there.\nconst effectiveCollapsed = computed(() =>\n isMobile.value ? false : isCollapsed.value,\n);\n\nconst surfaceRef = ref<HTMLElement | null>(null);\nconst previousFocusedElement = ref<HTMLElement | null>(null);\nlet previousBodyOverflow = '';\nlet bodyLocked = false;\n\nconst lockBodyScroll = () => {\n if (typeof document === 'undefined' || bodyLocked) {\n return;\n }\n\n previousBodyOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n bodyLocked = true;\n};\n\nconst unlockBodyScroll = () => {\n if (typeof document === 'undefined' || !bodyLocked) {\n return;\n }\n\n document.body.style.overflow = previousBodyOverflow;\n bodyLocked = false;\n};\n\nconst focusSurface = () => {\n nextTick(() => {\n if (!surfaceRef.value) {\n return;\n }\n\n const focused = focusFirst(surfaceRef.value);\n if (!focused) {\n surfaceRef.value.focus();\n }\n });\n};\n\nconst openSidebar = () => {\n previousFocusedElement.value =\n typeof document === 'undefined'\n ? null\n : (document.activeElement as HTMLElement | null);\n setSidebarOpen(true);\n};\n\nconst closeSidebar = () => {\n setSidebarOpen(false);\n};\n\nconst toggleSidebar = () => {\n if (isSidebarOpen.value) {\n closeSidebar();\n } else {\n openSidebar();\n }\n};\n\nconst toggleCollapsed = () => {\n if (!props.collapsible) {\n return;\n }\n\n setCollapsed(!isCollapsed.value);\n};\n\nconst onOverlayClick = () => {\n if (props.closeOnOverlay) {\n closeSidebar();\n }\n};\n\nconst onKeydown = (event: KeyboardEvent) => {\n if (isEscapeKey(event) && props.closeOnEscape) {\n event.preventDefault();\n closeSidebar();\n return;\n }\n\n if (event.key !== 'Tab' || !surfaceRef.value) {\n return;\n }\n\n const focusable = getFocusableElements(surfaceRef.value);\n\n if (!focusable.length) {\n event.preventDefault();\n surfaceRef.value.focus();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const activeElement = document.activeElement as HTMLElement | null;\n\n if (event.shiftKey && activeElement === first) {\n event.preventDefault();\n focusLast(surfaceRef.value);\n }\n\n if (!event.shiftKey && activeElement === last) {\n event.preventDefault();\n focusFirst(surfaceRef.value);\n }\n};\n\n// The off-canvas drawer only exists in mobile mode; keep body-scroll lock and\n// focus handling in sync with both the open flag and the responsive mode.\nconst syncOverlayState = (open: boolean, wasOpen: boolean) => {\n if (open && isMobile.value) {\n lockBodyScroll();\n focusSurface();\n return;\n }\n\n if (wasOpen) {\n unlockBodyScroll();\n nextTick(() => {\n previousFocusedElement.value?.focus();\n });\n }\n};\n\nwatch(isSidebarOpen, (value, previousValue) => {\n syncOverlayState(value, Boolean(previousValue));\n});\n\nwatch(isMobile, (mobile) => {\n if (!isSidebarOpen.value) {\n return;\n }\n\n // Leaving mobile removes the drawer from the DOM, so release the lock and\n // restore focus; re-entering mobile while \"open\" re-arms the overlay.\n if (mobile) {\n lockBodyScroll();\n focusSurface();\n } else {\n unlockBodyScroll();\n }\n});\n\nwatch(\n () => props.breakpoint,\n () => {\n if (typeof window !== 'undefined') {\n setupMediaQuery();\n }\n },\n);\n\nonMounted(() => {\n setupMediaQuery();\n});\n\nonBeforeUnmount(() => {\n teardownMediaQuery();\n unlockBodyScroll();\n});\n\nprovide(treeSidebarInjectionKey, {\n collapsed: effectiveCollapsed,\n});\n\nconst rootClasses = computed(() => [\n 't-app-shell',\n `t-app-shell--${props.side}`,\n {\n 'is-mobile': isMobile.value,\n 'is-collapsed':\n !isMobile.value && props.collapsible && isCollapsed.value,\n 'is-sidebar-open': isMobile.value && isSidebarOpen.value,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => [\n {\n '--tree-app-shell-sidebar-width': props.sidebarWidth,\n '--tree-app-shell-collapsed-width': props.collapsedWidth,\n },\n attrs.style,\n]);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst slotProps = computed<AppShellSlotProps>(() => ({\n mobile: isMobile.value,\n collapsed: effectiveCollapsed.value,\n sidebarOpen: isSidebarOpen.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n}));\n</script>\n\n<template>\n <component\n :is=\"as\"\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n :data-mobile=\"isMobile ? 'true' : 'false'\"\n >\n <header class=\"t-app-shell__header\">\n <button\n v-if=\"isMobile && showMenuButton\"\n type=\"button\"\n class=\"t-app-shell__menu-button\"\n :aria-label=\"menuLabel\"\n :aria-expanded=\"isSidebarOpen\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleSidebar\"\n >\n <slot\n name=\"menu-icon\"\n :sidebar-open=\"isSidebarOpen\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"6\"\n y2=\"6\"\n />\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"12\"\n y2=\"12\"\n />\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"18\"\n y2=\"18\"\n />\n </svg>\n </slot>\n </button>\n\n <div class=\"t-app-shell__header-content\">\n <slot\n name=\"header\"\n v-bind=\"slotProps\"\n />\n </div>\n </header>\n\n <aside\n v-if=\"!isMobile\"\n :id=\"sidebarId\"\n class=\"t-app-shell__sidebar\"\n :aria-label=\"sidebarLabel\"\n >\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen\"\n class=\"t-app-shell__overlay\"\n >\n <div\n class=\"t-app-shell__backdrop\"\n aria-hidden=\"true\"\n @click=\"onOverlayClick\"\n />\n <aside\n :id=\"sidebarId\"\n ref=\"surfaceRef\"\n class=\"t-app-shell__drawer\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"sidebarLabel\"\n tabindex=\"-1\"\n @keydown=\"onKeydown\"\n >\n <div class=\"t-app-shell__drawer-topbar\">\n <button\n type=\"button\"\n class=\"t-app-shell__drawer-close\"\n :aria-label=\"closeLabel\"\n @click=\"closeSidebar\"\n >\n <CloseIcon v-bind=\"treeIconDefaults\" />\n </button>\n </div>\n <div class=\"t-app-shell__drawer-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,244 @@
1
+ import { useControllableOpen as e } from "../composables/useControllableOpen.js";
2
+ import { treeSidebarInjectionKey as t } from "./sidebar.js";
3
+ import { Transition as n, computed as r, createBlock as i, createCommentVNode as a, createElementBlock as o, createElementVNode as s, createVNode as c, defineComponent as l, guardReactiveProps as u, mergeProps as ee, nextTick as d, normalizeProps as f, onBeforeUnmount as p, onMounted as m, openBlock as h, provide as te, ref as g, renderSlot as _, resolveDynamicComponent as v, toRef as y, unref as b, useAttrs as ne, watch as x, withCtx as S } from "vue";
4
+ import { createId as C, focusFirst as w, focusLast as T, getFocusableElements as E, isEscapeKey as re } from "@treeui/utils";
5
+ import { getTreeIcon as ie, treeIconDefaults as ae } from "@treeui/icons";
6
+ //#region src/components/TAppShell.vue?vue&type=script&setup=true&lang.ts
7
+ var oe = { class: "t-app-shell__header" }, se = [
8
+ "aria-label",
9
+ "aria-expanded",
10
+ "aria-controls"
11
+ ], ce = { class: "t-app-shell__header-content" }, le = ["id", "aria-label"], ue = { class: "t-app-shell__main" }, de = {
12
+ key: 0,
13
+ class: "t-app-shell__overlay"
14
+ }, fe = ["id", "aria-label"], pe = { class: "t-app-shell__drawer-topbar" }, me = ["aria-label"], he = { class: "t-app-shell__drawer-body" }, D = /*@__PURE__*/ l({
15
+ inheritAttrs: !1,
16
+ __name: "TAppShell",
17
+ props: {
18
+ as: { default: "div" },
19
+ side: { default: "left" },
20
+ sidebarWidth: { default: "17.5rem" },
21
+ collapsedWidth: { default: "4.75rem" },
22
+ collapsible: {
23
+ type: Boolean,
24
+ default: !1
25
+ },
26
+ collapsed: {
27
+ type: Boolean,
28
+ default: void 0
29
+ },
30
+ defaultCollapsed: {
31
+ type: Boolean,
32
+ default: !1
33
+ },
34
+ mobile: {
35
+ type: Boolean,
36
+ default: void 0
37
+ },
38
+ breakpoint: { default: "768px" },
39
+ sidebarOpen: {
40
+ type: Boolean,
41
+ default: void 0
42
+ },
43
+ defaultSidebarOpen: {
44
+ type: Boolean,
45
+ default: !1
46
+ },
47
+ closeOnEscape: {
48
+ type: Boolean,
49
+ default: !0
50
+ },
51
+ closeOnOverlay: {
52
+ type: Boolean,
53
+ default: !0
54
+ },
55
+ showMenuButton: {
56
+ type: Boolean,
57
+ default: !0
58
+ },
59
+ menuLabel: { default: "Open menu" },
60
+ closeLabel: { default: "Close menu" },
61
+ sidebarLabel: { default: "Sidebar" }
62
+ },
63
+ emits: [
64
+ "update:collapsed",
65
+ "collapse-change",
66
+ "update:sidebarOpen",
67
+ "sidebar-open-change"
68
+ ],
69
+ setup(l, { emit: D }) {
70
+ let ge = ie("x"), O = l, k = D, A = ne(), j = C("t-app-shell-sidebar"), { value: M, setValue: N } = e(y(O, "collapsed"), O.defaultCollapsed, (e) => {
71
+ k("update:collapsed", e), k("collapse-change", e);
72
+ }), { value: P, setValue: F } = e(y(O, "sidebarOpen"), O.defaultSidebarOpen, (e) => {
73
+ k("update:sidebarOpen", e), k("sidebar-open-change", e);
74
+ }), I = g(!1), L = null, R = (e) => {
75
+ I.value = e.matches;
76
+ }, z = () => {
77
+ typeof window > "u" || typeof window.matchMedia != "function" || (B(), L = window.matchMedia(`(max-width: ${O.breakpoint})`), I.value = L.matches, L.addEventListener?.("change", R));
78
+ }, B = () => {
79
+ L?.removeEventListener?.("change", R), L = null;
80
+ }, V = r(() => O.mobile ?? I.value), H = r(() => !V.value && M.value), U = g(null), W = g(null), G = "", K = !1, q = () => {
81
+ typeof document > "u" || K || (G = document.body.style.overflow, document.body.style.overflow = "hidden", K = !0);
82
+ }, J = () => {
83
+ typeof document > "u" || !K || (document.body.style.overflow = G, K = !1);
84
+ }, Y = () => {
85
+ d(() => {
86
+ U.value && (w(U.value) || U.value.focus());
87
+ });
88
+ }, _e = () => {
89
+ W.value = typeof document > "u" ? null : document.activeElement, F(!0);
90
+ }, X = () => {
91
+ F(!1);
92
+ }, Z = () => {
93
+ P.value ? X() : _e();
94
+ }, Q = () => {
95
+ O.collapsible && N(!M.value);
96
+ }, ve = () => {
97
+ O.closeOnOverlay && X();
98
+ }, ye = (e) => {
99
+ if (re(e) && O.closeOnEscape) {
100
+ e.preventDefault(), X();
101
+ return;
102
+ }
103
+ if (e.key !== "Tab" || !U.value) return;
104
+ let t = E(U.value);
105
+ if (!t.length) {
106
+ e.preventDefault(), U.value.focus();
107
+ return;
108
+ }
109
+ let n = t[0], r = t[t.length - 1], i = document.activeElement;
110
+ e.shiftKey && i === n && (e.preventDefault(), T(U.value)), !e.shiftKey && i === r && (e.preventDefault(), w(U.value));
111
+ }, be = (e, t) => {
112
+ if (e && V.value) {
113
+ q(), Y();
114
+ return;
115
+ }
116
+ t && (J(), d(() => {
117
+ W.value?.focus();
118
+ }));
119
+ };
120
+ x(P, (e, t) => {
121
+ be(e, !!t);
122
+ }), x(V, (e) => {
123
+ P.value && (e ? (q(), Y()) : J());
124
+ }), x(() => O.breakpoint, () => {
125
+ typeof window < "u" && z();
126
+ }), m(() => {
127
+ z();
128
+ }), p(() => {
129
+ B(), J();
130
+ }), te(t, { collapsed: H });
131
+ let xe = r(() => [
132
+ "t-app-shell",
133
+ `t-app-shell--${O.side}`,
134
+ {
135
+ "is-mobile": V.value,
136
+ "is-collapsed": !V.value && O.collapsible && M.value,
137
+ "is-sidebar-open": V.value && P.value
138
+ },
139
+ A.class
140
+ ]), Se = r(() => [{
141
+ "--tree-app-shell-sidebar-width": O.sidebarWidth,
142
+ "--tree-app-shell-collapsed-width": O.collapsedWidth
143
+ }, A.style]), Ce = r(() => {
144
+ let { class: e, style: t, ...n } = A;
145
+ return n;
146
+ }), $ = r(() => ({
147
+ mobile: V.value,
148
+ collapsed: H.value,
149
+ sidebarOpen: P.value,
150
+ toggleSidebar: Z,
151
+ closeSidebar: X,
152
+ toggleCollapsed: Q
153
+ }));
154
+ return (e, t) => (h(), i(v(l.as), ee(Ce.value, {
155
+ class: xe.value,
156
+ style: Se.value,
157
+ "data-mobile": V.value ? "true" : "false"
158
+ }), {
159
+ default: S(() => [
160
+ s("header", oe, [V.value && l.showMenuButton ? (h(), o("button", {
161
+ key: 0,
162
+ type: "button",
163
+ class: "t-app-shell__menu-button",
164
+ "aria-label": l.menuLabel,
165
+ "aria-expanded": b(P),
166
+ "aria-controls": b(j),
167
+ onClick: Z
168
+ }, [_(e.$slots, "menu-icon", { sidebarOpen: b(P) }, () => [t[0] ||= s("svg", {
169
+ xmlns: "http://www.w3.org/2000/svg",
170
+ width: "24",
171
+ height: "24",
172
+ viewBox: "0 0 24 24",
173
+ fill: "none",
174
+ stroke: "currentColor",
175
+ "stroke-width": "2",
176
+ "stroke-linecap": "round",
177
+ "stroke-linejoin": "round",
178
+ "aria-hidden": "true"
179
+ }, [
180
+ s("line", {
181
+ x1: "4",
182
+ x2: "20",
183
+ y1: "6",
184
+ y2: "6"
185
+ }),
186
+ s("line", {
187
+ x1: "4",
188
+ x2: "20",
189
+ y1: "12",
190
+ y2: "12"
191
+ }),
192
+ s("line", {
193
+ x1: "4",
194
+ x2: "20",
195
+ y1: "18",
196
+ y2: "18"
197
+ })
198
+ ], -1)])], 8, se)) : a("", !0), s("div", ce, [_(e.$slots, "header", f(u($.value)))])]),
199
+ V.value ? a("", !0) : (h(), o("aside", {
200
+ key: 0,
201
+ id: b(j),
202
+ class: "t-app-shell__sidebar",
203
+ "aria-label": l.sidebarLabel
204
+ }, [_(e.$slots, "sidebar", f(u($.value)))], 8, le)),
205
+ s("main", ue, [_(e.$slots, "default", {
206
+ mobile: V.value,
207
+ collapsed: H.value
208
+ })]),
209
+ c(n, { name: "t-app-shell-fade" }, {
210
+ default: S(() => [V.value && b(P) ? (h(), o("div", de, [s("div", {
211
+ class: "t-app-shell__backdrop",
212
+ "aria-hidden": "true",
213
+ onClick: ve
214
+ }), s("aside", {
215
+ id: b(j),
216
+ ref_key: "surfaceRef",
217
+ ref: U,
218
+ class: "t-app-shell__drawer",
219
+ role: "dialog",
220
+ "aria-modal": "true",
221
+ "aria-label": l.sidebarLabel,
222
+ tabindex: "-1",
223
+ onKeydown: ye
224
+ }, [s("div", pe, [s("button", {
225
+ type: "button",
226
+ class: "t-app-shell__drawer-close",
227
+ "aria-label": l.closeLabel,
228
+ onClick: X
229
+ }, [c(b(ge), f(u(b(ae))), null, 16)], 8, me)]), s("div", he, [_(e.$slots, "sidebar", f(u($.value)))])], 40, fe)])) : a("", !0)]),
230
+ _: 3
231
+ })
232
+ ]),
233
+ _: 3
234
+ }, 16, [
235
+ "class",
236
+ "style",
237
+ "data-mobile"
238
+ ]));
239
+ }
240
+ });
241
+ //#endregion
242
+ export { D as default };
243
+
244
+ //# sourceMappingURL=TAppShell.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TAppShell.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/TAppShell.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n createId,\n focusFirst,\n focusLast,\n getFocusableElements,\n isEscapeKey,\n} from '@treeui/utils';\nimport { getTreeIcon, treeIconDefaults } from '@treeui/icons';\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n provide,\n ref,\n toRef,\n useAttrs,\n watch,\n} from 'vue';\nimport { useControllableOpen } from '../composables/useControllableOpen';\nimport type { TSidebarSide } from './TSidebar.vue';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nconst CloseIcon = getTreeIcon('x');\n\nconst props = withDefaults(\n defineProps<{\n as?: string;\n side?: TSidebarSide;\n sidebarWidth?: string;\n collapsedWidth?: string;\n collapsible?: boolean;\n collapsed?: boolean;\n defaultCollapsed?: boolean;\n mobile?: boolean;\n breakpoint?: string;\n sidebarOpen?: boolean;\n defaultSidebarOpen?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n }>(),\n {\n as: 'div',\n side: 'left',\n sidebarWidth: '17.5rem',\n collapsedWidth: '4.75rem',\n collapsible: false,\n collapsed: undefined,\n defaultCollapsed: false,\n mobile: undefined,\n breakpoint: '768px',\n sidebarOpen: undefined,\n defaultSidebarOpen: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n },\n);\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean];\n 'collapse-change': [value: boolean];\n 'update:sidebarOpen': [value: boolean];\n 'sidebar-open-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n default?: (props: Pick<AppShellSlotProps, 'mobile' | 'collapsed'>) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst sidebarId = createId('t-app-shell-sidebar');\n\nconst { value: isCollapsed, setValue: setCollapsed } = useControllableOpen(\n toRef(props, 'collapsed'),\n props.defaultCollapsed,\n (value) => {\n emit('update:collapsed', value);\n emit('collapse-change', value);\n },\n);\n\nconst { value: isSidebarOpen, setValue: setSidebarOpen } = useControllableOpen(\n toRef(props, 'sidebarOpen'),\n props.defaultSidebarOpen,\n (value) => {\n emit('update:sidebarOpen', value);\n emit('sidebar-open-change', value);\n },\n);\n\n// Auto responsive detection when `mobile` is not explicitly controlled.\nconst autoMobile = ref(false);\nlet mediaQuery: MediaQueryList | null = null;\n\nconst onMediaChange = (event: MediaQueryListEvent | MediaQueryList) => {\n autoMobile.value = event.matches;\n};\n\nconst setupMediaQuery = () => {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return;\n }\n\n teardownMediaQuery();\n mediaQuery = window.matchMedia(`(max-width: ${props.breakpoint})`);\n autoMobile.value = mediaQuery.matches;\n mediaQuery.addEventListener?.('change', onMediaChange);\n};\n\nconst teardownMediaQuery = () => {\n mediaQuery?.removeEventListener?.('change', onMediaChange);\n mediaQuery = null;\n};\n\nconst isMobile = computed(() => props.mobile ?? autoMobile.value);\n\n// Collapse is a desktop-only concept; the mobile drawer always shows the\n// expanded sidebar, so descendants and slots see `collapsed: false` there.\nconst effectiveCollapsed = computed(() =>\n isMobile.value ? false : isCollapsed.value,\n);\n\nconst surfaceRef = ref<HTMLElement | null>(null);\nconst previousFocusedElement = ref<HTMLElement | null>(null);\nlet previousBodyOverflow = '';\nlet bodyLocked = false;\n\nconst lockBodyScroll = () => {\n if (typeof document === 'undefined' || bodyLocked) {\n return;\n }\n\n previousBodyOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n bodyLocked = true;\n};\n\nconst unlockBodyScroll = () => {\n if (typeof document === 'undefined' || !bodyLocked) {\n return;\n }\n\n document.body.style.overflow = previousBodyOverflow;\n bodyLocked = false;\n};\n\nconst focusSurface = () => {\n nextTick(() => {\n if (!surfaceRef.value) {\n return;\n }\n\n const focused = focusFirst(surfaceRef.value);\n if (!focused) {\n surfaceRef.value.focus();\n }\n });\n};\n\nconst openSidebar = () => {\n previousFocusedElement.value =\n typeof document === 'undefined'\n ? null\n : (document.activeElement as HTMLElement | null);\n setSidebarOpen(true);\n};\n\nconst closeSidebar = () => {\n setSidebarOpen(false);\n};\n\nconst toggleSidebar = () => {\n if (isSidebarOpen.value) {\n closeSidebar();\n } else {\n openSidebar();\n }\n};\n\nconst toggleCollapsed = () => {\n if (!props.collapsible) {\n return;\n }\n\n setCollapsed(!isCollapsed.value);\n};\n\nconst onOverlayClick = () => {\n if (props.closeOnOverlay) {\n closeSidebar();\n }\n};\n\nconst onKeydown = (event: KeyboardEvent) => {\n if (isEscapeKey(event) && props.closeOnEscape) {\n event.preventDefault();\n closeSidebar();\n return;\n }\n\n if (event.key !== 'Tab' || !surfaceRef.value) {\n return;\n }\n\n const focusable = getFocusableElements(surfaceRef.value);\n\n if (!focusable.length) {\n event.preventDefault();\n surfaceRef.value.focus();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const activeElement = document.activeElement as HTMLElement | null;\n\n if (event.shiftKey && activeElement === first) {\n event.preventDefault();\n focusLast(surfaceRef.value);\n }\n\n if (!event.shiftKey && activeElement === last) {\n event.preventDefault();\n focusFirst(surfaceRef.value);\n }\n};\n\n// The off-canvas drawer only exists in mobile mode; keep body-scroll lock and\n// focus handling in sync with both the open flag and the responsive mode.\nconst syncOverlayState = (open: boolean, wasOpen: boolean) => {\n if (open && isMobile.value) {\n lockBodyScroll();\n focusSurface();\n return;\n }\n\n if (wasOpen) {\n unlockBodyScroll();\n nextTick(() => {\n previousFocusedElement.value?.focus();\n });\n }\n};\n\nwatch(isSidebarOpen, (value, previousValue) => {\n syncOverlayState(value, Boolean(previousValue));\n});\n\nwatch(isMobile, (mobile) => {\n if (!isSidebarOpen.value) {\n return;\n }\n\n // Leaving mobile removes the drawer from the DOM, so release the lock and\n // restore focus; re-entering mobile while \"open\" re-arms the overlay.\n if (mobile) {\n lockBodyScroll();\n focusSurface();\n } else {\n unlockBodyScroll();\n }\n});\n\nwatch(\n () => props.breakpoint,\n () => {\n if (typeof window !== 'undefined') {\n setupMediaQuery();\n }\n },\n);\n\nonMounted(() => {\n setupMediaQuery();\n});\n\nonBeforeUnmount(() => {\n teardownMediaQuery();\n unlockBodyScroll();\n});\n\nprovide(treeSidebarInjectionKey, {\n collapsed: effectiveCollapsed,\n});\n\nconst rootClasses = computed(() => [\n 't-app-shell',\n `t-app-shell--${props.side}`,\n {\n 'is-mobile': isMobile.value,\n 'is-collapsed':\n !isMobile.value && props.collapsible && isCollapsed.value,\n 'is-sidebar-open': isMobile.value && isSidebarOpen.value,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => [\n {\n '--tree-app-shell-sidebar-width': props.sidebarWidth,\n '--tree-app-shell-collapsed-width': props.collapsedWidth,\n },\n attrs.style,\n]);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst slotProps = computed<AppShellSlotProps>(() => ({\n mobile: isMobile.value,\n collapsed: effectiveCollapsed.value,\n sidebarOpen: isSidebarOpen.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n}));\n</script>\n\n<template>\n <component\n :is=\"as\"\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n :data-mobile=\"isMobile ? 'true' : 'false'\"\n >\n <header class=\"t-app-shell__header\">\n <button\n v-if=\"isMobile && showMenuButton\"\n type=\"button\"\n class=\"t-app-shell__menu-button\"\n :aria-label=\"menuLabel\"\n :aria-expanded=\"isSidebarOpen\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleSidebar\"\n >\n <slot\n name=\"menu-icon\"\n :sidebar-open=\"isSidebarOpen\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"6\"\n y2=\"6\"\n />\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"12\"\n y2=\"12\"\n />\n <line\n x1=\"4\"\n x2=\"20\"\n y1=\"18\"\n y2=\"18\"\n />\n </svg>\n </slot>\n </button>\n\n <div class=\"t-app-shell__header-content\">\n <slot\n name=\"header\"\n v-bind=\"slotProps\"\n />\n </div>\n </header>\n\n <aside\n v-if=\"!isMobile\"\n :id=\"sidebarId\"\n class=\"t-app-shell__sidebar\"\n :aria-label=\"sidebarLabel\"\n >\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen\"\n class=\"t-app-shell__overlay\"\n >\n <div\n class=\"t-app-shell__backdrop\"\n aria-hidden=\"true\"\n @click=\"onOverlayClick\"\n />\n <aside\n :id=\"sidebarId\"\n ref=\"surfaceRef\"\n class=\"t-app-shell__drawer\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"sidebarLabel\"\n tabindex=\"-1\"\n @keydown=\"onKeydown\"\n >\n <div class=\"t-app-shell__drawer-topbar\">\n <button\n type=\"button\"\n class=\"t-app-shell__drawer-close\"\n :aria-label=\"closeLabel\"\n @click=\"closeSidebar\"\n >\n <CloseIcon v-bind=\"treeIconDefaults\" />\n </button>\n </div>\n <div class=\"t-app-shell__drawer-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e=require("./TChart.vue_vue_type_script_setup_true_lang.cjs").default;exports.default=e;
2
+ //# sourceMappingURL=TChart.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TChart.cjs","names":[],"sources":["../../src/components/TChart.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue';\nimport {\n buildAreaPath,\n buildLinePath,\n linearScale,\n niceScale,\n type ChartPoint,\n} from '@treeui/utils';\n\nexport type TChartType = 'line' | 'area' | 'bar';\n\nexport interface TChartSeries {\n /** Legend/tooltip name for the series. */\n name: string;\n /** One value per category, aligned to `labels`. */\n data: number[];\n /** Override the series color; defaults to a chart palette token. */\n color?: string;\n}\n\nexport interface TChartActivePayload {\n index: number;\n label: string;\n values: Array<{ name: string; value: number; color: string }>;\n}\n\nconst props = withDefaults(\n defineProps<{\n /** Visual form of the plot. */\n type?: TChartType;\n /** One or more data series. */\n series?: TChartSeries[];\n /** Category labels for the x-axis, aligned to each series' data. */\n labels?: string[];\n /** Plot height in px (width is fluid). */\n height?: number;\n /** Smooth (curved) line/area. */\n smooth?: boolean;\n /**\n * Stack bar series on top of each other instead of grouping side by side.\n * Negative values are clamped to 0 in stacked mode (grouped mode renders them).\n */\n stacked?: boolean;\n /** Draw horizontal gridlines. */\n showGrid?: boolean;\n /** Show the legend. Defaults to on when there is more than one series. */\n showLegend?: boolean;\n /** Show x-axis category labels. */\n showXAxis?: boolean;\n /** Show y-axis value ticks. */\n showYAxis?: boolean;\n /** Show the hover crosshair + tooltip. */\n showTooltip?: boolean;\n /** Always render markers on line/area points (not just on hover). */\n showPoints?: boolean;\n /** Target number of y-axis ticks. */\n yTicks?: number;\n /** Format axis + tooltip values. */\n valueFormat?: (value: number) => string;\n /** Force the low end of the y domain. */\n minY?: number;\n /** Force the high end of the y domain. */\n maxY?: number;\n /** Animate marks on first render (respects prefers-reduced-motion). */\n animated?: boolean;\n /** Render skeleton placeholders instead of the plot. */\n loading?: boolean;\n /** Accessible summary of the chart. */\n ariaLabel?: string;\n }>(),\n {\n type: 'line',\n series: () => [],\n labels: () => [],\n height: 260,\n smooth: false,\n stacked: false,\n showGrid: true,\n showLegend: undefined,\n showXAxis: true,\n showYAxis: true,\n showTooltip: true,\n showPoints: false,\n yTicks: 5,\n valueFormat: undefined,\n minY: undefined,\n maxY: undefined,\n animated: true,\n loading: false,\n ariaLabel: undefined,\n },\n);\n\nconst emit = defineEmits<{\n 'point-click': [payload: TChartActivePayload];\n}>();\n\ndefineSlots<{\n legend?: (props: { series: Array<{ name: string; color: string }> }) => unknown;\n tooltip?: (props: TChartActivePayload) => unknown;\n empty?: () => unknown;\n}>();\n\n// --- measurement ----------------------------------------------------------\nconst canvasRef = ref<HTMLElement | null>(null);\nconst width = ref(640);\nlet observer: ResizeObserver | null = null;\n\nonMounted(() => {\n const element = canvasRef.value;\n if (!element) return;\n width.value = element.clientWidth || width.value;\n\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured) width.value = measured;\n });\n observer.observe(element);\n }\n});\n\nonBeforeUnmount(() => observer?.disconnect());\n\n// --- derived data ---------------------------------------------------------\nconst validSeries = computed(() => props.series.filter((item) => item.data.length > 0));\n\nconst categoryCount = computed(() =>\n Math.max(0, ...validSeries.value.map((item) => item.data.length)),\n);\n\n// Shared category index range, so the a11y table stays rectangular even when\n// series have different lengths.\nconst axisIndices = computed(() =>\n Array.from({ length: categoryCount.value }, (_, index) => index),\n);\n\nconst hasData = computed(() => validSeries.value.length > 0 && categoryCount.value > 0);\n\nconst resolvedShowLegend = computed(() =>\n props.showLegend ?? validSeries.value.length > 1,\n);\n\nconst colorFor = (index: number, series: TChartSeries) =>\n series.color ?? `var(--tree-color-chart-${(index % 8) + 1})`;\n\nconst legendItems = computed(() =>\n validSeries.value.map((series, index) => ({\n name: series.name,\n color: colorFor(index, series),\n })),\n);\n\nconst formatValue = (value: number) => {\n if (props.valueFormat) return props.valueFormat(value);\n if (Math.abs(value) >= 1000) return value.toLocaleString('en-US');\n return `${value}`;\n};\n\n// --- layout ---------------------------------------------------------------\nconst margin = computed(() => ({\n top: 12,\n right: 12,\n bottom: props.showXAxis && props.labels.length ? 28 : 10,\n left: props.showYAxis ? 44 : 10,\n}));\n\nconst plot = computed(() => {\n const { top, right, bottom, left } = margin.value;\n return {\n left,\n right: width.value - right,\n top,\n bottom: props.height - bottom,\n get innerWidth() {\n return this.right - this.left;\n },\n get innerHeight() {\n return this.bottom - this.top;\n },\n };\n});\n\nconst stackedTotals = computed(() => {\n if (!props.stacked || props.type !== 'bar') return [];\n return Array.from({ length: categoryCount.value }, (_, index) =>\n validSeries.value.reduce((sum, series) => sum + Math.max(0, series.data[index] ?? 0), 0),\n );\n});\n\nconst scale = computed(() => {\n const baselineZero = props.type === 'bar' || props.type === 'area';\n const flat = validSeries.value.flatMap((series) => series.data);\n\n const source = props.stacked && props.type === 'bar' ? stackedTotals.value : flat;\n const rawMin = source.length ? Math.min(...source) : 0;\n const rawMax = source.length ? Math.max(...source) : 1;\n\n const min = props.minY ?? (baselineZero ? Math.min(0, rawMin) : rawMin);\n const max = props.maxY ?? Math.max(rawMax, min + 1);\n\n return niceScale(min, max, props.yTicks);\n});\n\nconst yScale = computed(() =>\n linearScale([scale.value.min, scale.value.max], [plot.value.bottom, plot.value.top]),\n);\n\nconst baselineY = computed(() => yScale.value(Math.max(scale.value.min, 0)));\n\n// x-position helpers -------------------------------------------------------\nconst pointX = (index: number) => {\n const count = categoryCount.value;\n if (count <= 1) return plot.value.left + plot.value.innerWidth / 2;\n return plot.value.left + (index / (count - 1)) * plot.value.innerWidth;\n};\n\nconst band = computed(() => plot.value.innerWidth / Math.max(1, categoryCount.value));\nconst bandCenter = (index: number) => plot.value.left + band.value * (index + 0.5);\n\n// active category (for line/area points and axis labels)\nconst activeX = (index: number) =>\n props.type === 'bar' ? bandCenter(index) : pointX(index);\n\n// --- line / area geometry -------------------------------------------------\nconst linePaths = computed(() =>\n props.type === 'bar'\n ? []\n : validSeries.value.map((series, seriesIndex) => {\n const points: ChartPoint[] = series.data.map((value, index) => ({\n x: pointX(index),\n y: yScale.value(value),\n }));\n return {\n color: colorFor(seriesIndex, series),\n line: buildLinePath(points, props.smooth),\n area: props.type === 'area' ? buildAreaPath(points, plot.value.bottom, props.smooth) : '',\n points,\n };\n }),\n);\n\n// --- bar geometry ---------------------------------------------------------\nconst BAR_MAX = 24;\nconst BAR_GAP = 2;\n\nconst bars = computed(() => {\n if (props.type !== 'bar') return [];\n const output: Array<{\n x: number;\n y: number;\n width: number;\n height: number;\n color: string;\n seriesIndex: number;\n index: number;\n }> = [];\n\n const bandWidth = band.value;\n const groupWidth = Math.min(bandWidth * 0.72, BAR_MAX * validSeries.value.length + BAR_GAP);\n\n for (let index = 0; index < categoryCount.value; index += 1) {\n const center = bandCenter(index);\n\n if (props.stacked) {\n const barWidth = Math.min(groupWidth, BAR_MAX);\n let cursorBottom = baselineY.value;\n validSeries.value.forEach((series, seriesIndex) => {\n const value = Math.max(0, series.data[index] ?? 0);\n // Pixel height is proportional to the segment's own value.\n const segmentHeight = baselineY.value - yScale.value(value);\n if (segmentHeight <= 0) return;\n const top = cursorBottom - segmentHeight;\n output.push({\n x: center - barWidth / 2,\n y: top,\n // Trim 2px off each segment for a surface gap between stacked marks.\n height: Math.max(0, segmentHeight - BAR_GAP),\n width: barWidth,\n color: colorFor(seriesIndex, series),\n seriesIndex,\n index,\n });\n cursorBottom = top;\n });\n } else {\n const barWidth = Math.max(1, (groupWidth - BAR_GAP * (validSeries.value.length - 1)) / validSeries.value.length);\n validSeries.value.forEach((series, seriesIndex) => {\n const value = series.data[index] ?? 0;\n const top = yScale.value(value);\n const start = center - groupWidth / 2 + seriesIndex * (barWidth + BAR_GAP);\n output.push({\n x: start,\n y: Math.min(top, baselineY.value),\n width: barWidth,\n height: Math.abs(baselineY.value - top),\n color: colorFor(seriesIndex, series),\n seriesIndex,\n index,\n });\n });\n }\n }\n\n return output;\n});\n\n// --- gridlines & axes -----------------------------------------------------\nconst gridLines = computed(() =>\n scale.value.ticks.map((tick) => ({\n value: tick,\n y: yScale.value(tick),\n label: formatValue(tick),\n })),\n);\n\nconst xLabels = computed(() => {\n if (!props.showXAxis) return [];\n const count = categoryCount.value;\n const maxLabels = Math.max(2, Math.floor(plot.value.innerWidth / 52));\n const step = Math.max(1, Math.ceil(count / maxLabels));\n return props.labels.slice(0, count).map((label, index) => ({\n label,\n x: activeX(index),\n show: index % step === 0 || index === count - 1,\n }));\n});\n\n// --- interaction ----------------------------------------------------------\nconst activeIndex = ref<number | null>(null);\n\nconst pointerToIndex = (clientX: number) => {\n const element = canvasRef.value;\n if (!element) return null;\n const rect = element.getBoundingClientRect();\n const x = clientX - rect.left;\n const count = categoryCount.value;\n if (count === 0) return null;\n\n if (props.type === 'bar') {\n const index = Math.floor((x - plot.value.left) / band.value);\n return Math.min(count - 1, Math.max(0, index));\n }\n\n const ratio = (x - plot.value.left) / Math.max(1, plot.value.innerWidth);\n return Math.min(count - 1, Math.max(0, Math.round(ratio * (count - 1))));\n};\n\nconst onPointerMove = (event: PointerEvent) => {\n if (!props.showTooltip || !hasData.value) return;\n activeIndex.value = pointerToIndex(event.clientX);\n};\n\nconst onPointerLeave = () => {\n activeIndex.value = null;\n};\n\nconst activePayload = computed<TChartActivePayload | null>(() => {\n const index = activeIndex.value;\n if (index === null) return null;\n return {\n index,\n label: props.labels[index] ?? `#${index + 1}`,\n values: validSeries.value.map((series, seriesIndex) => ({\n name: series.name,\n value: series.data[index] ?? 0,\n color: colorFor(seriesIndex, series),\n })),\n };\n});\n\nconst tooltipStyle = computed(() => {\n const index = activeIndex.value;\n if (index === null) return {};\n const x = activeX(index);\n const clamped = Math.min(Math.max(x, 64), width.value - 64);\n return { left: `${clamped}px`, top: `${plot.value.top}px` };\n});\n\nconst activeMarkers = computed(() => {\n const index = activeIndex.value;\n if (index === null || props.type === 'bar') return [];\n return linePaths.value.map((path) => ({\n x: path.points[index]?.x ?? 0,\n y: path.points[index]?.y ?? 0,\n color: path.color,\n }));\n});\n\nconst onClick = () => {\n if (activePayload.value) emit('point-click', activePayload.value);\n};\n\nconst resolvedLabel = computed(\n () => props.ariaLabel ?? `${props.type} chart with ${validSeries.value.length} series`,\n);\n\nconst rootClasses = computed(() => [\n 't-chart',\n `t-chart--${props.type}`,\n {\n 'is-animated': props.animated,\n 'is-loading': props.loading,\n },\n]);\n</script>\n\n<template>\n <div :class=\"rootClasses\">\n <div\n ref=\"canvasRef\"\n class=\"t-chart__canvas\"\n :style=\"{ height: `${height}px` }\"\n >\n <div\n v-if=\"loading\"\n class=\"t-chart__loading\"\n >\n <span\n v-for=\"n in 5\"\n :key=\"n\"\n class=\"t-chart__loading-bar\"\n :style=\"{ height: `${30 + ((n * 37) % 60)}%` }\"\n />\n </div>\n\n <div\n v-else-if=\"!hasData\"\n class=\"t-chart__empty\"\n >\n <slot name=\"empty\">\n No data to display\n </slot>\n </div>\n\n <template v-else>\n <svg\n class=\"t-chart__svg\"\n :width=\"width\"\n :height=\"height\"\n :viewBox=\"`0 0 ${width} ${height}`\"\n aria-hidden=\"true\"\n @pointermove=\"onPointerMove\"\n @pointerleave=\"onPointerLeave\"\n @click=\"onClick\"\n >\n <!-- gridlines + y ticks -->\n <g class=\"t-chart__grid\">\n <template\n v-for=\"line in gridLines\"\n :key=\"line.value\"\n >\n <line\n v-if=\"showGrid\"\n class=\"t-chart__gridline\"\n :x1=\"plot.left\"\n :x2=\"plot.right\"\n :y1=\"line.y\"\n :y2=\"line.y\"\n />\n <text\n v-if=\"showYAxis\"\n class=\"t-chart__axis-label t-chart__axis-label--y\"\n :x=\"plot.left - 8\"\n :y=\"line.y\"\n >\n {{ line.label }}\n </text>\n </template>\n </g>\n\n <!-- area fills -->\n <g class=\"t-chart__areas\">\n <path\n v-for=\"(path, index) in linePaths\"\n v-show=\"type === 'area'\"\n :key=\"`area-${index}`\"\n class=\"t-chart__area\"\n :d=\"path.area\"\n :style=\"{ color: path.color }\"\n />\n </g>\n\n <!-- bars -->\n <g class=\"t-chart__bars\">\n <rect\n v-for=\"(bar, index) in bars\"\n :key=\"`bar-${index}`\"\n class=\"t-chart__bar\"\n :x=\"bar.x\"\n :y=\"bar.y\"\n :width=\"bar.width\"\n :height=\"bar.height\"\n :rx=\"Math.min(4, bar.width / 2)\"\n :style=\"{ color: bar.color, animationDelay: `${bar.index * 40}ms` }\"\n :class=\"{ 'is-active': activeIndex === bar.index }\"\n />\n </g>\n\n <!-- lines + optional static points -->\n <g class=\"t-chart__lines\">\n <template\n v-for=\"(path, index) in linePaths\"\n :key=\"`line-${index}`\"\n >\n <path\n v-if=\"type !== 'bar'\"\n class=\"t-chart__line\"\n :d=\"path.line\"\n path-length=\"1\"\n :style=\"{ color: path.color }\"\n />\n <template v-if=\"showPoints && type !== 'bar'\">\n <circle\n v-for=\"(point, pointIndex) in path.points\"\n :key=\"`point-${index}-${pointIndex}`\"\n class=\"t-chart__point\"\n :cx=\"point.x\"\n :cy=\"point.y\"\n r=\"3\"\n :style=\"{ color: path.color }\"\n />\n </template>\n </template>\n </g>\n\n <!-- hover crosshair + active markers -->\n <g\n v-if=\"activeIndex !== null && showTooltip\"\n class=\"t-chart__hover\"\n >\n <line\n class=\"t-chart__crosshair\"\n :x1=\"activeX(activeIndex)\"\n :x2=\"activeX(activeIndex)\"\n :y1=\"plot.top\"\n :y2=\"plot.bottom\"\n />\n <circle\n v-for=\"(marker, index) in activeMarkers\"\n :key=\"`marker-${index}`\"\n class=\"t-chart__marker\"\n :cx=\"marker.x\"\n :cy=\"marker.y\"\n r=\"4\"\n :style=\"{ color: marker.color }\"\n />\n </g>\n\n <!-- x-axis labels -->\n <g\n v-if=\"showXAxis\"\n class=\"t-chart__x-axis\"\n >\n <text\n v-for=\"(item, index) in xLabels\"\n v-show=\"item.show\"\n :key=\"`x-${index}`\"\n class=\"t-chart__axis-label t-chart__axis-label--x\"\n :x=\"item.x\"\n :y=\"height - 8\"\n >\n {{ item.label }}\n </text>\n </g>\n </svg>\n\n <div\n v-if=\"showTooltip && activePayload\"\n class=\"t-chart__tooltip\"\n :style=\"tooltipStyle\"\n role=\"presentation\"\n >\n <slot\n name=\"tooltip\"\n v-bind=\"activePayload\"\n >\n <p class=\"t-chart__tooltip-label\">\n {{ activePayload.label }}\n </p>\n <ul class=\"t-chart__tooltip-list\">\n <li\n v-for=\"(item, index) in activePayload.values\"\n :key=\"index\"\n class=\"t-chart__tooltip-item\"\n >\n <span\n class=\"t-chart__tooltip-dot\"\n :style=\"{ background: item.color }\"\n aria-hidden=\"true\"\n />\n <span class=\"t-chart__tooltip-name\">{{ item.name }}</span>\n <span class=\"t-chart__tooltip-value\">{{ formatValue(item.value) }}</span>\n </li>\n </ul>\n </slot>\n </div>\n </template>\n </div>\n\n <div\n v-if=\"resolvedShowLegend && hasData\"\n class=\"t-chart__legend\"\n >\n <slot\n name=\"legend\"\n :series=\"legendItems\"\n >\n <span\n v-for=\"(item, index) in legendItems\"\n :key=\"index\"\n class=\"t-chart__legend-item\"\n >\n <span\n class=\"t-chart__legend-swatch\"\n :style=\"{ background: item.color }\"\n aria-hidden=\"true\"\n />\n {{ item.name }}\n </span>\n </slot>\n </div>\n\n <!-- Accessible data table: the chart's data as text for assistive tech. -->\n <table class=\"t-chart__a11y t-visually-hidden\">\n <caption>{{ resolvedLabel }}</caption>\n <thead>\n <tr>\n <th scope=\"col\">\n Series\n </th>\n <th\n v-for=\"index in axisIndices\"\n :key=\"index\"\n scope=\"col\"\n >\n {{ labels[index] ?? '' }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr\n v-for=\"(row, rowIndex) in validSeries\"\n :key=\"rowIndex\"\n >\n <th scope=\"row\">\n {{ row.name }}\n </th>\n <td\n v-for=\"index in axisIndices\"\n :key=\"index\"\n >\n {{ row.data[index] === undefined ? '' : formatValue(row.data[index]) }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import e from "./TChart.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/TChart.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
6
+
7
+ //# sourceMappingURL=TChart.js.map