@treeui/vue 0.19.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/TAppShell.cjs.map +1 -1
- package/dist/components/TAppShell.js.map +1 -1
- package/dist/components/TAppShell.vue.d.ts.map +1 -1
- package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.cjs +1 -1
- package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
- package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.js +9 -5
- package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +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 = computed(() => getTreeIcon('x'));\nconst MenuIcon = computed(() => getTreeIcon('menu'));\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 /**\n * Hide the shell chrome (header and sidebar) so the content fills the\n * viewport. The default slot stays mounted across the toggle.\n */\n immersive?: boolean;\n defaultImmersive?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n /** Render the built-in collapse toggle in the sidebar footer (desktop). */\n showCollapseButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n collapseLabel?: string;\n expandLabel?: 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 immersive: undefined,\n defaultImmersive: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n showCollapseButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n collapseLabel: 'Collapse sidebar',\n expandLabel: 'Expand 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 'update:immersive': [value: boolean];\n 'immersive-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n immersive: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n toggleImmersive: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n /** Header region over the sidebar rail; the `header` slot then aligns with the content panel. */\n 'header-start'?: (props: AppShellSlotProps) => unknown;\n /** Pinned above the sidebar body, inset to line up with nav item icons. */\n 'sidebar-header'?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n /** Pinned to the bottom of the sidebar, inset to line up with nav item icons. */\n 'sidebar-footer'?: (props: AppShellSlotProps) => unknown;\n /**\n * Immersive hides the chrome, so the content is the only place an exit\n * control can live — `immersive` and `toggleImmersive` are passed through.\n */\n default?: (\n props: Pick<AppShellSlotProps, 'mobile' | 'collapsed' | 'immersive' | 'toggleImmersive'>,\n ) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n 'collapse-icon'?: (props: { collapsed: 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\nconst { value: isImmersive, setValue: setImmersive } = useControllableOpen(\n toRef(props, 'immersive'),\n props.defaultImmersive,\n (value) => {\n emit('update:immersive', value);\n emit('immersive-change', value);\n },\n);\n\nconst toggleImmersive = () => {\n setImmersive(!isImmersive.value);\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// Collapsing is desktop-only, so the built-in toggle follows the same rule.\nconst showCollapseToggle = computed(\n () => !isMobile.value && props.collapsible && props.showCollapseButton,\n);\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\n// The glyph points the way the rail will move: collapsing a left sidebar folds\n// it left, expanding it pushes right (mirrored for a right-anchored sidebar).\nconst CollapseIcon = computed(() => {\n const pointsStart = props.side === 'right' ? effectiveCollapsed.value : !effectiveCollapsed.value;\n return getTreeIcon(pointsStart ? 'panel-left' : 'panel-right');\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 'is-immersive': isImmersive.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 immersive: isImmersive.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n toggleImmersive,\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\n class=\"t-app-shell__header\"\n :class=\"{ 'has-start': Boolean($slots['header-start']) }\"\n >\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 <MenuIcon\n v-bind=\"treeIconDefaults\"\n :size=\"24\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n\n <div\n v-if=\"$slots['header-start']\"\n class=\"t-app-shell__header-start\"\n >\n <slot\n name=\"header-start\"\n v-bind=\"slotProps\"\n />\n </div>\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer'] || showCollapseToggle\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n\n <button\n v-if=\"showCollapseToggle\"\n type=\"button\"\n class=\"t-app-shell__collapse-button\"\n :aria-label=\"isCollapsed ? expandLabel : collapseLabel\"\n :aria-expanded=\"!isCollapsed\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleCollapsed\"\n >\n <slot\n name=\"collapse-icon\"\n :collapsed=\"effectiveCollapsed\"\n >\n <CollapseIcon\n v-bind=\"treeIconDefaults\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n </div>\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n :immersive=\"isImmersive\"\n :toggle-immersive=\"toggleImmersive\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen && !isImmersive\"\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer']\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n </div>\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":""}
|
|
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 = computed(() => getTreeIcon('x'));\nconst MenuIcon = computed(() => getTreeIcon('menu'));\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 /**\n * Hide the shell chrome (header and sidebar) so the content fills the\n * viewport. The default slot stays mounted across the toggle.\n */\n immersive?: boolean;\n defaultImmersive?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n /** Render the built-in collapse toggle in the sidebar footer (desktop). */\n showCollapseButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n collapseLabel?: string;\n expandLabel?: 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 immersive: undefined,\n defaultImmersive: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n showCollapseButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n collapseLabel: 'Collapse sidebar',\n expandLabel: 'Expand 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 'update:immersive': [value: boolean];\n 'immersive-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n immersive: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n toggleImmersive: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n /** Header region over the sidebar rail; the `header` slot then aligns with the content panel. */\n 'header-start'?: (props: AppShellSlotProps) => unknown;\n /** Pinned above the sidebar body, inset to line up with nav item icons. */\n 'sidebar-header'?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n /** Pinned to the bottom of the sidebar, inset to line up with nav item icons. */\n 'sidebar-footer'?: (props: AppShellSlotProps) => unknown;\n /**\n * Immersive hides the chrome, so the content is the only place an exit\n * control can live — `immersive` and `toggleImmersive` are passed through.\n */\n default?: (\n props: Pick<AppShellSlotProps, 'mobile' | 'collapsed' | 'immersive' | 'toggleImmersive'>,\n ) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n 'collapse-icon'?: (props: { collapsed: 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\nconst { value: isImmersive, setValue: setImmersive } = useControllableOpen(\n toRef(props, 'immersive'),\n props.defaultImmersive,\n (value) => {\n emit('update:immersive', value);\n emit('immersive-change', value);\n },\n);\n\nconst toggleImmersive = () => {\n setImmersive(!isImmersive.value);\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// Collapsing is desktop-only, so the built-in toggle follows the same rule.\nconst showCollapseToggle = computed(\n () => !isMobile.value && props.collapsible && props.showCollapseButton,\n);\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\n// The glyph points the way the rail will move: collapsing a left sidebar folds\n// it left, expanding it pushes right (mirrored for a right-anchored sidebar).\nconst CollapseIcon = computed(() => {\n const pointsStart = props.side === 'right' ? effectiveCollapsed.value : !effectiveCollapsed.value;\n return getTreeIcon(pointsStart ? 'panel-left' : 'panel-right');\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\n// Entering immersive removes the drawer from the DOM without changing\n// `sidebarOpen`, so the sidebarOpen watcher never fires and would leave the\n// body scroll-locked with focus stranded on an unmounted surface. Release and\n// re-arm here, mirroring the responsive-mode watcher below.\nwatch(isImmersive, (immersive) => {\n if (!isSidebarOpen.value || !isMobile.value) {\n return;\n }\n\n if (immersive) {\n unlockBodyScroll();\n nextTick(() => {\n previousFocusedElement.value?.focus();\n });\n } else {\n lockBodyScroll();\n focusSurface();\n }\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 'is-immersive': isImmersive.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 immersive: isImmersive.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n toggleImmersive,\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\n class=\"t-app-shell__header\"\n :class=\"{ 'has-start': Boolean($slots['header-start']) }\"\n >\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 <MenuIcon\n v-bind=\"treeIconDefaults\"\n :size=\"24\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n\n <div\n v-if=\"$slots['header-start']\"\n class=\"t-app-shell__header-start\"\n >\n <slot\n name=\"header-start\"\n v-bind=\"slotProps\"\n />\n </div>\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer'] || showCollapseToggle\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n\n <button\n v-if=\"showCollapseToggle\"\n type=\"button\"\n class=\"t-app-shell__collapse-button\"\n :aria-label=\"isCollapsed ? expandLabel : collapseLabel\"\n :aria-expanded=\"!isCollapsed\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleCollapsed\"\n >\n <slot\n name=\"collapse-icon\"\n :collapsed=\"effectiveCollapsed\"\n >\n <CollapseIcon\n v-bind=\"treeIconDefaults\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n </div>\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n :immersive=\"isImmersive\"\n :toggle-immersive=\"toggleImmersive\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen && !isImmersive\"\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer']\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n </div>\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":""}
|
|
@@ -1 +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 = computed(() => getTreeIcon('x'));\nconst MenuIcon = computed(() => getTreeIcon('menu'));\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 /**\n * Hide the shell chrome (header and sidebar) so the content fills the\n * viewport. The default slot stays mounted across the toggle.\n */\n immersive?: boolean;\n defaultImmersive?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n /** Render the built-in collapse toggle in the sidebar footer (desktop). */\n showCollapseButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n collapseLabel?: string;\n expandLabel?: 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 immersive: undefined,\n defaultImmersive: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n showCollapseButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n collapseLabel: 'Collapse sidebar',\n expandLabel: 'Expand 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 'update:immersive': [value: boolean];\n 'immersive-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n immersive: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n toggleImmersive: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n /** Header region over the sidebar rail; the `header` slot then aligns with the content panel. */\n 'header-start'?: (props: AppShellSlotProps) => unknown;\n /** Pinned above the sidebar body, inset to line up with nav item icons. */\n 'sidebar-header'?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n /** Pinned to the bottom of the sidebar, inset to line up with nav item icons. */\n 'sidebar-footer'?: (props: AppShellSlotProps) => unknown;\n /**\n * Immersive hides the chrome, so the content is the only place an exit\n * control can live — `immersive` and `toggleImmersive` are passed through.\n */\n default?: (\n props: Pick<AppShellSlotProps, 'mobile' | 'collapsed' | 'immersive' | 'toggleImmersive'>,\n ) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n 'collapse-icon'?: (props: { collapsed: 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\nconst { value: isImmersive, setValue: setImmersive } = useControllableOpen(\n toRef(props, 'immersive'),\n props.defaultImmersive,\n (value) => {\n emit('update:immersive', value);\n emit('immersive-change', value);\n },\n);\n\nconst toggleImmersive = () => {\n setImmersive(!isImmersive.value);\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// Collapsing is desktop-only, so the built-in toggle follows the same rule.\nconst showCollapseToggle = computed(\n () => !isMobile.value && props.collapsible && props.showCollapseButton,\n);\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\n// The glyph points the way the rail will move: collapsing a left sidebar folds\n// it left, expanding it pushes right (mirrored for a right-anchored sidebar).\nconst CollapseIcon = computed(() => {\n const pointsStart = props.side === 'right' ? effectiveCollapsed.value : !effectiveCollapsed.value;\n return getTreeIcon(pointsStart ? 'panel-left' : 'panel-right');\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 'is-immersive': isImmersive.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 immersive: isImmersive.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n toggleImmersive,\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\n class=\"t-app-shell__header\"\n :class=\"{ 'has-start': Boolean($slots['header-start']) }\"\n >\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 <MenuIcon\n v-bind=\"treeIconDefaults\"\n :size=\"24\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n\n <div\n v-if=\"$slots['header-start']\"\n class=\"t-app-shell__header-start\"\n >\n <slot\n name=\"header-start\"\n v-bind=\"slotProps\"\n />\n </div>\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer'] || showCollapseToggle\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n\n <button\n v-if=\"showCollapseToggle\"\n type=\"button\"\n class=\"t-app-shell__collapse-button\"\n :aria-label=\"isCollapsed ? expandLabel : collapseLabel\"\n :aria-expanded=\"!isCollapsed\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleCollapsed\"\n >\n <slot\n name=\"collapse-icon\"\n :collapsed=\"effectiveCollapsed\"\n >\n <CollapseIcon\n v-bind=\"treeIconDefaults\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n </div>\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n :immersive=\"isImmersive\"\n :toggle-immersive=\"toggleImmersive\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen && !isImmersive\"\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer']\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n </div>\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":""}
|
|
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 = computed(() => getTreeIcon('x'));\nconst MenuIcon = computed(() => getTreeIcon('menu'));\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 /**\n * Hide the shell chrome (header and sidebar) so the content fills the\n * viewport. The default slot stays mounted across the toggle.\n */\n immersive?: boolean;\n defaultImmersive?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n /** Render the built-in collapse toggle in the sidebar footer (desktop). */\n showCollapseButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n collapseLabel?: string;\n expandLabel?: 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 immersive: undefined,\n defaultImmersive: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n showCollapseButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n collapseLabel: 'Collapse sidebar',\n expandLabel: 'Expand 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 'update:immersive': [value: boolean];\n 'immersive-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n immersive: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n toggleImmersive: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n /** Header region over the sidebar rail; the `header` slot then aligns with the content panel. */\n 'header-start'?: (props: AppShellSlotProps) => unknown;\n /** Pinned above the sidebar body, inset to line up with nav item icons. */\n 'sidebar-header'?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n /** Pinned to the bottom of the sidebar, inset to line up with nav item icons. */\n 'sidebar-footer'?: (props: AppShellSlotProps) => unknown;\n /**\n * Immersive hides the chrome, so the content is the only place an exit\n * control can live — `immersive` and `toggleImmersive` are passed through.\n */\n default?: (\n props: Pick<AppShellSlotProps, 'mobile' | 'collapsed' | 'immersive' | 'toggleImmersive'>,\n ) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n 'collapse-icon'?: (props: { collapsed: 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\nconst { value: isImmersive, setValue: setImmersive } = useControllableOpen(\n toRef(props, 'immersive'),\n props.defaultImmersive,\n (value) => {\n emit('update:immersive', value);\n emit('immersive-change', value);\n },\n);\n\nconst toggleImmersive = () => {\n setImmersive(!isImmersive.value);\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// Collapsing is desktop-only, so the built-in toggle follows the same rule.\nconst showCollapseToggle = computed(\n () => !isMobile.value && props.collapsible && props.showCollapseButton,\n);\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\n// The glyph points the way the rail will move: collapsing a left sidebar folds\n// it left, expanding it pushes right (mirrored for a right-anchored sidebar).\nconst CollapseIcon = computed(() => {\n const pointsStart = props.side === 'right' ? effectiveCollapsed.value : !effectiveCollapsed.value;\n return getTreeIcon(pointsStart ? 'panel-left' : 'panel-right');\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\n// Entering immersive removes the drawer from the DOM without changing\n// `sidebarOpen`, so the sidebarOpen watcher never fires and would leave the\n// body scroll-locked with focus stranded on an unmounted surface. Release and\n// re-arm here, mirroring the responsive-mode watcher below.\nwatch(isImmersive, (immersive) => {\n if (!isSidebarOpen.value || !isMobile.value) {\n return;\n }\n\n if (immersive) {\n unlockBodyScroll();\n nextTick(() => {\n previousFocusedElement.value?.focus();\n });\n } else {\n lockBodyScroll();\n focusSurface();\n }\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 'is-immersive': isImmersive.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 immersive: isImmersive.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n toggleImmersive,\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\n class=\"t-app-shell__header\"\n :class=\"{ 'has-start': Boolean($slots['header-start']) }\"\n >\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 <MenuIcon\n v-bind=\"treeIconDefaults\"\n :size=\"24\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n\n <div\n v-if=\"$slots['header-start']\"\n class=\"t-app-shell__header-start\"\n >\n <slot\n name=\"header-start\"\n v-bind=\"slotProps\"\n />\n </div>\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer'] || showCollapseToggle\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n\n <button\n v-if=\"showCollapseToggle\"\n type=\"button\"\n class=\"t-app-shell__collapse-button\"\n :aria-label=\"isCollapsed ? expandLabel : collapseLabel\"\n :aria-expanded=\"!isCollapsed\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleCollapsed\"\n >\n <slot\n name=\"collapse-icon\"\n :collapsed=\"effectiveCollapsed\"\n >\n <CollapseIcon\n v-bind=\"treeIconDefaults\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n </div>\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n :immersive=\"isImmersive\"\n :toggle-immersive=\"toggleImmersive\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen && !isImmersive\"\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer']\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n </div>\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TAppShell.vue.d.ts","sourceRoot":"","sources":["../../src/components/TAppShell.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TAppShell.vue.d.ts","sourceRoot":"","sources":["../../src/components/TAppShell.vue"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +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=[`aria-label`,`aria-expanded`,`aria-controls`],
|
|
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=[`aria-label`,`aria-expanded`,`aria-controls`],o={key:1,class:`t-app-shell__header-start`},ee={class:`t-app-shell__header-content`},s=[`id`,`aria-label`],c={key:0,class:`t-app-shell__sidebar-header`},l={class:`t-app-shell__sidebar-body`},u={key:1,class:`t-app-shell__sidebar-footer`},d=[`aria-label`,`aria-expanded`,`aria-controls`],f={class:`t-app-shell__main`},p={key:0,class:`t-app-shell__overlay`},m=[`id`,`aria-label`],h={class:`t-app-shell__drawer-topbar`},g=[`aria-label`],_={class:`t-app-shell__drawer-body`},v={key:0,class:`t-app-shell__sidebar-header`},y={class:`t-app-shell__sidebar-body`},b={key:1,class:`t-app-shell__sidebar-footer`},x=(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},immersive:{type:Boolean,default:void 0},defaultImmersive:{type:Boolean,default:!1},closeOnEscape:{type:Boolean,default:!0},closeOnOverlay:{type:Boolean,default:!0},showMenuButton:{type:Boolean,default:!0},showCollapseButton:{type:Boolean,default:!0},menuLabel:{default:`Open menu`},closeLabel:{default:`Close menu`},sidebarLabel:{default:`Sidebar`},collapseLabel:{default:`Collapse sidebar`},expandLabel:{default:`Expand sidebar`}},emits:[`update:collapsed`,`collapse-change`,`update:sidebarOpen`,`sidebar-open-change`,`update:immersive`,`immersive-change`],setup(x,{emit:S}){let te=(0,n.computed)(()=>(0,i.getTreeIcon)(`x`)),ne=(0,n.computed)(()=>(0,i.getTreeIcon)(`menu`)),C=x,w=S,T=(0,n.useAttrs)(),E=(0,r.createId)(`t-app-shell-sidebar`),{value:D,setValue:O}=e.useControllableOpen((0,n.toRef)(C,`collapsed`),C.defaultCollapsed,e=>{w(`update:collapsed`,e),w(`collapse-change`,e)}),{value:k,setValue:A}=e.useControllableOpen((0,n.toRef)(C,`sidebarOpen`),C.defaultSidebarOpen,e=>{w(`update:sidebarOpen`,e),w(`sidebar-open-change`,e)}),{value:j,setValue:re}=e.useControllableOpen((0,n.toRef)(C,`immersive`),C.defaultImmersive,e=>{w(`update:immersive`,e),w(`immersive-change`,e)}),M=()=>{re(!j.value)},N=(0,n.ref)(!1),P=null,F=e=>{N.value=e.matches},I=()=>{typeof window>`u`||typeof window.matchMedia!=`function`||(L(),P=window.matchMedia(`(max-width: ${C.breakpoint})`),N.value=P.matches,P.addEventListener?.(`change`,F))},L=()=>{P?.removeEventListener?.(`change`,F),P=null},R=(0,n.computed)(()=>C.mobile??N.value),z=(0,n.computed)(()=>!R.value&&C.collapsible&&C.showCollapseButton),B=(0,n.computed)(()=>!R.value&&D.value),V=(0,n.computed)(()=>(0,i.getTreeIcon)((C.side===`right`?B.value:!B.value)?`panel-left`:`panel-right`)),H=(0,n.ref)(null),U=(0,n.ref)(null),W=``,G=!1,K=()=>{typeof document>`u`||G||(W=document.body.style.overflow,document.body.style.overflow=`hidden`,G=!0)},q=()=>{typeof document>`u`||!G||(document.body.style.overflow=W,G=!1)},J=()=>{(0,n.nextTick)(()=>{H.value&&((0,r.focusFirst)(H.value)||H.value.focus())})},Y=()=>{U.value=typeof document>`u`?null:document.activeElement,A(!0)},X=()=>{A(!1)},Z=()=>{k.value?X():Y()},Q=()=>{C.collapsible&&O(!D.value)},ie=()=>{C.closeOnOverlay&&X()},ae=e=>{if((0,r.isEscapeKey)(e)&&C.closeOnEscape){e.preventDefault(),X();return}if(e.key!==`Tab`||!H.value)return;let t=(0,r.getFocusableElements)(H.value);if(!t.length){e.preventDefault(),H.value.focus();return}let n=t[0],i=t[t.length-1],a=document.activeElement;e.shiftKey&&a===n&&(e.preventDefault(),(0,r.focusLast)(H.value)),!e.shiftKey&&a===i&&(e.preventDefault(),(0,r.focusFirst)(H.value))},oe=(e,t)=>{if(e&&R.value){K(),J();return}t&&(q(),(0,n.nextTick)(()=>{U.value?.focus()}))};(0,n.watch)(k,(e,t)=>{oe(e,!!t)}),(0,n.watch)(j,e=>{!k.value||!R.value||(e?(q(),(0,n.nextTick)(()=>{U.value?.focus()})):(K(),J()))}),(0,n.watch)(R,e=>{k.value&&(e?(K(),J()):q())}),(0,n.watch)(()=>C.breakpoint,()=>{typeof window<`u`&&I()}),(0,n.onMounted)(()=>{I()}),(0,n.onBeforeUnmount)(()=>{L(),q()}),(0,n.provide)(t.treeSidebarInjectionKey,{collapsed:B});let se=(0,n.computed)(()=>[`t-app-shell`,`t-app-shell--${C.side}`,{"is-mobile":R.value,"is-collapsed":!R.value&&C.collapsible&&D.value,"is-sidebar-open":R.value&&k.value,"is-immersive":j.value},T.class]),ce=(0,n.computed)(()=>[{"--tree-app-shell-sidebar-width":C.sidebarWidth,"--tree-app-shell-collapsed-width":C.collapsedWidth},T.style]),le=(0,n.computed)(()=>{let{class:e,style:t,...n}=T;return n}),$=(0,n.computed)(()=>({mobile:R.value,collapsed:B.value,sidebarOpen:k.value,immersive:j.value,toggleSidebar:Z,closeSidebar:X,toggleCollapsed:Q,toggleImmersive:M}));return(e,t)=>((0,n.openBlock)(),(0,n.createBlock)((0,n.resolveDynamicComponent)(x.as),(0,n.mergeProps)(le.value,{class:se.value,style:ce.value,"data-mobile":R.value?`true`:`false`}),{default:(0,n.withCtx)(()=>[(0,n.createElementVNode)(`header`,{class:(0,n.normalizeClass)([`t-app-shell__header`,{"has-start":!!e.$slots[`header-start`]}])},[R.value&&x.showMenuButton?((0,n.openBlock)(),(0,n.createElementBlock)(`button`,{key:0,type:`button`,class:`t-app-shell__menu-button`,"aria-label":x.menuLabel,"aria-expanded":(0,n.unref)(k),"aria-controls":(0,n.unref)(E),onClick:Z},[(0,n.renderSlot)(e.$slots,`menu-icon`,{sidebarOpen:(0,n.unref)(k)},()=>[(0,n.createVNode)((0,n.unref)(ne),(0,n.mergeProps)((0,n.unref)(i.treeIconDefaults),{size:24,"aria-hidden":`true`}),null,16)])],8,a)):(0,n.createCommentVNode)(``,!0),e.$slots[`header-start`]?((0,n.openBlock)(),(0,n.createElementBlock)(`div`,o,[(0,n.renderSlot)(e.$slots,`header-start`,(0,n.normalizeProps)((0,n.guardReactiveProps)($.value)))])):(0,n.createCommentVNode)(``,!0),(0,n.createElementVNode)(`div`,ee,[(0,n.renderSlot)(e.$slots,`header`,(0,n.normalizeProps)((0,n.guardReactiveProps)($.value)))])],2),R.value?(0,n.createCommentVNode)(``,!0):((0,n.openBlock)(),(0,n.createElementBlock)(`aside`,{key:0,id:(0,n.unref)(E),class:`t-app-shell__sidebar`,"aria-label":x.sidebarLabel},[e.$slots[`sidebar-header`]?((0,n.openBlock)(),(0,n.createElementBlock)(`div`,c,[(0,n.renderSlot)(e.$slots,`sidebar-header`,(0,n.normalizeProps)((0,n.guardReactiveProps)($.value)))])):(0,n.createCommentVNode)(``,!0),(0,n.createElementVNode)(`div`,l,[(0,n.renderSlot)(e.$slots,`sidebar`,(0,n.normalizeProps)((0,n.guardReactiveProps)($.value)))]),e.$slots[`sidebar-footer`]||z.value?((0,n.openBlock)(),(0,n.createElementBlock)(`div`,u,[(0,n.renderSlot)(e.$slots,`sidebar-footer`,(0,n.normalizeProps)((0,n.guardReactiveProps)($.value))),z.value?((0,n.openBlock)(),(0,n.createElementBlock)(`button`,{key:0,type:`button`,class:`t-app-shell__collapse-button`,"aria-label":(0,n.unref)(D)?x.expandLabel:x.collapseLabel,"aria-expanded":!(0,n.unref)(D),"aria-controls":(0,n.unref)(E),onClick:Q},[(0,n.renderSlot)(e.$slots,`collapse-icon`,{collapsed:B.value},()=>[(0,n.createVNode)((0,n.unref)(V),(0,n.mergeProps)((0,n.unref)(i.treeIconDefaults),{"aria-hidden":`true`}),null,16)])],8,d)):(0,n.createCommentVNode)(``,!0)])):(0,n.createCommentVNode)(``,!0)],8,s)),(0,n.createElementVNode)(`main`,f,[(0,n.renderSlot)(e.$slots,`default`,{mobile:R.value,collapsed:B.value,immersive:(0,n.unref)(j),toggleImmersive:M})]),(0,n.createVNode)(n.Transition,{name:`t-app-shell-fade`},{default:(0,n.withCtx)(()=>[R.value&&(0,n.unref)(k)&&!(0,n.unref)(j)?((0,n.openBlock)(),(0,n.createElementBlock)(`div`,p,[(0,n.createElementVNode)(`div`,{class:`t-app-shell__backdrop`,"aria-hidden":`true`,onClick:ie}),(0,n.createElementVNode)(`aside`,{id:(0,n.unref)(E),ref_key:`surfaceRef`,ref:H,class:`t-app-shell__drawer`,role:`dialog`,"aria-modal":`true`,"aria-label":x.sidebarLabel,tabindex:`-1`,onKeydown:ae},[(0,n.createElementVNode)(`div`,h,[(0,n.createElementVNode)(`button`,{type:`button`,class:`t-app-shell__drawer-close`,"aria-label":x.closeLabel,onClick:X},[(0,n.createVNode)((0,n.unref)(te),(0,n.normalizeProps)((0,n.guardReactiveProps)((0,n.unref)(i.treeIconDefaults))),null,16)],8,g)]),(0,n.createElementVNode)(`div`,_,[e.$slots[`sidebar-header`]?((0,n.openBlock)(),(0,n.createElementBlock)(`div`,v,[(0,n.renderSlot)(e.$slots,`sidebar-header`,(0,n.normalizeProps)((0,n.guardReactiveProps)($.value)))])):(0,n.createCommentVNode)(``,!0),(0,n.createElementVNode)(`div`,y,[(0,n.renderSlot)(e.$slots,`sidebar`,(0,n.normalizeProps)((0,n.guardReactiveProps)($.value)))]),e.$slots[`sidebar-footer`]?((0,n.openBlock)(),(0,n.createElementBlock)(`div`,b,[(0,n.renderSlot)(e.$slots,`sidebar-footer`,(0,n.normalizeProps)((0,n.guardReactiveProps)($.value)))])):(0,n.createCommentVNode)(``,!0)])],40,m)])):(0,n.createCommentVNode)(``,!0)]),_:3})]),_:3},16,[`class`,`style`,`data-mobile`]))}});exports.default=x;
|
|
2
2
|
//# sourceMappingURL=TAppShell.vue_vue_type_script_setup_true_lang.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TAppShell.vue_vue_type_script_setup_true_lang.cjs","names":["$slots"],"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 = computed(() => getTreeIcon('x'));\nconst MenuIcon = computed(() => getTreeIcon('menu'));\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 /**\n * Hide the shell chrome (header and sidebar) so the content fills the\n * viewport. The default slot stays mounted across the toggle.\n */\n immersive?: boolean;\n defaultImmersive?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n /** Render the built-in collapse toggle in the sidebar footer (desktop). */\n showCollapseButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n collapseLabel?: string;\n expandLabel?: 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 immersive: undefined,\n defaultImmersive: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n showCollapseButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n collapseLabel: 'Collapse sidebar',\n expandLabel: 'Expand 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 'update:immersive': [value: boolean];\n 'immersive-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n immersive: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n toggleImmersive: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n /** Header region over the sidebar rail; the `header` slot then aligns with the content panel. */\n 'header-start'?: (props: AppShellSlotProps) => unknown;\n /** Pinned above the sidebar body, inset to line up with nav item icons. */\n 'sidebar-header'?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n /** Pinned to the bottom of the sidebar, inset to line up with nav item icons. */\n 'sidebar-footer'?: (props: AppShellSlotProps) => unknown;\n /**\n * Immersive hides the chrome, so the content is the only place an exit\n * control can live — `immersive` and `toggleImmersive` are passed through.\n */\n default?: (\n props: Pick<AppShellSlotProps, 'mobile' | 'collapsed' | 'immersive' | 'toggleImmersive'>,\n ) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n 'collapse-icon'?: (props: { collapsed: 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\nconst { value: isImmersive, setValue: setImmersive } = useControllableOpen(\n toRef(props, 'immersive'),\n props.defaultImmersive,\n (value) => {\n emit('update:immersive', value);\n emit('immersive-change', value);\n },\n);\n\nconst toggleImmersive = () => {\n setImmersive(!isImmersive.value);\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// Collapsing is desktop-only, so the built-in toggle follows the same rule.\nconst showCollapseToggle = computed(\n () => !isMobile.value && props.collapsible && props.showCollapseButton,\n);\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\n// The glyph points the way the rail will move: collapsing a left sidebar folds\n// it left, expanding it pushes right (mirrored for a right-anchored sidebar).\nconst CollapseIcon = computed(() => {\n const pointsStart = props.side === 'right' ? effectiveCollapsed.value : !effectiveCollapsed.value;\n return getTreeIcon(pointsStart ? 'panel-left' : 'panel-right');\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 'is-immersive': isImmersive.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 immersive: isImmersive.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n toggleImmersive,\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\n class=\"t-app-shell__header\"\n :class=\"{ 'has-start': Boolean($slots['header-start']) }\"\n >\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 <MenuIcon\n v-bind=\"treeIconDefaults\"\n :size=\"24\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n\n <div\n v-if=\"$slots['header-start']\"\n class=\"t-app-shell__header-start\"\n >\n <slot\n name=\"header-start\"\n v-bind=\"slotProps\"\n />\n </div>\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer'] || showCollapseToggle\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n\n <button\n v-if=\"showCollapseToggle\"\n type=\"button\"\n class=\"t-app-shell__collapse-button\"\n :aria-label=\"isCollapsed ? expandLabel : collapseLabel\"\n :aria-expanded=\"!isCollapsed\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleCollapsed\"\n >\n <slot\n name=\"collapse-icon\"\n :collapsed=\"effectiveCollapsed\"\n >\n <CollapseIcon\n v-bind=\"treeIconDefaults\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n </div>\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n :immersive=\"isImmersive\"\n :toggle-immersive=\"toggleImmersive\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen && !isImmersive\"\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer']\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n </div>\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TAppShell.vue_vue_type_script_setup_true_lang.cjs","names":["$slots"],"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 = computed(() => getTreeIcon('x'));\nconst MenuIcon = computed(() => getTreeIcon('menu'));\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 /**\n * Hide the shell chrome (header and sidebar) so the content fills the\n * viewport. The default slot stays mounted across the toggle.\n */\n immersive?: boolean;\n defaultImmersive?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n /** Render the built-in collapse toggle in the sidebar footer (desktop). */\n showCollapseButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n collapseLabel?: string;\n expandLabel?: 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 immersive: undefined,\n defaultImmersive: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n showCollapseButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n collapseLabel: 'Collapse sidebar',\n expandLabel: 'Expand 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 'update:immersive': [value: boolean];\n 'immersive-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n immersive: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n toggleImmersive: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n /** Header region over the sidebar rail; the `header` slot then aligns with the content panel. */\n 'header-start'?: (props: AppShellSlotProps) => unknown;\n /** Pinned above the sidebar body, inset to line up with nav item icons. */\n 'sidebar-header'?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n /** Pinned to the bottom of the sidebar, inset to line up with nav item icons. */\n 'sidebar-footer'?: (props: AppShellSlotProps) => unknown;\n /**\n * Immersive hides the chrome, so the content is the only place an exit\n * control can live — `immersive` and `toggleImmersive` are passed through.\n */\n default?: (\n props: Pick<AppShellSlotProps, 'mobile' | 'collapsed' | 'immersive' | 'toggleImmersive'>,\n ) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n 'collapse-icon'?: (props: { collapsed: 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\nconst { value: isImmersive, setValue: setImmersive } = useControllableOpen(\n toRef(props, 'immersive'),\n props.defaultImmersive,\n (value) => {\n emit('update:immersive', value);\n emit('immersive-change', value);\n },\n);\n\nconst toggleImmersive = () => {\n setImmersive(!isImmersive.value);\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// Collapsing is desktop-only, so the built-in toggle follows the same rule.\nconst showCollapseToggle = computed(\n () => !isMobile.value && props.collapsible && props.showCollapseButton,\n);\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\n// The glyph points the way the rail will move: collapsing a left sidebar folds\n// it left, expanding it pushes right (mirrored for a right-anchored sidebar).\nconst CollapseIcon = computed(() => {\n const pointsStart = props.side === 'right' ? effectiveCollapsed.value : !effectiveCollapsed.value;\n return getTreeIcon(pointsStart ? 'panel-left' : 'panel-right');\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\n// Entering immersive removes the drawer from the DOM without changing\n// `sidebarOpen`, so the sidebarOpen watcher never fires and would leave the\n// body scroll-locked with focus stranded on an unmounted surface. Release and\n// re-arm here, mirroring the responsive-mode watcher below.\nwatch(isImmersive, (immersive) => {\n if (!isSidebarOpen.value || !isMobile.value) {\n return;\n }\n\n if (immersive) {\n unlockBodyScroll();\n nextTick(() => {\n previousFocusedElement.value?.focus();\n });\n } else {\n lockBodyScroll();\n focusSurface();\n }\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 'is-immersive': isImmersive.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 immersive: isImmersive.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n toggleImmersive,\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\n class=\"t-app-shell__header\"\n :class=\"{ 'has-start': Boolean($slots['header-start']) }\"\n >\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 <MenuIcon\n v-bind=\"treeIconDefaults\"\n :size=\"24\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n\n <div\n v-if=\"$slots['header-start']\"\n class=\"t-app-shell__header-start\"\n >\n <slot\n name=\"header-start\"\n v-bind=\"slotProps\"\n />\n </div>\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer'] || showCollapseToggle\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n\n <button\n v-if=\"showCollapseToggle\"\n type=\"button\"\n class=\"t-app-shell__collapse-button\"\n :aria-label=\"isCollapsed ? expandLabel : collapseLabel\"\n :aria-expanded=\"!isCollapsed\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleCollapsed\"\n >\n <slot\n name=\"collapse-icon\"\n :collapsed=\"effectiveCollapsed\"\n >\n <CollapseIcon\n v-bind=\"treeIconDefaults\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n </div>\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n :immersive=\"isImmersive\"\n :toggle-immersive=\"toggleImmersive\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen && !isImmersive\"\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer']\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n </div>\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useControllableOpen as e } from "../composables/useControllableOpen.js";
|
|
2
2
|
import { treeSidebarInjectionKey as t } from "./sidebar.js";
|
|
3
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 d, nextTick as f, normalizeClass as ee, normalizeProps as p, onBeforeUnmount as te, onMounted as ne, openBlock as m, provide as re, ref as h, renderSlot as g, resolveDynamicComponent as ie, toRef as _, unref as v, useAttrs as ae, watch as y, withCtx as b } from "vue";
|
|
4
|
-
import { createId as
|
|
4
|
+
import { createId as oe, focusFirst as x, focusLast as se, getFocusableElements as S, isEscapeKey as ce } from "@treeui/utils";
|
|
5
5
|
import { getTreeIcon as C, treeIconDefaults as w } from "@treeui/icons";
|
|
6
6
|
//#region src/components/TAppShell.vue?vue&type=script&setup=true&lang.ts
|
|
7
7
|
var le = [
|
|
@@ -102,7 +102,7 @@ var le = [
|
|
|
102
102
|
"immersive-change"
|
|
103
103
|
],
|
|
104
104
|
setup(l, { emit: T }) {
|
|
105
|
-
let Ee = r(() => C("x")), De = r(() => C("menu")), E = l, D = T, O = ae(), k =
|
|
105
|
+
let Ee = r(() => C("x")), De = r(() => C("menu")), E = l, D = T, O = ae(), k = oe("t-app-shell-sidebar"), { value: A, setValue: Oe } = e(_(E, "collapsed"), E.defaultCollapsed, (e) => {
|
|
106
106
|
D("update:collapsed", e), D("collapse-change", e);
|
|
107
107
|
}), { value: j, setValue: M } = e(_(E, "sidebarOpen"), E.defaultSidebarOpen, (e) => {
|
|
108
108
|
D("update:sidebarOpen", e), D("sidebar-open-change", e);
|
|
@@ -122,7 +122,7 @@ var le = [
|
|
|
122
122
|
typeof document > "u" || !K || (document.body.style.overflow = G, K = !1);
|
|
123
123
|
}, Y = () => {
|
|
124
124
|
f(() => {
|
|
125
|
-
U.value && (
|
|
125
|
+
U.value && (x(U.value) || U.value.focus());
|
|
126
126
|
});
|
|
127
127
|
}, je = () => {
|
|
128
128
|
W.value = typeof document > "u" ? null : document.activeElement, M(!0);
|
|
@@ -140,13 +140,13 @@ var le = [
|
|
|
140
140
|
return;
|
|
141
141
|
}
|
|
142
142
|
if (e.key !== "Tab" || !U.value) return;
|
|
143
|
-
let t =
|
|
143
|
+
let t = S(U.value);
|
|
144
144
|
if (!t.length) {
|
|
145
145
|
e.preventDefault(), U.value.focus();
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
let n = t[0], r = t[t.length - 1], i = document.activeElement;
|
|
149
|
-
e.shiftKey && i === n && (e.preventDefault(),
|
|
149
|
+
e.shiftKey && i === n && (e.preventDefault(), se(U.value)), !e.shiftKey && i === r && (e.preventDefault(), x(U.value));
|
|
150
150
|
}, Pe = (e, t) => {
|
|
151
151
|
if (e && B.value) {
|
|
152
152
|
q(), Y();
|
|
@@ -158,6 +158,10 @@ var le = [
|
|
|
158
158
|
};
|
|
159
159
|
y(j, (e, t) => {
|
|
160
160
|
Pe(e, !!t);
|
|
161
|
+
}), y(N, (e) => {
|
|
162
|
+
!j.value || !B.value || (e ? (J(), f(() => {
|
|
163
|
+
W.value?.focus();
|
|
164
|
+
})) : (q(), Y()));
|
|
161
165
|
}), y(B, (e) => {
|
|
162
166
|
j.value && (e ? (q(), Y()) : J());
|
|
163
167
|
}), y(() => E.breakpoint, () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TAppShell.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"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 = computed(() => getTreeIcon('x'));\nconst MenuIcon = computed(() => getTreeIcon('menu'));\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 /**\n * Hide the shell chrome (header and sidebar) so the content fills the\n * viewport. The default slot stays mounted across the toggle.\n */\n immersive?: boolean;\n defaultImmersive?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n /** Render the built-in collapse toggle in the sidebar footer (desktop). */\n showCollapseButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n collapseLabel?: string;\n expandLabel?: 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 immersive: undefined,\n defaultImmersive: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n showCollapseButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n collapseLabel: 'Collapse sidebar',\n expandLabel: 'Expand 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 'update:immersive': [value: boolean];\n 'immersive-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n immersive: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n toggleImmersive: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n /** Header region over the sidebar rail; the `header` slot then aligns with the content panel. */\n 'header-start'?: (props: AppShellSlotProps) => unknown;\n /** Pinned above the sidebar body, inset to line up with nav item icons. */\n 'sidebar-header'?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n /** Pinned to the bottom of the sidebar, inset to line up with nav item icons. */\n 'sidebar-footer'?: (props: AppShellSlotProps) => unknown;\n /**\n * Immersive hides the chrome, so the content is the only place an exit\n * control can live — `immersive` and `toggleImmersive` are passed through.\n */\n default?: (\n props: Pick<AppShellSlotProps, 'mobile' | 'collapsed' | 'immersive' | 'toggleImmersive'>,\n ) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n 'collapse-icon'?: (props: { collapsed: 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\nconst { value: isImmersive, setValue: setImmersive } = useControllableOpen(\n toRef(props, 'immersive'),\n props.defaultImmersive,\n (value) => {\n emit('update:immersive', value);\n emit('immersive-change', value);\n },\n);\n\nconst toggleImmersive = () => {\n setImmersive(!isImmersive.value);\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// Collapsing is desktop-only, so the built-in toggle follows the same rule.\nconst showCollapseToggle = computed(\n () => !isMobile.value && props.collapsible && props.showCollapseButton,\n);\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\n// The glyph points the way the rail will move: collapsing a left sidebar folds\n// it left, expanding it pushes right (mirrored for a right-anchored sidebar).\nconst CollapseIcon = computed(() => {\n const pointsStart = props.side === 'right' ? effectiveCollapsed.value : !effectiveCollapsed.value;\n return getTreeIcon(pointsStart ? 'panel-left' : 'panel-right');\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 'is-immersive': isImmersive.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 immersive: isImmersive.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n toggleImmersive,\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\n class=\"t-app-shell__header\"\n :class=\"{ 'has-start': Boolean($slots['header-start']) }\"\n >\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 <MenuIcon\n v-bind=\"treeIconDefaults\"\n :size=\"24\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n\n <div\n v-if=\"$slots['header-start']\"\n class=\"t-app-shell__header-start\"\n >\n <slot\n name=\"header-start\"\n v-bind=\"slotProps\"\n />\n </div>\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer'] || showCollapseToggle\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n\n <button\n v-if=\"showCollapseToggle\"\n type=\"button\"\n class=\"t-app-shell__collapse-button\"\n :aria-label=\"isCollapsed ? expandLabel : collapseLabel\"\n :aria-expanded=\"!isCollapsed\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleCollapsed\"\n >\n <slot\n name=\"collapse-icon\"\n :collapsed=\"effectiveCollapsed\"\n >\n <CollapseIcon\n v-bind=\"treeIconDefaults\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n </div>\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n :immersive=\"isImmersive\"\n :toggle-immersive=\"toggleImmersive\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen && !isImmersive\"\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer']\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n </div>\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TAppShell.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"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 = computed(() => getTreeIcon('x'));\nconst MenuIcon = computed(() => getTreeIcon('menu'));\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 /**\n * Hide the shell chrome (header and sidebar) so the content fills the\n * viewport. The default slot stays mounted across the toggle.\n */\n immersive?: boolean;\n defaultImmersive?: boolean;\n closeOnEscape?: boolean;\n closeOnOverlay?: boolean;\n showMenuButton?: boolean;\n /** Render the built-in collapse toggle in the sidebar footer (desktop). */\n showCollapseButton?: boolean;\n menuLabel?: string;\n closeLabel?: string;\n sidebarLabel?: string;\n collapseLabel?: string;\n expandLabel?: 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 immersive: undefined,\n defaultImmersive: false,\n closeOnEscape: true,\n closeOnOverlay: true,\n showMenuButton: true,\n showCollapseButton: true,\n menuLabel: 'Open menu',\n closeLabel: 'Close menu',\n sidebarLabel: 'Sidebar',\n collapseLabel: 'Collapse sidebar',\n expandLabel: 'Expand 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 'update:immersive': [value: boolean];\n 'immersive-change': [value: boolean];\n}>();\n\nexport interface AppShellSlotProps {\n mobile: boolean;\n collapsed: boolean;\n sidebarOpen: boolean;\n immersive: boolean;\n toggleSidebar: () => void;\n closeSidebar: () => void;\n toggleCollapsed: () => void;\n toggleImmersive: () => void;\n}\n\ndefineSlots<{\n header?: (props: AppShellSlotProps) => unknown;\n /** Header region over the sidebar rail; the `header` slot then aligns with the content panel. */\n 'header-start'?: (props: AppShellSlotProps) => unknown;\n /** Pinned above the sidebar body, inset to line up with nav item icons. */\n 'sidebar-header'?: (props: AppShellSlotProps) => unknown;\n sidebar?: (props: AppShellSlotProps) => unknown;\n /** Pinned to the bottom of the sidebar, inset to line up with nav item icons. */\n 'sidebar-footer'?: (props: AppShellSlotProps) => unknown;\n /**\n * Immersive hides the chrome, so the content is the only place an exit\n * control can live — `immersive` and `toggleImmersive` are passed through.\n */\n default?: (\n props: Pick<AppShellSlotProps, 'mobile' | 'collapsed' | 'immersive' | 'toggleImmersive'>,\n ) => unknown;\n 'menu-icon'?: (props: { sidebarOpen: boolean }) => unknown;\n 'collapse-icon'?: (props: { collapsed: 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\nconst { value: isImmersive, setValue: setImmersive } = useControllableOpen(\n toRef(props, 'immersive'),\n props.defaultImmersive,\n (value) => {\n emit('update:immersive', value);\n emit('immersive-change', value);\n },\n);\n\nconst toggleImmersive = () => {\n setImmersive(!isImmersive.value);\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// Collapsing is desktop-only, so the built-in toggle follows the same rule.\nconst showCollapseToggle = computed(\n () => !isMobile.value && props.collapsible && props.showCollapseButton,\n);\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\n// The glyph points the way the rail will move: collapsing a left sidebar folds\n// it left, expanding it pushes right (mirrored for a right-anchored sidebar).\nconst CollapseIcon = computed(() => {\n const pointsStart = props.side === 'right' ? effectiveCollapsed.value : !effectiveCollapsed.value;\n return getTreeIcon(pointsStart ? 'panel-left' : 'panel-right');\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\n// Entering immersive removes the drawer from the DOM without changing\n// `sidebarOpen`, so the sidebarOpen watcher never fires and would leave the\n// body scroll-locked with focus stranded on an unmounted surface. Release and\n// re-arm here, mirroring the responsive-mode watcher below.\nwatch(isImmersive, (immersive) => {\n if (!isSidebarOpen.value || !isMobile.value) {\n return;\n }\n\n if (immersive) {\n unlockBodyScroll();\n nextTick(() => {\n previousFocusedElement.value?.focus();\n });\n } else {\n lockBodyScroll();\n focusSurface();\n }\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 'is-immersive': isImmersive.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 immersive: isImmersive.value,\n toggleSidebar,\n closeSidebar,\n toggleCollapsed,\n toggleImmersive,\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\n class=\"t-app-shell__header\"\n :class=\"{ 'has-start': Boolean($slots['header-start']) }\"\n >\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 <MenuIcon\n v-bind=\"treeIconDefaults\"\n :size=\"24\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n\n <div\n v-if=\"$slots['header-start']\"\n class=\"t-app-shell__header-start\"\n >\n <slot\n name=\"header-start\"\n v-bind=\"slotProps\"\n />\n </div>\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer'] || showCollapseToggle\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n\n <button\n v-if=\"showCollapseToggle\"\n type=\"button\"\n class=\"t-app-shell__collapse-button\"\n :aria-label=\"isCollapsed ? expandLabel : collapseLabel\"\n :aria-expanded=\"!isCollapsed\"\n :aria-controls=\"sidebarId\"\n @click=\"toggleCollapsed\"\n >\n <slot\n name=\"collapse-icon\"\n :collapsed=\"effectiveCollapsed\"\n >\n <CollapseIcon\n v-bind=\"treeIconDefaults\"\n aria-hidden=\"true\"\n />\n </slot>\n </button>\n </div>\n </aside>\n\n <main class=\"t-app-shell__main\">\n <slot\n :mobile=\"isMobile\"\n :collapsed=\"effectiveCollapsed\"\n :immersive=\"isImmersive\"\n :toggle-immersive=\"toggleImmersive\"\n />\n </main>\n\n <transition name=\"t-app-shell-fade\">\n <div\n v-if=\"isMobile && isSidebarOpen && !isImmersive\"\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 <div\n v-if=\"$slots['sidebar-header']\"\n class=\"t-app-shell__sidebar-header\"\n >\n <slot\n name=\"sidebar-header\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div class=\"t-app-shell__sidebar-body\">\n <slot\n name=\"sidebar\"\n v-bind=\"slotProps\"\n />\n </div>\n\n <div\n v-if=\"$slots['sidebar-footer']\"\n class=\"t-app-shell__sidebar-footer\"\n >\n <slot\n name=\"sidebar-footer\"\n v-bind=\"slotProps\"\n />\n </div>\n </div>\n </aside>\n </div>\n </transition>\n </component>\n</template>\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treeui/vue",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "Vue 3 component package for TreeUI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@treeui/icons": "0.18.0",
|
|
28
|
-
"@treeui/
|
|
29
|
-
"@treeui/
|
|
28
|
+
"@treeui/tokens": "0.15.0",
|
|
29
|
+
"@treeui/utils": "0.13.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "pnpm exec rimraf dist && pnpm exec vite build",
|