@illinois-grad/grad-vue 3.0.2 → 3.0.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"grad-vue-SX-RHHr3.js","names":["$emit","$slots","$props","$attrs","$attrs","$attrs","$slots"],"sources":["../src/compose/useCustomElementAttrs.ts","../src/components/GButton.vue","../src/components/GButton.vue","../src/components/GTreeMenu.vue","../src/components/GTreeMenu.vue","../src/components/tree-menu/GTreeMenuList.vue","../src/components/tree-menu/GTreeMenuList.vue","../src/components/tree-menu/GTreeMenuItem.vue","../src/components/tree-menu/GTreeMenuItem.vue","../src/compose/useForm.ts","../src/compose/useWebComponentForm.ts","../src/compose/useFormField.ts","../src/components/form/GFormErrorMessages.vue","../src/components/form/GFormErrorMessages.vue","../src/components/GTextInput.vue","../src/components/GTextInput.vue","../src/compose/useOverlayStack.ts","../../../node_modules/@vueuse/shared/dist/index.js","../../../node_modules/@vueuse/integrations/dist/useFocusTrap-lXZ_YG-8.js","../src/compose/useOverlayFocus.ts","../src/compose/useOverlayEscape.ts","../src/compose/popoverPosition.ts","../src/components/GPopover.vue","../src/components/GPopover.vue","../src/compose/tooltipDom.ts","../src/components/GTooltip.vue","../src/components/GTooltip.vue","../src/components/GSelectButton.vue","../src/components/GSelectButton.vue","../src/components/GProgress.vue","../src/components/GProgress.vue","../src/components/GAlertDialog.vue","../src/components/GAlertDialog.vue","../src/compose/useSelectDropdown.ts","../src/components/GSelect.vue","../src/components/GSelect.vue","../src/components/GSearch.vue","../src/components/GSearch.vue","../src/components/GAppHeader.vue","../src/components/GAppHeader.vue","../src/compose/useWebComponentSidebar.ts","../src/components/GSidebar.vue","../src/components/GSidebar.vue","../src/components/GSidebarMenu.vue","../src/components/GSidebarMenu.vue","../src/directives/v-gtooltip.ts","../src/components/GClipboard.vue","../src/components/GClipboard.vue","../src/components/GHistoryScroller.vue","../src/components/GHistoryScroller.vue","../src/components/GThreeWayToggle.vue","../src/components/GThreeWayToggle.vue","../src/components/table/GTableBody.vue","../src/components/table/GTableBody.vue","../src/compose/useFiltering.ts","../src/components/GTable.vue","../src/components/GTable.vue","../src/components/table/GTablePagination.vue","../src/components/table/GTablePagination.vue","../src/components/GModal.vue","../src/components/GModal.vue","../src/components/GHamburgerMenu.vue","../src/components/GHamburgerMenu.vue","../src/components/GDetailList.vue","../src/components/GDetailList.vue","../src/components/detail-list/GDetailListItem.vue","../src/components/detail-list/GDetailListItem.vue","../src/components/GOverlay.vue","../src/components/GOverlay.vue","../src/components/term/GTermSelectorControl.vue","../src/components/term/GTermSelectorControl.vue","../src/components/GTermSelector.vue","../src/components/GTermSelector.vue","../src/components/GUserMenu.vue","../src/components/GUserMenu.vue","../src/components/GCurrencyInput.vue","../src/components/GCurrencyInput.vue","../src/components/GEmailInput.vue","../src/components/GEmailInput.vue","../src/components/GFileInput.vue","../src/components/GFileInput.vue","../src/components/GDateInput.vue","../src/components/GDateInput.vue","../src/components/GDateRangeInput.vue","../src/components/GDateRangeInput.vue","../src/components/GForm.vue","../src/components/GForm.vue","../src/components/GSubmitButton.vue","../src/components/GSubmitButton.vue","../src/components/GCheckboxGroup.vue","../src/components/GCheckboxGroup.vue","../src/components/GTextarea.vue","../src/components/GTextarea.vue","../src/components/GMultiSelect.vue","../src/components/GMultiSelect.vue","../src/compose/useActiveLink.ts","../src/compose/useSidebar.ts","../src/compose/useTableChanges.ts"],"sourcesContent":["import { computed, useAttrs } from \"vue\";\n\ntype UseCustomElementAttrsOptions = {\n omitInCustomElement?: string[];\n};\n\nexport function isCustomElementMode() {\n const globalScope = globalThis as typeof globalThis & {\n __GRAD_VUE_IS_WEB_COMPONENTS_BUILD__?: boolean;\n };\n return globalScope.__GRAD_VUE_IS_WEB_COMPONENTS_BUILD__ === true;\n}\n\nexport function useCustomElementAttrs(options: UseCustomElementAttrsOptions = {}) {\n const attrs = useAttrs();\n const globalScope = globalThis as typeof globalThis & {\n __GRAD_VUE_IS_WEB_COMPONENTS_BUILD__?: boolean;\n };\n const isCustomElement = globalScope.__GRAD_VUE_IS_WEB_COMPONENTS_BUILD__ === true;\n const omitInCustomElement = options.omitInCustomElement ?? [];\n\n const forwardedAttrs = computed(() => {\n if (!isCustomElement || omitInCustomElement.length === 0) {\n return attrs;\n }\n\n const attrObject = attrs as Record<string, unknown>;\n const filtered: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(attrObject)) {\n if (!omitInCustomElement.includes(key)) {\n filtered[key] = value;\n }\n }\n\n return filtered;\n });\n\n return {\n attrs,\n isCustomElement,\n forwardedAttrs,\n };\n}","<script lang=\"ts\">\n/**\n * The element or component can be set with the `component` prop, so it can be\n * a link or `router-link` component from vue-router. For example:\n *\n * ```vue-html\n * <GButton component=\"router-link\" to=\"/some-route\">\n * Click me\n * </GButton>\n * ```\n *\n * Note that grad-vue doesn't include vue-router as a dependency.\n *\n * **Icons** can be added with either the `icon` prop or a named slot `icon`:\n * - Use the `icon` prop to pass an icon class string, e.g., \"fa-solid fa-plus\".\n * - If using the `icon` prop, the icon will be rendered as a span with the `aria-hidden` attribute set to `true`.\n * - Use a named slot `icon` to provide custom icon content.\n * - If both `icon` prop and named slot `icon` are provided, the named slot takes precedence.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Button size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\";\n /**\n * Button color theme\n * @demo\n */\n theme?: \"primary\" | \"secondary\" | \"accent\" | \"danger\" | \"none\";\n /**\n * Use outlined style\n * @demo\n */\n outlined?: boolean;\n /**\n * Use text style\n * @demo\n */\n text?: boolean;\n\n /**\n * The to target for when using the button as a router-link\n */\n to?: string | Record<string, any>;\n\n /**\n * The component to use for the button\n */\n component?: string;\n\n /**\n * Optional icon classes to render an icon span before the label.\n * Example: \"fa-solid fa-plus\" or \"material-symbols:add\".\n * If a named slot `icon` is provided, it takes precedence over this prop.\n */\n icon?: string;\n /**\n * Native button type\n */\n type?: \"button\" | \"submit\" | \"reset\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n theme: \"primary\",\n outlined: false,\n text: false,\n to: undefined,\n component: undefined,\n icon: undefined,\n type: \"button\",\n});\n\nconst slots = defineSlots<{\n default(): any;\n icon?: () => any;\n}>();\n\ndefineEmits([\n \"click\",\n \"focus\",\n \"blur\",\n \"keydown\",\n \"keyup\",\n \"mousedown\",\n \"mouseup\",\n \"mouseenter\",\n \"mouseleave\",\n]);\nconst { forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\n\nconst classes = computed(() => [\n \"g-btn\",\n `g-btn--${props.size}`,\n `g-btn--${props.theme}`,\n {\n \"g-btn--outlined\": props.outlined,\n \"g-btn--text\": props.text,\n \"g-btn--primary\": props.theme === \"primary\",\n \"g-btn--accent\": props.theme === \"accent\",\n \"g-btn-has-text\": props.text,\n \"g-btn-has-icon-class\": props.icon,\n \"g-btn-has-icon-svg\": !!slots.icon,\n },\n]);\n</script>\n\n<template>\n <component\n :is=\"props.component ? props.component : 'button'\"\n v-bind=\"forwardedAttrs\"\n :to=\"props.to\"\n :class=\"classes\"\n :type=\"props.to ? undefined : props.type\"\n @click=\"$emit('click', $event)\"\n @focus=\"$emit('focus', $event)\"\n @blur=\"$emit('blur', $event)\"\n @keydown=\"$emit('keydown', $event)\"\n @keyup=\"$emit('keyup', $event)\"\n @mousedown=\"$emit('mousedown', $event)\"\n @mouseup=\"$emit('mouseup', $event)\"\n @mouseenter=\"$emit('mouseenter', $event)\"\n @mouseleave=\"$emit('mouseleave', $event)\"\n >\n <template v-if=\"icon || slots.icon\">\n <span class=\"g-btn--icon\">\n <slot v-if=\"slots.icon\" name=\"icon\" />\n <span v-else :class=\"icon + ' g-btn--icon-span'\" aria-hidden=\"true\"></span>\n </span>\n <span class=\"g-btn--label\">\n <slot />\n </span>\n </template>\n <template v-else>\n <slot />\n </template>\n </component>\n</template>\n\n<style>\ng-button {\n display: inline-block;\n}\n\n.g-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--il-font-sans);\n font-weight: 700;\n font-size: 19px;\n line-height: 20px;\n border: 2px solid var(--ilw-color--background);\n background: var(--ilw-color--background);\n color: var(--ilw-color--heading);\n cursor: pointer;\n padding: 12px 20px;\n border-radius: var(--g-border-radius-m);\n text-decoration: none;\n\n &:hover {\n color: var(--ilw-color--background);\n background: var(--ilw-color--heading);\n border-color: var(--ilw-color--background);\n text-decoration: underline;\n }\n\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--g-primary-500);\n }\n}\n\n\n.g-btn--small {\n font-size: 14px;\n padding: 6px 10px 7px;\n\n --g-accent-500: var(--il-altgeld);\n}\n\n.g-btn--large {\n font-size: 21px;\n line-height: 24px;\n padding: 16px 24px;\n}\n\n.g-btn-has-icon-class, .g-btn-has-icon-svg {\n gap: 2px;\n padding: 6px 20px 6px 6px;\n\n &.g-btn--small {\n padding: 0 14px 1px 0;\n }\n &.g-btn--large {\n padding: 12px 24px 12px 10px;\n }\n\n &:hover {\n text-decoration: none;\n\n .g-btn--label {\n text-decoration: underline;\n }\n }\n}\n\n.g-btn--icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n overflow: hidden;\n text-decoration: none;\n\n span.g-btn--icon-span {\n max-width: 100%;\n max-height: 100%;\n }\n}\n\n.g-btn--label {\n display: block;\n}\n\n/* Visually balance leading icon by slightly reducing left offset */\n.g-btn > .g-btn--icon:first-child {\n margin-left: -0.1em;\n}\n\n.g-btn--icon > svg,\n.g-btn--icon > img {\n width: 1em;\n height: 1em;\n display: block;\n flex: 0 0 auto;\n}\n\n.g-btn--primary {\n --ilw-color--background: var(--g-primary-500);\n --ilw-color--heading: var(--g-primary-text);\n}\n.g-btn--accent {\n --ilw-color--background: var(--g-accent-500);\n --ilw-color--heading: var(--g-surface-0);\n}\n\n.g-btn--danger {\n --ilw-color--background: var(--g-danger-500);\n --ilw-color--heading: var(--g-danger-text);\n}\n\n.g-btn--secondary {\n --ilw-color--background: var(--g-surface-700);\n --ilw-color--heading: var(--g-surface-100);\n --ilw-color--heading-link-hover: var(--g-surface-900);\n}\n\n.g-btn--outlined {\n color: var(--ilw-color--background);\n background: var(--ilw-color--heading);\n border-color: var(--ilw-color--background);\n\n &:hover {\n background: var(--ilw-color--background);\n color: var(--ilw-color--heading);\n }\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n }\n}\n\n.g-btn--text {\n background: none;\n border: none;\n color: var(--ilw-color--background);\n padding: 0.25em 0.5em; /* lighter padding using ems for consistency */\n &:hover {\n color: var(--ilw-color--heading-link-hover);\n text-decoration: underline;\n }\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * The element or component can be set with the `component` prop, so it can be\n * a link or `router-link` component from vue-router. For example:\n *\n * ```vue-html\n * <GButton component=\"router-link\" to=\"/some-route\">\n * Click me\n * </GButton>\n * ```\n *\n * Note that grad-vue doesn't include vue-router as a dependency.\n *\n * **Icons** can be added with either the `icon` prop or a named slot `icon`:\n * - Use the `icon` prop to pass an icon class string, e.g., \"fa-solid fa-plus\".\n * - If using the `icon` prop, the icon will be rendered as a span with the `aria-hidden` attribute set to `true`.\n * - Use a named slot `icon` to provide custom icon content.\n * - If both `icon` prop and named slot `icon` are provided, the named slot takes precedence.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Button size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\";\n /**\n * Button color theme\n * @demo\n */\n theme?: \"primary\" | \"secondary\" | \"accent\" | \"danger\" | \"none\";\n /**\n * Use outlined style\n * @demo\n */\n outlined?: boolean;\n /**\n * Use text style\n * @demo\n */\n text?: boolean;\n\n /**\n * The to target for when using the button as a router-link\n */\n to?: string | Record<string, any>;\n\n /**\n * The component to use for the button\n */\n component?: string;\n\n /**\n * Optional icon classes to render an icon span before the label.\n * Example: \"fa-solid fa-plus\" or \"material-symbols:add\".\n * If a named slot `icon` is provided, it takes precedence over this prop.\n */\n icon?: string;\n /**\n * Native button type\n */\n type?: \"button\" | \"submit\" | \"reset\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n theme: \"primary\",\n outlined: false,\n text: false,\n to: undefined,\n component: undefined,\n icon: undefined,\n type: \"button\",\n});\n\nconst slots = defineSlots<{\n default(): any;\n icon?: () => any;\n}>();\n\ndefineEmits([\n \"click\",\n \"focus\",\n \"blur\",\n \"keydown\",\n \"keyup\",\n \"mousedown\",\n \"mouseup\",\n \"mouseenter\",\n \"mouseleave\",\n]);\nconst { forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\n\nconst classes = computed(() => [\n \"g-btn\",\n `g-btn--${props.size}`,\n `g-btn--${props.theme}`,\n {\n \"g-btn--outlined\": props.outlined,\n \"g-btn--text\": props.text,\n \"g-btn--primary\": props.theme === \"primary\",\n \"g-btn--accent\": props.theme === \"accent\",\n \"g-btn-has-text\": props.text,\n \"g-btn-has-icon-class\": props.icon,\n \"g-btn-has-icon-svg\": !!slots.icon,\n },\n]);\n</script>\n\n<template>\n <component\n :is=\"props.component ? props.component : 'button'\"\n v-bind=\"forwardedAttrs\"\n :to=\"props.to\"\n :class=\"classes\"\n :type=\"props.to ? undefined : props.type\"\n @click=\"$emit('click', $event)\"\n @focus=\"$emit('focus', $event)\"\n @blur=\"$emit('blur', $event)\"\n @keydown=\"$emit('keydown', $event)\"\n @keyup=\"$emit('keyup', $event)\"\n @mousedown=\"$emit('mousedown', $event)\"\n @mouseup=\"$emit('mouseup', $event)\"\n @mouseenter=\"$emit('mouseenter', $event)\"\n @mouseleave=\"$emit('mouseleave', $event)\"\n >\n <template v-if=\"icon || slots.icon\">\n <span class=\"g-btn--icon\">\n <slot v-if=\"slots.icon\" name=\"icon\" />\n <span v-else :class=\"icon + ' g-btn--icon-span'\" aria-hidden=\"true\"></span>\n </span>\n <span class=\"g-btn--label\">\n <slot />\n </span>\n </template>\n <template v-else>\n <slot />\n </template>\n </component>\n</template>\n\n<style>\ng-button {\n display: inline-block;\n}\n\n.g-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--il-font-sans);\n font-weight: 700;\n font-size: 19px;\n line-height: 20px;\n border: 2px solid var(--ilw-color--background);\n background: var(--ilw-color--background);\n color: var(--ilw-color--heading);\n cursor: pointer;\n padding: 12px 20px;\n border-radius: var(--g-border-radius-m);\n text-decoration: none;\n\n &:hover {\n color: var(--ilw-color--background);\n background: var(--ilw-color--heading);\n border-color: var(--ilw-color--background);\n text-decoration: underline;\n }\n\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--g-primary-500);\n }\n}\n\n\n.g-btn--small {\n font-size: 14px;\n padding: 6px 10px 7px;\n\n --g-accent-500: var(--il-altgeld);\n}\n\n.g-btn--large {\n font-size: 21px;\n line-height: 24px;\n padding: 16px 24px;\n}\n\n.g-btn-has-icon-class, .g-btn-has-icon-svg {\n gap: 2px;\n padding: 6px 20px 6px 6px;\n\n &.g-btn--small {\n padding: 0 14px 1px 0;\n }\n &.g-btn--large {\n padding: 12px 24px 12px 10px;\n }\n\n &:hover {\n text-decoration: none;\n\n .g-btn--label {\n text-decoration: underline;\n }\n }\n}\n\n.g-btn--icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n overflow: hidden;\n text-decoration: none;\n\n span.g-btn--icon-span {\n max-width: 100%;\n max-height: 100%;\n }\n}\n\n.g-btn--label {\n display: block;\n}\n\n/* Visually balance leading icon by slightly reducing left offset */\n.g-btn > .g-btn--icon:first-child {\n margin-left: -0.1em;\n}\n\n.g-btn--icon > svg,\n.g-btn--icon > img {\n width: 1em;\n height: 1em;\n display: block;\n flex: 0 0 auto;\n}\n\n.g-btn--primary {\n --ilw-color--background: var(--g-primary-500);\n --ilw-color--heading: var(--g-primary-text);\n}\n.g-btn--accent {\n --ilw-color--background: var(--g-accent-500);\n --ilw-color--heading: var(--g-surface-0);\n}\n\n.g-btn--danger {\n --ilw-color--background: var(--g-danger-500);\n --ilw-color--heading: var(--g-danger-text);\n}\n\n.g-btn--secondary {\n --ilw-color--background: var(--g-surface-700);\n --ilw-color--heading: var(--g-surface-100);\n --ilw-color--heading-link-hover: var(--g-surface-900);\n}\n\n.g-btn--outlined {\n color: var(--ilw-color--background);\n background: var(--ilw-color--heading);\n border-color: var(--ilw-color--background);\n\n &:hover {\n background: var(--ilw-color--background);\n color: var(--ilw-color--heading);\n }\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n }\n}\n\n.g-btn--text {\n background: none;\n border: none;\n color: var(--ilw-color--background);\n padding: 0.25em 0.5em; /* lighter padding using ems for consistency */\n &:hover {\n color: var(--ilw-color--heading-link-hover);\n text-decoration: underline;\n }\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A hierarchical sidebar menu component suitable for book-like or nested-section\n * navigation. Items with children start collapsed and can be expanded/collapsed\n * individually.\n *\n * Links are authored directly in HTML for progressive enhancement — the page\n * works as a basic list of links even without JavaScript.\n *\n * Use `GTreeMenuList` and `GTreeMenuItem` sub-components to build the menu:\n *\n * ```vue-html\n * <GTreeMenu heading=\"Contents\">\n * <GTreeMenuList>\n * <GTreeMenuItem label=\"Chapter 1\">\n * <a href=\"#ch1\">Chapter 1</a>\n * <template #children>\n * <GTreeMenuItem><a href=\"#s1\">Section 1.1</a></GTreeMenuItem>\n * </template>\n * </GTreeMenuItem>\n * </GTreeMenuList>\n * </GTreeMenu>\n * ```\n *\n * > [!IMPORTANT]\n * > All items must have a focusable element for proper accessibility. If there\n * > is no link, it should be a button.\n * >\n * > To support progressive enhancement, the component applies ARIA attributes\n * > to the focusable elements in your HTML.\n *\n * **Props**:\n *\n * - `heading` - optional heading and accessible name for the nav landmark.\n * - `listType` - `ul` (default) or `ol`. Use `ol` for numbered\n * hierarchies such as book chapters. Inherited by nested `GTreeMenuList`\n * components via provide/inject.\n * - `theme` - `light` (default) or `dark`.\n * - `storageKey` - when provided, expanded/collapsed states are persisted to\n * `sessionStorage` under this key and restored on page load. This is useful\n * in Web Component / Drupal contexts where every page navigation is a full\n * refresh. Item states are keyed by the item's `label` prop.\n *\n * **Keyboard navigation** (tree-view style):\n *\n * - `Up Arrow` / `Down Arrow` - move between visible menu items.\n * - `Right Arrow` - expand a collapsed item; if already expanded, move to its first child.\n * - `Left Arrow` - collapse an expanded item; if already collapsed, move focus to its\n * parent.\n * - `Home` / `End` - jump to the first or last visible item.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, provide, reactive, ref, useId } from \"vue\";\nimport { useSessionStorage } from \"@vueuse/core\";\n\ntype Props = {\n /**\n * Heading and accessible name for the nav landmark\n * @demo Tree Menu\n */\n heading?: string;\n /**\n * List element type\n * @demo\n */\n listType?: \"ul\" | \"ol\";\n /**\n * Theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * When provided, expanded/collapsed states are saved to `sessionStorage`\n * under this key and restored on page load. Item states are keyed by each\n * the `label` prop.\n */\n storageKey?: string;\n /**\n * Show an expand/collapse all button\n * @demo\n */\n showExpandAll?: boolean;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n listType: \"ul\",\n theme: \"light\",\n showExpandAll: false,\n});\n\nconst id = useId();\n\nprovide(\"g-tree-menu-list-type\", props.listType);\n\nconst expandedStorage = props.storageKey\n ? useSessionStorage<Record<string, boolean>>(props.storageKey, {})\n : null;\n\nprovide(\"g-tree-menu-expanded-storage\", expandedStorage);\n\n// --- Expand / Collapse All ---\n\nconst expandableItems = reactive(new Map<symbol, boolean>());\nprovide(\"g-tree-menu-expandable-items\", expandableItems);\n\nconst expandAllSignal = ref<{ expanded: boolean; version: number }>({\n expanded: true,\n version: 0,\n});\nprovide(\"g-tree-menu-expand-all-signal\", expandAllSignal);\n\nconst allExpanded = computed(() => {\n if (expandableItems.size === 0) return false;\n for (const v of expandableItems.values()) {\n if (!v) return false;\n }\n return true;\n});\n\nfunction toggleExpandAll() {\n const target = !allExpanded.value;\n expandAllSignal.value = {\n expanded: target,\n version: expandAllSignal.value.version + 1,\n };\n}\n\n/**\n * Returns the best focusable element for the given [data-tree-primary] marker.\n */\nfunction getFocusTarget(primary: HTMLElement): HTMLElement {\n const anchor = primary.querySelector<HTMLElement>(\n \"a, button, [tabindex='0']\",\n );\n if (anchor) return anchor;\n return primary;\n}\n\n/**\n * Returns all visible primary focusable items ([data-tree-primary]) inside nav.\n * Because collapsed children are removed from the DOM via v-if, only currently\n * visible items are returned.\n */\nfunction getPrimaryItems(nav: HTMLElement): HTMLElement[] {\n return Array.from(nav.querySelectorAll<HTMLElement>(\"[data-tree-primary]\"));\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const nav = event.currentTarget as HTMLElement;\n const focused = document.activeElement as HTMLElement;\n if (!nav.contains(focused)) return;\n\n const handled = [\n \"ArrowUp\",\n \"ArrowDown\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Home\",\n \"End\",\n ];\n if (!handled.includes(event.key)) return;\n\n const currentLi = focused.closest<HTMLElement>(\".g-tree-menu__item\");\n const currentPrimary =\n currentLi?.querySelector<HTMLElement>(\"[data-tree-primary]\") ?? null;\n\n const primaries = getPrimaryItems(nav);\n const primaryIdx = currentPrimary ? primaries.indexOf(currentPrimary) : -1;\n\n switch (event.key) {\n case \"ArrowDown\": {\n const next = primaries[primaryIdx + 1];\n if (next) getFocusTarget(next).focus();\n break;\n }\n case \"ArrowUp\": {\n const prev = primaries[primaryIdx - 1];\n if (prev) getFocusTarget(prev).focus();\n break;\n }\n case \"ArrowRight\": {\n if (!currentLi) break;\n const isExpandable = currentLi.dataset.treeExpandable === \"true\";\n if (!isExpandable) break;\n const isExpanded =\n currentLi.querySelector(\"[aria-expanded='true']\") !== null;\n if (!isExpanded) {\n const toggleBtn = currentLi.querySelector<HTMLElement>(\n \".g-tree-menu__toggle-btn\",\n );\n if (toggleBtn) toggleBtn.click();\n } else {\n const next = primaries[primaryIdx + 1];\n if (next) getFocusTarget(next).focus();\n }\n break;\n }\n case \"ArrowLeft\": {\n if (!currentLi) break;\n const isExpanded =\n currentLi.querySelector(\"[aria-expanded='true']\") !== null;\n if (isExpanded) {\n const toggleBtn = currentLi.querySelector<HTMLElement>(\n \".g-tree-menu__toggle-btn\",\n );\n if (toggleBtn) toggleBtn.click();\n if (currentPrimary)\n nextTick(() => getFocusTarget(currentPrimary).focus());\n } else {\n const parentItem =\n currentLi.parentElement?.closest<HTMLElement>(\n \".g-tree-menu__item\",\n );\n if (parentItem) {\n const parentPrimary = parentItem.querySelector<HTMLElement>(\n \"[data-tree-primary]\",\n );\n if (parentPrimary) getFocusTarget(parentPrimary).focus();\n }\n }\n break;\n }\n case \"Home\": {\n if (primaries.length > 0) getFocusTarget(primaries[0]).focus();\n break;\n }\n case \"End\": {\n if (primaries.length > 0)\n getFocusTarget(primaries[primaries.length - 1]).focus();\n break;\n }\n }\n\n event.preventDefault();\n}\n</script>\n\n<template>\n <nav\n class=\"g-tree-menu\"\n :class=\"`g-tree-menu--${props.theme}`\"\n v-bind=\"{\n 'aria-labelledby': heading ? id : undefined,\n 'aria-label': heading ? undefined : 'Tree Menu',\n }\"\n @keydown=\"handleKeydown\"\n >\n <h2 v-if=\"heading\" :id=\"id\" class=\"g-tree-menu__title\">\n {{ heading }}\n </h2>\n <div class=\"g-tree-menu__divider\">\n <div class=\"g-tree-menu__divider-line\"></div>\n <div v-if=\"showExpandAll\" class=\"g-tree-menu__expand-all-wrapper\">\n <button\n class=\"g-tree-menu__expand-all-btn\"\n @click=\"toggleExpandAll\"\n >\n <svg\n class=\"g-tree-menu__expand-all-icon\"\n :class=\"{\n 'g-tree-menu__expand-all-icon--collapse':\n allExpanded,\n }\"\n role=\"none presentation\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"7 8 12 13 17 8\" />\n <polyline points=\"7 13 12 18 17 13\" />\n </svg>\n {{ allExpanded ? \"Collapse all\" : \"Expand all\" }}\n </button>\n </div>\n </div>\n <div class=\"g-tree-menu__content\">\n <slot />\n </div>\n </nav>\n</template>\n\n<style>\n.g-tree-menu {\n font-size: 1.125rem;\n line-height: 1.2;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.g-tree-menu--dark {\n color: var(--g-surface-0);\n\n .g-tree-menu__title {\n color: var(--g-surface-0);\n }\n}\n\n.g-tree-menu--light {\n background: var(--g-surface-50);\n\n .g-tree-menu__title {\n color: var(--g-primary-500);\n }\n}\n\n.g-tree-menu__title {\n margin: 2rem 2rem 0;\n font-size: 2rem;\n font-family: var(--il-font-heading);\n}\n\n.g-tree-menu__divider {\n display: flex;\n justify-content: space-between;\n}\n.g-tree-menu__divider-line {\n margin: 1rem 0 1rem 2rem;\n height: 4px;\n width: 60px;\n min-width: 60px;\n max-width: 60px;\n background: var(--g-accent-500);\n}\n\n.g-tree-menu__expand-all-wrapper {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n margin-right: 2rem;\n}\n\n.g-tree-menu__expand-all-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.25em;\n background: none;\n border: none;\n cursor: pointer;\n font-family: inherit;\n font-size: 0.85em;\n font-weight: 600;\n padding: 0.35em 0.5em;\n margin: 0;\n color: inherit;\n min-width: 120px;\n}\n\n.g-tree-menu__expand-all-icon {\n width: 1.2em;\n height: 1.2em;\n flex-shrink: 0;\n transition: transform 0.15s ease;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n}\n\n.g-tree-menu__expand-all-icon--collapse {\n transform: rotate(180deg);\n}\n.g-tree-menu__expand-all-btn:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\ng-tree-menu:not(:defined) {\n display: block;\n padding-top: 0;\n color: var(--g-surface-0);\n\n &[theme=\"light\"] {\n background: var(--g-surface-50);\n color: var(--g-primary-500);\n }\n\n &[theme=\"light\"] a {\n color: var(--g-primary-500);\n }\n\n &[theme=\"dark\"] a {\n color: var(--g-surface-0);\n }\n\n g-tree-menu-item {\n margin: 0.4em 0 0.4em 1.2em;\n }\n\n g-tree-menu-item[slot=\"children\"] {\n font-size: 0.95em;\n font-weight: 600;\n }\n\n g-tree-menu-item > a {\n color: inherit;\n text-decoration: none;\n }\n\n g-tree-menu-item > a:hover {\n text-decoration: underline;\n }\n g-tree-menu-list {\n display: block;\n margin-top: 1em;\n padding-left: 1em;\n }\n}\n\ng-tree-menu:not(:defined)[heading]::before {\n content: attr(heading);\n display: block;\n margin: 2rem 2rem 0.5rem;\n padding-bottom: 1rem;\n font-size: 2rem;\n line-height: 1.1;\n font-family: var(--il-font-heading);\n font-weight: 700;\n background-image: linear-gradient(var(--g-accent-500), var(--g-accent-500));\n background-repeat: no-repeat;\n background-size: 60px 4px;\n background-position: left bottom;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A hierarchical sidebar menu component suitable for book-like or nested-section\n * navigation. Items with children start collapsed and can be expanded/collapsed\n * individually.\n *\n * Links are authored directly in HTML for progressive enhancement — the page\n * works as a basic list of links even without JavaScript.\n *\n * Use `GTreeMenuList` and `GTreeMenuItem` sub-components to build the menu:\n *\n * ```vue-html\n * <GTreeMenu heading=\"Contents\">\n * <GTreeMenuList>\n * <GTreeMenuItem label=\"Chapter 1\">\n * <a href=\"#ch1\">Chapter 1</a>\n * <template #children>\n * <GTreeMenuItem><a href=\"#s1\">Section 1.1</a></GTreeMenuItem>\n * </template>\n * </GTreeMenuItem>\n * </GTreeMenuList>\n * </GTreeMenu>\n * ```\n *\n * > [!IMPORTANT]\n * > All items must have a focusable element for proper accessibility. If there\n * > is no link, it should be a button.\n * >\n * > To support progressive enhancement, the component applies ARIA attributes\n * > to the focusable elements in your HTML.\n *\n * **Props**:\n *\n * - `heading` - optional heading and accessible name for the nav landmark.\n * - `listType` - `ul` (default) or `ol`. Use `ol` for numbered\n * hierarchies such as book chapters. Inherited by nested `GTreeMenuList`\n * components via provide/inject.\n * - `theme` - `light` (default) or `dark`.\n * - `storageKey` - when provided, expanded/collapsed states are persisted to\n * `sessionStorage` under this key and restored on page load. This is useful\n * in Web Component / Drupal contexts where every page navigation is a full\n * refresh. Item states are keyed by the item's `label` prop.\n *\n * **Keyboard navigation** (tree-view style):\n *\n * - `Up Arrow` / `Down Arrow` - move between visible menu items.\n * - `Right Arrow` - expand a collapsed item; if already expanded, move to its first child.\n * - `Left Arrow` - collapse an expanded item; if already collapsed, move focus to its\n * parent.\n * - `Home` / `End` - jump to the first or last visible item.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, provide, reactive, ref, useId } from \"vue\";\nimport { useSessionStorage } from \"@vueuse/core\";\n\ntype Props = {\n /**\n * Heading and accessible name for the nav landmark\n * @demo Tree Menu\n */\n heading?: string;\n /**\n * List element type\n * @demo\n */\n listType?: \"ul\" | \"ol\";\n /**\n * Theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * When provided, expanded/collapsed states are saved to `sessionStorage`\n * under this key and restored on page load. Item states are keyed by each\n * the `label` prop.\n */\n storageKey?: string;\n /**\n * Show an expand/collapse all button\n * @demo\n */\n showExpandAll?: boolean;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n listType: \"ul\",\n theme: \"light\",\n showExpandAll: false,\n});\n\nconst id = useId();\n\nprovide(\"g-tree-menu-list-type\", props.listType);\n\nconst expandedStorage = props.storageKey\n ? useSessionStorage<Record<string, boolean>>(props.storageKey, {})\n : null;\n\nprovide(\"g-tree-menu-expanded-storage\", expandedStorage);\n\n// --- Expand / Collapse All ---\n\nconst expandableItems = reactive(new Map<symbol, boolean>());\nprovide(\"g-tree-menu-expandable-items\", expandableItems);\n\nconst expandAllSignal = ref<{ expanded: boolean; version: number }>({\n expanded: true,\n version: 0,\n});\nprovide(\"g-tree-menu-expand-all-signal\", expandAllSignal);\n\nconst allExpanded = computed(() => {\n if (expandableItems.size === 0) return false;\n for (const v of expandableItems.values()) {\n if (!v) return false;\n }\n return true;\n});\n\nfunction toggleExpandAll() {\n const target = !allExpanded.value;\n expandAllSignal.value = {\n expanded: target,\n version: expandAllSignal.value.version + 1,\n };\n}\n\n/**\n * Returns the best focusable element for the given [data-tree-primary] marker.\n */\nfunction getFocusTarget(primary: HTMLElement): HTMLElement {\n const anchor = primary.querySelector<HTMLElement>(\n \"a, button, [tabindex='0']\",\n );\n if (anchor) return anchor;\n return primary;\n}\n\n/**\n * Returns all visible primary focusable items ([data-tree-primary]) inside nav.\n * Because collapsed children are removed from the DOM via v-if, only currently\n * visible items are returned.\n */\nfunction getPrimaryItems(nav: HTMLElement): HTMLElement[] {\n return Array.from(nav.querySelectorAll<HTMLElement>(\"[data-tree-primary]\"));\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const nav = event.currentTarget as HTMLElement;\n const focused = document.activeElement as HTMLElement;\n if (!nav.contains(focused)) return;\n\n const handled = [\n \"ArrowUp\",\n \"ArrowDown\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Home\",\n \"End\",\n ];\n if (!handled.includes(event.key)) return;\n\n const currentLi = focused.closest<HTMLElement>(\".g-tree-menu__item\");\n const currentPrimary =\n currentLi?.querySelector<HTMLElement>(\"[data-tree-primary]\") ?? null;\n\n const primaries = getPrimaryItems(nav);\n const primaryIdx = currentPrimary ? primaries.indexOf(currentPrimary) : -1;\n\n switch (event.key) {\n case \"ArrowDown\": {\n const next = primaries[primaryIdx + 1];\n if (next) getFocusTarget(next).focus();\n break;\n }\n case \"ArrowUp\": {\n const prev = primaries[primaryIdx - 1];\n if (prev) getFocusTarget(prev).focus();\n break;\n }\n case \"ArrowRight\": {\n if (!currentLi) break;\n const isExpandable = currentLi.dataset.treeExpandable === \"true\";\n if (!isExpandable) break;\n const isExpanded =\n currentLi.querySelector(\"[aria-expanded='true']\") !== null;\n if (!isExpanded) {\n const toggleBtn = currentLi.querySelector<HTMLElement>(\n \".g-tree-menu__toggle-btn\",\n );\n if (toggleBtn) toggleBtn.click();\n } else {\n const next = primaries[primaryIdx + 1];\n if (next) getFocusTarget(next).focus();\n }\n break;\n }\n case \"ArrowLeft\": {\n if (!currentLi) break;\n const isExpanded =\n currentLi.querySelector(\"[aria-expanded='true']\") !== null;\n if (isExpanded) {\n const toggleBtn = currentLi.querySelector<HTMLElement>(\n \".g-tree-menu__toggle-btn\",\n );\n if (toggleBtn) toggleBtn.click();\n if (currentPrimary)\n nextTick(() => getFocusTarget(currentPrimary).focus());\n } else {\n const parentItem =\n currentLi.parentElement?.closest<HTMLElement>(\n \".g-tree-menu__item\",\n );\n if (parentItem) {\n const parentPrimary = parentItem.querySelector<HTMLElement>(\n \"[data-tree-primary]\",\n );\n if (parentPrimary) getFocusTarget(parentPrimary).focus();\n }\n }\n break;\n }\n case \"Home\": {\n if (primaries.length > 0) getFocusTarget(primaries[0]).focus();\n break;\n }\n case \"End\": {\n if (primaries.length > 0)\n getFocusTarget(primaries[primaries.length - 1]).focus();\n break;\n }\n }\n\n event.preventDefault();\n}\n</script>\n\n<template>\n <nav\n class=\"g-tree-menu\"\n :class=\"`g-tree-menu--${props.theme}`\"\n v-bind=\"{\n 'aria-labelledby': heading ? id : undefined,\n 'aria-label': heading ? undefined : 'Tree Menu',\n }\"\n @keydown=\"handleKeydown\"\n >\n <h2 v-if=\"heading\" :id=\"id\" class=\"g-tree-menu__title\">\n {{ heading }}\n </h2>\n <div class=\"g-tree-menu__divider\">\n <div class=\"g-tree-menu__divider-line\"></div>\n <div v-if=\"showExpandAll\" class=\"g-tree-menu__expand-all-wrapper\">\n <button\n class=\"g-tree-menu__expand-all-btn\"\n @click=\"toggleExpandAll\"\n >\n <svg\n class=\"g-tree-menu__expand-all-icon\"\n :class=\"{\n 'g-tree-menu__expand-all-icon--collapse':\n allExpanded,\n }\"\n role=\"none presentation\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"7 8 12 13 17 8\" />\n <polyline points=\"7 13 12 18 17 13\" />\n </svg>\n {{ allExpanded ? \"Collapse all\" : \"Expand all\" }}\n </button>\n </div>\n </div>\n <div class=\"g-tree-menu__content\">\n <slot />\n </div>\n </nav>\n</template>\n\n<style>\n.g-tree-menu {\n font-size: 1.125rem;\n line-height: 1.2;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.g-tree-menu--dark {\n color: var(--g-surface-0);\n\n .g-tree-menu__title {\n color: var(--g-surface-0);\n }\n}\n\n.g-tree-menu--light {\n background: var(--g-surface-50);\n\n .g-tree-menu__title {\n color: var(--g-primary-500);\n }\n}\n\n.g-tree-menu__title {\n margin: 2rem 2rem 0;\n font-size: 2rem;\n font-family: var(--il-font-heading);\n}\n\n.g-tree-menu__divider {\n display: flex;\n justify-content: space-between;\n}\n.g-tree-menu__divider-line {\n margin: 1rem 0 1rem 2rem;\n height: 4px;\n width: 60px;\n min-width: 60px;\n max-width: 60px;\n background: var(--g-accent-500);\n}\n\n.g-tree-menu__expand-all-wrapper {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n margin-right: 2rem;\n}\n\n.g-tree-menu__expand-all-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.25em;\n background: none;\n border: none;\n cursor: pointer;\n font-family: inherit;\n font-size: 0.85em;\n font-weight: 600;\n padding: 0.35em 0.5em;\n margin: 0;\n color: inherit;\n min-width: 120px;\n}\n\n.g-tree-menu__expand-all-icon {\n width: 1.2em;\n height: 1.2em;\n flex-shrink: 0;\n transition: transform 0.15s ease;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n}\n\n.g-tree-menu__expand-all-icon--collapse {\n transform: rotate(180deg);\n}\n.g-tree-menu__expand-all-btn:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\ng-tree-menu:not(:defined) {\n display: block;\n padding-top: 0;\n color: var(--g-surface-0);\n\n &[theme=\"light\"] {\n background: var(--g-surface-50);\n color: var(--g-primary-500);\n }\n\n &[theme=\"light\"] a {\n color: var(--g-primary-500);\n }\n\n &[theme=\"dark\"] a {\n color: var(--g-surface-0);\n }\n\n g-tree-menu-item {\n margin: 0.4em 0 0.4em 1.2em;\n }\n\n g-tree-menu-item[slot=\"children\"] {\n font-size: 0.95em;\n font-weight: 600;\n }\n\n g-tree-menu-item > a {\n color: inherit;\n text-decoration: none;\n }\n\n g-tree-menu-item > a:hover {\n text-decoration: underline;\n }\n g-tree-menu-list {\n display: block;\n margin-top: 1em;\n padding-left: 1em;\n }\n}\n\ng-tree-menu:not(:defined)[heading]::before {\n content: attr(heading);\n display: block;\n margin: 2rem 2rem 0.5rem;\n padding-bottom: 1rem;\n font-size: 2rem;\n line-height: 1.1;\n font-family: var(--il-font-heading);\n font-weight: 700;\n background-image: linear-gradient(var(--g-accent-500), var(--g-accent-500));\n background-repeat: no-repeat;\n background-size: 60px 4px;\n background-position: left bottom;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * List wrapper for `GTreeMenuItem` items inside a `GTreeMenu`.\n * Renders as `<ul>` or `<ol>` depending on the `listType` prop.\n * When no `listType` is specified the value provided by the parent\n * `GTreeMenu` (via provide/inject) is used, falling back to `ul`.\n */\nexport default { name: \"GTreeMenuList\" };\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, inject } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n /**\n * List element type (`ul` or `ol`).\n */\n listType?: \"ul\" | \"ol\";\n }>(),\n {\n listType: undefined,\n },\n);\n\nconst injectedListType = inject<string>(\"g-tree-menu-list-type\", \"ul\");\n\nconst resolvedListType = computed(\n () => props.listType ?? injectedListType ?? \"ul\",\n);\n</script>\n\n<template>\n <component :is=\"resolvedListType\" class=\"g-tree-menu__list\">\n <slot />\n </component>\n</template>\n\n<style>\ng-tree-menu-list,\n.g-tree-menu__list {\n list-style: none;\n margin: 0;\n padding: 0;\n font-weight: bold;\n display: block;\n}\n.g-tree-menu__list ul,\n.g-tree-menu__list ol {\n font-weight: 600 !important;\n}\n\n.g-tree-menu__list .g-tree-menu__list {\n padding-left: 1.25em;\n}\n\ng-tree-menu-list > g-tree-menu-item {\n margin-top: 0.4em;\n}\n\ng-tree-menu-list > g-tree-menu-item:first-of-type {\n margin-top: 0;\n}\n\n</style>","<script lang=\"ts\">\n/**\n * List wrapper for `GTreeMenuItem` items inside a `GTreeMenu`.\n * Renders as `<ul>` or `<ol>` depending on the `listType` prop.\n * When no `listType` is specified the value provided by the parent\n * `GTreeMenu` (via provide/inject) is used, falling back to `ul`.\n */\nexport default { name: \"GTreeMenuList\" };\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, inject } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n /**\n * List element type (`ul` or `ol`).\n */\n listType?: \"ul\" | \"ol\";\n }>(),\n {\n listType: undefined,\n },\n);\n\nconst injectedListType = inject<string>(\"g-tree-menu-list-type\", \"ul\");\n\nconst resolvedListType = computed(\n () => props.listType ?? injectedListType ?? \"ul\",\n);\n</script>\n\n<template>\n <component :is=\"resolvedListType\" class=\"g-tree-menu__list\">\n <slot />\n </component>\n</template>\n\n<style>\ng-tree-menu-list,\n.g-tree-menu__list {\n list-style: none;\n margin: 0;\n padding: 0;\n font-weight: bold;\n display: block;\n}\n.g-tree-menu__list ul,\n.g-tree-menu__list ol {\n font-weight: 600 !important;\n}\n\n.g-tree-menu__list .g-tree-menu__list {\n padding-left: 1.25em;\n}\n\ng-tree-menu-list > g-tree-menu-item {\n margin-top: 0.4em;\n}\n\ng-tree-menu-list > g-tree-menu-item:first-of-type {\n margin-top: 0;\n}\n\n</style>","<script lang=\"ts\">\nexport default { name: \"GTreeMenuItem\" };\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, getCurrentInstance, inject, onBeforeUnmount, onMounted, onUpdated, ref, useId, useSlots, watch } from \"vue\";\nimport type { Ref } from \"vue\";\nimport GTreeMenuList from \"./GTreeMenuList.vue\";\n\nconst props = withDefaults(\n defineProps<{\n /**\n * Label for the item. Used as a stable identifier for the item.\n * @demo\n '\n */\n label?: string;\n /**\n * Whether the item starts expanded. Only meaningful for items that\n * have a `#children` slot. When a `storageKey` is active on the parent\n * `GTreeMenu` and a stored value exists for this item's `label`, the\n * stored value takes precedence over this prop and subsequent prop\n * updates are ignored for that item.\n * @demo\n */\n expanded?: boolean;\n }>(),\n {\n expanded: false,\n },\n);\n\nconst emit = defineEmits<{\n /** Fired when the item is expanded. */\n expand: [];\n /** Fired when the item is collapsed. */\n collapse: [];\n}>();\n\nconst slots = useSlots();\nconst instance = getCurrentInstance();\n\nconst id = useId();\n\n// In CE mode without Shadow DOM, useSlots() doesn't detect slot=\"children\"\n// on child elements. Fall back to checking the CE host's parsed _slots.\n// This accesses Vue's internal CE implementation (VueElement._slots) which\n// is stable since Vue 3.2+ but is not a public API — verify compatibility\n// when upgrading Vue.\nconst ceHost = (instance as any)?.ce as any | undefined;\nconst hasCeChildren = ceHost?._slots?.children?.length > 0;\n\nconst hasChildren = computed(() => !!slots.children || hasCeChildren);\n\nconst expandedStorage = inject<Ref<Record<string, boolean>> | null>(\n \"g-tree-menu-expanded-storage\",\n null,\n);\n\nfunction resolveInitialExpanded(): boolean {\n if (expandedStorage && props.label !== undefined) {\n if (expandedStorage.value[props.label] === true) return true;\n if (props.expanded) {\n expandedStorage.value[props.label] = true;\n }\n }\n return props.expanded;\n}\n\nconst isExpanded = ref(resolveInitialExpanded());\n\nwatch(\n () => props.expanded,\n (val) => {\n if (expandedStorage && props.label !== undefined) return;\n isExpanded.value = val;\n },\n);\n\nwatch(isExpanded, (val) => {\n if (expandedStorage && props.label !== undefined) {\n if (val) {\n expandedStorage.value[props.label] = true;\n } else {\n delete expandedStorage.value[props.label];\n }\n }\n updateSlotAria();\n});\n\nconst contentRef = ref<HTMLElement | null>(null);\n\nfunction updateSlotAria() {\n if (!hasChildren.value || !contentRef.value) return;\n const focusable = contentRef.value.querySelector(\"a, button\");\n if (focusable) {\n focusable.setAttribute(\"aria-controls\", id + \"-children\");\n focusable.setAttribute(\"aria-expanded\", isExpanded.value ? \"true\" : \"false\");\n } else {\n console.warn(\"No focusable element found for GTreeMenuItem with label:\", props.label, \"Every item must at least have a plain button to properly work for accessibility.\");\n }\n}\n\nonMounted(updateSlotAria);\nonUpdated(updateSlotAria);\n\nfunction toggle() {\n isExpanded.value = !isExpanded.value;\n if (isExpanded.value) {\n emit(\"expand\");\n } else {\n emit(\"collapse\");\n }\n}\n\n// --- Expand / Collapse All registration ---\n\nconst itemId = Symbol();\nconst expandableItems = inject<Map<symbol, boolean> | null>(\n \"g-tree-menu-expandable-items\",\n null,\n);\nconst expandAllSignal = inject<Ref<{ expanded: boolean; version: number }> | null>(\n \"g-tree-menu-expand-all-signal\",\n null,\n);\nconst lastProcessedVersion = ref(0);\n\nfunction registerItem() {\n if (expandableItems && hasChildren.value) {\n expandableItems.set(itemId, isExpanded.value);\n }\n}\n\nfunction unregisterItem() {\n expandableItems?.delete(itemId);\n}\n\nwatch(isExpanded, (val) => {\n if (expandableItems && hasChildren.value) {\n expandableItems.set(itemId, val);\n }\n});\n\nif (expandAllSignal) {\n watch(\n () => expandAllSignal.value.version,\n () => {\n if (!hasChildren.value) return;\n isExpanded.value = expandAllSignal.value.expanded;\n lastProcessedVersion.value = expandAllSignal.value.version;\n },\n );\n}\n\nonMounted(() => {\n registerItem();\n if (\n expandAllSignal &&\n hasChildren.value &&\n expandAllSignal.value.version > lastProcessedVersion.value &&\n expandAllSignal.value.expanded\n ) {\n isExpanded.value = true;\n lastProcessedVersion.value = expandAllSignal.value.version;\n }\n});\n\nonBeforeUnmount(() => {\n unregisterItem();\n});\n\nfunction handleContentClick(event: MouseEvent) {\n if (!(event.target as Element).closest(\"a\")) {\n toggle();\n }\n}\n\nfunction handleContentKeydown(event: KeyboardEvent) {\n if (event.key === \"Enter\" || event.key === \" \") {\n toggle();\n event.preventDefault();\n }\n}\n</script>\n\n<template>\n <li\n class=\"g-tree-menu__item\"\n :data-tree-expandable=\"hasChildren ? 'true' : undefined\"\n >\n <!-- Parent: has children → toggle button + slot content (which may contain a link) -->\n <div v-if=\"hasChildren\" class=\"g-tree-menu__row\">\n <div\n class=\"g-tree-menu__toggle-btn\"\n @click=\"toggle\"\n >\n <svg\n class=\"g-tree-menu__chevron\"\n :class=\"{ 'g-tree-menu__chevron--expanded': isExpanded }\"\n role=\"none presentation\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n </div>\n <span\n ref=\"contentRef\"\n class=\"g-tree-menu__row-content\"\n data-tree-primary\n @click=\"handleContentClick\"\n @keydown=\"handleContentKeydown\"\n >\n <span class=\"g-tree-menu__row-content-text\">\n <slot />\n </span>\n </span>\n </div>\n\n <!-- Leaf: no children → just render the slot content -->\n <div v-else class=\"g-tree-menu__row g-tree-menu__row--leaf\">\n <span class=\"g-tree-menu__spacer\"></span>\n <span\n class=\"g-tree-menu__row-content\"\n data-tree-primary\n >\n <slot />\n </span>\n </div>\n\n <!-- Children (shown when expanded) -->\n <GTreeMenuList v-if=\"hasChildren && isExpanded\" :id=\"id + '-children'\">\n <slot name=\"children\" />\n </GTreeMenuList>\n </li>\n</template>\n\n<style>\ng-tree-menu-item,\n.g-tree-menu__item {\n display: block;\n}\n\ng-tree-menu-item > a {\n color: inherit;\n display: flex;\n align-items: flex-start;\n text-decoration: none;\n}\n\ng-tree-menu-item > a:hover {\n text-decoration: underline;\n}\n\n.g-tree-menu__row {\n display: flex;\n align-items: stretch;\n}\n\n.g-tree-menu__toggle-btn {\n flex-shrink: 0;\n align-self: center;\n background: none;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n box-sizing: border-box;\n border-radius: 2px;\n margin-right: 2px;\n font-size: inherit;\n font-weight: inherit;\n}\n\n.g-tree-menu__spacer {\n display: inline-block;\n width: 2em;\n height: 2em;\n min-width: 2em;\n min-height: 2em;\n margin-right: 2px;\n align-self: center;\n}\n\n.g-tree-menu__row-content {\n display: flex;\n align-items: stretch;\n flex: 1;\n padding: 0 0.5em 0 0;\n box-sizing: border-box;\n\n button, a {\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n padding: 2px 0;\n margin: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n flex: 1;\n text-decoration: none;\n height: 100%;\n\n &:hover {\n text-decoration: underline;\n }\n }\n}\n\n.g-tree-menu__row:not(.g-tree-menu__row--leaf) .g-tree-menu__row-content {\n cursor: pointer;\n}\n\n.g-tree-menu__row-content-text {\n flex: 1;\n display: flex;\n align-items: center;\n}\n\n.g-tree-menu__chevron {\n width: 2em;\n height: 2em;\n min-width: 2em;\n min-height: 2em;\n padding: 0.35em;\n box-sizing: border-box;\n flex-shrink: 0;\n transform: rotate(0deg);\n transition: transform 0.1s ease;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n}\n\n.g-tree-menu__chevron--expanded {\n transform: rotate(90deg);\n}\n\n.g-tree-menu__toggle-btn:focus-visible,\n.g-tree-menu__row-content:focus-visible,\n.g-tree-menu__row-content a:focus-visible,\n.g-tree-menu__row-content button:focus-visible{\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n\n.g-tree-menu__row .g-tree-menu__toggle-btn {\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n }\n}\n\n.g-tree-menu--dark {\n .g-tree-menu__toggle-btn,\n .g-tree-menu__row-content a {\n color: var(--g-surface-0);\n\n &:hover {\n color: var(--g-accent-500);\n }\n }\n}\n\n.g-tree-menu--light {\n .g-tree-menu__row-content,\n .g-tree-menu__row-content a {\n color: var(--g-primary-500);\n\n &:hover {\n color: var(--g-accent-700);\n }\n &:focus-visible {\n color: var(--ilw-color--focus--text);\n }\n }\n}\n\n</style>\n","<script lang=\"ts\">\nexport default { name: \"GTreeMenuItem\" };\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, getCurrentInstance, inject, onBeforeUnmount, onMounted, onUpdated, ref, useId, useSlots, watch } from \"vue\";\nimport type { Ref } from \"vue\";\nimport GTreeMenuList from \"./GTreeMenuList.vue\";\n\nconst props = withDefaults(\n defineProps<{\n /**\n * Label for the item. Used as a stable identifier for the item.\n * @demo\n '\n */\n label?: string;\n /**\n * Whether the item starts expanded. Only meaningful for items that\n * have a `#children` slot. When a `storageKey` is active on the parent\n * `GTreeMenu` and a stored value exists for this item's `label`, the\n * stored value takes precedence over this prop and subsequent prop\n * updates are ignored for that item.\n * @demo\n */\n expanded?: boolean;\n }>(),\n {\n expanded: false,\n },\n);\n\nconst emit = defineEmits<{\n /** Fired when the item is expanded. */\n expand: [];\n /** Fired when the item is collapsed. */\n collapse: [];\n}>();\n\nconst slots = useSlots();\nconst instance = getCurrentInstance();\n\nconst id = useId();\n\n// In CE mode without Shadow DOM, useSlots() doesn't detect slot=\"children\"\n// on child elements. Fall back to checking the CE host's parsed _slots.\n// This accesses Vue's internal CE implementation (VueElement._slots) which\n// is stable since Vue 3.2+ but is not a public API — verify compatibility\n// when upgrading Vue.\nconst ceHost = (instance as any)?.ce as any | undefined;\nconst hasCeChildren = ceHost?._slots?.children?.length > 0;\n\nconst hasChildren = computed(() => !!slots.children || hasCeChildren);\n\nconst expandedStorage = inject<Ref<Record<string, boolean>> | null>(\n \"g-tree-menu-expanded-storage\",\n null,\n);\n\nfunction resolveInitialExpanded(): boolean {\n if (expandedStorage && props.label !== undefined) {\n if (expandedStorage.value[props.label] === true) return true;\n if (props.expanded) {\n expandedStorage.value[props.label] = true;\n }\n }\n return props.expanded;\n}\n\nconst isExpanded = ref(resolveInitialExpanded());\n\nwatch(\n () => props.expanded,\n (val) => {\n if (expandedStorage && props.label !== undefined) return;\n isExpanded.value = val;\n },\n);\n\nwatch(isExpanded, (val) => {\n if (expandedStorage && props.label !== undefined) {\n if (val) {\n expandedStorage.value[props.label] = true;\n } else {\n delete expandedStorage.value[props.label];\n }\n }\n updateSlotAria();\n});\n\nconst contentRef = ref<HTMLElement | null>(null);\n\nfunction updateSlotAria() {\n if (!hasChildren.value || !contentRef.value) return;\n const focusable = contentRef.value.querySelector(\"a, button\");\n if (focusable) {\n focusable.setAttribute(\"aria-controls\", id + \"-children\");\n focusable.setAttribute(\"aria-expanded\", isExpanded.value ? \"true\" : \"false\");\n } else {\n console.warn(\"No focusable element found for GTreeMenuItem with label:\", props.label, \"Every item must at least have a plain button to properly work for accessibility.\");\n }\n}\n\nonMounted(updateSlotAria);\nonUpdated(updateSlotAria);\n\nfunction toggle() {\n isExpanded.value = !isExpanded.value;\n if (isExpanded.value) {\n emit(\"expand\");\n } else {\n emit(\"collapse\");\n }\n}\n\n// --- Expand / Collapse All registration ---\n\nconst itemId = Symbol();\nconst expandableItems = inject<Map<symbol, boolean> | null>(\n \"g-tree-menu-expandable-items\",\n null,\n);\nconst expandAllSignal = inject<Ref<{ expanded: boolean; version: number }> | null>(\n \"g-tree-menu-expand-all-signal\",\n null,\n);\nconst lastProcessedVersion = ref(0);\n\nfunction registerItem() {\n if (expandableItems && hasChildren.value) {\n expandableItems.set(itemId, isExpanded.value);\n }\n}\n\nfunction unregisterItem() {\n expandableItems?.delete(itemId);\n}\n\nwatch(isExpanded, (val) => {\n if (expandableItems && hasChildren.value) {\n expandableItems.set(itemId, val);\n }\n});\n\nif (expandAllSignal) {\n watch(\n () => expandAllSignal.value.version,\n () => {\n if (!hasChildren.value) return;\n isExpanded.value = expandAllSignal.value.expanded;\n lastProcessedVersion.value = expandAllSignal.value.version;\n },\n );\n}\n\nonMounted(() => {\n registerItem();\n if (\n expandAllSignal &&\n hasChildren.value &&\n expandAllSignal.value.version > lastProcessedVersion.value &&\n expandAllSignal.value.expanded\n ) {\n isExpanded.value = true;\n lastProcessedVersion.value = expandAllSignal.value.version;\n }\n});\n\nonBeforeUnmount(() => {\n unregisterItem();\n});\n\nfunction handleContentClick(event: MouseEvent) {\n if (!(event.target as Element).closest(\"a\")) {\n toggle();\n }\n}\n\nfunction handleContentKeydown(event: KeyboardEvent) {\n if (event.key === \"Enter\" || event.key === \" \") {\n toggle();\n event.preventDefault();\n }\n}\n</script>\n\n<template>\n <li\n class=\"g-tree-menu__item\"\n :data-tree-expandable=\"hasChildren ? 'true' : undefined\"\n >\n <!-- Parent: has children → toggle button + slot content (which may contain a link) -->\n <div v-if=\"hasChildren\" class=\"g-tree-menu__row\">\n <div\n class=\"g-tree-menu__toggle-btn\"\n @click=\"toggle\"\n >\n <svg\n class=\"g-tree-menu__chevron\"\n :class=\"{ 'g-tree-menu__chevron--expanded': isExpanded }\"\n role=\"none presentation\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n </div>\n <span\n ref=\"contentRef\"\n class=\"g-tree-menu__row-content\"\n data-tree-primary\n @click=\"handleContentClick\"\n @keydown=\"handleContentKeydown\"\n >\n <span class=\"g-tree-menu__row-content-text\">\n <slot />\n </span>\n </span>\n </div>\n\n <!-- Leaf: no children → just render the slot content -->\n <div v-else class=\"g-tree-menu__row g-tree-menu__row--leaf\">\n <span class=\"g-tree-menu__spacer\"></span>\n <span\n class=\"g-tree-menu__row-content\"\n data-tree-primary\n >\n <slot />\n </span>\n </div>\n\n <!-- Children (shown when expanded) -->\n <GTreeMenuList v-if=\"hasChildren && isExpanded\" :id=\"id + '-children'\">\n <slot name=\"children\" />\n </GTreeMenuList>\n </li>\n</template>\n\n<style>\ng-tree-menu-item,\n.g-tree-menu__item {\n display: block;\n}\n\ng-tree-menu-item > a {\n color: inherit;\n display: flex;\n align-items: flex-start;\n text-decoration: none;\n}\n\ng-tree-menu-item > a:hover {\n text-decoration: underline;\n}\n\n.g-tree-menu__row {\n display: flex;\n align-items: stretch;\n}\n\n.g-tree-menu__toggle-btn {\n flex-shrink: 0;\n align-self: center;\n background: none;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n box-sizing: border-box;\n border-radius: 2px;\n margin-right: 2px;\n font-size: inherit;\n font-weight: inherit;\n}\n\n.g-tree-menu__spacer {\n display: inline-block;\n width: 2em;\n height: 2em;\n min-width: 2em;\n min-height: 2em;\n margin-right: 2px;\n align-self: center;\n}\n\n.g-tree-menu__row-content {\n display: flex;\n align-items: stretch;\n flex: 1;\n padding: 0 0.5em 0 0;\n box-sizing: border-box;\n\n button, a {\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n padding: 2px 0;\n margin: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n flex: 1;\n text-decoration: none;\n height: 100%;\n\n &:hover {\n text-decoration: underline;\n }\n }\n}\n\n.g-tree-menu__row:not(.g-tree-menu__row--leaf) .g-tree-menu__row-content {\n cursor: pointer;\n}\n\n.g-tree-menu__row-content-text {\n flex: 1;\n display: flex;\n align-items: center;\n}\n\n.g-tree-menu__chevron {\n width: 2em;\n height: 2em;\n min-width: 2em;\n min-height: 2em;\n padding: 0.35em;\n box-sizing: border-box;\n flex-shrink: 0;\n transform: rotate(0deg);\n transition: transform 0.1s ease;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n}\n\n.g-tree-menu__chevron--expanded {\n transform: rotate(90deg);\n}\n\n.g-tree-menu__toggle-btn:focus-visible,\n.g-tree-menu__row-content:focus-visible,\n.g-tree-menu__row-content a:focus-visible,\n.g-tree-menu__row-content button:focus-visible{\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n\n.g-tree-menu__row .g-tree-menu__toggle-btn {\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n }\n}\n\n.g-tree-menu--dark {\n .g-tree-menu__toggle-btn,\n .g-tree-menu__row-content a {\n color: var(--g-surface-0);\n\n &:hover {\n color: var(--g-accent-500);\n }\n }\n}\n\n.g-tree-menu--light {\n .g-tree-menu__row-content,\n .g-tree-menu__row-content a {\n color: var(--g-primary-500);\n\n &:hover {\n color: var(--g-accent-700);\n }\n &:focus-visible {\n color: var(--ilw-color--focus--text);\n }\n }\n}\n\n</style>\n","import { ref, Ref, computed, ComputedRef, shallowReactive } from \"vue\";\n\nexport interface FormField {\n name: string;\n value: Ref<any>;\n errors: Ref<string[]> | ComputedRef<string[]>;\n}\n\nexport interface UseFormReturn {\n fields: Record<string, FormField>;\n values: Ref<Record<string, any>>;\n errors: Ref<Record<string, string[]>>;\n isSubmitting: Ref<boolean>;\n hasErrors: Ref<boolean>;\n registerField: (name: string, field: FormField) => void;\n unregisterField: (name: string) => void;\n submit: (handler: (values: Record<string, any>) => Promise<void> | void) => Promise<void>;\n}\n\n/**\n * Composable to manage form state and link form inputs together.\n * Uses reactive state pattern - errors are provided as reactive props to input components.\n */\nexport function useForm(): UseFormReturn {\n const fields: Record<string, FormField> = shallowReactive({});\n const isSubmitting = ref(false);\n\n const values = computed(() => {\n const vals: Record<string, any> = {};\n Object.entries(fields).forEach(([name, field]) => {\n if (field && field.value) {\n vals[name] = field.value.value;\n }\n });\n return vals;\n });\n\n const errors = computed(() => {\n const errs: Record<string, string[]> = {};\n Object.entries(fields).forEach(([name, field]) => {\n const errorValue = field.errors.value;\n if (errorValue && errorValue.length > 0) {\n errs[name] = errorValue;\n }\n });\n return errs;\n });\n\n const hasErrors = computed(() => {\n return Object.keys(errors.value).length > 0;\n });\n\n function registerField(name: string, field: FormField) {\n fields[name] = field;\n }\n\n function unregisterField(name: string) {\n delete fields[name];\n }\n\n async function submit(handler: (values: Record<string, any>) => Promise<void> | void) {\n if (isSubmitting.value) {\n return;\n }\n isSubmitting.value = true;\n try {\n await handler(values.value);\n } finally {\n isSubmitting.value = false;\n }\n }\n\n return {\n fields,\n values,\n errors,\n isSubmitting,\n hasErrors,\n registerField,\n unregisterField,\n submit,\n };\n}\n","import { useForm, UseFormReturn } from \"./useForm\";\n\nfunction getFormStore() {\n const globalScope = globalThis as typeof globalThis & {\n __GRAD_VUE_WC_FORMS__?: Map<string, UseFormReturn>;\n };\n\n if (!globalScope.__GRAD_VUE_WC_FORMS__) {\n globalScope.__GRAD_VUE_WC_FORMS__ = new Map();\n }\n\n return globalScope.__GRAD_VUE_WC_FORMS__;\n}\n\nexport function useWebComponentForm(key = \"default\") {\n const forms = getFormStore();\n const formKey = key || \"default\";\n\n if (!forms.has(formKey)) {\n forms.set(formKey, useForm());\n }\n\n return forms.get(formKey)!;\n}\n","import { Ref, computed, inject, onMounted, onBeforeUnmount, ComputedRef, useAttrs } from \"vue\";\nimport { UseFormReturn } from \"./useForm\";\nimport { useWebComponentForm } from \"./useWebComponentForm\";\nimport { isCustomElementMode } from \"./useCustomElementAttrs\";\n\nexport interface UseFormFieldOptions {\n /**\n * The name of the field (required for form registration)\n */\n name?: string;\n /**\n * The model value ref to register with the form\n */\n value: Ref<any>;\n /**\n * Error messages from props (optional) - should be a reactive reference\n */\n errors?: Ref<string[]> | ComputedRef<string[]>;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n}\n\nexport interface UseFormFieldReturn {\n /**\n * Combined filtered errors array\n */\n displayErrors: Ref<string[]>;\n /**\n * Whether the field has any errors\n */\n hasErrors: Ref<boolean>;\n}\n\n/**\n * Composable to handle form field registration and error management.\n */\nexport function useFormField(options: UseFormFieldOptions): UseFormFieldReturn {\n const attrs = useAttrs();\n const attrFormKey = typeof attrs[\"form-key\"] === \"string\" ? attrs[\"form-key\"] : undefined;\n const formKey = options.formKey ?? attrFormKey ?? \"default\";\n const injectedForm = inject<UseFormReturn | null>(\"form\", null);\n const form =\n injectedForm ??\n (isCustomElementMode() ? useWebComponentForm(formKey) : null);\n\n const displayErrors = computed(() => {\n const allErrors: string[] = [];\n \n // Add prop errors (now reactive)\n if (options.errors) {\n allErrors.push(...options.errors.value.filter(Boolean));\n }\n return allErrors;\n });\n\n const hasErrors = computed(() => displayErrors.value.length > 0);\n\n // Register field with form if name is provided\n const name = options.name;\n if (form && name) {\n onMounted(() => {\n // Note: FormField interface requires name field for consistency,\n // even though it's also used as the registration key\n form.registerField(name, {\n name: name,\n value: options.value,\n errors: displayErrors,\n });\n });\n\n onBeforeUnmount(() => {\n if (options.name) {\n form.unregisterField(options.name);\n }\n });\n }\n\n return {\n displayErrors,\n hasErrors\n };\n}\n","<script lang=\"ts\">\n/**\n * Internal component for displaying form field error messages.\n * This component is used by form input components to display validation errors\n * in a consistent way across all form fields.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Array of error messages to display\n */\n errors: string[];\n /**\n * ID for the error container (used for aria-describedby)\n */\n id: string;\n}\n\ndefineProps<Props>();\n</script>\n\n<template>\n <div\n v-if=\"errors.length > 0\"\n class=\"g-form-error-messages\"\n :id=\"id\"\n role=\"alert\"\n >\n <div\n v-for=\"(errorMsg, index) in errors\"\n :key=\"index\"\n class=\"g-form-error-message\"\n >\n <svg class=\"g-form-error-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\">\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M320 64C334.7 64 348.2 72.1 355.2 85L571.2 485C577.9 497.4 577.6 512.4 570.4 524.5C563.2 536.6 550.1 544 536 544L104 544C89.9 544 76.8 536.6 69.6 524.5C62.4 512.4 62.1 497.4 68.8 485L284.8 85C291.8 72.1 305.3 64 320 64zM320 416C302.3 416 288 430.3 288 448C288 465.7 302.3 480 320 480C337.7 480 352 465.7 352 448C352 430.3 337.7 416 320 416zM320 224C301.8 224 287.3 239.5 288.6 257.7L296 361.7C296.9 374.2 307.4 384 319.9 384C332.5 384 342.9 374.3 343.8 361.7L351.2 257.7C352.5 239.5 338.1 224 319.8 224z\"\n />\n </svg>\n {{ errorMsg }}\n </div>\n </div>\n</template>\n\n<style>\n.g-form-error-messages {\n display: flex;\n flex-direction: column;\n gap: 0.25em;\n margin-top: 0.25em;\n}\n\n.g-form-error-message {\n background: var(--g-surface-0);\n color: var(--g-danger-600);\n padding: 0.25em 0.5em;\n}\n\n.g-form-error-icon {\n height: 1.2em;\n padding: 0.2em 0;\n display: inline;\n margin: 0 0.2em 0 0;\n vertical-align: middle;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Internal component for displaying form field error messages.\n * This component is used by form input components to display validation errors\n * in a consistent way across all form fields.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Array of error messages to display\n */\n errors: string[];\n /**\n * ID for the error container (used for aria-describedby)\n */\n id: string;\n}\n\ndefineProps<Props>();\n</script>\n\n<template>\n <div\n v-if=\"errors.length > 0\"\n class=\"g-form-error-messages\"\n :id=\"id\"\n role=\"alert\"\n >\n <div\n v-for=\"(errorMsg, index) in errors\"\n :key=\"index\"\n class=\"g-form-error-message\"\n >\n <svg class=\"g-form-error-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\">\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M320 64C334.7 64 348.2 72.1 355.2 85L571.2 485C577.9 497.4 577.6 512.4 570.4 524.5C563.2 536.6 550.1 544 536 544L104 544C89.9 544 76.8 536.6 69.6 524.5C62.4 512.4 62.1 497.4 68.8 485L284.8 85C291.8 72.1 305.3 64 320 64zM320 416C302.3 416 288 430.3 288 448C288 465.7 302.3 480 320 480C337.7 480 352 465.7 352 448C352 430.3 337.7 416 320 416zM320 224C301.8 224 287.3 239.5 288.6 257.7L296 361.7C296.9 374.2 307.4 384 319.9 384C332.5 384 342.9 374.3 343.8 361.7L351.2 257.7C352.5 239.5 338.1 224 319.8 224z\"\n />\n </svg>\n {{ errorMsg }}\n </div>\n </div>\n</template>\n\n<style>\n.g-form-error-messages {\n display: flex;\n flex-direction: column;\n gap: 0.25em;\n margin-top: 0.25em;\n}\n\n.g-form-error-message {\n background: var(--g-surface-0);\n color: var(--g-danger-600);\n padding: 0.25em 0.5em;\n}\n\n.g-form-error-icon {\n height: 1.2em;\n padding: 0.2em 0;\n display: inline;\n margin: 0 0.2em 0 0;\n vertical-align: middle;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A text input with styling for a label, instructions, and error messages.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n * All non-prop attributes are passed through to the input element, including\n * `id`.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, ref, useId, watch, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Example Label\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Prefix text (displayed before input)\n * @demo\n */\n prefix?: string;\n /**\n * Suffix text (displayed after input)\n * @demo\n */\n suffix?: string;\n /**\n * Debounce in milliseconds\n * @demo\n */\n debounce?: number;\n\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n prefix: \"\",\n suffix: \"\",\n debounce: 100,\n name: undefined,\n formKey: undefined,\n});\nconst model = defineModel<string | null>({ type: String });\n\nconst id = useId();\nconst { attrs, isCustomElement, forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\nconst inputId = computed(() => {\n if (isCustomElement) {\n return id;\n }\n return (attrs.id as string) || id;\n});\n\n// Use form field composable for form registration and error handling\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [\n {\n was: string | null | undefined;\n to: string | null;\n },\n ];\n}>();\n\nconst lastInputValue = ref<string | null>(model.value ?? \"\");\nlet inputTimer: ReturnType<typeof setTimeout> | null = null;\n\nfunction emitChangeIfNeeded(val: string | null) {\n if (val !== model.value) {\n const prev = model.value;\n model.value = val;\n emit(\"change\", {\n was: prev,\n to: val,\n });\n }\n}\n\nfunction onInput(e: Event) {\n const value = (e.target as HTMLInputElement).value;\n lastInputValue.value = value;\n if (inputTimer) {\n clearTimeout(inputTimer);\n }\n inputTimer = setTimeout(() => {\n emitChangeIfNeeded(lastInputValue.value);\n inputTimer = null;\n }, props.debounce);\n}\n\nfunction onBlur(e: FocusEvent) {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n}\n\nfunction onPaste(e: ClipboardEvent) {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n // Wait for paste to update value\n setTimeout(() => {\n const value = (e.target as HTMLInputElement).value;\n emitChangeIfNeeded(value);\n }, 0);\n}\n\nfunction onKeydown(e: KeyboardEvent) {\n if (e.key === \"PageUp\" || e.key === \"PageDown\") {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n }\n if (e.key === \"Enter\") {\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n }\n}\n</script>\n\n<template>\n <div\n class=\"g-text-input-wrap\"\n :class=\"{ 'g-text-input-has-error': hasErrors }\"\n >\n <label\n v-if=\"props.label\"\n :for=\"inputId\"\n class=\"g-text-input-label\"\n >{{ props.label\n }}<span v-if=\"props.required\" class=\"g-text-input-required\" aria-hidden=\"true\"> *</span></label\n >\n <div\n v-if=\"$slots.instructions || instructions\"\n :id=\"'instructions-' + id\"\n class=\"g-text-input-instructions\"\n >\n <slot name=\"instructions\">{{ instructions }}</slot>\n </div>\n <div :class=\"[{\n 'g-text-input-field-wrapper': true,\n }, `g-text-input-field-wrapper--${name || 'nameless'}`]\">\n <span v-if=\"props.prefix\" class=\"g-text-input-prefix\">{{\n props.prefix\n }}</span>\n <input\n :value=\"model\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n @input=\"onInput\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n @keydown=\"onKeydown\"\n type=\"text\"\n class=\"g-text-input\"\n v-bind=\"{\n ...forwardedAttrs,\n id: inputId,\n 'aria-describedby':\n $slots.instructions || instructions\n ? 'instructions-' + id\n : undefined,\n 'aria-errormessage': hasErrors\n ? 'error-message-' + id\n : undefined,\n }\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n />\n <span v-if=\"props.suffix\" class=\"g-text-input-suffix\">{{\n props.suffix\n }}</span>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + id\"\n />\n </div>\n</template>\n\n<style>\ng-text-input {\n display: block;\n}\n.g-text-input-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n.g-text-input-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n.g-text-input-required {\n color: var(--g-danger-600);\n}\n.g-text-input-instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n}\n.g-text-input-field-wrapper {\n display: flex;\n align-items: center;\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n background: var(--g-surface-0);\n overflow: hidden;\n\n &:focus-within {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n.g-text-input-prefix,\n.g-text-input-suffix {\n padding: 0.5em;\n background: var(--g-surface-100);\n color: var(--g-surface-700);\n white-space: nowrap;\n font-family: var(--il-font-sans);\n}\n.g-text-input {\n width: 100%;\n padding: 0.5em;\n font-size: 1em;\n border: none;\n border-radius: 0;\n background: transparent;\n color: var(--g-surface-950);\n font-family: var(--il-font-sans);\n}\n.g-text-input:focus {\n outline: none;\n}\n.g-text-input-has-error {\n .g-text-input-field-wrapper {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n }\n}\n.g-text-input:disabled {\n background: transparent;\n color: var(--g-surface-700);\n}\n.g-text-input-field-wrapper:has(.g-text-input:disabled) {\n background: var(--g-surface-100);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A text input with styling for a label, instructions, and error messages.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n * All non-prop attributes are passed through to the input element, including\n * `id`.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, ref, useId, watch, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Example Label\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Prefix text (displayed before input)\n * @demo\n */\n prefix?: string;\n /**\n * Suffix text (displayed after input)\n * @demo\n */\n suffix?: string;\n /**\n * Debounce in milliseconds\n * @demo\n */\n debounce?: number;\n\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n prefix: \"\",\n suffix: \"\",\n debounce: 100,\n name: undefined,\n formKey: undefined,\n});\nconst model = defineModel<string | null>({ type: String });\n\nconst id = useId();\nconst { attrs, isCustomElement, forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\nconst inputId = computed(() => {\n if (isCustomElement) {\n return id;\n }\n return (attrs.id as string) || id;\n});\n\n// Use form field composable for form registration and error handling\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [\n {\n was: string | null | undefined;\n to: string | null;\n },\n ];\n}>();\n\nconst lastInputValue = ref<string | null>(model.value ?? \"\");\nlet inputTimer: ReturnType<typeof setTimeout> | null = null;\n\nfunction emitChangeIfNeeded(val: string | null) {\n if (val !== model.value) {\n const prev = model.value;\n model.value = val;\n emit(\"change\", {\n was: prev,\n to: val,\n });\n }\n}\n\nfunction onInput(e: Event) {\n const value = (e.target as HTMLInputElement).value;\n lastInputValue.value = value;\n if (inputTimer) {\n clearTimeout(inputTimer);\n }\n inputTimer = setTimeout(() => {\n emitChangeIfNeeded(lastInputValue.value);\n inputTimer = null;\n }, props.debounce);\n}\n\nfunction onBlur(e: FocusEvent) {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n}\n\nfunction onPaste(e: ClipboardEvent) {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n // Wait for paste to update value\n setTimeout(() => {\n const value = (e.target as HTMLInputElement).value;\n emitChangeIfNeeded(value);\n }, 0);\n}\n\nfunction onKeydown(e: KeyboardEvent) {\n if (e.key === \"PageUp\" || e.key === \"PageDown\") {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n }\n if (e.key === \"Enter\") {\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n }\n}\n</script>\n\n<template>\n <div\n class=\"g-text-input-wrap\"\n :class=\"{ 'g-text-input-has-error': hasErrors }\"\n >\n <label\n v-if=\"props.label\"\n :for=\"inputId\"\n class=\"g-text-input-label\"\n >{{ props.label\n }}<span v-if=\"props.required\" class=\"g-text-input-required\" aria-hidden=\"true\"> *</span></label\n >\n <div\n v-if=\"$slots.instructions || instructions\"\n :id=\"'instructions-' + id\"\n class=\"g-text-input-instructions\"\n >\n <slot name=\"instructions\">{{ instructions }}</slot>\n </div>\n <div :class=\"[{\n 'g-text-input-field-wrapper': true,\n }, `g-text-input-field-wrapper--${name || 'nameless'}`]\">\n <span v-if=\"props.prefix\" class=\"g-text-input-prefix\">{{\n props.prefix\n }}</span>\n <input\n :value=\"model\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n @input=\"onInput\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n @keydown=\"onKeydown\"\n type=\"text\"\n class=\"g-text-input\"\n v-bind=\"{\n ...forwardedAttrs,\n id: inputId,\n 'aria-describedby':\n $slots.instructions || instructions\n ? 'instructions-' + id\n : undefined,\n 'aria-errormessage': hasErrors\n ? 'error-message-' + id\n : undefined,\n }\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n />\n <span v-if=\"props.suffix\" class=\"g-text-input-suffix\">{{\n props.suffix\n }}</span>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + id\"\n />\n </div>\n</template>\n\n<style>\ng-text-input {\n display: block;\n}\n.g-text-input-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n.g-text-input-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n.g-text-input-required {\n color: var(--g-danger-600);\n}\n.g-text-input-instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n}\n.g-text-input-field-wrapper {\n display: flex;\n align-items: center;\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n background: var(--g-surface-0);\n overflow: hidden;\n\n &:focus-within {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n.g-text-input-prefix,\n.g-text-input-suffix {\n padding: 0.5em;\n background: var(--g-surface-100);\n color: var(--g-surface-700);\n white-space: nowrap;\n font-family: var(--il-font-sans);\n}\n.g-text-input {\n width: 100%;\n padding: 0.5em;\n font-size: 1em;\n border: none;\n border-radius: 0;\n background: transparent;\n color: var(--g-surface-950);\n font-family: var(--il-font-sans);\n}\n.g-text-input:focus {\n outline: none;\n}\n.g-text-input-has-error {\n .g-text-input-field-wrapper {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n }\n}\n.g-text-input:disabled {\n background: transparent;\n color: var(--g-surface-700);\n}\n.g-text-input-field-wrapper:has(.g-text-input:disabled) {\n background: var(--g-surface-100);\n}\n</style>\n\n","import { computed, onBeforeUnmount, Ref, ref } from \"vue\";\n\nconst OVERLAY_Z_INDEX_BASE = 100;\nconst MODAL_Z_INDEX_BASE = 200;\nconst DEFAULT_TOOLTIP_Z_INDEX = 102;\n\nexport type OverlayStack = {\n push: () => void;\n pop: () => void;\n isTop: Ref<boolean>;\n zIndex: Ref<number>;\n}\n\nexport type OverlayStackState = {\n hasModal: Ref<boolean>;\n hasOverlay: Ref<boolean>;\n hasScrollLock: Ref<boolean>;\n};\n\nconst stack = ref<string[]>([]);\nconst modalStack = ref<string[]>([]);\nconst scrollLockStack = ref<string[]>([]);\n\nfunction updateBodyScrollLock() {\n if (typeof document === \"undefined\") return;\n if (scrollLockStack.value.length > 0) {\n // Account for possible vertical scrollbar reducing viewport width\n const scrollbarWidth =\n window.innerWidth - document.documentElement.clientWidth;\n document.body.classList.add(\"g-scroll-lock\");\n document.body.style.paddingRight = `${scrollbarWidth}px`;\n document.body.style.setProperty(\"--g-scrollbar-width\", `${scrollbarWidth}px`);\n } else {\n document.body.style.paddingRight = \"0\";\n document.body.classList.remove(\"g-scroll-lock\");\n document.body.style.removeProperty(\"--g-scrollbar-width\");\n }\n}\n\nexport function useOverlayStack(id: string, modal = false, lockScroll = false): OverlayStack {\n if (typeof document === \"undefined\") {\n return {} as OverlayStack;\n }\n\n const stackRef = modal ? modalStack : stack;\n\n function push() {\n stackRef.value.push(id);\n if (lockScroll && !scrollLockStack.value.includes(id)) {\n scrollLockStack.value.push(id);\n updateBodyScrollLock();\n }\n }\n\n function pop() {\n const idx = stackRef.value.lastIndexOf(id);\n if (idx !== -1) {\n stackRef.value.splice(idx, 1);\n }\n const lockIdx = scrollLockStack.value.lastIndexOf(id);\n if (lockIdx !== -1) {\n scrollLockStack.value.splice(lockIdx, 1);\n updateBodyScrollLock();\n }\n }\n\n const isTop = computed(() => {\n if (!modal && modalStack.value.length > 0) {\n return false;\n }\n return (\n stackRef.value.length > 0 &&\n stackRef.value[stackRef.value.length - 1] === id\n );\n });\n\n const zIndex = computed(() => {\n const pos = stackRef.value.indexOf(id);\n return pos === -1 ? 0 : (modal ? MODAL_Z_INDEX_BASE : OVERLAY_Z_INDEX_BASE) + pos;\n });\n\n onBeforeUnmount(pop);\n\n return { push, pop, isTop, zIndex };\n}\n\nexport function useOverlayStackState(): OverlayStackState {\n if (typeof document === \"undefined\") {\n return {} as OverlayStackState;\n }\n\n const hasModal = computed(() => modalStack.value.length > 0);\n const hasOverlay = computed(\n () => stack.value.length > 0 || modalStack.value.length > 0,\n );\n const hasScrollLock = computed(() => scrollLockStack.value.length > 0);\n\n return { hasModal, hasOverlay, hasScrollLock };\n}\n\n/**\n * Returns a z-index value that is above all currently open overlays.\n * Uses the same base values as useOverlayStack: 100 + pos for non-modal,\n * 200 + pos for modal. Falls back to DEFAULT_TOOLTIP_Z_INDEX when no\n * overlays are open.\n *\n * This function can be called outside of a Vue component setup context,\n * which makes it suitable for use in Vue directives.\n */\nexport function getTopZIndex(): number {\n let max = 0;\n stack.value.forEach((_, idx) => {\n max = Math.max(max, OVERLAY_Z_INDEX_BASE + idx);\n });\n modalStack.value.forEach((_, idx) => {\n max = Math.max(max, MODAL_Z_INDEX_BASE + idx);\n });\n return max > 0 ? max + 1 : DEFAULT_TOOLTIP_Z_INDEX;\n}\n","import { computed, customRef, effectScope, getCurrentInstance, getCurrentScope, hasInjectionContext, inject, isReactive, isRef, nextTick, onBeforeMount, onBeforeUnmount, onMounted, onScopeDispose, onUnmounted, provide, reactive, readonly, ref, shallowReadonly, shallowRef, toRef as toRef$1, toRefs as toRefs$1, toValue, unref, watch, watchEffect } from \"vue\";\n\n//#region computedEager/index.ts\n/**\n*\n* @deprecated This function will be removed in future version.\n*\n* Note: If you are using Vue 3.4+, you can straight use computed instead.\n* Because in Vue 3.4+, if computed new value does not change,\n* computed, effect, watch, watchEffect, render dependencies will not be triggered.\n* refer: https://github.com/vuejs/core/pull/5912\n*\n* @param fn effect function\n* @param options WatchOptionsBase\n* @returns readonly shallowRef\n*/\nfunction computedEager(fn, options) {\n\tvar _options$flush;\n\tconst result = shallowRef();\n\twatchEffect(() => {\n\t\tresult.value = fn();\n\t}, {\n\t\t...options,\n\t\tflush: (_options$flush = options === null || options === void 0 ? void 0 : options.flush) !== null && _options$flush !== void 0 ? _options$flush : \"sync\"\n\t});\n\treturn readonly(result);\n}\n/** @deprecated use `computedEager` instead */\nconst eagerComputed = computedEager;\n\n//#endregion\n//#region computedWithControl/index.ts\n/**\n* Explicitly define the deps of computed.\n*\n* @param source\n* @param fn\n*/\nfunction computedWithControl(source, fn, options = {}) {\n\tlet v = void 0;\n\tlet track;\n\tlet trigger;\n\tlet dirty = true;\n\tconst update = () => {\n\t\tdirty = true;\n\t\ttrigger();\n\t};\n\twatch(source, update, {\n\t\tflush: \"sync\",\n\t\t...options\n\t});\n\tconst get$1 = typeof fn === \"function\" ? fn : fn.get;\n\tconst set$1 = typeof fn === \"function\" ? void 0 : fn.set;\n\tconst result = customRef((_track, _trigger) => {\n\t\ttrack = _track;\n\t\ttrigger = _trigger;\n\t\treturn {\n\t\t\tget() {\n\t\t\t\tif (dirty) {\n\t\t\t\t\tv = get$1(v);\n\t\t\t\t\tdirty = false;\n\t\t\t\t}\n\t\t\t\ttrack();\n\t\t\t\treturn v;\n\t\t\t},\n\t\t\tset(v$1) {\n\t\t\t\tset$1 === null || set$1 === void 0 || set$1(v$1);\n\t\t\t}\n\t\t};\n\t});\n\tresult.trigger = update;\n\treturn result;\n}\n/** @deprecated use `computedWithControl` instead */\nconst controlledComputed = computedWithControl;\n\n//#endregion\n//#region tryOnScopeDispose/index.ts\n/**\n* Call onScopeDispose() if it's inside an effect scope lifecycle, if not, do nothing\n*\n* @param fn\n*/\nfunction tryOnScopeDispose(fn, failSilently) {\n\tif (getCurrentScope()) {\n\t\tonScopeDispose(fn, failSilently);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n//#endregion\n//#region createEventHook/index.ts\n/**\n* Utility for creating event hooks\n*\n* @see https://vueuse.org/createEventHook\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction createEventHook() {\n\tconst fns = /* @__PURE__ */ new Set();\n\tconst off = (fn) => {\n\t\tfns.delete(fn);\n\t};\n\tconst clear = () => {\n\t\tfns.clear();\n\t};\n\tconst on = (fn) => {\n\t\tfns.add(fn);\n\t\tconst offFn = () => off(fn);\n\t\ttryOnScopeDispose(offFn);\n\t\treturn { off: offFn };\n\t};\n\tconst trigger = (...args) => {\n\t\treturn Promise.all(Array.from(fns).map((fn) => fn(...args)));\n\t};\n\treturn {\n\t\ton,\n\t\toff,\n\t\ttrigger,\n\t\tclear\n\t};\n}\n\n//#endregion\n//#region createGlobalState/index.ts\n/**\n* Keep states in the global scope to be reusable across Vue instances.\n*\n* @see https://vueuse.org/createGlobalState\n* @param stateFactory A factory function to create the state\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction createGlobalState(stateFactory) {\n\tlet initialized = false;\n\tlet state;\n\tconst scope = effectScope(true);\n\treturn ((...args) => {\n\t\tif (!initialized) {\n\t\t\tstate = scope.run(() => stateFactory(...args));\n\t\t\tinitialized = true;\n\t\t}\n\t\treturn state;\n\t});\n}\n\n//#endregion\n//#region provideLocal/map.ts\nconst localProvidedStateMap = /* @__PURE__ */ new WeakMap();\n\n//#endregion\n//#region injectLocal/index.ts\n/**\n* On the basis of `inject`, it is allowed to directly call inject to obtain the value after call provide in the same component.\n*\n* @example\n* ```ts\n* injectLocal('MyInjectionKey', 1)\n* const injectedValue = injectLocal('MyInjectionKey') // injectedValue === 1\n* ```\n*\n* @__NO_SIDE_EFFECTS__\n*/\nconst injectLocal = (...args) => {\n\tvar _getCurrentInstance;\n\tconst key = args[0];\n\tconst instance = (_getCurrentInstance = getCurrentInstance()) === null || _getCurrentInstance === void 0 ? void 0 : _getCurrentInstance.proxy;\n\tconst owner = instance !== null && instance !== void 0 ? instance : getCurrentScope();\n\tif (owner == null && !hasInjectionContext()) throw new Error(\"injectLocal must be called in setup\");\n\tif (owner && localProvidedStateMap.has(owner) && key in localProvidedStateMap.get(owner)) return localProvidedStateMap.get(owner)[key];\n\treturn inject(...args);\n};\n\n//#endregion\n//#region provideLocal/index.ts\n/**\n* On the basis of `provide`, it is allowed to directly call inject to obtain the value after call provide in the same component.\n*\n* @example\n* ```ts\n* provideLocal('MyInjectionKey', 1)\n* const injectedValue = injectLocal('MyInjectionKey') // injectedValue === 1\n* ```\n*/\nfunction provideLocal(key, value) {\n\tvar _getCurrentInstance;\n\tconst instance = (_getCurrentInstance = getCurrentInstance()) === null || _getCurrentInstance === void 0 ? void 0 : _getCurrentInstance.proxy;\n\tconst owner = instance !== null && instance !== void 0 ? instance : getCurrentScope();\n\tif (owner == null) throw new Error(\"provideLocal must be called in setup\");\n\tif (!localProvidedStateMap.has(owner)) localProvidedStateMap.set(owner, Object.create(null));\n\tconst localProvidedState = localProvidedStateMap.get(owner);\n\tlocalProvidedState[key] = value;\n\treturn provide(key, value);\n}\n\n//#endregion\n//#region createInjectionState/index.ts\n/**\n* Create global state that can be injected into components.\n*\n* @see https://vueuse.org/createInjectionState\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction createInjectionState(composable, options) {\n\tconst key = (options === null || options === void 0 ? void 0 : options.injectionKey) || Symbol(composable.name || \"InjectionState\");\n\tconst defaultValue = options === null || options === void 0 ? void 0 : options.defaultValue;\n\tconst useProvidingState = (...args) => {\n\t\tconst state = composable(...args);\n\t\tprovideLocal(key, state);\n\t\treturn state;\n\t};\n\tconst useInjectedState = () => injectLocal(key, defaultValue);\n\treturn [useProvidingState, useInjectedState];\n}\n\n//#endregion\n//#region createRef/index.ts\n/**\n* Returns a `deepRef` or `shallowRef` depending on the `deep` param.\n*\n* @example createRef(1) // ShallowRef<number>\n* @example createRef(1, false) // ShallowRef<number>\n* @example createRef(1, true) // Ref<number>\n* @example createRef(\"string\") // ShallowRef<string>\n* @example createRef<\"A\"|\"B\">(\"A\", true) // Ref<\"A\"|\"B\">\n*\n* @param value\n* @param deep\n* @returns the `deepRef` or `shallowRef`\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction createRef(value, deep) {\n\tif (deep === true) return ref(value);\n\telse return shallowRef(value);\n}\n\n//#endregion\n//#region utils/is.ts\nconst isClient = typeof window !== \"undefined\" && typeof document !== \"undefined\";\nconst isWorker = typeof WorkerGlobalScope !== \"undefined\" && globalThis instanceof WorkerGlobalScope;\nconst isDef = (val) => typeof val !== \"undefined\";\nconst notNullish = (val) => val != null;\nconst assert = (condition, ...infos) => {\n\tif (!condition) console.warn(...infos);\n};\nconst toString = Object.prototype.toString;\nconst isObject = (val) => toString.call(val) === \"[object Object]\";\nconst now = () => Date.now();\nconst timestamp = () => +Date.now();\nconst clamp = (n, min, max) => Math.min(max, Math.max(min, n));\nconst noop = () => {};\nconst rand = (min, max) => {\n\tmin = Math.ceil(min);\n\tmax = Math.floor(max);\n\treturn Math.floor(Math.random() * (max - min + 1)) + min;\n};\nconst hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);\nconst isIOS = /* @__PURE__ */ getIsIOS();\nfunction getIsIOS() {\n\tvar _window, _window2, _window3;\n\treturn isClient && !!((_window = window) === null || _window === void 0 || (_window = _window.navigator) === null || _window === void 0 ? void 0 : _window.userAgent) && (/iP(?:ad|hone|od)/.test(window.navigator.userAgent) || ((_window2 = window) === null || _window2 === void 0 || (_window2 = _window2.navigator) === null || _window2 === void 0 ? void 0 : _window2.maxTouchPoints) > 2 && /iPad|Macintosh/.test((_window3 = window) === null || _window3 === void 0 ? void 0 : _window3.navigator.userAgent));\n}\n\n//#endregion\n//#region toRef/index.ts\nfunction toRef(...args) {\n\tif (args.length !== 1) return toRef$1(...args);\n\tconst r = args[0];\n\treturn typeof r === \"function\" ? readonly(customRef(() => ({\n\t\tget: r,\n\t\tset: noop\n\t}))) : ref(r);\n}\n\n//#endregion\n//#region utils/filters.ts\n/**\n* @internal\n*/\nfunction createFilterWrapper(filter, fn) {\n\tfunction wrapper(...args) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tPromise.resolve(filter(() => fn.apply(this, args), {\n\t\t\t\tfn,\n\t\t\t\tthisArg: this,\n\t\t\t\targs\n\t\t\t})).then(resolve).catch(reject);\n\t\t});\n\t}\n\treturn wrapper;\n}\nconst bypassFilter = (invoke$1) => {\n\treturn invoke$1();\n};\n/**\n* Create an EventFilter that debounce the events\n*/\nfunction debounceFilter(ms, options = {}) {\n\tlet timer;\n\tlet maxTimer;\n\tlet lastRejector = noop;\n\tconst _clearTimeout = (timer$1) => {\n\t\tclearTimeout(timer$1);\n\t\tlastRejector();\n\t\tlastRejector = noop;\n\t};\n\tlet lastInvoker;\n\tconst filter = (invoke$1) => {\n\t\tconst duration = toValue(ms);\n\t\tconst maxDuration = toValue(options.maxWait);\n\t\tif (timer) _clearTimeout(timer);\n\t\tif (duration <= 0 || maxDuration !== void 0 && maxDuration <= 0) {\n\t\t\tif (maxTimer) {\n\t\t\t\t_clearTimeout(maxTimer);\n\t\t\t\tmaxTimer = void 0;\n\t\t\t}\n\t\t\treturn Promise.resolve(invoke$1());\n\t\t}\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlastRejector = options.rejectOnCancel ? reject : resolve;\n\t\t\tlastInvoker = invoke$1;\n\t\t\tif (maxDuration && !maxTimer) maxTimer = setTimeout(() => {\n\t\t\t\tif (timer) _clearTimeout(timer);\n\t\t\t\tmaxTimer = void 0;\n\t\t\t\tresolve(lastInvoker());\n\t\t\t}, maxDuration);\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tif (maxTimer) _clearTimeout(maxTimer);\n\t\t\t\tmaxTimer = void 0;\n\t\t\t\tresolve(invoke$1());\n\t\t\t}, duration);\n\t\t});\n\t};\n\treturn filter;\n}\nfunction throttleFilter(...args) {\n\tlet lastExec = 0;\n\tlet timer;\n\tlet isLeading = true;\n\tlet lastRejector = noop;\n\tlet lastValue;\n\tlet ms;\n\tlet trailing;\n\tlet leading;\n\tlet rejectOnCancel;\n\tif (!isRef(args[0]) && typeof args[0] === \"object\") ({delay: ms, trailing = true, leading = true, rejectOnCancel = false} = args[0]);\n\telse [ms, trailing = true, leading = true, rejectOnCancel = false] = args;\n\tconst clear = () => {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = void 0;\n\t\t\tlastRejector();\n\t\t\tlastRejector = noop;\n\t\t}\n\t};\n\tconst filter = (_invoke) => {\n\t\tconst duration = toValue(ms);\n\t\tconst elapsed = Date.now() - lastExec;\n\t\tconst invoke$1 = () => {\n\t\t\treturn lastValue = _invoke();\n\t\t};\n\t\tclear();\n\t\tif (duration <= 0) {\n\t\t\tlastExec = Date.now();\n\t\t\treturn invoke$1();\n\t\t}\n\t\tif (elapsed > duration) {\n\t\t\tlastExec = Date.now();\n\t\t\tif (leading || !isLeading) invoke$1();\n\t\t} else if (trailing) lastValue = new Promise((resolve, reject) => {\n\t\t\tlastRejector = rejectOnCancel ? reject : resolve;\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tlastExec = Date.now();\n\t\t\t\tisLeading = true;\n\t\t\t\tresolve(invoke$1());\n\t\t\t\tclear();\n\t\t\t}, Math.max(0, duration - elapsed));\n\t\t});\n\t\tif (!leading && !timer) timer = setTimeout(() => isLeading = true, duration);\n\t\tisLeading = false;\n\t\treturn lastValue;\n\t};\n\treturn filter;\n}\n/**\n* EventFilter that gives extra controls to pause and resume the filter\n*\n* @param extendFilter Extra filter to apply when the PausableFilter is active, default to none\n* @param options Options to configure the filter\n*/\nfunction pausableFilter(extendFilter = bypassFilter, options = {}) {\n\tconst { initialState = \"active\" } = options;\n\tconst isActive = toRef(initialState === \"active\");\n\tfunction pause() {\n\t\tisActive.value = false;\n\t}\n\tfunction resume() {\n\t\tisActive.value = true;\n\t}\n\tconst eventFilter = (...args) => {\n\t\tif (isActive.value) extendFilter(...args);\n\t};\n\treturn {\n\t\tisActive: readonly(isActive),\n\t\tpause,\n\t\tresume,\n\t\teventFilter\n\t};\n}\n\n//#endregion\n//#region utils/general.ts\nfunction promiseTimeout(ms, throwOnTimeout = false, reason = \"Timeout\") {\n\treturn new Promise((resolve, reject) => {\n\t\tif (throwOnTimeout) setTimeout(() => reject(reason), ms);\n\t\telse setTimeout(resolve, ms);\n\t});\n}\nfunction identity(arg) {\n\treturn arg;\n}\n/**\n* Create singleton promise function\n*\n* @example\n* ```\n* const promise = createSingletonPromise(async () => { ... })\n*\n* await promise()\n* await promise() // all of them will be bind to a single promise instance\n* await promise() // and be resolved together\n* ```\n*/\nfunction createSingletonPromise(fn) {\n\tlet _promise;\n\tfunction wrapper() {\n\t\tif (!_promise) _promise = fn();\n\t\treturn _promise;\n\t}\n\twrapper.reset = async () => {\n\t\tconst _prev = _promise;\n\t\t_promise = void 0;\n\t\tif (_prev) await _prev;\n\t};\n\treturn wrapper;\n}\nfunction invoke(fn) {\n\treturn fn();\n}\nfunction containsProp(obj, ...props) {\n\treturn props.some((k) => k in obj);\n}\nfunction increaseWithUnit(target, delta) {\n\tvar _target$match;\n\tif (typeof target === \"number\") return target + delta;\n\tconst value = ((_target$match = target.match(/^-?\\d+\\.?\\d*/)) === null || _target$match === void 0 ? void 0 : _target$match[0]) || \"\";\n\tconst unit = target.slice(value.length);\n\tconst result = Number.parseFloat(value) + delta;\n\tif (Number.isNaN(result)) return target;\n\treturn result + unit;\n}\n/**\n* Get a px value for SSR use, do not rely on this method outside of SSR as REM unit is assumed at 16px, which might not be the case on the client\n*/\nfunction pxValue(px) {\n\treturn px.endsWith(\"rem\") ? Number.parseFloat(px) * 16 : Number.parseFloat(px);\n}\n/**\n* Create a new subset object by giving keys\n*/\nfunction objectPick(obj, keys, omitUndefined = false) {\n\treturn keys.reduce((n, k) => {\n\t\tif (k in obj) {\n\t\t\tif (!omitUndefined || obj[k] !== void 0) n[k] = obj[k];\n\t\t}\n\t\treturn n;\n\t}, {});\n}\n/**\n* Create a new subset object by omit giving keys\n*/\nfunction objectOmit(obj, keys, omitUndefined = false) {\n\treturn Object.fromEntries(Object.entries(obj).filter(([key, value]) => {\n\t\treturn (!omitUndefined || value !== void 0) && !keys.includes(key);\n\t}));\n}\nfunction objectEntries(obj) {\n\treturn Object.entries(obj);\n}\nfunction toArray(value) {\n\treturn Array.isArray(value) ? value : [value];\n}\n\n//#endregion\n//#region utils/port.ts\nfunction cacheStringFunction(fn) {\n\tconst cache = Object.create(null);\n\treturn ((str) => {\n\t\treturn cache[str] || (cache[str] = fn(str));\n\t});\n}\nconst hyphenateRE = /\\B([A-Z])/g;\nconst hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, \"-$1\").toLowerCase());\nconst camelizeRE = /-(\\w)/g;\nconst camelize = cacheStringFunction((str) => {\n\treturn str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : \"\");\n});\n\n//#endregion\n//#region utils/vue.ts\nfunction getLifeCycleTarget(target) {\n\treturn target || getCurrentInstance();\n}\n\n//#endregion\n//#region createSharedComposable/index.ts\n/**\n* Make a composable function usable with multiple Vue instances.\n*\n* @see https://vueuse.org/createSharedComposable\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction createSharedComposable(composable) {\n\tif (!isClient) return composable;\n\tlet subscribers = 0;\n\tlet state;\n\tlet scope;\n\tconst dispose = () => {\n\t\tsubscribers -= 1;\n\t\tif (scope && subscribers <= 0) {\n\t\t\tscope.stop();\n\t\t\tstate = void 0;\n\t\t\tscope = void 0;\n\t\t}\n\t};\n\treturn ((...args) => {\n\t\tsubscribers += 1;\n\t\tif (!scope) {\n\t\t\tscope = effectScope(true);\n\t\t\tstate = scope.run(() => composable(...args));\n\t\t}\n\t\ttryOnScopeDispose(dispose);\n\t\treturn state;\n\t});\n}\n\n//#endregion\n//#region extendRef/index.ts\nfunction extendRef(ref$1, extend, { enumerable = false, unwrap = true } = {}) {\n\tfor (const [key, value] of Object.entries(extend)) {\n\t\tif (key === \"value\") continue;\n\t\tif (isRef(value) && unwrap) Object.defineProperty(ref$1, key, {\n\t\t\tget() {\n\t\t\t\treturn value.value;\n\t\t\t},\n\t\t\tset(v) {\n\t\t\t\tvalue.value = v;\n\t\t\t},\n\t\t\tenumerable\n\t\t});\n\t\telse Object.defineProperty(ref$1, key, {\n\t\t\tvalue,\n\t\t\tenumerable\n\t\t});\n\t}\n\treturn ref$1;\n}\n\n//#endregion\n//#region get/index.ts\nfunction get(obj, key) {\n\tif (key == null) return unref(obj);\n\treturn unref(obj)[key];\n}\n\n//#endregion\n//#region isDefined/index.ts\nfunction isDefined(v) {\n\treturn unref(v) != null;\n}\n\n//#endregion\n//#region makeDestructurable/index.ts\n/* @__NO_SIDE_EFFECTS__ */\nfunction makeDestructurable(obj, arr) {\n\tif (typeof Symbol !== \"undefined\") {\n\t\tconst clone = { ...obj };\n\t\tObject.defineProperty(clone, Symbol.iterator, {\n\t\t\tenumerable: false,\n\t\t\tvalue() {\n\t\t\t\tlet index = 0;\n\t\t\t\treturn { next: () => ({\n\t\t\t\t\tvalue: arr[index++],\n\t\t\t\t\tdone: index > arr.length\n\t\t\t\t}) };\n\t\t\t}\n\t\t});\n\t\treturn clone;\n\t} else return Object.assign([...arr], obj);\n}\n\n//#endregion\n//#region reactify/index.ts\n/**\n* Converts plain function into a reactive function.\n* The converted function accepts refs as it's arguments\n* and returns a ComputedRef, with proper typing.\n*\n* @param fn - Source function\n* @param options - Options\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction reactify(fn, options) {\n\tconst unrefFn = (options === null || options === void 0 ? void 0 : options.computedGetter) === false ? unref : toValue;\n\treturn function(...args) {\n\t\treturn computed(() => fn.apply(this, args.map((i) => unrefFn(i))));\n\t};\n}\n/** @deprecated use `reactify` instead */\nconst createReactiveFn = reactify;\n\n//#endregion\n//#region reactifyObject/index.ts\n/**\n* Apply `reactify` to an object\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction reactifyObject(obj, optionsOrKeys = {}) {\n\tlet keys = [];\n\tlet options;\n\tif (Array.isArray(optionsOrKeys)) keys = optionsOrKeys;\n\telse {\n\t\toptions = optionsOrKeys;\n\t\tconst { includeOwnProperties = true } = optionsOrKeys;\n\t\tkeys.push(...Object.keys(obj));\n\t\tif (includeOwnProperties) keys.push(...Object.getOwnPropertyNames(obj));\n\t}\n\treturn Object.fromEntries(keys.map((key) => {\n\t\tconst value = obj[key];\n\t\treturn [key, typeof value === \"function\" ? reactify(value.bind(obj), options) : value];\n\t}));\n}\n\n//#endregion\n//#region toReactive/index.ts\n/**\n* Converts ref to reactive.\n*\n* @see https://vueuse.org/toReactive\n* @param objectRef A ref of object\n*/\nfunction toReactive(objectRef) {\n\tif (!isRef(objectRef)) return reactive(objectRef);\n\treturn reactive(new Proxy({}, {\n\t\tget(_, p, receiver) {\n\t\t\treturn unref(Reflect.get(objectRef.value, p, receiver));\n\t\t},\n\t\tset(_, p, value) {\n\t\t\tif (isRef(objectRef.value[p]) && !isRef(value)) objectRef.value[p].value = value;\n\t\t\telse objectRef.value[p] = value;\n\t\t\treturn true;\n\t\t},\n\t\tdeleteProperty(_, p) {\n\t\t\treturn Reflect.deleteProperty(objectRef.value, p);\n\t\t},\n\t\thas(_, p) {\n\t\t\treturn Reflect.has(objectRef.value, p);\n\t\t},\n\t\townKeys() {\n\t\t\treturn Object.keys(objectRef.value);\n\t\t},\n\t\tgetOwnPropertyDescriptor() {\n\t\t\treturn {\n\t\t\t\tenumerable: true,\n\t\t\t\tconfigurable: true\n\t\t\t};\n\t\t}\n\t}));\n}\n\n//#endregion\n//#region reactiveComputed/index.ts\n/**\n* Computed reactive object.\n*/\nfunction reactiveComputed(fn) {\n\treturn toReactive(computed(fn));\n}\n\n//#endregion\n//#region reactiveOmit/index.ts\n/**\n* Reactively omit fields from a reactive object\n*\n* @see https://vueuse.org/reactiveOmit\n*/\nfunction reactiveOmit(obj, ...keys) {\n\tconst flatKeys = keys.flat();\n\tconst predicate = flatKeys[0];\n\treturn reactiveComputed(() => typeof predicate === \"function\" ? Object.fromEntries(Object.entries(toRefs$1(obj)).filter(([k, v]) => !predicate(toValue(v), k))) : Object.fromEntries(Object.entries(toRefs$1(obj)).filter((e) => !flatKeys.includes(e[0]))));\n}\n\n//#endregion\n//#region reactivePick/index.ts\n/**\n* Reactively pick fields from a reactive object\n*\n* @see https://vueuse.org/reactivePick\n*/\nfunction reactivePick(obj, ...keys) {\n\tconst flatKeys = keys.flat();\n\tconst predicate = flatKeys[0];\n\treturn reactiveComputed(() => typeof predicate === \"function\" ? Object.fromEntries(Object.entries(toRefs$1(obj)).filter(([k, v]) => predicate(toValue(v), k))) : Object.fromEntries(flatKeys.map((k) => [k, toRef(obj, k)])));\n}\n\n//#endregion\n//#region refAutoReset/index.ts\n/**\n* Create a ref which will be reset to the default value after some time.\n*\n* @see https://vueuse.org/refAutoReset\n* @param defaultValue The value which will be set.\n* @param afterMs A zero-or-greater delay in milliseconds.\n*/\nfunction refAutoReset(defaultValue, afterMs = 1e4) {\n\treturn customRef((track, trigger) => {\n\t\tlet value = toValue(defaultValue);\n\t\tlet timer;\n\t\tconst resetAfter = () => setTimeout(() => {\n\t\t\tvalue = toValue(defaultValue);\n\t\t\ttrigger();\n\t\t}, toValue(afterMs));\n\t\ttryOnScopeDispose(() => {\n\t\t\tclearTimeout(timer);\n\t\t});\n\t\treturn {\n\t\t\tget() {\n\t\t\t\ttrack();\n\t\t\t\treturn value;\n\t\t\t},\n\t\t\tset(newValue) {\n\t\t\t\tvalue = newValue;\n\t\t\t\ttrigger();\n\t\t\t\tclearTimeout(timer);\n\t\t\t\ttimer = resetAfter();\n\t\t\t}\n\t\t};\n\t});\n}\n/** @deprecated use `refAutoReset` instead */\nconst autoResetRef = refAutoReset;\n\n//#endregion\n//#region useDebounceFn/index.ts\n/**\n* Debounce execution of a function.\n*\n* @see https://vueuse.org/useDebounceFn\n* @param fn A function to be executed after delay milliseconds debounced.\n* @param ms A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.\n* @param options Options\n*\n* @return A new, debounce, function.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useDebounceFn(fn, ms = 200, options = {}) {\n\treturn createFilterWrapper(debounceFilter(ms, options), fn);\n}\n\n//#endregion\n//#region refDebounced/index.ts\n/**\n* Debounce updates of a ref.\n*\n* @return A new debounced ref.\n*/\nfunction refDebounced(value, ms = 200, options = {}) {\n\tconst debounced = ref(toValue(value));\n\tconst updater = useDebounceFn(() => {\n\t\tdebounced.value = value.value;\n\t}, ms, options);\n\twatch(value, () => updater());\n\treturn shallowReadonly(debounced);\n}\n/** @deprecated use `refDebounced` instead */\nconst debouncedRef = refDebounced;\n/** @deprecated use `refDebounced` instead */\nconst useDebounce = refDebounced;\n\n//#endregion\n//#region refDefault/index.ts\n/**\n* Apply default value to a ref.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction refDefault(source, defaultValue) {\n\treturn computed({\n\t\tget() {\n\t\t\tvar _source$value;\n\t\t\treturn (_source$value = source.value) !== null && _source$value !== void 0 ? _source$value : defaultValue;\n\t\t},\n\t\tset(value) {\n\t\t\tsource.value = value;\n\t\t}\n\t});\n}\n\n//#endregion\n//#region refManualReset/index.ts\n/**\n* Create a ref with manual reset functionality.\n*\n* @see https://vueuse.org/refManualReset\n* @param defaultValue The value which will be set.\n*/\nfunction refManualReset(defaultValue) {\n\tlet value = toValue(defaultValue);\n\tlet trigger;\n\tconst reset = () => {\n\t\tvalue = toValue(defaultValue);\n\t\ttrigger();\n\t};\n\tconst refValue = customRef((track, _trigger) => {\n\t\ttrigger = _trigger;\n\t\treturn {\n\t\t\tget() {\n\t\t\t\ttrack();\n\t\t\t\treturn value;\n\t\t\t},\n\t\t\tset(newValue) {\n\t\t\t\tvalue = newValue;\n\t\t\t\ttrigger();\n\t\t\t}\n\t\t};\n\t});\n\trefValue.reset = reset;\n\treturn refValue;\n}\n\n//#endregion\n//#region useThrottleFn/index.ts\n/**\n* Throttle execution of a function. Especially useful for rate limiting\n* execution of handlers on events like resize and scroll.\n*\n* @param fn A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is,\n* to `callback` when the throttled-function is executed.\n* @param ms A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.\n* (default value: 200)\n*\n* @param [trailing] if true, call fn again after the time is up (default value: false)\n*\n* @param [leading] if true, call fn on the leading edge of the ms timeout (default value: true)\n*\n* @param [rejectOnCancel] if true, reject the last call if it's been cancel (default value: false)\n*\n* @return A new, throttled, function.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useThrottleFn(fn, ms = 200, trailing = false, leading = true, rejectOnCancel = false) {\n\treturn createFilterWrapper(throttleFilter(ms, trailing, leading, rejectOnCancel), fn);\n}\n\n//#endregion\n//#region refThrottled/index.ts\n/**\n* Throttle execution of a function. Especially useful for rate limiting\n* execution of handlers on events like resize and scroll.\n*\n* @param value Ref value to be watched with throttle effect\n* @param delay A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.\n* @param trailing if true, update the value again after the delay time is up\n* @param leading if true, update the value on the leading edge of the ms timeout\n*/\nfunction refThrottled(value, delay = 200, trailing = true, leading = true) {\n\tif (delay <= 0) return value;\n\tconst throttled = ref(toValue(value));\n\tconst updater = useThrottleFn(() => {\n\t\tthrottled.value = value.value;\n\t}, delay, trailing, leading);\n\twatch(value, () => updater());\n\treturn throttled;\n}\n/** @deprecated use `refThrottled` instead */\nconst throttledRef = refThrottled;\n/** @deprecated use `refThrottled` instead */\nconst useThrottle = refThrottled;\n\n//#endregion\n//#region refWithControl/index.ts\n/**\n* Fine-grained controls over ref and its reactivity.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction refWithControl(initial, options = {}) {\n\tlet source = initial;\n\tlet track;\n\tlet trigger;\n\tconst ref$1 = customRef((_track, _trigger) => {\n\t\ttrack = _track;\n\t\ttrigger = _trigger;\n\t\treturn {\n\t\t\tget() {\n\t\t\t\treturn get$1();\n\t\t\t},\n\t\t\tset(v) {\n\t\t\t\tset$1(v);\n\t\t\t}\n\t\t};\n\t});\n\tfunction get$1(tracking = true) {\n\t\tif (tracking) track();\n\t\treturn source;\n\t}\n\tfunction set$1(value, triggering = true) {\n\t\tvar _options$onBeforeChan, _options$onChanged;\n\t\tif (value === source) return;\n\t\tconst old = source;\n\t\tif (((_options$onBeforeChan = options.onBeforeChange) === null || _options$onBeforeChan === void 0 ? void 0 : _options$onBeforeChan.call(options, value, old)) === false) return;\n\t\tsource = value;\n\t\t(_options$onChanged = options.onChanged) === null || _options$onChanged === void 0 || _options$onChanged.call(options, value, old);\n\t\tif (triggering) trigger();\n\t}\n\t/**\n\t* Get the value without tracked in the reactivity system\n\t*/\n\tconst untrackedGet = () => get$1(false);\n\t/**\n\t* Set the value without triggering the reactivity system\n\t*/\n\tconst silentSet = (v) => set$1(v, false);\n\t/**\n\t* Get the value without tracked in the reactivity system.\n\t*\n\t* Alias for `untrackedGet()`\n\t*/\n\tconst peek = () => get$1(false);\n\t/**\n\t* Set the value without triggering the reactivity system\n\t*\n\t* Alias for `silentSet(v)`\n\t*/\n\tconst lay = (v) => set$1(v, false);\n\treturn extendRef(ref$1, {\n\t\tget: get$1,\n\t\tset: set$1,\n\t\tuntrackedGet,\n\t\tsilentSet,\n\t\tpeek,\n\t\tlay\n\t}, { enumerable: true });\n}\n/** @deprecated use `refWithControl` instead */\nconst controlledRef = refWithControl;\n\n//#endregion\n//#region set/index.ts\n/**\n* Shorthand for `ref.value = x`\n*/\nfunction set(...args) {\n\tif (args.length === 2) {\n\t\tconst [ref$1, value] = args;\n\t\tref$1.value = value;\n\t}\n\tif (args.length === 3) {\n\t\tconst [target, key, value] = args;\n\t\ttarget[key] = value;\n\t}\n}\n\n//#endregion\n//#region watchWithFilter/index.ts\nfunction watchWithFilter(source, cb, options = {}) {\n\tconst { eventFilter = bypassFilter,...watchOptions } = options;\n\treturn watch(source, createFilterWrapper(eventFilter, cb), watchOptions);\n}\n\n//#endregion\n//#region watchPausable/index.ts\n/** @deprecated Use Vue's built-in `watch` instead. This function will be removed in future version. */\nfunction watchPausable(source, cb, options = {}) {\n\tconst { eventFilter: filter, initialState = \"active\",...watchOptions } = options;\n\tconst { eventFilter, pause, resume, isActive } = pausableFilter(filter, { initialState });\n\treturn {\n\t\tstop: watchWithFilter(source, cb, {\n\t\t\t...watchOptions,\n\t\t\teventFilter\n\t\t}),\n\t\tpause,\n\t\tresume,\n\t\tisActive\n\t};\n}\n/** @deprecated Use Vue's built-in `watch` instead. This function will be removed in future version. */\nconst pausableWatch = watchPausable;\n\n//#endregion\n//#region syncRef/index.ts\n/**\n* Two-way refs synchronization.\n* From the set theory perspective to restrict the option's type\n* Check in the following order:\n* 1. L = R\n* 2. L ∩ R ≠ ∅\n* 3. L ⊆ R\n* 4. L ∩ R = ∅\n*/\nfunction syncRef(left, right, ...[options]) {\n\tconst { flush = \"sync\", deep = false, immediate = true, direction = \"both\", transform = {} } = options || {};\n\tconst watchers = [];\n\tconst transformLTR = \"ltr\" in transform && transform.ltr || ((v) => v);\n\tconst transformRTL = \"rtl\" in transform && transform.rtl || ((v) => v);\n\tif (direction === \"both\" || direction === \"ltr\") watchers.push(watchPausable(left, (newValue) => {\n\t\twatchers.forEach((w) => w.pause());\n\t\tright.value = transformLTR(newValue);\n\t\twatchers.forEach((w) => w.resume());\n\t}, {\n\t\tflush,\n\t\tdeep,\n\t\timmediate\n\t}));\n\tif (direction === \"both\" || direction === \"rtl\") watchers.push(watchPausable(right, (newValue) => {\n\t\twatchers.forEach((w) => w.pause());\n\t\tleft.value = transformRTL(newValue);\n\t\twatchers.forEach((w) => w.resume());\n\t}, {\n\t\tflush,\n\t\tdeep,\n\t\timmediate\n\t}));\n\tconst stop = () => {\n\t\twatchers.forEach((w) => w.stop());\n\t};\n\treturn stop;\n}\n\n//#endregion\n//#region syncRefs/index.ts\n/**\n* Keep target ref(s) in sync with the source ref\n*\n* @param source source ref\n* @param targets\n*/\nfunction syncRefs(source, targets, options = {}) {\n\tconst { flush = \"sync\", deep = false, immediate = true } = options;\n\tconst targetsArray = toArray(targets);\n\treturn watch(source, (newValue) => targetsArray.forEach((target) => target.value = newValue), {\n\t\tflush,\n\t\tdeep,\n\t\timmediate\n\t});\n}\n\n//#endregion\n//#region toRefs/index.ts\n/**\n* Extended `toRefs` that also accepts refs of an object.\n*\n* @see https://vueuse.org/toRefs\n* @param objectRef A ref or normal object or array.\n* @param options Options\n*/\nfunction toRefs(objectRef, options = {}) {\n\tif (!isRef(objectRef)) return toRefs$1(objectRef);\n\tconst result = Array.isArray(objectRef.value) ? Array.from({ length: objectRef.value.length }) : {};\n\tfor (const key in objectRef.value) result[key] = customRef(() => ({\n\t\tget() {\n\t\t\treturn objectRef.value[key];\n\t\t},\n\t\tset(v) {\n\t\t\tvar _toValue;\n\t\t\tif ((_toValue = toValue(options.replaceRef)) !== null && _toValue !== void 0 ? _toValue : true) if (Array.isArray(objectRef.value)) {\n\t\t\t\tconst copy = [...objectRef.value];\n\t\t\t\tcopy[key] = v;\n\t\t\t\tobjectRef.value = copy;\n\t\t\t} else {\n\t\t\t\tconst newObject = {\n\t\t\t\t\t...objectRef.value,\n\t\t\t\t\t[key]: v\n\t\t\t\t};\n\t\t\t\tObject.setPrototypeOf(newObject, Object.getPrototypeOf(objectRef.value));\n\t\t\t\tobjectRef.value = newObject;\n\t\t\t}\n\t\t\telse objectRef.value[key] = v;\n\t\t}\n\t}));\n\treturn result;\n}\n\n//#endregion\n//#region tryOnBeforeMount/index.ts\n/**\n* Call onBeforeMount() if it's inside a component lifecycle, if not, just call the function\n*\n* @param fn\n* @param sync if set to false, it will run in the nextTick() of Vue\n* @param target\n*/\nfunction tryOnBeforeMount(fn, sync = true, target) {\n\tif (getLifeCycleTarget(target)) onBeforeMount(fn, target);\n\telse if (sync) fn();\n\telse nextTick(fn);\n}\n\n//#endregion\n//#region tryOnBeforeUnmount/index.ts\n/**\n* Call onBeforeUnmount() if it's inside a component lifecycle, if not, do nothing\n*\n* @param fn\n* @param target\n*/\nfunction tryOnBeforeUnmount(fn, target) {\n\tif (getLifeCycleTarget(target)) onBeforeUnmount(fn, target);\n}\n\n//#endregion\n//#region tryOnMounted/index.ts\n/**\n* Call onMounted() if it's inside a component lifecycle, if not, just call the function\n*\n* @param fn\n* @param sync if set to false, it will run in the nextTick() of Vue\n* @param target\n*/\nfunction tryOnMounted(fn, sync = true, target) {\n\tif (getLifeCycleTarget(target)) onMounted(fn, target);\n\telse if (sync) fn();\n\telse nextTick(fn);\n}\n\n//#endregion\n//#region tryOnUnmounted/index.ts\n/**\n* Call onUnmounted() if it's inside a component lifecycle, if not, do nothing\n*\n* @param fn\n* @param target\n*/\nfunction tryOnUnmounted(fn, target) {\n\tif (getLifeCycleTarget(target)) onUnmounted(fn, target);\n}\n\n//#endregion\n//#region until/index.ts\nfunction createUntil(r, isNot = false) {\n\tfunction toMatch(condition, { flush = \"sync\", deep = false, timeout, throwOnTimeout } = {}) {\n\t\tlet stop = null;\n\t\tconst promises = [new Promise((resolve) => {\n\t\t\tstop = watch(r, (v) => {\n\t\t\t\tif (condition(v) !== isNot) {\n\t\t\t\t\tif (stop) stop();\n\t\t\t\t\telse nextTick(() => stop === null || stop === void 0 ? void 0 : stop());\n\t\t\t\t\tresolve(v);\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\tflush,\n\t\t\t\tdeep,\n\t\t\t\timmediate: true\n\t\t\t});\n\t\t})];\n\t\tif (timeout != null) promises.push(promiseTimeout(timeout, throwOnTimeout).then(() => toValue(r)).finally(() => stop === null || stop === void 0 ? void 0 : stop()));\n\t\treturn Promise.race(promises);\n\t}\n\tfunction toBe(value, options) {\n\t\tif (!isRef(value)) return toMatch((v) => v === value, options);\n\t\tconst { flush = \"sync\", deep = false, timeout, throwOnTimeout } = options !== null && options !== void 0 ? options : {};\n\t\tlet stop = null;\n\t\tconst promises = [new Promise((resolve) => {\n\t\t\tstop = watch([r, value], ([v1, v2]) => {\n\t\t\t\tif (isNot !== (v1 === v2)) {\n\t\t\t\t\tif (stop) stop();\n\t\t\t\t\telse nextTick(() => stop === null || stop === void 0 ? void 0 : stop());\n\t\t\t\t\tresolve(v1);\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\tflush,\n\t\t\t\tdeep,\n\t\t\t\timmediate: true\n\t\t\t});\n\t\t})];\n\t\tif (timeout != null) promises.push(promiseTimeout(timeout, throwOnTimeout).then(() => toValue(r)).finally(() => {\n\t\t\tstop === null || stop === void 0 || stop();\n\t\t\treturn toValue(r);\n\t\t}));\n\t\treturn Promise.race(promises);\n\t}\n\tfunction toBeTruthy(options) {\n\t\treturn toMatch((v) => Boolean(v), options);\n\t}\n\tfunction toBeNull(options) {\n\t\treturn toBe(null, options);\n\t}\n\tfunction toBeUndefined(options) {\n\t\treturn toBe(void 0, options);\n\t}\n\tfunction toBeNaN(options) {\n\t\treturn toMatch(Number.isNaN, options);\n\t}\n\tfunction toContains(value, options) {\n\t\treturn toMatch((v) => {\n\t\t\tconst array = Array.from(v);\n\t\t\treturn array.includes(value) || array.includes(toValue(value));\n\t\t}, options);\n\t}\n\tfunction changed(options) {\n\t\treturn changedTimes(1, options);\n\t}\n\tfunction changedTimes(n = 1, options) {\n\t\tlet count = -1;\n\t\treturn toMatch(() => {\n\t\t\tcount += 1;\n\t\t\treturn count >= n;\n\t\t}, options);\n\t}\n\tif (Array.isArray(toValue(r))) return {\n\t\ttoMatch,\n\t\ttoContains,\n\t\tchanged,\n\t\tchangedTimes,\n\t\tget not() {\n\t\t\treturn createUntil(r, !isNot);\n\t\t}\n\t};\n\telse return {\n\t\ttoMatch,\n\t\ttoBe,\n\t\ttoBeTruthy,\n\t\ttoBeNull,\n\t\ttoBeNaN,\n\t\ttoBeUndefined,\n\t\tchanged,\n\t\tchangedTimes,\n\t\tget not() {\n\t\t\treturn createUntil(r, !isNot);\n\t\t}\n\t};\n}\nfunction until(r) {\n\treturn createUntil(r);\n}\n\n//#endregion\n//#region useArrayDifference/index.ts\nfunction defaultComparator(value, othVal) {\n\treturn value === othVal;\n}\n/**\n* Reactive get array difference of two array\n* @see https://vueuse.org/useArrayDifference\n* @returns - the difference of two array\n* @param args\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayDifference(...args) {\n\tvar _args$, _args$2;\n\tconst list = args[0];\n\tconst values = args[1];\n\tlet compareFn = (_args$ = args[2]) !== null && _args$ !== void 0 ? _args$ : defaultComparator;\n\tconst { symmetric = false } = (_args$2 = args[3]) !== null && _args$2 !== void 0 ? _args$2 : {};\n\tif (typeof compareFn === \"string\") {\n\t\tconst key = compareFn;\n\t\tcompareFn = (value, othVal) => value[key] === othVal[key];\n\t}\n\tconst diff1 = computed(() => toValue(list).filter((x) => toValue(values).findIndex((y) => compareFn(x, y)) === -1));\n\tif (symmetric) {\n\t\tconst diff2 = computed(() => toValue(values).filter((x) => toValue(list).findIndex((y) => compareFn(x, y)) === -1));\n\t\treturn computed(() => symmetric ? [...toValue(diff1), ...toValue(diff2)] : toValue(diff1));\n\t} else return diff1;\n}\n\n//#endregion\n//#region useArrayEvery/index.ts\n/**\n* Reactive `Array.every`\n*\n* @see https://vueuse.org/useArrayEvery\n* @param list - the array was called upon.\n* @param fn - a function to test each element.\n*\n* @returns **true** if the `fn` function returns a **truthy** value for every element from the array. Otherwise, **false**.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayEvery(list, fn) {\n\treturn computed(() => toValue(list).every((element, index, array) => fn(toValue(element), index, array)));\n}\n\n//#endregion\n//#region useArrayFilter/index.ts\n/**\n* Reactive `Array.filter`\n*\n* @see https://vueuse.org/useArrayFilter\n* @param list - the array was called upon.\n* @param fn - a function that is called for every element of the given `list`. Each time `fn` executes, the returned value is added to the new array.\n*\n* @returns a shallow copy of a portion of the given array, filtered down to just the elements from the given array that pass the test implemented by the provided function. If no elements pass the test, an empty array will be returned.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayFilter(list, fn) {\n\treturn computed(() => toValue(list).map((i) => toValue(i)).filter(fn));\n}\n\n//#endregion\n//#region useArrayFind/index.ts\n/**\n* Reactive `Array.find`\n*\n* @see https://vueuse.org/useArrayFind\n* @param list - the array was called upon.\n* @param fn - a function to test each element.\n*\n* @returns the first element in the array that satisfies the provided testing function. Otherwise, undefined is returned.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayFind(list, fn) {\n\treturn computed(() => toValue(toValue(list).find((element, index, array) => fn(toValue(element), index, array))));\n}\n\n//#endregion\n//#region useArrayFindIndex/index.ts\n/**\n* Reactive `Array.findIndex`\n*\n* @see https://vueuse.org/useArrayFindIndex\n* @param list - the array was called upon.\n* @param fn - a function to test each element.\n*\n* @returns the index of the first element in the array that passes the test. Otherwise, \"-1\".\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayFindIndex(list, fn) {\n\treturn computed(() => toValue(list).findIndex((element, index, array) => fn(toValue(element), index, array)));\n}\n\n//#endregion\n//#region useArrayFindLast/index.ts\nfunction findLast(arr, cb) {\n\tlet index = arr.length;\n\twhile (index-- > 0) if (cb(arr[index], index, arr)) return arr[index];\n}\n/**\n* Reactive `Array.findLast`\n*\n* @see https://vueuse.org/useArrayFindLast\n* @param list - the array was called upon.\n* @param fn - a function to test each element.\n*\n* @returns the last element in the array that satisfies the provided testing function. Otherwise, undefined is returned.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayFindLast(list, fn) {\n\treturn computed(() => toValue(!Array.prototype.findLast ? findLast(toValue(list), (element, index, array) => fn(toValue(element), index, array)) : toValue(list).findLast((element, index, array) => fn(toValue(element), index, array))));\n}\n\n//#endregion\n//#region useArrayIncludes/index.ts\nfunction isArrayIncludesOptions(obj) {\n\treturn isObject(obj) && containsProp(obj, \"formIndex\", \"comparator\");\n}\n/**\n* Reactive `Array.includes`\n*\n* @see https://vueuse.org/useArrayIncludes\n*\n* @returns true if the `value` is found in the array. Otherwise, false.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayIncludes(...args) {\n\tvar _comparator;\n\tconst list = args[0];\n\tconst value = args[1];\n\tlet comparator = args[2];\n\tlet formIndex = 0;\n\tif (isArrayIncludesOptions(comparator)) {\n\t\tvar _comparator$fromIndex;\n\t\tformIndex = (_comparator$fromIndex = comparator.fromIndex) !== null && _comparator$fromIndex !== void 0 ? _comparator$fromIndex : 0;\n\t\tcomparator = comparator.comparator;\n\t}\n\tif (typeof comparator === \"string\") {\n\t\tconst key = comparator;\n\t\tcomparator = (element, value$1) => element[key] === toValue(value$1);\n\t}\n\tcomparator = (_comparator = comparator) !== null && _comparator !== void 0 ? _comparator : ((element, value$1) => element === toValue(value$1));\n\treturn computed(() => toValue(list).slice(formIndex).some((element, index, array) => comparator(toValue(element), toValue(value), index, toValue(array))));\n}\n\n//#endregion\n//#region useArrayJoin/index.ts\n/**\n* Reactive `Array.join`\n*\n* @see https://vueuse.org/useArrayJoin\n* @param list - the array was called upon.\n* @param separator - a string to separate each pair of adjacent elements of the array. If omitted, the array elements are separated with a comma (\",\").\n*\n* @returns a string with all array elements joined. If arr.length is 0, the empty string is returned.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayJoin(list, separator) {\n\treturn computed(() => toValue(list).map((i) => toValue(i)).join(toValue(separator)));\n}\n\n//#endregion\n//#region useArrayMap/index.ts\n/**\n* Reactive `Array.map`\n*\n* @see https://vueuse.org/useArrayMap\n* @param list - the array was called upon.\n* @param fn - a function that is called for every element of the given `list`. Each time `fn` executes, the returned value is added to the new array.\n*\n* @returns a new array with each element being the result of the callback function.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayMap(list, fn) {\n\treturn computed(() => toValue(list).map((i) => toValue(i)).map(fn));\n}\n\n//#endregion\n//#region useArrayReduce/index.ts\n/**\n* Reactive `Array.reduce`\n*\n* @see https://vueuse.org/useArrayReduce\n* @param list - the array was called upon.\n* @param reducer - a \"reducer\" function.\n* @param args\n*\n* @returns the value that results from running the \"reducer\" callback function to completion over the entire array.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayReduce(list, reducer, ...args) {\n\tconst reduceCallback = (sum, value, index) => reducer(toValue(sum), toValue(value), index);\n\treturn computed(() => {\n\t\tconst resolved = toValue(list);\n\t\treturn args.length ? resolved.reduce(reduceCallback, typeof args[0] === \"function\" ? toValue(args[0]()) : toValue(args[0])) : resolved.reduce(reduceCallback);\n\t});\n}\n\n//#endregion\n//#region useArraySome/index.ts\n/**\n* Reactive `Array.some`\n*\n* @see https://vueuse.org/useArraySome\n* @param list - the array was called upon.\n* @param fn - a function to test each element.\n*\n* @returns **true** if the `fn` function returns a **truthy** value for any element from the array. Otherwise, **false**.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArraySome(list, fn) {\n\treturn computed(() => toValue(list).some((element, index, array) => fn(toValue(element), index, array)));\n}\n\n//#endregion\n//#region useArrayUnique/index.ts\nfunction uniq(array) {\n\treturn Array.from(new Set(array));\n}\nfunction uniqueElementsBy(array, fn) {\n\treturn array.reduce((acc, v) => {\n\t\tif (!acc.some((x) => fn(v, x, array))) acc.push(v);\n\t\treturn acc;\n\t}, []);\n}\n/**\n* reactive unique array\n* @see https://vueuse.org/useArrayUnique\n* @param list - the array was called upon.\n* @param compareFn\n* @returns A computed ref that returns a unique array of items.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayUnique(list, compareFn) {\n\treturn computed(() => {\n\t\tconst resolvedList = toValue(list).map((element) => toValue(element));\n\t\treturn compareFn ? uniqueElementsBy(resolvedList, compareFn) : uniq(resolvedList);\n\t});\n}\n\n//#endregion\n//#region useCounter/index.ts\n/**\n* Basic counter with utility functions.\n*\n* @see https://vueuse.org/useCounter\n* @param [initialValue]\n* @param options\n*/\nfunction useCounter(initialValue = 0, options = {}) {\n\tlet _initialValue = unref(initialValue);\n\tconst count = shallowRef(initialValue);\n\tconst { max = Number.POSITIVE_INFINITY, min = Number.NEGATIVE_INFINITY } = options;\n\tconst inc = (delta = 1) => count.value = Math.max(Math.min(max, count.value + delta), min);\n\tconst dec = (delta = 1) => count.value = Math.min(Math.max(min, count.value - delta), max);\n\tconst get$1 = () => count.value;\n\tconst set$1 = (val) => count.value = Math.max(min, Math.min(max, val));\n\tconst reset = (val = _initialValue) => {\n\t\t_initialValue = val;\n\t\treturn set$1(val);\n\t};\n\treturn {\n\t\tcount: shallowReadonly(count),\n\t\tinc,\n\t\tdec,\n\t\tget: get$1,\n\t\tset: set$1,\n\t\treset\n\t};\n}\n\n//#endregion\n//#region useDateFormat/index.ts\nconst REGEX_PARSE = /^(\\d{4})[-/]?(\\d{1,2})?[-/]?(\\d{0,2})[T\\s]*(\\d{1,2})?:?(\\d{1,2})?:?(\\d{1,2})?[.:]?(\\d+)?$/i;\nconst REGEX_FORMAT = /[YMDHhms]o|\\[([^\\]]+)\\]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a{1,2}|A{1,2}|m{1,2}|s{1,2}|Z{1,2}|z{1,4}|SSS/g;\nfunction defaultMeridiem(hours, minutes, isLowercase, hasPeriod) {\n\tlet m = hours < 12 ? \"AM\" : \"PM\";\n\tif (hasPeriod) m = m.split(\"\").reduce((acc, curr) => acc += `${curr}.`, \"\");\n\treturn isLowercase ? m.toLowerCase() : m;\n}\nfunction formatOrdinal(num) {\n\tconst suffixes = [\n\t\t\"th\",\n\t\t\"st\",\n\t\t\"nd\",\n\t\t\"rd\"\n\t];\n\tconst v = num % 100;\n\treturn num + (suffixes[(v - 20) % 10] || suffixes[v] || suffixes[0]);\n}\nfunction formatDate(date, formatStr, options = {}) {\n\tvar _options$customMeridi;\n\tconst years = date.getFullYear();\n\tconst month = date.getMonth();\n\tconst days = date.getDate();\n\tconst hours = date.getHours();\n\tconst minutes = date.getMinutes();\n\tconst seconds = date.getSeconds();\n\tconst milliseconds = date.getMilliseconds();\n\tconst day = date.getDay();\n\tconst meridiem = (_options$customMeridi = options.customMeridiem) !== null && _options$customMeridi !== void 0 ? _options$customMeridi : defaultMeridiem;\n\tconst stripTimeZone = (dateString) => {\n\t\tvar _dateString$split$;\n\t\treturn (_dateString$split$ = dateString.split(\" \")[1]) !== null && _dateString$split$ !== void 0 ? _dateString$split$ : \"\";\n\t};\n\tconst matches = {\n\t\tYo: () => formatOrdinal(years),\n\t\tYY: () => String(years).slice(-2),\n\t\tYYYY: () => years,\n\t\tM: () => month + 1,\n\t\tMo: () => formatOrdinal(month + 1),\n\t\tMM: () => `${month + 1}`.padStart(2, \"0\"),\n\t\tMMM: () => date.toLocaleDateString(toValue(options.locales), { month: \"short\" }),\n\t\tMMMM: () => date.toLocaleDateString(toValue(options.locales), { month: \"long\" }),\n\t\tD: () => String(days),\n\t\tDo: () => formatOrdinal(days),\n\t\tDD: () => `${days}`.padStart(2, \"0\"),\n\t\tH: () => String(hours),\n\t\tHo: () => formatOrdinal(hours),\n\t\tHH: () => `${hours}`.padStart(2, \"0\"),\n\t\th: () => `${hours % 12 || 12}`.padStart(1, \"0\"),\n\t\tho: () => formatOrdinal(hours % 12 || 12),\n\t\thh: () => `${hours % 12 || 12}`.padStart(2, \"0\"),\n\t\tm: () => String(minutes),\n\t\tmo: () => formatOrdinal(minutes),\n\t\tmm: () => `${minutes}`.padStart(2, \"0\"),\n\t\ts: () => String(seconds),\n\t\tso: () => formatOrdinal(seconds),\n\t\tss: () => `${seconds}`.padStart(2, \"0\"),\n\t\tSSS: () => `${milliseconds}`.padStart(3, \"0\"),\n\t\td: () => day,\n\t\tdd: () => date.toLocaleDateString(toValue(options.locales), { weekday: \"narrow\" }),\n\t\tddd: () => date.toLocaleDateString(toValue(options.locales), { weekday: \"short\" }),\n\t\tdddd: () => date.toLocaleDateString(toValue(options.locales), { weekday: \"long\" }),\n\t\tA: () => meridiem(hours, minutes),\n\t\tAA: () => meridiem(hours, minutes, false, true),\n\t\ta: () => meridiem(hours, minutes, true),\n\t\taa: () => meridiem(hours, minutes, true, true),\n\t\tz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: \"shortOffset\" })),\n\t\tzz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: \"shortOffset\" })),\n\t\tzzz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: \"shortOffset\" })),\n\t\tzzzz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: \"longOffset\" }))\n\t};\n\treturn formatStr.replace(REGEX_FORMAT, (match, $1) => {\n\t\tvar _ref, _matches$match;\n\t\treturn (_ref = $1 !== null && $1 !== void 0 ? $1 : (_matches$match = matches[match]) === null || _matches$match === void 0 ? void 0 : _matches$match.call(matches)) !== null && _ref !== void 0 ? _ref : match;\n\t});\n}\nfunction normalizeDate(date) {\n\tif (date === null) return /* @__PURE__ */ new Date(NaN);\n\tif (date === void 0) return /* @__PURE__ */ new Date();\n\tif (date instanceof Date) return new Date(date);\n\tif (typeof date === \"string\" && !/Z$/i.test(date)) {\n\t\tconst d = date.match(REGEX_PARSE);\n\t\tif (d) {\n\t\t\tconst m = d[2] - 1 || 0;\n\t\t\tconst ms = (d[7] || \"0\").substring(0, 3);\n\t\t\treturn new Date(d[1], m, d[3] || 1, d[4] || 0, d[5] || 0, d[6] || 0, ms);\n\t\t}\n\t}\n\treturn new Date(date);\n}\n/**\n* Get the formatted date according to the string of tokens passed in.\n*\n* @see https://vueuse.org/useDateFormat\n* @param date - The date to format, can either be a `Date` object, a timestamp, or a string\n* @param formatStr - The combination of tokens to format the date\n* @param options - UseDateFormatOptions\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useDateFormat(date, formatStr = \"HH:mm:ss\", options = {}) {\n\treturn computed(() => formatDate(normalizeDate(toValue(date)), toValue(formatStr), options));\n}\n\n//#endregion\n//#region useIntervalFn/index.ts\n/**\n* Wrapper for `setInterval` with controls\n*\n* @see https://vueuse.org/useIntervalFn\n* @param cb\n* @param interval\n* @param options\n*/\nfunction useIntervalFn(cb, interval = 1e3, options = {}) {\n\tconst { immediate = true, immediateCallback = false } = options;\n\tlet timer = null;\n\tconst isActive = shallowRef(false);\n\tfunction clean() {\n\t\tif (timer) {\n\t\t\tclearInterval(timer);\n\t\t\ttimer = null;\n\t\t}\n\t}\n\tfunction pause() {\n\t\tisActive.value = false;\n\t\tclean();\n\t}\n\tfunction resume() {\n\t\tconst intervalValue = toValue(interval);\n\t\tif (intervalValue <= 0) return;\n\t\tisActive.value = true;\n\t\tif (immediateCallback) cb();\n\t\tclean();\n\t\tif (isActive.value) timer = setInterval(cb, intervalValue);\n\t}\n\tif (immediate && isClient) resume();\n\tif (isRef(interval) || typeof interval === \"function\") tryOnScopeDispose(watch(interval, () => {\n\t\tif (isActive.value && isClient) resume();\n\t}));\n\ttryOnScopeDispose(pause);\n\treturn {\n\t\tisActive: shallowReadonly(isActive),\n\t\tpause,\n\t\tresume\n\t};\n}\n\n//#endregion\n//#region useInterval/index.ts\nfunction useInterval(interval = 1e3, options = {}) {\n\tconst { controls: exposeControls = false, immediate = true, callback } = options;\n\tconst counter = shallowRef(0);\n\tconst update = () => counter.value += 1;\n\tconst reset = () => {\n\t\tcounter.value = 0;\n\t};\n\tconst controls = useIntervalFn(callback ? () => {\n\t\tupdate();\n\t\tcallback(counter.value);\n\t} : update, interval, { immediate });\n\tif (exposeControls) return {\n\t\tcounter: shallowReadonly(counter),\n\t\treset,\n\t\t...controls\n\t};\n\telse return shallowReadonly(counter);\n}\n\n//#endregion\n//#region useLastChanged/index.ts\nfunction useLastChanged(source, options = {}) {\n\tvar _options$initialValue;\n\tconst ms = shallowRef((_options$initialValue = options.initialValue) !== null && _options$initialValue !== void 0 ? _options$initialValue : null);\n\twatch(source, () => ms.value = timestamp(), options);\n\treturn shallowReadonly(ms);\n}\n\n//#endregion\n//#region useTimeoutFn/index.ts\n/**\n* Wrapper for `setTimeout` with controls.\n*\n* @param cb\n* @param interval\n* @param options\n*/\nfunction useTimeoutFn(cb, interval, options = {}) {\n\tconst { immediate = true, immediateCallback = false } = options;\n\tconst isPending = shallowRef(false);\n\tlet timer;\n\tfunction clear() {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = void 0;\n\t\t}\n\t}\n\tfunction stop() {\n\t\tisPending.value = false;\n\t\tclear();\n\t}\n\tfunction start(...args) {\n\t\tif (immediateCallback) cb();\n\t\tclear();\n\t\tisPending.value = true;\n\t\ttimer = setTimeout(() => {\n\t\t\tisPending.value = false;\n\t\t\ttimer = void 0;\n\t\t\tcb(...args);\n\t\t}, toValue(interval));\n\t}\n\tif (immediate) {\n\t\tisPending.value = true;\n\t\tif (isClient) start();\n\t}\n\ttryOnScopeDispose(stop);\n\treturn {\n\t\tisPending: shallowReadonly(isPending),\n\t\tstart,\n\t\tstop\n\t};\n}\n\n//#endregion\n//#region useTimeout/index.ts\nfunction useTimeout(interval = 1e3, options = {}) {\n\tconst { controls: exposeControls = false, callback } = options;\n\tconst controls = useTimeoutFn(callback !== null && callback !== void 0 ? callback : noop, interval, options);\n\tconst ready = computed(() => !controls.isPending.value);\n\tif (exposeControls) return {\n\t\tready,\n\t\t...controls\n\t};\n\telse return ready;\n}\n\n//#endregion\n//#region useToNumber/index.ts\n/**\n* Reactively convert a string ref to number.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useToNumber(value, options = {}) {\n\tconst { method = \"parseFloat\", radix, nanToZero } = options;\n\treturn computed(() => {\n\t\tlet resolved = toValue(value);\n\t\tif (typeof method === \"function\") resolved = method(resolved);\n\t\telse if (typeof resolved === \"string\") resolved = Number[method](resolved, radix);\n\t\tif (nanToZero && Number.isNaN(resolved)) resolved = 0;\n\t\treturn resolved;\n\t});\n}\n\n//#endregion\n//#region useToString/index.ts\n/**\n* Reactively convert a ref to string.\n*\n* @see https://vueuse.org/useToString\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useToString(value) {\n\treturn computed(() => `${toValue(value)}`);\n}\n\n//#endregion\n//#region useToggle/index.ts\n/**\n* A boolean ref with a toggler\n*\n* @see https://vueuse.org/useToggle\n* @param [initialValue]\n* @param options\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useToggle(initialValue = false, options = {}) {\n\tconst { truthyValue = true, falsyValue = false } = options;\n\tconst valueIsRef = isRef(initialValue);\n\tconst _value = shallowRef(initialValue);\n\tfunction toggle(value) {\n\t\tif (arguments.length) {\n\t\t\t_value.value = value;\n\t\t\treturn _value.value;\n\t\t} else {\n\t\t\tconst truthy = toValue(truthyValue);\n\t\t\t_value.value = _value.value === truthy ? toValue(falsyValue) : truthy;\n\t\t\treturn _value.value;\n\t\t}\n\t}\n\tif (valueIsRef) return toggle;\n\telse return [_value, toggle];\n}\n\n//#endregion\n//#region watchArray/index.ts\n/**\n* Watch for an array with additions and removals.\n*\n* @see https://vueuse.org/watchArray\n*/\nfunction watchArray(source, cb, options) {\n\tlet oldList = (options === null || options === void 0 ? void 0 : options.immediate) ? [] : [...typeof source === \"function\" ? source() : Array.isArray(source) ? source : toValue(source)];\n\treturn watch(source, (newList, _, onCleanup) => {\n\t\tconst oldListRemains = Array.from({ length: oldList.length });\n\t\tconst added = [];\n\t\tfor (const obj of newList) {\n\t\t\tlet found = false;\n\t\t\tfor (let i = 0; i < oldList.length; i++) if (!oldListRemains[i] && obj === oldList[i]) {\n\t\t\t\toldListRemains[i] = true;\n\t\t\t\tfound = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!found) added.push(obj);\n\t\t}\n\t\tconst removed = oldList.filter((_$1, i) => !oldListRemains[i]);\n\t\tcb(newList, oldList, added, removed, onCleanup);\n\t\toldList = [...newList];\n\t}, options);\n}\n\n//#endregion\n//#region watchAtMost/index.ts\nfunction watchAtMost(source, cb, options) {\n\tconst { count,...watchOptions } = options;\n\tconst current = shallowRef(0);\n\tconst { stop, resume, pause } = watchWithFilter(source, (...args) => {\n\t\tcurrent.value += 1;\n\t\tif (current.value >= toValue(count)) nextTick(() => stop());\n\t\tcb(...args);\n\t}, watchOptions);\n\treturn {\n\t\tcount: current,\n\t\tstop,\n\t\tresume,\n\t\tpause\n\t};\n}\n\n//#endregion\n//#region watchDebounced/index.ts\nfunction watchDebounced(source, cb, options = {}) {\n\tconst { debounce = 0, maxWait = void 0,...watchOptions } = options;\n\treturn watchWithFilter(source, cb, {\n\t\t...watchOptions,\n\t\teventFilter: debounceFilter(debounce, { maxWait })\n\t});\n}\n/** @deprecated use `watchDebounced` instead */\nconst debouncedWatch = watchDebounced;\n\n//#endregion\n//#region watchDeep/index.ts\n/**\n* Shorthand for watching value with {deep: true}\n*\n* @see https://vueuse.org/watchDeep\n*/\nfunction watchDeep(source, cb, options) {\n\treturn watch(source, cb, {\n\t\t...options,\n\t\tdeep: true\n\t});\n}\n\n//#endregion\n//#region watchIgnorable/index.ts\nfunction watchIgnorable(source, cb, options = {}) {\n\tconst { eventFilter = bypassFilter,...watchOptions } = options;\n\tconst filteredCb = createFilterWrapper(eventFilter, cb);\n\tlet ignoreUpdates;\n\tlet ignorePrevAsyncUpdates;\n\tlet stop;\n\tif (watchOptions.flush === \"sync\") {\n\t\tlet ignore = false;\n\t\tignorePrevAsyncUpdates = () => {};\n\t\tignoreUpdates = (updater) => {\n\t\t\tignore = true;\n\t\t\tupdater();\n\t\t\tignore = false;\n\t\t};\n\t\tstop = watch(source, (...args) => {\n\t\t\tif (!ignore) filteredCb(...args);\n\t\t}, watchOptions);\n\t} else {\n\t\tconst disposables = [];\n\t\tlet ignoreCounter = 0;\n\t\tlet syncCounter = 0;\n\t\tignorePrevAsyncUpdates = () => {\n\t\t\tignoreCounter = syncCounter;\n\t\t};\n\t\tdisposables.push(watch(source, () => {\n\t\t\tsyncCounter++;\n\t\t}, {\n\t\t\t...watchOptions,\n\t\t\tflush: \"sync\"\n\t\t}));\n\t\tignoreUpdates = (updater) => {\n\t\t\tconst syncCounterPrev = syncCounter;\n\t\t\tupdater();\n\t\t\tignoreCounter += syncCounter - syncCounterPrev;\n\t\t};\n\t\tdisposables.push(watch(source, (...args) => {\n\t\t\tconst ignore = ignoreCounter > 0 && ignoreCounter === syncCounter;\n\t\t\tignoreCounter = 0;\n\t\t\tsyncCounter = 0;\n\t\t\tif (ignore) return;\n\t\t\tfilteredCb(...args);\n\t\t}, watchOptions));\n\t\tstop = () => {\n\t\t\tdisposables.forEach((fn) => fn());\n\t\t};\n\t}\n\treturn {\n\t\tstop,\n\t\tignoreUpdates,\n\t\tignorePrevAsyncUpdates\n\t};\n}\n/** @deprecated use `watchIgnorable` instead */\nconst ignorableWatch = watchIgnorable;\n\n//#endregion\n//#region watchImmediate/index.ts\n/**\n* Shorthand for watching value with {immediate: true}\n*\n* @see https://vueuse.org/watchImmediate\n*/\nfunction watchImmediate(source, cb, options) {\n\treturn watch(source, cb, {\n\t\t...options,\n\t\timmediate: true\n\t});\n}\n\n//#endregion\n//#region watchOnce/index.ts\n/**\n* Shorthand for watching value with { once: true }\n*\n* @see https://vueuse.org/watchOnce\n*/\nfunction watchOnce(source, cb, options) {\n\treturn watch(source, cb, {\n\t\t...options,\n\t\tonce: true\n\t});\n}\n\n//#endregion\n//#region watchThrottled/index.ts\nfunction watchThrottled(source, cb, options = {}) {\n\tconst { throttle = 0, trailing = true, leading = true,...watchOptions } = options;\n\treturn watchWithFilter(source, cb, {\n\t\t...watchOptions,\n\t\teventFilter: throttleFilter(throttle, trailing, leading)\n\t});\n}\n/** @deprecated use `watchThrottled` instead */\nconst throttledWatch = watchThrottled;\n\n//#endregion\n//#region watchTriggerable/index.ts\nfunction watchTriggerable(source, cb, options = {}) {\n\tlet cleanupFn;\n\tfunction onEffect() {\n\t\tif (!cleanupFn) return;\n\t\tconst fn = cleanupFn;\n\t\tcleanupFn = void 0;\n\t\tfn();\n\t}\n\t/** Register the function `cleanupFn` */\n\tfunction onCleanup(callback) {\n\t\tcleanupFn = callback;\n\t}\n\tconst _cb = (value, oldValue) => {\n\t\tonEffect();\n\t\treturn cb(value, oldValue, onCleanup);\n\t};\n\tconst res = watchIgnorable(source, _cb, options);\n\tconst { ignoreUpdates } = res;\n\tconst trigger = () => {\n\t\tlet res$1;\n\t\tignoreUpdates(() => {\n\t\t\tres$1 = _cb(getWatchSources(source), getOldValue(source));\n\t\t});\n\t\treturn res$1;\n\t};\n\treturn {\n\t\t...res,\n\t\ttrigger\n\t};\n}\nfunction getWatchSources(sources) {\n\tif (isReactive(sources)) return sources;\n\tif (Array.isArray(sources)) return sources.map((item) => toValue(item));\n\treturn toValue(sources);\n}\nfunction getOldValue(source) {\n\treturn Array.isArray(source) ? source.map(() => void 0) : void 0;\n}\n\n//#endregion\n//#region whenever/index.ts\n/**\n* Shorthand for watching value to be truthy\n*\n* @see https://vueuse.org/whenever\n*/\nfunction whenever(source, cb, options) {\n\tconst stop = watch(source, (v, ov, onInvalidate) => {\n\t\tif (v) {\n\t\t\tif (options === null || options === void 0 ? void 0 : options.once) nextTick(() => stop());\n\t\t\tcb(v, ov, onInvalidate);\n\t\t}\n\t}, {\n\t\t...options,\n\t\tonce: false\n\t});\n\treturn stop;\n}\n\n//#endregion\nexport { assert, autoResetRef, bypassFilter, camelize, clamp, computedEager, computedWithControl, containsProp, controlledComputed, controlledRef, createEventHook, createFilterWrapper, createGlobalState, createInjectionState, createReactiveFn, createRef, createSharedComposable, createSingletonPromise, debounceFilter, debouncedRef, debouncedWatch, eagerComputed, extendRef, formatDate, get, getLifeCycleTarget, hasOwn, hyphenate, identity, ignorableWatch, increaseWithUnit, injectLocal, invoke, isClient, isDef, isDefined, isIOS, isObject, isWorker, makeDestructurable, noop, normalizeDate, notNullish, now, objectEntries, objectOmit, objectPick, pausableFilter, pausableWatch, promiseTimeout, provideLocal, pxValue, rand, reactify, reactifyObject, reactiveComputed, reactiveOmit, reactivePick, refAutoReset, refDebounced, refDefault, refManualReset, refThrottled, refWithControl, set, syncRef, syncRefs, throttleFilter, throttledRef, throttledWatch, timestamp, toArray, toReactive, toRef, toRefs, tryOnBeforeMount, tryOnBeforeUnmount, tryOnMounted, tryOnScopeDispose, tryOnUnmounted, until, useArrayDifference, useArrayEvery, useArrayFilter, useArrayFind, useArrayFindIndex, useArrayFindLast, useArrayIncludes, useArrayJoin, useArrayMap, useArrayReduce, useArraySome, useArrayUnique, useCounter, useDateFormat, useDebounce, useDebounceFn, useInterval, useIntervalFn, useLastChanged, useThrottle, useThrottleFn, useTimeout, useTimeoutFn, useToNumber, useToString, useToggle, watchArray, watchAtMost, watchDebounced, watchDeep, watchIgnorable, watchImmediate, watchOnce, watchPausable, watchThrottled, watchTriggerable, watchWithFilter, whenever };","import { notNullish } from \"@vueuse/shared\";\nimport { computed, shallowRef, toValue, watch } from \"vue\";\nimport { toArray, tryOnScopeDispose as tryOnScopeDispose$1, unrefElement } from \"@vueuse/core\";\nimport { createFocusTrap } from \"focus-trap\";\n\n//#region useFocusTrap/index.ts\n/**\n* Reactive focus-trap\n*\n* @see https://vueuse.org/useFocusTrap\n*/\nfunction useFocusTrap(target, options = {}) {\n\tlet trap;\n\tconst { immediate,...focusTrapOptions } = options;\n\tconst hasFocus = shallowRef(false);\n\tconst isPaused = shallowRef(false);\n\tconst activate = (opts) => trap && trap.activate(opts);\n\tconst deactivate = (opts) => trap && trap.deactivate(opts);\n\tconst pause = () => {\n\t\tif (trap) {\n\t\t\ttrap.pause();\n\t\t\tisPaused.value = true;\n\t\t}\n\t};\n\tconst unpause = () => {\n\t\tif (trap) {\n\t\t\ttrap.unpause();\n\t\t\tisPaused.value = false;\n\t\t}\n\t};\n\twatch(computed(() => {\n\t\treturn toArray(toValue(target)).map((el) => {\n\t\t\tconst _el = toValue(el);\n\t\t\treturn typeof _el === \"string\" ? _el : unrefElement(_el);\n\t\t}).filter(notNullish);\n\t}), (els) => {\n\t\tif (!els.length) return;\n\t\tif (!trap) {\n\t\t\ttrap = createFocusTrap(els, {\n\t\t\t\t...focusTrapOptions,\n\t\t\t\tonActivate() {\n\t\t\t\t\thasFocus.value = true;\n\t\t\t\t\tif (options.onActivate) options.onActivate();\n\t\t\t\t},\n\t\t\t\tonDeactivate() {\n\t\t\t\t\thasFocus.value = false;\n\t\t\t\t\tif (options.onDeactivate) options.onDeactivate();\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (immediate) activate();\n\t\t} else {\n\t\t\tconst isActive = trap === null || trap === void 0 ? void 0 : trap.active;\n\t\t\ttrap === null || trap === void 0 || trap.updateContainerElements(els);\n\t\t\tif (!isActive && immediate) activate();\n\t\t}\n\t}, { flush: \"post\" });\n\ttryOnScopeDispose$1(() => deactivate());\n\treturn {\n\t\thasFocus,\n\t\tisPaused,\n\t\tactivate,\n\t\tdeactivate,\n\t\tpause,\n\t\tunpause\n\t};\n}\n\n//#endregion\nexport { useFocusTrap as t };","import { nextTick, Ref, ref, watch } from \"vue\";\nimport { useFocusTrap } from \"@vueuse/integrations/useFocusTrap\";\n\nconst tabbableSelector = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n \"[contenteditable='true']\",\n].join(\",\");\n\nexport function useOverlayFocus(\n element: Ref<HTMLElement | null>,\n isTop: Ref<boolean>,\n clickOutsideDeactivates = false,\n) {\n const unpausing = ref(false);\n\n const hasTabbableNodes = () =>\n !!element.value?.querySelector(tabbableSelector);\n\n const { activate, deactivate, pause, unpause } = useFocusTrap(element, {\n immediate: false,\n clickOutsideDeactivates,\n initialFocus: () => {\n if (unpausing.value) {\n return false;\n }\n const focus = element.value?.querySelector(\"[popover-focus]\");\n if (focus) {\n return focus as HTMLElement;\n }\n const h2 = element.value?.querySelector(\"h2\");\n if (h2) {\n return h2 as HTMLElement;\n }\n const selected = element.value?.querySelector(\n \"[aria-selected='true']\",\n );\n if (selected) {\n return selected as HTMLElement;\n }\n },\n onPostPause: () => (unpausing.value = true),\n onPostUnpause: () => {\n nextTick(() => {\n unpausing.value = false;\n }).catch((err) => {\n console.error(err);\n });\n },\n });\n\n watch(isTop, (top) => {\n if (top) {\n nextTick(() => {\n if (hasTabbableNodes()) {\n unpause();\n }\n }).catch((err) => {\n console.error(err);\n });\n } else {\n pause();\n }\n });\n\n const activateWhenReady = () => {\n nextTick(() => {\n requestAnimationFrame(() => {\n if (hasTabbableNodes()) {\n activate();\n }\n });\n }).catch((err) => {\n console.error(err);\n });\n };\n\n return { activate: activateWhenReady, deactivate, pause, unpause };\n}\n","import { nextTick, onBeforeUnmount, onMounted, Ref } from \"vue\";\n\nexport function useOverlayEscape(\n containers: Ref<Element | null>[],\n isTop: Ref<boolean>,\n open: Ref<boolean>,\n hide: () => void,\n pop: () => void,\n) {\n function onDocumentClick(e: MouseEvent) {\n if (!open.value || !isTop.value) {\n return;\n }\n for (const ref of containers) {\n if (ref.value?.contains(e.target as Node)) {\n return;\n }\n }\n hide();\n }\n\n function onDocumentKeydown(e: KeyboardEvent) {\n if (e.key === \"Escape\" && open.value) {\n if (isTop.value) {\n e.preventDefault();\n nextTick(hide).catch((err) => {\n console.error(err);\n })\n }\n }\n }\n\n onMounted(() => {\n document.addEventListener(\"mousedown\", onDocumentClick);\n document.addEventListener(\"keydown\", onDocumentKeydown);\n });\n onBeforeUnmount(() => {\n document.removeEventListener(\"mousedown\", onDocumentClick);\n document.removeEventListener(\"keydown\", onDocumentKeydown);\n pop();\n });\n}\n","/**\n * Calculates the position for a popover based on the anchor element's position,\n * the popover's dimensions, and the viewport dimensions.\n *\n * @param anchorRect\n * @param popoverRect\n * @param viewportRect\n * @param options Optional config: { gap?: number, margin?: number }\n */\nexport function calculatePopoverPosition(\n anchorRect: DOMRect,\n popoverRect: DOMRect,\n viewportRect: DOMRect,\n options?: { gap?: number; margin?: number; preferAbove?: boolean },\n): { top: number; left: number; xOffset: number; placedAbove: boolean; overlay: boolean } {\n const gap = options?.gap ?? 8;\n const margin = options?.margin ?? 16;\n const preferAbove = options?.preferAbove ?? false;\n\n // Prefer below, but go above if not enough space\n let placedAbove = false;\n let overlay = false;\n let top: number;\n if (preferAbove) {\n if (anchorRect.top - popoverRect.height - gap > viewportRect.top + margin) {\n // Place above\n top = anchorRect.top - popoverRect.height - gap;\n placedAbove = true;\n } else if (anchorRect.bottom + popoverRect.height + gap <= viewportRect.bottom - margin) {\n // Place below if not enough room above\n top = anchorRect.bottom + gap;\n } else {\n // Not enough room above or below, overlay on anchor, margin from top\n top = viewportRect.top + margin;\n overlay = true;\n }\n } else {\n if (\n anchorRect.bottom + popoverRect.height + gap > viewportRect.bottom - margin &&\n anchorRect.top - popoverRect.height - gap > viewportRect.top + margin\n ) {\n // Place above\n top = anchorRect.top - popoverRect.height - gap;\n placedAbove = true;\n } else if (\n anchorRect.bottom + popoverRect.height + gap > viewportRect.bottom - margin &&\n anchorRect.top - popoverRect.height - gap <= viewportRect.top + margin\n ) {\n // Not enough room above or below, overlay on anchor, margin from top\n top = viewportRect.top + margin;\n overlay = true;\n } else {\n // Place below\n top = anchorRect.bottom + gap;\n }\n }\n\n // Center horizontally by default\n let left = anchorRect.left + (anchorRect.width - popoverRect.width) / 2;\n // Clamp to viewport\n if (left < viewportRect.left + margin) {\n left = viewportRect.left + margin;\n }\n if (left + popoverRect.width > viewportRect.right - margin) {\n left = viewportRect.right - popoverRect.width - margin;\n }\n // Final safety clamp (if popover is wider than viewport)\n if (left < viewportRect.left + margin) {\n left = viewportRect.left + margin;\n }\n\n // Calculate X offset from centered position (for arrow)\n const centeredLeft = anchorRect.left + (anchorRect.width - popoverRect.width) / 2;\n const xOffset = left - centeredLeft;\n\n return { top, left, xOffset, placedAbove, overlay };\n}\n","<script lang=\"ts\">\n/**\n * Popover that appears next to or over a trigger element, staying visible\n * in the viewport as much as possible.\n *\n * **Slot** `trigger` is optional. When provided, it should contain an\n * interactive element for opening the popover and it is used for\n * `aria-labelledby`. The trigger is passed a prop `toggle` which is a function\n * that toggles the popover's open state.\n *\n * Without a trigger slot, open the popover programmatically via `show()` or\n * `toggle()` on the component instance / custom element.\n *\n * **Slot** `default` is the content of the popover.\n *\n * Example:\n *\n * ```vue-html\n * <GPopover>\n * <template #trigger=\"{ toggle }\">\n * <GButton @click=\"toggle\">\n * Can Popovers have Popovers?\n * </GButton>\n * </template>\n * <div>In theory, but I wouldn't recommend it.</div>\n * </GPopover>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n ref,\n useId,\n useSlots,\n useTemplateRef,\n watch,\n toRef,\n} from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\nimport { calculatePopoverPosition } from \"../compose/popoverPosition.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Render without padding\n * @demo\n */\n minimal?: boolean;\n /**\n * v-model binding for the open state. Also works as a plain\n * prop/attribute in custom-element mode where `defineModel`\n * would revert local state.\n */\n modelValue?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n minimal: false,\n modelValue: false,\n});\nconst emit = defineEmits([\"show\", \"hide\", \"update:modelValue\"]);\n\nconst open = ref(props.modelValue);\nwatch(toRef(props, \"modelValue\"), (v) => {\n open.value = v;\n});\nconst slots = useSlots();\nconst hasTrigger = computed(() => !!slots.trigger);\n\nconst triggerRef = useTemplateRef<HTMLElement | null>(\"triggerRef\");\nconst popoverRef = useTemplateRef<HTMLElement | null>(\"popoverRef\");\n\n// Disable Teleport inside custom elements: scoped named slots\n// break CE slot distribution when content is teleported.\nconst { isCustomElement } = useCustomElementAttrs();\nconst disableTeleport = isCustomElement;\n\nconst id = useId();\nconst { push, pop, isTop, zIndex } = useOverlayStack(id, true);\nconst { activate, deactivate } = useOverlayFocus(popoverRef, isTop, true);\nuseOverlayEscape([popoverRef, triggerRef], isTop, open, hide, pop);\n\nwatch(open, (val) => {\n if (val) {\n nextTick(() => {\n nextTick(() => activate());\n });\n push();\n emit(\"show\");\n } else {\n deactivate();\n pop();\n emit(\"hide\");\n }\n});\n\nfunction show() {\n open.value = true;\n emit(\"update:modelValue\", true);\n}\n\nfunction hide() {\n open.value = false;\n emit(\"update:modelValue\", false);\n}\n\nfunction toggle() {\n open.value = !open.value;\n emit(\"update:modelValue\", open.value);\n}\n\nconst popoverPosition = ref<Record<string, any>>({ top: 0, left: 0 });\nconst arrowPosition = ref<Record<string, any>>({ left: \"50%\" });\nconst popoverAbove = ref(false);\nconst popoverOverlay = ref(false);\nlet resizeObserver: ResizeObserver | null = null;\n\nfunction getAnchorElement() {\n if (triggerRef.value) {\n return triggerRef.value;\n }\n\n let host: HTMLElement | null = popoverRef.value?.parentElement ?? null;\n while (host && host.tagName.toLowerCase() !== \"g-popover\") {\n host = host.parentElement;\n }\n const previousSibling = host?.previousElementSibling;\n\n if (previousSibling instanceof HTMLElement) {\n return previousSibling;\n }\n\n return null;\n}\n\nfunction updatePopoverPosition() {\n if (!popoverRef.value) {\n return;\n }\n\n // Use offsetWidth/offsetHeight for popover dimensions to avoid getting\n // scaled values during the CSS scale() enter transition.\n const popoverRect = new DOMRect(0, 0, popoverRef.value.offsetWidth, popoverRef.value.offsetHeight);\n const viewportRect = new DOMRect(0, 0, window.innerWidth, window.innerHeight);\n\n const anchorEl = getAnchorElement();\n\n if (!anchorEl) {\n popoverPosition.value = {\n top: Math.max((viewportRect.height - popoverRect.height) / 2, 8),\n left: Math.max((viewportRect.width - popoverRect.width) / 2, 8),\n };\n popoverOverlay.value = false;\n popoverAbove.value = false;\n arrowPosition.value = { left: \"50%\" };\n return;\n }\n\n const triggerRect = anchorEl.getBoundingClientRect();\n\n const { top, left, xOffset, placedAbove, overlay } =\n calculatePopoverPosition(triggerRect, popoverRect, viewportRect, {\n gap: props.minimal ? 0 : 8,\n });\n popoverPosition.value = { top, left };\n arrowPosition.value = {\n left: `${popoverRect.width / 2 - xOffset}px`,\n top: placedAbove ? \"auto\" : undefined,\n bottom: placedAbove ? \"-8px\" : undefined,\n };\n popoverAbove.value = placedAbove;\n popoverOverlay.value = overlay;\n}\n\nwatch(open, (val) => {\n if (val) {\n nextTick(() => {\n updatePopoverPosition();\n window.addEventListener(\"resize\", updatePopoverPosition);\n window.addEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (popoverRef.value) {\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n resizeObserver = new ResizeObserver(() =>\n updatePopoverPosition(),\n );\n resizeObserver.observe(popoverRef.value);\n }\n });\n } else {\n window.removeEventListener(\"resize\", updatePopoverPosition);\n window.removeEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n }\n});\n\nonBeforeUnmount(() => {\n window.removeEventListener(\"resize\", updatePopoverPosition);\n window.removeEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n});\ndefineExpose({\n show,\n hide,\n toggle\n})\n\n</script>\n\n<template>\n <div class=\"g-popover-wrap\">\n <div v-if=\"hasTrigger\" ref=\"triggerRef\" class=\"g-popover-trigger\" :id=\"`${id}-trigger`\">\n <slot name=\"trigger\" :toggle=\"toggle\"></slot>\n </div>\n <Teleport to=\"#modal-root\" :disabled=\"disableTeleport\">\n <transition name=\"g-popover-expand\" appear>\n <div\n v-if=\"isCustomElement || open\" v-show=\"open\"\n ref=\"popoverRef\"\n :class=\"{\n 'g-popover': true,\n 'g-popover-above': popoverAbove,\n 'g-popover-below': !popoverAbove,\n 'g-popover-minimal': minimal,\n }\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"hasTrigger ? `${id}-trigger` : undefined\"\n :aria-label=\"hasTrigger ? undefined : 'Popover'\"\n :style=\"{\n top: popoverPosition.top + 'px',\n left: popoverPosition.left + 'px',\n zIndex,\n }\"\n >\n <div\n v-if=\"!popoverOverlay && !minimal\"\n class=\"g-popover-arrow\"\n :class=\"{ 'g-popover-arrow-above': popoverAbove }\"\n :style=\"arrowPosition\"\n aria-hidden=\"true\"\n ></div>\n <slot></slot>\n <button\n v-if=\"!minimal\"\n class=\"g-popover-close\"\n type=\"button\"\n aria-label=\"Close popover\"\n @click=\"hide\"\n >\n <svg\n class=\"g-popover-close-icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n </div>\n </transition>\n </Teleport>\n </div>\n</template>\n\n<style>\ng-popover:not(:defined) {\n display: inline-block;\n}\n\n.g-popover {\n h2 {\n font-size: 1.25rem;\n margin: 0 0 0.75rem 0;\n }\n p {\n margin: 0 0 0.5rem 0;\n }\n}\n.g-popover-trigger {\n display: inline-block;\n}\n.g-popover {\n position: fixed;\n z-index: 1000;\n background: var(--g-surface-0);\n border: 1px solid var(--g-surface-200);\n color: var(--g-surface-900);\n font-weight: normal;\n font-size: 1rem;\n border-radius: 4px;\n box-shadow: var(--il-shadow);\n padding: 1.5rem 1rem 1rem;\n min-width: 200px;\n max-width: 500px;\n top: 0;\n left: 0;\n text-align: left;\n}\n.g-popover.g-popover-minimal {\n padding: 0;\n min-width: 0;\n}\n\n.g-popover-arrow {\n box-sizing: border-box;\n position: absolute;\n top: -8px;\n width: 20px;\n height: 8px;\n left: 50%;\n transform: translateX(-50%);\n pointer-events: none;\n z-index: 1;\n}\n\n.g-popover-arrow::after {\n box-sizing: border-box;\n content: \"\";\n display: block;\n margin: 0 auto;\n width: 16px;\n height: 8px;\n background: transparent;\n border-left: 8px solid transparent;\n border-right: 8px solid transparent;\n border-bottom: 8px solid white;\n /* Add border for the arrow */\n position: relative;\n z-index: 2;\n}\n\n.g-popover-arrow::before {\n box-sizing: border-box;\n content: \"\";\n display: block;\n position: absolute;\n top: -1px;\n left: 1px;\n width: 18px;\n height: 9px;\n border-left: 8px solid transparent;\n border-right: 8px solid transparent;\n border-bottom: 8px solid var(--g-surface-200, #ccc);\n z-index: 1;\n}\n\n.g-popover-arrow-above {\n transform: translateX(-50%) rotate(180deg);\n}\n.g-popover-close {\n position: absolute;\n display: block;\n top: 1px;\n right: 1px;\n border: none;\n padding: 0.25rem 0.25rem;\n border-radius: 7px;\n background: transparent;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n }\n &:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n .g-popover-close-icon {\n width: 1.25rem;\n height: 1.25rem;\n display: block;\n }\n}\n\n.g-popover-expand-enter-active,\n.g-popover-expand-leave-active {\n transition:\n opacity 0.18s cubic-bezier(0.4, 0, 0.2, 1),\n transform 0.18s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.g-popover-expand-enter-from,\n.g-popover-expand-leave-to {\n opacity: 0;\n transform: scale(0.95);\n}\n\n.g-popover-expand-enter-to,\n.g-popover-expand-leave-from {\n opacity: 1;\n transform: scale(1);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-popover-expand-enter-active,\n .g-popover-expand-leave-active {\n transition: none !important;\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Popover that appears next to or over a trigger element, staying visible\n * in the viewport as much as possible.\n *\n * **Slot** `trigger` is optional. When provided, it should contain an\n * interactive element for opening the popover and it is used for\n * `aria-labelledby`. The trigger is passed a prop `toggle` which is a function\n * that toggles the popover's open state.\n *\n * Without a trigger slot, open the popover programmatically via `show()` or\n * `toggle()` on the component instance / custom element.\n *\n * **Slot** `default` is the content of the popover.\n *\n * Example:\n *\n * ```vue-html\n * <GPopover>\n * <template #trigger=\"{ toggle }\">\n * <GButton @click=\"toggle\">\n * Can Popovers have Popovers?\n * </GButton>\n * </template>\n * <div>In theory, but I wouldn't recommend it.</div>\n * </GPopover>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n ref,\n useId,\n useSlots,\n useTemplateRef,\n watch,\n toRef,\n} from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\nimport { calculatePopoverPosition } from \"../compose/popoverPosition.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Render without padding\n * @demo\n */\n minimal?: boolean;\n /**\n * v-model binding for the open state. Also works as a plain\n * prop/attribute in custom-element mode where `defineModel`\n * would revert local state.\n */\n modelValue?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n minimal: false,\n modelValue: false,\n});\nconst emit = defineEmits([\"show\", \"hide\", \"update:modelValue\"]);\n\nconst open = ref(props.modelValue);\nwatch(toRef(props, \"modelValue\"), (v) => {\n open.value = v;\n});\nconst slots = useSlots();\nconst hasTrigger = computed(() => !!slots.trigger);\n\nconst triggerRef = useTemplateRef<HTMLElement | null>(\"triggerRef\");\nconst popoverRef = useTemplateRef<HTMLElement | null>(\"popoverRef\");\n\n// Disable Teleport inside custom elements: scoped named slots\n// break CE slot distribution when content is teleported.\nconst { isCustomElement } = useCustomElementAttrs();\nconst disableTeleport = isCustomElement;\n\nconst id = useId();\nconst { push, pop, isTop, zIndex } = useOverlayStack(id, true);\nconst { activate, deactivate } = useOverlayFocus(popoverRef, isTop, true);\nuseOverlayEscape([popoverRef, triggerRef], isTop, open, hide, pop);\n\nwatch(open, (val) => {\n if (val) {\n nextTick(() => {\n nextTick(() => activate());\n });\n push();\n emit(\"show\");\n } else {\n deactivate();\n pop();\n emit(\"hide\");\n }\n});\n\nfunction show() {\n open.value = true;\n emit(\"update:modelValue\", true);\n}\n\nfunction hide() {\n open.value = false;\n emit(\"update:modelValue\", false);\n}\n\nfunction toggle() {\n open.value = !open.value;\n emit(\"update:modelValue\", open.value);\n}\n\nconst popoverPosition = ref<Record<string, any>>({ top: 0, left: 0 });\nconst arrowPosition = ref<Record<string, any>>({ left: \"50%\" });\nconst popoverAbove = ref(false);\nconst popoverOverlay = ref(false);\nlet resizeObserver: ResizeObserver | null = null;\n\nfunction getAnchorElement() {\n if (triggerRef.value) {\n return triggerRef.value;\n }\n\n let host: HTMLElement | null = popoverRef.value?.parentElement ?? null;\n while (host && host.tagName.toLowerCase() !== \"g-popover\") {\n host = host.parentElement;\n }\n const previousSibling = host?.previousElementSibling;\n\n if (previousSibling instanceof HTMLElement) {\n return previousSibling;\n }\n\n return null;\n}\n\nfunction updatePopoverPosition() {\n if (!popoverRef.value) {\n return;\n }\n\n // Use offsetWidth/offsetHeight for popover dimensions to avoid getting\n // scaled values during the CSS scale() enter transition.\n const popoverRect = new DOMRect(0, 0, popoverRef.value.offsetWidth, popoverRef.value.offsetHeight);\n const viewportRect = new DOMRect(0, 0, window.innerWidth, window.innerHeight);\n\n const anchorEl = getAnchorElement();\n\n if (!anchorEl) {\n popoverPosition.value = {\n top: Math.max((viewportRect.height - popoverRect.height) / 2, 8),\n left: Math.max((viewportRect.width - popoverRect.width) / 2, 8),\n };\n popoverOverlay.value = false;\n popoverAbove.value = false;\n arrowPosition.value = { left: \"50%\" };\n return;\n }\n\n const triggerRect = anchorEl.getBoundingClientRect();\n\n const { top, left, xOffset, placedAbove, overlay } =\n calculatePopoverPosition(triggerRect, popoverRect, viewportRect, {\n gap: props.minimal ? 0 : 8,\n });\n popoverPosition.value = { top, left };\n arrowPosition.value = {\n left: `${popoverRect.width / 2 - xOffset}px`,\n top: placedAbove ? \"auto\" : undefined,\n bottom: placedAbove ? \"-8px\" : undefined,\n };\n popoverAbove.value = placedAbove;\n popoverOverlay.value = overlay;\n}\n\nwatch(open, (val) => {\n if (val) {\n nextTick(() => {\n updatePopoverPosition();\n window.addEventListener(\"resize\", updatePopoverPosition);\n window.addEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (popoverRef.value) {\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n resizeObserver = new ResizeObserver(() =>\n updatePopoverPosition(),\n );\n resizeObserver.observe(popoverRef.value);\n }\n });\n } else {\n window.removeEventListener(\"resize\", updatePopoverPosition);\n window.removeEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n }\n});\n\nonBeforeUnmount(() => {\n window.removeEventListener(\"resize\", updatePopoverPosition);\n window.removeEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n});\ndefineExpose({\n show,\n hide,\n toggle\n})\n\n</script>\n\n<template>\n <div class=\"g-popover-wrap\">\n <div v-if=\"hasTrigger\" ref=\"triggerRef\" class=\"g-popover-trigger\" :id=\"`${id}-trigger`\">\n <slot name=\"trigger\" :toggle=\"toggle\"></slot>\n </div>\n <Teleport to=\"#modal-root\" :disabled=\"disableTeleport\">\n <transition name=\"g-popover-expand\" appear>\n <div\n v-if=\"isCustomElement || open\" v-show=\"open\"\n ref=\"popoverRef\"\n :class=\"{\n 'g-popover': true,\n 'g-popover-above': popoverAbove,\n 'g-popover-below': !popoverAbove,\n 'g-popover-minimal': minimal,\n }\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"hasTrigger ? `${id}-trigger` : undefined\"\n :aria-label=\"hasTrigger ? undefined : 'Popover'\"\n :style=\"{\n top: popoverPosition.top + 'px',\n left: popoverPosition.left + 'px',\n zIndex,\n }\"\n >\n <div\n v-if=\"!popoverOverlay && !minimal\"\n class=\"g-popover-arrow\"\n :class=\"{ 'g-popover-arrow-above': popoverAbove }\"\n :style=\"arrowPosition\"\n aria-hidden=\"true\"\n ></div>\n <slot></slot>\n <button\n v-if=\"!minimal\"\n class=\"g-popover-close\"\n type=\"button\"\n aria-label=\"Close popover\"\n @click=\"hide\"\n >\n <svg\n class=\"g-popover-close-icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n </div>\n </transition>\n </Teleport>\n </div>\n</template>\n\n<style>\ng-popover:not(:defined) {\n display: inline-block;\n}\n\n.g-popover {\n h2 {\n font-size: 1.25rem;\n margin: 0 0 0.75rem 0;\n }\n p {\n margin: 0 0 0.5rem 0;\n }\n}\n.g-popover-trigger {\n display: inline-block;\n}\n.g-popover {\n position: fixed;\n z-index: 1000;\n background: var(--g-surface-0);\n border: 1px solid var(--g-surface-200);\n color: var(--g-surface-900);\n font-weight: normal;\n font-size: 1rem;\n border-radius: 4px;\n box-shadow: var(--il-shadow);\n padding: 1.5rem 1rem 1rem;\n min-width: 200px;\n max-width: 500px;\n top: 0;\n left: 0;\n text-align: left;\n}\n.g-popover.g-popover-minimal {\n padding: 0;\n min-width: 0;\n}\n\n.g-popover-arrow {\n box-sizing: border-box;\n position: absolute;\n top: -8px;\n width: 20px;\n height: 8px;\n left: 50%;\n transform: translateX(-50%);\n pointer-events: none;\n z-index: 1;\n}\n\n.g-popover-arrow::after {\n box-sizing: border-box;\n content: \"\";\n display: block;\n margin: 0 auto;\n width: 16px;\n height: 8px;\n background: transparent;\n border-left: 8px solid transparent;\n border-right: 8px solid transparent;\n border-bottom: 8px solid white;\n /* Add border for the arrow */\n position: relative;\n z-index: 2;\n}\n\n.g-popover-arrow::before {\n box-sizing: border-box;\n content: \"\";\n display: block;\n position: absolute;\n top: -1px;\n left: 1px;\n width: 18px;\n height: 9px;\n border-left: 8px solid transparent;\n border-right: 8px solid transparent;\n border-bottom: 8px solid var(--g-surface-200, #ccc);\n z-index: 1;\n}\n\n.g-popover-arrow-above {\n transform: translateX(-50%) rotate(180deg);\n}\n.g-popover-close {\n position: absolute;\n display: block;\n top: 1px;\n right: 1px;\n border: none;\n padding: 0.25rem 0.25rem;\n border-radius: 7px;\n background: transparent;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n }\n &:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n .g-popover-close-icon {\n width: 1.25rem;\n height: 1.25rem;\n display: block;\n }\n}\n\n.g-popover-expand-enter-active,\n.g-popover-expand-leave-active {\n transition:\n opacity 0.18s cubic-bezier(0.4, 0, 0.2, 1),\n transform 0.18s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.g-popover-expand-enter-from,\n.g-popover-expand-leave-to {\n opacity: 0;\n transform: scale(0.95);\n}\n\n.g-popover-expand-enter-to,\n.g-popover-expand-leave-from {\n opacity: 1;\n transform: scale(1);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-popover-expand-enter-active,\n .g-popover-expand-leave-active {\n transition: none !important;\n }\n}\n</style>\n\n","import { calculatePopoverPosition } from \"./popoverPosition.ts\";\nimport { getTopZIndex } from \"./useOverlayStack.ts\";\n\nlet tooltipIdCounter = 1;\n\nexport function nextTooltipId(prefix = \"v-gtooltip\") {\n return `${prefix}-${++tooltipIdCounter}`;\n}\n\nexport function resolveTooltipId(el: HTMLElement, prefix = \"v-gtooltip\") {\n const existing = el.getAttribute(\"aria-describedby\");\n if (existing) {\n return existing;\n }\n const id = nextTooltipId(prefix);\n el.setAttribute(\"aria-describedby\", id);\n return id;\n}\n\nexport function createTooltipEl(text: string, id: string) {\n const tooltip = document.createElement(\"div\");\n tooltip.className = \"v-gtooltip\";\n tooltip.textContent = text;\n tooltip.setAttribute(\"role\", \"tooltip\");\n tooltip.setAttribute(\"id\", id);\n return tooltip;\n}\n\nexport function appendTooltipEl(tooltip: HTMLElement) {\n const modalRoot = document.getElementById(\"modal-root\");\n (modalRoot ?? document.body).appendChild(tooltip);\n}\n\nexport function showTooltip(anchor: HTMLElement, tooltip: HTMLElement) {\n const anchorRect = anchor.getBoundingClientRect();\n const popoverRect = tooltip.getBoundingClientRect();\n const viewportRect = new DOMRect(0, 0, window.innerWidth, window.innerHeight);\n const { top, left, placedAbove } = calculatePopoverPosition(anchorRect, popoverRect, viewportRect, {\n gap: 8,\n margin: 8,\n preferAbove: true,\n });\n const anchorCenter = anchorRect.left + anchorRect.width / 2;\n const arrowX = ((anchorCenter - left) / popoverRect.width) * 100;\n tooltip.style.setProperty(\"--v-gtooltip-arrow-x\", `${arrowX}%`);\n tooltip.classList.remove(\"v-gtooltip-bottom\");\n if (!placedAbove) {\n tooltip.classList.add(\"v-gtooltip-bottom\");\n }\n tooltip.style.left = `${left}px`;\n tooltip.style.top = `${top}px`;\n tooltip.style.zIndex = `${getTopZIndex()}`;\n tooltip.style.opacity = \"1\";\n}\n\nexport function hideTooltip(tooltip: HTMLElement) {\n tooltip.style.opacity = \"0\";\n}\n","<script lang=\"ts\">\n/**\n * Tooltip for concise contextual help text.\n *\n * The `trigger` slot is optional. Without a trigger slot, the tooltip anchors\n * to the previous sibling element and can still be controlled via exposed\n * methods.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, useSlots, useTemplateRef, watch } from \"vue\";\nimport {\n appendTooltipEl,\n createTooltipEl,\n hideTooltip,\n nextTooltipId,\n resolveTooltipId,\n showTooltip,\n} from \"../compose/tooltipDom.ts\";\n\ntype Props = {\n /**\n * Tooltip text\n * @demo\n */\n text: string;\n}\n\nconst props = defineProps<Props>();\nconst emit = defineEmits([\"tooltip-hide\"]);\n\nconst slots = useSlots();\nconst hasTrigger = computed(() => !!slots.trigger);\n\nconst hostRef = useTemplateRef<HTMLElement | null>(\"hostRef\");\nconst triggerRef = useTemplateRef<HTMLElement | null>(\"triggerRef\");\nconst tooltipEl = ref<HTMLElement | null>(null);\nconst isHovered = ref(false);\nconst isFocused = ref(false);\n\nlet resizeObserver: ResizeObserver | null = null;\nlet scrollListenerActive = false;\nlet hideTimer: number | null = null;\nlet tooltipId: string | null = null;\nlet anchorEl: HTMLElement | null = null;\n\nfunction getAnchorElement() {\n if (hasTrigger.value && triggerRef.value) {\n const triggerChild = triggerRef.value.firstElementChild;\n if (triggerChild instanceof HTMLElement) {\n return triggerChild;\n }\n return triggerRef.value;\n }\n\n const host = hostRef.value;\n const previousSibling = host?.previousElementSibling ?? host?.parentElement?.previousElementSibling;\n\n if (previousSibling instanceof HTMLElement) {\n return previousSibling;\n }\n\n return null;\n}\n\nfunction ensureTooltip() {\n if (!anchorEl) {\n return;\n }\n\n if (!tooltipId) {\n tooltipId = hasTrigger.value ? nextTooltipId(\"g-tooltip\") : resolveTooltipId(anchorEl, \"g-tooltip\");\n if (hasTrigger.value) {\n anchorEl.setAttribute(\"aria-describedby\", tooltipId);\n }\n }\n\n if (!tooltipEl.value) {\n tooltipEl.value = createTooltipEl(props.text, tooltipId);\n appendTooltipEl(tooltipEl.value);\n resizeObserver = new ResizeObserver(() => {\n if (tooltipEl.value && (isHovered.value || isFocused.value) && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n });\n resizeObserver.observe(tooltipEl.value);\n }\n}\n\nfunction onScroll() {\n if (tooltipEl.value && (isHovered.value || isFocused.value) && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n}\n\nfunction show() {\n isHovered.value = true;\n}\n\nfunction hide() {\n isHovered.value = false;\n isFocused.value = false;\n}\n\nfunction toggle() {\n if (isHovered.value || isFocused.value) {\n hide();\n return;\n }\n show();\n}\n\nfunction attachAnchorEvents(nextAnchor: HTMLElement | null) {\n if (anchorEl === nextAnchor) {\n return;\n }\n\n detachAnchorEvents();\n anchorEl = nextAnchor;\n\n if (!anchorEl) {\n return;\n }\n\n anchorEl.addEventListener(\"mouseenter\", onMouseEnter);\n anchorEl.addEventListener(\"mouseleave\", onMouseLeave);\n anchorEl.addEventListener(\"focusin\", onFocus);\n anchorEl.addEventListener(\"focusout\", onBlur);\n anchorEl.addEventListener(\"keydown\", onKeyDown);\n}\n\nfunction detachAnchorEvents() {\n if (!anchorEl) {\n return;\n }\n\n anchorEl.removeEventListener(\"mouseenter\", onMouseEnter);\n anchorEl.removeEventListener(\"mouseleave\", onMouseLeave);\n anchorEl.removeEventListener(\"focusin\", onFocus);\n anchorEl.removeEventListener(\"focusout\", onBlur);\n anchorEl.removeEventListener(\"keydown\", onKeyDown);\n anchorEl = null;\n}\n\nfunction onMouseEnter() {\n isHovered.value = true;\n}\n\nfunction onMouseLeave() {\n isHovered.value = false;\n}\n\nfunction onFocus() {\n isFocused.value = true;\n}\n\nfunction onBlur() {\n isFocused.value = false;\n}\n\nfunction onKeyDown(e: KeyboardEvent) {\n if (e.key === \"Escape\" || e.key === \"Esc\") {\n isHovered.value = false;\n isFocused.value = false;\n }\n}\n\nwatch(\n () => [hostRef.value, triggerRef.value, hasTrigger.value],\n () => {\n attachAnchorEvents(getAnchorElement());\n ensureTooltip();\n },\n { immediate: true },\n);\n\nwatch(\n () => props.text,\n (value) => {\n if (tooltipEl.value) {\n tooltipEl.value.textContent = value;\n }\n },\n);\n\nwatch(\n () => isHovered.value || isFocused.value,\n (active) => {\n if (active) {\n ensureTooltip();\n if (tooltipEl.value && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n if (!scrollListenerActive) {\n window.addEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = true;\n }\n return;\n }\n\n if (scrollListenerActive) {\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = false;\n }\n if (tooltipEl.value) {\n hideTooltip(tooltipEl.value);\n if (hideTimer) {\n clearTimeout(hideTimer);\n }\n hideTimer = window.setTimeout(() => {\n emit(\"tooltip-hide\");\n }, 150);\n }\n },\n);\n\nonBeforeUnmount(() => {\n if (scrollListenerActive) {\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n }\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n if (tooltipEl.value) {\n tooltipEl.value.remove();\n tooltipEl.value = null;\n }\n if (hideTimer) {\n clearTimeout(hideTimer);\n }\n if (hasTrigger.value && anchorEl) {\n anchorEl.removeAttribute(\"aria-describedby\");\n }\n detachAnchorEvents();\n});\n\ndefineExpose({\n show,\n hide,\n toggle,\n});\n</script>\n\n<template>\n <div ref=\"hostRef\" class=\"g-tooltip-host\">\n <div v-if=\"hasTrigger\" ref=\"triggerRef\" class=\"g-tooltip-trigger\">\n <slot name=\"trigger\"></slot>\n </div>\n </div>\n</template>\n\n<style>\ng-tooltip {\n display: contents;\n}\n.g-tooltip-host {\n display: contents;\n}\n\n.g-tooltip-trigger {\n display: inline-block;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Tooltip for concise contextual help text.\n *\n * The `trigger` slot is optional. Without a trigger slot, the tooltip anchors\n * to the previous sibling element and can still be controlled via exposed\n * methods.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, useSlots, useTemplateRef, watch } from \"vue\";\nimport {\n appendTooltipEl,\n createTooltipEl,\n hideTooltip,\n nextTooltipId,\n resolveTooltipId,\n showTooltip,\n} from \"../compose/tooltipDom.ts\";\n\ntype Props = {\n /**\n * Tooltip text\n * @demo\n */\n text: string;\n}\n\nconst props = defineProps<Props>();\nconst emit = defineEmits([\"tooltip-hide\"]);\n\nconst slots = useSlots();\nconst hasTrigger = computed(() => !!slots.trigger);\n\nconst hostRef = useTemplateRef<HTMLElement | null>(\"hostRef\");\nconst triggerRef = useTemplateRef<HTMLElement | null>(\"triggerRef\");\nconst tooltipEl = ref<HTMLElement | null>(null);\nconst isHovered = ref(false);\nconst isFocused = ref(false);\n\nlet resizeObserver: ResizeObserver | null = null;\nlet scrollListenerActive = false;\nlet hideTimer: number | null = null;\nlet tooltipId: string | null = null;\nlet anchorEl: HTMLElement | null = null;\n\nfunction getAnchorElement() {\n if (hasTrigger.value && triggerRef.value) {\n const triggerChild = triggerRef.value.firstElementChild;\n if (triggerChild instanceof HTMLElement) {\n return triggerChild;\n }\n return triggerRef.value;\n }\n\n const host = hostRef.value;\n const previousSibling = host?.previousElementSibling ?? host?.parentElement?.previousElementSibling;\n\n if (previousSibling instanceof HTMLElement) {\n return previousSibling;\n }\n\n return null;\n}\n\nfunction ensureTooltip() {\n if (!anchorEl) {\n return;\n }\n\n if (!tooltipId) {\n tooltipId = hasTrigger.value ? nextTooltipId(\"g-tooltip\") : resolveTooltipId(anchorEl, \"g-tooltip\");\n if (hasTrigger.value) {\n anchorEl.setAttribute(\"aria-describedby\", tooltipId);\n }\n }\n\n if (!tooltipEl.value) {\n tooltipEl.value = createTooltipEl(props.text, tooltipId);\n appendTooltipEl(tooltipEl.value);\n resizeObserver = new ResizeObserver(() => {\n if (tooltipEl.value && (isHovered.value || isFocused.value) && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n });\n resizeObserver.observe(tooltipEl.value);\n }\n}\n\nfunction onScroll() {\n if (tooltipEl.value && (isHovered.value || isFocused.value) && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n}\n\nfunction show() {\n isHovered.value = true;\n}\n\nfunction hide() {\n isHovered.value = false;\n isFocused.value = false;\n}\n\nfunction toggle() {\n if (isHovered.value || isFocused.value) {\n hide();\n return;\n }\n show();\n}\n\nfunction attachAnchorEvents(nextAnchor: HTMLElement | null) {\n if (anchorEl === nextAnchor) {\n return;\n }\n\n detachAnchorEvents();\n anchorEl = nextAnchor;\n\n if (!anchorEl) {\n return;\n }\n\n anchorEl.addEventListener(\"mouseenter\", onMouseEnter);\n anchorEl.addEventListener(\"mouseleave\", onMouseLeave);\n anchorEl.addEventListener(\"focusin\", onFocus);\n anchorEl.addEventListener(\"focusout\", onBlur);\n anchorEl.addEventListener(\"keydown\", onKeyDown);\n}\n\nfunction detachAnchorEvents() {\n if (!anchorEl) {\n return;\n }\n\n anchorEl.removeEventListener(\"mouseenter\", onMouseEnter);\n anchorEl.removeEventListener(\"mouseleave\", onMouseLeave);\n anchorEl.removeEventListener(\"focusin\", onFocus);\n anchorEl.removeEventListener(\"focusout\", onBlur);\n anchorEl.removeEventListener(\"keydown\", onKeyDown);\n anchorEl = null;\n}\n\nfunction onMouseEnter() {\n isHovered.value = true;\n}\n\nfunction onMouseLeave() {\n isHovered.value = false;\n}\n\nfunction onFocus() {\n isFocused.value = true;\n}\n\nfunction onBlur() {\n isFocused.value = false;\n}\n\nfunction onKeyDown(e: KeyboardEvent) {\n if (e.key === \"Escape\" || e.key === \"Esc\") {\n isHovered.value = false;\n isFocused.value = false;\n }\n}\n\nwatch(\n () => [hostRef.value, triggerRef.value, hasTrigger.value],\n () => {\n attachAnchorEvents(getAnchorElement());\n ensureTooltip();\n },\n { immediate: true },\n);\n\nwatch(\n () => props.text,\n (value) => {\n if (tooltipEl.value) {\n tooltipEl.value.textContent = value;\n }\n },\n);\n\nwatch(\n () => isHovered.value || isFocused.value,\n (active) => {\n if (active) {\n ensureTooltip();\n if (tooltipEl.value && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n if (!scrollListenerActive) {\n window.addEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = true;\n }\n return;\n }\n\n if (scrollListenerActive) {\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = false;\n }\n if (tooltipEl.value) {\n hideTooltip(tooltipEl.value);\n if (hideTimer) {\n clearTimeout(hideTimer);\n }\n hideTimer = window.setTimeout(() => {\n emit(\"tooltip-hide\");\n }, 150);\n }\n },\n);\n\nonBeforeUnmount(() => {\n if (scrollListenerActive) {\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n }\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n if (tooltipEl.value) {\n tooltipEl.value.remove();\n tooltipEl.value = null;\n }\n if (hideTimer) {\n clearTimeout(hideTimer);\n }\n if (hasTrigger.value && anchorEl) {\n anchorEl.removeAttribute(\"aria-describedby\");\n }\n detachAnchorEvents();\n});\n\ndefineExpose({\n show,\n hide,\n toggle,\n});\n</script>\n\n<template>\n <div ref=\"hostRef\" class=\"g-tooltip-host\">\n <div v-if=\"hasTrigger\" ref=\"triggerRef\" class=\"g-tooltip-trigger\">\n <slot name=\"trigger\"></slot>\n </div>\n </div>\n</template>\n\n<style>\ng-tooltip {\n display: contents;\n}\n.g-tooltip-host {\n display: contents;\n}\n\n.g-tooltip-trigger {\n display: inline-block;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This component is just a radio button group with special styling.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Use the `options` prop to provide a list of choices. Each option can\n * be a string or an object with `label` and `value` properties.\n *\n * In addition to `v-model`, a `change` event is emitted when the\n * option changes from user interaction.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, useId, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype OptionType = {\n label: string;\n value: string | number;\n}\n\ntype Props = {\n /**\n * List of options to select from\n */\n options: Array<string | OptionType>;\n /**\n * Accessible label\n * @demo Select Option\n */\n label: string;\n /**\n * Size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\";\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n name: undefined,\n disabled: false,\n required: false,\n errors: () => [],\n formKey: undefined,\n});\n\nconst emit = defineEmits([\"change\"]);\nconst model = defineModel<string | number>({default: () => \"\"});\n\nconst baseId = useId();\n\n// Use form field composable for form registration and error handling\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst normalizedOptions = computed(() => {\n return props.options.map((opt) => {\n if (typeof opt === \"string\") {\n return { label: opt, value: opt };\n } else {\n return opt;\n }\n });\n});\n\nconst groupClasses = computed(() => [\n \"g-select-btn-group\",\n `g-select-btn-group--${props.size}`,\n]);\n\nconst getBtnClasses = (selected: boolean) => [\n \"g-select-btn\",\n selected ? \"g-select-btn--selected\" : \"\",\n { \"g-select-btn--disabled\": props.disabled },\n];\n\nfunction onChange(val: string | number) {\n if (!props.disabled && val !== model.value) {\n model.value = val;\n emit(\"change\", val);\n }\n}\n</script>\n\n<template>\n <fieldset :class=\"groupClasses\" :disabled=\"props.disabled\">\n <legend class=\"g-select-btn-legend\">\n {{ props.label }}<span v-if=\"props.required\" class=\"g-select-btn-required\" aria-hidden=\"true\"> *</span>\n </legend>\n <div class=\"g-select-btn-wrapper\" :class=\"{ 'g-select-btn-has-error': hasErrors }\">\n <div class=\"g-select-btn-row\">\n <template\n v-for=\"(option, idx) in normalizedOptions\"\n :key=\"option.value\"\n >\n <input\n class=\"g-select-btn-radio\"\n type=\"radio\"\n :id=\"`${baseId}-${option.value}`\"\n :name=\"props.name || baseId\"\n :value=\"option.value\"\n :checked=\"option.value === model\"\n :disabled=\"props.disabled\"\n :required=\"props.required && idx === 0\"\n @change=\"onChange(option.value)\"\n />\n <label\n :for=\"`${baseId}-${option.value}`\"\n :class=\"getBtnClasses(option.value === model)\"\n >\n {{ option.label }}\n </label>\n </template>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + baseId\"\n />\n </div>\n </fieldset>\n</template>\n\n<style>\ng-select-button {\n display: block;\n}\n.g-select-btn-group {\n border: none;\n margin: 0;\n padding: 0;\n min-width: 0;\n border-radius: 4px;\n}\n\n.g-select-btn-legend {\n position: static;\n display: block;\n margin: 0 0 0.5rem 0;\n padding: 0;\n font-weight: 700;\n color: var(--g-surface-900);\n}\n\n.g-select-btn-required {\n color: var(--g-danger-600);\n}\n\n.g-select-btn-row {\n display: flex;\n align-items: stretch;\n border-radius: var(--g-border-radius-m);\n}\n\n.g-select-btn-row:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n background: var(--ilw-color--focus--background);\n box-shadow: 0 0 0 2px var(--ilw-color--focus--background);\n}\n\n.g-select-btn-group--small {\n font-size: 0.875rem;\n}\n\n.g-select-btn-group--medium {\n font-size: 1rem;\n}\n\n.g-select-btn-group--large {\n font-size: 1.125rem;\n}\n\n.g-select-btn-radio {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n width: 0;\n height: 0;\n margin: 0;\n}\n\n.g-select-btn {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--g-primary-500);\n border-left-width: 1px;\n border-right-width: 1px;\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n font-weight: 700;\n padding: 0.5em;\n cursor: pointer;\n outline: none;\n &:hover {\n text-decoration: underline;\n }\n}\n.g-select-btn--selected {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n}\n.g-select-btn:first-of-type {\n border-top-left-radius: var(--g-border-radius-m);\n border-bottom-left-radius: var(--g-border-radius-m);\n border-left-width: 2px;\n}\n.g-select-btn:last-of-type {\n border-top-right-radius: var(--g-border-radius-m);\n border-bottom-right-radius: var(--g-border-radius-m);\n border-right-width: 2px;\n}\n\n.g-select-btn-has-error .g-select-btn-row {\n border: 2px solid var(--g-danger-600);\n border-radius: var(--g-border-radius-m);\n}\n\n.g-select-btn-has-error .g-select-btn {\n background: var(--g-danger-100);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This component is just a radio button group with special styling.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Use the `options` prop to provide a list of choices. Each option can\n * be a string or an object with `label` and `value` properties.\n *\n * In addition to `v-model`, a `change` event is emitted when the\n * option changes from user interaction.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, useId, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype OptionType = {\n label: string;\n value: string | number;\n}\n\ntype Props = {\n /**\n * List of options to select from\n */\n options: Array<string | OptionType>;\n /**\n * Accessible label\n * @demo Select Option\n */\n label: string;\n /**\n * Size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\";\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n name: undefined,\n disabled: false,\n required: false,\n errors: () => [],\n formKey: undefined,\n});\n\nconst emit = defineEmits([\"change\"]);\nconst model = defineModel<string | number>({default: () => \"\"});\n\nconst baseId = useId();\n\n// Use form field composable for form registration and error handling\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst normalizedOptions = computed(() => {\n return props.options.map((opt) => {\n if (typeof opt === \"string\") {\n return { label: opt, value: opt };\n } else {\n return opt;\n }\n });\n});\n\nconst groupClasses = computed(() => [\n \"g-select-btn-group\",\n `g-select-btn-group--${props.size}`,\n]);\n\nconst getBtnClasses = (selected: boolean) => [\n \"g-select-btn\",\n selected ? \"g-select-btn--selected\" : \"\",\n { \"g-select-btn--disabled\": props.disabled },\n];\n\nfunction onChange(val: string | number) {\n if (!props.disabled && val !== model.value) {\n model.value = val;\n emit(\"change\", val);\n }\n}\n</script>\n\n<template>\n <fieldset :class=\"groupClasses\" :disabled=\"props.disabled\">\n <legend class=\"g-select-btn-legend\">\n {{ props.label }}<span v-if=\"props.required\" class=\"g-select-btn-required\" aria-hidden=\"true\"> *</span>\n </legend>\n <div class=\"g-select-btn-wrapper\" :class=\"{ 'g-select-btn-has-error': hasErrors }\">\n <div class=\"g-select-btn-row\">\n <template\n v-for=\"(option, idx) in normalizedOptions\"\n :key=\"option.value\"\n >\n <input\n class=\"g-select-btn-radio\"\n type=\"radio\"\n :id=\"`${baseId}-${option.value}`\"\n :name=\"props.name || baseId\"\n :value=\"option.value\"\n :checked=\"option.value === model\"\n :disabled=\"props.disabled\"\n :required=\"props.required && idx === 0\"\n @change=\"onChange(option.value)\"\n />\n <label\n :for=\"`${baseId}-${option.value}`\"\n :class=\"getBtnClasses(option.value === model)\"\n >\n {{ option.label }}\n </label>\n </template>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + baseId\"\n />\n </div>\n </fieldset>\n</template>\n\n<style>\ng-select-button {\n display: block;\n}\n.g-select-btn-group {\n border: none;\n margin: 0;\n padding: 0;\n min-width: 0;\n border-radius: 4px;\n}\n\n.g-select-btn-legend {\n position: static;\n display: block;\n margin: 0 0 0.5rem 0;\n padding: 0;\n font-weight: 700;\n color: var(--g-surface-900);\n}\n\n.g-select-btn-required {\n color: var(--g-danger-600);\n}\n\n.g-select-btn-row {\n display: flex;\n align-items: stretch;\n border-radius: var(--g-border-radius-m);\n}\n\n.g-select-btn-row:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n background: var(--ilw-color--focus--background);\n box-shadow: 0 0 0 2px var(--ilw-color--focus--background);\n}\n\n.g-select-btn-group--small {\n font-size: 0.875rem;\n}\n\n.g-select-btn-group--medium {\n font-size: 1rem;\n}\n\n.g-select-btn-group--large {\n font-size: 1.125rem;\n}\n\n.g-select-btn-radio {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n width: 0;\n height: 0;\n margin: 0;\n}\n\n.g-select-btn {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--g-primary-500);\n border-left-width: 1px;\n border-right-width: 1px;\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n font-weight: 700;\n padding: 0.5em;\n cursor: pointer;\n outline: none;\n &:hover {\n text-decoration: underline;\n }\n}\n.g-select-btn--selected {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n}\n.g-select-btn:first-of-type {\n border-top-left-radius: var(--g-border-radius-m);\n border-bottom-left-radius: var(--g-border-radius-m);\n border-left-width: 2px;\n}\n.g-select-btn:last-of-type {\n border-top-right-radius: var(--g-border-radius-m);\n border-bottom-right-radius: var(--g-border-radius-m);\n border-right-width: 2px;\n}\n\n.g-select-btn-has-error .g-select-btn-row {\n border: 2px solid var(--g-danger-600);\n border-radius: var(--g-border-radius-m);\n}\n\n.g-select-btn-has-error .g-select-btn {\n background: var(--g-danger-100);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A component that can show progress from 1 to 100 or an indeterminate spinner.\n * If a value is omitted, the progress will be indeterminate.\n *\n * If no `label` is specified, the default accessible label will be \"Loading\".\n *\n * If a value is provided, the element will have the ARIA role `progressbar`\n * with the appropriate ARIA value attributes.\n *\n * If no value is provided, the element will have the ARIA role `status`.\n *\n * > [!NOTE]\n * > This element announces to assistive technologies, so make sure to\n * > limit its use to cases where that's appropriate.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Progress 1-100 or blank\n * @demo\n */\n value?: number;\n /**\n * Progress circle size\n * @demo\n */\n size?: \"tiny\" | \"small\" | \"medium\" | \"large\";\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n value: undefined,\n label: \"Loading\",\n});\n\nconst isDeterminate = computed(\n () =>\n props.value &&\n props.value >= 1 &&\n props.value <= 100,\n);\nconst radius = computed(() => {\n switch (props.size) {\n case \"tiny\":\n return 9;\n case \"small\":\n return 12;\n case \"large\":\n return 24;\n default:\n return 18;\n }\n});\nconst stroke = 4;\nconst circumference = computed(() => 2 * Math.PI * radius.value);\nconst progress = computed(() =>\n isDeterminate.value ? (props.value! / 100) * circumference.value : 0,\n);\n\nconst ariaProps = computed(() =>\n isDeterminate.value\n ? {\n role: \"progressbar\",\n \"aria-valuenow\": props.value,\n \"aria-valuemin\": 1,\n \"aria-valuemax\": 100,\n \"aria-label\": props.label,\n }\n : {\n \"role\": \"status\",\n \"aria-label\": props.label },\n);\n</script>\n\n<template>\n <span class=\"g-progress\" v-bind=\"ariaProps\">\n <svg\n :width=\"radius * 2 + stroke\"\n :height=\"radius * 2 + stroke\"\n :class=\"[\n 'g-progress__svg',\n {\n 'g-progress--determinate': isDeterminate,\n 'g-progress--indeterminate': !isDeterminate,\n },\n ]\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <circle\n class=\"g-progress__track\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n />\n <circle\n v-if=\"isDeterminate\"\n class=\"g-progress__value\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"circumference - progress\"\n style=\"transform: rotate(-90deg); transform-origin: center\"\n />\n <circle\n v-else\n class=\"g-progress__spinner\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n />\n </svg>\n </span>\n</template>\n\n<style>\ng-progress:not(:defined) {\n display: inline-block;\n vertical-align: middle;\n}\n.g-progress {\n display: inline-block;\n vertical-align: middle;\n}\n\n.g-progress__svg {\n display: block;\n}\n.g-progress__track {\n stroke: var(--g-surface-200);\n}\n.g-progress__value {\n stroke: var(--g-primary-500);\n transition: stroke-dashoffset 0.2s linear;\n}\n.g-progress__spinner {\n animation: g-progress-spin 1s linear infinite;\n transform-box: fill-box;\n transform-origin: center;\n}\n.g-progress__spinner {\n stroke: var(--g-primary-500);\n stroke-dasharray: 40 80;\n}\n@media (prefers-reduced-motion: reduce) {\n .g-progress__spinner {\n animation: g-progress-spin-blink 1s both infinite;\n }\n}\n@keyframes g-progress-spin {\n 100% {\n transform: rotate(360deg);\n }\n}\n@keyframes g-progress-spin-blink {\n 0%,\n 100% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A component that can show progress from 1 to 100 or an indeterminate spinner.\n * If a value is omitted, the progress will be indeterminate.\n *\n * If no `label` is specified, the default accessible label will be \"Loading\".\n *\n * If a value is provided, the element will have the ARIA role `progressbar`\n * with the appropriate ARIA value attributes.\n *\n * If no value is provided, the element will have the ARIA role `status`.\n *\n * > [!NOTE]\n * > This element announces to assistive technologies, so make sure to\n * > limit its use to cases where that's appropriate.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Progress 1-100 or blank\n * @demo\n */\n value?: number;\n /**\n * Progress circle size\n * @demo\n */\n size?: \"tiny\" | \"small\" | \"medium\" | \"large\";\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n value: undefined,\n label: \"Loading\",\n});\n\nconst isDeterminate = computed(\n () =>\n props.value &&\n props.value >= 1 &&\n props.value <= 100,\n);\nconst radius = computed(() => {\n switch (props.size) {\n case \"tiny\":\n return 9;\n case \"small\":\n return 12;\n case \"large\":\n return 24;\n default:\n return 18;\n }\n});\nconst stroke = 4;\nconst circumference = computed(() => 2 * Math.PI * radius.value);\nconst progress = computed(() =>\n isDeterminate.value ? (props.value! / 100) * circumference.value : 0,\n);\n\nconst ariaProps = computed(() =>\n isDeterminate.value\n ? {\n role: \"progressbar\",\n \"aria-valuenow\": props.value,\n \"aria-valuemin\": 1,\n \"aria-valuemax\": 100,\n \"aria-label\": props.label,\n }\n : {\n \"role\": \"status\",\n \"aria-label\": props.label },\n);\n</script>\n\n<template>\n <span class=\"g-progress\" v-bind=\"ariaProps\">\n <svg\n :width=\"radius * 2 + stroke\"\n :height=\"radius * 2 + stroke\"\n :class=\"[\n 'g-progress__svg',\n {\n 'g-progress--determinate': isDeterminate,\n 'g-progress--indeterminate': !isDeterminate,\n },\n ]\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <circle\n class=\"g-progress__track\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n />\n <circle\n v-if=\"isDeterminate\"\n class=\"g-progress__value\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"circumference - progress\"\n style=\"transform: rotate(-90deg); transform-origin: center\"\n />\n <circle\n v-else\n class=\"g-progress__spinner\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n />\n </svg>\n </span>\n</template>\n\n<style>\ng-progress:not(:defined) {\n display: inline-block;\n vertical-align: middle;\n}\n.g-progress {\n display: inline-block;\n vertical-align: middle;\n}\n\n.g-progress__svg {\n display: block;\n}\n.g-progress__track {\n stroke: var(--g-surface-200);\n}\n.g-progress__value {\n stroke: var(--g-primary-500);\n transition: stroke-dashoffset 0.2s linear;\n}\n.g-progress__spinner {\n animation: g-progress-spin 1s linear infinite;\n transform-box: fill-box;\n transform-origin: center;\n}\n.g-progress__spinner {\n stroke: var(--g-primary-500);\n stroke-dasharray: 40 80;\n}\n@media (prefers-reduced-motion: reduce) {\n .g-progress__spinner {\n animation: g-progress-spin-blink 1s both infinite;\n }\n}\n@keyframes g-progress-spin {\n 100% {\n transform: rotate(360deg);\n }\n}\n@keyframes g-progress-spin-blink {\n 0%,\n 100% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Alert dialog for confirming or canceling actions.\n *\n * Clicking on the outside or pressing the escape key will close the dialog\n * and that counts as canceling.\n *\n * > [!IMPORTANT]\n * >\n * > The surrounding page **must** have an element with the id `modal-root`,\n * > this dialog will be teleported to it, so it can properly be over all\n * > other content. The `modal-root` should be somewhere near the end of the\n * > page structure.\n *\n * **Slot** `default` is used as the content of the alert, and also becomes\n * the ARIA description of the alert.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { onBeforeMount, onMounted, ref, useId } from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Dialog label\n * @demo\n */\n label?: string;\n /**\n * Accept button text\n * @demo\n */\n buttonText?: string;\n /**\n * Accept button color\n * @demo\n */\n buttonColor?: \"primary\" | \"secondary\" | \"danger\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"Confirmation\",\n buttonText: \"Continue\",\n buttonColor: \"primary\",\n});\n\nconst emit = defineEmits([\"cancel\", \"confirm\"]);\n\nconst dialog = ref<HTMLElement | null>(null);\nconst open = ref(true);\n\nconst id = useId();\nconst { pop, push, isTop, zIndex } = useOverlayStack(id, true, true);\n\nconst { deactivate, activate } = useOverlayFocus(dialog, isTop);\n\nfunction close() {\n emit(\"cancel\");\n}\n\nuseOverlayEscape([dialog], isTop, open, close, pop);\n\nonMounted(() => {\n push();\n activate();\n});\n\nonBeforeMount(() => {\n pop();\n deactivate();\n});\n</script>\n\n<template>\n <Teleport to=\"#modal-root\">\n <Transition name=\"g-fade\" appear>\n <div\n :id=\"'alertdialog-' + id\"\n class=\"g-alertdialog\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"'alertdialog-label-' + id\"\n :aria-describedby=\"'alertdialog-description-' + id\"\n ref=\"dialog\"\n :style=\"{ zIndex }\"\n >\n <div class=\"g-alertdialog-inner\">\n <h2\n :id=\"'alertdialog-label-' + id\"\n class=\"g-alertdialog-label\"\n >\n {{ props.label }}\n </h2>\n <div\n :id=\"'alertdialog-description-' + id\"\n class=\"g-alertdialog-content\"\n >\n <slot />\n </div>\n <div class=\"g-alertdialog-actions\">\n <GButton outlined @click=\"emit('cancel')\"\n >Cancel</GButton\n >\n <GButton\n :theme=\"props.buttonColor\"\n @click=\"emit('confirm')\"\n >{{ props.buttonText }}</GButton\n >\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n.g-alertdialog {\n position: fixed;\n left: 50vw;\n top: 50vh;\n transform: translate(-50%, -50%);\n max-width: 400px;\n min-width: 300px;\n height: auto;\n max-height: 90vh;\n overflow-y: auto;\n background: var(--g-surface-50);\n border-top: 8px solid var(--g-accent-500);\n padding: 2rem;\n box-shadow:\n 0 0 2px rgba(0, 0, 0, 0.4),\n 0 10px 20px rgba(0, 0, 0, 0.1);\n}\n.g-alertdialog-label {\n font-family: var(--il-font-heading);\n font-size: 2rem;\n margin-top: 0;\n color: var(--g-primary-500);\n}\n.g-alertdialog-content {\n margin-top: 1rem;\n font-size: 1.125rem;\n color: var(--g-surface-900);\n}\n.g-alertdialog-actions {\n margin-top: 2rem;\n display: flex;\n justify-content: flex-end;\n gap: 1rem;\n}\n\n.fade-enter-active,\n.fade-leave-active {\n transition: opacity 0.2s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n opacity: 0;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Alert dialog for confirming or canceling actions.\n *\n * Clicking on the outside or pressing the escape key will close the dialog\n * and that counts as canceling.\n *\n * > [!IMPORTANT]\n * >\n * > The surrounding page **must** have an element with the id `modal-root`,\n * > this dialog will be teleported to it, so it can properly be over all\n * > other content. The `modal-root` should be somewhere near the end of the\n * > page structure.\n *\n * **Slot** `default` is used as the content of the alert, and also becomes\n * the ARIA description of the alert.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { onBeforeMount, onMounted, ref, useId } from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Dialog label\n * @demo\n */\n label?: string;\n /**\n * Accept button text\n * @demo\n */\n buttonText?: string;\n /**\n * Accept button color\n * @demo\n */\n buttonColor?: \"primary\" | \"secondary\" | \"danger\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"Confirmation\",\n buttonText: \"Continue\",\n buttonColor: \"primary\",\n});\n\nconst emit = defineEmits([\"cancel\", \"confirm\"]);\n\nconst dialog = ref<HTMLElement | null>(null);\nconst open = ref(true);\n\nconst id = useId();\nconst { pop, push, isTop, zIndex } = useOverlayStack(id, true, true);\n\nconst { deactivate, activate } = useOverlayFocus(dialog, isTop);\n\nfunction close() {\n emit(\"cancel\");\n}\n\nuseOverlayEscape([dialog], isTop, open, close, pop);\n\nonMounted(() => {\n push();\n activate();\n});\n\nonBeforeMount(() => {\n pop();\n deactivate();\n});\n</script>\n\n<template>\n <Teleport to=\"#modal-root\">\n <Transition name=\"g-fade\" appear>\n <div\n :id=\"'alertdialog-' + id\"\n class=\"g-alertdialog\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"'alertdialog-label-' + id\"\n :aria-describedby=\"'alertdialog-description-' + id\"\n ref=\"dialog\"\n :style=\"{ zIndex }\"\n >\n <div class=\"g-alertdialog-inner\">\n <h2\n :id=\"'alertdialog-label-' + id\"\n class=\"g-alertdialog-label\"\n >\n {{ props.label }}\n </h2>\n <div\n :id=\"'alertdialog-description-' + id\"\n class=\"g-alertdialog-content\"\n >\n <slot />\n </div>\n <div class=\"g-alertdialog-actions\">\n <GButton outlined @click=\"emit('cancel')\"\n >Cancel</GButton\n >\n <GButton\n :theme=\"props.buttonColor\"\n @click=\"emit('confirm')\"\n >{{ props.buttonText }}</GButton\n >\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n.g-alertdialog {\n position: fixed;\n left: 50vw;\n top: 50vh;\n transform: translate(-50%, -50%);\n max-width: 400px;\n min-width: 300px;\n height: auto;\n max-height: 90vh;\n overflow-y: auto;\n background: var(--g-surface-50);\n border-top: 8px solid var(--g-accent-500);\n padding: 2rem;\n box-shadow:\n 0 0 2px rgba(0, 0, 0, 0.4),\n 0 10px 20px rgba(0, 0, 0, 0.1);\n}\n.g-alertdialog-label {\n font-family: var(--il-font-heading);\n font-size: 2rem;\n margin-top: 0;\n color: var(--g-primary-500);\n}\n.g-alertdialog-content {\n margin-top: 1rem;\n font-size: 1.125rem;\n color: var(--g-surface-900);\n}\n.g-alertdialog-actions {\n margin-top: 2rem;\n display: flex;\n justify-content: flex-end;\n gap: 1rem;\n}\n\n.fade-enter-active,\n.fade-leave-active {\n transition: opacity 0.2s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n opacity: 0;\n}\n</style>\n","import { computed, nextTick, onBeforeUnmount, ref, watch, type Ref } from \"vue\";\nimport { useOverlayStack } from \"./useOverlayStack.ts\";\n\n/**\n * A normalized select option with a label and a value.\n * Both GSelect and GMultiSelect accept `Array<string | SelectOption>` and\n * normalize the strings to this shape internally.\n */\nexport type SelectOption = {\n label: string;\n value: string | number;\n};\n\n/**\n * Normalizes a mixed `Array<string | SelectOption>` into a uniform\n * `SelectOption[]`. Strings are converted to `{ label: s, value: s }`.\n */\nexport function normalizeSelectOptions(\n options: Array<string | SelectOption>,\n): SelectOption[] {\n return options.map((opt) =>\n typeof opt === \"string\" ? { label: opt, value: opt } : opt,\n );\n}\n\nexport interface UseSelectDropdownOptions {\n /** Reactive flag that is `true` when the dropdown is visible. */\n open: Ref<boolean>;\n /**\n * The element whose bounding rect is used to decide whether the dropdown\n * opens above or below (the trigger / combobox control).\n */\n anchorRef: Ref<HTMLElement | null>;\n /** The listbox element – used to measure its natural scroll-height. */\n listboxRef: Ref<HTMLElement | null>;\n /**\n * A stable unique id that doubles as the overlay-stack entry and the\n * prefix for option element ids (`${baseId}-option-${index}`).\n */\n baseId: string;\n /** The currently-highlighted option index, used by scrollOptionIntoView. */\n activeIndex: Ref<number>;\n}\n\nexport interface UseSelectDropdownReturn {\n /** Current placement of the dropdown relative to the anchor. */\n menuPlacement: Ref<\"below\" | \"above\">;\n /** Inline styles to apply to the listbox element. */\n menuStyle: Readonly<Ref<Record<string, string>>>;\n /**\n * Whether this overlay is topmost in the global stack.\n * Use this to guard Escape-key handling.\n */\n isTop: Ref<boolean>;\n /** Scrolls the currently-active option into view. */\n scrollOptionIntoView: () => void;\n}\n\n/**\n * Shared dropdown behaviour for GSelect and GMultiSelect.\n *\n * Manages:\n * - Menu placement (above / below) and max-height constraints\n * - window resize / scroll listeners (added when open, removed when closed)\n * - Overlay-stack registration (push on open, pop on close / unmount)\n * - Scrolling the active option into view\n */\nexport function useSelectDropdown({\n open,\n anchorRef,\n listboxRef,\n baseId,\n activeIndex,\n}: UseSelectDropdownOptions): UseSelectDropdownReturn {\n const { push, pop, isTop } = useOverlayStack(baseId);\n\n const menuPlacement = ref<\"below\" | \"above\">(\"below\");\n const menuMaxHeight = ref<number | null>(null);\n\n const menuStyle = computed(() => {\n const style: Record<string, string> = {};\n if (menuMaxHeight.value !== null) {\n style.maxHeight = `${menuMaxHeight.value}px`;\n }\n if (menuPlacement.value === \"above\") {\n style.top = \"auto\";\n style.bottom = \"100%\";\n } else {\n style.top = \"100%\";\n style.bottom = \"auto\";\n }\n return style;\n });\n\n function updateMenuPlacement() {\n if (!open.value || !anchorRef.value) return;\n const rect = anchorRef.value.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const spaceAbove = rect.top;\n const listboxFullHeight = listboxRef.value?.scrollHeight ?? 200;\n const minSpaceToOpenBelow = Math.min(200, listboxFullHeight);\n const gap = 8;\n\n if (spaceBelow >= minSpaceToOpenBelow) {\n menuPlacement.value = \"below\";\n menuMaxHeight.value = Math.max(0, Math.floor(spaceBelow - gap));\n } else if (spaceAbove > spaceBelow) {\n menuPlacement.value = \"above\";\n menuMaxHeight.value = Math.max(0, Math.floor(spaceAbove - gap));\n } else {\n menuPlacement.value = \"below\";\n menuMaxHeight.value = Math.max(0, Math.floor(spaceBelow - gap));\n }\n }\n\n let removeWindowListeners: (() => void) | null = null;\n\n function addWindowListeners() {\n if (removeWindowListeners) return;\n const onChange = () => updateMenuPlacement();\n window.addEventListener(\"resize\", onChange, { passive: true });\n window.addEventListener(\"scroll\", onChange, {\n passive: true,\n capture: true,\n });\n removeWindowListeners = () => {\n window.removeEventListener(\"resize\", onChange);\n window.removeEventListener(\"scroll\", onChange, true);\n removeWindowListeners = null;\n };\n }\n\n function removeListeners() {\n if (removeWindowListeners) removeWindowListeners();\n }\n\n watch(open, (val) => {\n if (val) {\n push();\n addWindowListeners();\n nextTick(() => updateMenuPlacement());\n } else {\n pop();\n removeListeners();\n menuPlacement.value = \"below\";\n menuMaxHeight.value = null;\n }\n });\n\n // useOverlayStack already registers onBeforeUnmount(pop); we only need\n // to ensure window listeners are cleaned up if the component is destroyed\n // while the dropdown is still open.\n onBeforeUnmount(() => {\n removeListeners();\n });\n\n function scrollOptionIntoView() {\n nextTick(() => {\n const el = document.getElementById(\n `${baseId}-option-${activeIndex.value}`,\n );\n if (el) el.scrollIntoView({ block: \"nearest\" });\n });\n }\n\n return {\n menuPlacement,\n menuStyle,\n isTop,\n scrollOptionIntoView,\n };\n}\n","<script lang=\"ts\">\n/**\n * By default, this component behaves like a normal select element with\n * custom styling.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * The component can be marked `searchable` to enable search functionality.\n * This turns it into a text input that filters the options. Filtering is\n * done with a simple lower-case string search.\n *\n * The `options` prop can be an array of strings or objects with `label`\n * and `value` properties.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, ref, useId, watch, toRef } from \"vue\";\nimport { useSelectDropdown, normalizeSelectOptions, type SelectOption } from \"../compose/useSelectDropdown.ts\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype Props = {\n /**\n * List of options to choose from\n */\n options: Array<string | SelectOption>;\n /**\n * Accessible label\n * @demo Select Option\n */\n label: string;\n /**\n * Hide the label visually\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Placeholder\n *\n * Only used if the component is searchable.\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Searchable\n * @demo\n */\n searchable?: boolean;\n /**\n * Show clear button\n * @demo\n */\n clearButton?: boolean;\n /**\n * Compact\n * @demo\n */\n compact?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n required: false,\n searchable: false,\n compact: false,\n errors: () => [],\n});\nconst emit = defineEmits([\"change\"]);\nconst model = defineModel<string | number | null>();\n\nconst baseId = useId();\nconst comboRef = ref<HTMLElement | null>(null);\nconst listboxRef = ref<HTMLElement | null>(null);\nconst comboInputRef = ref<HTMLInputElement | null>(null);\nconst open = ref(false);\nconst activeIndex = ref(0);\nconst ignoreBlur = ref(false);\nconst ignoreFocus = ref(false);\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst { menuPlacement, menuStyle, isTop, scrollOptionIntoView } = useSelectDropdown({\n open,\n anchorRef: comboRef,\n listboxRef,\n baseId,\n activeIndex,\n});\n\nconst normalizedOptions = computed(() => normalizeSelectOptions(props.options));\n\nconst searchQuery = ref(\"\");\n\nconst filteredOptions = computed(() => {\n if (!props.searchable || !open.value || !searchQuery.value) {\n return normalizedOptions.value;\n }\n const q = searchQuery.value.toLowerCase();\n return normalizedOptions.value.filter((opt) =>\n opt.label.toLowerCase().includes(q),\n );\n});\n\nconst selectedIndex = computed(() => {\n return filteredOptions.value.findIndex((opt) => opt.value === model.value);\n});\n\nwatch(\n () => model.value,\n (val) => {\n const idx = filteredOptions.value.findIndex((opt) => opt.value === val);\n if (idx !== -1) {\n activeIndex.value = idx;\n }\n },\n);\n\nfunction openMenu() {\n if (props.disabled) {\n return;\n }\n open.value = true;\n if (props.searchable) {\n searchQuery.value = \"\";\n // If a value is selected, highlight it in filtered list\n const idx = filteredOptions.value.findIndex(\n (opt) => opt.value === model.value,\n );\n activeIndex.value = idx !== -1 ? idx : 0;\n nextTick(() => {\n if (comboInputRef.value) {\n comboInputRef.value.focus();\n }\n });\n }\n}\n\nfunction closeMenu() {\n open.value = false;\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n}\n\nfunction onComboFocus(e: FocusEvent) {\n if (props.disabled) {\n return;\n }\n if (props.searchable) {\n if (ignoreFocus.value) {\n ignoreFocus.value = false;\n return;\n }\n openMenu();\n }\n}\n\nfunction onComboInput(e: Event) {\n if (!props.searchable) return;\n // If closed and user types, open and start search\n if (!open.value) {\n openMenu();\n }\n searchQuery.value = (e.target as HTMLInputElement).value;\n // Always highlight the first filtered option, or selected if present\n const idx = filteredOptions.value.findIndex(\n (opt) => opt.value === model.value,\n );\n activeIndex.value = idx !== -1 ? idx : 0;\n}\n\nfunction onComboBlur(e: FocusEvent) {\n // Prevent closing if focus moves to the dropdown menu (e.g. scrollbar interaction)\n const relatedTarget = e.relatedTarget as HTMLElement | null;\n if (ignoreBlur.value) {\n ignoreBlur.value = false;\n return;\n }\n if (\n relatedTarget &&\n listboxRef.value &&\n listboxRef.value.contains(relatedTarget)\n ) {\n // Focus moved inside the dropdown, don't close\n return;\n }\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n closeMenu();\n}\n\nfunction selectOption(idx: number) {\n const opt = filteredOptions.value[idx];\n if (opt && opt.value !== model.value) {\n model.value = opt.value;\n emit(\"change\", opt.value);\n }\n ignoreFocus.value = true;\n closeMenu();\n // We want to ignore the focus just briefly so it doesn't re-open\n setTimeout(() => {\n ignoreFocus.value = false;\n }, 100);\n}\n\nfunction onComboClick() {\n if (props.disabled) {\n return;\n }\n if (!open.value) {\n openMenu();\n } else {\n closeMenu();\n }\n}\n\nfunction onComboKeydown(e: KeyboardEvent) {\n if (props.disabled) {\n return;\n }\n const max = filteredOptions.value.length - 1;\n if (!open.value && [\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(e.key)) {\n e.preventDefault();\n openMenu();\n return;\n }\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.min(max, activeIndex.value + 1);\n scrollOptionIntoView();\n }\n break;\n case \"ArrowUp\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.max(0, activeIndex.value - 1);\n scrollOptionIntoView();\n }\n break;\n case \"Home\":\n e.preventDefault();\n activeIndex.value = 0;\n scrollOptionIntoView();\n break;\n case \"End\":\n e.preventDefault();\n activeIndex.value = max;\n scrollOptionIntoView();\n break;\n case \"Enter\":\n case \" \":\n e.preventDefault();\n if (open.value) {\n selectOption(activeIndex.value);\n } else {\n openMenu();\n }\n break;\n case \"Escape\":\n if (isTop.value) {\n e.preventDefault();\n setTimeout(() => {\n closeMenu();\n }, 0);\n }\n break;\n }\n}\n\nfunction onOptionClick(idx: number) {\n selectOption(idx);\n}\n\nfunction onOptionMouseDown() {\n ignoreBlur.value = true;\n}\n\nconst showClearButton = computed(() => {\n return (\n props.clearButton &&\n model.value !== null &&\n model.value !== undefined &&\n !props.disabled\n );\n});\n\nfunction clearValue() {\n if (!props.disabled) {\n model.value = null;\n emit(\"change\", null);\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n }\n}\n</script>\n\n<template>\n <div\n class=\"g-select-root g-select-combo\"\n :class=\"{ 'g-select-open': open, 'g-select-compact': compact, 'g-select-has-error': hasErrors }\"\n >\n <div\n v-if=\"!hiddenLabel\"\n :id=\"baseId + '-label'\"\n class=\"g-select-combo-label g-select-label\"\n >\n {{ props.label }}<span v-if=\"props.required\" class=\"g-select-required\" aria-hidden=\"true\"> *</span>\n </div>\n <div class=\"g-select-input-wrap\">\n <div\n v-if=\"props.searchable\"\n class=\"g-select-combo-input g-select-control\"\n :id=\"baseId\"\n >\n <input\n ref=\"comboRef\"\n type=\"text\"\n name=\"comboInput\"\n class=\"g-select-search-input\"\n :class=\"{ 'g-select-clearable': clearButton }\"\n :value=\"\n open\n ? searchQuery\n : normalizedOptions[selectedIndex]\n ? normalizedOptions[selectedIndex].label\n : ''\n \"\n :placeholder=\"open ? '' : placeholder\"\n :disabled=\"props.disabled\"\n @focus=\"onComboFocus\"\n @input=\"onComboInput\"\n @keydown=\"onComboKeydown\"\n @blur=\"onComboBlur\"\n :aria-autocomplete=\"'list'\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-required=\"props.required ? 'true' : undefined\"\n aria-haspopup=\"listbox\"\n :aria-activedescendant=\"\n open ? baseId + '-option-' + activeIndex : undefined\n \"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n role=\"combobox\"\n autocomplete=\"off\"\n />\n <button\n v-if=\"showClearButton\"\n type=\"button\"\n class=\"g-select-clear-btn\"\n @click=\"clearValue\"\n >\n <svg\n role=\"img\"\n aria-label=\"Clear Selection\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1.125em\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n\n <svg\n class=\"g-select-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n </div>\n <div\n v-else\n ref=\"comboRef\"\n :id=\"baseId\"\n class=\"g-select-combo-button g-select-control\"\n :class=\"{ 'g-select-clearable': clearButton }\"\n role=\"combobox\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-required=\"props.required ? 'true' : undefined\"\n aria-haspopup=\"listbox\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n :aria-activedescendant=\"\n open ? baseId + '-option-' + activeIndex : undefined\n \"\n tabindex=\"0\"\n @click=\"onComboClick\"\n @keydown=\"onComboKeydown\"\n @focus=\"onComboFocus\"\n @blur=\"onComboBlur\"\n >\n {{\n normalizedOptions[selectedIndex]\n ? normalizedOptions[selectedIndex].label\n : \"\"\n }}\n <button\n v-if=\"showClearButton\"\n type=\"button\"\n class=\"g-select-clear-btn\"\n @click.stop=\"clearValue\"\n >\n <svg\n role=\"img\"\n aria-label=\"Clear Selection\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1.125em\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n\n <svg\n class=\"g-select-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n </div>\n <div\n v-show=\"open\"\n ref=\"listboxRef\"\n class=\"g-select-combo-menu g-select-list\"\n :class=\"{\n 'g-select-combo-menu--above': menuPlacement === 'above',\n }\"\n :style=\"menuStyle\"\n role=\"listbox\"\n :id=\"baseId + '-listbox'\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n tabindex=\"-1\"\n >\n <template v-if=\"filteredOptions.length > 0\">\n <div\n v-for=\"(option, idx) in filteredOptions\"\n :key=\"option.value\"\n :id=\"baseId + '-option-' + idx\"\n class=\"g-select-combo-option g-select-option\"\n :class=\"{\n 'g-select-option-current': idx === activeIndex,\n 'ilw-theme-blue': option.value === model,\n }\"\n role=\"option\"\n :aria-selected=\"\n option.value === model ? 'true' : 'false'\n \"\n @mousedown=\"onOptionMouseDown\"\n @click=\"onOptionClick(idx)\"\n >\n <slot\n name=\"option\"\n :option=\"option\"\n :selected=\"option.value === model\"\n :index=\"idx\"\n >\n {{ option.label }}\n </slot>\n </div>\n </template>\n <template v-else>\n <div\n aria-live=\"polite\"\n class=\"g-select-combo-option g-select-option g-select-no-results\"\n >\n No results found.\n </div>\n </template>\n </div>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + baseId\"\n />\n </div>\n</template>\n\n<style>\ng-select {\n display: block;\n}\n.g-select-root {\n position: relative;\n font-size: 1rem;\n}\n\n.g-select-label {\n font-weight: 700;\n color: var(--g-surface-900);\n margin-bottom: 0.5em;\n}\n\n.g-select-required {\n color: var(--g-danger-600);\n}\n\n.g-select-input-wrap {\n position: relative;\n}\n\n.g-select-control {\n line-height: 1.5;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-radius: var(--g-border-radius-m);\n text-align: left;\n position: relative;\n\n &:focus-visible {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n outline-color: var(--g-primary-500);\n\n .g-select-caret {\n color: var(--g-primary-500);\n }\n }\n\n &:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 1px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n}\n\n.g-select-combo-button {\n}\n\n.g-select-caret {\n position: absolute;\n right: 0.5em;\n line-height: 1.5em;\n top: calc(50% - 0.55em);\n color: var(--g-accent-700);\n pointer-events: none;\n\n transform: rotate(90deg);\n}\n\n.g-select-open .g-select-caret {\n transform: rotate(-90deg);\n}\n\n.g-select-combo-menu {\n background-color: var(--g-surface-0);\n border: 2px solid var(--g-surface-700);\n border-radius: 0 0 var(--g-border-radius-m) var(--g-border-radius-m);\n box-sizing: border-box;\n box-shadow:\n 0 4px 4px rgba(0, 0, 0, 0.2),\n 0 1px 0 1px rgba(0, 0, 0, 0.18);\n max-height: 50vh;\n overflow-y: auto;\n left: 0;\n position: absolute;\n top: 100%;\n width: 100%;\n z-index: 1000;\n display: none;\n}\n\n.g-select-combo-menu--above {\n border-radius: var(--g-border-radius-m) var(--g-border-radius-m) 0 0;\n}\n\n.g-select-open .g-select-combo-menu {\n display: block;\n}\n\n.g-select-combo-option {\n padding: 0.5em 0.5em;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n border-color: var(--g-accent-700);\n }\n}\n\n.g-select-option-current {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n\n &:hover {\n color: var(--g-primary-text);\n }\n}\n\n.g-select-search-input {\n box-sizing: border-box;\n display: block;\n width: 100%;\n font-family: var(--il-font-sans);\n border: none;\n box-sizing: border-box;\n text-overflow: ellipsis;\n font-size: 1em;\n\n &.g-select-clearable {\n padding-right: 3.5em; /* Space for clear button */\n }\n}\n\n.g-select-search-input,\n.g-select-combo-button {\n padding: 0.25em 2em 0.25em 0.75em;\n line-height: 1.875em;\n box-sizing: border-box;\n}\n.g-select-combo-button {\n /* Padding + line height + padding + border */\n min-height: calc(0.25em + 1.875em + 0.25em + 4px);\n min-width: 8rem;\n}\n\n.g-select-compact {\n font-size: 0.875rem;\n}\n\n.g-select-search-input,\n.g-select-combo-button {\n &.g-select-clearable {\n padding-right: 3em; /* Space for clear button */\n }\n}\n\n.g-select-no-results {\n padding: 0.25em 1em;\n text-align: center;\n color: var(--g-surface-900);\n font-style: italic;\n}\n\n.g-select-clear-btn {\n position: absolute;\n right: 1.25em;\n top: calc(50% - 1.15em);\n background: none;\n border: none;\n color: var(--g-accent-700);\n font-size: 1.25em;\n cursor: pointer;\n padding: 0.6em 0.3em 0.4em;\n line-height: 1;\n\n &:hover {\n color: var(--g-accent-700);\n }\n &:focus {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n outline-color: var(--g-primary-500);\n }\n}\n\n.g-select-has-error .g-select-control {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * By default, this component behaves like a normal select element with\n * custom styling.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * The component can be marked `searchable` to enable search functionality.\n * This turns it into a text input that filters the options. Filtering is\n * done with a simple lower-case string search.\n *\n * The `options` prop can be an array of strings or objects with `label`\n * and `value` properties.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, ref, useId, watch, toRef } from \"vue\";\nimport { useSelectDropdown, normalizeSelectOptions, type SelectOption } from \"../compose/useSelectDropdown.ts\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype Props = {\n /**\n * List of options to choose from\n */\n options: Array<string | SelectOption>;\n /**\n * Accessible label\n * @demo Select Option\n */\n label: string;\n /**\n * Hide the label visually\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Placeholder\n *\n * Only used if the component is searchable.\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Searchable\n * @demo\n */\n searchable?: boolean;\n /**\n * Show clear button\n * @demo\n */\n clearButton?: boolean;\n /**\n * Compact\n * @demo\n */\n compact?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n required: false,\n searchable: false,\n compact: false,\n errors: () => [],\n});\nconst emit = defineEmits([\"change\"]);\nconst model = defineModel<string | number | null>();\n\nconst baseId = useId();\nconst comboRef = ref<HTMLElement | null>(null);\nconst listboxRef = ref<HTMLElement | null>(null);\nconst comboInputRef = ref<HTMLInputElement | null>(null);\nconst open = ref(false);\nconst activeIndex = ref(0);\nconst ignoreBlur = ref(false);\nconst ignoreFocus = ref(false);\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst { menuPlacement, menuStyle, isTop, scrollOptionIntoView } = useSelectDropdown({\n open,\n anchorRef: comboRef,\n listboxRef,\n baseId,\n activeIndex,\n});\n\nconst normalizedOptions = computed(() => normalizeSelectOptions(props.options));\n\nconst searchQuery = ref(\"\");\n\nconst filteredOptions = computed(() => {\n if (!props.searchable || !open.value || !searchQuery.value) {\n return normalizedOptions.value;\n }\n const q = searchQuery.value.toLowerCase();\n return normalizedOptions.value.filter((opt) =>\n opt.label.toLowerCase().includes(q),\n );\n});\n\nconst selectedIndex = computed(() => {\n return filteredOptions.value.findIndex((opt) => opt.value === model.value);\n});\n\nwatch(\n () => model.value,\n (val) => {\n const idx = filteredOptions.value.findIndex((opt) => opt.value === val);\n if (idx !== -1) {\n activeIndex.value = idx;\n }\n },\n);\n\nfunction openMenu() {\n if (props.disabled) {\n return;\n }\n open.value = true;\n if (props.searchable) {\n searchQuery.value = \"\";\n // If a value is selected, highlight it in filtered list\n const idx = filteredOptions.value.findIndex(\n (opt) => opt.value === model.value,\n );\n activeIndex.value = idx !== -1 ? idx : 0;\n nextTick(() => {\n if (comboInputRef.value) {\n comboInputRef.value.focus();\n }\n });\n }\n}\n\nfunction closeMenu() {\n open.value = false;\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n}\n\nfunction onComboFocus(e: FocusEvent) {\n if (props.disabled) {\n return;\n }\n if (props.searchable) {\n if (ignoreFocus.value) {\n ignoreFocus.value = false;\n return;\n }\n openMenu();\n }\n}\n\nfunction onComboInput(e: Event) {\n if (!props.searchable) return;\n // If closed and user types, open and start search\n if (!open.value) {\n openMenu();\n }\n searchQuery.value = (e.target as HTMLInputElement).value;\n // Always highlight the first filtered option, or selected if present\n const idx = filteredOptions.value.findIndex(\n (opt) => opt.value === model.value,\n );\n activeIndex.value = idx !== -1 ? idx : 0;\n}\n\nfunction onComboBlur(e: FocusEvent) {\n // Prevent closing if focus moves to the dropdown menu (e.g. scrollbar interaction)\n const relatedTarget = e.relatedTarget as HTMLElement | null;\n if (ignoreBlur.value) {\n ignoreBlur.value = false;\n return;\n }\n if (\n relatedTarget &&\n listboxRef.value &&\n listboxRef.value.contains(relatedTarget)\n ) {\n // Focus moved inside the dropdown, don't close\n return;\n }\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n closeMenu();\n}\n\nfunction selectOption(idx: number) {\n const opt = filteredOptions.value[idx];\n if (opt && opt.value !== model.value) {\n model.value = opt.value;\n emit(\"change\", opt.value);\n }\n ignoreFocus.value = true;\n closeMenu();\n // We want to ignore the focus just briefly so it doesn't re-open\n setTimeout(() => {\n ignoreFocus.value = false;\n }, 100);\n}\n\nfunction onComboClick() {\n if (props.disabled) {\n return;\n }\n if (!open.value) {\n openMenu();\n } else {\n closeMenu();\n }\n}\n\nfunction onComboKeydown(e: KeyboardEvent) {\n if (props.disabled) {\n return;\n }\n const max = filteredOptions.value.length - 1;\n if (!open.value && [\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(e.key)) {\n e.preventDefault();\n openMenu();\n return;\n }\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.min(max, activeIndex.value + 1);\n scrollOptionIntoView();\n }\n break;\n case \"ArrowUp\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.max(0, activeIndex.value - 1);\n scrollOptionIntoView();\n }\n break;\n case \"Home\":\n e.preventDefault();\n activeIndex.value = 0;\n scrollOptionIntoView();\n break;\n case \"End\":\n e.preventDefault();\n activeIndex.value = max;\n scrollOptionIntoView();\n break;\n case \"Enter\":\n case \" \":\n e.preventDefault();\n if (open.value) {\n selectOption(activeIndex.value);\n } else {\n openMenu();\n }\n break;\n case \"Escape\":\n if (isTop.value) {\n e.preventDefault();\n setTimeout(() => {\n closeMenu();\n }, 0);\n }\n break;\n }\n}\n\nfunction onOptionClick(idx: number) {\n selectOption(idx);\n}\n\nfunction onOptionMouseDown() {\n ignoreBlur.value = true;\n}\n\nconst showClearButton = computed(() => {\n return (\n props.clearButton &&\n model.value !== null &&\n model.value !== undefined &&\n !props.disabled\n );\n});\n\nfunction clearValue() {\n if (!props.disabled) {\n model.value = null;\n emit(\"change\", null);\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n }\n}\n</script>\n\n<template>\n <div\n class=\"g-select-root g-select-combo\"\n :class=\"{ 'g-select-open': open, 'g-select-compact': compact, 'g-select-has-error': hasErrors }\"\n >\n <div\n v-if=\"!hiddenLabel\"\n :id=\"baseId + '-label'\"\n class=\"g-select-combo-label g-select-label\"\n >\n {{ props.label }}<span v-if=\"props.required\" class=\"g-select-required\" aria-hidden=\"true\"> *</span>\n </div>\n <div class=\"g-select-input-wrap\">\n <div\n v-if=\"props.searchable\"\n class=\"g-select-combo-input g-select-control\"\n :id=\"baseId\"\n >\n <input\n ref=\"comboRef\"\n type=\"text\"\n name=\"comboInput\"\n class=\"g-select-search-input\"\n :class=\"{ 'g-select-clearable': clearButton }\"\n :value=\"\n open\n ? searchQuery\n : normalizedOptions[selectedIndex]\n ? normalizedOptions[selectedIndex].label\n : ''\n \"\n :placeholder=\"open ? '' : placeholder\"\n :disabled=\"props.disabled\"\n @focus=\"onComboFocus\"\n @input=\"onComboInput\"\n @keydown=\"onComboKeydown\"\n @blur=\"onComboBlur\"\n :aria-autocomplete=\"'list'\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-required=\"props.required ? 'true' : undefined\"\n aria-haspopup=\"listbox\"\n :aria-activedescendant=\"\n open ? baseId + '-option-' + activeIndex : undefined\n \"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n role=\"combobox\"\n autocomplete=\"off\"\n />\n <button\n v-if=\"showClearButton\"\n type=\"button\"\n class=\"g-select-clear-btn\"\n @click=\"clearValue\"\n >\n <svg\n role=\"img\"\n aria-label=\"Clear Selection\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1.125em\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n\n <svg\n class=\"g-select-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n </div>\n <div\n v-else\n ref=\"comboRef\"\n :id=\"baseId\"\n class=\"g-select-combo-button g-select-control\"\n :class=\"{ 'g-select-clearable': clearButton }\"\n role=\"combobox\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-required=\"props.required ? 'true' : undefined\"\n aria-haspopup=\"listbox\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n :aria-activedescendant=\"\n open ? baseId + '-option-' + activeIndex : undefined\n \"\n tabindex=\"0\"\n @click=\"onComboClick\"\n @keydown=\"onComboKeydown\"\n @focus=\"onComboFocus\"\n @blur=\"onComboBlur\"\n >\n {{\n normalizedOptions[selectedIndex]\n ? normalizedOptions[selectedIndex].label\n : \"\"\n }}\n <button\n v-if=\"showClearButton\"\n type=\"button\"\n class=\"g-select-clear-btn\"\n @click.stop=\"clearValue\"\n >\n <svg\n role=\"img\"\n aria-label=\"Clear Selection\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1.125em\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n\n <svg\n class=\"g-select-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n </div>\n <div\n v-show=\"open\"\n ref=\"listboxRef\"\n class=\"g-select-combo-menu g-select-list\"\n :class=\"{\n 'g-select-combo-menu--above': menuPlacement === 'above',\n }\"\n :style=\"menuStyle\"\n role=\"listbox\"\n :id=\"baseId + '-listbox'\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n tabindex=\"-1\"\n >\n <template v-if=\"filteredOptions.length > 0\">\n <div\n v-for=\"(option, idx) in filteredOptions\"\n :key=\"option.value\"\n :id=\"baseId + '-option-' + idx\"\n class=\"g-select-combo-option g-select-option\"\n :class=\"{\n 'g-select-option-current': idx === activeIndex,\n 'ilw-theme-blue': option.value === model,\n }\"\n role=\"option\"\n :aria-selected=\"\n option.value === model ? 'true' : 'false'\n \"\n @mousedown=\"onOptionMouseDown\"\n @click=\"onOptionClick(idx)\"\n >\n <slot\n name=\"option\"\n :option=\"option\"\n :selected=\"option.value === model\"\n :index=\"idx\"\n >\n {{ option.label }}\n </slot>\n </div>\n </template>\n <template v-else>\n <div\n aria-live=\"polite\"\n class=\"g-select-combo-option g-select-option g-select-no-results\"\n >\n No results found.\n </div>\n </template>\n </div>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + baseId\"\n />\n </div>\n</template>\n\n<style>\ng-select {\n display: block;\n}\n.g-select-root {\n position: relative;\n font-size: 1rem;\n}\n\n.g-select-label {\n font-weight: 700;\n color: var(--g-surface-900);\n margin-bottom: 0.5em;\n}\n\n.g-select-required {\n color: var(--g-danger-600);\n}\n\n.g-select-input-wrap {\n position: relative;\n}\n\n.g-select-control {\n line-height: 1.5;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-radius: var(--g-border-radius-m);\n text-align: left;\n position: relative;\n\n &:focus-visible {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n outline-color: var(--g-primary-500);\n\n .g-select-caret {\n color: var(--g-primary-500);\n }\n }\n\n &:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 1px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n}\n\n.g-select-combo-button {\n}\n\n.g-select-caret {\n position: absolute;\n right: 0.5em;\n line-height: 1.5em;\n top: calc(50% - 0.55em);\n color: var(--g-accent-700);\n pointer-events: none;\n\n transform: rotate(90deg);\n}\n\n.g-select-open .g-select-caret {\n transform: rotate(-90deg);\n}\n\n.g-select-combo-menu {\n background-color: var(--g-surface-0);\n border: 2px solid var(--g-surface-700);\n border-radius: 0 0 var(--g-border-radius-m) var(--g-border-radius-m);\n box-sizing: border-box;\n box-shadow:\n 0 4px 4px rgba(0, 0, 0, 0.2),\n 0 1px 0 1px rgba(0, 0, 0, 0.18);\n max-height: 50vh;\n overflow-y: auto;\n left: 0;\n position: absolute;\n top: 100%;\n width: 100%;\n z-index: 1000;\n display: none;\n}\n\n.g-select-combo-menu--above {\n border-radius: var(--g-border-radius-m) var(--g-border-radius-m) 0 0;\n}\n\n.g-select-open .g-select-combo-menu {\n display: block;\n}\n\n.g-select-combo-option {\n padding: 0.5em 0.5em;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n border-color: var(--g-accent-700);\n }\n}\n\n.g-select-option-current {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n\n &:hover {\n color: var(--g-primary-text);\n }\n}\n\n.g-select-search-input {\n box-sizing: border-box;\n display: block;\n width: 100%;\n font-family: var(--il-font-sans);\n border: none;\n box-sizing: border-box;\n text-overflow: ellipsis;\n font-size: 1em;\n\n &.g-select-clearable {\n padding-right: 3.5em; /* Space for clear button */\n }\n}\n\n.g-select-search-input,\n.g-select-combo-button {\n padding: 0.25em 2em 0.25em 0.75em;\n line-height: 1.875em;\n box-sizing: border-box;\n}\n.g-select-combo-button {\n /* Padding + line height + padding + border */\n min-height: calc(0.25em + 1.875em + 0.25em + 4px);\n min-width: 8rem;\n}\n\n.g-select-compact {\n font-size: 0.875rem;\n}\n\n.g-select-search-input,\n.g-select-combo-button {\n &.g-select-clearable {\n padding-right: 3em; /* Space for clear button */\n }\n}\n\n.g-select-no-results {\n padding: 0.25em 1em;\n text-align: center;\n color: var(--g-surface-900);\n font-style: italic;\n}\n\n.g-select-clear-btn {\n position: absolute;\n right: 1.25em;\n top: calc(50% - 1.15em);\n background: none;\n border: none;\n color: var(--g-accent-700);\n font-size: 1.25em;\n cursor: pointer;\n padding: 0.6em 0.3em 0.4em;\n line-height: 1;\n\n &:hover {\n color: var(--g-accent-700);\n }\n &:focus {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n outline-color: var(--g-primary-500);\n }\n}\n\n.g-select-has-error .g-select-control {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A combobox-style search that shows a list of results as an auto\n * complete dropdown.\n *\n * The component doesn't perform any real searching. It emits events\n * that can be used to trigger searches, and then the results are\n * passed back to the component.\n *\n * **Events**:\n *\n * - `submit` event is emitted when a search should be performed.\n * - `select` event is submitted when a user makes a selection\n * in the dropdown.\n *\n * > [!NOTE]\n * > The `v-model` value should *not* be used to trigger a search,\n * > but it can be used to get the current user input.\n *\n * **Props**:\n *\n * - `results` will be rendered in the dropdown. There are two options:\n * - Pass an array of objects that extend `{ id: string | number; title: string; }`.\n * - Pass an array of `GSearchGroup<T>` objects, where the `items` property extends the above type.\n * In this case the results are grouped.\n * - `auto` makes search submit on user input. Defaults to `true`.\n * if `false`, submission happens on Enter or clicking the search button.\n * - `loading` shows a loading indicator. Use if the search may take longer.\n *\n * **Slot**: `option` customizes how an option is rendered.\n * It receives the current item as `option`.\n *\n * **Slot**: `group` customizes the group label for each group.\n *\n * Here is a minimal implementation:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * interface SearchResult {\n * id: string;\n * title: string;\n * }\n *\n * const searchData = ref<SearchResult[]>([\n * { id: \"1\", title: \"The Quick Fox\" },\n * { id: \"2\", title: \"The Lazy Dog\" },\n * { id: \"3\", title: \"The Brown Bear\" },\n * ]);\n * const searchResults = ref<SearchResult[]>([]);\n *\n * function submit(query: string) {\n * searchResults.value = searchData.value.filter((result) =>\n * result.title.toLowerCase().includes(query.toLowerCase()),\n * );\n * }\n * function selected(item: SearchResult) {\n * console.log(\"Selected:\", item);\n * }\n * &lt;/script>\n * <template>\n * <GSearch\n * :results=\"searchResults\"\n * @submit=\"submit\"\n * @select=\"selected\">\n * </GSearch>\n * </template>\n * ```\n */\nexport default {};\n</script>\n\n<script\n setup\n lang=\"ts\"\n generic=\"\n T extends {\n id: string | number;\n title: string;\n }\n \"\n>\nimport { computed, nextTick, ref, useId, watch } from \"vue\";\nimport { useDebounceFn, useFocusWithin } from \"@vueuse/core\";\nimport GProgress from \"./GProgress.vue\";\n\nexport interface GSearchGroup<R> {\n type: string;\n label: string;\n items: R[];\n}\n\ntype Props = {\n results: GSearchGroup<T>[] | T[];\n /**\n * Placeholder\n * @demo\n */\n placeholder?: string;\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Automatic search\n * @demo\n */\n auto?: boolean;\n /**\n * Show search loading indicator\n * @demo\n */\n loading?: boolean;\n};\n\nconst modelValue = defineModel<string | null>({ default: () => \"\" });\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: \"Search...\",\n label: \"Search\",\n auto: true\n});\nconst emit = defineEmits([\"select\", \"submit\"]);\n\nconst inputRef = ref<HTMLInputElement | null>(null);\nconst listboxRef = ref<HTMLDivElement | null>(null);\nconst closed = ref(true);\nconst activeIndex = ref<number>(-1);\nconst flatResults = computed(() => {\n if (\n Array.isArray(props.results) &&\n props.results.length &&\n \"items\" in props.results[0]\n ) {\n // Grouped results\n return (props.results as GSearchGroup<T>[]).flatMap((g) => g.items);\n } else {\n return props.results as T[];\n }\n});\nconst resultCount = computed(() => flatResults.value.length);\n\nfunction onInput(ev: Event) {\n const value = (ev.target as HTMLInputElement).value;\n modelValue.value = value;\n if (props.auto && value.length > 1) {\n closed.value = false;\n }\n}\n\nfunction scrollOptionIntoView() {\n nextTick(() => {\n const el = listboxRef.value?.querySelector('[aria-selected=\"true\"]');\n if (el) {\n el.scrollIntoView({ block: \"nearest\" });\n }\n });\n}\n\nconst { focused } = useFocusWithin(inputRef);\n\nfunction onKeydown(ev: KeyboardEvent) {\n const altKey = ev.altKey;\n if (ev.key === \"ArrowDown\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n closed.value = false;\n if (!altKey) {\n activeIndex.value = (activeIndex.value + 1) % resultCount.value;\n scrollOptionIntoView();\n }\n } else if (ev.key === \"ArrowUp\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n closed.value = false;\n activeIndex.value =\n (activeIndex.value - 1 + resultCount.value) % resultCount.value;\n scrollOptionIntoView();\n } else if (ev.key === \"Enter\") {\n if (closed.value) {\n // Don't debounce on enter\n emit(\"submit\", modelValue.value);\n closed.value = false;\n ev.preventDefault();\n } else {\n selectResult(flatResults.value[activeIndex.value]);\n }\n } else if (ev.key === \"Escape\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n if (!expanded.value) {\n modelValue.value = \"\";\n }\n closed.value = true;\n activeIndex.value = -1;\n }\n\n if ([\"Backspace\", \"Delete\", \"Clear\", \"Undo\"].includes(ev.key)) {\n closed.value = true;\n }\n}\n\nfunction selectResult(result: T | null) {\n emit(\"select\", result);\n modelValue.value = \"\";\n closed.value = true;\n activeIndex.value = -1;\n}\n\nconst isLoading = computed(() => {\n return !!props.loading;\n});\n\nconst expanded = computed(() => {\n return focused.value && !closed.value;\n});\n\nconst submit = useDebounceFn(() => {\n emit(\"submit\", modelValue.value);\n}, 300);\n\nwatch(\n () => modelValue.value,\n (val) => {\n if (!val) {\n activeIndex.value = -1;\n } else if (props.auto) {\n // Auto-submit on input change with debounce\n submit();\n }\n },\n);\nconst id = useId();\n</script>\n\n<template>\n <div class=\"g-search\" role=\"search\" :aria-label=\"props.label\">\n <form class=\"g-search-form\" @submit.prevent=\"selectResult(null)\">\n <input\n ref=\"inputRef\"\n class=\"g-search-input\"\n name=\"search\"\n type=\"search\"\n :placeholder=\"props.placeholder\"\n :value=\"modelValue\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n role=\"combobox\"\n :aria-expanded=\"expanded\"\n aria-autocomplete=\"list\"\n :aria-controls=\"`${id}-list`\"\n :aria-activedescendant=\"\n activeIndex >= 0\n ? 'g-search-option-' + flatResults[activeIndex].id\n : undefined\n \"\n />\n <button\n type=\"submit\"\n class=\"g-search-submit\"\n aria-label=\"Submit search\"\n @keydown=\"onKeydown\"\n >\n <template v-if=\"isLoading\">\n <GProgress size=\"tiny\" />\n </template>\n <svg\n role=\"img\"\n aria-label=\"Search\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n >\n <path\n fill=\"currentColor\"\n d=\"M30 9.76A14.05 14.05 0 1 0 28.3 31l11.3 13a3.34 3.34 0 0 0 4.72-4.72L31.44 27.86A14.05 14.05 0 0 0 30 9.76ZM27.27 27a10.26 10.26 0 1 1 0-14.5 10.25 10.25 0 0 1 0 14.5Z\"\n />\n </svg>\n </button>\n </form>\n <div v-if=\"expanded\" class=\"g-search-dropdown\">\n <div aria-live=\"polite\" class=\"g-search-result-count\">\n <template v-if=\"!isLoading\">\n {{ resultCount }} result{{\n resultCount === 1 ? \"\" : \"s\"\n }}</template\n >\n </div>\n <div\n role=\"listbox\"\n :id=\"`${id}-list`\"\n ref=\"listboxRef\"\n aria-label=\"Search results\"\n >\n <template v-if=\"resultCount > 0 && 'items' in props.results[0]\">\n <template\n v-for=\"(\n group, gIdx\n ) in props.results as GSearchGroup<T>[]\"\n :key=\"group.type\"\n >\n <div\n class=\"g-search-group\"\n role=\"group\"\n :aria-label=\"group.label\"\n >\n <slot name=\"group\" :group=\"group\">\n <div class=\"g-search-group-label\">\n {{ group.label }}\n </div>\n </slot>\n <div\n v-for=\"(item, idx) in group.items\"\n :key=\"item.id\"\n :id=\"'g-search-option-' + item.id\"\n class=\"g-search-option\"\n :class=\"{\n 'g-search-option-active':\n flatResults[activeIndex] &&\n flatResults[activeIndex].id === item.id,\n }\"\n role=\"option\"\n @mousedown.prevent=\"selectResult(item)\"\n :aria-selected=\"\n flatResults[activeIndex] &&\n flatResults[activeIndex].id === item.id\n \"\n >\n <slot name=\"option\" :option=\"item\">\n {{ item.title }}\n </slot>\n </div>\n </div>\n </template>\n </template>\n <template v-else-if=\"resultCount > 0\">\n <div\n v-for=\"(item, idx) in flatResults\"\n :key=\"item.id\"\n :id=\"'g-search-option-' + item.id\"\n class=\"g-search-option\"\n :class=\"{\n 'g-search-option-active': activeIndex === idx,\n }\"\n role=\"option\"\n @mousedown.prevent=\"selectResult(item)\"\n :aria-selected=\"activeIndex === idx\"\n >\n <slot name=\"option\" :option=\"item\">\n {{ item.title }}\n </slot>\n </div>\n </template>\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-search {\n display: block;\n}\n\n.g-search {\n position: relative;\n min-width: 200px;\n width: 100%;\n}\n\n.g-search-form {\n display: flex;\n align-items: stretch;\n}\n\n.g-search-input {\n width: 100%;\n padding: 0.5rem 1rem;\n line-height: 1.33rem;\n font-size: 1rem;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-right-width: 1px;\n border-top-left-radius: var(--g-border-radius-m);\n border-bottom-left-radius: var(--g-border-radius-m);\n &:focus {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-search-submit {\n box-sizing: border-box;\n background: var(--g-surface-0);\n color: var(--g-accent-700);\n border: 2px solid var(--g-primary-500);\n border-left-width: 1px;\n border-top-right-radius: var(--g-border-radius-m);\n border-bottom-right-radius: var(--g-border-radius-m);\n padding: 0.2rem 0.5rem;\n font-size: 1rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n &:focus {\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--ilw-color--focus--outline);\n }\n}\n\n.g-search-dropdown {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n background: var(--g-surface-0);\n border: 2px solid var(--g-surface-200);\n z-index: 10;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n}\n\n.g-search-result-count {\n height: 28px;\n padding: 0.2rem 1rem;\n font-size: 0.95rem;\n color: var(--g-surface-900);\n background: var(--g-surface-100);\n}\n\n.g-search-group {\n}\n\n.g-search-group-label {\n font-weight: bold;\n padding: 0.5rem 1rem 0.25rem 1rem;\n color: var(--g-surface-900);\n background: var(--g-surface-100);\n}\n\n.g-search-option {\n padding: 0.3rem 0.8rem;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n }\n}\n\n.g-search-option.g-search-option-active {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n border: 2px solid var(--g-primary-500);\n}\n\n.fa-spin {\n color: var(--g-primary-300);\n}\n\n@media (max-width: 960px) {\n .g-search {\n min-width: 150px;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A combobox-style search that shows a list of results as an auto\n * complete dropdown.\n *\n * The component doesn't perform any real searching. It emits events\n * that can be used to trigger searches, and then the results are\n * passed back to the component.\n *\n * **Events**:\n *\n * - `submit` event is emitted when a search should be performed.\n * - `select` event is submitted when a user makes a selection\n * in the dropdown.\n *\n * > [!NOTE]\n * > The `v-model` value should *not* be used to trigger a search,\n * > but it can be used to get the current user input.\n *\n * **Props**:\n *\n * - `results` will be rendered in the dropdown. There are two options:\n * - Pass an array of objects that extend `{ id: string | number; title: string; }`.\n * - Pass an array of `GSearchGroup<T>` objects, where the `items` property extends the above type.\n * In this case the results are grouped.\n * - `auto` makes search submit on user input. Defaults to `true`.\n * if `false`, submission happens on Enter or clicking the search button.\n * - `loading` shows a loading indicator. Use if the search may take longer.\n *\n * **Slot**: `option` customizes how an option is rendered.\n * It receives the current item as `option`.\n *\n * **Slot**: `group` customizes the group label for each group.\n *\n * Here is a minimal implementation:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * interface SearchResult {\n * id: string;\n * title: string;\n * }\n *\n * const searchData = ref<SearchResult[]>([\n * { id: \"1\", title: \"The Quick Fox\" },\n * { id: \"2\", title: \"The Lazy Dog\" },\n * { id: \"3\", title: \"The Brown Bear\" },\n * ]);\n * const searchResults = ref<SearchResult[]>([]);\n *\n * function submit(query: string) {\n * searchResults.value = searchData.value.filter((result) =>\n * result.title.toLowerCase().includes(query.toLowerCase()),\n * );\n * }\n * function selected(item: SearchResult) {\n * console.log(\"Selected:\", item);\n * }\n * &lt;/script>\n * <template>\n * <GSearch\n * :results=\"searchResults\"\n * @submit=\"submit\"\n * @select=\"selected\">\n * </GSearch>\n * </template>\n * ```\n */\nexport default {};\n</script>\n\n<script\n setup\n lang=\"ts\"\n generic=\"\n T extends {\n id: string | number;\n title: string;\n }\n \"\n>\nimport { computed, nextTick, ref, useId, watch } from \"vue\";\nimport { useDebounceFn, useFocusWithin } from \"@vueuse/core\";\nimport GProgress from \"./GProgress.vue\";\n\nexport interface GSearchGroup<R> {\n type: string;\n label: string;\n items: R[];\n}\n\ntype Props = {\n results: GSearchGroup<T>[] | T[];\n /**\n * Placeholder\n * @demo\n */\n placeholder?: string;\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Automatic search\n * @demo\n */\n auto?: boolean;\n /**\n * Show search loading indicator\n * @demo\n */\n loading?: boolean;\n};\n\nconst modelValue = defineModel<string | null>({ default: () => \"\" });\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: \"Search...\",\n label: \"Search\",\n auto: true\n});\nconst emit = defineEmits([\"select\", \"submit\"]);\n\nconst inputRef = ref<HTMLInputElement | null>(null);\nconst listboxRef = ref<HTMLDivElement | null>(null);\nconst closed = ref(true);\nconst activeIndex = ref<number>(-1);\nconst flatResults = computed(() => {\n if (\n Array.isArray(props.results) &&\n props.results.length &&\n \"items\" in props.results[0]\n ) {\n // Grouped results\n return (props.results as GSearchGroup<T>[]).flatMap((g) => g.items);\n } else {\n return props.results as T[];\n }\n});\nconst resultCount = computed(() => flatResults.value.length);\n\nfunction onInput(ev: Event) {\n const value = (ev.target as HTMLInputElement).value;\n modelValue.value = value;\n if (props.auto && value.length > 1) {\n closed.value = false;\n }\n}\n\nfunction scrollOptionIntoView() {\n nextTick(() => {\n const el = listboxRef.value?.querySelector('[aria-selected=\"true\"]');\n if (el) {\n el.scrollIntoView({ block: \"nearest\" });\n }\n });\n}\n\nconst { focused } = useFocusWithin(inputRef);\n\nfunction onKeydown(ev: KeyboardEvent) {\n const altKey = ev.altKey;\n if (ev.key === \"ArrowDown\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n closed.value = false;\n if (!altKey) {\n activeIndex.value = (activeIndex.value + 1) % resultCount.value;\n scrollOptionIntoView();\n }\n } else if (ev.key === \"ArrowUp\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n closed.value = false;\n activeIndex.value =\n (activeIndex.value - 1 + resultCount.value) % resultCount.value;\n scrollOptionIntoView();\n } else if (ev.key === \"Enter\") {\n if (closed.value) {\n // Don't debounce on enter\n emit(\"submit\", modelValue.value);\n closed.value = false;\n ev.preventDefault();\n } else {\n selectResult(flatResults.value[activeIndex.value]);\n }\n } else if (ev.key === \"Escape\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n if (!expanded.value) {\n modelValue.value = \"\";\n }\n closed.value = true;\n activeIndex.value = -1;\n }\n\n if ([\"Backspace\", \"Delete\", \"Clear\", \"Undo\"].includes(ev.key)) {\n closed.value = true;\n }\n}\n\nfunction selectResult(result: T | null) {\n emit(\"select\", result);\n modelValue.value = \"\";\n closed.value = true;\n activeIndex.value = -1;\n}\n\nconst isLoading = computed(() => {\n return !!props.loading;\n});\n\nconst expanded = computed(() => {\n return focused.value && !closed.value;\n});\n\nconst submit = useDebounceFn(() => {\n emit(\"submit\", modelValue.value);\n}, 300);\n\nwatch(\n () => modelValue.value,\n (val) => {\n if (!val) {\n activeIndex.value = -1;\n } else if (props.auto) {\n // Auto-submit on input change with debounce\n submit();\n }\n },\n);\nconst id = useId();\n</script>\n\n<template>\n <div class=\"g-search\" role=\"search\" :aria-label=\"props.label\">\n <form class=\"g-search-form\" @submit.prevent=\"selectResult(null)\">\n <input\n ref=\"inputRef\"\n class=\"g-search-input\"\n name=\"search\"\n type=\"search\"\n :placeholder=\"props.placeholder\"\n :value=\"modelValue\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n role=\"combobox\"\n :aria-expanded=\"expanded\"\n aria-autocomplete=\"list\"\n :aria-controls=\"`${id}-list`\"\n :aria-activedescendant=\"\n activeIndex >= 0\n ? 'g-search-option-' + flatResults[activeIndex].id\n : undefined\n \"\n />\n <button\n type=\"submit\"\n class=\"g-search-submit\"\n aria-label=\"Submit search\"\n @keydown=\"onKeydown\"\n >\n <template v-if=\"isLoading\">\n <GProgress size=\"tiny\" />\n </template>\n <svg\n role=\"img\"\n aria-label=\"Search\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n >\n <path\n fill=\"currentColor\"\n d=\"M30 9.76A14.05 14.05 0 1 0 28.3 31l11.3 13a3.34 3.34 0 0 0 4.72-4.72L31.44 27.86A14.05 14.05 0 0 0 30 9.76ZM27.27 27a10.26 10.26 0 1 1 0-14.5 10.25 10.25 0 0 1 0 14.5Z\"\n />\n </svg>\n </button>\n </form>\n <div v-if=\"expanded\" class=\"g-search-dropdown\">\n <div aria-live=\"polite\" class=\"g-search-result-count\">\n <template v-if=\"!isLoading\">\n {{ resultCount }} result{{\n resultCount === 1 ? \"\" : \"s\"\n }}</template\n >\n </div>\n <div\n role=\"listbox\"\n :id=\"`${id}-list`\"\n ref=\"listboxRef\"\n aria-label=\"Search results\"\n >\n <template v-if=\"resultCount > 0 && 'items' in props.results[0]\">\n <template\n v-for=\"(\n group, gIdx\n ) in props.results as GSearchGroup<T>[]\"\n :key=\"group.type\"\n >\n <div\n class=\"g-search-group\"\n role=\"group\"\n :aria-label=\"group.label\"\n >\n <slot name=\"group\" :group=\"group\">\n <div class=\"g-search-group-label\">\n {{ group.label }}\n </div>\n </slot>\n <div\n v-for=\"(item, idx) in group.items\"\n :key=\"item.id\"\n :id=\"'g-search-option-' + item.id\"\n class=\"g-search-option\"\n :class=\"{\n 'g-search-option-active':\n flatResults[activeIndex] &&\n flatResults[activeIndex].id === item.id,\n }\"\n role=\"option\"\n @mousedown.prevent=\"selectResult(item)\"\n :aria-selected=\"\n flatResults[activeIndex] &&\n flatResults[activeIndex].id === item.id\n \"\n >\n <slot name=\"option\" :option=\"item\">\n {{ item.title }}\n </slot>\n </div>\n </div>\n </template>\n </template>\n <template v-else-if=\"resultCount > 0\">\n <div\n v-for=\"(item, idx) in flatResults\"\n :key=\"item.id\"\n :id=\"'g-search-option-' + item.id\"\n class=\"g-search-option\"\n :class=\"{\n 'g-search-option-active': activeIndex === idx,\n }\"\n role=\"option\"\n @mousedown.prevent=\"selectResult(item)\"\n :aria-selected=\"activeIndex === idx\"\n >\n <slot name=\"option\" :option=\"item\">\n {{ item.title }}\n </slot>\n </div>\n </template>\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-search {\n display: block;\n}\n\n.g-search {\n position: relative;\n min-width: 200px;\n width: 100%;\n}\n\n.g-search-form {\n display: flex;\n align-items: stretch;\n}\n\n.g-search-input {\n width: 100%;\n padding: 0.5rem 1rem;\n line-height: 1.33rem;\n font-size: 1rem;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-right-width: 1px;\n border-top-left-radius: var(--g-border-radius-m);\n border-bottom-left-radius: var(--g-border-radius-m);\n &:focus {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-search-submit {\n box-sizing: border-box;\n background: var(--g-surface-0);\n color: var(--g-accent-700);\n border: 2px solid var(--g-primary-500);\n border-left-width: 1px;\n border-top-right-radius: var(--g-border-radius-m);\n border-bottom-right-radius: var(--g-border-radius-m);\n padding: 0.2rem 0.5rem;\n font-size: 1rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n &:focus {\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--ilw-color--focus--outline);\n }\n}\n\n.g-search-dropdown {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n background: var(--g-surface-0);\n border: 2px solid var(--g-surface-200);\n z-index: 10;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n}\n\n.g-search-result-count {\n height: 28px;\n padding: 0.2rem 1rem;\n font-size: 0.95rem;\n color: var(--g-surface-900);\n background: var(--g-surface-100);\n}\n\n.g-search-group {\n}\n\n.g-search-group-label {\n font-weight: bold;\n padding: 0.5rem 1rem 0.25rem 1rem;\n color: var(--g-surface-900);\n background: var(--g-surface-100);\n}\n\n.g-search-option {\n padding: 0.3rem 0.8rem;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n }\n}\n\n.g-search-option.g-search-option-active {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n border: 2px solid var(--g-primary-500);\n}\n\n.fa-spin {\n color: var(--g-primary-300);\n}\n\n@media (max-width: 960px) {\n .g-search {\n min-width: 150px;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * This is a minimal header meant for web apps where a full Illinois\n * brand header would be too large.\n *\n * **Slot** `left` allows replacing the link element in the top-left corner.\n *\n * **Slot** `title` is to the right of the logo.\n *\n * **Slot** `app-controls` is the remaining area to the right.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Whether to show the Illinois logo\n */\n illinois?: boolean;\n /**\n * Top-left corner text\n *\n * You can customize this text element with the \"left\" slot.\n * @demo\n */\n brand?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n illinois: false,\n brand: \"GRAD\",\n});\n</script>\n\n<template>\n <header\n :class=\"{\n 'g-app-header': true,\n }\"\n >\n <div class=\"g-app-header__background\">\n <div class=\"g-app-header__background-pattern\"></div>\n <div class=\"g-app-header__background-gradient\"></div>\n </div>\n <div class=\"g-app-header__brand\">\n <slot name=\"left\">\n <a class=\"g-app-header__brand-text\" href=\"/\">{{ brand }}</a>\n </slot>\n </div>\n <div v-if=\"illinois\" class=\"g-app-header__block-i-container\">\n <svg\n class=\"g-app-header__block-i\"\n role=\"img\"\n width=\"55\"\n viewBox=\"0 0 55 79\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <!-- NO LICENSE GRANTED for this logo, must have the right to use it -->\n <title>Block I logo</title>\n <path\n class=\"g-app-header__block-i-outline\"\n d=\"M54.2 21.1V0H0v21.1h12v36.1H0v21.1h54.2V57.2h-12V21.1z\"\n ></path>\n <path\n class=\"g-app-header__block-i-fill\"\n d=\"M42.1 18.1h9V3H3v15h9c1.7 0 3 1.3 3 3v36.1c0 1.7-1.3 3-3 3H3v15h48.1v-15h-9c-1.7 0-3-1.3-3-3v-36c0-1.7 1.4-3 3-3z\"\n ></path>\n </svg>\n </div>\n <slot v-else name=\"icon\"></slot>\n <div class=\"g-app-header__title\">\n <slot name=\"title\"></slot>\n </div>\n <div class=\"g-app-header__app-controls-wrap\">\n <slot name=\"app-controls\" class=\"g-app-header__app-controls\"></slot>\n </div>\n </header>\n</template>\n\n<style>\n\n@layer base {\n :root {\n --g-toolbar-height: 48px;\n }\n}\n\nhtml {\n scroll-padding-top: 70px;\n}\n\ng-app-header:not(:defined),\n.g-app-header {\n box-sizing: border-box;\n background-color: var(--g-surface-100);\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n z-index: 2;\n height: var(--g-toolbar-height);\n border-bottom: 2px solid var(--g-accent-500);\n box-shadow:\n 0px 1px 2px 0px rgba(0, 0, 0, 0.25),\n 0px 1px 10px 5px rgba(0, 0, 0, 0.08);\n}\n\ng-app-header:not(:defined) > [slot=\"title\"] {\n margin: 0;\n margin-left: calc(78px + 20px);\n}\n\ng-app-header:not(:defined)[illinois] > [slot=\"title\"] {\n margin-left: calc(78px + 48px + 20px);\n}\n\n.g-app-header {\n .g-app-header__title {\n display: flex;\n align-items: center;\n margin-left: 20px;\n flex: 1;\n }\n\n .g-app-header__title > * {\n font-size: 20px;\n font-family: var(--il-font-sans);\n font-style: normal;\n line-height: 30px;\n color: var(--g-primary-500);\n margin: 0;\n font-weight: 700;\n text-decoration: none;\n }\n\n .g-app-header__title {\n\n a {\n color: var(--g-primary-500);\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n }\n }\n }\n\n .g-app-header__app-controls-wrap {\n margin-right: 20px;\n }\n}\n\n.g-app-header {\n /*noinspection CssUnresolvedCustomProperty*/\n padding-right: var(--g-scrollbar-width, 0px);\n}\n\n.g-app-header__background {\n position: absolute;\n z-index: -1;\n top: 0;\n left: 0;\n bottom: 0;\n width: 470px;\n\n @media screen and (max-width: 640px) {\n width: 80%;\n }\n}\n\n.g-app-header__background-pattern {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBoZWlnaHQ9IjQ2IiB2aWV3Qm94PSIwIDAgNzEyLjkyNSA2NjkuMTY1Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTE4MTEuMzA3IiB5Mj0iLTIyODQuNDA1IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTM2MjkuODQ4IC0yNzk4LjAxNSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNjOGM2YzciLz48c3RvcCBvZmZzZXQ9Ii45ODEiIHN0b3AtY29sb3I9IiNmZmYiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImIiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTE1NTguNjU1IiB5Mj0iLTIwMzEuNzUzIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTM4MDguNSAtMjM2Ni43MTEpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iYyIgeDE9Ii03MTk2Ljk3NyIgeDI9Ii03MjA0LjIiIHkxPSItMTMwNi4wMDMiIHkyPSItMTc3OS4xMDEiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMi4wNDY1IDAgMCAtMSAxMzk4Ny4xNTIgLTE5MzUuNDA3KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImQiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTEwNTMuMzUxIiB5Mj0iLTE1MjYuNDQ5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQxNjUuODAzIC0xNTA0LjEwMykiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJlIiB4MT0iLTcxOTYuOTc3IiB4Mj0iLTcyMDQuMiIgeTE9Ii04MDAuNjk5IiB5Mj0iLTEyNzMuNzk3IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQzNDQuNDU1IC0xMDcyLjc5OSkiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJmIiB4MT0iLTcxOTYuOTc3IiB4Mj0iLTcyMDQuMiIgeTE9Ii01NDguMDQ3IiB5Mj0iLTEwMjEuMTQ0IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQ1MjMuMTA3IC02NDEuNDk1KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImciIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9Ii0yOTUuMzcxIiB5Mj0iLTc2OC40NjkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMi4wNDY1IDAgMCAtMSAxNDcxMC41NTUgLTIxMy43ODcpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iaCIgeDE9Ii03MTk5LjQ5IiB4Mj0iLTcyMDYuNzEzIiB5MT0iLTQyLjcxOSIgeTI9Ii01MTUuODE3IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQ4ODkuMjA3IDIxNy41MTcpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iaSIgeDE9Ii03MTk5LjQ5IiB4Mj0iLTcyMDYuNzEzIiB5MT0iMjA5LjkzMyIgeTI9Ii0yNjMuMTY1IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTUwNjcuODU5IDY0OC44MikiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJqIiB4MT0iLTcxOTkuNDkiIHgyPSItNzIwNi43MTMiIHkxPSI0NjIuNTg1IiB5Mj0iLTEwLjUxMyIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyLjA0NjUgMCAwIC0xIDE1MjQ2LjUxIDEwODAuMTI1KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImsiIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9IjcxNS4yMzciIHkyPSIyNDIuMTM5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTU0MjUuMTYyIDE1MTEuNDI5KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImwiIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9Ijk2Ny44ODkiIHkyPSI0OTQuNzkxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTU2MDMuODE0IDE5NDIuNzMzKSIvPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjY2xpcHBhdGgpIj48cGF0aCBmaWxsPSJ1cmwoI2EpIiBkPSJNLTI1NDYuNjc0LTk3OC41MzNIMzQwLjY4djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0xMTAyLjk5NyAtODUyLjIwOCkiLz48cGF0aCBmaWxsPSJ1cmwoI2IpIiBkPSJNLTIzNjguMDIyLTc5OS44ODFINTE5LjMzMnYyNTIuNjUyaC0yODg3LjM1NHoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSAtOTI0LjM0NSAtNjczLjU1NikiLz48cGF0aCBmaWxsPSJ1cmwoI2MpIiBkPSJNLTIxODkuMzctNjIxLjIyOUg2OTcuOTg0djI1Mi42NTJILTIxODkuMzd6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTc0NS42OTMgLTQ5NC45MDMpIi8+PHBhdGggZmlsbD0idXJsKCNkKSIgZD0iTS0yMDEwLjcxOC00NDIuNTc3SDg3Ni42MzZ2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTU2Ny4wNDEgLTMxNi4yNTEpIi8+PHBhdGggZmlsbD0idXJsKCNlKSIgZD0iTS0xODMyLjA2Ni0yNjMuOTI1aDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0zODguMzkgLTEzNy42KSIvPjxwYXRoIGZpbGw9InVybCgjZikiIGQ9Ik0tMTY1My40MTQtODUuMjczSDEyMzMuOTR2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTIwOS43MzcgNDEuMDUzKSIvPjxwYXRoIGZpbGw9InVybCgjZykiIGQ9Ik0tMTQ3MS4xMDkgODkuNzU5aDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0yNy40MzIgMjE2LjA4NSkiLz48cGF0aCBmaWxsPSJ1cmwoI2gpIiBkPSJNLTEyOTIuNDU3IDI2OC40MTFoMjg4Ny4zNTR2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgMTUxLjIyIDM5NC43MzcpIi8+PHBhdGggZmlsbD0idXJsKCNpKSIgZD0iTS0xMTEzLjgwNiA0NDcuMDYzaDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IDMyOS44NzEgNTczLjM4OSkiLz48cGF0aCBmaWxsPSJ1cmwoI2opIiBkPSJNLTkzNS4xNTQgNjI1LjcxNUgxOTUyLjJ2MjUyLjY1MkgtOTM1LjE1NHoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSA1MDguNTIzIDc1Mi4wNCkiLz48cGF0aCBmaWxsPSJ1cmwoI2spIiBkPSJNLTc1Ni41MDIgODA0LjM2N2gyODg3LjM1NHYyNTIuNjUySC03NTYuNTAyeiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IDY4Ny4xNzUgOTMwLjY5MykiLz48cGF0aCBmaWxsPSJ1cmwoI2wpIiBkPSJNLTU3Ny44NSA5ODMuMDE5aDI4ODcuMzU0djI1Mi42NTJILTU3Ny44NXoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSA4NjUuODI3IDExMDkuMzQ1KSIvPjwvZz48L3N2Zz4=');\n background-repeat: repeat;\n opacity: 0.5;\n}\n\n.g-app-header__background-gradient {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n background: linear-gradient(\n 90deg,\n transparent -15.89%,\n var(--g-surface-100) 95.85%\n );\n}\n\n.g-app-header__brand {\n min-width: 1rem;\n margin-top: 2px;\n\n & a {\n text-decoration: none;\n padding: 10px 15px;\n font-family: var(--il-font-montserrat);\n letter-spacing: 0.98px;\n font-size: 14px;\n font-style: normal;\n font-weight: 800;\n color: var(--g-primary-300);\n\n &:hover {\n text-decoration: underline;\n color: var(--g-primary-500);\n }\n\n @media screen and (max-width: 740px) {\n display: none;\n }\n }\n}\n\n.g-app-header__block-i-container {\n height: calc(var(--g-toolbar-height) + 3px);\n box-sizing: border-box;\n margin-top: 6px;\n box-shadow:\n 0px 0px 1px 1px rgba(0, 0, 0, 0.08),\n 2px 1px 10px 0px rgba(0, 0, 0, 0.35);\n}\n\n.g-app-header__app-controls {\n flex: 1;\n display: flex;\n justify-content: flex-end;\n padding: 0 10px;\n gap: 10px;\n}\n.g-app-header__block-i-container {\n background-color: var(--il-blue);\n min-width: 40px;\n padding: 8px 10px;\n\n .g-app-header__block-i {\n display: block;\n width: 24px;\n }\n\n .g-app-header__block-i-outline {\n fill: #fff;\n }\n\n .g-app-header__block-i-fill {\n fill: var(--il-orange);\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This is a minimal header meant for web apps where a full Illinois\n * brand header would be too large.\n *\n * **Slot** `left` allows replacing the link element in the top-left corner.\n *\n * **Slot** `title` is to the right of the logo.\n *\n * **Slot** `app-controls` is the remaining area to the right.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Whether to show the Illinois logo\n */\n illinois?: boolean;\n /**\n * Top-left corner text\n *\n * You can customize this text element with the \"left\" slot.\n * @demo\n */\n brand?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n illinois: false,\n brand: \"GRAD\",\n});\n</script>\n\n<template>\n <header\n :class=\"{\n 'g-app-header': true,\n }\"\n >\n <div class=\"g-app-header__background\">\n <div class=\"g-app-header__background-pattern\"></div>\n <div class=\"g-app-header__background-gradient\"></div>\n </div>\n <div class=\"g-app-header__brand\">\n <slot name=\"left\">\n <a class=\"g-app-header__brand-text\" href=\"/\">{{ brand }}</a>\n </slot>\n </div>\n <div v-if=\"illinois\" class=\"g-app-header__block-i-container\">\n <svg\n class=\"g-app-header__block-i\"\n role=\"img\"\n width=\"55\"\n viewBox=\"0 0 55 79\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <!-- NO LICENSE GRANTED for this logo, must have the right to use it -->\n <title>Block I logo</title>\n <path\n class=\"g-app-header__block-i-outline\"\n d=\"M54.2 21.1V0H0v21.1h12v36.1H0v21.1h54.2V57.2h-12V21.1z\"\n ></path>\n <path\n class=\"g-app-header__block-i-fill\"\n d=\"M42.1 18.1h9V3H3v15h9c1.7 0 3 1.3 3 3v36.1c0 1.7-1.3 3-3 3H3v15h48.1v-15h-9c-1.7 0-3-1.3-3-3v-36c0-1.7 1.4-3 3-3z\"\n ></path>\n </svg>\n </div>\n <slot v-else name=\"icon\"></slot>\n <div class=\"g-app-header__title\">\n <slot name=\"title\"></slot>\n </div>\n <div class=\"g-app-header__app-controls-wrap\">\n <slot name=\"app-controls\" class=\"g-app-header__app-controls\"></slot>\n </div>\n </header>\n</template>\n\n<style>\n\n@layer base {\n :root {\n --g-toolbar-height: 48px;\n }\n}\n\nhtml {\n scroll-padding-top: 70px;\n}\n\ng-app-header:not(:defined),\n.g-app-header {\n box-sizing: border-box;\n background-color: var(--g-surface-100);\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n z-index: 2;\n height: var(--g-toolbar-height);\n border-bottom: 2px solid var(--g-accent-500);\n box-shadow:\n 0px 1px 2px 0px rgba(0, 0, 0, 0.25),\n 0px 1px 10px 5px rgba(0, 0, 0, 0.08);\n}\n\ng-app-header:not(:defined) > [slot=\"title\"] {\n margin: 0;\n margin-left: calc(78px + 20px);\n}\n\ng-app-header:not(:defined)[illinois] > [slot=\"title\"] {\n margin-left: calc(78px + 48px + 20px);\n}\n\n.g-app-header {\n .g-app-header__title {\n display: flex;\n align-items: center;\n margin-left: 20px;\n flex: 1;\n }\n\n .g-app-header__title > * {\n font-size: 20px;\n font-family: var(--il-font-sans);\n font-style: normal;\n line-height: 30px;\n color: var(--g-primary-500);\n margin: 0;\n font-weight: 700;\n text-decoration: none;\n }\n\n .g-app-header__title {\n\n a {\n color: var(--g-primary-500);\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n }\n }\n }\n\n .g-app-header__app-controls-wrap {\n margin-right: 20px;\n }\n}\n\n.g-app-header {\n /*noinspection CssUnresolvedCustomProperty*/\n padding-right: var(--g-scrollbar-width, 0px);\n}\n\n.g-app-header__background {\n position: absolute;\n z-index: -1;\n top: 0;\n left: 0;\n bottom: 0;\n width: 470px;\n\n @media screen and (max-width: 640px) {\n width: 80%;\n }\n}\n\n.g-app-header__background-pattern {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBoZWlnaHQ9IjQ2IiB2aWV3Qm94PSIwIDAgNzEyLjkyNSA2NjkuMTY1Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTE4MTEuMzA3IiB5Mj0iLTIyODQuNDA1IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTM2MjkuODQ4IC0yNzk4LjAxNSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNjOGM2YzciLz48c3RvcCBvZmZzZXQ9Ii45ODEiIHN0b3AtY29sb3I9IiNmZmYiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImIiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTE1NTguNjU1IiB5Mj0iLTIwMzEuNzUzIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTM4MDguNSAtMjM2Ni43MTEpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iYyIgeDE9Ii03MTk2Ljk3NyIgeDI9Ii03MjA0LjIiIHkxPSItMTMwNi4wMDMiIHkyPSItMTc3OS4xMDEiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMi4wNDY1IDAgMCAtMSAxMzk4Ny4xNTIgLTE5MzUuNDA3KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImQiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTEwNTMuMzUxIiB5Mj0iLTE1MjYuNDQ5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQxNjUuODAzIC0xNTA0LjEwMykiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJlIiB4MT0iLTcxOTYuOTc3IiB4Mj0iLTcyMDQuMiIgeTE9Ii04MDAuNjk5IiB5Mj0iLTEyNzMuNzk3IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQzNDQuNDU1IC0xMDcyLjc5OSkiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJmIiB4MT0iLTcxOTYuOTc3IiB4Mj0iLTcyMDQuMiIgeTE9Ii01NDguMDQ3IiB5Mj0iLTEwMjEuMTQ0IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQ1MjMuMTA3IC02NDEuNDk1KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImciIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9Ii0yOTUuMzcxIiB5Mj0iLTc2OC40NjkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMi4wNDY1IDAgMCAtMSAxNDcxMC41NTUgLTIxMy43ODcpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iaCIgeDE9Ii03MTk5LjQ5IiB4Mj0iLTcyMDYuNzEzIiB5MT0iLTQyLjcxOSIgeTI9Ii01MTUuODE3IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQ4ODkuMjA3IDIxNy41MTcpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iaSIgeDE9Ii03MTk5LjQ5IiB4Mj0iLTcyMDYuNzEzIiB5MT0iMjA5LjkzMyIgeTI9Ii0yNjMuMTY1IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTUwNjcuODU5IDY0OC44MikiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJqIiB4MT0iLTcxOTkuNDkiIHgyPSItNzIwNi43MTMiIHkxPSI0NjIuNTg1IiB5Mj0iLTEwLjUxMyIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyLjA0NjUgMCAwIC0xIDE1MjQ2LjUxIDEwODAuMTI1KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImsiIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9IjcxNS4yMzciIHkyPSIyNDIuMTM5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTU0MjUuMTYyIDE1MTEuNDI5KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImwiIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9Ijk2Ny44ODkiIHkyPSI0OTQuNzkxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTU2MDMuODE0IDE5NDIuNzMzKSIvPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjY2xpcHBhdGgpIj48cGF0aCBmaWxsPSJ1cmwoI2EpIiBkPSJNLTI1NDYuNjc0LTk3OC41MzNIMzQwLjY4djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0xMTAyLjk5NyAtODUyLjIwOCkiLz48cGF0aCBmaWxsPSJ1cmwoI2IpIiBkPSJNLTIzNjguMDIyLTc5OS44ODFINTE5LjMzMnYyNTIuNjUyaC0yODg3LjM1NHoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSAtOTI0LjM0NSAtNjczLjU1NikiLz48cGF0aCBmaWxsPSJ1cmwoI2MpIiBkPSJNLTIxODkuMzctNjIxLjIyOUg2OTcuOTg0djI1Mi42NTJILTIxODkuMzd6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTc0NS42OTMgLTQ5NC45MDMpIi8+PHBhdGggZmlsbD0idXJsKCNkKSIgZD0iTS0yMDEwLjcxOC00NDIuNTc3SDg3Ni42MzZ2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTU2Ny4wNDEgLTMxNi4yNTEpIi8+PHBhdGggZmlsbD0idXJsKCNlKSIgZD0iTS0xODMyLjA2Ni0yNjMuOTI1aDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0zODguMzkgLTEzNy42KSIvPjxwYXRoIGZpbGw9InVybCgjZikiIGQ9Ik0tMTY1My40MTQtODUuMjczSDEyMzMuOTR2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTIwOS43MzcgNDEuMDUzKSIvPjxwYXRoIGZpbGw9InVybCgjZykiIGQ9Ik0tMTQ3MS4xMDkgODkuNzU5aDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0yNy40MzIgMjE2LjA4NSkiLz48cGF0aCBmaWxsPSJ1cmwoI2gpIiBkPSJNLTEyOTIuNDU3IDI2OC40MTFoMjg4Ny4zNTR2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgMTUxLjIyIDM5NC43MzcpIi8+PHBhdGggZmlsbD0idXJsKCNpKSIgZD0iTS0xMTEzLjgwNiA0NDcuMDYzaDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IDMyOS44NzEgNTczLjM4OSkiLz48cGF0aCBmaWxsPSJ1cmwoI2opIiBkPSJNLTkzNS4xNTQgNjI1LjcxNUgxOTUyLjJ2MjUyLjY1MkgtOTM1LjE1NHoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSA1MDguNTIzIDc1Mi4wNCkiLz48cGF0aCBmaWxsPSJ1cmwoI2spIiBkPSJNLTc1Ni41MDIgODA0LjM2N2gyODg3LjM1NHYyNTIuNjUySC03NTYuNTAyeiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IDY4Ny4xNzUgOTMwLjY5MykiLz48cGF0aCBmaWxsPSJ1cmwoI2wpIiBkPSJNLTU3Ny44NSA5ODMuMDE5aDI4ODcuMzU0djI1Mi42NTJILTU3Ny44NXoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSA4NjUuODI3IDExMDkuMzQ1KSIvPjwvZz48L3N2Zz4=');\n background-repeat: repeat;\n opacity: 0.5;\n}\n\n.g-app-header__background-gradient {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n background: linear-gradient(\n 90deg,\n transparent -15.89%,\n var(--g-surface-100) 95.85%\n );\n}\n\n.g-app-header__brand {\n min-width: 1rem;\n margin-top: 2px;\n\n & a {\n text-decoration: none;\n padding: 10px 15px;\n font-family: var(--il-font-montserrat);\n letter-spacing: 0.98px;\n font-size: 14px;\n font-style: normal;\n font-weight: 800;\n color: var(--g-primary-300);\n\n &:hover {\n text-decoration: underline;\n color: var(--g-primary-500);\n }\n\n @media screen and (max-width: 740px) {\n display: none;\n }\n }\n}\n\n.g-app-header__block-i-container {\n height: calc(var(--g-toolbar-height) + 3px);\n box-sizing: border-box;\n margin-top: 6px;\n box-shadow:\n 0px 0px 1px 1px rgba(0, 0, 0, 0.08),\n 2px 1px 10px 0px rgba(0, 0, 0, 0.35);\n}\n\n.g-app-header__app-controls {\n flex: 1;\n display: flex;\n justify-content: flex-end;\n padding: 0 10px;\n gap: 10px;\n}\n.g-app-header__block-i-container {\n background-color: var(--il-blue);\n min-width: 40px;\n padding: 8px 10px;\n\n .g-app-header__block-i {\n display: block;\n width: 24px;\n }\n\n .g-app-header__block-i-outline {\n fill: #fff;\n }\n\n .g-app-header__block-i-fill {\n fill: var(--il-orange);\n }\n}\n</style>\n\n","import { onBeforeUnmount, onMounted, Ref, ref, watch } from \"vue\";\nimport { useMediaQuery } from \"@vueuse/core\";\n\ntype SidebarChannel = {\n id: string;\n open: Ref<boolean>;\n isCollapsible: Ref<boolean>;\n toggle: () => void;\n};\n\nfunction normalizeKey(key: string) {\n return key.replace(/[^a-zA-Z0-9_-]/g, \"-\");\n}\n\nfunction getChannelsStore() {\n const globalScope = globalThis as typeof globalThis & {\n __GRAD_VUE_WC_SIDEBAR_CHANNELS__?: Map<string, SidebarChannel>;\n };\n\n if (!globalScope.__GRAD_VUE_WC_SIDEBAR_CHANNELS__) {\n globalScope.__GRAD_VUE_WC_SIDEBAR_CHANNELS__ = new Map();\n }\n\n return globalScope.__GRAD_VUE_WC_SIDEBAR_CHANNELS__;\n}\n\nexport function useWebComponentSidebar(\n key = \"default\",\n breakpoint: Ref<string> | string = \"(max-width: 800px)\",\n) {\n const channels = getChannelsStore();\n const channelKey = key || \"default\";\n\n if (!channels.has(channelKey)) {\n const safeKey = normalizeKey(channelKey);\n channels.set(channelKey, {\n id: `g-wc-sidebar-${safeKey}`,\n open: ref(false),\n isCollapsible: useMediaQuery(breakpoint, {\n ssrWidth: 1000,\n }),\n toggle: () => undefined,\n });\n }\n\n const channel = channels.get(channelKey)!;\n channel.toggle = () => (channel.open.value = !channel.open.value);\n\n function onDocumentClick(e: MouseEvent) {\n if (!channel.isCollapsible.value || !channel.open.value) {\n return;\n }\n const target = e.target as HTMLElement;\n const sidebarEl = document.getElementById(`${channel.id}-sidebar`);\n if (!sidebarEl) {\n return;\n }\n if (sidebarEl.contains(target)) {\n return;\n }\n setTimeout(() => {\n channel.open.value = false;\n }, 5);\n }\n\n function onDocumentFocus(e: FocusEvent) {\n if (!channel.isCollapsible.value || !channel.open.value) {\n return;\n }\n const target = e.target as HTMLElement;\n const sidebarEl = document.getElementById(`${channel.id}-sidebar`);\n const hamburgerEl = document.getElementById(`${channel.id}-hamburger`);\n if (!sidebarEl) {\n return;\n }\n if (sidebarEl.contains(target) || hamburgerEl?.contains(target)) {\n return;\n }\n setTimeout(() => {\n channel.open.value = false;\n }, 5);\n }\n\n onMounted(() => {\n watch(\n channel.isCollapsible,\n (val) => {\n if (val) {\n document.addEventListener(\"mousedown\", onDocumentClick);\n document.addEventListener(\"focusin\", onDocumentFocus);\n } else {\n document.removeEventListener(\"mousedown\", onDocumentClick);\n document.removeEventListener(\"focusin\", onDocumentFocus);\n }\n },\n { immediate: true },\n );\n });\n\n onBeforeUnmount(() => {\n document.removeEventListener(\"mousedown\", onDocumentClick);\n document.removeEventListener(\"focusin\", onDocumentFocus);\n });\n\n return channel;\n}\n","<script lang=\"ts\">\n/**\n * A simple sidebar that's fixed to the left side of the viewport.\n *\n * This includes the CSS for the `fixed` position and sizing, so the element\n * should be fairly high in the DOM tree.\n *\n * If neither `top-offset` nor `top-offset-var` are defined, the sidebar will be\n * offset by `var(--g-toolbar-height)`. If there is no toolbar, just pass\n * `0` as the `top-offset`.\n *\n * The sidebar can be made collapsible by providing the `sidebar` injected\n * object from `useSidebar`. See the [Hamburger Menu Documentation](#use-sidebar)\n * for details.\n *\n * In web components mode, use the `sidebar-key` prop to pair this sidebar\n * with a matching GHamburgerMenu instance.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, inject, useId } from \"vue\";\nimport { useSidebar } from \"../compose/useSidebar.ts\";\nimport { useWebComponentSidebar } from \"../compose/useWebComponentSidebar.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Custom background color\n * @demo\n */\n backgroundColor?: string;\n /**\n * Custom background image\n * @demo none\n */\n backgroundImage?: string;\n /**\n * Sidebar theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * Offset from the top of the viewport\n */\n topOffset?: string;\n /**\n * Top offset variable to use instead of topOffset\n */\n topOffsetVar?: string;\n /**\n * Width\n *\n * Width of the sidebar\n * @demo\n */\n width?: string;\n /**\n * Sidebar channel key for custom elements mode\n * @demo\n */\n sidebarKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n backgroundColor: \"\",\n backgroundImage: \"none\",\n theme: \"dark\",\n width: \"300px\",\n topOffset: \"\",\n topOffsetVar: \"\",\n sidebarKey: \"default\",\n});\n\nconst injectedSidebar = inject<ReturnType<typeof useSidebar>>(\n \"sidebar\",\n // This isn't required, so the default value just avoids compiler warnings\n () => undefined as any,\n true,\n);\n\nconst sidebar =\n injectedSidebar ??\n (isCustomElementMode() ? useWebComponentSidebar(props.sidebarKey) : undefined);\n\nconst bgImage = computed(() => {\n if (props.backgroundImage) {\n return props.backgroundImage;\n }\n if (props.theme === \"light\") {\n return \"none\";\n }\n return \"url('https://gradcdn.blob.core.windows.net/public/sidebar-bg2.jpg')\";\n});\n\nconst bgColor = computed(() => {\n if (props.backgroundColor) {\n return props.backgroundColor;\n }\n if (props.theme === \"light\") {\n return \"#f9f9f9\";\n }\n return \"#030913\";\n});\n\nconst topOff = computed(() => {\n if (props.topOffsetVar) {\n return `var(${props.topOffsetVar})`;\n }\n return props.topOffset ? props.topOffset : \"var(--g-toolbar-height)\";\n});\n\nconst fallbackId = useId();\n\nfunction handleEscapeKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n if (sidebar?.isCollapsible?.value && sidebar?.open?.value) {\n sidebar.open.value = false;\n document.getElementById(`${sidebar.id}-hamburger`)?.focus();\n }\n }\n}\n</script>\n\n<template>\n <div\n ref=\"sidebar-ref\"\n :id=\"`${sidebar?.id ?? fallbackId}-sidebar`\"\n class=\"g-sidebar\"\n :class=\"[\n `g-sidebar__${theme}`,\n {\n 'g-sidebar--collapsible': sidebar?.isCollapsible?.value,\n 'g-sidebar--closed':\n !sidebar?.open?.value && sidebar?.isCollapsible?.value,\n 'g-sidebar--open':\n sidebar?.open?.value && sidebar?.isCollapsible?.value,\n },\n ]\"\n :style=\"{\n backgroundImage: bgImage,\n backgroundColor: bgColor,\n '--g-sidebar-top-offset': topOff,\n '--g-sidebar-width': width ?? '300px',\n width: 'var(--g-sidebar-width)',\n }\"\n @keydown=\"handleEscapeKey\"\n >\n <slot></slot>\n </div>\n</template>\n\n<style>\ng-sidebar:not(:defined),\n.g-sidebar {\n box-sizing: border-box;\n background-size: cover;\n background-position: top;\n background-image: url('https://gradcdn.blob.core.windows.net/public/sidebar-bg2.jpg');\n background-color: #030913;\n position: fixed;\n left: 0;\n /*noinspection CssUnresolvedCustomProperty*/\n top: var(--g-sidebar-top-offset, var(--g-toolbar-height));\n bottom: 0;\n /*noinspection CssUnresolvedCustomProperty*/\n width: var(--g-sidebar-width, 300px);\n overflow-y: auto;\n}\n\ng-sidebar[theme=\"light\"] {\n background-image: none;\n background-color: #f9f9f9;\n}\n\n.g-sidebar--open {\n transition: opacity 0.1s ease-out;\n opacity: 1;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-sidebar--open {\n transition: none;\n }\n}\n\n.g-sidebar--closed {\n display: none;\n}\n.g-sidebar--collapsible {\n height: 100vh;\n top: 0;\n z-index: 198;\n box-shadow:\n 0 2px 10px rgba(0, 0, 0, 0.1),\n 0 0 10px rgba(0, 0, 0, 0.1);\n}\n@starting-style {\n .g-sidebar {\n opacity: 0;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A simple sidebar that's fixed to the left side of the viewport.\n *\n * This includes the CSS for the `fixed` position and sizing, so the element\n * should be fairly high in the DOM tree.\n *\n * If neither `top-offset` nor `top-offset-var` are defined, the sidebar will be\n * offset by `var(--g-toolbar-height)`. If there is no toolbar, just pass\n * `0` as the `top-offset`.\n *\n * The sidebar can be made collapsible by providing the `sidebar` injected\n * object from `useSidebar`. See the [Hamburger Menu Documentation](#use-sidebar)\n * for details.\n *\n * In web components mode, use the `sidebar-key` prop to pair this sidebar\n * with a matching GHamburgerMenu instance.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, inject, useId } from \"vue\";\nimport { useSidebar } from \"../compose/useSidebar.ts\";\nimport { useWebComponentSidebar } from \"../compose/useWebComponentSidebar.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Custom background color\n * @demo\n */\n backgroundColor?: string;\n /**\n * Custom background image\n * @demo none\n */\n backgroundImage?: string;\n /**\n * Sidebar theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * Offset from the top of the viewport\n */\n topOffset?: string;\n /**\n * Top offset variable to use instead of topOffset\n */\n topOffsetVar?: string;\n /**\n * Width\n *\n * Width of the sidebar\n * @demo\n */\n width?: string;\n /**\n * Sidebar channel key for custom elements mode\n * @demo\n */\n sidebarKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n backgroundColor: \"\",\n backgroundImage: \"none\",\n theme: \"dark\",\n width: \"300px\",\n topOffset: \"\",\n topOffsetVar: \"\",\n sidebarKey: \"default\",\n});\n\nconst injectedSidebar = inject<ReturnType<typeof useSidebar>>(\n \"sidebar\",\n // This isn't required, so the default value just avoids compiler warnings\n () => undefined as any,\n true,\n);\n\nconst sidebar =\n injectedSidebar ??\n (isCustomElementMode() ? useWebComponentSidebar(props.sidebarKey) : undefined);\n\nconst bgImage = computed(() => {\n if (props.backgroundImage) {\n return props.backgroundImage;\n }\n if (props.theme === \"light\") {\n return \"none\";\n }\n return \"url('https://gradcdn.blob.core.windows.net/public/sidebar-bg2.jpg')\";\n});\n\nconst bgColor = computed(() => {\n if (props.backgroundColor) {\n return props.backgroundColor;\n }\n if (props.theme === \"light\") {\n return \"#f9f9f9\";\n }\n return \"#030913\";\n});\n\nconst topOff = computed(() => {\n if (props.topOffsetVar) {\n return `var(${props.topOffsetVar})`;\n }\n return props.topOffset ? props.topOffset : \"var(--g-toolbar-height)\";\n});\n\nconst fallbackId = useId();\n\nfunction handleEscapeKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n if (sidebar?.isCollapsible?.value && sidebar?.open?.value) {\n sidebar.open.value = false;\n document.getElementById(`${sidebar.id}-hamburger`)?.focus();\n }\n }\n}\n</script>\n\n<template>\n <div\n ref=\"sidebar-ref\"\n :id=\"`${sidebar?.id ?? fallbackId}-sidebar`\"\n class=\"g-sidebar\"\n :class=\"[\n `g-sidebar__${theme}`,\n {\n 'g-sidebar--collapsible': sidebar?.isCollapsible?.value,\n 'g-sidebar--closed':\n !sidebar?.open?.value && sidebar?.isCollapsible?.value,\n 'g-sidebar--open':\n sidebar?.open?.value && sidebar?.isCollapsible?.value,\n },\n ]\"\n :style=\"{\n backgroundImage: bgImage,\n backgroundColor: bgColor,\n '--g-sidebar-top-offset': topOff,\n '--g-sidebar-width': width ?? '300px',\n width: 'var(--g-sidebar-width)',\n }\"\n @keydown=\"handleEscapeKey\"\n >\n <slot></slot>\n </div>\n</template>\n\n<style>\ng-sidebar:not(:defined),\n.g-sidebar {\n box-sizing: border-box;\n background-size: cover;\n background-position: top;\n background-image: url('https://gradcdn.blob.core.windows.net/public/sidebar-bg2.jpg');\n background-color: #030913;\n position: fixed;\n left: 0;\n /*noinspection CssUnresolvedCustomProperty*/\n top: var(--g-sidebar-top-offset, var(--g-toolbar-height));\n bottom: 0;\n /*noinspection CssUnresolvedCustomProperty*/\n width: var(--g-sidebar-width, 300px);\n overflow-y: auto;\n}\n\ng-sidebar[theme=\"light\"] {\n background-image: none;\n background-color: #f9f9f9;\n}\n\n.g-sidebar--open {\n transition: opacity 0.1s ease-out;\n opacity: 1;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-sidebar--open {\n transition: none;\n }\n}\n\n.g-sidebar--closed {\n display: none;\n}\n.g-sidebar--collapsible {\n height: 100vh;\n top: 0;\n z-index: 198;\n box-shadow:\n 0 2px 10px rgba(0, 0, 0, 0.1),\n 0 0 10px rgba(0, 0, 0, 0.1);\n}\n@starting-style {\n .g-sidebar {\n opacity: 0;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A sidebar menu component for use with `GSidebar`. Displays a title and\n * a list of links.\n *\n * This component also supports showing active links for in-page navigation.\n *\n * **Props**:\n *\n * - `items` with a list of `MenuItem` objects. The objects should have:\n * - `label` for the link text\n * - `href` or `to` for the destination. If `to` is used, the links will\n * be rendered as `router-link` for vue-router.\n * - `spy` to enable tracking active links for in-page navigation\n * - `offset` to adjust the active link tracking position\n * - `theme` to set the menu theme\n *\n * For tracking the active link, the `spy` prop must be set to `true` and\n * a `v-model` must be provided that has the ID of the target element (without #).\n *\n * The composable function `useActiveLinkContent` can be used to track active links.\n * It takes the following parameters:\n *\n * - `Ref<HTMLElement>` Children of this element will be observed\n * - `number` Offset from the top of the window to consider not visible\n * - `Ref<string>` Ref to store the active element ID\n *\n * The direct children of the element must all have an ID to properly work with\n * in-page navigation, and the matching menu item's `href` should be set to\n * `#<id>`.\n *\n * Here's a minimal example of a page using `useActiveLinkContent`:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * import { computed, onMounted, ref, useTemplateRef } from \"vue\";\n * import { useActiveLinkContent } from \"@illinois-grad/grad-vue\";\n *\n * const activeId = ref<string>(\"\");\n * const main = useTemplateRef(\"main\");\n * // onMounted is for Nuxt compatibility\n * onMounted(() => {\n * useActiveLinkContent(main, 70, activeId);\n * });\n * &lt;/script>\n *\n * <template>\n * <GSidebar>\n * <GSidebarMenu\n * :items=\"[\n * { label: 'Buttons', href: '#buttons' },\n * { label: 'More Buttons', href: '#more-buttons' }\n * ]\"\n * v-model=\"activeId\"\n * />\n * </GSidebar>\n * <main class=\"main\" ref=\"main\">\n * <section id=\"buttons\">\n * <h2>Buttons</h2>\n * <p>Some buttons</p>\n * </section>\n * <section id=\"more-buttons\">\n * <h2>More Buttons</h2>\n * <p>Some more buttons</p>\n * </section>\n * </main>\n * </template>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n getCurrentInstance,\n nextTick,\n onMounted,\n useId,\n useTemplateRef,\n watch,\n} from \"vue\";\n\ntype MenuItem = {\n label: string;\n href?: string;\n to?: string;\n};\n\ntype Props = {\n /**\n * Heading and accessible name\n * @demo Sidebar Menu\n */\n heading?: string;\n /**\n * Items for the menu\n */\n items: MenuItem[];\n /**\n * Offset for tracking active position to account for toolbars\n */\n offset?: number;\n /**\n * Track active position for in-page links\n */\n spy?: boolean;\n /**\n * Sidebar theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * Use compact layout\n * @demo\n */\n compact?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n offset: 70,\n spy: true,\n theme: \"light\",\n compact: false,\n});\n\nconst activeId = defineModel<string | null>({ default: null, type: String });\n\nconst activeLink = computed(() => {\n if (props.spy && activeId.value) {\n return \"#\" + activeId.value;\n }\n return null;\n});\n\nconst content = useTemplateRef(\"content\");\n\nonMounted(() => {\n watch(\n activeId,\n () => {\n nextTick(() => {\n const activeItem = content.value?.querySelector<HTMLElement>(\n `.g-sidebar-menu__is-active`,\n );\n if (activeItem) {\n activeItem.scrollIntoView({ block: \"nearest\" });\n }\n });\n },\n { immediate: true },\n );\n});\n\n// Detect vue-router without adding it as a dependency\nconst instance = getCurrentInstance();\nconst RouterLinkComp = computed<any | null>(() => {\n const comp = instance?.appContext?.components?.RouterLink as\n | any\n | undefined;\n return comp ?? null;\n});\n\nfunction onLinkClick(e: MouseEvent, item: MenuItem) {\n // Only handle in-page hash links here\n if (!item.href || !item.href.startsWith(\"#\")) {\n return;\n }\n\n const id = item.href.slice(1);\n const el = document\n .getElementById(id)\n ?.querySelector<HTMLElement>(\"h2, h3, h4, h5\");\n if (!el) {\n return;\n }\n e.preventDefault();\n el.setAttribute(\"tabindex\", \"-1\");\n el.focus();\n el.scrollIntoView({ block: \"start\" });\n\n history.replaceState(null, \"\", item.href);\n}\n\nconst id = useId();\n</script>\n\n<template>\n <nav\n class=\"g-sidebar-menu\"\n :class=\"[\n `g-sidebar-menu__${props.theme}`,\n { 'g-sidebar-menu--compact': props.compact },\n ]\"\n v-bind=\"{\n 'aria-labelledby': heading ? id : undefined,\n 'aria-label': heading ? undefined : 'Sidebar Menu',\n }\"\n >\n <h2 v-if=\"heading\" :id=\"id\" class=\"g-sidebar-menu__title\">{{ heading }}</h2>\n <div class=\"g-sidebar-menu__divider\"></div>\n <div class=\"g-sidebar-menu__content\" ref=\"content\">\n <ul class=\"g-sidebar-menu__list\">\n <li\n v-for=\"item in items\"\n :key=\"item.href || item.to\"\n class=\"g-sidebar-menu__item\"\n ref=\"listItems\"\n >\n <!-- Prefer RouterLink when available and item uses 'to' -->\n <component\n v-if=\"item.to && RouterLinkComp\"\n :is=\"RouterLinkComp\"\n class=\"g-sidebar-menu__link\"\n :to=\"item.to\"\n >\n {{ item.label }}\n </component>\n <a\n v-else\n class=\"g-sidebar-menu__link\"\n :href=\"item.href || item.to || '#'\"\n :class=\"{\n 'g-sidebar-menu__is-active':\n activeLink === (item.href || ''),\n }\"\n :aria-current=\"\n activeLink === (item.href || '')\n ? 'location'\n : undefined\n \"\n @click=\"(e) => onLinkClick(e, item)\"\n >\n {{ item.label }}\n </a>\n </li>\n </ul>\n </div>\n </nav>\n</template>\n\n<style>\ng-sidebar-menu,\n.g-sidebar-menu {\n box-sizing: border-box;\n padding-top: 2rem;\n display: flex;\n flex-direction: column;\n}\n.g-sidebar-menu {\n color: var(--g-surface-0);\n}\n.g-sidebar-menu__title {\n margin: 2rem 2rem 0.5rem;\n font-size: 2rem;\n font-family: var(--il-font-heading);\n color: var(--g-surface-0);\n}\n.g-sidebar-menu__divider {\n margin-left: 2rem;\n margin-top: 2px;\n height: 4px;\n width: 60px;\n flex: 0 0 4px;\n background: var(--g-accent-500);\n}\n.g-sidebar-menu__content {\n margin: 0 0 0;\n}\n.g-sidebar-menu__list {\n list-style: none;\n margin: 1rem 0 0;\n padding: 0;\n}\n.g-sidebar-menu__item {\n display: block;\n margin: 0;\n}\n.g-sidebar-menu__link {\n display: block;\n padding: 0.5rem calc(2rem - 8px);\n border-left: 8px solid transparent;\n color: var(--g-surface-0);\n text-decoration: none;\n font-size: 1.25rem;\n font-weight: bold;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-500);\n }\n\n &.g-sidebar-menu__is-active {\n background: var(--g-primary-500);\n border-left: 8px solid var(--g-accent-500);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n}\n.g-sidebar-menu--compact {\n .g-sidebar-menu__link {\n padding: 0.1rem calc(2rem - 8px);\n font-size: 1.125rem;\n font-weight: 700;\n }\n}\n\n.g-sidebar-menu__light {\n background: var(--g-surface-50);\n\n .g-sidebar-menu__title,\n .g-sidebar-menu__link {\n color: var(--g-primary-500);\n }\n\n .g-sidebar-menu__link {\n &:hover {\n color: var(--g-accent-700);\n }\n\n &.g-sidebar-menu__is-active {\n background: var(--g-accent-500);\n color: var(--g-surface-0);\n border-left: 8px solid var(--g-primary-500);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A sidebar menu component for use with `GSidebar`. Displays a title and\n * a list of links.\n *\n * This component also supports showing active links for in-page navigation.\n *\n * **Props**:\n *\n * - `items` with a list of `MenuItem` objects. The objects should have:\n * - `label` for the link text\n * - `href` or `to` for the destination. If `to` is used, the links will\n * be rendered as `router-link` for vue-router.\n * - `spy` to enable tracking active links for in-page navigation\n * - `offset` to adjust the active link tracking position\n * - `theme` to set the menu theme\n *\n * For tracking the active link, the `spy` prop must be set to `true` and\n * a `v-model` must be provided that has the ID of the target element (without #).\n *\n * The composable function `useActiveLinkContent` can be used to track active links.\n * It takes the following parameters:\n *\n * - `Ref<HTMLElement>` Children of this element will be observed\n * - `number` Offset from the top of the window to consider not visible\n * - `Ref<string>` Ref to store the active element ID\n *\n * The direct children of the element must all have an ID to properly work with\n * in-page navigation, and the matching menu item's `href` should be set to\n * `#<id>`.\n *\n * Here's a minimal example of a page using `useActiveLinkContent`:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * import { computed, onMounted, ref, useTemplateRef } from \"vue\";\n * import { useActiveLinkContent } from \"@illinois-grad/grad-vue\";\n *\n * const activeId = ref<string>(\"\");\n * const main = useTemplateRef(\"main\");\n * // onMounted is for Nuxt compatibility\n * onMounted(() => {\n * useActiveLinkContent(main, 70, activeId);\n * });\n * &lt;/script>\n *\n * <template>\n * <GSidebar>\n * <GSidebarMenu\n * :items=\"[\n * { label: 'Buttons', href: '#buttons' },\n * { label: 'More Buttons', href: '#more-buttons' }\n * ]\"\n * v-model=\"activeId\"\n * />\n * </GSidebar>\n * <main class=\"main\" ref=\"main\">\n * <section id=\"buttons\">\n * <h2>Buttons</h2>\n * <p>Some buttons</p>\n * </section>\n * <section id=\"more-buttons\">\n * <h2>More Buttons</h2>\n * <p>Some more buttons</p>\n * </section>\n * </main>\n * </template>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n getCurrentInstance,\n nextTick,\n onMounted,\n useId,\n useTemplateRef,\n watch,\n} from \"vue\";\n\ntype MenuItem = {\n label: string;\n href?: string;\n to?: string;\n};\n\ntype Props = {\n /**\n * Heading and accessible name\n * @demo Sidebar Menu\n */\n heading?: string;\n /**\n * Items for the menu\n */\n items: MenuItem[];\n /**\n * Offset for tracking active position to account for toolbars\n */\n offset?: number;\n /**\n * Track active position for in-page links\n */\n spy?: boolean;\n /**\n * Sidebar theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * Use compact layout\n * @demo\n */\n compact?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n offset: 70,\n spy: true,\n theme: \"light\",\n compact: false,\n});\n\nconst activeId = defineModel<string | null>({ default: null, type: String });\n\nconst activeLink = computed(() => {\n if (props.spy && activeId.value) {\n return \"#\" + activeId.value;\n }\n return null;\n});\n\nconst content = useTemplateRef(\"content\");\n\nonMounted(() => {\n watch(\n activeId,\n () => {\n nextTick(() => {\n const activeItem = content.value?.querySelector<HTMLElement>(\n `.g-sidebar-menu__is-active`,\n );\n if (activeItem) {\n activeItem.scrollIntoView({ block: \"nearest\" });\n }\n });\n },\n { immediate: true },\n );\n});\n\n// Detect vue-router without adding it as a dependency\nconst instance = getCurrentInstance();\nconst RouterLinkComp = computed<any | null>(() => {\n const comp = instance?.appContext?.components?.RouterLink as\n | any\n | undefined;\n return comp ?? null;\n});\n\nfunction onLinkClick(e: MouseEvent, item: MenuItem) {\n // Only handle in-page hash links here\n if (!item.href || !item.href.startsWith(\"#\")) {\n return;\n }\n\n const id = item.href.slice(1);\n const el = document\n .getElementById(id)\n ?.querySelector<HTMLElement>(\"h2, h3, h4, h5\");\n if (!el) {\n return;\n }\n e.preventDefault();\n el.setAttribute(\"tabindex\", \"-1\");\n el.focus();\n el.scrollIntoView({ block: \"start\" });\n\n history.replaceState(null, \"\", item.href);\n}\n\nconst id = useId();\n</script>\n\n<template>\n <nav\n class=\"g-sidebar-menu\"\n :class=\"[\n `g-sidebar-menu__${props.theme}`,\n { 'g-sidebar-menu--compact': props.compact },\n ]\"\n v-bind=\"{\n 'aria-labelledby': heading ? id : undefined,\n 'aria-label': heading ? undefined : 'Sidebar Menu',\n }\"\n >\n <h2 v-if=\"heading\" :id=\"id\" class=\"g-sidebar-menu__title\">{{ heading }}</h2>\n <div class=\"g-sidebar-menu__divider\"></div>\n <div class=\"g-sidebar-menu__content\" ref=\"content\">\n <ul class=\"g-sidebar-menu__list\">\n <li\n v-for=\"item in items\"\n :key=\"item.href || item.to\"\n class=\"g-sidebar-menu__item\"\n ref=\"listItems\"\n >\n <!-- Prefer RouterLink when available and item uses 'to' -->\n <component\n v-if=\"item.to && RouterLinkComp\"\n :is=\"RouterLinkComp\"\n class=\"g-sidebar-menu__link\"\n :to=\"item.to\"\n >\n {{ item.label }}\n </component>\n <a\n v-else\n class=\"g-sidebar-menu__link\"\n :href=\"item.href || item.to || '#'\"\n :class=\"{\n 'g-sidebar-menu__is-active':\n activeLink === (item.href || ''),\n }\"\n :aria-current=\"\n activeLink === (item.href || '')\n ? 'location'\n : undefined\n \"\n @click=\"(e) => onLinkClick(e, item)\"\n >\n {{ item.label }}\n </a>\n </li>\n </ul>\n </div>\n </nav>\n</template>\n\n<style>\ng-sidebar-menu,\n.g-sidebar-menu {\n box-sizing: border-box;\n padding-top: 2rem;\n display: flex;\n flex-direction: column;\n}\n.g-sidebar-menu {\n color: var(--g-surface-0);\n}\n.g-sidebar-menu__title {\n margin: 2rem 2rem 0.5rem;\n font-size: 2rem;\n font-family: var(--il-font-heading);\n color: var(--g-surface-0);\n}\n.g-sidebar-menu__divider {\n margin-left: 2rem;\n margin-top: 2px;\n height: 4px;\n width: 60px;\n flex: 0 0 4px;\n background: var(--g-accent-500);\n}\n.g-sidebar-menu__content {\n margin: 0 0 0;\n}\n.g-sidebar-menu__list {\n list-style: none;\n margin: 1rem 0 0;\n padding: 0;\n}\n.g-sidebar-menu__item {\n display: block;\n margin: 0;\n}\n.g-sidebar-menu__link {\n display: block;\n padding: 0.5rem calc(2rem - 8px);\n border-left: 8px solid transparent;\n color: var(--g-surface-0);\n text-decoration: none;\n font-size: 1.25rem;\n font-weight: bold;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-500);\n }\n\n &.g-sidebar-menu__is-active {\n background: var(--g-primary-500);\n border-left: 8px solid var(--g-accent-500);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n}\n.g-sidebar-menu--compact {\n .g-sidebar-menu__link {\n padding: 0.1rem calc(2rem - 8px);\n font-size: 1.125rem;\n font-weight: 700;\n }\n}\n\n.g-sidebar-menu__light {\n background: var(--g-surface-50);\n\n .g-sidebar-menu__title,\n .g-sidebar-menu__link {\n color: var(--g-primary-500);\n }\n\n .g-sidebar-menu__link {\n &:hover {\n color: var(--g-accent-700);\n }\n\n &.g-sidebar-menu__is-active {\n background: var(--g-accent-500);\n color: var(--g-surface-0);\n border-left: 8px solid var(--g-primary-500);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n }\n}\n</style>\n\n","import type { Directive, DirectiveBinding } from \"vue\";\nimport { ref, watchEffect } from \"vue\";\nimport {\n appendTooltipEl,\n createTooltipEl,\n hideTooltip,\n resolveTooltipId,\n showTooltip,\n} from \"../compose/tooltipDom.ts\";\n\nexport type VGtooltipDirective = Directive<HTMLElement, string>;\n\nconst VGtooltip: VGtooltipDirective = {\n mounted(el: HTMLElement, binding: DirectiveBinding) {\n const tooltip = ref<HTMLElement | null>(null);\n const isHovered = ref(false);\n const isFocused = ref(false);\n const tooltipText = ref(binding.value);\n let resizeObserver: ResizeObserver | null = null;\n let scrollListenerActive = false;\n // Generate unique id for tooltip\n const tooltipId = resolveTooltipId(el);\n\n const ensureTooltip = () => {\n if (!tooltip.value) {\n tooltip.value = createTooltipEl(tooltipText.value, tooltipId);\n // Append to #modal-root (with document.body fallback) so the tooltip\n // shares the same DOM container as GModal and GPopover. This ensures\n // consistent z-index stacking context and avoids CSS transform issues\n // when the trigger is inside a transformed ancestor (e.g., GModal uses\n // transform: translate(-50%, -50%) for centering).\n appendTooltipEl(tooltip.value);\n resizeObserver = new ResizeObserver(() => {\n if (tooltip.value && (isHovered.value || isFocused.value)) {\n showTooltip(el, tooltip.value);\n }\n });\n resizeObserver.observe(tooltip.value);\n }\n };\n\n watchEffect(() => {\n if (tooltip.value) {\n tooltip.value.textContent = tooltipText.value;\n }\n });\n\n // Watch for changes in hover and focus states,\n // and show/hide the tooltip accordingly\n const onScroll = () => {\n if (tooltip.value && (isHovered.value || isFocused.value)) {\n showTooltip(el, tooltip.value);\n }\n };\n\n watchEffect(() => {\n if (isHovered.value || isFocused.value) {\n ensureTooltip();\n if (tooltip.value) {\n showTooltip(el, tooltip.value);\n }\n if (!scrollListenerActive) {\n window.addEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = true;\n }\n } else {\n if (scrollListenerActive) {\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = false;\n }\n if (tooltip.value) {\n hideTooltip(tooltip.value);\n\n setTimeout(() => {\n // After the fade out, emit an event in case the user needs\n // to perform any cleanup\n el.dispatchEvent(new CustomEvent(\"tooltip-hide\"));\n }, 150);\n }\n }\n });\n\n const onMouseEnter = () => {\n isHovered.value = true;\n };\n const onMouseLeave = () => {\n isHovered.value = false;\n };\n const onFocus = () => {\n isFocused.value = true;\n };\n const onBlur = () => {\n isFocused.value = false;\n };\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" || e.key === \"Esc\") {\n isHovered.value = false;\n isFocused.value = false;\n }\n };\n\n el.addEventListener(\"mouseenter\", onMouseEnter);\n el.addEventListener(\"mouseleave\", onMouseLeave);\n el.addEventListener(\"focus\", onFocus);\n el.addEventListener(\"blur\", onBlur);\n el.addEventListener(\"keydown\", onKeyDown);\n\n ensureTooltip();\n\n // Since this is a directive, we need to store the variables on the element\n (el as any)._v_gtooltip = {\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n onKeyDown,\n onScroll,\n tooltip,\n tooltipText,\n isHovered,\n isFocused,\n resizeObserver,\n tooltipId,\n };\n },\n updated(el: HTMLElement, binding: DirectiveBinding) {\n const data = (el as any)._v_gtooltip;\n if (data && data.tooltipText) {\n data.tooltipText.value = binding.value;\n }\n },\n unmounted(el: HTMLElement) {\n const data = (el as any)._v_gtooltip;\n if (data && data.tooltip && data.tooltip.value) {\n if (data.resizeObserver) {\n data.resizeObserver.disconnect();\n }\n data.tooltip.value.remove();\n data.tooltip.value = null;\n }\n if (data && data.onScroll) {\n window.removeEventListener(\"scroll\", data.onScroll, { capture: true });\n }\n el.removeEventListener(\"mouseenter\", data.onMouseEnter);\n el.removeEventListener(\"mouseleave\", data.onMouseLeave);\n el.removeEventListener(\"focus\", data.onFocus);\n el.removeEventListener(\"blur\", data.onBlur);\n el.removeEventListener(\"keydown\", data.onKeyDown);\n el.removeAttribute(\"aria-describedby\");\n },\n};\n\nexport default VGtooltip;\n","<script lang=\"ts\">\n/**\n * Displays text with a clipboard button that copies the text to the clipboard.\n * The text can be hidden in cases where it's already displayed differently.\n *\n * If for some reason the user's browser doesn't support a clipboard,\n * the tooltip will indicate that when they try to copy.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useClipboard } from \"@vueuse/core\";\nimport { ref } from \"vue\";\nimport VGtooltip from \"../directives/v-gtooltip.ts\";\n\ntype Props = {\n /**\n * Text\n * @demo This is some text to get copied\n */\n text: string;\n\n /**\n * Hide the visible text\n * @demo\n */\n hideText?: boolean;\n\n /**\n * Copy button label\n * @demo\n */\n copyLabel?: string;\n}\n\nconst props = defineProps<Props>();\n\nconst vGtooltip = VGtooltip;\n\nconst { text, copy, copied, isSupported } = useClipboard({\n source: props.text,\n});\n\nconst tooltip = ref<string>(props.copyLabel ?? \"Copy to clipboard\");\n\nconst handleCopy = () => {\n if (isSupported.value) {\n copy();\n tooltip.value = \"Copied\";\n } else {\n tooltip.value = \"Copy not supported\";\n }\n};\nconst handleTooltipHide = () => {\n tooltip.value = props.copyLabel ?? \"Copy to clipboard\";\n};\n</script>\n\n<template>\n <div class=\"g-clipboard-text\">\n <template v-if=\"!hideText\">{{ props.text }}</template>\n\n <button\n type=\"button\"\n aria-label=\"Copy\"\n @click=\"handleCopy\"\n v-gtooltip=\"tooltip\"\n @tooltip-hide=\"handleTooltipHide\"\n class=\"g-clipboard-text-button\"\n >\n <svg\n class=\"g-clipboard-svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.125rem\"\n role=\"none presentation\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M480 400L288 400C279.2 400 272 392.8 272 384L272 128C272 119.2 279.2 112 288 112L421.5 112C425.7 112 429.8 113.7 432.8 116.7L491.3 175.2C494.3 178.2 496 182.3 496 186.5L496 384C496 392.8 488.8 400 480 400zM288 448L480 448C515.3 448 544 419.3 544 384L544 186.5C544 169.5 537.3 153.2 525.3 141.2L466.7 82.7C454.7 70.7 438.5 64 421.5 64L288 64C252.7 64 224 92.7 224 128L224 384C224 419.3 252.7 448 288 448zM160 192C124.7 192 96 220.7 96 256L96 512C96 547.3 124.7 576 160 576L352 576C387.3 576 416 547.3 416 512L416 496L368 496L368 512C368 520.8 360.8 528 352 528L160 528C151.2 528 144 520.8 144 512L144 256C144 247.2 151.2 240 160 240L176 240L176 192L160 192z\"\n />\n </svg>\n </button>\n </div>\n</template>\n\n<style>\ng-clipboard {\n display: inline-block;\n}\n.g-clipboard-text-button {\n display: inline-flex;\n align-items: center;\n margin-left: 0.5rem;\n cursor: pointer;\n color: var(--g-surface-0);\n background: var(--g-primary-500);\n border: 1px solid var(--g-primary-500);\n border-radius: 4px;\n padding: 0.4rem;\n font-size: 14px;\n\n .g-clipboard-svg {\n display: block;\n }\n\n &:hover {\n color: var(--g-primary-500);\n background: var(--g-surface-0);\n }\n\n &:focus {\n color: var(--g-primary-500);\n background: var(--g-info-200);\n outline-color: var(--g-primary-500);\n }\n\n span {\n display: block;\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Displays text with a clipboard button that copies the text to the clipboard.\n * The text can be hidden in cases where it's already displayed differently.\n *\n * If for some reason the user's browser doesn't support a clipboard,\n * the tooltip will indicate that when they try to copy.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useClipboard } from \"@vueuse/core\";\nimport { ref } from \"vue\";\nimport VGtooltip from \"../directives/v-gtooltip.ts\";\n\ntype Props = {\n /**\n * Text\n * @demo This is some text to get copied\n */\n text: string;\n\n /**\n * Hide the visible text\n * @demo\n */\n hideText?: boolean;\n\n /**\n * Copy button label\n * @demo\n */\n copyLabel?: string;\n}\n\nconst props = defineProps<Props>();\n\nconst vGtooltip = VGtooltip;\n\nconst { text, copy, copied, isSupported } = useClipboard({\n source: props.text,\n});\n\nconst tooltip = ref<string>(props.copyLabel ?? \"Copy to clipboard\");\n\nconst handleCopy = () => {\n if (isSupported.value) {\n copy();\n tooltip.value = \"Copied\";\n } else {\n tooltip.value = \"Copy not supported\";\n }\n};\nconst handleTooltipHide = () => {\n tooltip.value = props.copyLabel ?? \"Copy to clipboard\";\n};\n</script>\n\n<template>\n <div class=\"g-clipboard-text\">\n <template v-if=\"!hideText\">{{ props.text }}</template>\n\n <button\n type=\"button\"\n aria-label=\"Copy\"\n @click=\"handleCopy\"\n v-gtooltip=\"tooltip\"\n @tooltip-hide=\"handleTooltipHide\"\n class=\"g-clipboard-text-button\"\n >\n <svg\n class=\"g-clipboard-svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.125rem\"\n role=\"none presentation\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M480 400L288 400C279.2 400 272 392.8 272 384L272 128C272 119.2 279.2 112 288 112L421.5 112C425.7 112 429.8 113.7 432.8 116.7L491.3 175.2C494.3 178.2 496 182.3 496 186.5L496 384C496 392.8 488.8 400 480 400zM288 448L480 448C515.3 448 544 419.3 544 384L544 186.5C544 169.5 537.3 153.2 525.3 141.2L466.7 82.7C454.7 70.7 438.5 64 421.5 64L288 64C252.7 64 224 92.7 224 128L224 384C224 419.3 252.7 448 288 448zM160 192C124.7 192 96 220.7 96 256L96 512C96 547.3 124.7 576 160 576L352 576C387.3 576 416 547.3 416 512L416 496L368 496L368 512C368 520.8 360.8 528 352 528L160 528C151.2 528 144 520.8 144 512L144 256C144 247.2 151.2 240 160 240L176 240L176 192L160 192z\"\n />\n </svg>\n </button>\n </div>\n</template>\n\n<style>\ng-clipboard {\n display: inline-block;\n}\n.g-clipboard-text-button {\n display: inline-flex;\n align-items: center;\n margin-left: 0.5rem;\n cursor: pointer;\n color: var(--g-surface-0);\n background: var(--g-primary-500);\n border: 1px solid var(--g-primary-500);\n border-radius: 4px;\n padding: 0.4rem;\n font-size: 14px;\n\n .g-clipboard-svg {\n display: block;\n }\n\n &:hover {\n color: var(--g-primary-500);\n background: var(--g-surface-0);\n }\n\n &:focus {\n color: var(--g-primary-500);\n background: var(--g-info-200);\n outline-color: var(--g-primary-500);\n }\n\n span {\n display: block;\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A scroller that is used for content that's typically shown like a chat log,\n * meaning it starts at the bottom, and you scroll up for older entries.\n *\n * The scroller automatically starts at the bottom. When scrolled up, a button\n * appears that jumps to the bottom. This button is also the first focusable\n * element for accessibility.\n *\n * If the `label` is provided, the scroller will have the ARIA `role=\"log\"` and\n * the label as the `aria-label`.\n *\n * **Slot** `default` is what will be rendered for each entry in the\n * `entries`. For example:\n *\n * ```vue-html\n * <GHistoryScroller\n * v-bind=\"props\"\n * :entries=\"historyEntries\"\n * class=\"history-scroller\">\n * <template #default=\"{ entry }\">\n * <div class=\"history-entry\">\n * This is history for: {{ entry.id }}\n * </div>\n * </template>\n * </GHistoryScroller>\n * ```\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup generic=\"T extends { id: string | number }\">\n\n\nimport { computed, nextTick, onMounted, ref, watch } from \"vue\";\nimport { useResizeObserver } from \"@vueuse/core\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo History\n */\n label?: string;\n\n /**\n * History entries passed to default slot\n * @demo\n */\n entries: T[];\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n entries: () => [],\n});\n\nconst scrollerRef = ref<HTMLElement | null>(null);\nconst contentRef = ref<HTMLElement | null>(null);\nconst isAtBottom = ref(true);\nconst isAtTop = ref(true);\n\nasync function scrollToBottom({ focusLast = false } = {}) {\n if (scrollerRef.value) {\n scrollerRef.value.scrollTop = scrollerRef.value.scrollHeight;\n }\n if (focusLast) {\n if (contentRef.value) {\n const items = contentRef.value.querySelectorAll(\".g-history-entry\");\n if (items.length > 0) {\n const last = items[items.length - 1] as HTMLElement;\n await nextTick();\n last.focus();\n }\n }\n }\n}\n\nfunction handleScroll() {\n if (!scrollerRef.value) return;\n const { scrollTop, scrollHeight, clientHeight } = scrollerRef.value;\n // Consider within 2px of bottom/top as \"at bottom/top\"\n isAtBottom.value = scrollTop + clientHeight >= scrollHeight - 2;\n isAtTop.value = scrollTop <= 2;\n}\n\nonMounted(() => {\n nextTick(scrollToBottom);\n});\n\n// Use VueUse's useResizeObserver for scroller and content\nuseResizeObserver(scrollerRef, () => {\n if (isAtBottom.value) {\n scrollToBottom();\n }\n});\nuseResizeObserver(contentRef, () => {\n if (isAtBottom.value) {\n scrollToBottom();\n }\n});\n\nwatch(\n () => props.entries,\n async () => {\n // Only scroll if at bottom\n if (isAtBottom.value) {\n await nextTick();\n scrollToBottom();\n }\n },\n);\n\nconst reversedEntries = computed(() => [...props.entries].reverse());\n</script>\n\n<template>\n <div class=\"g-history-scroller-wrapper\">\n <div\n v-if=\"!isAtTop\"\n class=\"g-history-shadow g-history-shadow--top\"\n aria-hidden=\"true\"\n ></div>\n <div\n v-if=\"!isAtBottom\"\n class=\"g-history-shadow g-history-shadow--bottom\"\n aria-hidden=\"true\"\n ></div>\n <div\n ref=\"scrollerRef\"\n class=\"g-history-scroller\"\n :role=\"label ? 'log' : undefined\"\n :aria-label=\"label\"\n @scroll=\"handleScroll\"\n >\n <GButton\n class=\"g-scroll-to-bottom-btn\"\n :class=\"{ 'scroll-to-bottom-btn--hidden': isAtBottom }\"\n size=\"small\"\n type=\"button\"\n @click=\"() => scrollToBottom({ focusLast: true })\"\n aria-label=\"Jump to Latest\"\n >\n <svg\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.25rem\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M303.5 569C312.9 578.4 328.1 578.4 337.4 569L505.4 401C514.8 391.6 514.8 376.4 505.4 367.1C496 357.8 480.8 357.7 471.5 367.1L344.5 494.1L344.5 88C344.5 74.7 333.8 64 320.5 64C307.2 64 296.5 74.7 296.5 88L296.5 494.1L169.5 367.1C160.1 357.7 144.9 357.7 135.6 367.1C126.3 376.5 126.2 391.7 135.6 401L303.6 569z\"\n />\n </svg>\n </GButton>\n <div role=\"list\" ref=\"contentRef\" class=\"g-history-list\">\n <div\n v-for=\"entry in reversedEntries\"\n role=\"listitem\"\n :key=\"entry.id\"\n class=\"g-history-entry\"\n tabindex=\"-1\"\n >\n <slot :entry=\"entry\" />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-history-scroller {\n display: block;\n}\n.g-history-scroller-wrapper {\n position: relative;\n}\n.g-history-list {\n padding: 1rem;\n margin: 0;\n list-style: none;\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n.g-history-scroller {\n overflow-y: auto;\n height: 100%;\n width: 100%;\n}\n.g-history-entry {\n margin: 0;\n padding: 0;\n}\n.g-scroll-to-bottom-btn {\n position: absolute;\n right: 2rem;\n bottom: 2rem;\n z-index: 100;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n.scroll-to-bottom-btn--hidden {\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s;\n}\n.scroll-to-bottom-btn--hidden:focus {\n opacity: 1;\n pointer-events: auto;\n}\n\n.g-history-shadow {\n position: absolute;\n left: 0;\n right: 0;\n height: 2rem;\n pointer-events: none;\n position: absolute;\n}\n.g-history-shadow--top {\n top: 0;\n background: linear-gradient(\n to bottom,\n rgba(0, 0, 0, 0.12),\n rgba(0, 0, 0, 0)\n );\n}\n.g-history-shadow--bottom {\n bottom: 0;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0));\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A scroller that is used for content that's typically shown like a chat log,\n * meaning it starts at the bottom, and you scroll up for older entries.\n *\n * The scroller automatically starts at the bottom. When scrolled up, a button\n * appears that jumps to the bottom. This button is also the first focusable\n * element for accessibility.\n *\n * If the `label` is provided, the scroller will have the ARIA `role=\"log\"` and\n * the label as the `aria-label`.\n *\n * **Slot** `default` is what will be rendered for each entry in the\n * `entries`. For example:\n *\n * ```vue-html\n * <GHistoryScroller\n * v-bind=\"props\"\n * :entries=\"historyEntries\"\n * class=\"history-scroller\">\n * <template #default=\"{ entry }\">\n * <div class=\"history-entry\">\n * This is history for: {{ entry.id }}\n * </div>\n * </template>\n * </GHistoryScroller>\n * ```\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup generic=\"T extends { id: string | number }\">\n\n\nimport { computed, nextTick, onMounted, ref, watch } from \"vue\";\nimport { useResizeObserver } from \"@vueuse/core\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo History\n */\n label?: string;\n\n /**\n * History entries passed to default slot\n * @demo\n */\n entries: T[];\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n entries: () => [],\n});\n\nconst scrollerRef = ref<HTMLElement | null>(null);\nconst contentRef = ref<HTMLElement | null>(null);\nconst isAtBottom = ref(true);\nconst isAtTop = ref(true);\n\nasync function scrollToBottom({ focusLast = false } = {}) {\n if (scrollerRef.value) {\n scrollerRef.value.scrollTop = scrollerRef.value.scrollHeight;\n }\n if (focusLast) {\n if (contentRef.value) {\n const items = contentRef.value.querySelectorAll(\".g-history-entry\");\n if (items.length > 0) {\n const last = items[items.length - 1] as HTMLElement;\n await nextTick();\n last.focus();\n }\n }\n }\n}\n\nfunction handleScroll() {\n if (!scrollerRef.value) return;\n const { scrollTop, scrollHeight, clientHeight } = scrollerRef.value;\n // Consider within 2px of bottom/top as \"at bottom/top\"\n isAtBottom.value = scrollTop + clientHeight >= scrollHeight - 2;\n isAtTop.value = scrollTop <= 2;\n}\n\nonMounted(() => {\n nextTick(scrollToBottom);\n});\n\n// Use VueUse's useResizeObserver for scroller and content\nuseResizeObserver(scrollerRef, () => {\n if (isAtBottom.value) {\n scrollToBottom();\n }\n});\nuseResizeObserver(contentRef, () => {\n if (isAtBottom.value) {\n scrollToBottom();\n }\n});\n\nwatch(\n () => props.entries,\n async () => {\n // Only scroll if at bottom\n if (isAtBottom.value) {\n await nextTick();\n scrollToBottom();\n }\n },\n);\n\nconst reversedEntries = computed(() => [...props.entries].reverse());\n</script>\n\n<template>\n <div class=\"g-history-scroller-wrapper\">\n <div\n v-if=\"!isAtTop\"\n class=\"g-history-shadow g-history-shadow--top\"\n aria-hidden=\"true\"\n ></div>\n <div\n v-if=\"!isAtBottom\"\n class=\"g-history-shadow g-history-shadow--bottom\"\n aria-hidden=\"true\"\n ></div>\n <div\n ref=\"scrollerRef\"\n class=\"g-history-scroller\"\n :role=\"label ? 'log' : undefined\"\n :aria-label=\"label\"\n @scroll=\"handleScroll\"\n >\n <GButton\n class=\"g-scroll-to-bottom-btn\"\n :class=\"{ 'scroll-to-bottom-btn--hidden': isAtBottom }\"\n size=\"small\"\n type=\"button\"\n @click=\"() => scrollToBottom({ focusLast: true })\"\n aria-label=\"Jump to Latest\"\n >\n <svg\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.25rem\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M303.5 569C312.9 578.4 328.1 578.4 337.4 569L505.4 401C514.8 391.6 514.8 376.4 505.4 367.1C496 357.8 480.8 357.7 471.5 367.1L344.5 494.1L344.5 88C344.5 74.7 333.8 64 320.5 64C307.2 64 296.5 74.7 296.5 88L296.5 494.1L169.5 367.1C160.1 357.7 144.9 357.7 135.6 367.1C126.3 376.5 126.2 391.7 135.6 401L303.6 569z\"\n />\n </svg>\n </GButton>\n <div role=\"list\" ref=\"contentRef\" class=\"g-history-list\">\n <div\n v-for=\"entry in reversedEntries\"\n role=\"listitem\"\n :key=\"entry.id\"\n class=\"g-history-entry\"\n tabindex=\"-1\"\n >\n <slot :entry=\"entry\" />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-history-scroller {\n display: block;\n}\n.g-history-scroller-wrapper {\n position: relative;\n}\n.g-history-list {\n padding: 1rem;\n margin: 0;\n list-style: none;\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n.g-history-scroller {\n overflow-y: auto;\n height: 100%;\n width: 100%;\n}\n.g-history-entry {\n margin: 0;\n padding: 0;\n}\n.g-scroll-to-bottom-btn {\n position: absolute;\n right: 2rem;\n bottom: 2rem;\n z-index: 100;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n.scroll-to-bottom-btn--hidden {\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s;\n}\n.scroll-to-bottom-btn--hidden:focus {\n opacity: 1;\n pointer-events: auto;\n}\n\n.g-history-shadow {\n position: absolute;\n left: 0;\n right: 0;\n height: 2rem;\n pointer-events: none;\n position: absolute;\n}\n.g-history-shadow--top {\n top: 0;\n background: linear-gradient(\n to bottom,\n rgba(0, 0, 0, 0.12),\n rgba(0, 0, 0, 0)\n );\n}\n.g-history-shadow--bottom {\n bottom: 0;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0));\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This component acts like a radio button group condensed into a compact\n * element with the goal of making it easy to go over many of them at once.\n *\n * In addition to the arrow keys changing the selected value, special key\n * bindings exist for 'y' and 'n' to set yes and no respectively.\n *\n * A `describedby` prop can be passed with an ID to an element to be used as\n * the `aria-describedby` for the group element.\n *\n * When the value changes, `v-model` is updated. A `change` event is also emitted\n * if the value changed from user interaction.\n *\n * Slots:\n * - `label`: Custom label content. Defaults to `label` prop if not provided.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, useId } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo Three-way toggle\n */\n label: string;\n\n /**\n * ID of an element that describes the input\n */\n describedby?: string;\n\n /**\n * Error message\n * @demo\n */\n error?: string;\n\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n}\n\nconst props = defineProps<Props>();\nconst model = defineModel<boolean | null>({default: () => null});\n\nconst emit = defineEmits([\"change\"]);\n\nfunction change(val: boolean | null) {\n const prev = model.value;\n model.value = val;\n if (val !== prev) {\n emit(\"change\", {\n was: prev,\n to: val,\n });\n }\n}\n\nfunction onChange(val: boolean | null) {\n if (props.disabled) {\n return;\n }\n if (model.value === val) {\n change(null);\n } else {\n change(val);\n }\n}\n\nfunction onClickLabel(val: boolean) {\n if (props.disabled) {\n return;\n }\n if (model.value === val) {\n change(null);\n }\n}\n\nconst id = useId();\nconst radioName = computed(() => `g-three-way-toggle-${id}`);\n\nconst leftId = useId();\nconst centerId = useId();\nconst rightId = useId();\n\nconst thumbPosition = computed(() => {\n if (model.value === false) {\n return \"g-left\";\n } else if (model.value === true) {\n return \"g-right\";\n } else {\n return \"g-center\";\n }\n});\n\nfunction onLabelKeydown(e: KeyboardEvent) {\n if (props.disabled) {\n return;\n }\n if (e.key === \"n\" || e.key === \"N\") {\n change(false);\n e.preventDefault();\n } else if (e.key === \"y\" || e.key === \"Y\") {\n change(true);\n e.preventDefault();\n }\n}\n</script>\n\n<template>\n <div class=\"g-three-way-toggle-wrapper\">\n <div class=\"g-three-way-toggle-control\">\n <span class=\"g-label\" :id=\"id\">\n <slot name=\"label\">\n {{ label }}\n </slot>\n </span>\n <fieldset\n class=\"g-three-way-toggle\"\n :class=\"{ 'g-has-error': error }\"\n role=\"radiogroup\"\n :aria-labelledby=\"id\"\n :aria-describedby=\"describedby\"\n :disabled=\"disabled\"\n :aria-invalid=\"error ? 'true' : undefined\"\n :aria-errormessage=\"error ? id + '-error' : undefined\"\n >\n <div\n class=\"g-toggle-track\"\n :class=\"[thumbPosition, { 'g-disabled': disabled }]\"\n >\n <span\n class=\"g-toggle-thumb\"\n :class=\"thumbPosition\"\n aria-hidden=\"true\"\n >\n <span v-if=\"model === false\">NO</span>\n <span v-else-if=\"model === true\">YES</span>\n <span v-else></span>\n </span>\n <label\n :for=\"leftId\"\n class=\"g-toggle-option g-left\"\n @click=\"onClickLabel(false)\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"leftId\"\n :name=\"radioName\"\n :checked=\"model === false\"\n value=\"false\"\n :disabled=\"disabled\"\n @change=\"onChange(false)\"\n /><span class=\"ilw-sr-only\">No</span>\n </label>\n <label\n :for=\"centerId\"\n class=\"g-toggle-option g-center\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"centerId\"\n :name=\"radioName\"\n :checked=\"model === null\"\n :disabled=\"disabled\"\n @change=\"onChange(null)\"\n /><span class=\"ilw-sr-only\">Unset</span>\n </label>\n <label\n :for=\"rightId\"\n class=\"g-toggle-option g-right\"\n @click=\"onClickLabel(true)\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"rightId\"\n :name=\"radioName\"\n value=\"true\"\n :checked=\"model === true\"\n :disabled=\"disabled\"\n @change=\"onChange(true)\"\n /><span class=\"ilw-sr-only\">Yes</span>\n </label>\n </div>\n </fieldset>\n </div>\n <div\n v-if=\"error\"\n :id=\"`${id}-error`\"\n class=\"g-form-error\"\n role=\"alert\"\n aria-atomic=\"true\"\n >\n {{ error }}\n </div>\n </div>\n</template>\n\n<style>\ng-three-way-toggle {\n display: block;\n}\n.g-three-way-toggle-control {\n display: flex;\n margin-bottom: 4px;\n column-gap: 8px;\n\n .g-label {\n flex: 1;\n font-weight: 600;\n font-size: 0.875rem;\n line-height: 1;\n align-self: center;\n display: block;\n }\n}\n\n.g-three-way-toggle {\n border: none;\n padding: 0;\n margin: 0;\n width: 50px;\n background: none;\n border-radius: 12px;\n\n &:focus-within {\n outline: 2px solid var(--il-blue);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-toggle-track {\n display: flex;\n position: relative;\n background: var(--g-surface-700);\n border-radius: 14px;\n height: 24px;\n width: 100%;\n min-width: 50px;\n box-sizing: border-box;\n font-family: var(--il-font-sans);\n\n &.g-left {\n background: var(--il-industrial);\n }\n &.g-right {\n background: var(--il-prairie);\n }\n &.g-disabled {\n pointer-events: none;\n background: var(--g-surface-400);\n }\n}\n\n.g-toggle-thumb {\n position: absolute;\n top: 2px;\n width: 20px;\n height: 20px;\n background: var(--g-surface-100);\n border-radius: 50%;\n left: 2px;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n color: var(--g-surface-0);\n font-weight: 700;\n transition:\n left 0.1s ease-in-out,\n background-color 0.1s ease-in-out;\n letter-spacing: -0.5px;\n &.g-left {\n color: var(--il-industrial);\n }\n &.g-right {\n color: var(--il-prairie);\n letter-spacing: -1.5px;\n }\n}\n.g-toggle-track.g-left .g-toggle-thumb {\n left: 2px;\n}\n.g-toggle-track.g-center .g-toggle-thumb {\n left: calc(50% - 10px);\n}\n.g-toggle-track.g-right .g-toggle-thumb {\n left: calc(100% - 20px - 2px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-toggle-thumb {\n transition: none;\n }\n}\n\n.g-toggle-option {\n flex: 1 1 0;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n &.g-center {\n flex: 0 0 1%;\n }\n}\n.g-toggle-option input[type=\"radio\"] {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n left: 0;\n top: 0;\n margin: 0;\n cursor: pointer;\n z-index: 2;\n}\n\n.g-has-error {\n .g-toggle-track {\n background: var(--g-danger-500);\n }\n}\n.g-form-error {\n color: var(--g-danger-600);\n font-size: 0.875rem;\n font-weight: bold;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This component acts like a radio button group condensed into a compact\n * element with the goal of making it easy to go over many of them at once.\n *\n * In addition to the arrow keys changing the selected value, special key\n * bindings exist for 'y' and 'n' to set yes and no respectively.\n *\n * A `describedby` prop can be passed with an ID to an element to be used as\n * the `aria-describedby` for the group element.\n *\n * When the value changes, `v-model` is updated. A `change` event is also emitted\n * if the value changed from user interaction.\n *\n * Slots:\n * - `label`: Custom label content. Defaults to `label` prop if not provided.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, useId } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo Three-way toggle\n */\n label: string;\n\n /**\n * ID of an element that describes the input\n */\n describedby?: string;\n\n /**\n * Error message\n * @demo\n */\n error?: string;\n\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n}\n\nconst props = defineProps<Props>();\nconst model = defineModel<boolean | null>({default: () => null});\n\nconst emit = defineEmits([\"change\"]);\n\nfunction change(val: boolean | null) {\n const prev = model.value;\n model.value = val;\n if (val !== prev) {\n emit(\"change\", {\n was: prev,\n to: val,\n });\n }\n}\n\nfunction onChange(val: boolean | null) {\n if (props.disabled) {\n return;\n }\n if (model.value === val) {\n change(null);\n } else {\n change(val);\n }\n}\n\nfunction onClickLabel(val: boolean) {\n if (props.disabled) {\n return;\n }\n if (model.value === val) {\n change(null);\n }\n}\n\nconst id = useId();\nconst radioName = computed(() => `g-three-way-toggle-${id}`);\n\nconst leftId = useId();\nconst centerId = useId();\nconst rightId = useId();\n\nconst thumbPosition = computed(() => {\n if (model.value === false) {\n return \"g-left\";\n } else if (model.value === true) {\n return \"g-right\";\n } else {\n return \"g-center\";\n }\n});\n\nfunction onLabelKeydown(e: KeyboardEvent) {\n if (props.disabled) {\n return;\n }\n if (e.key === \"n\" || e.key === \"N\") {\n change(false);\n e.preventDefault();\n } else if (e.key === \"y\" || e.key === \"Y\") {\n change(true);\n e.preventDefault();\n }\n}\n</script>\n\n<template>\n <div class=\"g-three-way-toggle-wrapper\">\n <div class=\"g-three-way-toggle-control\">\n <span class=\"g-label\" :id=\"id\">\n <slot name=\"label\">\n {{ label }}\n </slot>\n </span>\n <fieldset\n class=\"g-three-way-toggle\"\n :class=\"{ 'g-has-error': error }\"\n role=\"radiogroup\"\n :aria-labelledby=\"id\"\n :aria-describedby=\"describedby\"\n :disabled=\"disabled\"\n :aria-invalid=\"error ? 'true' : undefined\"\n :aria-errormessage=\"error ? id + '-error' : undefined\"\n >\n <div\n class=\"g-toggle-track\"\n :class=\"[thumbPosition, { 'g-disabled': disabled }]\"\n >\n <span\n class=\"g-toggle-thumb\"\n :class=\"thumbPosition\"\n aria-hidden=\"true\"\n >\n <span v-if=\"model === false\">NO</span>\n <span v-else-if=\"model === true\">YES</span>\n <span v-else></span>\n </span>\n <label\n :for=\"leftId\"\n class=\"g-toggle-option g-left\"\n @click=\"onClickLabel(false)\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"leftId\"\n :name=\"radioName\"\n :checked=\"model === false\"\n value=\"false\"\n :disabled=\"disabled\"\n @change=\"onChange(false)\"\n /><span class=\"ilw-sr-only\">No</span>\n </label>\n <label\n :for=\"centerId\"\n class=\"g-toggle-option g-center\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"centerId\"\n :name=\"radioName\"\n :checked=\"model === null\"\n :disabled=\"disabled\"\n @change=\"onChange(null)\"\n /><span class=\"ilw-sr-only\">Unset</span>\n </label>\n <label\n :for=\"rightId\"\n class=\"g-toggle-option g-right\"\n @click=\"onClickLabel(true)\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"rightId\"\n :name=\"radioName\"\n value=\"true\"\n :checked=\"model === true\"\n :disabled=\"disabled\"\n @change=\"onChange(true)\"\n /><span class=\"ilw-sr-only\">Yes</span>\n </label>\n </div>\n </fieldset>\n </div>\n <div\n v-if=\"error\"\n :id=\"`${id}-error`\"\n class=\"g-form-error\"\n role=\"alert\"\n aria-atomic=\"true\"\n >\n {{ error }}\n </div>\n </div>\n</template>\n\n<style>\ng-three-way-toggle {\n display: block;\n}\n.g-three-way-toggle-control {\n display: flex;\n margin-bottom: 4px;\n column-gap: 8px;\n\n .g-label {\n flex: 1;\n font-weight: 600;\n font-size: 0.875rem;\n line-height: 1;\n align-self: center;\n display: block;\n }\n}\n\n.g-three-way-toggle {\n border: none;\n padding: 0;\n margin: 0;\n width: 50px;\n background: none;\n border-radius: 12px;\n\n &:focus-within {\n outline: 2px solid var(--il-blue);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-toggle-track {\n display: flex;\n position: relative;\n background: var(--g-surface-700);\n border-radius: 14px;\n height: 24px;\n width: 100%;\n min-width: 50px;\n box-sizing: border-box;\n font-family: var(--il-font-sans);\n\n &.g-left {\n background: var(--il-industrial);\n }\n &.g-right {\n background: var(--il-prairie);\n }\n &.g-disabled {\n pointer-events: none;\n background: var(--g-surface-400);\n }\n}\n\n.g-toggle-thumb {\n position: absolute;\n top: 2px;\n width: 20px;\n height: 20px;\n background: var(--g-surface-100);\n border-radius: 50%;\n left: 2px;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n color: var(--g-surface-0);\n font-weight: 700;\n transition:\n left 0.1s ease-in-out,\n background-color 0.1s ease-in-out;\n letter-spacing: -0.5px;\n &.g-left {\n color: var(--il-industrial);\n }\n &.g-right {\n color: var(--il-prairie);\n letter-spacing: -1.5px;\n }\n}\n.g-toggle-track.g-left .g-toggle-thumb {\n left: 2px;\n}\n.g-toggle-track.g-center .g-toggle-thumb {\n left: calc(50% - 10px);\n}\n.g-toggle-track.g-right .g-toggle-thumb {\n left: calc(100% - 20px - 2px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-toggle-thumb {\n transition: none;\n }\n}\n\n.g-toggle-option {\n flex: 1 1 0;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n &.g-center {\n flex: 0 0 1%;\n }\n}\n.g-toggle-option input[type=\"radio\"] {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n left: 0;\n top: 0;\n margin: 0;\n cursor: pointer;\n z-index: 2;\n}\n\n.g-has-error {\n .g-toggle-track {\n background: var(--g-danger-500);\n }\n}\n.g-form-error {\n color: var(--g-danger-600);\n font-size: 0.875rem;\n font-weight: bold;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Internal component for rendering the table body.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\" generic=\"T extends TableRow, C extends TableColumn<T>\">\nimport { computed, ref, type VNode } from \"vue\";\nimport { TableColumn, TableRow } from \"./TableColumn.ts\";\nimport { UseTableChangesReturn } from \"../../compose/useTableChanges.ts\";\n\ntype Props = {\n data: T[];\n groupBy?: keyof T;\n columns: C[];\n groupRender?: (groupValue: any, row: T) => VNode;\n rowClickable?: boolean;\n rowClass?: (row: T) => string | string[] | undefined;\n startIndex: number;\n bulkSelectionEnabled?: boolean;\n selectedRows?: string[];\n tableId: string;\n changeTracker?: UseTableChangesReturn<T>;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n (e: \"row-click\", link: string): void;\n (e: \"toggle-row\", rowKey: string, shiftKey: boolean): void;\n (e: \"cell-change\", payload: { row: T; column: C; value: any }): void;\n}>();\n\n\nfunction handleMouseDown(event: MouseEvent, rowKey: string) {\n // Prevent text selection when shift-clicking for bulk selection\n // Only if bulk selection is enabled and shift is held and we're not on an input\n if (\n props.bulkSelectionEnabled &&\n event.shiftKey &&\n !(event.target as HTMLElement).closest(\"a,button,[tabindex],input\")\n ) {\n event.preventDefault();\n }\n}\n\nfunction handleRowClick(event: MouseEvent, rowKey: string) {\n if (!props.rowClickable && !props.bulkSelectionEnabled) {\n return; // Do nothing if rows are not clickable\n }\n // Only trigger if not clicking on a link or button directly\n if ((event.target as HTMLElement).closest(\"a,button,[tabindex],input\")) {\n return;\n }\n const row = (event.target as HTMLElement).closest(\n \"tr\",\n ) as HTMLTableRowElement;\n if (row) {\n if (props.bulkSelectionEnabled) {\n let checkbox = row.querySelector(\n \"input[type=checkbox]\",\n ) as HTMLInputElement | null;\n if (checkbox) {\n // Trigger the checkbox change with shift key info\n handleCheckboxChange(rowKey, event.shiftKey);\n }\n } else if (props.rowClickable) {\n const firstLink = row.querySelector(\"a[href]\");\n const link = firstLink?.getAttribute(\"href\");\n if (link) {\n emit(\"row-click\", link);\n }\n }\n }\n}\n\nfunction isRowSelected(rowKey: string): boolean {\n return props.selectedRows?.includes(rowKey) ?? false;\n}\n\nfunction handleCheckboxChange(rowKey: string, shiftKey: boolean = false) {\n emit(\"toggle-row\", rowKey, shiftKey);\n}\n\nfunction handleCellChange(event: Event, row: T, col: C) {\n const target = event.target as unknown as\n | HTMLInputElement\n | HTMLSelectElement;\n const value = target.value;\n emit(\"cell-change\", { row, column: col, value });\n}\n\nfunction buildAriaLabelledBy(row: T, col: C): string {\n const columnHeaderId = `${props.tableId}-th-${String(col.key)}`;\n\n // If labelKey is specified, add the label cell ID\n if (col.editable?.labelKey) {\n const labelCellId = `${props.tableId}-td-${row.key}-${col.editable.labelKey}`;\n return `${labelCellId} ${columnHeaderId} `;\n }\n\n return columnHeaderId;\n}\n\nconst labelCellColumn = computed(() => {\n for (const col of props.columns) {\n if (col.editable?.labelKey) {\n return col.editable.labelKey;\n }\n }\n return undefined;\n }\n);\n\nfunction shouldAddCellId(col: C): boolean {\n // Check if this column is used as a label for any editable column\n return col.key === labelCellColumn.value;\n}\n\nfunction hasCellChange(row: T, col: C): boolean {\n if (!props.changeTracker) return false;\n return props.changeTracker.hasChange(row.key, col.key);\n}\n\nfunction hasCellError(row: T, col: C): boolean {\n if (!props.changeTracker) return false;\n return props.changeTracker.hasError(row.key, col.key);\n}\nfunction getCellError(row: T, col: C): string | undefined {\n if (!props.changeTracker) return undefined;\n return props.changeTracker.getError(row.key, col.key);\n}\n\n</script>\n\n<template>\n <tbody ref=\"tableBodyRef\" class=\"efficient-table-body\">\n <template v-for=\"(row, idx) in data\" :key=\"row.key\">\n <tr\n v-if=\"\n groupBy &&\n (idx === 0 || row[groupBy] !== data[idx - 1][groupBy])\n \"\n :aria-rowindex=\"startIndex + idx + 2\"\n >\n <td\n v-if=\"bulkSelectionEnabled\"\n class=\"table-group-checkbox\"\n ></td>\n <td :colspan=\"columns.length\" class=\"table-group-row\">\n <template v-if=\"groupRender\">\n <component :is=\"groupRender(row[groupBy], row)\" />\n </template>\n <template v-else>\n {{ row[groupBy] }}\n </template>\n </td>\n </tr>\n\n <tr\n :class=\"[\n 'efficient-table-row',\n {\n 'row-striped': idx % 2 === 1,\n 'row-clickable': rowClickable || bulkSelectionEnabled,\n },\n rowClass ? rowClass(row) : undefined,\n ]\"\n :aria-rowindex=\"startIndex + idx + 2\"\n @mousedown=\"handleMouseDown($event, row.key)\"\n @click=\"handleRowClick($event, row.key)\"\n >\n <td v-if=\"bulkSelectionEnabled\" class=\"td-checkbox\" @click.stop>\n <input\n type=\"checkbox\"\n :checked=\"isRowSelected(row.key)\"\n @click=\"(e) => handleCheckboxChange(row.key, e.shiftKey)\"\n :aria-label=\"`Select row ${row.key}`\"\n :name=\"`row-${row.key}-checkbox`\"\n class=\"g-bulk-select-checkbox\"\n />\n </td>\n\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n :id=\"\n shouldAddCellId(col)\n ? `${tableId}-td-${row.key}-${String(col.key)}`\n : undefined\n \"\n :class=\"[\n col.editable ? 'editable-td' : '',\n hasCellChange(row, col) ? 'g-cell-changed' : '',\n hasCellError(row, col) ? 'g-cell-error' : '',\n typeof col.tdClass === 'function'\n ? col.tdClass(row)\n : col.tdClass,\n ]\"\n >\n <div v-if=\"col.editable\" class=\"editable-cell\">\n <span v-if=\"col.editable.prefix\" class=\"cell-prefix\">{{\n col.editable.prefix\n }}</span>\n <select\n v-if=\"col.editable.type === 'select'\"\n :value=\"row[col.key]\"\n @change=\"handleCellChange($event, row, col)\"\n :aria-labelledby=\"buildAriaLabelledBy(row, col)\"\n :aria-invalid=\"hasCellError(row, col)\"\n :name=\"`row-${row.key}-${String(col.key)}-select`\"\n class=\"editable-input editable-select\"\n >\n <option\n v-for=\"option in col.editable.options\"\n :key=\"option.value\"\n :value=\"option.value\"\n >\n {{ option.label }}\n </option>\n </select>\n <input\n v-else\n :value=\"row[col.key]\"\n v-bind=\"col.editable.inputAttributes\"\n @input=\"handleCellChange($event, row, col)\"\n :aria-labelledby=\"buildAriaLabelledBy(row, col)\"\n :aria-invalid=\"hasCellError(row, col)\"\n :aria-errormessage=\"hasCellError(row, col) ? `${tableId}-error-${row.key}-${String(col.key)}` : undefined\"\n :name=\"`row-${row.key}-${String(col.key)}-input`\"\n class=\"editable-input\"\n :style=\"{\n paddingLeft: col.editable.prefix\n ? '1.5rem'\n : undefined,\n paddingRight: col.editable.suffix\n ? '2rem'\n : undefined,\n }\"\n />\n <span v-if=\"col.editable.suffix\" class=\"cell-suffix\">{{\n col.editable.suffix\n }}</span>\n </div>\n <component v-else-if=\"col.display\" :is=\"col.display(row)\" />\n <template v-else>{{ row[col.key] }}</template>\n <div v-if=\"hasCellError(row, col)\" role=\"alert\" class=\"g-cell-error-message\" :id=\"`${tableId}-error-${row.key}-${String(col.key)}`\">\n {{ getCellError(row, col) }}\n </div>\n </td>\n </tr>\n </template>\n </tbody>\n</template>\n\n<style>\n.efficient-table-body {\n th,\n td {\n padding: 0.4rem 0.2rem;\n }\n\n td.editable-td {\n padding: 0;\n border-right: 1px solid var(--g-surface-300);\n border-bottom: 1px solid var(--g-surface-300);\n }\n\n /* Add left border to first editable cell or after non-editable cell */\n td.editable-td:first-child,\n td:not(.editable-td) + td.editable-td {\n border-left: 1px solid var(--g-surface-300);\n }\n\n /* Add top border to editable cells in first row */\n tr:first-child td.editable-td {\n border-top: 1px solid var(--g-surface-300);\n }\n\n .table-group-row {\n font-weight: 600;\n background: var(--g-surface-0);\n padding: 1rem;\n font-size: 1.25rem;\n border-bottom: 1px solid var(--g-accent-500);\n }\n\n .table-group-checkbox {\n width: 50px;\n background: var(--g-surface-0);\n }\n\n .td-checkbox {\n width: 50px;\n text-align: center;\n padding: 0.4rem;\n }\n\n .g-bulk-select-checkbox {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--g-primary-500);\n }\n}\n\n.row-striped {\n background-color: var(--g-surface-100);\n}\n\n.efficient-table-row.row-clickable {\n cursor: pointer;\n}\n\n.efficient-table-row.row-clickable:hover {\n background-color: var(--ilw-color--focus--background);\n}\n\n.efficient-table-row.row-clickable:hover td,\n.efficient-table-row.row-clickable:hover a,\n.efficient-table-row.row-clickable:hover span,\n.efficient-table-row.row-clickable:hover strong {\n text-decoration: underline;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .efficient-table-row.row-clickable,\n .efficient-table-row.row-clickable:hover {\n transition: none !important;\n }\n}\n\n.editable-cell {\n position: relative;\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.editable-input {\n width: 100%;\n padding: 0.4rem 0.5rem 0.4rem 0.75rem;\n border: none;\n border-radius: 0;\n font-size: 1rem;\n font-family: var(--il-font-sans);\n background: transparent;\n box-sizing: border-box;\n}\n\n.editable-input:focus {\n outline: 2px solid var(--g-primary-500);\n outline-offset: -2px;\n background: var(--g-surface-0);\n}\n\n.editable-select {\n cursor: pointer;\n}\n\n.cell-prefix,\n.cell-suffix {\n position: absolute;\n font-size: 1rem;\n color: var(--g-surface-600);\n pointer-events: none;\n top: 50%;\n transform: translateY(-50%);\n}\n\n.cell-prefix {\n left: 0.5rem;\n}\n\n.cell-suffix {\n right: 0.5rem;\n}\n\n/* Highlight cells that have been changed by the user */\n.g-cell-changed {\n background: rgba(101, 199, 255, 0.29);\n}\n\n/* Highlight cells with errors */\n.g-cell-error {\n background: var(--g-danger-100);\n position: relative;\n}\n\n.g-cell-error-message {\n background: var(--g-danger-500);\n color: var(--g-surface-0);\n font-size: 0.875rem;\n padding: 0.25rem 0.5rem;\n}\n\n.g-cell-error .editable-input[aria-invalid=\"true\"]:focus {\n outline-color: var(--g-danger-500);\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Internal component for rendering the table body.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\" generic=\"T extends TableRow, C extends TableColumn<T>\">\nimport { computed, ref, type VNode } from \"vue\";\nimport { TableColumn, TableRow } from \"./TableColumn.ts\";\nimport { UseTableChangesReturn } from \"../../compose/useTableChanges.ts\";\n\ntype Props = {\n data: T[];\n groupBy?: keyof T;\n columns: C[];\n groupRender?: (groupValue: any, row: T) => VNode;\n rowClickable?: boolean;\n rowClass?: (row: T) => string | string[] | undefined;\n startIndex: number;\n bulkSelectionEnabled?: boolean;\n selectedRows?: string[];\n tableId: string;\n changeTracker?: UseTableChangesReturn<T>;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n (e: \"row-click\", link: string): void;\n (e: \"toggle-row\", rowKey: string, shiftKey: boolean): void;\n (e: \"cell-change\", payload: { row: T; column: C; value: any }): void;\n}>();\n\n\nfunction handleMouseDown(event: MouseEvent, rowKey: string) {\n // Prevent text selection when shift-clicking for bulk selection\n // Only if bulk selection is enabled and shift is held and we're not on an input\n if (\n props.bulkSelectionEnabled &&\n event.shiftKey &&\n !(event.target as HTMLElement).closest(\"a,button,[tabindex],input\")\n ) {\n event.preventDefault();\n }\n}\n\nfunction handleRowClick(event: MouseEvent, rowKey: string) {\n if (!props.rowClickable && !props.bulkSelectionEnabled) {\n return; // Do nothing if rows are not clickable\n }\n // Only trigger if not clicking on a link or button directly\n if ((event.target as HTMLElement).closest(\"a,button,[tabindex],input\")) {\n return;\n }\n const row = (event.target as HTMLElement).closest(\n \"tr\",\n ) as HTMLTableRowElement;\n if (row) {\n if (props.bulkSelectionEnabled) {\n let checkbox = row.querySelector(\n \"input[type=checkbox]\",\n ) as HTMLInputElement | null;\n if (checkbox) {\n // Trigger the checkbox change with shift key info\n handleCheckboxChange(rowKey, event.shiftKey);\n }\n } else if (props.rowClickable) {\n const firstLink = row.querySelector(\"a[href]\");\n const link = firstLink?.getAttribute(\"href\");\n if (link) {\n emit(\"row-click\", link);\n }\n }\n }\n}\n\nfunction isRowSelected(rowKey: string): boolean {\n return props.selectedRows?.includes(rowKey) ?? false;\n}\n\nfunction handleCheckboxChange(rowKey: string, shiftKey: boolean = false) {\n emit(\"toggle-row\", rowKey, shiftKey);\n}\n\nfunction handleCellChange(event: Event, row: T, col: C) {\n const target = event.target as unknown as\n | HTMLInputElement\n | HTMLSelectElement;\n const value = target.value;\n emit(\"cell-change\", { row, column: col, value });\n}\n\nfunction buildAriaLabelledBy(row: T, col: C): string {\n const columnHeaderId = `${props.tableId}-th-${String(col.key)}`;\n\n // If labelKey is specified, add the label cell ID\n if (col.editable?.labelKey) {\n const labelCellId = `${props.tableId}-td-${row.key}-${col.editable.labelKey}`;\n return `${labelCellId} ${columnHeaderId} `;\n }\n\n return columnHeaderId;\n}\n\nconst labelCellColumn = computed(() => {\n for (const col of props.columns) {\n if (col.editable?.labelKey) {\n return col.editable.labelKey;\n }\n }\n return undefined;\n }\n);\n\nfunction shouldAddCellId(col: C): boolean {\n // Check if this column is used as a label for any editable column\n return col.key === labelCellColumn.value;\n}\n\nfunction hasCellChange(row: T, col: C): boolean {\n if (!props.changeTracker) return false;\n return props.changeTracker.hasChange(row.key, col.key);\n}\n\nfunction hasCellError(row: T, col: C): boolean {\n if (!props.changeTracker) return false;\n return props.changeTracker.hasError(row.key, col.key);\n}\nfunction getCellError(row: T, col: C): string | undefined {\n if (!props.changeTracker) return undefined;\n return props.changeTracker.getError(row.key, col.key);\n}\n\n</script>\n\n<template>\n <tbody ref=\"tableBodyRef\" class=\"efficient-table-body\">\n <template v-for=\"(row, idx) in data\" :key=\"row.key\">\n <tr\n v-if=\"\n groupBy &&\n (idx === 0 || row[groupBy] !== data[idx - 1][groupBy])\n \"\n :aria-rowindex=\"startIndex + idx + 2\"\n >\n <td\n v-if=\"bulkSelectionEnabled\"\n class=\"table-group-checkbox\"\n ></td>\n <td :colspan=\"columns.length\" class=\"table-group-row\">\n <template v-if=\"groupRender\">\n <component :is=\"groupRender(row[groupBy], row)\" />\n </template>\n <template v-else>\n {{ row[groupBy] }}\n </template>\n </td>\n </tr>\n\n <tr\n :class=\"[\n 'efficient-table-row',\n {\n 'row-striped': idx % 2 === 1,\n 'row-clickable': rowClickable || bulkSelectionEnabled,\n },\n rowClass ? rowClass(row) : undefined,\n ]\"\n :aria-rowindex=\"startIndex + idx + 2\"\n @mousedown=\"handleMouseDown($event, row.key)\"\n @click=\"handleRowClick($event, row.key)\"\n >\n <td v-if=\"bulkSelectionEnabled\" class=\"td-checkbox\" @click.stop>\n <input\n type=\"checkbox\"\n :checked=\"isRowSelected(row.key)\"\n @click=\"(e) => handleCheckboxChange(row.key, e.shiftKey)\"\n :aria-label=\"`Select row ${row.key}`\"\n :name=\"`row-${row.key}-checkbox`\"\n class=\"g-bulk-select-checkbox\"\n />\n </td>\n\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n :id=\"\n shouldAddCellId(col)\n ? `${tableId}-td-${row.key}-${String(col.key)}`\n : undefined\n \"\n :class=\"[\n col.editable ? 'editable-td' : '',\n hasCellChange(row, col) ? 'g-cell-changed' : '',\n hasCellError(row, col) ? 'g-cell-error' : '',\n typeof col.tdClass === 'function'\n ? col.tdClass(row)\n : col.tdClass,\n ]\"\n >\n <div v-if=\"col.editable\" class=\"editable-cell\">\n <span v-if=\"col.editable.prefix\" class=\"cell-prefix\">{{\n col.editable.prefix\n }}</span>\n <select\n v-if=\"col.editable.type === 'select'\"\n :value=\"row[col.key]\"\n @change=\"handleCellChange($event, row, col)\"\n :aria-labelledby=\"buildAriaLabelledBy(row, col)\"\n :aria-invalid=\"hasCellError(row, col)\"\n :name=\"`row-${row.key}-${String(col.key)}-select`\"\n class=\"editable-input editable-select\"\n >\n <option\n v-for=\"option in col.editable.options\"\n :key=\"option.value\"\n :value=\"option.value\"\n >\n {{ option.label }}\n </option>\n </select>\n <input\n v-else\n :value=\"row[col.key]\"\n v-bind=\"col.editable.inputAttributes\"\n @input=\"handleCellChange($event, row, col)\"\n :aria-labelledby=\"buildAriaLabelledBy(row, col)\"\n :aria-invalid=\"hasCellError(row, col)\"\n :aria-errormessage=\"hasCellError(row, col) ? `${tableId}-error-${row.key}-${String(col.key)}` : undefined\"\n :name=\"`row-${row.key}-${String(col.key)}-input`\"\n class=\"editable-input\"\n :style=\"{\n paddingLeft: col.editable.prefix\n ? '1.5rem'\n : undefined,\n paddingRight: col.editable.suffix\n ? '2rem'\n : undefined,\n }\"\n />\n <span v-if=\"col.editable.suffix\" class=\"cell-suffix\">{{\n col.editable.suffix\n }}</span>\n </div>\n <component v-else-if=\"col.display\" :is=\"col.display(row)\" />\n <template v-else>{{ row[col.key] }}</template>\n <div v-if=\"hasCellError(row, col)\" role=\"alert\" class=\"g-cell-error-message\" :id=\"`${tableId}-error-${row.key}-${String(col.key)}`\">\n {{ getCellError(row, col) }}\n </div>\n </td>\n </tr>\n </template>\n </tbody>\n</template>\n\n<style>\n.efficient-table-body {\n th,\n td {\n padding: 0.4rem 0.2rem;\n }\n\n td.editable-td {\n padding: 0;\n border-right: 1px solid var(--g-surface-300);\n border-bottom: 1px solid var(--g-surface-300);\n }\n\n /* Add left border to first editable cell or after non-editable cell */\n td.editable-td:first-child,\n td:not(.editable-td) + td.editable-td {\n border-left: 1px solid var(--g-surface-300);\n }\n\n /* Add top border to editable cells in first row */\n tr:first-child td.editable-td {\n border-top: 1px solid var(--g-surface-300);\n }\n\n .table-group-row {\n font-weight: 600;\n background: var(--g-surface-0);\n padding: 1rem;\n font-size: 1.25rem;\n border-bottom: 1px solid var(--g-accent-500);\n }\n\n .table-group-checkbox {\n width: 50px;\n background: var(--g-surface-0);\n }\n\n .td-checkbox {\n width: 50px;\n text-align: center;\n padding: 0.4rem;\n }\n\n .g-bulk-select-checkbox {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--g-primary-500);\n }\n}\n\n.row-striped {\n background-color: var(--g-surface-100);\n}\n\n.efficient-table-row.row-clickable {\n cursor: pointer;\n}\n\n.efficient-table-row.row-clickable:hover {\n background-color: var(--ilw-color--focus--background);\n}\n\n.efficient-table-row.row-clickable:hover td,\n.efficient-table-row.row-clickable:hover a,\n.efficient-table-row.row-clickable:hover span,\n.efficient-table-row.row-clickable:hover strong {\n text-decoration: underline;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .efficient-table-row.row-clickable,\n .efficient-table-row.row-clickable:hover {\n transition: none !important;\n }\n}\n\n.editable-cell {\n position: relative;\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.editable-input {\n width: 100%;\n padding: 0.4rem 0.5rem 0.4rem 0.75rem;\n border: none;\n border-radius: 0;\n font-size: 1rem;\n font-family: var(--il-font-sans);\n background: transparent;\n box-sizing: border-box;\n}\n\n.editable-input:focus {\n outline: 2px solid var(--g-primary-500);\n outline-offset: -2px;\n background: var(--g-surface-0);\n}\n\n.editable-select {\n cursor: pointer;\n}\n\n.cell-prefix,\n.cell-suffix {\n position: absolute;\n font-size: 1rem;\n color: var(--g-surface-600);\n pointer-events: none;\n top: 50%;\n transform: translateY(-50%);\n}\n\n.cell-prefix {\n left: 0.5rem;\n}\n\n.cell-suffix {\n right: 0.5rem;\n}\n\n/* Highlight cells that have been changed by the user */\n.g-cell-changed {\n background: rgba(101, 199, 255, 0.29);\n}\n\n/* Highlight cells with errors */\n.g-cell-error {\n background: var(--g-danger-100);\n position: relative;\n}\n\n.g-cell-error-message {\n background: var(--g-danger-500);\n color: var(--g-surface-0);\n font-size: 0.875rem;\n padding: 0.25rem 0.5rem;\n}\n\n.g-cell-error .editable-input[aria-invalid=\"true\"]:focus {\n outline-color: var(--g-danger-500);\n}\n</style>\n","import {\n computed,\n Reactive,\n reactive,\n ref,\n type Ref,\n toRaw,\n toValue,\n unref,\n watch,\n} from \"vue\";\n\nexport type FilterLocationQueryValueRaw = string | number;\n\n/**\n * Query representation for filtering, compatible with vue-router\n */\nexport type FilterLocationQuery = {\n [p: string]:\n | string\n | null\n | number\n | undefined\n | FilterLocationQueryValueRaw[];\n};\n\nexport interface FilteringOptions {\n syncWith?: Ref<{\n [p: string]: string | null | (string | null)[] | undefined;\n }>;\n}\n\n/**\n * Represents a type that defines a set of filters for a given record type.\n * The keys are based on the record, and the values are possible values\n * for a filter.\n */\nexport type FiltersForRecord<\n T extends object,\n F extends { [K in keyof T]?: any },\n> = {\n [K in keyof T]?: T[K] extends string | number | boolean | undefined | null\n ? T[K] | string | string[]\n : T[K] extends string[] | number[]\n ? T[K][]\n : never;\n};\n\n/**\n * Represents the return type of a composition function used for handling\n * filtering logic in a data structure.\n */\nexport interface UseFilteringReturn<\n T extends Record<string, any> = Record<string, any>,\n F extends { [K in keyof T]?: any } = Record<keyof T, any>,\n> {\n filters: Reactive<F>;\n isFiltered: Ref<boolean>;\n clearFilters: () => void;\n filteredColumns: Ref<Partial<Record<keyof T, boolean>>>;\n}\n\n/**\n * Returns the value if it's not empty, or undefined if it's empty.\n */\nexport function emptyAsUndefined<\n T extends\n | string\n | number\n | boolean\n | string[]\n | number[]\n | undefined\n | null,\n>(value: T) {\n if (Array.isArray(value)) {\n if (value.length === 0) {\n return undefined;\n }\n }\n if (value === null || value === false || value === \"\") {\n return undefined;\n }\n return value;\n}\n\nexport function filterOmitEmpty<T extends object>(value: T): Partial<T> {\n return Object.fromEntries(\n Object.entries(value).filter(([k, v]) => {\n return v && (!Array.isArray(v) || v.length > 0);\n }),\n ) as Partial<T>;\n}\n\n/**\n * Return a value as an array if it's not already one, or\n * undefined if it's undefined.\n */\nexport function asArray<T>(value: T | T[]): NonNullable<T>[] | undefined {\n if (value === undefined || value === null) {\n return undefined;\n }\n if (Array.isArray(value)) {\n // Exclude null and undefined from array\n return value.filter(\n (v) => v !== null && v !== undefined,\n ) as NonNullable<T>[];\n }\n return [value];\n}\n\n/**\n * Converts filter criteria into a format suitable for use as a query object\n * in vue-router.\n */\nexport function filterAsQuery<\n T extends Record<string, any>,\n F extends { [K in keyof T]?: any } = Record<keyof T, any>,\n>(filters: FiltersForRecord<T, F>): FilterLocationQuery {\n let query: FilterLocationQuery = {};\n for (let [key, value] of Object.entries(toRaw(filters))) {\n if (Array.isArray(value)) {\n if (value.length > 0) {\n query[key] = value;\n }\n } else if (value === true) {\n query[key] = \"true\";\n } else {\n query[key] = value || undefined;\n }\n }\n return query;\n}\n\n/**\n * Converts an object of filters into a query parameters object for API calls.\n *\n * Transforms the values into strings or arrays of strings. Excludes fields with undefined,\n * null, empty string, or false values. Supports single values and arrays.\n */\nexport function filtersToQueryParams<T extends Record<string, any>>(\n filters: T,\n): Record<keyof T, string | string[]> {\n const query: Record<string, string | string[]> = {};\n Object.keys(filters).forEach((key) => {\n const value = filters[key];\n if (\n value !== undefined &&\n value !== null &&\n value !== \"\" &&\n value !== false\n ) {\n if (Array.isArray(value)) {\n if (value.length > 0) {\n query[key] = value.map((v) => String(v));\n }\n } else {\n query[key] = String(value);\n }\n }\n });\n return query as Record<keyof T, string | string[]>;\n}\n\n/**\n * Provides a mechanism to manage and synchronize filterable data with given filters and options.\n *\n * @param filters An object that defines the filters applicable to the data record.\n * @param options Configuration options for filtering, such as synchronization.\n * @return Returns an object that can be used with GTable.\n */\nexport function useFiltering<\n T extends Record<string, any> = Record<string, any>,\n F extends { [K in keyof T]?: any } = Record<keyof T, any>,\n>(filters: F, options: FilteringOptions = {}): UseFilteringReturn<T, F> {\n const values = reactive<T>(\n Object.fromEntries(\n Object.entries(filters).map(([key, val]) => [key, val]),\n ) as any,\n );\n const syncWith = options.syncWith;\n\n if (syncWith) {\n if (syncWith.value) {\n const queryParams = toValue(syncWith);\n Object.keys(filters).forEach((key) => {\n if (queryParams[key] !== undefined) {\n // Handle arrays as a comma-separated string\n const val = queryParams[key];\n if (typeof val === \"string\") {\n if (val.includes(\",\")) {\n values[key] = val.split(\",\");\n } else {\n values[key] = val;\n }\n }\n }\n });\n }\n\n watch(\n values,\n (newValues) => {\n syncWith.value = filtersToQueryParams(newValues);\n },\n { deep: true },\n );\n }\n\n const isFiltered = computed(() => {\n for (const key of Object.keys(filters)) {\n if (!!emptyAsUndefined(values[key])) {\n return true;\n }\n }\n return false;\n });\n\n const clearFilters = () => {\n Object.keys(values).forEach((key) => {\n values[key] = undefined;\n });\n };\n\n const filteredColumns = computed(() => {\n const result: Record<string, boolean> = {};\n for (const key of Object.keys(filters)) {\n result[key] = !!emptyAsUndefined(values[key]);\n }\n return result as Record<keyof T, boolean>;\n });\n\n return {\n filters: values as any,\n isFiltered,\n clearFilters,\n filteredColumns,\n };\n}\n","<script lang=\"ts\">\n/**\n * A data table component with support for grouping, sorting, filtering, and pagination.\n *\n * A heavy focus has been on performance. The table body doesn't use any\n * Vue components, it's pure render functions. We've used it with\n * 4000 rows and 14 columns loaded without issues.\n *\n * This is a bit complicated to use, so an example has been omitted here.\n * Instead, look at the source for this demo: [GTable Demo Source](https://github.com/graduatecollege/grad-vue/blob/main/demo/components/demo/GTableDemo.vue).\n *\n * Here are some of the key points.\n *\n * Table content is provided with:\n * - `columns` configuration using the `TableColumn` type.\n * - At minimum the configuration must include `key` for which field of the data\n * objects to use, and `label` for the column header.\n * - `sortable: true` makes the column sortable.\n * - `filter` can be used to provide a `TableColumnFilter` configuration.\n * - `display` accepts a custom render function for the column data.\n * - `trClass` and `tdClass` can be used to provide custom classes for table rows and cells.\n * - `data` array with objects containing fields for the columns.\n *\n * Rows can be made clickable with `row-clickable`. In this case, one of the\n * cells must contain a link. Clicking a row will emit a `row-click` event\n * with the link `href` from the first link in the row.\n *\n * Grouping can be enabled by passing a column key to `groupBy`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\" generic=\"T extends TableRow, C extends TableColumn<T>\">\nimport GTableBody from \"./table/GTableBody.vue\";\nimport GPopover from \"./GPopover.vue\";\nimport { TableColumn, TableRow } from \"./table/TableColumn.ts\";\nimport {\n computed,\n onMounted,\n ref,\n toRaw,\n useId,\n useSlots,\n useTemplateRef,\n VNode,\n watch,\n} from \"vue\";\nimport GSelect from \"./GSelect.vue\";\nimport { useFiltering, UseFilteringReturn } from \"../compose/useFiltering.ts\";\nimport {\n CellChangePayload,\n UseTableChangesReturn,\n} from \"../compose/useTableChanges.ts\";\nimport GButton from \"./GButton.vue\";\n\nexport interface BulkAction {\n /**\n * Action identifier\n */\n id: string;\n /**\n * Action label\n */\n label: string;\n /**\n * Action theme/color\n */\n theme?: \"primary\" | \"secondary\" | \"accent\" | \"danger\";\n}\n\ntype Props = {\n /**\n * Accessible label\n * @demo Colleges\n */\n label: string;\n /**\n * The data to display in the table\n *\n * The data should be an array of objects, each representing a row in the table.\n * Each object should have a unique `key` property that can be used to identify the row.\n */\n data: T[];\n /**\n * The columns to display in the table\n *\n * Each column's key needs to match the key of a property in the data objects,\n * which determines the data to display in that column by default. You can also\n * provide a custom display function to customize the data display.\n */\n columns: C[];\n /**\n * Result count for all of the possible results (not just the current page)\n *\n * This is shown in the toolbar.\n */\n resultCount?: number;\n /**\n * A column key to group the data by\n *\n * If provided, the `groupRender` render function will be used to render the group header.\n */\n groupBy?: keyof T;\n /**\n * A render function to customize the display of the group header.\n *\n * @param groupValue The value of the group key for the current row\n * @param row The row object for the first row in the group\n */\n groupRender?: (groupValue: any, row: T) => VNode;\n /**\n * Filtering object created with useFiltering()\n */\n filtering?: UseFilteringReturn<any>;\n /**\n * Make the table rows clickable\n */\n rowClickable?: boolean;\n /**\n * A function to customize the classes applied to table rows\n * @param row The row object\n */\n rowClass?: (row: T) => string | string[] | undefined;\n /**\n * The starting index for this page\n *\n * This is used for the ARIA rowindex attribute, and is VERY important\n * to not get wrong.\n */\n startIndex: number;\n /**\n * Enable bulk selection with checkboxes\n * @demo\n */\n bulkSelectionEnabled?: boolean;\n /**\n * Array of actions to show in the sticky toolbar when rows are selected\n */\n bulkActions?: BulkAction[];\n\n /**\n * Optional change tracker for editable tables.\n * Pass a composable from useTableChanges() to track user edits.\n */\n changeTracker?: UseTableChangesReturn<T>;\n\n /**\n * Explicitly show the pagination bar even if the slot is empty\n * @demo\n */\n showPagination?: boolean;\n};\n\nconst sortField = defineModel<keyof T>(\"sortField\");\nconst sortOrder = defineModel<1 | -1>(\"sortOrder\");\nconst filter = defineModel<Partial<Record<keyof T, any>>>(\"filter\", {\n default: () => ({}),\n});\nconst selectedRows = defineModel<string[]>(\"selectedRows\", {\n default: () => [],\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n bulkSelectionEnabled: false,\n bulkActions: () => [],\n showPagination: false,\n});\n\nconst emit = defineEmits<{\n (e: \"row-click\", link: string): void;\n (e: \"bulk-action\", actionId: string, selectedKeys: string[]): void;\n (e: \"cell-change\", payload: CellChangePayload<T>): void;\n}>();\n\nfunction onSort(col: TableColumn<T>) {\n if (!col.sortable) {\n return;\n }\n if (sortField.value === col.key) {\n if (sortOrder.value === 1) {\n sortOrder.value = -1;\n } else if (sortOrder.value === -1) {\n sortField.value = undefined as any;\n sortOrder.value = 1;\n }\n } else {\n sortField.value = col.key;\n sortOrder.value = 1;\n }\n}\n\nlet filtering: UseFilteringReturn<any> = props.filtering!;\n\nif (!filtering) {\n filtering = useFiltering({}) as any;\n}\n\nconst { filters, filteredColumns, isFiltered, clearFilters } = filtering;\n\n// Bulk selection logic\nconst allRowKeys = computed(() => props.data.map((row) => row.key));\nconst selectedRowsOnPage = computed(() => {\n return selectedRows.value.filter((key) => allRowKeys.value.includes(key));\n});\nconst allSelected = computed(() => {\n if (!props.bulkSelectionEnabled || props.data.length === 0) {\n return false;\n }\n return selectedRowsOnPage.value.length === allRowKeys.value.length;\n});\nconst someSelected = computed(() => {\n if (!props.bulkSelectionEnabled || props.data.length === 0) {\n return false;\n }\n return (\n selectedRowsOnPage.value.length > 0 &&\n selectedRowsOnPage.value.length < allRowKeys.value.length\n );\n});\n\nconst lastClickedRowKey = ref<string | null>(null);\n\nfunction toggleAllRows() {\n if (allSelected.value) {\n // Deselect all rows on current page\n selectedRows.value = selectedRows.value.filter(\n (key) => !allRowKeys.value.includes(key),\n );\n } else {\n // Select all rows on current page\n const newSelected = new Set(selectedRows.value);\n allRowKeys.value.forEach((key) => newSelected.add(key));\n selectedRows.value = Array.from(newSelected);\n }\n}\n\nfunction toggleRow(rowKey: string, shiftKey: boolean = false) {\n if (shiftKey && lastClickedRowKey.value) {\n // Handle shift-click range selection\n const lastIndex = allRowKeys.value.indexOf(lastClickedRowKey.value);\n const currentIndex = allRowKeys.value.indexOf(rowKey);\n\n if (lastIndex !== -1 && currentIndex !== -1) {\n const start = Math.min(lastIndex, currentIndex);\n const end = Math.max(lastIndex, currentIndex);\n const rowsInRange = allRowKeys.value.slice(start, end + 1);\n\n // Select all rows in the range\n const newSelected = new Set(selectedRows.value);\n rowsInRange.forEach((key) => newSelected.add(key));\n selectedRows.value = Array.from(newSelected);\n }\n } else {\n // Normal toggle behavior\n if (selectedRows.value.includes(rowKey)) {\n selectedRows.value = selectedRows.value.filter(\n (key) => key !== rowKey,\n );\n } else {\n selectedRows.value = [...selectedRows.value, rowKey];\n }\n }\n\n // Update last clicked row\n lastClickedRowKey.value = rowKey;\n}\n\nfunction clickRow(link: string) {\n emit(\"row-click\", link);\n}\n\nfunction handleBulkAction(actionId: string) {\n emit(\"bulk-action\", actionId, selectedRows.value);\n}\n\nfunction handleCellChange(change: { row: T; column: C; value: any }) {\n // Update the reactive data\n // Convert the value to the appropriate type based on input attributes\n let convertedValue: any = change.value;\n const columnKey = change.column.key;\n const previousValue = toRaw(change.row[columnKey]);\n if (change.column.editable?.inputAttributes?.type === \"number\") {\n convertedValue = change.value === \"\" ? null : Number(change.value);\n }\n change.row[columnKey] = convertedValue;\n\n const payload: CellChangePayload<T> = {\n row: change.row,\n column: change.column,\n value: convertedValue,\n previousValue,\n };\n\n emit(\"cell-change\", payload);\n}\n\nconst id = useId();\nconst slots = useSlots();\n\nconst shouldShowPagination = computed(() => {\n // Show if explicitly requested via prop\n if (props.showPagination) {\n return true;\n }\n // Show if the pagination slot has content\n return !!slots.pagination;\n});\n\nconst shouldShowControls = computed(() => {\n // Show if filters are active (clear filters button is visible)\n if (isFiltered.value) {\n return true;\n }\n // Show if pagination should be shown\n if (shouldShowPagination.value) {\n return true;\n }\n // Otherwise hide the entire controls bar\n return false;\n});\n\nonMounted(() => {\n if (props.rowClickable && props.bulkSelectionEnabled) {\n console.warn(\n \"GTable: rowClickable and bulkSelectionEnabled cannot be used together. rowClickable will be ignored.\",\n );\n }\n for (const col of props.columns) {\n if (col.editable && col.display) {\n console.warn(\n `GTable: Column \"${String(col.key)}\" has both 'editable' and 'display' configured. 'display' will be ignored.`,\n );\n }\n if (col.filter && col.filter.type === \"multi-select\") {\n if (!Array.isArray(filter.value[col.key])) {\n let val = filter.value[col.key];\n filter.value[col.key] = val ? [val] : [];\n }\n }\n }\n});\n\nwatch(\n () => props.columns,\n (newColumns) => {\n for (const col of newColumns) {\n if (col.filter && col.filter.type === \"multi-select\") {\n if (!Array.isArray(filter.value[col.key])) {\n let val = filter.value[col.key];\n filter.value[col.key] = val ? [val] : [];\n }\n }\n }\n },\n { immediate: true },\n);\n</script>\n\n<template>\n <div class=\"g-table-outer-wrap\">\n <div v-if=\"shouldShowControls\" class=\"g-table-controls\">\n <div class=\"g-clear-filters-wrap\">\n <GButton\n v-if=\"isFiltered\"\n outlined\n size=\"small\"\n class=\"clear-filters\"\n @click=\"clearFilters\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n height=\"1em\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n <span class=\"g-clear-filters-text\"> Clear Filters </span>\n </GButton>\n </div>\n <div v-if=\"shouldShowPagination\" class=\"pagination\">\n <slot name=\"pagination\"></slot>\n </div>\n <span class=\"g-result-count\"\n >{{ props.resultCount || data.length }} results</span\n >\n </div>\n <table\n class=\"g-table\"\n ref=\"tableRef\"\n :aria-label=\"label\"\n :aria-rowcount=\"props.resultCount || data.length\"\n >\n <thead class=\"g-table-head\">\n <tr aria-rowindex=\"1\">\n <th\n v-if=\"bulkSelectionEnabled\"\n scope=\"col\"\n class=\"g-th g-th-checkbox\"\n >\n <input\n type=\"checkbox\"\n :checked=\"allSelected\"\n :indeterminate=\"someSelected\"\n @change=\"toggleAllRows\"\n :aria-label=\"\n allSelected\n ? 'Deselect all rows'\n : 'Select all rows'\n \"\n class=\"g-bulk-select-checkbox\"\n />\n </th>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n :id=\"`${id}-th-${String(col.key)}`\"\n :aria-sort=\"\n sortField === col.key\n ? sortOrder === 1\n ? 'ascending'\n : 'descending'\n : 'none'\n \"\n :class=\"[\n 'g-th',\n { sorted: sortField === col.key },\n { filtered: filteredColumns[col.key] },\n ]\"\n scope=\"col\"\n >\n <div class=\"th-inner\">\n <button\n v-if=\"col.sortable\"\n type=\"button\"\n class=\"g-column-head\"\n @click=\"onSort(col)\"\n >\n {{ col.label }}\n <span\n v-if=\"sortField === col.key\"\n class=\"sort-indicator\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.5em\"\n role=\"img\"\n :aria-label=\"\n sortOrder === 1\n ? 'Sorted ascending'\n : 'Sorted descending'\n \"\n :style=\"{\n transform: `rotate(${sortOrder === 1 ? 0 : 180}deg)`,\n }\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M300.3 199.2C312.9 188.9 331.4 189.7 343.1 201.4L471.1 329.4C480.3 338.6 483 352.3 478 364.3C473 376.3 461.4 384 448.5 384L192.5 384C179.6 384 167.9 376.2 162.9 364.2C157.9 352.2 160.7 338.5 169.9 329.4L297.9 201.4L300.3 199.2z\"\n />\n </svg>\n </span>\n </button>\n <span v-else class=\"g-column-head\">{{\n col.label\n }}</span>\n <GPopover v-if=\"col.filter\">\n <template #trigger=\"{ toggle }\">\n <button\n @click.stop=\"toggle\"\n :aria-label=\"\n filteredColumns[col.key]\n ? 'Column Filtered'\n : 'Filter Column'\n \"\n class=\"g-filter-btn\"\n :class=\"{\n 'g-active':\n filteredColumns[col.key],\n }\"\n type=\"button\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.5em\"\n aria-hidden=\"true\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M96 128C83.1 128 71.4 135.8 66.4 147.8C61.4 159.8 64.2 173.5 73.4 182.6L256 365.3L256 480C256 488.5 259.4 496.6 265.4 502.6L329.4 566.6C338.6 575.8 352.3 578.5 364.3 573.5C376.3 568.5 384 556.9 384 544L384 365.3L566.6 182.7C575.8 173.5 578.5 159.8 573.5 147.8C568.5 135.8 556.9 128 544 128L96 128z\"\n />\n </svg>\n </button>\n </template>\n <GSelect\n v-if=\"col.filter.type === 'select'\"\n v-model=\"filter[col.key]\"\n :options=\"col.filter.options\"\n class=\"g-filter-select\"\n label=\"Filter select\"\n searchable\n clear-button\n />\n <div v-else-if=\"col.filter.type === 'toggle'\">\n <div class=\"g-filter-toggle\">\n <input\n type=\"checkbox\"\n v-model=\"filter[col.key]\"\n :id=\"`${id}-filter-${String(col.key)}`\"\n :aria-describedby=\"\n col.filter.description\n ? `${id}-filter-description-${String(col.key)}`\n : undefined\n \"\n />\n <label\n :for=\"`${id}-filter-${String(col.key)}`\"\n >{{ col.filter.label }}</label\n >\n <span\n class=\"g-filter-description\"\n v-if=\"col.filter.description\"\n :id=\"`${id}-filter-description-${String(col.key)}`\"\n >\n {{ col.filter.description }}\n </span>\n </div>\n </div>\n <fieldset\n v-else-if=\"\n col.filter.type === 'multi-select'\n \"\n class=\"g-multi-select\"\n >\n <legend class=\"g-multi-select-legend\">\n Include values\n </legend>\n <div\n v-for=\"opt in col.filter.options\"\n :key=\"opt.value\"\n >\n <input\n type=\"checkbox\"\n v-model=\"filter[col.key]\"\n :id=\"`filter-${String(col.key)}-${opt.value}`\"\n :value=\"opt.value\"\n name=\"filter-multiselect\"\n />\n <label\n :for=\"`filter-${String(col.key)}-${opt.value}`\"\n >{{ opt.label }}</label\n >\n </div>\n <GButton\n class=\"clear-multiselect-btn\"\n theme=\"accent\"\n size=\"small\"\n @click=\"filter[col.key] = []\"\n v-if=\"\n filter[col.key] &&\n filter[col.key].length\n \"\n >\n Clear\n </GButton>\n </fieldset>\n </GPopover>\n </div>\n </th>\n </tr>\n </thead>\n <!-- @vue-generic {T, C} -->\n <GTableBody\n :data=\"data\"\n :columns=\"columns\"\n :group-by=\"groupBy\"\n :group-render=\"groupRender\"\n :row-clickable=\"rowClickable\"\n :row-class=\"rowClass as any\"\n :start-index=\"startIndex\"\n :bulk-selection-enabled=\"bulkSelectionEnabled\"\n :selected-rows=\"selectedRows\"\n :table-id=\"id\"\n :change-tracker=\"changeTracker\"\n @row-click=\"clickRow\"\n @toggle-row=\"toggleRow\"\n @cell-change=\"handleCellChange\"\n />\n </table>\n <div\n v-if=\"bulkSelectionEnabled && selectedRows.length > 0\"\n class=\"g-bulk-actions-toolbar\"\n >\n <span class=\"g-selected-count\"\n >{{ selectedRows.length }} row{{\n selectedRows.length === 1 ? \"\" : \"s\"\n }}\n selected</span\n >\n <ul class=\"g-bulk-actions\">\n <li v-for=\"action in bulkActions\" :key=\"action.id\">\n <GButton\n :theme=\"action.theme || 'accent'\"\n @click=\"handleBulkAction(action.id)\"\n size=\"small\"\n >\n {{ action.label }} {{ selectedRows.length }} row{{\n selectedRows.length === 1 ? \"\" : \"s\"\n }}\n </GButton>\n </li>\n </ul>\n </div>\n </div>\n</template>\n\n<style>\ng-table {\n display: block;\n}\n.g-table-outer-wrap {\n}\n\n.g-table-controls {\n height: 40px;\n position: sticky;\n display: flex;\n top: 0;\n left: 0;\n padding: 2px 6px;\n}\n\n.g-table-head {\n background: var(--g-surface-0);\n position: sticky;\n top: 40px;\n z-index: 1;\n}\n\n.g-th {\n text-align: left;\n padding: 0.5rem 0.2rem;\n border: 0;\n border-bottom: 2px solid var(--g-surface-900);\n background: var(--g-surface-0);\n\n &.sorted {\n color: var(--ilw-color--link-hover);\n }\n\n &.filtered {\n .g-filter-btn {\n color: var(--ilw-color--link-hover);\n }\n }\n\n .th-inner {\n display: flex;\n align-items: center;\n }\n}\n\n.g-column-head {\n color: currentColor;\n position: relative;\n border: none;\n font-weight: 700;\n font-family: var(--il-font-sans);\n font-size: 1rem;\n line-height: 1.3;\n white-space: nowrap;\n padding-left: 4px;\n background: var(--g-surface-0);\n\n .sort-indicator {\n position: absolute;\n bottom: -1.1em;\n left: calc(50% - 0.7em);\n }\n}\n\nth:first-of-type .g-column-head {\n padding-left: 0;\n}\n\nbutton.g-column-head {\n cursor: pointer;\n height: 2rem;\n}\n\nbutton.g-column-head:hover {\n text-decoration: underline;\n color: var(--ilw-color--link-hover);\n}\n\n.g-table {\n border-spacing: 0;\n min-width: 100%;\n}\n\n.g-filter-btn {\n border: none;\n background: transparent;\n border-radius: 50%;\n width: 2rem;\n height: 2rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n &.g-active {\n border: 2px solid var(--ilw-color--link-hover);\n }\n}\n\n.g-clear-filters-text {\n white-space: nowrap;\n}\n\n@media screen and (max-width: 600px) {\n .g-clear-filters-text {\n opacity: 0;\n width: 1px;\n height: 1px;\n overflow: hidden;\n }\n}\n\n.g-filter-select {\n min-width: 200px;\n}\n\n.g-table-controls {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1.5rem;\n padding: 0.2rem 1rem;\n background: var(--g-surface-150);\n\n .g-result-count {\n font-size: 1rem;\n line-height: 1.2;\n }\n}\n\n.g-multi-select {\n border: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n\n .clear-multiselect-btn {\n margin-top: 0.5rem;\n }\n\n legend {\n font-size: 1.125rem;\n font-weight: bold;\n margin-bottom: 0.5rem;\n }\n\n div {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n\n &:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n }\n }\n\n input {\n width: 24px;\n height: 24px;\n accent-color: var(--g-primary-500);\n display: block;\n }\n\n label {\n font-size: 1.125rem;\n flex: 1;\n }\n}\n\n.g-multi-select-legend {\n margin: 0;\n padding: 0;\n font-size: 1rem;\n line-height: 1.2;\n}\n\n.g-filter-toggle {\n display: grid;\n grid-template-areas:\n \"label input\"\n \"description description\";\n\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: 0.5rem;\n\n input {\n width: 24px;\n height: 24px;\n }\n\n label {\n font-size: 1.125rem;\n font-weight: bold;\n }\n\n .g-filter-description {\n grid-area: description;\n }\n}\n\n.g-clear-filters-wrap,\n.g-result-count {\n}\n\n/* Bulk selection styles */\n.g-th-checkbox {\n width: 50px;\n text-align: center;\n}\n\n.g-bulk-select-checkbox {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--g-primary-500);\n}\n\n.g-bulk-actions-toolbar {\n position: sticky;\n bottom: 0;\n left: 0;\n right: 0;\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n padding: 0.75rem 1rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);\n z-index: 1;\n\n ul {\n display: flex;\n gap: 1rem;\n list-style: none;\n padding: 0;\n margin: 0;\n }\n\n li {\n margin: 0;\n }\n}\n\n.g-selected-count {\n font-weight: 600;\n font-size: 1rem;\n}\n\n.g-bulk-actions {\n display: flex;\n gap: 0.5rem;\n align-items: center;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A data table component with support for grouping, sorting, filtering, and pagination.\n *\n * A heavy focus has been on performance. The table body doesn't use any\n * Vue components, it's pure render functions. We've used it with\n * 4000 rows and 14 columns loaded without issues.\n *\n * This is a bit complicated to use, so an example has been omitted here.\n * Instead, look at the source for this demo: [GTable Demo Source](https://github.com/graduatecollege/grad-vue/blob/main/demo/components/demo/GTableDemo.vue).\n *\n * Here are some of the key points.\n *\n * Table content is provided with:\n * - `columns` configuration using the `TableColumn` type.\n * - At minimum the configuration must include `key` for which field of the data\n * objects to use, and `label` for the column header.\n * - `sortable: true` makes the column sortable.\n * - `filter` can be used to provide a `TableColumnFilter` configuration.\n * - `display` accepts a custom render function for the column data.\n * - `trClass` and `tdClass` can be used to provide custom classes for table rows and cells.\n * - `data` array with objects containing fields for the columns.\n *\n * Rows can be made clickable with `row-clickable`. In this case, one of the\n * cells must contain a link. Clicking a row will emit a `row-click` event\n * with the link `href` from the first link in the row.\n *\n * Grouping can be enabled by passing a column key to `groupBy`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\" generic=\"T extends TableRow, C extends TableColumn<T>\">\nimport GTableBody from \"./table/GTableBody.vue\";\nimport GPopover from \"./GPopover.vue\";\nimport { TableColumn, TableRow } from \"./table/TableColumn.ts\";\nimport {\n computed,\n onMounted,\n ref,\n toRaw,\n useId,\n useSlots,\n useTemplateRef,\n VNode,\n watch,\n} from \"vue\";\nimport GSelect from \"./GSelect.vue\";\nimport { useFiltering, UseFilteringReturn } from \"../compose/useFiltering.ts\";\nimport {\n CellChangePayload,\n UseTableChangesReturn,\n} from \"../compose/useTableChanges.ts\";\nimport GButton from \"./GButton.vue\";\n\nexport interface BulkAction {\n /**\n * Action identifier\n */\n id: string;\n /**\n * Action label\n */\n label: string;\n /**\n * Action theme/color\n */\n theme?: \"primary\" | \"secondary\" | \"accent\" | \"danger\";\n}\n\ntype Props = {\n /**\n * Accessible label\n * @demo Colleges\n */\n label: string;\n /**\n * The data to display in the table\n *\n * The data should be an array of objects, each representing a row in the table.\n * Each object should have a unique `key` property that can be used to identify the row.\n */\n data: T[];\n /**\n * The columns to display in the table\n *\n * Each column's key needs to match the key of a property in the data objects,\n * which determines the data to display in that column by default. You can also\n * provide a custom display function to customize the data display.\n */\n columns: C[];\n /**\n * Result count for all of the possible results (not just the current page)\n *\n * This is shown in the toolbar.\n */\n resultCount?: number;\n /**\n * A column key to group the data by\n *\n * If provided, the `groupRender` render function will be used to render the group header.\n */\n groupBy?: keyof T;\n /**\n * A render function to customize the display of the group header.\n *\n * @param groupValue The value of the group key for the current row\n * @param row The row object for the first row in the group\n */\n groupRender?: (groupValue: any, row: T) => VNode;\n /**\n * Filtering object created with useFiltering()\n */\n filtering?: UseFilteringReturn<any>;\n /**\n * Make the table rows clickable\n */\n rowClickable?: boolean;\n /**\n * A function to customize the classes applied to table rows\n * @param row The row object\n */\n rowClass?: (row: T) => string | string[] | undefined;\n /**\n * The starting index for this page\n *\n * This is used for the ARIA rowindex attribute, and is VERY important\n * to not get wrong.\n */\n startIndex: number;\n /**\n * Enable bulk selection with checkboxes\n * @demo\n */\n bulkSelectionEnabled?: boolean;\n /**\n * Array of actions to show in the sticky toolbar when rows are selected\n */\n bulkActions?: BulkAction[];\n\n /**\n * Optional change tracker for editable tables.\n * Pass a composable from useTableChanges() to track user edits.\n */\n changeTracker?: UseTableChangesReturn<T>;\n\n /**\n * Explicitly show the pagination bar even if the slot is empty\n * @demo\n */\n showPagination?: boolean;\n};\n\nconst sortField = defineModel<keyof T>(\"sortField\");\nconst sortOrder = defineModel<1 | -1>(\"sortOrder\");\nconst filter = defineModel<Partial<Record<keyof T, any>>>(\"filter\", {\n default: () => ({}),\n});\nconst selectedRows = defineModel<string[]>(\"selectedRows\", {\n default: () => [],\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n bulkSelectionEnabled: false,\n bulkActions: () => [],\n showPagination: false,\n});\n\nconst emit = defineEmits<{\n (e: \"row-click\", link: string): void;\n (e: \"bulk-action\", actionId: string, selectedKeys: string[]): void;\n (e: \"cell-change\", payload: CellChangePayload<T>): void;\n}>();\n\nfunction onSort(col: TableColumn<T>) {\n if (!col.sortable) {\n return;\n }\n if (sortField.value === col.key) {\n if (sortOrder.value === 1) {\n sortOrder.value = -1;\n } else if (sortOrder.value === -1) {\n sortField.value = undefined as any;\n sortOrder.value = 1;\n }\n } else {\n sortField.value = col.key;\n sortOrder.value = 1;\n }\n}\n\nlet filtering: UseFilteringReturn<any> = props.filtering!;\n\nif (!filtering) {\n filtering = useFiltering({}) as any;\n}\n\nconst { filters, filteredColumns, isFiltered, clearFilters } = filtering;\n\n// Bulk selection logic\nconst allRowKeys = computed(() => props.data.map((row) => row.key));\nconst selectedRowsOnPage = computed(() => {\n return selectedRows.value.filter((key) => allRowKeys.value.includes(key));\n});\nconst allSelected = computed(() => {\n if (!props.bulkSelectionEnabled || props.data.length === 0) {\n return false;\n }\n return selectedRowsOnPage.value.length === allRowKeys.value.length;\n});\nconst someSelected = computed(() => {\n if (!props.bulkSelectionEnabled || props.data.length === 0) {\n return false;\n }\n return (\n selectedRowsOnPage.value.length > 0 &&\n selectedRowsOnPage.value.length < allRowKeys.value.length\n );\n});\n\nconst lastClickedRowKey = ref<string | null>(null);\n\nfunction toggleAllRows() {\n if (allSelected.value) {\n // Deselect all rows on current page\n selectedRows.value = selectedRows.value.filter(\n (key) => !allRowKeys.value.includes(key),\n );\n } else {\n // Select all rows on current page\n const newSelected = new Set(selectedRows.value);\n allRowKeys.value.forEach((key) => newSelected.add(key));\n selectedRows.value = Array.from(newSelected);\n }\n}\n\nfunction toggleRow(rowKey: string, shiftKey: boolean = false) {\n if (shiftKey && lastClickedRowKey.value) {\n // Handle shift-click range selection\n const lastIndex = allRowKeys.value.indexOf(lastClickedRowKey.value);\n const currentIndex = allRowKeys.value.indexOf(rowKey);\n\n if (lastIndex !== -1 && currentIndex !== -1) {\n const start = Math.min(lastIndex, currentIndex);\n const end = Math.max(lastIndex, currentIndex);\n const rowsInRange = allRowKeys.value.slice(start, end + 1);\n\n // Select all rows in the range\n const newSelected = new Set(selectedRows.value);\n rowsInRange.forEach((key) => newSelected.add(key));\n selectedRows.value = Array.from(newSelected);\n }\n } else {\n // Normal toggle behavior\n if (selectedRows.value.includes(rowKey)) {\n selectedRows.value = selectedRows.value.filter(\n (key) => key !== rowKey,\n );\n } else {\n selectedRows.value = [...selectedRows.value, rowKey];\n }\n }\n\n // Update last clicked row\n lastClickedRowKey.value = rowKey;\n}\n\nfunction clickRow(link: string) {\n emit(\"row-click\", link);\n}\n\nfunction handleBulkAction(actionId: string) {\n emit(\"bulk-action\", actionId, selectedRows.value);\n}\n\nfunction handleCellChange(change: { row: T; column: C; value: any }) {\n // Update the reactive data\n // Convert the value to the appropriate type based on input attributes\n let convertedValue: any = change.value;\n const columnKey = change.column.key;\n const previousValue = toRaw(change.row[columnKey]);\n if (change.column.editable?.inputAttributes?.type === \"number\") {\n convertedValue = change.value === \"\" ? null : Number(change.value);\n }\n change.row[columnKey] = convertedValue;\n\n const payload: CellChangePayload<T> = {\n row: change.row,\n column: change.column,\n value: convertedValue,\n previousValue,\n };\n\n emit(\"cell-change\", payload);\n}\n\nconst id = useId();\nconst slots = useSlots();\n\nconst shouldShowPagination = computed(() => {\n // Show if explicitly requested via prop\n if (props.showPagination) {\n return true;\n }\n // Show if the pagination slot has content\n return !!slots.pagination;\n});\n\nconst shouldShowControls = computed(() => {\n // Show if filters are active (clear filters button is visible)\n if (isFiltered.value) {\n return true;\n }\n // Show if pagination should be shown\n if (shouldShowPagination.value) {\n return true;\n }\n // Otherwise hide the entire controls bar\n return false;\n});\n\nonMounted(() => {\n if (props.rowClickable && props.bulkSelectionEnabled) {\n console.warn(\n \"GTable: rowClickable and bulkSelectionEnabled cannot be used together. rowClickable will be ignored.\",\n );\n }\n for (const col of props.columns) {\n if (col.editable && col.display) {\n console.warn(\n `GTable: Column \"${String(col.key)}\" has both 'editable' and 'display' configured. 'display' will be ignored.`,\n );\n }\n if (col.filter && col.filter.type === \"multi-select\") {\n if (!Array.isArray(filter.value[col.key])) {\n let val = filter.value[col.key];\n filter.value[col.key] = val ? [val] : [];\n }\n }\n }\n});\n\nwatch(\n () => props.columns,\n (newColumns) => {\n for (const col of newColumns) {\n if (col.filter && col.filter.type === \"multi-select\") {\n if (!Array.isArray(filter.value[col.key])) {\n let val = filter.value[col.key];\n filter.value[col.key] = val ? [val] : [];\n }\n }\n }\n },\n { immediate: true },\n);\n</script>\n\n<template>\n <div class=\"g-table-outer-wrap\">\n <div v-if=\"shouldShowControls\" class=\"g-table-controls\">\n <div class=\"g-clear-filters-wrap\">\n <GButton\n v-if=\"isFiltered\"\n outlined\n size=\"small\"\n class=\"clear-filters\"\n @click=\"clearFilters\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n height=\"1em\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n <span class=\"g-clear-filters-text\"> Clear Filters </span>\n </GButton>\n </div>\n <div v-if=\"shouldShowPagination\" class=\"pagination\">\n <slot name=\"pagination\"></slot>\n </div>\n <span class=\"g-result-count\"\n >{{ props.resultCount || data.length }} results</span\n >\n </div>\n <table\n class=\"g-table\"\n ref=\"tableRef\"\n :aria-label=\"label\"\n :aria-rowcount=\"props.resultCount || data.length\"\n >\n <thead class=\"g-table-head\">\n <tr aria-rowindex=\"1\">\n <th\n v-if=\"bulkSelectionEnabled\"\n scope=\"col\"\n class=\"g-th g-th-checkbox\"\n >\n <input\n type=\"checkbox\"\n :checked=\"allSelected\"\n :indeterminate=\"someSelected\"\n @change=\"toggleAllRows\"\n :aria-label=\"\n allSelected\n ? 'Deselect all rows'\n : 'Select all rows'\n \"\n class=\"g-bulk-select-checkbox\"\n />\n </th>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n :id=\"`${id}-th-${String(col.key)}`\"\n :aria-sort=\"\n sortField === col.key\n ? sortOrder === 1\n ? 'ascending'\n : 'descending'\n : 'none'\n \"\n :class=\"[\n 'g-th',\n { sorted: sortField === col.key },\n { filtered: filteredColumns[col.key] },\n ]\"\n scope=\"col\"\n >\n <div class=\"th-inner\">\n <button\n v-if=\"col.sortable\"\n type=\"button\"\n class=\"g-column-head\"\n @click=\"onSort(col)\"\n >\n {{ col.label }}\n <span\n v-if=\"sortField === col.key\"\n class=\"sort-indicator\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.5em\"\n role=\"img\"\n :aria-label=\"\n sortOrder === 1\n ? 'Sorted ascending'\n : 'Sorted descending'\n \"\n :style=\"{\n transform: `rotate(${sortOrder === 1 ? 0 : 180}deg)`,\n }\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M300.3 199.2C312.9 188.9 331.4 189.7 343.1 201.4L471.1 329.4C480.3 338.6 483 352.3 478 364.3C473 376.3 461.4 384 448.5 384L192.5 384C179.6 384 167.9 376.2 162.9 364.2C157.9 352.2 160.7 338.5 169.9 329.4L297.9 201.4L300.3 199.2z\"\n />\n </svg>\n </span>\n </button>\n <span v-else class=\"g-column-head\">{{\n col.label\n }}</span>\n <GPopover v-if=\"col.filter\">\n <template #trigger=\"{ toggle }\">\n <button\n @click.stop=\"toggle\"\n :aria-label=\"\n filteredColumns[col.key]\n ? 'Column Filtered'\n : 'Filter Column'\n \"\n class=\"g-filter-btn\"\n :class=\"{\n 'g-active':\n filteredColumns[col.key],\n }\"\n type=\"button\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.5em\"\n aria-hidden=\"true\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M96 128C83.1 128 71.4 135.8 66.4 147.8C61.4 159.8 64.2 173.5 73.4 182.6L256 365.3L256 480C256 488.5 259.4 496.6 265.4 502.6L329.4 566.6C338.6 575.8 352.3 578.5 364.3 573.5C376.3 568.5 384 556.9 384 544L384 365.3L566.6 182.7C575.8 173.5 578.5 159.8 573.5 147.8C568.5 135.8 556.9 128 544 128L96 128z\"\n />\n </svg>\n </button>\n </template>\n <GSelect\n v-if=\"col.filter.type === 'select'\"\n v-model=\"filter[col.key]\"\n :options=\"col.filter.options\"\n class=\"g-filter-select\"\n label=\"Filter select\"\n searchable\n clear-button\n />\n <div v-else-if=\"col.filter.type === 'toggle'\">\n <div class=\"g-filter-toggle\">\n <input\n type=\"checkbox\"\n v-model=\"filter[col.key]\"\n :id=\"`${id}-filter-${String(col.key)}`\"\n :aria-describedby=\"\n col.filter.description\n ? `${id}-filter-description-${String(col.key)}`\n : undefined\n \"\n />\n <label\n :for=\"`${id}-filter-${String(col.key)}`\"\n >{{ col.filter.label }}</label\n >\n <span\n class=\"g-filter-description\"\n v-if=\"col.filter.description\"\n :id=\"`${id}-filter-description-${String(col.key)}`\"\n >\n {{ col.filter.description }}\n </span>\n </div>\n </div>\n <fieldset\n v-else-if=\"\n col.filter.type === 'multi-select'\n \"\n class=\"g-multi-select\"\n >\n <legend class=\"g-multi-select-legend\">\n Include values\n </legend>\n <div\n v-for=\"opt in col.filter.options\"\n :key=\"opt.value\"\n >\n <input\n type=\"checkbox\"\n v-model=\"filter[col.key]\"\n :id=\"`filter-${String(col.key)}-${opt.value}`\"\n :value=\"opt.value\"\n name=\"filter-multiselect\"\n />\n <label\n :for=\"`filter-${String(col.key)}-${opt.value}`\"\n >{{ opt.label }}</label\n >\n </div>\n <GButton\n class=\"clear-multiselect-btn\"\n theme=\"accent\"\n size=\"small\"\n @click=\"filter[col.key] = []\"\n v-if=\"\n filter[col.key] &&\n filter[col.key].length\n \"\n >\n Clear\n </GButton>\n </fieldset>\n </GPopover>\n </div>\n </th>\n </tr>\n </thead>\n <!-- @vue-generic {T, C} -->\n <GTableBody\n :data=\"data\"\n :columns=\"columns\"\n :group-by=\"groupBy\"\n :group-render=\"groupRender\"\n :row-clickable=\"rowClickable\"\n :row-class=\"rowClass as any\"\n :start-index=\"startIndex\"\n :bulk-selection-enabled=\"bulkSelectionEnabled\"\n :selected-rows=\"selectedRows\"\n :table-id=\"id\"\n :change-tracker=\"changeTracker\"\n @row-click=\"clickRow\"\n @toggle-row=\"toggleRow\"\n @cell-change=\"handleCellChange\"\n />\n </table>\n <div\n v-if=\"bulkSelectionEnabled && selectedRows.length > 0\"\n class=\"g-bulk-actions-toolbar\"\n >\n <span class=\"g-selected-count\"\n >{{ selectedRows.length }} row{{\n selectedRows.length === 1 ? \"\" : \"s\"\n }}\n selected</span\n >\n <ul class=\"g-bulk-actions\">\n <li v-for=\"action in bulkActions\" :key=\"action.id\">\n <GButton\n :theme=\"action.theme || 'accent'\"\n @click=\"handleBulkAction(action.id)\"\n size=\"small\"\n >\n {{ action.label }} {{ selectedRows.length }} row{{\n selectedRows.length === 1 ? \"\" : \"s\"\n }}\n </GButton>\n </li>\n </ul>\n </div>\n </div>\n</template>\n\n<style>\ng-table {\n display: block;\n}\n.g-table-outer-wrap {\n}\n\n.g-table-controls {\n height: 40px;\n position: sticky;\n display: flex;\n top: 0;\n left: 0;\n padding: 2px 6px;\n}\n\n.g-table-head {\n background: var(--g-surface-0);\n position: sticky;\n top: 40px;\n z-index: 1;\n}\n\n.g-th {\n text-align: left;\n padding: 0.5rem 0.2rem;\n border: 0;\n border-bottom: 2px solid var(--g-surface-900);\n background: var(--g-surface-0);\n\n &.sorted {\n color: var(--ilw-color--link-hover);\n }\n\n &.filtered {\n .g-filter-btn {\n color: var(--ilw-color--link-hover);\n }\n }\n\n .th-inner {\n display: flex;\n align-items: center;\n }\n}\n\n.g-column-head {\n color: currentColor;\n position: relative;\n border: none;\n font-weight: 700;\n font-family: var(--il-font-sans);\n font-size: 1rem;\n line-height: 1.3;\n white-space: nowrap;\n padding-left: 4px;\n background: var(--g-surface-0);\n\n .sort-indicator {\n position: absolute;\n bottom: -1.1em;\n left: calc(50% - 0.7em);\n }\n}\n\nth:first-of-type .g-column-head {\n padding-left: 0;\n}\n\nbutton.g-column-head {\n cursor: pointer;\n height: 2rem;\n}\n\nbutton.g-column-head:hover {\n text-decoration: underline;\n color: var(--ilw-color--link-hover);\n}\n\n.g-table {\n border-spacing: 0;\n min-width: 100%;\n}\n\n.g-filter-btn {\n border: none;\n background: transparent;\n border-radius: 50%;\n width: 2rem;\n height: 2rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n &.g-active {\n border: 2px solid var(--ilw-color--link-hover);\n }\n}\n\n.g-clear-filters-text {\n white-space: nowrap;\n}\n\n@media screen and (max-width: 600px) {\n .g-clear-filters-text {\n opacity: 0;\n width: 1px;\n height: 1px;\n overflow: hidden;\n }\n}\n\n.g-filter-select {\n min-width: 200px;\n}\n\n.g-table-controls {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1.5rem;\n padding: 0.2rem 1rem;\n background: var(--g-surface-150);\n\n .g-result-count {\n font-size: 1rem;\n line-height: 1.2;\n }\n}\n\n.g-multi-select {\n border: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n\n .clear-multiselect-btn {\n margin-top: 0.5rem;\n }\n\n legend {\n font-size: 1.125rem;\n font-weight: bold;\n margin-bottom: 0.5rem;\n }\n\n div {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n\n &:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n }\n }\n\n input {\n width: 24px;\n height: 24px;\n accent-color: var(--g-primary-500);\n display: block;\n }\n\n label {\n font-size: 1.125rem;\n flex: 1;\n }\n}\n\n.g-multi-select-legend {\n margin: 0;\n padding: 0;\n font-size: 1rem;\n line-height: 1.2;\n}\n\n.g-filter-toggle {\n display: grid;\n grid-template-areas:\n \"label input\"\n \"description description\";\n\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: 0.5rem;\n\n input {\n width: 24px;\n height: 24px;\n }\n\n label {\n font-size: 1.125rem;\n font-weight: bold;\n }\n\n .g-filter-description {\n grid-area: description;\n }\n}\n\n.g-clear-filters-wrap,\n.g-result-count {\n}\n\n/* Bulk selection styles */\n.g-th-checkbox {\n width: 50px;\n text-align: center;\n}\n\n.g-bulk-select-checkbox {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--g-primary-500);\n}\n\n.g-bulk-actions-toolbar {\n position: sticky;\n bottom: 0;\n left: 0;\n right: 0;\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n padding: 0.75rem 1rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);\n z-index: 1;\n\n ul {\n display: flex;\n gap: 1rem;\n list-style: none;\n padding: 0;\n margin: 0;\n }\n\n li {\n margin: 0;\n }\n}\n\n.g-selected-count {\n font-weight: 600;\n font-size: 1rem;\n}\n\n.g-bulk-actions {\n display: flex;\n gap: 0.5rem;\n align-items: center;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Pagination component for GTable.\n */\nexport default {};\n</script>\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n /**\n * The index of the first item\n */\n start: number;\n /**\n * The value of the page size control\n */\n pageSize: number;\n /**\n * The total number of items\n */\n total: number;\n /**\n * Available page sizes for the dropdown\n */\n pageSizes?: number[];\n}>();\n\nconst totalPages = computed(() => {\n return Math.max(1, Math.ceil(props.total / props.pageSize));\n});\n\nconst startModel = defineModel<number>(\"start\");\nconst pageSizeModel = defineModel<number>(\"pageSize\");\n\nconst startVal = computed(() => startModel.value ?? props.start);\nconst pageSizeVal = computed(() => pageSizeModel.value ?? props.pageSize);\n\nconst startDisplay = computed(() => {\n if (props.total === 0) {\n return 0;\n }\n return startVal.value + 1;\n});\n\nconst end = computed(() => {\n if (props.total === 0) {\n return 0;\n }\n return Math.min(startVal.value + pageSizeVal.value, props.total);\n});\n\nconst currentPage = computed(() => {\n return Math.floor(startVal.value / pageSizeVal.value) + 1;\n});\n\nfunction goToPage(p: number) {\n if (p < 1 || p > totalPages.value) {\n return;\n }\n startModel.value = (p - 1) * pageSizeVal.value;\n}\n\nfunction onPageSizeChange(e: Event) {\n pageSizeModel.value = parseInt((e.target as HTMLSelectElement).value, 10);\n}\n</script>\n\n<template>\n <nav class=\"g-pagination\" aria-label=\"Pagination\">\n <button\n class=\"first-page g-pagination-button\"\n :disabled=\"currentPage === 1\"\n @click=\"goToPage(1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"First Page\"\n height=\"2em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M11 7l-5 5l5 5\" />\n <path d=\"M17 7l-5 5l5 5\" />\n </svg>\n </button>\n <button\n class=\"prev-page g-pagination-button\"\n :disabled=\"currentPage === 1\"\n @click=\"goToPage(currentPage - 1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Previous Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M15 6l-6 6l6 6\" />\n </svg>\n </button>\n <span class=\"page-range\"> {{ startDisplay }} to {{ end }} </span>\n <button\n class=\"next-page g-pagination-button\"\n :disabled=\"currentPage === totalPages\"\n @click=\"goToPage(currentPage + 1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Next Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M9 6l6 6l-6 6\" />\n </svg>\n </button>\n <button\n class=\"last-page g-pagination-button\"\n :disabled=\"currentPage === totalPages\"\n @click=\"goToPage(totalPages)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Last Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M7 7l5 5l-5 5\" />\n <path d=\"M13 7l5 5l-5 5\" />\n </svg>\n </button>\n <select\n id=\"page-size-select\"\n class=\"page-size-select\"\n :value=\"pageSizeModel\"\n @change=\"onPageSizeChange\"\n >\n <option\n v-for=\"size in props.pageSizes || [10, 25, 50, 100]\"\n :key=\"size\"\n :value=\"size\"\n >\n {{ size }}\n </option>\n </select>\n <label class=\"page-size-label\" for=\"page-size-select\">per page</label>\n </nav>\n</template>\n\n<style>\n.g-pagination {\n display: flex;\n align-items: center;\n gap: 0.1rem;\n font-size: 1rem;\n\n .g-pagination-button {\n background: transparent;\n border: none;\n color: var(--g-surface-900);\n padding: 0.2rem 0.4rem;\n border-radius: 4px;\n cursor: pointer;\n\n &:not(:disabled) {\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n }\n }\n}\n.g-pagination button:disabled {\n cursor: auto;\n color: var(--g-surface-600);\n}\n.g-pagination .page-range {\n min-width: 3rem;\n text-align: center;\n}\n\n.g-pagination .page-size-select {\n margin-left: 1rem;\n margin-right: 0.5rem;\n padding: 0.2em 0.5em 0.2em 0.5em;\n border-radius: 0.2em;\n border: 2px solid var(--g-primary-500);\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n cursor: pointer;\n font-size: 1rem;\n font-family: var(--il-font-sans);\n}\n.g-pagination .page-size-select:hover {\n}\n.g-pagination .page-size-select:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n.page-size-label {\n line-height: 1.2;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-pagination .page-size-select {\n transition: none;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Pagination component for GTable.\n */\nexport default {};\n</script>\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n /**\n * The index of the first item\n */\n start: number;\n /**\n * The value of the page size control\n */\n pageSize: number;\n /**\n * The total number of items\n */\n total: number;\n /**\n * Available page sizes for the dropdown\n */\n pageSizes?: number[];\n}>();\n\nconst totalPages = computed(() => {\n return Math.max(1, Math.ceil(props.total / props.pageSize));\n});\n\nconst startModel = defineModel<number>(\"start\");\nconst pageSizeModel = defineModel<number>(\"pageSize\");\n\nconst startVal = computed(() => startModel.value ?? props.start);\nconst pageSizeVal = computed(() => pageSizeModel.value ?? props.pageSize);\n\nconst startDisplay = computed(() => {\n if (props.total === 0) {\n return 0;\n }\n return startVal.value + 1;\n});\n\nconst end = computed(() => {\n if (props.total === 0) {\n return 0;\n }\n return Math.min(startVal.value + pageSizeVal.value, props.total);\n});\n\nconst currentPage = computed(() => {\n return Math.floor(startVal.value / pageSizeVal.value) + 1;\n});\n\nfunction goToPage(p: number) {\n if (p < 1 || p > totalPages.value) {\n return;\n }\n startModel.value = (p - 1) * pageSizeVal.value;\n}\n\nfunction onPageSizeChange(e: Event) {\n pageSizeModel.value = parseInt((e.target as HTMLSelectElement).value, 10);\n}\n</script>\n\n<template>\n <nav class=\"g-pagination\" aria-label=\"Pagination\">\n <button\n class=\"first-page g-pagination-button\"\n :disabled=\"currentPage === 1\"\n @click=\"goToPage(1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"First Page\"\n height=\"2em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M11 7l-5 5l5 5\" />\n <path d=\"M17 7l-5 5l5 5\" />\n </svg>\n </button>\n <button\n class=\"prev-page g-pagination-button\"\n :disabled=\"currentPage === 1\"\n @click=\"goToPage(currentPage - 1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Previous Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M15 6l-6 6l6 6\" />\n </svg>\n </button>\n <span class=\"page-range\"> {{ startDisplay }} to {{ end }} </span>\n <button\n class=\"next-page g-pagination-button\"\n :disabled=\"currentPage === totalPages\"\n @click=\"goToPage(currentPage + 1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Next Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M9 6l6 6l-6 6\" />\n </svg>\n </button>\n <button\n class=\"last-page g-pagination-button\"\n :disabled=\"currentPage === totalPages\"\n @click=\"goToPage(totalPages)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Last Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M7 7l5 5l-5 5\" />\n <path d=\"M13 7l5 5l-5 5\" />\n </svg>\n </button>\n <select\n id=\"page-size-select\"\n class=\"page-size-select\"\n :value=\"pageSizeModel\"\n @change=\"onPageSizeChange\"\n >\n <option\n v-for=\"size in props.pageSizes || [10, 25, 50, 100]\"\n :key=\"size\"\n :value=\"size\"\n >\n {{ size }}\n </option>\n </select>\n <label class=\"page-size-label\" for=\"page-size-select\">per page</label>\n </nav>\n</template>\n\n<style>\n.g-pagination {\n display: flex;\n align-items: center;\n gap: 0.1rem;\n font-size: 1rem;\n\n .g-pagination-button {\n background: transparent;\n border: none;\n color: var(--g-surface-900);\n padding: 0.2rem 0.4rem;\n border-radius: 4px;\n cursor: pointer;\n\n &:not(:disabled) {\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n }\n }\n}\n.g-pagination button:disabled {\n cursor: auto;\n color: var(--g-surface-600);\n}\n.g-pagination .page-range {\n min-width: 3rem;\n text-align: center;\n}\n\n.g-pagination .page-size-select {\n margin-left: 1rem;\n margin-right: 0.5rem;\n padding: 0.2em 0.5em 0.2em 0.5em;\n border-radius: 0.2em;\n border: 2px solid var(--g-primary-500);\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n cursor: pointer;\n font-size: 1rem;\n font-family: var(--il-font-sans);\n}\n.g-pagination .page-size-select:hover {\n}\n.g-pagination .page-size-select:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n.page-size-label {\n line-height: 1.2;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-pagination .page-size-select {\n transition: none;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Generic modal component.\n *\n * Clicking on the outside or pressing the escape key will close the modal.\n *\n * > [!IMPORTANT]\n * >\n * > The surrounding page **must** have an element with the id `modal-root`,\n * > this modal will be teleported to it, so it can properly be over all\n * > other content. The `modal-root` should be somewhere near the end of the\n * > page structure.\n *\n * **Props**:\n *\n * - `label`: Modal accessible label.\n * - `describedby`: Element ID to pass to aria-describedby. Use this if there's\n * specific important text to describe the modal.\n * - `hiddenLabel`: Hide label visually. It will still be used as `aria-label`.\n * - `size`: Modal size\n *\n * **Slot** `default` is used as the content of the modal.\n *\n * When the modal is opened, focus is placed on the H2 label element. This\n * can be overridden by providing a `popover-focus` attribute on an element\n * inside the modal.\n *\n * Adding a dimming overlay behind modals can be done by placing `GOverlay`\n * at the end of the page structure.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n onBeforeMount,\n onMounted,\n ref,\n useId,\n useTemplateRef,\n} from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\n\ntype Props = {\n /**\n * Modal label\n * @demo Basic Modal\n */\n label: string;\n /**\n * ID for aria-describedby\n * @demo\n */\n describedby?: string;\n /**\n * Hide label\n *\n * The label is still used as the `aria-label` for accessibility, but it will not be visible in the UI.\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Modal size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n /**\n * Modal classes\n * @demo\n */\n classes?: string | string[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n describedby: undefined,\n hiddenLabel: false,\n size: \"medium\",\n});\n\nconst emit = defineEmits([\"close\"]);\n\nconst dialog = useTemplateRef(\"dialog\");\nconst open = ref(true);\n\nconst id = useId();\nconst { pop, push, isTop, zIndex } = useOverlayStack(id, true, true);\n\nconst { deactivate, activate } = useOverlayFocus(dialog, isTop);\n\nfunction close() {\n emit(\"close\");\n}\n\nuseOverlayEscape([dialog], isTop, open, close, pop);\n\nonMounted(() => {\n push();\n activate();\n});\n\nonBeforeMount(() => {\n pop();\n deactivate();\n});\n\nconst useClasses = computed(() => {\n let modalClasses = [`g-modal--${props.size}`];\n if (props.classes) {\n modalClasses = modalClasses.concat(Array.isArray(props.classes) ? props.classes : [props.classes]);\n }\n return modalClasses;\n});\n</script>\n\n<template>\n <Teleport to=\"#modal-root\">\n <Transition name=\"g-fade\" appear>\n <div\n :id=\"'modal-' + id\"\n class=\"g-modal\"\n :class=\"useClasses\"\n role=\"dialog\"\n aria-modal=\"true\"\n v-bind=\"{\n 'aria-labelledby': !hiddenLabel\n ? 'modal-label-' + id\n : undefined,\n 'aria-label': hiddenLabel ? label : undefined,\n 'aria-describedby': describedby ? describedby : undefined,\n }\"\n ref=\"dialog\"\n :style=\"{ zIndex }\"\n >\n <div class=\"g-modal-inner\">\n <div class=\"g-modal-header\">\n <h2\n v-if=\"!hiddenLabel\"\n :id=\"'modal-label-' + id\"\n class=\"g-modal-label\"\n tabindex=\"-1\"\n >\n {{ label }}\n </h2>\n <button\n class=\"g-modal-close\"\n @click=\"close\"\n aria-label=\"Close\"\n >\n <svg\n viewBox=\"0 0 24 24\"\n width=\"24\"\n height=\"24\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\"\n />\n </svg>\n </button>\n </div>\n <div\n :id=\"'modal-description-' + id\"\n class=\"g-modal-content\"\n >\n <slot />\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n.g-modal {\n position: fixed;\n left: 50vw;\n top: 50vh;\n transform: translate(-50%, -50%);\n height: auto;\n max-height: 90vh;\n overflow-y: auto;\n background: var(--g-surface-50);\n border-top: 8px solid var(--g-accent-500);\n padding: 2rem;\n box-sizing: border-box;\n box-shadow:\n 0 0 2px rgba(0, 0, 0, 0.4),\n 0 10px 20px rgba(0, 0, 0, 0.1);\n}\n.g-modal--small {\n width: 400px;\n max-width: 90vw;\n}\n.g-modal--medium {\n width: 600px;\n max-width: 90vw;\n}\n.g-modal--large {\n width: 900px;\n max-width: 90vw;\n}\n.g-modal--full {\n width: 100vw;\n height: 100vh;\n max-width: none;\n max-height: none;\n border-top: none;\n}\n.g-modal-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n}\n.g-modal-label {\n font-family: var(--il-font-heading);\n font-size: 2rem;\n margin-top: 0;\n color: var(--g-primary-500);\n}\n.g-modal-close {\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 0.5rem;\n margin: -1.25rem -1rem -1rem 1rem;\n color: var(--g-surface-600);\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Generic modal component.\n *\n * Clicking on the outside or pressing the escape key will close the modal.\n *\n * > [!IMPORTANT]\n * >\n * > The surrounding page **must** have an element with the id `modal-root`,\n * > this modal will be teleported to it, so it can properly be over all\n * > other content. The `modal-root` should be somewhere near the end of the\n * > page structure.\n *\n * **Props**:\n *\n * - `label`: Modal accessible label.\n * - `describedby`: Element ID to pass to aria-describedby. Use this if there's\n * specific important text to describe the modal.\n * - `hiddenLabel`: Hide label visually. It will still be used as `aria-label`.\n * - `size`: Modal size\n *\n * **Slot** `default` is used as the content of the modal.\n *\n * When the modal is opened, focus is placed on the H2 label element. This\n * can be overridden by providing a `popover-focus` attribute on an element\n * inside the modal.\n *\n * Adding a dimming overlay behind modals can be done by placing `GOverlay`\n * at the end of the page structure.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n onBeforeMount,\n onMounted,\n ref,\n useId,\n useTemplateRef,\n} from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\n\ntype Props = {\n /**\n * Modal label\n * @demo Basic Modal\n */\n label: string;\n /**\n * ID for aria-describedby\n * @demo\n */\n describedby?: string;\n /**\n * Hide label\n *\n * The label is still used as the `aria-label` for accessibility, but it will not be visible in the UI.\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Modal size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n /**\n * Modal classes\n * @demo\n */\n classes?: string | string[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n describedby: undefined,\n hiddenLabel: false,\n size: \"medium\",\n});\n\nconst emit = defineEmits([\"close\"]);\n\nconst dialog = useTemplateRef(\"dialog\");\nconst open = ref(true);\n\nconst id = useId();\nconst { pop, push, isTop, zIndex } = useOverlayStack(id, true, true);\n\nconst { deactivate, activate } = useOverlayFocus(dialog, isTop);\n\nfunction close() {\n emit(\"close\");\n}\n\nuseOverlayEscape([dialog], isTop, open, close, pop);\n\nonMounted(() => {\n push();\n activate();\n});\n\nonBeforeMount(() => {\n pop();\n deactivate();\n});\n\nconst useClasses = computed(() => {\n let modalClasses = [`g-modal--${props.size}`];\n if (props.classes) {\n modalClasses = modalClasses.concat(Array.isArray(props.classes) ? props.classes : [props.classes]);\n }\n return modalClasses;\n});\n</script>\n\n<template>\n <Teleport to=\"#modal-root\">\n <Transition name=\"g-fade\" appear>\n <div\n :id=\"'modal-' + id\"\n class=\"g-modal\"\n :class=\"useClasses\"\n role=\"dialog\"\n aria-modal=\"true\"\n v-bind=\"{\n 'aria-labelledby': !hiddenLabel\n ? 'modal-label-' + id\n : undefined,\n 'aria-label': hiddenLabel ? label : undefined,\n 'aria-describedby': describedby ? describedby : undefined,\n }\"\n ref=\"dialog\"\n :style=\"{ zIndex }\"\n >\n <div class=\"g-modal-inner\">\n <div class=\"g-modal-header\">\n <h2\n v-if=\"!hiddenLabel\"\n :id=\"'modal-label-' + id\"\n class=\"g-modal-label\"\n tabindex=\"-1\"\n >\n {{ label }}\n </h2>\n <button\n class=\"g-modal-close\"\n @click=\"close\"\n aria-label=\"Close\"\n >\n <svg\n viewBox=\"0 0 24 24\"\n width=\"24\"\n height=\"24\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\"\n />\n </svg>\n </button>\n </div>\n <div\n :id=\"'modal-description-' + id\"\n class=\"g-modal-content\"\n >\n <slot />\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n.g-modal {\n position: fixed;\n left: 50vw;\n top: 50vh;\n transform: translate(-50%, -50%);\n height: auto;\n max-height: 90vh;\n overflow-y: auto;\n background: var(--g-surface-50);\n border-top: 8px solid var(--g-accent-500);\n padding: 2rem;\n box-sizing: border-box;\n box-shadow:\n 0 0 2px rgba(0, 0, 0, 0.4),\n 0 10px 20px rgba(0, 0, 0, 0.1);\n}\n.g-modal--small {\n width: 400px;\n max-width: 90vw;\n}\n.g-modal--medium {\n width: 600px;\n max-width: 90vw;\n}\n.g-modal--large {\n width: 900px;\n max-width: 90vw;\n}\n.g-modal--full {\n width: 100vw;\n height: 100vh;\n max-width: none;\n max-height: none;\n border-top: none;\n}\n.g-modal-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n}\n.g-modal-label {\n font-family: var(--il-font-heading);\n font-size: 2rem;\n margin-top: 0;\n color: var(--g-primary-500);\n}\n.g-modal-close {\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 0.5rem;\n margin: -1.25rem -1rem -1rem 1rem;\n color: var(--g-surface-600);\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A hamburger menu button that toggles a sidebar, intended for the\n * GAppHeader and GSidebar components.\n *\n * <span id=\"use-sidebar\">Use with the `useSidebar`</span> composable function\n * that takes care of passing state between the different components.\n *\n * Here's an example, this could be your App.vue or a layout file:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * import { computed, h, onMounted, provide, ref, useTemplateRef } from \"vue\";\n * import { useSidebar } from \"../src/compose/useSidebar\";\n *\n * const sidebar = useSidebar();\n * provide(\"sidebar\", sidebar);\n *\n * // Or optionally a custom breakpoint\n * // const sidebar = useSidebar(\"(max-width: 600px)\");\n * &lt;/script>\n * ```\n *\n * As long as GHamburgerMenu and GSidebar are descendants of the component that\n * provides the sidebar, they will be able to communicate with each other.\n *\n * > [!NOTE]\n * > This button hides itself automatically according to the useSidebar media query.\n * > In web components mode, use the `sidebar-key` prop to pair this menu with a\n * > matching GSidebar instance.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useSidebar } from \"../compose/useSidebar.ts\";\nimport { useWebComponentSidebar } from \"../compose/useWebComponentSidebar.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\nimport { inject, useId } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Sidebar channel key for custom elements mode\n * @demo\n */\n sidebarKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"Main Navigation\",\n sidebarKey: \"default\",\n});\n\nconst injectedSidebar = inject<ReturnType<typeof useSidebar>>(\"sidebar\");\nconst sidebar =\n injectedSidebar ??\n (isCustomElementMode() ? useWebComponentSidebar(props.sidebarKey) : undefined);\n\nconst emit = defineEmits<{\n toggle: [];\n}>();\n\nfunction toggle() {\n emit(\"toggle\");\n sidebar?.toggle();\n}\n\n// Close menu on escape\nfunction handleEscapeKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n if (sidebar?.open?.value) {\n sidebar.open.value = false;\n }\n }\n}\n\nconst fallbackId = useId();\n</script>\n<template>\n <button\n :id=\"`${sidebar?.id ?? fallbackId}-hamburger`\"\n class=\"g-hamburger-button\"\n :class=\"{\n 'g-hamburger-button--open': sidebar?.open?.value,\n 'g-hamburger-button--collapsible': sidebar?.isCollapsible?.value\n }\"\n @click=\"toggle\"\n @keydown=\"handleEscapeKey\"\n :aria-expanded=\"sidebar?.open?.value ? 'true' : 'false'\"\n :aria-label=\"label\"\n :aria-controls=\"sidebar ? `${sidebar.id}-sidebar` : undefined\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 51.26 51.26\">\n <g fill=\"currentColor\">\n <path\n d=\"M11.6 16.52h28.06a3.24 3.24 0 1 0 0-6.48H11.6a3.24 3.24 0 0 0 0 6.48ZM39.66 22.07H11.6a3.24 3.24 0 0 0 0 6.48h28.06a3.24 3.24 0 1 0 0-6.48ZM39.66 34.1H11.6a3.24 3.24 0 0 0 0 6.48h28.06a3.24 3.24 0 1 0 0-6.48Z\"\n />\n </g>\n </svg>\n </button>\n</template>\n\n<style>\ng-hamburger-menu:not(:defined) {\n display: none;\n}\n\n.g-hamburger-button {\n svg {\n width: 1.6rem;\n }\n}\n.g-hamburger-button {\n width: 34px;\n height: 34px;\n padding: 0;\n display: none;\n justify-content: center;\n align-items: center;\n text-decoration: none;\n border: 2px solid var(--g-primary-500);\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n border-radius: 4px;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-text);\n color: var(--g-primary-500);\n }\n &:active {\n background: var(--g-accent-500);\n color: var(--g-primary-text);\n }\n &:focus-visible {\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--g-primary-500);\n }\n}\n.g-hamburger-button--collapsible {\n display: flex;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A hamburger menu button that toggles a sidebar, intended for the\n * GAppHeader and GSidebar components.\n *\n * <span id=\"use-sidebar\">Use with the `useSidebar`</span> composable function\n * that takes care of passing state between the different components.\n *\n * Here's an example, this could be your App.vue or a layout file:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * import { computed, h, onMounted, provide, ref, useTemplateRef } from \"vue\";\n * import { useSidebar } from \"../src/compose/useSidebar\";\n *\n * const sidebar = useSidebar();\n * provide(\"sidebar\", sidebar);\n *\n * // Or optionally a custom breakpoint\n * // const sidebar = useSidebar(\"(max-width: 600px)\");\n * &lt;/script>\n * ```\n *\n * As long as GHamburgerMenu and GSidebar are descendants of the component that\n * provides the sidebar, they will be able to communicate with each other.\n *\n * > [!NOTE]\n * > This button hides itself automatically according to the useSidebar media query.\n * > In web components mode, use the `sidebar-key` prop to pair this menu with a\n * > matching GSidebar instance.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useSidebar } from \"../compose/useSidebar.ts\";\nimport { useWebComponentSidebar } from \"../compose/useWebComponentSidebar.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\nimport { inject, useId } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Sidebar channel key for custom elements mode\n * @demo\n */\n sidebarKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"Main Navigation\",\n sidebarKey: \"default\",\n});\n\nconst injectedSidebar = inject<ReturnType<typeof useSidebar>>(\"sidebar\");\nconst sidebar =\n injectedSidebar ??\n (isCustomElementMode() ? useWebComponentSidebar(props.sidebarKey) : undefined);\n\nconst emit = defineEmits<{\n toggle: [];\n}>();\n\nfunction toggle() {\n emit(\"toggle\");\n sidebar?.toggle();\n}\n\n// Close menu on escape\nfunction handleEscapeKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n if (sidebar?.open?.value) {\n sidebar.open.value = false;\n }\n }\n}\n\nconst fallbackId = useId();\n</script>\n<template>\n <button\n :id=\"`${sidebar?.id ?? fallbackId}-hamburger`\"\n class=\"g-hamburger-button\"\n :class=\"{\n 'g-hamburger-button--open': sidebar?.open?.value,\n 'g-hamburger-button--collapsible': sidebar?.isCollapsible?.value\n }\"\n @click=\"toggle\"\n @keydown=\"handleEscapeKey\"\n :aria-expanded=\"sidebar?.open?.value ? 'true' : 'false'\"\n :aria-label=\"label\"\n :aria-controls=\"sidebar ? `${sidebar.id}-sidebar` : undefined\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 51.26 51.26\">\n <g fill=\"currentColor\">\n <path\n d=\"M11.6 16.52h28.06a3.24 3.24 0 1 0 0-6.48H11.6a3.24 3.24 0 0 0 0 6.48ZM39.66 22.07H11.6a3.24 3.24 0 0 0 0 6.48h28.06a3.24 3.24 0 1 0 0-6.48ZM39.66 34.1H11.6a3.24 3.24 0 0 0 0 6.48h28.06a3.24 3.24 0 1 0 0-6.48Z\"\n />\n </g>\n </svg>\n </button>\n</template>\n\n<style>\ng-hamburger-menu:not(:defined) {\n display: none;\n}\n\n.g-hamburger-button {\n svg {\n width: 1.6rem;\n }\n}\n.g-hamburger-button {\n width: 34px;\n height: 34px;\n padding: 0;\n display: none;\n justify-content: center;\n align-items: center;\n text-decoration: none;\n border: 2px solid var(--g-primary-500);\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n border-radius: 4px;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-text);\n color: var(--g-primary-500);\n }\n &:active {\n background: var(--g-accent-500);\n color: var(--g-primary-text);\n }\n &:focus-visible {\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--g-primary-500);\n }\n}\n.g-hamburger-button--collapsible {\n display: flex;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * This component is used with the `GDetailListItem` component to display\n * a list of key-value pairs in a grid or vertical layout.\n *\n * For example:\n *\n * ```vue-html\n * <GDetailList>\n * <GDetailListItem label=\"Name\">John Doe</GDetailListItem>\n * <GDetailListItem label=\"Age\">30</GDetailListItem>\n * <GDetailListItem label=\"City\">New York</GDetailListItem>\n * </GDetailList>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Layout style for the items.\n * @demo\n */\n variant?: \"grid\" | \"vertical\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: \"grid\",\n});\n</script>\n\n<template>\n <dl\n class=\"g-detail-list\"\n :class=\"`g-detail-list--${props.variant}`\"\n >\n <slot />\n </dl>\n</template>\n\n<style>\ng-detail-list,\n.g-detail-list {\n margin: 0;\n display: block;\n}\n.g-detail-list--grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--g-detail-list-item-min-width, 10rem), var(--g-detail-list-item-max-width, 1fr)));\n column-gap: 2.5rem;\n row-gap: 1.5rem;\n align-items: start;\n}\n\n.g-detail-list--grid .g-detail-list-item {\n display: flex;\n flex-direction: column;\n border-left: 2px solid var(--g-accent-500);\n padding-left: 0.75rem;\n}\n\n.g-detail-list--grid .g-detail-list-item__label {\n font-size: 0.875rem;\n}\n\n.g-detail-list--vertical {\n display: flex;\n flex-direction: column;\n}\n\n.g-detail-list--vertical .g-detail-list-item {\n display: grid;\n grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);\n column-gap: 1rem;\n row-gap: 0.25rem;\n padding: 0.75rem 0;\n border-bottom: 1px solid var(--g-surface-200);\n}\n\n.g-detail-list--vertical .g-detail-list-item:last-child {\n border-bottom: none;\n}\n\n.g-detail-list--vertical .g-detail-list-item__label {\n font-size: 1rem;\n align-self: start;\n}\n\n.g-detail-list--vertical .g-detail-list-item__value {\n justify-self: end;\n text-align: right;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This component is used with the `GDetailListItem` component to display\n * a list of key-value pairs in a grid or vertical layout.\n *\n * For example:\n *\n * ```vue-html\n * <GDetailList>\n * <GDetailListItem label=\"Name\">John Doe</GDetailListItem>\n * <GDetailListItem label=\"Age\">30</GDetailListItem>\n * <GDetailListItem label=\"City\">New York</GDetailListItem>\n * </GDetailList>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Layout style for the items.\n * @demo\n */\n variant?: \"grid\" | \"vertical\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: \"grid\",\n});\n</script>\n\n<template>\n <dl\n class=\"g-detail-list\"\n :class=\"`g-detail-list--${props.variant}`\"\n >\n <slot />\n </dl>\n</template>\n\n<style>\ng-detail-list,\n.g-detail-list {\n margin: 0;\n display: block;\n}\n.g-detail-list--grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--g-detail-list-item-min-width, 10rem), var(--g-detail-list-item-max-width, 1fr)));\n column-gap: 2.5rem;\n row-gap: 1.5rem;\n align-items: start;\n}\n\n.g-detail-list--grid .g-detail-list-item {\n display: flex;\n flex-direction: column;\n border-left: 2px solid var(--g-accent-500);\n padding-left: 0.75rem;\n}\n\n.g-detail-list--grid .g-detail-list-item__label {\n font-size: 0.875rem;\n}\n\n.g-detail-list--vertical {\n display: flex;\n flex-direction: column;\n}\n\n.g-detail-list--vertical .g-detail-list-item {\n display: grid;\n grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);\n column-gap: 1rem;\n row-gap: 0.25rem;\n padding: 0.75rem 0;\n border-bottom: 1px solid var(--g-surface-200);\n}\n\n.g-detail-list--vertical .g-detail-list-item:last-child {\n border-bottom: none;\n}\n\n.g-detail-list--vertical .g-detail-list-item__label {\n font-size: 1rem;\n align-self: start;\n}\n\n.g-detail-list--vertical .g-detail-list-item__value {\n justify-self: end;\n text-align: right;\n}\n</style>\n\n","<script setup lang=\"ts\">\ninterface Props {\n /**\n * Label shown for the item.\n */\n label: string;\n}\n\ndefineProps<Props>();\n</script>\n\n<template>\n <div class=\"g-detail-list-item\">\n <dt class=\"g-detail-list-item__label\">\n <slot name=\"label\">{{ label }}</slot>\n </dt>\n <dd class=\"g-detail-list-item__value\">\n <slot />\n </dd>\n </div>\n</template>\n\n<style>\n.g-detail-list-item {\n margin: 0;\n}\n\n.g-detail-list-item__label {\n margin: 0;\n font-weight: 700;\n color: var(--g-primary-500);\n}\n\n.g-detail-list-item__value {\n margin: 0;\n}\n</style>\n","<script setup lang=\"ts\">\ninterface Props {\n /**\n * Label shown for the item.\n */\n label: string;\n}\n\ndefineProps<Props>();\n</script>\n\n<template>\n <div class=\"g-detail-list-item\">\n <dt class=\"g-detail-list-item__label\">\n <slot name=\"label\">{{ label }}</slot>\n </dt>\n <dd class=\"g-detail-list-item__value\">\n <slot />\n </dd>\n </div>\n</template>\n\n<style>\n.g-detail-list-item {\n margin: 0;\n}\n\n.g-detail-list-item__label {\n margin: 0;\n font-weight: 700;\n color: var(--g-primary-500);\n}\n\n.g-detail-list-item__value {\n margin: 0;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Client-side overlay for behind modal dialogs.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useOverlayStackState } from \"../compose/useOverlayStack.ts\";\n\ntype Props = {\n\n}\n\nconst { hasScrollLock } = useOverlayStackState();\n</script>\n\n<template>\n <Transition name=\"g-fade\">\n <div v-if=\"hasScrollLock\" class=\"g-scroll-lock-overlay\"></div>\n </Transition>\n</template>\n\n<style>\n@layer override {\n body.g-scroll-lock {\n overflow: hidden;\n }\n\n .g-scroll-lock-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.25);\n z-index: 199;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Client-side overlay for behind modal dialogs.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useOverlayStackState } from \"../compose/useOverlayStack.ts\";\n\ntype Props = {\n\n}\n\nconst { hasScrollLock } = useOverlayStackState();\n</script>\n\n<template>\n <Transition name=\"g-fade\">\n <div v-if=\"hasScrollLock\" class=\"g-scroll-lock-overlay\"></div>\n </Transition>\n</template>\n\n<style>\n@layer override {\n body.g-scroll-lock {\n overflow: hidden;\n }\n\n .g-scroll-lock-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.25);\n z-index: 199;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Prebuilt GSelect and GSelectButton components that can be used to\n * select a term.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GSelect from \"../GSelect.vue\";\nimport GSelectButton from \"../GSelectButton.vue\";\n\ntype Props = {\n /**\n * List of possible term years. Defaults to [\"2026\"].\n */\n termYears?: string[];\n /**\n * List of possible term names. Defaults to [\"Spring\", \"Summer\", \"Fall\"].\n */\n termNames?: string[];\n\n /**\n * Label for year select. Defaults to \"Select Year\".\n */\n yearLabel?: string;\n\n /**\n * Label for period select. Defaults to \"Term\".\n */\n periodLabel?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n termYears: () => [\"2026\"],\n termNames: () => [\"Spring\", \"Summer\", \"Fall\"],\n yearLabel: \"Select Year\",\n periodLabel: \"Term\",\n});\n\nconst term = defineModel<{year: string, name: string}>({\n default: () => ({year: \"2026\", name: \"Spring\"}),\n});\n</script>\n\n<template>\n <div class=\"popover-content\">\n <div class=\"year-dropdown\">\n <GSelect\n v-model=\"term.year\"\n :options=\"termYears\"\n :label=\"yearLabel\"\n />\n </div>\n <div class=\"month-selector\">\n <GSelectButton\n v-model=\"term.name\"\n :options=\"termNames\"\n :allow-empty=\"false\"\n :label=\"periodLabel\"\n />\n </div>\n </div>\n</template>\n\n<style>\n.year-dropdown {\n display: flex;\n justify-content: left;\n margin: 1rem 0;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Prebuilt GSelect and GSelectButton components that can be used to\n * select a term.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GSelect from \"../GSelect.vue\";\nimport GSelectButton from \"../GSelectButton.vue\";\n\ntype Props = {\n /**\n * List of possible term years. Defaults to [\"2026\"].\n */\n termYears?: string[];\n /**\n * List of possible term names. Defaults to [\"Spring\", \"Summer\", \"Fall\"].\n */\n termNames?: string[];\n\n /**\n * Label for year select. Defaults to \"Select Year\".\n */\n yearLabel?: string;\n\n /**\n * Label for period select. Defaults to \"Term\".\n */\n periodLabel?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n termYears: () => [\"2026\"],\n termNames: () => [\"Spring\", \"Summer\", \"Fall\"],\n yearLabel: \"Select Year\",\n periodLabel: \"Term\",\n});\n\nconst term = defineModel<{year: string, name: string}>({\n default: () => ({year: \"2026\", name: \"Spring\"}),\n});\n</script>\n\n<template>\n <div class=\"popover-content\">\n <div class=\"year-dropdown\">\n <GSelect\n v-model=\"term.year\"\n :options=\"termYears\"\n :label=\"yearLabel\"\n />\n </div>\n <div class=\"month-selector\">\n <GSelectButton\n v-model=\"term.name\"\n :options=\"termNames\"\n :allow-empty=\"false\"\n :label=\"periodLabel\"\n />\n </div>\n </div>\n</template>\n\n<style>\n.year-dropdown {\n display: flex;\n justify-content: left;\n margin: 1rem 0;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A button that shows the selected term. Clicking it opens a popover\n * that allows jumping to a different term.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTermSelectorControl from \"./term/GTermSelectorControl.vue\";\nimport GButton from \"./GButton.vue\";\nimport GPopover from \"./GPopover.vue\";\n\ntype Props = {\n /**\n * Heading for the popover.\n * @demo Period Selection\n */\n heading?: string;\n\n /**\n * Label for year select.\n * @demo Select Year\n */\n yearLabel?: string;\n\n /**\n * Label for period select.\n * @demo Term\n */\n periodLabel?: string;\n\n /**\n * List of possible term years\n */\n termYears?: string[];\n\n /**\n * List of possible term names\n */\n termNames?: string[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n heading: \"Period Selection\",\n termYears: () => [\"2026\"],\n termNames: () => [\"Spring\", \"Summer\", \"Fall\"],\n});\n\nconst term = defineModel<{year: string, name: string}>({\n default: () => ({year: \"2026\", name: \"Spring\"}),\n});\n</script>\n\n<template>\n <div class=\"g-term-selector\">\n <GPopover>\n <template #trigger=\"{ toggle }\">\n <GButton class=\"g-term-selector-button\" theme=\"none\" outlined @click=\"toggle\">\n <span class=\"g-calendar-icon\">\n <svg role=\"none presentation\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d=\"M224 64C206.3 64 192 78.3 192 96L192 128L160 128C124.7 128 96 156.7 96 192L96 240L544 240L544 192C544 156.7 515.3 128 480 128L448 128L448 96C448 78.3 433.7 64 416 64C398.3 64 384 78.3 384 96L384 128L256 128L256 96C256 78.3 241.7 64 224 64zM96 288L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 288L96 288z\"/></svg>\n </span>\n <span class=\"g-term-label\"> {{ term?.name }} {{ term?.year }} </span>\n <span class=\"g-caret\">\n <svg role=\"none presentation\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d=\"M300.3 440.8C312.9 451 331.4 450.3 343.1 438.6L471.1 310.6C480.3 301.4 483 287.7 478 275.7C473 263.7 461.4 256 448.5 256L192.5 256C179.6 256 167.9 263.8 162.9 275.8C157.9 287.8 160.7 301.5 169.9 310.6L297.9 438.6L300.3 440.8z\"/></svg>\n </span>\n </GButton>\n </template>\n <h2 class=\"g-popover-title\" tabindex=\"-1\">{{ heading}}</h2>\n <GTermSelectorControl v-bind=\"$props\" />\n </GPopover>\n </div>\n</template>\n\n<style>\n\n.g-popover-title {\n font-size: 1rem;\n display: block;\n margin: -1.5rem -1rem 0;\n padding: 0.5rem 1rem;\n background: var(--g-surface-50);\n font-weight: 600;\n color: var(--g-accent-700);\n text-align: center;\n}\n.g-term-selector .g-term-selector-button {\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n border-color: var(--g-primary-500);\n height: 2.35rem;\n padding: 0 8px 0 0;\n text-decoration: none;\n font-size: 1rem;\n\n .g-calendar-icon {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n display: flex;\n align-items: center;\n\n padding: 0 10px;\n height: 100%;\n\n svg {\n width: 1.5rem;\n fill: currentColor;\n stroke: currentColor;\n }\n }\n\n &:hover .g-term-label {\n text-decoration: underline;\n }\n\n &:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n .g-caret {\n pointer-events: none;\n color: var(--ilw-color--link-hover);\n width: 20px;\n }\n}\n\n\n.g-term-label {\n width: 120px;\n padding-top: 2px;\n @media screen and (max-width: 1000px) {\n width: 70px;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A button that shows the selected term. Clicking it opens a popover\n * that allows jumping to a different term.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTermSelectorControl from \"./term/GTermSelectorControl.vue\";\nimport GButton from \"./GButton.vue\";\nimport GPopover from \"./GPopover.vue\";\n\ntype Props = {\n /**\n * Heading for the popover.\n * @demo Period Selection\n */\n heading?: string;\n\n /**\n * Label for year select.\n * @demo Select Year\n */\n yearLabel?: string;\n\n /**\n * Label for period select.\n * @demo Term\n */\n periodLabel?: string;\n\n /**\n * List of possible term years\n */\n termYears?: string[];\n\n /**\n * List of possible term names\n */\n termNames?: string[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n heading: \"Period Selection\",\n termYears: () => [\"2026\"],\n termNames: () => [\"Spring\", \"Summer\", \"Fall\"],\n});\n\nconst term = defineModel<{year: string, name: string}>({\n default: () => ({year: \"2026\", name: \"Spring\"}),\n});\n</script>\n\n<template>\n <div class=\"g-term-selector\">\n <GPopover>\n <template #trigger=\"{ toggle }\">\n <GButton class=\"g-term-selector-button\" theme=\"none\" outlined @click=\"toggle\">\n <span class=\"g-calendar-icon\">\n <svg role=\"none presentation\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d=\"M224 64C206.3 64 192 78.3 192 96L192 128L160 128C124.7 128 96 156.7 96 192L96 240L544 240L544 192C544 156.7 515.3 128 480 128L448 128L448 96C448 78.3 433.7 64 416 64C398.3 64 384 78.3 384 96L384 128L256 128L256 96C256 78.3 241.7 64 224 64zM96 288L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 288L96 288z\"/></svg>\n </span>\n <span class=\"g-term-label\"> {{ term?.name }} {{ term?.year }} </span>\n <span class=\"g-caret\">\n <svg role=\"none presentation\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d=\"M300.3 440.8C312.9 451 331.4 450.3 343.1 438.6L471.1 310.6C480.3 301.4 483 287.7 478 275.7C473 263.7 461.4 256 448.5 256L192.5 256C179.6 256 167.9 263.8 162.9 275.8C157.9 287.8 160.7 301.5 169.9 310.6L297.9 438.6L300.3 440.8z\"/></svg>\n </span>\n </GButton>\n </template>\n <h2 class=\"g-popover-title\" tabindex=\"-1\">{{ heading}}</h2>\n <GTermSelectorControl v-bind=\"$props\" />\n </GPopover>\n </div>\n</template>\n\n<style>\n\n.g-popover-title {\n font-size: 1rem;\n display: block;\n margin: -1.5rem -1rem 0;\n padding: 0.5rem 1rem;\n background: var(--g-surface-50);\n font-weight: 600;\n color: var(--g-accent-700);\n text-align: center;\n}\n.g-term-selector .g-term-selector-button {\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n border-color: var(--g-primary-500);\n height: 2.35rem;\n padding: 0 8px 0 0;\n text-decoration: none;\n font-size: 1rem;\n\n .g-calendar-icon {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n display: flex;\n align-items: center;\n\n padding: 0 10px;\n height: 100%;\n\n svg {\n width: 1.5rem;\n fill: currentColor;\n stroke: currentColor;\n }\n }\n\n &:hover .g-term-label {\n text-decoration: underline;\n }\n\n &:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n .g-caret {\n pointer-events: none;\n color: var(--ilw-color--link-hover);\n width: 20px;\n }\n}\n\n\n.g-term-label {\n width: 120px;\n padding-top: 2px;\n @media screen and (max-width: 1000px) {\n width: 70px;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * User menu component for toolbars. Displays a button with the user's initials\n * inside a colored circle. When clicked, it opens a popover with the user's\n * email and a menu for account-related links.\n *\n * **Slots**:\n * - `default` contains menu items (links or buttons) that will be wrapped in\n * an unordered list for accessibility.\n *\n * **Props**:\n * - `initials` - User's initials to display in the avatar\n * - `email` - User's email to display in the popover\n * - `color` - Background color for the avatar (should be deterministic)\n * - `label` - Accessible label for the menu button. The initial will be prepended to this for the full label.\n *\n * Example:\n *\n * ```vue-html\n * <GUserMenu\n * initials=\"J\"\n * email=\"j@example.com\"\n * color=\"#4A90E2\"\n * >\n * <router-link to=\"/profile\">Profile</router-link>\n * <a href=\"/settings\">Settings</a>\n * <button @click=\"handleLogout\">Logout</button>\n * </GUserMenu>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { getCurrentInstance, ref, useId, useSlots, useTemplateRef } from \"vue\";\nimport GPopover from \"./GPopover.vue\";\n\ntype Props = {\n /**\n * User initial(s)\n * @demo J\n */\n initials: string;\n /**\n * User email\n * @demo j@example.org\n */\n email: string;\n /**\n * Background color\n * @demo\n */\n color?: string;\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"User menu\",\n color: \"var(--g-surface-700)\",\n});\n\nconst id = useId();\nconst emailHeadingId = `${id}-email`;\nconst open = ref(false);\n\n// Get the heading ref to focus it when the popover opens\nconst emailHeading = useTemplateRef<HTMLElement>(\"emailHeading\");\n\n// Detect vue-router without adding it as a dependency\nconst instance = getCurrentInstance();\nconst RouterLinkComp = instance?.appContext?.components?.RouterLink as\n | any\n | undefined;\n\nconst slots = defineSlots<{\n default(): any\n}>();\n\n</script>\n\n<template>\n <div class=\"g-user-menu\">\n <GPopover v-model=\"open\" minimal>\n <template #trigger=\"{ toggle }\">\n <button\n class=\"g-user-menu__avatar\"\n :style=\"{ backgroundColor: color }\"\n :aria-label=\"initials + ' - ' + label\"\n :aria-expanded=\"open\"\n aria-haspopup=\"menu\"\n @click=\"toggle\"\n >\n {{ initials }}\n </button>\n </template>\n <div class=\"g-user-menu__popover\">\n <h2\n :id=\"emailHeadingId\"\n ref=\"emailHeading\"\n class=\"g-user-menu__email\"\n tabindex=\"-1\"\n >\n {{ email }}\n </h2>\n <nav class=\"g-user-menu__nav\" :aria-labelledby=\"emailHeadingId\">\n <ul class=\"g-user-menu__list\">\n <li v-for=\"(link, index) in slots.default()\" :key=\"index\">\n <component :is=\"link\"></component>\n </li>\n </ul>\n </nav>\n </div>\n </GPopover>\n </div>\n</template>\n\n<style>\ng-user-menu {\n display: inline-block;\n}\n.g-user-menu {\n display: inline-block;\n}\n\n.g-user-menu__avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n color: var(--g-surface-0);\n font-weight: 700;\n font-size: 1rem;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition:\n transform 0.2s,\n box-shadow 0.2s;\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__avatar:hover {\n text-decoration: underline;\n}\n\n.g-user-menu__avatar:focus-visible {\n background-color: var(--ilw-color--focus--background) !important;\n color: var(--ilw-color--focus--text) !important;\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__popover {\n min-width: 200px;\n}\n\n.g-user-menu__email {\n margin: 0.75rem 1rem 0.25rem;\n font-size: 1rem;\n font-weight: normal;\n color: var(--g-primary-500);\n word-break: break-word;\n}\n\n.g-user-menu__nav {\n margin: 0;\n}\n\n.g-user-menu__list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 0;\n}\n\n.g-user-menu__list a,\n.g-user-menu__list button {\n display: block;\n padding: 0.75rem 1rem;\n box-sizing: border-box;\n color: var(--g-primary-500);\n text-decoration: none;\n border: none;\n background: none;\n font-size: 1rem;\n font-weight: 600;\n font-family: var(--il-font-sans);\n text-align: left;\n cursor: pointer;\n width: 100%;\n}\n\n.g-user-menu__list a:hover,\n.g-user-menu__list button:hover {\n color: var(--g-accent-700);\n text-decoration: underline;\n}\n\n.g-user-menu__list a:focus,\n.g-user-menu__list button:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__list a:active,\n.g-user-menu__list button:active {\n background-color: var(--g-accent-700);\n color: var(--g-surface-0);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * User menu component for toolbars. Displays a button with the user's initials\n * inside a colored circle. When clicked, it opens a popover with the user's\n * email and a menu for account-related links.\n *\n * **Slots**:\n * - `default` contains menu items (links or buttons) that will be wrapped in\n * an unordered list for accessibility.\n *\n * **Props**:\n * - `initials` - User's initials to display in the avatar\n * - `email` - User's email to display in the popover\n * - `color` - Background color for the avatar (should be deterministic)\n * - `label` - Accessible label for the menu button. The initial will be prepended to this for the full label.\n *\n * Example:\n *\n * ```vue-html\n * <GUserMenu\n * initials=\"J\"\n * email=\"j@example.com\"\n * color=\"#4A90E2\"\n * >\n * <router-link to=\"/profile\">Profile</router-link>\n * <a href=\"/settings\">Settings</a>\n * <button @click=\"handleLogout\">Logout</button>\n * </GUserMenu>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { getCurrentInstance, ref, useId, useSlots, useTemplateRef } from \"vue\";\nimport GPopover from \"./GPopover.vue\";\n\ntype Props = {\n /**\n * User initial(s)\n * @demo J\n */\n initials: string;\n /**\n * User email\n * @demo j@example.org\n */\n email: string;\n /**\n * Background color\n * @demo\n */\n color?: string;\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"User menu\",\n color: \"var(--g-surface-700)\",\n});\n\nconst id = useId();\nconst emailHeadingId = `${id}-email`;\nconst open = ref(false);\n\n// Get the heading ref to focus it when the popover opens\nconst emailHeading = useTemplateRef<HTMLElement>(\"emailHeading\");\n\n// Detect vue-router without adding it as a dependency\nconst instance = getCurrentInstance();\nconst RouterLinkComp = instance?.appContext?.components?.RouterLink as\n | any\n | undefined;\n\nconst slots = defineSlots<{\n default(): any\n}>();\n\n</script>\n\n<template>\n <div class=\"g-user-menu\">\n <GPopover v-model=\"open\" minimal>\n <template #trigger=\"{ toggle }\">\n <button\n class=\"g-user-menu__avatar\"\n :style=\"{ backgroundColor: color }\"\n :aria-label=\"initials + ' - ' + label\"\n :aria-expanded=\"open\"\n aria-haspopup=\"menu\"\n @click=\"toggle\"\n >\n {{ initials }}\n </button>\n </template>\n <div class=\"g-user-menu__popover\">\n <h2\n :id=\"emailHeadingId\"\n ref=\"emailHeading\"\n class=\"g-user-menu__email\"\n tabindex=\"-1\"\n >\n {{ email }}\n </h2>\n <nav class=\"g-user-menu__nav\" :aria-labelledby=\"emailHeadingId\">\n <ul class=\"g-user-menu__list\">\n <li v-for=\"(link, index) in slots.default()\" :key=\"index\">\n <component :is=\"link\"></component>\n </li>\n </ul>\n </nav>\n </div>\n </GPopover>\n </div>\n</template>\n\n<style>\ng-user-menu {\n display: inline-block;\n}\n.g-user-menu {\n display: inline-block;\n}\n\n.g-user-menu__avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n color: var(--g-surface-0);\n font-weight: 700;\n font-size: 1rem;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition:\n transform 0.2s,\n box-shadow 0.2s;\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__avatar:hover {\n text-decoration: underline;\n}\n\n.g-user-menu__avatar:focus-visible {\n background-color: var(--ilw-color--focus--background) !important;\n color: var(--ilw-color--focus--text) !important;\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__popover {\n min-width: 200px;\n}\n\n.g-user-menu__email {\n margin: 0.75rem 1rem 0.25rem;\n font-size: 1rem;\n font-weight: normal;\n color: var(--g-primary-500);\n word-break: break-word;\n}\n\n.g-user-menu__nav {\n margin: 0;\n}\n\n.g-user-menu__list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 0;\n}\n\n.g-user-menu__list a,\n.g-user-menu__list button {\n display: block;\n padding: 0.75rem 1rem;\n box-sizing: border-box;\n color: var(--g-primary-500);\n text-decoration: none;\n border: none;\n background: none;\n font-size: 1rem;\n font-weight: 600;\n font-family: var(--il-font-sans);\n text-align: left;\n cursor: pointer;\n width: 100%;\n}\n\n.g-user-menu__list a:hover,\n.g-user-menu__list button:hover {\n color: var(--g-accent-700);\n text-decoration: underline;\n}\n\n.g-user-menu__list a:focus,\n.g-user-menu__list button:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__list a:active,\n.g-user-menu__list button:active {\n background-color: var(--g-accent-700);\n color: var(--g-surface-0);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A currency input component for US dollars.\n *\n * This component is a wrapper around a text input with a prefix and\n * appropriate input type for currency values.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"props.name\"\n :label=\"props.label\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n :errors=\"props.errors\"\n :instructions=\"props.instructions\"\n :form-key=\"props.formKey\"\n prefix=\"$\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-currency-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * A currency input component for US dollars.\n *\n * This component is a wrapper around a text input with a prefix and\n * appropriate input type for currency values.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"props.name\"\n :label=\"props.label\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n :errors=\"props.errors\"\n :instructions=\"props.instructions\"\n :form-key=\"props.formKey\"\n prefix=\"$\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-currency-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * An email input component.\n * \n * This component is a wrapper around GTextInput with type=\"email\" for\n * proper email validation and mobile keyboard optimization.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"name\"\n :label=\"label\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :required=\"required\"\n :errors=\"errors\"\n :instructions=\"instructions\"\n type=\"email\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-email-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * An email input component.\n * \n * This component is a wrapper around GTextInput with type=\"email\" for\n * proper email validation and mobile keyboard optimization.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"name\"\n :label=\"label\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :required=\"required\"\n :errors=\"errors\"\n :instructions=\"instructions\"\n type=\"email\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-email-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * A file input component for accessible file uploads.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings. Multiple errors will all be\n * displayed. Client-side validation errors from `maxFileSize` and `maxFiles`\n * are shown alongside any provided `errors`.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, ref, useId, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Upload File\n */\n label?: string;\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n /**\n * Accepted file types (e.g. \".pdf,.docx\" or \"image/*\")\n * @demo\n */\n accept?: string;\n /**\n * Allow multiple file selection\n * @demo\n */\n multiple?: boolean;\n /**\n * Maximum file size in bytes for client-side validation\n * @demo\n */\n maxFileSize?: number;\n /**\n * Maximum number of files allowed for client-side validation\n * @demo\n */\n maxFiles?: number;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n instructions: \"\",\n disabled: false,\n errors: () => [],\n required: false,\n multiple: false,\n});\n\nconst model = defineModel<File[]>({ default: () => [] });\n\nconst id = useId();\n\nconst { displayErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [files: File[]];\n}>();\n\nconst validationErrors = ref<string[]>([]);\n\nfunction validateFiles(files: File[]): string[] {\n const errs: string[] = [];\n\n if (props.maxFiles !== undefined && files.length > props.maxFiles) {\n errs.push(\n `You may select at most ${props.maxFiles} file${props.maxFiles === 1 ? \"\" : \"s\"}.`,\n );\n }\n\n if (props.maxFileSize !== undefined) {\n const oversized = files.filter((f) => f.size > props.maxFileSize!);\n if (oversized.length > 0) {\n const maxMB = (props.maxFileSize / (1024 * 1024)).toFixed(1);\n errs.push(\n `${oversized.length === 1 ? \"One file exceeds\" : `${oversized.length} files exceed`} the maximum size of ${maxMB} MB.`,\n );\n }\n }\n\n return errs;\n}\n\nfunction onFileChange(e: Event) {\n const input = e.target as HTMLInputElement;\n const files = Array.from(input.files ?? []);\n validationErrors.value = validateFiles(files);\n model.value = files;\n emit(\"change\", files);\n}\n\nconst allErrors = computed(() => [\n ...displayErrors.value,\n ...validationErrors.value,\n]);\n\nconst hasErrors = computed(() => allErrors.value.length > 0);\n\nconst selectedFileNames = computed(() => model.value.map((f) => f.name));\n</script>\n\n<template>\n <div\n class=\"g-file-input-wrap\"\n :class=\"{ 'g-file-input-has-error': hasErrors }\"\n >\n <label v-if=\"label\" :for=\"id\" class=\"g-file-input-label\">\n {{ label }}\n <span v-if=\"required\" class=\"g-file-input-required\">*</span>\n </label>\n\n <div\n class=\"g-file-input-box\"\n :class=\"{ 'g-file-input-box--disabled': disabled }\"\n >\n <div v-if=\"instructions\" class=\"g-file-input-box-header\">\n <span\n :id=\"'instructions-' + id\"\n class=\"g-file-input-instructions\"\n >{{ instructions }}</span\n >\n </div>\n\n <input\n :id=\"id\"\n type=\"file\"\n class=\"g-file-input\"\n :disabled=\"disabled\"\n :required=\"required\"\n :accept=\"accept || undefined\"\n :multiple=\"multiple\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n :aria-describedby=\"\n instructions ? 'instructions-' + id : undefined\n \"\n :aria-errormessage=\"\n hasErrors ? 'error-message-' + id : undefined\n \"\n @change=\"onFileChange\"\n />\n\n <ul\n v-if=\"multiple && selectedFileNames.length > 0\"\n class=\"g-file-input-pills\"\n aria-label=\"Selected files\"\n >\n <li\n v-for=\"name in selectedFileNames\"\n :key=\"name\"\n class=\"g-file-input-pill\"\n >\n <svg\n class=\"g-file-input-pill-icon\"\n role=\"none presentation\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n >\n <!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n d=\"M192 64C156.7 64 128 92.7 128 128L128 512C128 547.3 156.7 576 192 576L448 576C483.3 576 512 547.3 512 512L512 234.5C512 217.5 505.3 201.2 493.3 189.2L386.7 82.7C374.7 70.7 358.5 64 341.5 64L192 64zM453.5 240L360 240C346.7 240 336 229.3 336 216L336 122.5L453.5 240z\"\n />\n </svg>\n {{ name }}\n </li>\n </ul>\n </div>\n\n <GFormErrorMessages :errors=\"allErrors\" :id=\"'error-message-' + id\" />\n </div>\n</template>\n\n<style>\ng-file-input {\n display: block;\n}\n.g-file-input-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n.g-file-input-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n\n.g-file-input-required {\n color: var(--g-danger-600);\n margin-left: 0.2em;\n}\n\n.g-file-input-box {\n display: flex;\n max-width: 360px;\n flex-direction: column;\n gap: 0.75em;\n border: 1px solid var(--g-primary-500);\n background: var(--g-surface-50);\n padding: 0.75em;\n}\n\n.g-file-input-box--disabled {\n border-color: var(--g-surface-400);\n background: var(--g-surface-100);\n}\n\n.g-file-input-has-error .g-file-input-box {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-file-input-box-header {\n display: flex;\n align-items: center;\n gap: 0.5em;\n color: var(--g-surface-700);\n}\n\n.g-file-input-upload-icon {\n flex-shrink: 0;\n height: 2em;\n width: 2em;\n color: var(--g-primary-500);\n}\n\n.g-file-input-box--disabled .g-file-input-upload-icon {\n color: var(--g-surface-600);\n}\n\n.g-file-input-instructions {\n font-size: 0.9em;\n color: var(--g-surface-900);\n}\n\n.g-file-input {\n font-size: 1em;\n font-family: var(--il-font-sans);\n color: var(--g-surface-950);\n background: transparent;\n border: none;\n padding: 0;\n cursor: pointer;\n width: 100%;\n}\n\n.g-file-input::file-selector-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--il-font-sans);\n font-weight: 700;\n font-size: 19px;\n line-height: 20px;\n border: 2px solid var(--g-primary-500);\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n cursor: pointer;\n padding: 12px 20px;\n border-radius: var(--g-border-radius-m);\n text-decoration: none;\n}\n\n.g-file-input::file-selector-button:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n}\n\n.g-file-input:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n border-radius: var(--g-border-radius-s);\n}\n\n.g-file-input:disabled {\n cursor: not-allowed;\n color: var(--g-surface-800);\n}\n\n.g-file-input:disabled::file-selector-button {\n background: var(--g-surface-200);\n color: var(--g-surface-900);\n border-color: var(--g-surface-200);\n cursor: not-allowed;\n}\n\n.g-file-input-pills {\n display: flex;\n flex-wrap: wrap;\n gap: 0.4em;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.g-file-input-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.3em;\n border: 1px solid var(--g-primary-500);\n color: var(--g-surface-950);\n border-radius: 999px;\n padding: 0.2em 0.7em;\n font-size: 0.85em;\n max-width: 24em;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.g-file-input-pill-icon {\n flex-shrink: 0;\n height: 1em;\n width: 1em;\n opacity: 0.7;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A file input component for accessible file uploads.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings. Multiple errors will all be\n * displayed. Client-side validation errors from `maxFileSize` and `maxFiles`\n * are shown alongside any provided `errors`.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, ref, useId, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Upload File\n */\n label?: string;\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n /**\n * Accepted file types (e.g. \".pdf,.docx\" or \"image/*\")\n * @demo\n */\n accept?: string;\n /**\n * Allow multiple file selection\n * @demo\n */\n multiple?: boolean;\n /**\n * Maximum file size in bytes for client-side validation\n * @demo\n */\n maxFileSize?: number;\n /**\n * Maximum number of files allowed for client-side validation\n * @demo\n */\n maxFiles?: number;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n instructions: \"\",\n disabled: false,\n errors: () => [],\n required: false,\n multiple: false,\n});\n\nconst model = defineModel<File[]>({ default: () => [] });\n\nconst id = useId();\n\nconst { displayErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [files: File[]];\n}>();\n\nconst validationErrors = ref<string[]>([]);\n\nfunction validateFiles(files: File[]): string[] {\n const errs: string[] = [];\n\n if (props.maxFiles !== undefined && files.length > props.maxFiles) {\n errs.push(\n `You may select at most ${props.maxFiles} file${props.maxFiles === 1 ? \"\" : \"s\"}.`,\n );\n }\n\n if (props.maxFileSize !== undefined) {\n const oversized = files.filter((f) => f.size > props.maxFileSize!);\n if (oversized.length > 0) {\n const maxMB = (props.maxFileSize / (1024 * 1024)).toFixed(1);\n errs.push(\n `${oversized.length === 1 ? \"One file exceeds\" : `${oversized.length} files exceed`} the maximum size of ${maxMB} MB.`,\n );\n }\n }\n\n return errs;\n}\n\nfunction onFileChange(e: Event) {\n const input = e.target as HTMLInputElement;\n const files = Array.from(input.files ?? []);\n validationErrors.value = validateFiles(files);\n model.value = files;\n emit(\"change\", files);\n}\n\nconst allErrors = computed(() => [\n ...displayErrors.value,\n ...validationErrors.value,\n]);\n\nconst hasErrors = computed(() => allErrors.value.length > 0);\n\nconst selectedFileNames = computed(() => model.value.map((f) => f.name));\n</script>\n\n<template>\n <div\n class=\"g-file-input-wrap\"\n :class=\"{ 'g-file-input-has-error': hasErrors }\"\n >\n <label v-if=\"label\" :for=\"id\" class=\"g-file-input-label\">\n {{ label }}\n <span v-if=\"required\" class=\"g-file-input-required\">*</span>\n </label>\n\n <div\n class=\"g-file-input-box\"\n :class=\"{ 'g-file-input-box--disabled': disabled }\"\n >\n <div v-if=\"instructions\" class=\"g-file-input-box-header\">\n <span\n :id=\"'instructions-' + id\"\n class=\"g-file-input-instructions\"\n >{{ instructions }}</span\n >\n </div>\n\n <input\n :id=\"id\"\n type=\"file\"\n class=\"g-file-input\"\n :disabled=\"disabled\"\n :required=\"required\"\n :accept=\"accept || undefined\"\n :multiple=\"multiple\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n :aria-describedby=\"\n instructions ? 'instructions-' + id : undefined\n \"\n :aria-errormessage=\"\n hasErrors ? 'error-message-' + id : undefined\n \"\n @change=\"onFileChange\"\n />\n\n <ul\n v-if=\"multiple && selectedFileNames.length > 0\"\n class=\"g-file-input-pills\"\n aria-label=\"Selected files\"\n >\n <li\n v-for=\"name in selectedFileNames\"\n :key=\"name\"\n class=\"g-file-input-pill\"\n >\n <svg\n class=\"g-file-input-pill-icon\"\n role=\"none presentation\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n >\n <!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n d=\"M192 64C156.7 64 128 92.7 128 128L128 512C128 547.3 156.7 576 192 576L448 576C483.3 576 512 547.3 512 512L512 234.5C512 217.5 505.3 201.2 493.3 189.2L386.7 82.7C374.7 70.7 358.5 64 341.5 64L192 64zM453.5 240L360 240C346.7 240 336 229.3 336 216L336 122.5L453.5 240z\"\n />\n </svg>\n {{ name }}\n </li>\n </ul>\n </div>\n\n <GFormErrorMessages :errors=\"allErrors\" :id=\"'error-message-' + id\" />\n </div>\n</template>\n\n<style>\ng-file-input {\n display: block;\n}\n.g-file-input-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n.g-file-input-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n\n.g-file-input-required {\n color: var(--g-danger-600);\n margin-left: 0.2em;\n}\n\n.g-file-input-box {\n display: flex;\n max-width: 360px;\n flex-direction: column;\n gap: 0.75em;\n border: 1px solid var(--g-primary-500);\n background: var(--g-surface-50);\n padding: 0.75em;\n}\n\n.g-file-input-box--disabled {\n border-color: var(--g-surface-400);\n background: var(--g-surface-100);\n}\n\n.g-file-input-has-error .g-file-input-box {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-file-input-box-header {\n display: flex;\n align-items: center;\n gap: 0.5em;\n color: var(--g-surface-700);\n}\n\n.g-file-input-upload-icon {\n flex-shrink: 0;\n height: 2em;\n width: 2em;\n color: var(--g-primary-500);\n}\n\n.g-file-input-box--disabled .g-file-input-upload-icon {\n color: var(--g-surface-600);\n}\n\n.g-file-input-instructions {\n font-size: 0.9em;\n color: var(--g-surface-900);\n}\n\n.g-file-input {\n font-size: 1em;\n font-family: var(--il-font-sans);\n color: var(--g-surface-950);\n background: transparent;\n border: none;\n padding: 0;\n cursor: pointer;\n width: 100%;\n}\n\n.g-file-input::file-selector-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--il-font-sans);\n font-weight: 700;\n font-size: 19px;\n line-height: 20px;\n border: 2px solid var(--g-primary-500);\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n cursor: pointer;\n padding: 12px 20px;\n border-radius: var(--g-border-radius-m);\n text-decoration: none;\n}\n\n.g-file-input::file-selector-button:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n}\n\n.g-file-input:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n border-radius: var(--g-border-radius-s);\n}\n\n.g-file-input:disabled {\n cursor: not-allowed;\n color: var(--g-surface-800);\n}\n\n.g-file-input:disabled::file-selector-button {\n background: var(--g-surface-200);\n color: var(--g-surface-900);\n border-color: var(--g-surface-200);\n cursor: not-allowed;\n}\n\n.g-file-input-pills {\n display: flex;\n flex-wrap: wrap;\n gap: 0.4em;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.g-file-input-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.3em;\n border: 1px solid var(--g-primary-500);\n color: var(--g-surface-950);\n border-radius: 999px;\n padding: 0.2em 0.7em;\n font-size: 0.85em;\n max-width: 24em;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.g-file-input-pill-icon {\n flex-shrink: 0;\n height: 1em;\n width: 1em;\n opacity: 0.7;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A date input component.\n *\n * This component is a wrapper around GTextInput with type=\"date\" for\n * proper date selection using the browser's native date picker.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"props.name\"\n :label=\"props.label\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n :errors=\"props.errors\"\n :instructions=\"props.instructions\"\n :form-key=\"props.formKey\"\n type=\"date\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-date-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * A date input component.\n *\n * This component is a wrapper around GTextInput with type=\"date\" for\n * proper date selection using the browser's native date picker.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"props.name\"\n :label=\"props.label\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n :errors=\"props.errors\"\n :instructions=\"props.instructions\"\n :form-key=\"props.formKey\"\n type=\"date\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-date-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * A date range input component with start and end dates.\n *\n * This component uses two GDateInput components laid out horizontally\n * to allow selecting a date range.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { ref, watch, toRef } from \"vue\";\nimport GDateInput from \"./GDateInput.vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\n\ntype Props = {\n /**\n * Label for the component\n * @demo\n */\n label?: string;\n /**\n * Label for the start date input\n * @demo\n */\n startLabel?: string;\n /**\n * Label for the end date input\n * @demo\n */\n endLabel?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n startLabel: \"Start Date\",\n endLabel: \"End Date\",\n instructions: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\ntype DateRange = {\n start: string | null;\n end: string | null;\n};\n\nconst model = defineModel<DateRange>({\n default: () => ({ start: null, end: null }),\n});\n\nconst startDate = ref<string | null>(model.value.start || null);\nconst endDate = ref<string | null>(model.value.end || null);\n\n// Use form field composable for form registration and error handling\nconst { displayErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nwatch([startDate, endDate], () => {\n model.value = {\n start: startDate.value,\n end: endDate.value,\n };\n});\n\nwatch(\n model,\n (newValue) => {\n if (newValue.start !== startDate.value) {\n startDate.value = newValue.start;\n }\n if (newValue.end !== endDate.value) {\n endDate.value = newValue.end;\n }\n },\n { deep: true }\n);\n</script>\n\n<template>\n <div class=\"g-date-range-input\">\n <div v-if=\"props.label\" class=\"g-date-range-input__label\">\n {{ props.label }}<span v-if=\"props.required\" class=\"g-date-range-input__required\" aria-hidden=\"true\"> *</span>\n </div>\n <div\n v-if=\"props.instructions\"\n class=\"g-date-range-input__instructions\"\n >\n {{ props.instructions }}\n </div>\n <div class=\"g-date-range-input__fields\">\n <GDateInput\n v-model=\"startDate\"\n :label=\"props.startLabel\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n class=\"g-date-range-input__field\"\n />\n <GDateInput\n v-model=\"endDate\"\n :label=\"props.endLabel\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n class=\"g-date-range-input__field\"\n />\n </div>\n <div v-if=\"displayErrors.length > 0\" class=\"g-date-range-input__errors\" role=\"alert\">\n <div\n v-for=\"(errorMsg, index) in displayErrors\"\n :key=\"index\"\n class=\"g-date-range-input__error\"\n >\n {{ errorMsg }}\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-date-range-input {\n display: block;\n}\n.g-date-range-input {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.g-date-range-input__label {\n font-size: 1.25em;\n font-weight: 600;\n}\n\n.g-date-range-input__required {\n color: var(--g-danger-600);\n}\n\n.g-date-range-input__instructions {\n margin: 0 0 0.25em 0.5em;\n color: var(--g-surface-800);\n}\n\n.g-date-range-input__fields {\n display: flex;\n flex-direction: row;\n gap: 1rem;\n align-items: flex-start;\n}\n\n.g-date-range-input__field {\n flex: 1;\n min-width: 0;\n}\n\n.g-date-range-input__errors {\n display: flex;\n flex-direction: column;\n gap: 0.25em;\n}\n\n.g-date-range-input__error {\n background: var(--g-surface-0);\n color: var(--g-danger-600);\n padding: 0.25em 0.5em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A date range input component with start and end dates.\n *\n * This component uses two GDateInput components laid out horizontally\n * to allow selecting a date range.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { ref, watch, toRef } from \"vue\";\nimport GDateInput from \"./GDateInput.vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\n\ntype Props = {\n /**\n * Label for the component\n * @demo\n */\n label?: string;\n /**\n * Label for the start date input\n * @demo\n */\n startLabel?: string;\n /**\n * Label for the end date input\n * @demo\n */\n endLabel?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n startLabel: \"Start Date\",\n endLabel: \"End Date\",\n instructions: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\ntype DateRange = {\n start: string | null;\n end: string | null;\n};\n\nconst model = defineModel<DateRange>({\n default: () => ({ start: null, end: null }),\n});\n\nconst startDate = ref<string | null>(model.value.start || null);\nconst endDate = ref<string | null>(model.value.end || null);\n\n// Use form field composable for form registration and error handling\nconst { displayErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nwatch([startDate, endDate], () => {\n model.value = {\n start: startDate.value,\n end: endDate.value,\n };\n});\n\nwatch(\n model,\n (newValue) => {\n if (newValue.start !== startDate.value) {\n startDate.value = newValue.start;\n }\n if (newValue.end !== endDate.value) {\n endDate.value = newValue.end;\n }\n },\n { deep: true }\n);\n</script>\n\n<template>\n <div class=\"g-date-range-input\">\n <div v-if=\"props.label\" class=\"g-date-range-input__label\">\n {{ props.label }}<span v-if=\"props.required\" class=\"g-date-range-input__required\" aria-hidden=\"true\"> *</span>\n </div>\n <div\n v-if=\"props.instructions\"\n class=\"g-date-range-input__instructions\"\n >\n {{ props.instructions }}\n </div>\n <div class=\"g-date-range-input__fields\">\n <GDateInput\n v-model=\"startDate\"\n :label=\"props.startLabel\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n class=\"g-date-range-input__field\"\n />\n <GDateInput\n v-model=\"endDate\"\n :label=\"props.endLabel\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n class=\"g-date-range-input__field\"\n />\n </div>\n <div v-if=\"displayErrors.length > 0\" class=\"g-date-range-input__errors\" role=\"alert\">\n <div\n v-for=\"(errorMsg, index) in displayErrors\"\n :key=\"index\"\n class=\"g-date-range-input__error\"\n >\n {{ errorMsg }}\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-date-range-input {\n display: block;\n}\n.g-date-range-input {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.g-date-range-input__label {\n font-size: 1.25em;\n font-weight: 600;\n}\n\n.g-date-range-input__required {\n color: var(--g-danger-600);\n}\n\n.g-date-range-input__instructions {\n margin: 0 0 0.25em 0.5em;\n color: var(--g-surface-800);\n}\n\n.g-date-range-input__fields {\n display: flex;\n flex-direction: row;\n gap: 1rem;\n align-items: flex-start;\n}\n\n.g-date-range-input__field {\n flex: 1;\n min-width: 0;\n}\n\n.g-date-range-input__errors {\n display: flex;\n flex-direction: column;\n gap: 0.25em;\n}\n\n.g-date-range-input__error {\n background: var(--g-surface-0);\n color: var(--g-danger-600);\n padding: 0.25em 0.5em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A form wrapper component that automatically manages form state and\n * connects to child input components.\n *\n * Child input components that have a `name` prop will automatically\n * register with the form, and their values will be tracked in the form model.\n *\n * ### Features\n *\n * - Automatic value tracking for child input components with the `name` prop\n * - Reactive error handling by providing a computed list of errors\n * - Optionally manage your own form state in a parent component by providing a\n * `form` injection\n * - In web components mode, use the `form-key` prop to pair a form with\n * matching inputs/buttons across custom element app boundaries\n *\n * ### Basic example\n *\n * ```vue-html\n * <GForm v-model=\"formData\" @submit=\"handleSubmit\">\n * <template #default=\"{ isSubmitting, hasErrors }\">\n * <GTextInput name=\"firstName\" label=\"First Name\" :errors=\"firstNameErrors\" />\n * <GSubmitButton :disabled=\"hasErrors\">Submit</GSubmitButton>\n * </template>\n * </GForm>\n * ```\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { provide, watch, inject } from \"vue\";\nimport { useForm, UseFormReturn } from \"../compose/useForm.ts\";\nimport { useWebComponentForm } from \"../compose/useWebComponentForm.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Action URL (optional, for native form submission)\n * @demo\n */\n action?: string;\n /**\n * HTTP method (optional, for native form submission)\n * @demo\n */\n method?: string;\n /**\n * Form channel key for custom elements mode\n * @demo\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n action: undefined,\n method: \"post\",\n formKey: \"default\",\n});\n\nconst model = defineModel<Record<string, any>>({ default: () => ({}) });\n\nconst emit = defineEmits<{\n submit: [values: Record<string, any>];\n}>();\n\n// Check if a form is already injected from a parent\nconst parentForm = inject<UseFormReturn | null>(\"form\", null);\n\nconst form =\n parentForm ??\n (isCustomElementMode() ? useWebComponentForm(props.formKey) : useForm());\n\n// Only provide the form if we created it (not if we're using a parent's form)\nif (!parentForm) {\n provide(\"form\", form);\n}\n\n// Sync form values to model\nwatch(\n () => form.values.value,\n (newValues) => {\n model.value = { ...newValues };\n },\n { deep: true },\n);\n\n// Initialize fields from model value\nwatch(\n () => model.value,\n (newModel) => {\n if (newModel) {\n Object.entries(newModel).forEach(([name, value]) => {\n const field = form.fields[name];\n if (field && field.value.value !== value) {\n field.value.value = value;\n }\n });\n }\n },\n { deep: true, immediate: true },\n);\n\nasync function handleSubmit(e: Event) {\n e.preventDefault();\n await form.submit(async (values) => {\n emit(\"submit\", values);\n });\n}\n\n</script>\n\n<template>\n <form\n @submit=\"handleSubmit\"\n :action=\"props.action\"\n :method=\"props.method\"\n class=\"g-form\"\n novalidate\n >\n <slot\n :isSubmitting=\"form.isSubmitting.value\"\n :hasErrors=\"form.hasErrors.value\"\n :values=\"form.values.value\"\n :errors=\"form.errors.value\"\n ></slot>\n </form>\n</template>\n\n<style>\ng-form {\n display: block;\n}\n.g-form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A form wrapper component that automatically manages form state and\n * connects to child input components.\n *\n * Child input components that have a `name` prop will automatically\n * register with the form, and their values will be tracked in the form model.\n *\n * ### Features\n *\n * - Automatic value tracking for child input components with the `name` prop\n * - Reactive error handling by providing a computed list of errors\n * - Optionally manage your own form state in a parent component by providing a\n * `form` injection\n * - In web components mode, use the `form-key` prop to pair a form with\n * matching inputs/buttons across custom element app boundaries\n *\n * ### Basic example\n *\n * ```vue-html\n * <GForm v-model=\"formData\" @submit=\"handleSubmit\">\n * <template #default=\"{ isSubmitting, hasErrors }\">\n * <GTextInput name=\"firstName\" label=\"First Name\" :errors=\"firstNameErrors\" />\n * <GSubmitButton :disabled=\"hasErrors\">Submit</GSubmitButton>\n * </template>\n * </GForm>\n * ```\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { provide, watch, inject } from \"vue\";\nimport { useForm, UseFormReturn } from \"../compose/useForm.ts\";\nimport { useWebComponentForm } from \"../compose/useWebComponentForm.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Action URL (optional, for native form submission)\n * @demo\n */\n action?: string;\n /**\n * HTTP method (optional, for native form submission)\n * @demo\n */\n method?: string;\n /**\n * Form channel key for custom elements mode\n * @demo\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n action: undefined,\n method: \"post\",\n formKey: \"default\",\n});\n\nconst model = defineModel<Record<string, any>>({ default: () => ({}) });\n\nconst emit = defineEmits<{\n submit: [values: Record<string, any>];\n}>();\n\n// Check if a form is already injected from a parent\nconst parentForm = inject<UseFormReturn | null>(\"form\", null);\n\nconst form =\n parentForm ??\n (isCustomElementMode() ? useWebComponentForm(props.formKey) : useForm());\n\n// Only provide the form if we created it (not if we're using a parent's form)\nif (!parentForm) {\n provide(\"form\", form);\n}\n\n// Sync form values to model\nwatch(\n () => form.values.value,\n (newValues) => {\n model.value = { ...newValues };\n },\n { deep: true },\n);\n\n// Initialize fields from model value\nwatch(\n () => model.value,\n (newModel) => {\n if (newModel) {\n Object.entries(newModel).forEach(([name, value]) => {\n const field = form.fields[name];\n if (field && field.value.value !== value) {\n field.value.value = value;\n }\n });\n }\n },\n { deep: true, immediate: true },\n);\n\nasync function handleSubmit(e: Event) {\n e.preventDefault();\n await form.submit(async (values) => {\n emit(\"submit\", values);\n });\n}\n\n</script>\n\n<template>\n <form\n @submit=\"handleSubmit\"\n :action=\"props.action\"\n :method=\"props.method\"\n class=\"g-form\"\n novalidate\n >\n <slot\n :isSubmitting=\"form.isSubmitting.value\"\n :hasErrors=\"form.hasErrors.value\"\n :values=\"form.values.value\"\n :errors=\"form.errors.value\"\n ></slot>\n </form>\n</template>\n\n<style>\ng-form {\n display: block;\n}\n.g-form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A submit button that integrates with GForm.\n *\n * When used inside a GForm, the button will automatically:\n * - Show a loading state during form submission\n * - Be disabled when specified\n *\n * In standard Vue usage, this resolves the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * @example\n * <GForm v-model=\"formData\" @submit=\"handleSubmit\">\n * <GTextInput name=\"email\" label=\"Email\" />\n * <GSubmitButton>Submit</GSubmitButton>\n * </GForm>\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { inject, computed, useAttrs } from \"vue\";\nimport { UseFormReturn } from \"../compose/useForm.ts\";\nimport { useWebComponentForm } from \"../compose/useWebComponentForm.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Disabled state\n * @demo\n */\n disabled?: boolean;\n /**\n * Loading text to show during submission\n * @demo\n */\n loadingText?: string;\n /**\n * Variant\n * @demo\n */\n variant?: \"primary\" | \"secondary\" | \"danger\";\n /**\n * Form channel key for custom elements mode\n * @demo\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n loadingText: \"Submitting...\",\n variant: \"primary\",\n formKey: \"default\",\n});\n\nconst attrs = useAttrs();\nconst attrFormKey = typeof attrs[\"form-key\"] === \"string\" ? attrs[\"form-key\"] : undefined;\nconst formKey = props.formKey || attrFormKey || \"default\";\n\nconst injectedForm = inject<UseFormReturn | null>(\"form\", null);\nconst form =\n injectedForm ??\n (isCustomElementMode() ? useWebComponentForm(formKey) : null);\n\nconst isDisabled = computed(() => {\n return props.disabled || (form?.isSubmitting.value ?? false);\n});\n\nconst isSubmitting = computed(() => {\n return form?.isSubmitting.value ?? false;\n});\n</script>\n\n<template>\n <GButton\n type=\"submit\"\n :disabled=\"isDisabled\"\n :variant=\"props.variant\"\n class=\"g-submit-button\"\n >\n <span v-show=\"isSubmitting\">{{ props.loadingText }}</span>\n <span v-show=\"!isSubmitting\">\n <slot>Submit</slot>\n </span>\n </GButton>\n</template>\n\n<style>\ng-submit-button {\n display: inline-block;\n}\n.g-submit-button {\n align-self: flex-start;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A submit button that integrates with GForm.\n *\n * When used inside a GForm, the button will automatically:\n * - Show a loading state during form submission\n * - Be disabled when specified\n *\n * In standard Vue usage, this resolves the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * @example\n * <GForm v-model=\"formData\" @submit=\"handleSubmit\">\n * <GTextInput name=\"email\" label=\"Email\" />\n * <GSubmitButton>Submit</GSubmitButton>\n * </GForm>\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { inject, computed, useAttrs } from \"vue\";\nimport { UseFormReturn } from \"../compose/useForm.ts\";\nimport { useWebComponentForm } from \"../compose/useWebComponentForm.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Disabled state\n * @demo\n */\n disabled?: boolean;\n /**\n * Loading text to show during submission\n * @demo\n */\n loadingText?: string;\n /**\n * Variant\n * @demo\n */\n variant?: \"primary\" | \"secondary\" | \"danger\";\n /**\n * Form channel key for custom elements mode\n * @demo\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n loadingText: \"Submitting...\",\n variant: \"primary\",\n formKey: \"default\",\n});\n\nconst attrs = useAttrs();\nconst attrFormKey = typeof attrs[\"form-key\"] === \"string\" ? attrs[\"form-key\"] : undefined;\nconst formKey = props.formKey || attrFormKey || \"default\";\n\nconst injectedForm = inject<UseFormReturn | null>(\"form\", null);\nconst form =\n injectedForm ??\n (isCustomElementMode() ? useWebComponentForm(formKey) : null);\n\nconst isDisabled = computed(() => {\n return props.disabled || (form?.isSubmitting.value ?? false);\n});\n\nconst isSubmitting = computed(() => {\n return form?.isSubmitting.value ?? false;\n});\n</script>\n\n<template>\n <GButton\n type=\"submit\"\n :disabled=\"isDisabled\"\n :variant=\"props.variant\"\n class=\"g-submit-button\"\n >\n <span v-show=\"isSubmitting\">{{ props.loadingText }}</span>\n <span v-show=\"!isSubmitting\">\n <slot>Submit</slot>\n </span>\n </GButton>\n</template>\n\n<style>\ng-submit-button {\n display: inline-block;\n}\n.g-submit-button {\n align-self: flex-start;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A group of checkboxes (or radio buttons) with styling for a label,\n * instructions, and error messages.\n *\n * When more than one option is provided (or `radio` mode is used), a\n * `fieldset` + `legend` provides semantic grouping. With a single checkbox\n * a plain `div` is rendered instead.\n *\n * Each option renders as a native `<input type=\"checkbox\">` (or\n * `type=\"radio\"` when `radio` is `true`) so that keyboard navigation and\n * browser/assistive-technology support come for free.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, toRef, useId } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\nexport interface CheckboxOption {\n label: string;\n value: string;\n disabled?: boolean;\n hint?: string;\n}\n\ntype Props = {\n /**\n * Legend / accessible label for the group\n * @demo Checkbox Group\n */\n label?: string;\n /**\n * List of checkbox options\n */\n options: CheckboxOption[];\n /**\n * Instructions shown below the legend\n * @demo\n */\n instructions?: string;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Mark the group as required\n * @demo\n */\n required?: boolean;\n /**\n * Render as radio buttons (single-select)\n * @demo\n */\n radio?: boolean;\n /**\n * Name for form registration and native input `name` attribute\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n errors: () => [],\n required: false,\n radio: false,\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string[]>({ default: () => [] });\n\nconst id = useId();\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [value: string[]];\n}>();\n\nfunction isChecked(value: string): boolean {\n return model.value.includes(value);\n}\n\nfunction handleChange(optionValue: string, checked: boolean) {\n let next: string[];\n if (props.radio) {\n next = checked ? [optionValue] : [];\n } else {\n if (checked) {\n next = model.value.includes(optionValue)\n ? model.value\n : [...model.value, optionValue];\n } else {\n next = model.value.filter((v) => v !== optionValue);\n }\n }\n model.value = next;\n emit(\"change\", next);\n}\n\nconst inputType = computed(() => (props.radio ? \"radio\" : \"checkbox\"));\nconst errorId = computed(() => `error-message-${id}`);\nconst instructionsId = computed(() => `instructions-${id}`);\n\n// Use a fieldset for radio mode or multiple checkboxes; a plain div for a single checkbox\nconst useFieldset = computed(() => props.radio || props.options.length > 1);\n\n// Attributes added to the outer element when native required semantics are unavailable\n// or when radio mode needs grouped error metadata.\nconst groupAttrs = computed(() => {\n const describedParts: string[] = [];\n if (props.instructions) describedParts.push(instructionsId.value);\n if (props.radio) {\n return {\n role: \"radiogroup\",\n \"aria-invalid\": hasErrors.value ? \"true\" : undefined,\n \"aria-errormessage\": hasErrors.value ? errorId.value : undefined,\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n }\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n});\n\nfunction hintId(index: number): string {\n return `${id}-hint-${index}`;\n}\n\nfunction inputId(index: number): string {\n return `${id}-input-${index}`;\n}\n\n// Per-input aria attributes.\n// - For checkbox inputs: aria-invalid and aria-errormessage live here.\n// - For radio inputs: the radiogroup fieldset carries those; inputs only reference hints.\nfunction inputAriaAttrs(option: CheckboxOption, index: number): Record<string, string | undefined> {\n const describedParts: string[] = [];\n if (!props.radio && props.instructions) describedParts.push(instructionsId.value);\n if (option.hint) describedParts.push(hintId(index));\n\n if (props.radio) {\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n }\n\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n \"aria-required\":\n props.required && props.options.length > 1 ? \"true\" : undefined,\n \"aria-invalid\": hasErrors.value ? \"true\" : \"false\",\n \"aria-errormessage\": hasErrors.value ? errorId.value : undefined,\n };\n}\n</script>\n\n<template>\n <component\n :is=\"useFieldset ? 'fieldset' : 'div'\"\n class=\"g-checkbox-group\"\n :class=\"{ 'g-checkbox-group--error': hasErrors }\"\n v-bind=\"groupAttrs\"\n >\n <!-- fieldset uses <legend>; single-checkbox div uses a plain heading div -->\n <legend v-if=\"useFieldset && label\" class=\"g-checkbox-group__legend\">\n {{ label }}<span v-if=\"required\" class=\"g-checkbox-group__required\" aria-hidden=\"true\">&nbsp;*</span>\n </legend>\n <div v-else-if=\"!useFieldset && label\" class=\"g-checkbox-group__label\">\n {{ label }}<span v-if=\"required\" class=\"g-checkbox-group__required\" aria-hidden=\"true\">&nbsp;*</span>\n </div>\n\n <div\n v-if=\"instructions\"\n :id=\"instructionsId\"\n class=\"g-checkbox-group__instructions\"\n >{{ instructions }}</div>\n\n <div class=\"g-checkbox-group__options\">\n <div\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n class=\"g-checkbox-group__option-wrapper\"\n :class=\"{ 'g-checkbox-group__option-wrapper--disabled': option.disabled }\"\n >\n <label\n class=\"g-checkbox-group__option\"\n :for=\"inputId(index)\"\n :class=\"{ 'g-checkbox-group__option--checked': isChecked(option.value) }\"\n >\n <input\n :id=\"inputId(index)\"\n :type=\"inputType\"\n :name=\"name || id\"\n :value=\"option.value\"\n :checked=\"isChecked(option.value)\"\n :disabled=\"option.disabled\"\n :required=\"\n required &&\n ((radio && index === 0) || (!radio && options.length === 1))\n \"\n class=\"g-checkbox-group__input\"\n v-bind=\"inputAriaAttrs(option, index)\"\n @change=\"handleChange(option.value, ($event.target as HTMLInputElement).checked)\"\n />\n <span class=\"g-checkbox-group__label-text\">{{ option.label }}</span>\n </label>\n <!-- Hint is outside the label and referenced via aria-describedby on the input -->\n <div\n v-if=\"option.hint\"\n :id=\"hintId(index)\"\n class=\"g-checkbox-group__hint\"\n >{{ option.hint }}</div>\n </div>\n </div>\n\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"errorId\"\n />\n </component>\n</template>\n\n<style>\ng-checkbox-group {\n display: block;\n}\n.g-checkbox-group {\n border: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n}\n\n.g-checkbox-group__legend,\n.g-checkbox-group__label {\n font-size: 1.25em;\n margin-bottom: 0.5em;\n padding: 0;\n float: left;\n width: 100%;\n}\n\n.g-checkbox-group__required {\n color: var(--g-danger-600);\n}\n\n.g-checkbox-group__instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n clear: both;\n}\n\n.g-checkbox-group__options {\n display: flex;\n flex-direction: column;\n gap: 0.5em;\n clear: both;\n}\n\n.g-checkbox-group__option-wrapper {\n display: flex;\n flex-direction: column;\n}\n\n.g-checkbox-group__option-wrapper--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.g-checkbox-group__option {\n display: flex;\n align-items: flex-start;\n gap: 0.5em;\n cursor: pointer;\n padding: 0.375em 0.5em;\n border-radius: 4px;\n}\n\n.g-checkbox-group__option-wrapper--disabled .g-checkbox-group__option {\n cursor: not-allowed;\n}\n\n.g-checkbox-group__input {\n margin-top: 0.2em;\n flex-shrink: 0;\n accent-color: var(--g-primary-500);\n width: 1.1em;\n height: 1.1em;\n cursor: pointer;\n\n &:focus-visible {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-checkbox-group__option-wrapper--disabled .g-checkbox-group__input {\n cursor: not-allowed;\n}\n\n.g-checkbox-group__label-text {\n font-family: var(--il-font-sans);\n line-height: 1.4;\n}\n\n/* Indent hint to align with the label text, accounting for the checkbox width\n (1.1em) + flex gap (0.5em) + option left-padding (0.5em). */\n.g-checkbox-group__hint {\n padding-left: calc(0.5em + 1.1em + 0.5em);\n padding-right: 0.5em;\n font-size: 0.875em;\n color: var(--g-surface-700);\n margin-top: 0.125em;\n}\n\n.g-checkbox-group--error .g-checkbox-group__options {\n border-left: 3px solid var(--g-danger-600);\n padding-left: 0.75em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A group of checkboxes (or radio buttons) with styling for a label,\n * instructions, and error messages.\n *\n * When more than one option is provided (or `radio` mode is used), a\n * `fieldset` + `legend` provides semantic grouping. With a single checkbox\n * a plain `div` is rendered instead.\n *\n * Each option renders as a native `<input type=\"checkbox\">` (or\n * `type=\"radio\"` when `radio` is `true`) so that keyboard navigation and\n * browser/assistive-technology support come for free.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, toRef, useId } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\nexport interface CheckboxOption {\n label: string;\n value: string;\n disabled?: boolean;\n hint?: string;\n}\n\ntype Props = {\n /**\n * Legend / accessible label for the group\n * @demo Checkbox Group\n */\n label?: string;\n /**\n * List of checkbox options\n */\n options: CheckboxOption[];\n /**\n * Instructions shown below the legend\n * @demo\n */\n instructions?: string;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Mark the group as required\n * @demo\n */\n required?: boolean;\n /**\n * Render as radio buttons (single-select)\n * @demo\n */\n radio?: boolean;\n /**\n * Name for form registration and native input `name` attribute\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n errors: () => [],\n required: false,\n radio: false,\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string[]>({ default: () => [] });\n\nconst id = useId();\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [value: string[]];\n}>();\n\nfunction isChecked(value: string): boolean {\n return model.value.includes(value);\n}\n\nfunction handleChange(optionValue: string, checked: boolean) {\n let next: string[];\n if (props.radio) {\n next = checked ? [optionValue] : [];\n } else {\n if (checked) {\n next = model.value.includes(optionValue)\n ? model.value\n : [...model.value, optionValue];\n } else {\n next = model.value.filter((v) => v !== optionValue);\n }\n }\n model.value = next;\n emit(\"change\", next);\n}\n\nconst inputType = computed(() => (props.radio ? \"radio\" : \"checkbox\"));\nconst errorId = computed(() => `error-message-${id}`);\nconst instructionsId = computed(() => `instructions-${id}`);\n\n// Use a fieldset for radio mode or multiple checkboxes; a plain div for a single checkbox\nconst useFieldset = computed(() => props.radio || props.options.length > 1);\n\n// Attributes added to the outer element when native required semantics are unavailable\n// or when radio mode needs grouped error metadata.\nconst groupAttrs = computed(() => {\n const describedParts: string[] = [];\n if (props.instructions) describedParts.push(instructionsId.value);\n if (props.radio) {\n return {\n role: \"radiogroup\",\n \"aria-invalid\": hasErrors.value ? \"true\" : undefined,\n \"aria-errormessage\": hasErrors.value ? errorId.value : undefined,\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n }\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n});\n\nfunction hintId(index: number): string {\n return `${id}-hint-${index}`;\n}\n\nfunction inputId(index: number): string {\n return `${id}-input-${index}`;\n}\n\n// Per-input aria attributes.\n// - For checkbox inputs: aria-invalid and aria-errormessage live here.\n// - For radio inputs: the radiogroup fieldset carries those; inputs only reference hints.\nfunction inputAriaAttrs(option: CheckboxOption, index: number): Record<string, string | undefined> {\n const describedParts: string[] = [];\n if (!props.radio && props.instructions) describedParts.push(instructionsId.value);\n if (option.hint) describedParts.push(hintId(index));\n\n if (props.radio) {\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n }\n\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n \"aria-required\":\n props.required && props.options.length > 1 ? \"true\" : undefined,\n \"aria-invalid\": hasErrors.value ? \"true\" : \"false\",\n \"aria-errormessage\": hasErrors.value ? errorId.value : undefined,\n };\n}\n</script>\n\n<template>\n <component\n :is=\"useFieldset ? 'fieldset' : 'div'\"\n class=\"g-checkbox-group\"\n :class=\"{ 'g-checkbox-group--error': hasErrors }\"\n v-bind=\"groupAttrs\"\n >\n <!-- fieldset uses <legend>; single-checkbox div uses a plain heading div -->\n <legend v-if=\"useFieldset && label\" class=\"g-checkbox-group__legend\">\n {{ label }}<span v-if=\"required\" class=\"g-checkbox-group__required\" aria-hidden=\"true\">&nbsp;*</span>\n </legend>\n <div v-else-if=\"!useFieldset && label\" class=\"g-checkbox-group__label\">\n {{ label }}<span v-if=\"required\" class=\"g-checkbox-group__required\" aria-hidden=\"true\">&nbsp;*</span>\n </div>\n\n <div\n v-if=\"instructions\"\n :id=\"instructionsId\"\n class=\"g-checkbox-group__instructions\"\n >{{ instructions }}</div>\n\n <div class=\"g-checkbox-group__options\">\n <div\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n class=\"g-checkbox-group__option-wrapper\"\n :class=\"{ 'g-checkbox-group__option-wrapper--disabled': option.disabled }\"\n >\n <label\n class=\"g-checkbox-group__option\"\n :for=\"inputId(index)\"\n :class=\"{ 'g-checkbox-group__option--checked': isChecked(option.value) }\"\n >\n <input\n :id=\"inputId(index)\"\n :type=\"inputType\"\n :name=\"name || id\"\n :value=\"option.value\"\n :checked=\"isChecked(option.value)\"\n :disabled=\"option.disabled\"\n :required=\"\n required &&\n ((radio && index === 0) || (!radio && options.length === 1))\n \"\n class=\"g-checkbox-group__input\"\n v-bind=\"inputAriaAttrs(option, index)\"\n @change=\"handleChange(option.value, ($event.target as HTMLInputElement).checked)\"\n />\n <span class=\"g-checkbox-group__label-text\">{{ option.label }}</span>\n </label>\n <!-- Hint is outside the label and referenced via aria-describedby on the input -->\n <div\n v-if=\"option.hint\"\n :id=\"hintId(index)\"\n class=\"g-checkbox-group__hint\"\n >{{ option.hint }}</div>\n </div>\n </div>\n\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"errorId\"\n />\n </component>\n</template>\n\n<style>\ng-checkbox-group {\n display: block;\n}\n.g-checkbox-group {\n border: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n}\n\n.g-checkbox-group__legend,\n.g-checkbox-group__label {\n font-size: 1.25em;\n margin-bottom: 0.5em;\n padding: 0;\n float: left;\n width: 100%;\n}\n\n.g-checkbox-group__required {\n color: var(--g-danger-600);\n}\n\n.g-checkbox-group__instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n clear: both;\n}\n\n.g-checkbox-group__options {\n display: flex;\n flex-direction: column;\n gap: 0.5em;\n clear: both;\n}\n\n.g-checkbox-group__option-wrapper {\n display: flex;\n flex-direction: column;\n}\n\n.g-checkbox-group__option-wrapper--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.g-checkbox-group__option {\n display: flex;\n align-items: flex-start;\n gap: 0.5em;\n cursor: pointer;\n padding: 0.375em 0.5em;\n border-radius: 4px;\n}\n\n.g-checkbox-group__option-wrapper--disabled .g-checkbox-group__option {\n cursor: not-allowed;\n}\n\n.g-checkbox-group__input {\n margin-top: 0.2em;\n flex-shrink: 0;\n accent-color: var(--g-primary-500);\n width: 1.1em;\n height: 1.1em;\n cursor: pointer;\n\n &:focus-visible {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-checkbox-group__option-wrapper--disabled .g-checkbox-group__input {\n cursor: not-allowed;\n}\n\n.g-checkbox-group__label-text {\n font-family: var(--il-font-sans);\n line-height: 1.4;\n}\n\n/* Indent hint to align with the label text, accounting for the checkbox width\n (1.1em) + flex gap (0.5em) + option left-padding (0.5em). */\n.g-checkbox-group__hint {\n padding-left: calc(0.5em + 1.1em + 0.5em);\n padding-right: 0.5em;\n font-size: 0.875em;\n color: var(--g-surface-700);\n margin-top: 0.125em;\n}\n\n.g-checkbox-group--error .g-checkbox-group__options {\n border-left: 3px solid var(--g-danger-600);\n padding-left: 0.75em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A multi-line plain text input with styling for a label, instructions,\n * and error messages.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n * All non-prop attributes are passed through to the textarea element, including\n * `id`.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, nextTick, ref, toRef, useId, useTemplateRef, watch } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Example Label\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Read-only\n * @demo\n */\n readonly?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Number of visible text rows\n * @demo\n */\n rows?: number;\n /**\n * Maximum number of characters allowed\n * @demo\n */\n maxlength?: number;\n /**\n * Automatically grow the textarea height to fit content\n * @demo\n */\n autoGrow?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n readonly: false,\n required: false,\n errors: () => [],\n rows: 4,\n maxlength: undefined,\n autoGrow: false,\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n\nconst id = useId();\nconst { attrs, isCustomElement, forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\nconst inputId = computed(() => {\n if (isCustomElement) {\n return id;\n }\n return (attrs.id as string) || id;\n});\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [\n {\n was: string | null | undefined;\n to: string | null;\n },\n ];\n}>();\n\nconst textareaRef = useTemplateRef<HTMLTextAreaElement>(\"textareaEl\");\n\nfunction adjustHeight() {\n const el = textareaRef.value;\n if (!el) return;\n el.style.height = \"auto\";\n el.style.height = `${el.scrollHeight}px`;\n}\n\nwatch(\n () => model.value,\n () => {\n if (props.autoGrow) {\n nextTick(adjustHeight);\n }\n },\n);\n\nfunction emitChangeIfNeeded(val: string | null) {\n if (val !== model.value) {\n const prev = model.value;\n model.value = val;\n emit(\"change\", { was: prev, to: val });\n }\n}\n\nfunction onInput(e: Event) {\n const value = (e.target as HTMLTextAreaElement).value;\n model.value = value;\n if (props.autoGrow) {\n adjustHeight();\n }\n}\n\nfunction onBlur(e: FocusEvent) {\n emitChangeIfNeeded((e.target as HTMLTextAreaElement).value);\n}\n\nfunction onPaste(e: ClipboardEvent) {\n setTimeout(() => {\n const value = (e.target as HTMLTextAreaElement).value;\n emitChangeIfNeeded(value);\n if (props.autoGrow) {\n adjustHeight();\n }\n }, 0);\n}\n</script>\n\n<template>\n <div\n class=\"g-textarea-wrap\"\n :class=\"{ 'g-textarea-has-error': hasErrors }\"\n >\n <label\n v-if=\"props.label\"\n :for=\"inputId\"\n class=\"g-textarea-label\"\n >{{ props.label\n }}<span v-if=\"props.required\" class=\"g-textarea-required\" aria-hidden=\"true\"> *</span></label\n >\n <div\n v-if=\"$slots.instructions || instructions\"\n :id=\"'instructions-' + id\"\n class=\"g-textarea-instructions\"\n >\n <slot name=\"instructions\">{{ instructions }}</slot>\n </div>\n <textarea\n ref=\"textareaEl\"\n :value=\"model ?? ''\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :readonly=\"props.readonly\"\n :required=\"props.required\"\n :rows=\"props.rows\"\n :maxlength=\"props.maxlength ?? undefined\"\n class=\"g-textarea\"\n v-bind=\"{\n ...forwardedAttrs,\n id: inputId,\n 'aria-describedby':\n $slots.instructions || instructions\n ? 'instructions-' + id\n : undefined,\n 'aria-errormessage': hasErrors\n ? 'error-message-' + id\n : undefined,\n }\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n @input=\"onInput\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n />\n <div\n v-if=\"props.maxlength !== undefined\"\n class=\"g-textarea-char-count\"\n aria-live=\"polite\"\n >\n {{ (model ?? '').length }} / {{ props.maxlength }}\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + id\"\n />\n </div>\n</template>\n\n<style>\ng-textarea {\n display: block;\n}\n.g-textarea-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n.g-textarea-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n\n.g-textarea-required {\n color: var(--g-danger-600);\n}\n\n.g-textarea-instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n}\n\n.g-textarea {\n width: 100%;\n padding: 0.5em;\n font-size: 1em;\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n background: var(--g-surface-0);\n color: var(--g-surface-950);\n font-family: var(--il-font-sans);\n resize: vertical;\n box-sizing: border-box;\n}\n\n.g-textarea:focus {\n outline: 2px solid var(--g-primary-500);\n box-shadow: 0 0 0 2px var(--g-info-200);\n outline-offset: 2px;\n}\n\n.g-textarea-has-error .g-textarea {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-textarea:disabled {\n background: var(--g-surface-100);\n color: var(--g-surface-700);\n cursor: not-allowed;\n}\n\n.g-textarea[readonly] {\n background: var(--g-surface-50);\n color: var(--g-surface-800);\n}\n\n.g-textarea-char-count {\n font-size: 0.875em;\n color: var(--g-surface-700);\n text-align: right;\n margin-top: 0.25em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A multi-line plain text input with styling for a label, instructions,\n * and error messages.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n * All non-prop attributes are passed through to the textarea element, including\n * `id`.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, nextTick, ref, toRef, useId, useTemplateRef, watch } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Example Label\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Read-only\n * @demo\n */\n readonly?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Number of visible text rows\n * @demo\n */\n rows?: number;\n /**\n * Maximum number of characters allowed\n * @demo\n */\n maxlength?: number;\n /**\n * Automatically grow the textarea height to fit content\n * @demo\n */\n autoGrow?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n readonly: false,\n required: false,\n errors: () => [],\n rows: 4,\n maxlength: undefined,\n autoGrow: false,\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n\nconst id = useId();\nconst { attrs, isCustomElement, forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\nconst inputId = computed(() => {\n if (isCustomElement) {\n return id;\n }\n return (attrs.id as string) || id;\n});\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [\n {\n was: string | null | undefined;\n to: string | null;\n },\n ];\n}>();\n\nconst textareaRef = useTemplateRef<HTMLTextAreaElement>(\"textareaEl\");\n\nfunction adjustHeight() {\n const el = textareaRef.value;\n if (!el) return;\n el.style.height = \"auto\";\n el.style.height = `${el.scrollHeight}px`;\n}\n\nwatch(\n () => model.value,\n () => {\n if (props.autoGrow) {\n nextTick(adjustHeight);\n }\n },\n);\n\nfunction emitChangeIfNeeded(val: string | null) {\n if (val !== model.value) {\n const prev = model.value;\n model.value = val;\n emit(\"change\", { was: prev, to: val });\n }\n}\n\nfunction onInput(e: Event) {\n const value = (e.target as HTMLTextAreaElement).value;\n model.value = value;\n if (props.autoGrow) {\n adjustHeight();\n }\n}\n\nfunction onBlur(e: FocusEvent) {\n emitChangeIfNeeded((e.target as HTMLTextAreaElement).value);\n}\n\nfunction onPaste(e: ClipboardEvent) {\n setTimeout(() => {\n const value = (e.target as HTMLTextAreaElement).value;\n emitChangeIfNeeded(value);\n if (props.autoGrow) {\n adjustHeight();\n }\n }, 0);\n}\n</script>\n\n<template>\n <div\n class=\"g-textarea-wrap\"\n :class=\"{ 'g-textarea-has-error': hasErrors }\"\n >\n <label\n v-if=\"props.label\"\n :for=\"inputId\"\n class=\"g-textarea-label\"\n >{{ props.label\n }}<span v-if=\"props.required\" class=\"g-textarea-required\" aria-hidden=\"true\"> *</span></label\n >\n <div\n v-if=\"$slots.instructions || instructions\"\n :id=\"'instructions-' + id\"\n class=\"g-textarea-instructions\"\n >\n <slot name=\"instructions\">{{ instructions }}</slot>\n </div>\n <textarea\n ref=\"textareaEl\"\n :value=\"model ?? ''\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :readonly=\"props.readonly\"\n :required=\"props.required\"\n :rows=\"props.rows\"\n :maxlength=\"props.maxlength ?? undefined\"\n class=\"g-textarea\"\n v-bind=\"{\n ...forwardedAttrs,\n id: inputId,\n 'aria-describedby':\n $slots.instructions || instructions\n ? 'instructions-' + id\n : undefined,\n 'aria-errormessage': hasErrors\n ? 'error-message-' + id\n : undefined,\n }\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n @input=\"onInput\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n />\n <div\n v-if=\"props.maxlength !== undefined\"\n class=\"g-textarea-char-count\"\n aria-live=\"polite\"\n >\n {{ (model ?? '').length }} / {{ props.maxlength }}\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + id\"\n />\n </div>\n</template>\n\n<style>\ng-textarea {\n display: block;\n}\n.g-textarea-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n.g-textarea-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n\n.g-textarea-required {\n color: var(--g-danger-600);\n}\n\n.g-textarea-instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n}\n\n.g-textarea {\n width: 100%;\n padding: 0.5em;\n font-size: 1em;\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n background: var(--g-surface-0);\n color: var(--g-surface-950);\n font-family: var(--il-font-sans);\n resize: vertical;\n box-sizing: border-box;\n}\n\n.g-textarea:focus {\n outline: 2px solid var(--g-primary-500);\n box-shadow: 0 0 0 2px var(--g-info-200);\n outline-offset: 2px;\n}\n\n.g-textarea-has-error .g-textarea {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-textarea:disabled {\n background: var(--g-surface-100);\n color: var(--g-surface-700);\n cursor: not-allowed;\n}\n\n.g-textarea[readonly] {\n background: var(--g-surface-50);\n color: var(--g-surface-800);\n}\n\n.g-textarea-char-count {\n font-size: 0.875em;\n color: var(--g-surface-700);\n text-align: right;\n margin-top: 0.25em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A multi-select combobox that allows selecting multiple values with\n * optional search/filter support.\n *\n * Selected values are displayed as removable chips inside the control.\n * The dropdown listbox shows all (or filtered) options with a checkmark\n * next to each selected option.\n *\n * The `options` prop accepts an array of strings or `{ label, value }`\n * objects. The `v-model` binds to an array of `string | number` values.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Keyboard navigation:\n * - `Down Arrow` / `Up Arrow`: move through options (opens menu if closed)\n * - `Enter`: toggle the focused option\n * - `Space`: toggle the focused option when the search field is empty\n * - `Escape`: close the dropdown\n * - `Home` / `End`: jump to first / last option\n * - `Backspace`: remove the last chip when the search field is empty\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n nextTick,\n ref,\n toRef,\n useId,\n} from \"vue\";\nimport { useSelectDropdown, normalizeSelectOptions, type SelectOption } from \"../compose/useSelectDropdown.ts\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype Props = {\n /**\n * List of options to choose from\n */\n options: Array<string | SelectOption>;\n /**\n * Accessible label\n * @demo Select Fruits\n */\n label: string;\n /**\n * Hide the label visually\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Placeholder text shown when no values are selected\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions shown below the label\n * @demo\n */\n instructions?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n hiddenLabel: false,\n required: false,\n errors: () => [],\n});\n\nconst emit = defineEmits<{\n change: [value: Array<string | number>];\n}>();\n\nconst model = defineModel<Array<string | number>>({ default: () => [] });\n\nconst baseId = useId();\nconst inputRef = ref<HTMLInputElement | null>(null);\nconst controlRef = ref<HTMLElement | null>(null);\nconst listboxRef = ref<HTMLElement | null>(null);\n\nconst open = ref(false);\nconst activeIndex = ref(0);\nconst searchQuery = ref(\"\");\nconst ignoreBlur = ref(false);\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst { menuPlacement, menuStyle, isTop, scrollOptionIntoView } = useSelectDropdown({\n open,\n anchorRef: controlRef,\n listboxRef,\n baseId,\n activeIndex,\n});\n\nconst normalizedOptions = computed<SelectOption[]>(() =>\n normalizeSelectOptions(props.options),\n);\n\nconst filteredOptions = computed<SelectOption[]>(() => {\n if (!searchQuery.value) return normalizedOptions.value;\n const q = searchQuery.value.toLowerCase();\n return normalizedOptions.value.filter((opt) =>\n opt.label.toLowerCase().includes(q),\n );\n});\n\nfunction isSelected(value: string | number): boolean {\n return model.value.includes(value);\n}\n\nfunction labelForValue(value: string | number): string {\n const opt = normalizedOptions.value.find((o) => o.value === value);\n return opt ? opt.label : String(value);\n}\n\n// ----- Open / close -----\n\nfunction openMenu() {\n if (props.disabled) return;\n open.value = true;\n activeIndex.value = 0;\n}\n\nfunction closeMenu() {\n open.value = false;\n searchQuery.value = \"\";\n}\n\n// ----- Option interaction -----\n\nfunction toggleOption(idx: number) {\n const opt = filteredOptions.value[idx];\n if (!opt) return;\n const current = model.value;\n const next: Array<string | number> = isSelected(opt.value)\n ? current.filter((v) => v !== opt.value)\n : [...current, opt.value];\n model.value = next;\n emit(\"change\", next);\n nextTick(() => inputRef.value?.focus());\n}\n\nfunction removeValue(value: string | number) {\n if (props.disabled) return;\n const next = model.value.filter((v) => v !== value);\n model.value = next;\n emit(\"change\", next);\n}\n\n// ----- Event handlers -----\n\nfunction onControlClick() {\n if (props.disabled) return;\n inputRef.value?.focus();\n if (!open.value) openMenu();\n}\n\nfunction onInput(e: Event) {\n searchQuery.value = (e.target as HTMLInputElement).value;\n activeIndex.value = 0;\n if (!open.value) openMenu();\n}\n\nfunction onFocus() {\n if (props.disabled) return;\n if (!open.value) openMenu();\n}\n\nfunction onBlur(e: FocusEvent) {\n if (ignoreBlur.value) {\n ignoreBlur.value = false;\n return;\n }\n const relatedTarget = e.relatedTarget as HTMLElement | null;\n if (relatedTarget && listboxRef.value?.contains(relatedTarget)) {\n return;\n }\n if (relatedTarget && controlRef.value?.contains(relatedTarget)) {\n return;\n }\n closeMenu();\n}\n\nfunction onOptionMouseDown(e: MouseEvent) {\n e.preventDefault();\n //ignoreBlur.value = true;\n}\n\nfunction onChipMouseDown() {\n ignoreBlur.value = true;\n}\n\nfunction onKeydown(e: KeyboardEvent) {\n if (props.disabled) return;\n const max = filteredOptions.value.length - 1;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.min(max, activeIndex.value + 1);\n scrollOptionIntoView();\n }\n break;\n case \"ArrowUp\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.max(0, activeIndex.value - 1);\n scrollOptionIntoView();\n }\n break;\n case \"Home\":\n e.preventDefault();\n activeIndex.value = 0;\n scrollOptionIntoView();\n break;\n case \"End\":\n e.preventDefault();\n activeIndex.value = max;\n scrollOptionIntoView();\n break;\n case \"Enter\":\n e.preventDefault();\n if (open.value && filteredOptions.value.length > 0) {\n toggleOption(activeIndex.value);\n } else if (!open.value) {\n openMenu();\n }\n break;\n case \" \":\n if (open.value && !searchQuery.value) {\n e.preventDefault();\n if (filteredOptions.value.length > 0) {\n toggleOption(activeIndex.value);\n }\n }\n break;\n case \"Escape\":\n if (isTop.value) {\n e.preventDefault();\n setTimeout(() => closeMenu(), 0);\n }\n break;\n case \"Backspace\":\n if (!searchQuery.value && model.value.length > 0) {\n removeValue(model.value[model.value.length - 1]);\n }\n break;\n }\n}\n\n// ----- IDs -----\n\nconst inputId = computed(() => `${baseId}-input`);\nconst labelId = computed(() => `${baseId}-label`);\nconst instructionsId = computed(() => `${baseId}-instructions`);\nconst errorId = computed(() => `error-message-${baseId}`);\n\nconst describedBy = computed(() => {\n const parts: string[] = [];\n if (props.instructions) parts.push(instructionsId.value);\n if (hasErrors.value) parts.push(errorId.value);\n return parts.length > 0 ? parts.join(\" \") : undefined;\n});\n</script>\n\n<template>\n <div\n class=\"g-multiselect-root\"\n :class=\"{\n 'g-multiselect-open': open,\n 'g-multiselect-has-error': hasErrors,\n }\"\n >\n <label v-if=\"!hiddenLabel\" :id=\"labelId\" :for=\"inputId\" class=\"g-multiselect-label\">\n {{ label }}<span v-if=\"required\" class=\"g-multiselect-required\" aria-hidden=\"true\"> *</span>\n </label>\n <div\n v-if=\"instructions\"\n :id=\"instructionsId\"\n class=\"g-multiselect-instructions\"\n >\n {{ instructions }}\n </div>\n <div\n ref=\"controlRef\"\n class=\"g-multiselect-control\"\n :class=\"{ 'g-multiselect-control--disabled': disabled }\"\n @click=\"onControlClick\"\n >\n <ul class=\"g-multiselect-chips\" :aria-labelledby=\"labelId\">\n <li v-for=\"val in model\" :key=\"val\" class=\"g-multiselect-chip\">\n <span class=\"g-multiselect-chip-label\">{{\n labelForValue(val)\n }}</span>\n <button\n type=\"button\"\n class=\"g-multiselect-chip-remove\"\n :aria-label=\"`Remove ${labelForValue(val)}`\"\n :disabled=\"disabled\"\n @mousedown=\"onChipMouseDown\"\n @click.stop=\"removeValue(val)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1em\"\n role=\"none presentation\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n </li>\n </ul>\n\n <input\n ref=\"inputRef\"\n type=\"text\"\n role=\"combobox\"\n class=\"g-multiselect-input\"\n :id=\"inputId\"\n :value=\"searchQuery\"\n :placeholder=\"model.length === 0 ? placeholder : undefined\"\n :disabled=\"disabled\"\n autocomplete=\"off\"\n aria-autocomplete=\"list\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n aria-haspopup=\"listbox\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-activedescendant=\"\n open && filteredOptions.length > 0\n ? baseId + '-option-' + activeIndex\n : undefined\n \"\n v-bind=\"hiddenLabel ? { 'aria-label': label } : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"required ? 'true' : undefined\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n />\n\n <svg\n class=\"g-multiselect-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n\n <div\n v-show=\"open\"\n ref=\"listboxRef\"\n :id=\"baseId + '-listbox'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': label }\n : { 'aria-labelledby': labelId }\n \"\n class=\"g-multiselect-listbox\"\n :class=\"{\n 'g-multiselect-listbox--above': menuPlacement === 'above',\n }\"\n :style=\"menuStyle\"\n tabindex=\"-1\"\n >\n <template v-if=\"filteredOptions.length > 0\">\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"opt.value\"\n :id=\"baseId + '-option-' + idx\"\n role=\"option\"\n class=\"g-multiselect-option\"\n :class=\"{\n 'g-multiselect-option--active': idx === activeIndex,\n 'g-multiselect-option--selected': isSelected(opt.value),\n }\"\n :aria-selected=\"isSelected(opt.value) ? 'true' : 'false'\"\n @mousedown=\"onOptionMouseDown\"\n @click=\"toggleOption(idx)\"\n >\n <span class=\"g-multiselect-option-check\" aria-hidden=\"true\">\n <svg\n v-if=\"isSelected(opt.value)\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n width=\"1.25em\"\n >\n <!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n d=\"M530.8 134.1C545.1 144.5 548.3 164.5 537.9 178.8L281.9 530.8C276.4 538.4 267.9 543.1 258.5 543.9C249.1 544.7 240 541.2 233.4 534.6L105.4 406.6C92.9 394.1 92.9 373.8 105.4 361.3C117.9 348.8 138.2 348.8 150.7 361.3L252.2 462.8L486.2 141.1C496.6 126.8 516.6 123.6 530.9 134z\"\n />\n </svg>\n </span>\n {{ opt.label }}\n </div>\n </template>\n <template v-else>\n <div\n aria-live=\"polite\"\n class=\"g-multiselect-option g-multiselect-no-results\"\n >\n No results found.\n </div>\n </template>\n </div>\n </div>\n\n\n <GFormErrorMessages :errors=\"displayErrors\" :id=\"errorId\" />\n </div>\n</template>\n\n<style>\ng-multi-select {\n display: block;\n}\n.g-multiselect-root {\n position: relative;\n font-size: 1rem;\n}\n\n.g-multiselect-label {\n font-weight: 700;\n color: var(--g-surface-900);\n margin-bottom: 0.5em;\n}\n\n.g-multiselect-required {\n color: var(--g-danger-600);\n}\n\n.g-multiselect-instructions {\n margin: 0 0 0.5em 0;\n color: var(--g-surface-800);\n}\n\n.g-multiselect-control {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n list-style: none;\n gap: 0.375em;\n min-height: calc(0.25em + 1.875em + 0.25em + 4px);\n padding: 0.3em 2.25em 0.3em 0.5em;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-radius: var(--g-border-radius-m);\n cursor: text;\n position: relative;\n box-sizing: border-box;\n\n &:focus-within {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 1px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-multiselect-control--disabled {\n cursor: not-allowed;\n border-color: var(--g-surface-400);\n}\n\n.g-multiselect-control--disabled .g-multiselect-input {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.g-multiselect-control--disabled .g-multiselect-caret {\n opacity: 0.6;\n}\n\n.g-multiselect-has-error .g-multiselect-control {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-multiselect-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 0.375em;\n list-style: none;\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\n.g-multiselect-chip {\n display: inline-flex;\n align-items: center;\n gap: 0.2em;\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n border-radius: 1em;\n padding: 0.1em 0.4em 0.1em 0.6em;\n line-height: 1.5;\n max-width: 100%;\n}\n\n.g-multiselect-chip-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.g-multiselect-chip-remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: none;\n border: none;\n color: inherit;\n cursor: pointer;\n padding: 0.15em;\n box-sizing: border-box;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n flex-shrink: 0;\n line-height: 1;\n\n &:hover {\n background: rgba(0, 0, 0, 0.15);\n }\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.7;\n }\n}\n\n.g-multiselect-input {\n flex: 1;\n min-width: 4em;\n border: none;\n background: transparent;\n font-family: var(--il-font-sans);\n font-size: 1em;\n line-height: 1.875em;\n padding: 0;\n outline: none;\n color: var(--g-surface-900);\n cursor: text;\n\n &:disabled {\n cursor: not-allowed;\n }\n\n &::placeholder {\n color: var(--g-surface-600);\n }\n}\n\n.g-multiselect-caret {\n position: absolute;\n right: 0.5em;\n top: calc(50% - 0.55em);\n color: var(--g-accent-700);\n pointer-events: none;\n transform: rotate(90deg);\n transition: transform 0.15s ease;\n}\n\n.g-multiselect-open .g-multiselect-caret {\n transform: rotate(-90deg);\n}\n\n.g-multiselect-listbox {\n position: absolute;\n left: 0;\n top: 100%;\n width: 100%;\n z-index: 1000;\n background-color: var(--g-surface-0);\n border: 2px solid var(--g-surface-700);\n border-radius: 0 0 var(--g-border-radius-m) var(--g-border-radius-m);\n box-shadow:\n 0 4px 4px rgba(0, 0, 0, 0.2),\n 0 1px 0 1px rgba(0, 0, 0, 0.18);\n max-height: 50vh;\n overflow-y: auto;\n box-sizing: border-box;\n display: none;\n}\n\n.g-multiselect-open .g-multiselect-listbox {\n display: block;\n}\n\n.g-multiselect-listbox--above {\n top: auto;\n bottom: 100%;\n border-radius: var(--g-border-radius-m) var(--g-border-radius-m) 0 0;\n}\n\n.g-multiselect-option {\n display: flex;\n align-items: center;\n gap: 0.5em;\n padding: 0.5em 0.5em;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n border-color: var(--g-accent-700);\n }\n}\n\n.g-multiselect-option--active {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n\n &:hover {\n color: var(--g-primary-text);\n }\n}\n\n.g-multiselect-option-check {\n display: block;\n align-items: center;\n justify-content: center;\n width: 1.25em;\n flex-shrink: 0;\n}\n.g-multiselect-option-check svg {\n display: block;\n fill: currentColor;\n}\n\n.g-multiselect-no-results {\n padding: 0.25em 1em;\n text-align: center;\n color: var(--g-surface-900);\n font-style: italic;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A multi-select combobox that allows selecting multiple values with\n * optional search/filter support.\n *\n * Selected values are displayed as removable chips inside the control.\n * The dropdown listbox shows all (or filtered) options with a checkmark\n * next to each selected option.\n *\n * The `options` prop accepts an array of strings or `{ label, value }`\n * objects. The `v-model` binds to an array of `string | number` values.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Keyboard navigation:\n * - `Down Arrow` / `Up Arrow`: move through options (opens menu if closed)\n * - `Enter`: toggle the focused option\n * - `Space`: toggle the focused option when the search field is empty\n * - `Escape`: close the dropdown\n * - `Home` / `End`: jump to first / last option\n * - `Backspace`: remove the last chip when the search field is empty\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n nextTick,\n ref,\n toRef,\n useId,\n} from \"vue\";\nimport { useSelectDropdown, normalizeSelectOptions, type SelectOption } from \"../compose/useSelectDropdown.ts\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype Props = {\n /**\n * List of options to choose from\n */\n options: Array<string | SelectOption>;\n /**\n * Accessible label\n * @demo Select Fruits\n */\n label: string;\n /**\n * Hide the label visually\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Placeholder text shown when no values are selected\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions shown below the label\n * @demo\n */\n instructions?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n hiddenLabel: false,\n required: false,\n errors: () => [],\n});\n\nconst emit = defineEmits<{\n change: [value: Array<string | number>];\n}>();\n\nconst model = defineModel<Array<string | number>>({ default: () => [] });\n\nconst baseId = useId();\nconst inputRef = ref<HTMLInputElement | null>(null);\nconst controlRef = ref<HTMLElement | null>(null);\nconst listboxRef = ref<HTMLElement | null>(null);\n\nconst open = ref(false);\nconst activeIndex = ref(0);\nconst searchQuery = ref(\"\");\nconst ignoreBlur = ref(false);\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst { menuPlacement, menuStyle, isTop, scrollOptionIntoView } = useSelectDropdown({\n open,\n anchorRef: controlRef,\n listboxRef,\n baseId,\n activeIndex,\n});\n\nconst normalizedOptions = computed<SelectOption[]>(() =>\n normalizeSelectOptions(props.options),\n);\n\nconst filteredOptions = computed<SelectOption[]>(() => {\n if (!searchQuery.value) return normalizedOptions.value;\n const q = searchQuery.value.toLowerCase();\n return normalizedOptions.value.filter((opt) =>\n opt.label.toLowerCase().includes(q),\n );\n});\n\nfunction isSelected(value: string | number): boolean {\n return model.value.includes(value);\n}\n\nfunction labelForValue(value: string | number): string {\n const opt = normalizedOptions.value.find((o) => o.value === value);\n return opt ? opt.label : String(value);\n}\n\n// ----- Open / close -----\n\nfunction openMenu() {\n if (props.disabled) return;\n open.value = true;\n activeIndex.value = 0;\n}\n\nfunction closeMenu() {\n open.value = false;\n searchQuery.value = \"\";\n}\n\n// ----- Option interaction -----\n\nfunction toggleOption(idx: number) {\n const opt = filteredOptions.value[idx];\n if (!opt) return;\n const current = model.value;\n const next: Array<string | number> = isSelected(opt.value)\n ? current.filter((v) => v !== opt.value)\n : [...current, opt.value];\n model.value = next;\n emit(\"change\", next);\n nextTick(() => inputRef.value?.focus());\n}\n\nfunction removeValue(value: string | number) {\n if (props.disabled) return;\n const next = model.value.filter((v) => v !== value);\n model.value = next;\n emit(\"change\", next);\n}\n\n// ----- Event handlers -----\n\nfunction onControlClick() {\n if (props.disabled) return;\n inputRef.value?.focus();\n if (!open.value) openMenu();\n}\n\nfunction onInput(e: Event) {\n searchQuery.value = (e.target as HTMLInputElement).value;\n activeIndex.value = 0;\n if (!open.value) openMenu();\n}\n\nfunction onFocus() {\n if (props.disabled) return;\n if (!open.value) openMenu();\n}\n\nfunction onBlur(e: FocusEvent) {\n if (ignoreBlur.value) {\n ignoreBlur.value = false;\n return;\n }\n const relatedTarget = e.relatedTarget as HTMLElement | null;\n if (relatedTarget && listboxRef.value?.contains(relatedTarget)) {\n return;\n }\n if (relatedTarget && controlRef.value?.contains(relatedTarget)) {\n return;\n }\n closeMenu();\n}\n\nfunction onOptionMouseDown(e: MouseEvent) {\n e.preventDefault();\n //ignoreBlur.value = true;\n}\n\nfunction onChipMouseDown() {\n ignoreBlur.value = true;\n}\n\nfunction onKeydown(e: KeyboardEvent) {\n if (props.disabled) return;\n const max = filteredOptions.value.length - 1;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.min(max, activeIndex.value + 1);\n scrollOptionIntoView();\n }\n break;\n case \"ArrowUp\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.max(0, activeIndex.value - 1);\n scrollOptionIntoView();\n }\n break;\n case \"Home\":\n e.preventDefault();\n activeIndex.value = 0;\n scrollOptionIntoView();\n break;\n case \"End\":\n e.preventDefault();\n activeIndex.value = max;\n scrollOptionIntoView();\n break;\n case \"Enter\":\n e.preventDefault();\n if (open.value && filteredOptions.value.length > 0) {\n toggleOption(activeIndex.value);\n } else if (!open.value) {\n openMenu();\n }\n break;\n case \" \":\n if (open.value && !searchQuery.value) {\n e.preventDefault();\n if (filteredOptions.value.length > 0) {\n toggleOption(activeIndex.value);\n }\n }\n break;\n case \"Escape\":\n if (isTop.value) {\n e.preventDefault();\n setTimeout(() => closeMenu(), 0);\n }\n break;\n case \"Backspace\":\n if (!searchQuery.value && model.value.length > 0) {\n removeValue(model.value[model.value.length - 1]);\n }\n break;\n }\n}\n\n// ----- IDs -----\n\nconst inputId = computed(() => `${baseId}-input`);\nconst labelId = computed(() => `${baseId}-label`);\nconst instructionsId = computed(() => `${baseId}-instructions`);\nconst errorId = computed(() => `error-message-${baseId}`);\n\nconst describedBy = computed(() => {\n const parts: string[] = [];\n if (props.instructions) parts.push(instructionsId.value);\n if (hasErrors.value) parts.push(errorId.value);\n return parts.length > 0 ? parts.join(\" \") : undefined;\n});\n</script>\n\n<template>\n <div\n class=\"g-multiselect-root\"\n :class=\"{\n 'g-multiselect-open': open,\n 'g-multiselect-has-error': hasErrors,\n }\"\n >\n <label v-if=\"!hiddenLabel\" :id=\"labelId\" :for=\"inputId\" class=\"g-multiselect-label\">\n {{ label }}<span v-if=\"required\" class=\"g-multiselect-required\" aria-hidden=\"true\"> *</span>\n </label>\n <div\n v-if=\"instructions\"\n :id=\"instructionsId\"\n class=\"g-multiselect-instructions\"\n >\n {{ instructions }}\n </div>\n <div\n ref=\"controlRef\"\n class=\"g-multiselect-control\"\n :class=\"{ 'g-multiselect-control--disabled': disabled }\"\n @click=\"onControlClick\"\n >\n <ul class=\"g-multiselect-chips\" :aria-labelledby=\"labelId\">\n <li v-for=\"val in model\" :key=\"val\" class=\"g-multiselect-chip\">\n <span class=\"g-multiselect-chip-label\">{{\n labelForValue(val)\n }}</span>\n <button\n type=\"button\"\n class=\"g-multiselect-chip-remove\"\n :aria-label=\"`Remove ${labelForValue(val)}`\"\n :disabled=\"disabled\"\n @mousedown=\"onChipMouseDown\"\n @click.stop=\"removeValue(val)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1em\"\n role=\"none presentation\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n </li>\n </ul>\n\n <input\n ref=\"inputRef\"\n type=\"text\"\n role=\"combobox\"\n class=\"g-multiselect-input\"\n :id=\"inputId\"\n :value=\"searchQuery\"\n :placeholder=\"model.length === 0 ? placeholder : undefined\"\n :disabled=\"disabled\"\n autocomplete=\"off\"\n aria-autocomplete=\"list\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n aria-haspopup=\"listbox\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-activedescendant=\"\n open && filteredOptions.length > 0\n ? baseId + '-option-' + activeIndex\n : undefined\n \"\n v-bind=\"hiddenLabel ? { 'aria-label': label } : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"required ? 'true' : undefined\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n />\n\n <svg\n class=\"g-multiselect-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n\n <div\n v-show=\"open\"\n ref=\"listboxRef\"\n :id=\"baseId + '-listbox'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': label }\n : { 'aria-labelledby': labelId }\n \"\n class=\"g-multiselect-listbox\"\n :class=\"{\n 'g-multiselect-listbox--above': menuPlacement === 'above',\n }\"\n :style=\"menuStyle\"\n tabindex=\"-1\"\n >\n <template v-if=\"filteredOptions.length > 0\">\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"opt.value\"\n :id=\"baseId + '-option-' + idx\"\n role=\"option\"\n class=\"g-multiselect-option\"\n :class=\"{\n 'g-multiselect-option--active': idx === activeIndex,\n 'g-multiselect-option--selected': isSelected(opt.value),\n }\"\n :aria-selected=\"isSelected(opt.value) ? 'true' : 'false'\"\n @mousedown=\"onOptionMouseDown\"\n @click=\"toggleOption(idx)\"\n >\n <span class=\"g-multiselect-option-check\" aria-hidden=\"true\">\n <svg\n v-if=\"isSelected(opt.value)\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n width=\"1.25em\"\n >\n <!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n d=\"M530.8 134.1C545.1 144.5 548.3 164.5 537.9 178.8L281.9 530.8C276.4 538.4 267.9 543.1 258.5 543.9C249.1 544.7 240 541.2 233.4 534.6L105.4 406.6C92.9 394.1 92.9 373.8 105.4 361.3C117.9 348.8 138.2 348.8 150.7 361.3L252.2 462.8L486.2 141.1C496.6 126.8 516.6 123.6 530.9 134z\"\n />\n </svg>\n </span>\n {{ opt.label }}\n </div>\n </template>\n <template v-else>\n <div\n aria-live=\"polite\"\n class=\"g-multiselect-option g-multiselect-no-results\"\n >\n No results found.\n </div>\n </template>\n </div>\n </div>\n\n\n <GFormErrorMessages :errors=\"displayErrors\" :id=\"errorId\" />\n </div>\n</template>\n\n<style>\ng-multi-select {\n display: block;\n}\n.g-multiselect-root {\n position: relative;\n font-size: 1rem;\n}\n\n.g-multiselect-label {\n font-weight: 700;\n color: var(--g-surface-900);\n margin-bottom: 0.5em;\n}\n\n.g-multiselect-required {\n color: var(--g-danger-600);\n}\n\n.g-multiselect-instructions {\n margin: 0 0 0.5em 0;\n color: var(--g-surface-800);\n}\n\n.g-multiselect-control {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n list-style: none;\n gap: 0.375em;\n min-height: calc(0.25em + 1.875em + 0.25em + 4px);\n padding: 0.3em 2.25em 0.3em 0.5em;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-radius: var(--g-border-radius-m);\n cursor: text;\n position: relative;\n box-sizing: border-box;\n\n &:focus-within {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 1px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-multiselect-control--disabled {\n cursor: not-allowed;\n border-color: var(--g-surface-400);\n}\n\n.g-multiselect-control--disabled .g-multiselect-input {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.g-multiselect-control--disabled .g-multiselect-caret {\n opacity: 0.6;\n}\n\n.g-multiselect-has-error .g-multiselect-control {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-multiselect-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 0.375em;\n list-style: none;\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\n.g-multiselect-chip {\n display: inline-flex;\n align-items: center;\n gap: 0.2em;\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n border-radius: 1em;\n padding: 0.1em 0.4em 0.1em 0.6em;\n line-height: 1.5;\n max-width: 100%;\n}\n\n.g-multiselect-chip-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.g-multiselect-chip-remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: none;\n border: none;\n color: inherit;\n cursor: pointer;\n padding: 0.15em;\n box-sizing: border-box;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n flex-shrink: 0;\n line-height: 1;\n\n &:hover {\n background: rgba(0, 0, 0, 0.15);\n }\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.7;\n }\n}\n\n.g-multiselect-input {\n flex: 1;\n min-width: 4em;\n border: none;\n background: transparent;\n font-family: var(--il-font-sans);\n font-size: 1em;\n line-height: 1.875em;\n padding: 0;\n outline: none;\n color: var(--g-surface-900);\n cursor: text;\n\n &:disabled {\n cursor: not-allowed;\n }\n\n &::placeholder {\n color: var(--g-surface-600);\n }\n}\n\n.g-multiselect-caret {\n position: absolute;\n right: 0.5em;\n top: calc(50% - 0.55em);\n color: var(--g-accent-700);\n pointer-events: none;\n transform: rotate(90deg);\n transition: transform 0.15s ease;\n}\n\n.g-multiselect-open .g-multiselect-caret {\n transform: rotate(-90deg);\n}\n\n.g-multiselect-listbox {\n position: absolute;\n left: 0;\n top: 100%;\n width: 100%;\n z-index: 1000;\n background-color: var(--g-surface-0);\n border: 2px solid var(--g-surface-700);\n border-radius: 0 0 var(--g-border-radius-m) var(--g-border-radius-m);\n box-shadow:\n 0 4px 4px rgba(0, 0, 0, 0.2),\n 0 1px 0 1px rgba(0, 0, 0, 0.18);\n max-height: 50vh;\n overflow-y: auto;\n box-sizing: border-box;\n display: none;\n}\n\n.g-multiselect-open .g-multiselect-listbox {\n display: block;\n}\n\n.g-multiselect-listbox--above {\n top: auto;\n bottom: 100%;\n border-radius: var(--g-border-radius-m) var(--g-border-radius-m) 0 0;\n}\n\n.g-multiselect-option {\n display: flex;\n align-items: center;\n gap: 0.5em;\n padding: 0.5em 0.5em;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n border-color: var(--g-accent-700);\n }\n}\n\n.g-multiselect-option--active {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n\n &:hover {\n color: var(--g-primary-text);\n }\n}\n\n.g-multiselect-option-check {\n display: block;\n align-items: center;\n justify-content: center;\n width: 1.25em;\n flex-shrink: 0;\n}\n.g-multiselect-option-check svg {\n display: block;\n fill: currentColor;\n}\n\n.g-multiselect-no-results {\n padding: 0.25em 1em;\n text-align: center;\n color: var(--g-surface-900);\n font-style: italic;\n}\n</style>\n\n","import { unrefElement, useIntersectionObserver, useMutationObserver } from \"@vueuse/core\";\nimport { ref, Ref } from \"vue\";\n\n/**\n * Monitor a list of elements' intersection with the viewport to update active links.\n *\n * This updates the activeLink store with the active content's ID for use in menus.\n *\n * @param element Children of this element will be observed\n * @param topOffset Offset from the top of the window to consider not visible\n * @param activeId Ref to store the active element ID\n */\nexport function useActiveLinkContent(\n element: Ref<HTMLElement | null>,\n topOffset: number,\n activeId: Ref<string>,\n) {\n const thresholds = [0, 0.25, 0.5, 0.75, 1];\n const rootMargin = `${-topOffset}px 0px 0px 0px`;\n // Track most visible section in viewport\n const visibility = new Map<Element, number>();\n\n // Get the children elements to observe\n const elements = ref<HTMLElement[]>(\n Array.from(unrefElement(element)?.children || []) as HTMLElement[],\n );\n\n // To maintain reactivity, observe for changes in child elements.\n // This works better with Nuxt's rendering than passing an array of refs.\n useMutationObserver(\n element,\n () => {\n elements.value = Array.from(\n unrefElement(element)?.children || [],\n ) as HTMLElement[];\n },\n { childList: true },\n );\n\n const { stop } = useIntersectionObserver(\n elements,\n (entries) => {\n const lastElement = elements.value[elements.value.length - 1];\n\n for (const entry of entries) {\n // Use intersection ratio as visibility score\n // Round up to 2 decimal places since it can sometimes be 0.99x\n // when jumping to it with anchor links.\n visibility.set(\n entry.target,\n entry.isIntersecting ? Math.ceil(100 * entry.intersectionRatio)/100 : 0,\n );\n if (\n entry.target === lastElement &&\n entry.intersectionRatio === 1\n ) {\n // If last element is fully visible, prioritize that\n visibility.set(entry.target, Number.POSITIVE_INFINITY);\n }\n }\n // Pick the element with highest visibility\n let bestEl: Element | null = null;\n let bestScore = 0;\n for (const el of visibility.keys()) {\n const score = visibility.get(el) || 0;\n if (score <= bestScore) {\n continue;\n }\n const rect = (el as HTMLElement).getBoundingClientRect();\n bestEl = el;\n bestScore = score;\n }\n if (bestEl instanceof HTMLElement) {\n activeId.value = bestEl.id;\n } else {\n activeId.value = \"\";\n }\n },\n {\n threshold: thresholds,\n root: null,\n rootMargin,\n immediate: true,\n },\n );\n\n return { stop };\n}\n","import { onBeforeUnmount, onMounted, Ref, ref, useId, watch } from \"vue\";\nimport { useMediaQuery } from \"@vueuse/core\";\n\n/**\n * Composable to manage sidebar state and link components together.\n *\n * @param breakpoint Media query string for when the sidebar should be collapsible\n */\nexport function useSidebar(\n breakpoint: Ref<string> | string = \"(max-width: 800px)\",\n) {\n const id = useId();\n const open = ref(false);\n const isCollapsible: Ref<boolean> = useMediaQuery(breakpoint, {\n ssrWidth: 1000\n });\n\n function onDocumentClick(e: MouseEvent) {\n if (!isCollapsible.value || !open.value) {\n return;\n }\n const target = e.target as HTMLElement;\n const sidebarEl = document.getElementById(`${id}-sidebar`);\n if (!sidebarEl) {\n return;\n }\n if (sidebarEl.contains(target)) {\n return;\n }\n // Very slight delay means if they click the menu button to close it,\n // it won't re-open\n setTimeout(() => {\n open.value = false;\n }, 5);\n }\n function onDocumentFocus(e: FocusEvent) {\n if (!isCollapsible.value || !open.value) {\n return;\n }\n const target = e.target as HTMLElement;\n const sidebarEl = document.getElementById(`${id}-sidebar`);\n const hamburgerEl = document.getElementById(`${id}-hamburger`);\n if (!sidebarEl) {\n return;\n }\n if (sidebarEl.contains(target) || hamburgerEl?.contains(target)) {\n return;\n }\n // Very slight delay means if they click the menu button to close it,\n // it won't re-open\n setTimeout(() => {\n open.value = false;\n }, 5);\n }\n\n onMounted(() => {\n watch(\n isCollapsible,\n (val) => {\n if (val) {\n document.addEventListener(\"mousedown\", onDocumentClick);\n document.addEventListener(\"focusin\", onDocumentFocus);\n } else {\n document.removeEventListener(\"mousedown\", onDocumentClick);\n document.removeEventListener(\"focusin\", onDocumentFocus);\n }\n },\n { immediate: true },\n );\n });\n\n onBeforeUnmount(() => {\n document.removeEventListener(\"mousedown\", onDocumentClick);\n document.removeEventListener(\"focusin\", onDocumentFocus);\n });\n\n return {\n id,\n open,\n isCollapsible,\n toggle: () => (open.value = !open.value),\n };\n}\n","import { computed, ComputedRef, shallowReactive } from \"vue\";\nimport { TableColumn, TableRow } from \"../components/table/TableColumn.ts\";\nimport { createEventHook, EventHook, EventHookOn } from \"@vueuse/core\";\n\nexport type ColumnKey<R extends TableRow> = Extract<keyof R, string>;\n\n/**\n * Represents a single cell change in the table\n */\nexport interface CellChange<\n R extends TableRow = TableRow,\n K extends ColumnKey<R> = ColumnKey<R>,\n> {\n rowKey: string;\n columnKey: K;\n row: R;\n /**\n * Represents the original value before user changes.\n */\n previousValue: R[K];\n /**\n * New value from the change. This will be null in events\n * if the new value is the same as the original.\n */\n newValue: R[K];\n error?: string;\n}\n\nexport interface CellChangeEvent<\n R extends TableRow = TableRow,\n K extends ColumnKey<R> = ColumnKey<R>,\n> {\n rowKey: string;\n columnKey: K;\n row: R;\n previousValue: R[K];\n newValue: R[K] | null;\n error?: string;\n}\n\n/**\n * Payload for the `trackChange` function\n */\nexport interface CellChangePayload<\n T extends TableRow = TableRow,\n K extends ColumnKey<T> = ColumnKey<T>,\n> {\n row: T;\n column: TableColumn<T, K>;\n value: T[K];\n previousValue: T[K];\n}\n\n/**\n * Map of changes organized by row key and column key\n */\nexport type ChangeMap<T extends TableRow> = Map<\n string,\n Map<ColumnKey<T>, CellChange<T>>\n>;\n\n/**\n * Return type for the useTableChanges composable\n */\nexport interface UseTableChangesReturn<T extends TableRow> {\n /**\n * Track a change to a cell\n */\n trackChange: <K extends ColumnKey<T>>(payload: CellChangePayload<T, K>) => void;\n\n /**\n * Reactive array of all current changes\n */\n changes: ComputedRef<CellChange<T>[]>;\n\n /**\n * Get changes organized by row\n */\n getChangesByRow: () => Map<string, Partial<T>>;\n\n /**\n * Reactive flag indicating whether there are any changes\n */\n hasChanges: ComputedRef<boolean>;\n\n /**\n * Check if a specific cell has changes\n */\n hasChange: <K extends ColumnKey<T>>(rowKey: string, columnKey: K) => boolean;\n\n /**\n * Get the changed value for a specific cell, or undefined if no change\n */\n getChange: <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ) => T[K] | undefined;\n\n /**\n * Clear all tracked changes\n */\n clearChanges: () => void;\n\n /**\n * Clear changes for a specific row\n */\n clearRowChanges: (rowKey: string) => void;\n\n /**\n * Apply tracked changes to a new dataset (for merging with updated data)\n */\n applyChangesToData: (data: T[]) => T[];\n\n /**\n * Reactive count of changed cells\n */\n changeCount: ComputedRef<number>;\n\n /**\n * Reactive flag indicating whether any cell has an error\n */\n hasErrors: ComputedRef<boolean>;\n\n /**\n * Set an error message for a specific cell\n */\n setError: <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n error: string,\n ) => void;\n\n /**\n * Clear the error for a specific cell\n */\n clearError: <K extends ColumnKey<T>>(rowKey: string, columnKey: K) => void;\n\n /**\n * Get the error message for a specific cell, or undefined if no error\n */\n getError: <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ) => string | undefined;\n\n /**\n * Check if a specific cell has an error\n */\n hasError: <K extends ColumnKey<T>>(rowKey: string, columnKey: K) => boolean;\n\n /**\n * Event emitted when a cell changes.\n *\n * `newValue` will be null if the cell's value is the same as the original.\n */\n onChange: EventHookOn<CellChangeEvent<T>>;\n}\n\n/**\n * Composable for tracking changes to table data\n *\n * This composable helps manage user edits to table data by:\n * - Tracking which cells have been modified\n * - Storing both old and new values\n * - Providing methods to retrieve just the changes (not full data)\n * - Allowing changes to be applied to updated data for real-time syncing\n *\n * @example\n * ```typescript\n * const changes = useTableChanges<ProductRow>();\n *\n * // Track a change when user edits a cell\n * function handleCellChange(row, column, newValue) {\n * changes.trackChange({\n * row,\n * column,\n * value: newValue,\n * previousValue: row[column.key],\n * });\n * }\n *\n * // Get all changes to submit\n * const changedData = changes.changes.value;\n * await api.updateProducts(changedData);\n *\n * // Apply user changes to fresh data from server\n * const freshData = await api.getProducts();\n * const mergedData = changes.applyChangesToData(freshData);\n * ```\n */\nexport function useTableChanges<\n T extends TableRow,\n>(): UseTableChangesReturn<T> {\n // Store changes in a reactive map: rowKey -> columnKey -> CellChange\n const changeMap = shallowReactive<ChangeMap<T>>(new Map());\n\n const changeEvent = createEventHook<CellChangeEvent<T>>();\n\n /**\n * Track a change to a specific cell\n */\n const trackChange = <K extends ColumnKey<T>>(\n payload: CellChangePayload<T, K>,\n ) => {\n const colKey: K = payload.column.key;\n const rowKey = payload.row.key;\n const newValue: T[K] = payload.value;\n const previousValue: T[K] = payload.previousValue;\n\n if (!changeMap.has(rowKey)) {\n changeMap.set(\n rowKey,\n shallowReactive(new Map<ColumnKey<T>, CellChange<T>>()),\n );\n }\n\n const rowChanges = changeMap.get(\n rowKey,\n )!;\n\n // If there's already a change for this cell, preserve the original previousValue\n const existingChange = rowChanges.get(colKey);\n const originalpreviousValue: T[K] = (existingChange\n ? existingChange.previousValue\n : previousValue) as T[K];\n const existingError = existingChange?.error;\n\n // If the new value equals the original value, remove the change\n if (newValue === originalpreviousValue) {\n rowChanges.delete(colKey);\n // Clean up empty row maps\n if (rowChanges.size === 0) {\n changeMap.delete(rowKey);\n }\n // null newValue means the value was reverted to the original\n changeEvent.trigger({\n rowKey,\n columnKey: colKey,\n row: payload.row,\n previousValue: originalpreviousValue,\n newValue: null,\n });\n } else {\n // Store or update the change, preserving error if it exists\n const updatedChange: CellChange<T, K> = {\n rowKey,\n columnKey: colKey,\n row: payload.row,\n previousValue: originalpreviousValue,\n newValue,\n };\n\n // Preserve error if it exists\n if (existingError !== undefined) {\n updatedChange.error = existingError;\n }\n\n rowChanges.set(colKey, updatedChange);\n changeEvent.trigger(updatedChange);\n }\n };\n\n const changes = computed(() => {\n const result: CellChange<T>[] = [];\n changeMap.forEach((rowChanges) => {\n rowChanges.forEach((change) => {\n result.push({ ...change });\n });\n });\n return result;\n });\n\n /**\n * Get changes organized by row key\n * Returns a map of rowKey -> object with changed fields\n */\n const getChangesByRow = (): Map<string, Partial<T>> => {\n const result = new Map<string, Partial<T>>();\n changeMap.forEach((rowChanges, rowKey) => {\n const rowData: Partial<T> = { key: rowKey } as Partial<T>;\n rowChanges.forEach((change, columnKey) => {\n rowData[columnKey] = change.newValue;\n });\n result.set(rowKey, rowData);\n });\n return result;\n };\n\n const hasChanges = computed<boolean>(() => changeMap.size > 0);\n\n /**\n * Check if a specific cell has changes\n */\n const hasChange = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ): boolean => {\n const rowChanges = changeMap.get(rowKey);\n if (!rowChanges) return false;\n return rowChanges.has(columnKey);\n };\n\n /**\n * Get the changed value for a specific cell\n */\n const getChange = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ): T[K] | undefined => {\n const rowChanges = changeMap.get(rowKey);\n if (!rowChanges) return undefined;\n const change = rowChanges.get(columnKey);\n return change?.newValue as T[K] | undefined;\n };\n\n /**\n * Clear all tracked changes\n */\n const clearChanges = () => {\n changeMap.clear();\n };\n\n /**\n * Clear changes for a specific row\n */\n const clearRowChanges = (rowKey: string) => {\n changeMap.delete(rowKey);\n };\n\n /**\n * Apply tracked changes to a new dataset\n * This is useful when you have fresh data from the server but want to preserve user edits\n */\n const applyChangesToData = (data: T[]): T[] => {\n return data.map((row) => {\n const rowChanges = changeMap.get(row.key);\n if (!rowChanges || rowChanges.size === 0) {\n return row;\n }\n\n // Create a new object with the row data and apply changes\n const updatedRow = { ...row };\n rowChanges.forEach((change, columnKey) => {\n updatedRow[columnKey] = change.newValue;\n });\n\n return updatedRow;\n });\n };\n\n const changeCount = computed<number>(() => {\n let count = 0;\n changeMap.forEach((rowChanges) => {\n count += rowChanges.size;\n });\n return count;\n });\n\n const hasErrors = computed<boolean>(() =>\n changes.value.some(\n (change) => change.error !== undefined && change.error !== \"\",\n ),\n );\n\n /**\n * Set an error message for a specific cell\n */\n const setError = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n error: string,\n ) => {\n const rowChanges = changeMap.get(rowKey);\n if (!rowChanges) return;\n\n const change = rowChanges.get(columnKey);\n if (!change) return;\n\n // With shallowReactive(Map), mutating nested objects won't trigger updates.\n // Replace the map entry so Vue can observe the change.\n const updatedChange: CellChange<T> = { ...change, error };\n rowChanges.set(columnKey, updatedChange);\n };\n\n /**\n * Clear the error for a specific cell\n */\n const clearError = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ) => {\n const rowChanges = changeMap.get(rowKey);\n if (!rowChanges) return;\n\n const change = rowChanges.get(columnKey);\n if (!change) return;\n\n // Replace the map entry so Vue can observe the change.\n const updatedChange: CellChange<T> = { ...change };\n delete updatedChange.error;\n rowChanges.set(columnKey, updatedChange);\n };\n\n /**\n * Get the error message for a specific cell\n */\n const getError = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ): string | undefined => {\n const rowChanges = changeMap.get(rowKey);\n if (!rowChanges) return undefined;\n\n const change = rowChanges.get(columnKey);\n return change?.error;\n };\n\n /**\n * Check if a specific cell has an error\n */\n const hasError = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ): boolean => {\n const error = getError(rowKey, columnKey);\n return error !== undefined && error !== \"\";\n };\n\n return {\n trackChange,\n changes,\n getChangesByRow,\n hasChanges,\n hasChange,\n getChange,\n clearChanges,\n clearRowChanges,\n applyChangesToData,\n changeCount,\n hasErrors,\n setError,\n clearError,\n getError,\n hasError,\n onChange: changeEvent.on,\n };\n}\n"],"x_google_ignoreList":[17,18],"mappings":"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"}
1
+ {"version":3,"file":"grad-vue-SX-RHHr3.js","names":["$emit","$slots","$props","$attrs","$attrs","$attrs","$slots"],"sources":["../src/compose/useCustomElementAttrs.ts","../src/components/GButton.vue","../src/components/GButton.vue","../src/components/GTreeMenu.vue","../src/components/GTreeMenu.vue","../src/components/tree-menu/GTreeMenuList.vue","../src/components/tree-menu/GTreeMenuList.vue","../src/components/tree-menu/GTreeMenuItem.vue","../src/components/tree-menu/GTreeMenuItem.vue","../src/compose/useForm.ts","../src/compose/useWebComponentForm.ts","../src/compose/useFormField.ts","../src/components/form/GFormErrorMessages.vue","../src/components/form/GFormErrorMessages.vue","../src/components/GTextInput.vue","../src/components/GTextInput.vue","../src/compose/useOverlayStack.ts","../../../node_modules/@vueuse/shared/dist/index.js","../../../node_modules/@vueuse/integrations/dist/useFocusTrap-lXZ_YG-8.js","../src/compose/useOverlayFocus.ts","../src/compose/useOverlayEscape.ts","../src/compose/popoverPosition.ts","../src/components/GPopover.vue","../src/components/GPopover.vue","../src/compose/tooltipDom.ts","../src/components/GTooltip.vue","../src/components/GTooltip.vue","../src/components/GSelectButton.vue","../src/components/GSelectButton.vue","../src/components/GProgress.vue","../src/components/GProgress.vue","../src/components/GAlertDialog.vue","../src/components/GAlertDialog.vue","../src/compose/useSelectDropdown.ts","../src/components/GSelect.vue","../src/components/GSelect.vue","../src/components/GSearch.vue","../src/components/GSearch.vue","../src/components/GAppHeader.vue","../src/components/GAppHeader.vue","../src/compose/useWebComponentSidebar.ts","../src/components/GSidebar.vue","../src/components/GSidebar.vue","../src/components/GSidebarMenu.vue","../src/components/GSidebarMenu.vue","../src/directives/v-gtooltip.ts","../src/components/GClipboard.vue","../src/components/GClipboard.vue","../src/components/GHistoryScroller.vue","../src/components/GHistoryScroller.vue","../src/components/GThreeWayToggle.vue","../src/components/GThreeWayToggle.vue","../src/components/table/GTableBody.vue","../src/components/table/GTableBody.vue","../src/compose/useFiltering.ts","../src/components/GTable.vue","../src/components/GTable.vue","../src/components/table/GTablePagination.vue","../src/components/table/GTablePagination.vue","../src/components/GModal.vue","../src/components/GModal.vue","../src/components/GHamburgerMenu.vue","../src/components/GHamburgerMenu.vue","../src/components/GDetailList.vue","../src/components/GDetailList.vue","../src/components/detail-list/GDetailListItem.vue","../src/components/detail-list/GDetailListItem.vue","../src/components/GOverlay.vue","../src/components/GOverlay.vue","../src/components/term/GTermSelectorControl.vue","../src/components/term/GTermSelectorControl.vue","../src/components/GTermSelector.vue","../src/components/GTermSelector.vue","../src/components/GUserMenu.vue","../src/components/GUserMenu.vue","../src/components/GCurrencyInput.vue","../src/components/GCurrencyInput.vue","../src/components/GEmailInput.vue","../src/components/GEmailInput.vue","../src/components/GFileInput.vue","../src/components/GFileInput.vue","../src/components/GDateInput.vue","../src/components/GDateInput.vue","../src/components/GDateRangeInput.vue","../src/components/GDateRangeInput.vue","../src/components/GForm.vue","../src/components/GForm.vue","../src/components/GSubmitButton.vue","../src/components/GSubmitButton.vue","../src/components/GCheckboxGroup.vue","../src/components/GCheckboxGroup.vue","../src/components/GTextarea.vue","../src/components/GTextarea.vue","../src/components/GMultiSelect.vue","../src/components/GMultiSelect.vue","../src/compose/useActiveLink.ts","../src/compose/useSidebar.ts","../src/compose/useTableChanges.ts"],"sourcesContent":["import { computed, useAttrs } from \"vue\";\n\ntype UseCustomElementAttrsOptions = {\n omitInCustomElement?: string[];\n};\n\nexport function isCustomElementMode() {\n const globalScope = globalThis as typeof globalThis & {\n __GRAD_VUE_IS_WEB_COMPONENTS_BUILD__?: boolean;\n };\n return globalScope.__GRAD_VUE_IS_WEB_COMPONENTS_BUILD__ === true;\n}\n\nexport function useCustomElementAttrs(options: UseCustomElementAttrsOptions = {}) {\n const attrs = useAttrs();\n const globalScope = globalThis as typeof globalThis & {\n __GRAD_VUE_IS_WEB_COMPONENTS_BUILD__?: boolean;\n };\n const isCustomElement = globalScope.__GRAD_VUE_IS_WEB_COMPONENTS_BUILD__ === true;\n const omitInCustomElement = options.omitInCustomElement ?? [];\n\n const forwardedAttrs = computed(() => {\n if (!isCustomElement || omitInCustomElement.length === 0) {\n return attrs;\n }\n\n const attrObject = attrs as Record<string, unknown>;\n const filtered: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(attrObject)) {\n if (!omitInCustomElement.includes(key)) {\n filtered[key] = value;\n }\n }\n\n return filtered;\n });\n\n return {\n attrs,\n isCustomElement,\n forwardedAttrs,\n };\n}","<script lang=\"ts\">\n/**\n * The element or component can be set with the `component` prop, so it can be\n * a link or `router-link` component from vue-router. For example:\n *\n * ```vue-html\n * <GButton component=\"router-link\" to=\"/some-route\">\n * Click me\n * </GButton>\n * ```\n *\n * Note that grad-vue doesn't include vue-router as a dependency.\n *\n * **Icons** can be added with either the `icon` prop or a named slot `icon`:\n * - Use the `icon` prop to pass an icon class string, e.g., \"fa-solid fa-plus\".\n * - If using the `icon` prop, the icon will be rendered as a span with the `aria-hidden` attribute set to `true`.\n * - Use a named slot `icon` to provide custom icon content.\n * - If both `icon` prop and named slot `icon` are provided, the named slot takes precedence.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Button size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\";\n /**\n * Button color theme\n * @demo\n */\n theme?: \"primary\" | \"secondary\" | \"accent\" | \"danger\" | \"none\";\n /**\n * Use outlined style\n * @demo\n */\n outlined?: boolean;\n /**\n * Use text style\n * @demo\n */\n text?: boolean;\n\n /**\n * The to target for when using the button as a router-link\n */\n to?: string | Record<string, any>;\n\n /**\n * The component to use for the button\n */\n component?: string;\n\n /**\n * Optional icon classes to render an icon span before the label.\n * Example: \"fa-solid fa-plus\" or \"material-symbols:add\".\n * If a named slot `icon` is provided, it takes precedence over this prop.\n */\n icon?: string;\n /**\n * Native button type\n */\n type?: \"button\" | \"submit\" | \"reset\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n theme: \"primary\",\n outlined: false,\n text: false,\n to: undefined,\n component: undefined,\n icon: undefined,\n type: \"button\",\n});\n\nconst slots = defineSlots<{\n default(): any;\n icon?: () => any;\n}>();\n\ndefineEmits([\n \"click\",\n \"focus\",\n \"blur\",\n \"keydown\",\n \"keyup\",\n \"mousedown\",\n \"mouseup\",\n \"mouseenter\",\n \"mouseleave\",\n]);\nconst { forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\n\nconst classes = computed(() => [\n \"g-btn\",\n `g-btn--${props.size}`,\n `g-btn--${props.theme}`,\n {\n \"g-btn--outlined\": props.outlined,\n \"g-btn--text\": props.text,\n \"g-btn--primary\": props.theme === \"primary\",\n \"g-btn--accent\": props.theme === \"accent\",\n \"g-btn-has-text\": props.text,\n \"g-btn-has-icon-class\": props.icon,\n \"g-btn-has-icon-svg\": !!slots.icon,\n },\n]);\n</script>\n\n<template>\n <component\n :is=\"props.component ? props.component : 'button'\"\n v-bind=\"forwardedAttrs\"\n :to=\"props.to\"\n :class=\"classes\"\n :type=\"props.to ? undefined : props.type\"\n @click=\"$emit('click', $event)\"\n @focus=\"$emit('focus', $event)\"\n @blur=\"$emit('blur', $event)\"\n @keydown=\"$emit('keydown', $event)\"\n @keyup=\"$emit('keyup', $event)\"\n @mousedown=\"$emit('mousedown', $event)\"\n @mouseup=\"$emit('mouseup', $event)\"\n @mouseenter=\"$emit('mouseenter', $event)\"\n @mouseleave=\"$emit('mouseleave', $event)\"\n >\n <template v-if=\"icon || slots.icon\">\n <span class=\"g-btn--icon\">\n <slot v-if=\"slots.icon\" name=\"icon\" />\n <span v-else :class=\"icon + ' g-btn--icon-span'\" aria-hidden=\"true\"></span>\n </span>\n <span class=\"g-btn--label\">\n <slot />\n </span>\n </template>\n <template v-else>\n <slot />\n </template>\n </component>\n</template>\n\n<style>\ng-button {\n display: inline-block;\n}\n\n.g-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--il-font-sans);\n font-weight: 700;\n font-size: 19px;\n line-height: 20px;\n border: 2px solid var(--ilw-color--background);\n background: var(--ilw-color--background);\n color: var(--ilw-color--heading);\n cursor: pointer;\n padding: 12px 20px;\n border-radius: var(--g-border-radius-m);\n text-decoration: none;\n\n &:hover {\n color: var(--ilw-color--background);\n background: var(--ilw-color--heading);\n border-color: var(--ilw-color--background);\n text-decoration: underline;\n }\n\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--g-primary-500);\n }\n}\n\n\n.g-btn--small {\n font-size: 14px;\n padding: 6px 10px 7px;\n\n --g-accent-500: var(--il-altgeld);\n}\n\n.g-btn--large {\n font-size: 21px;\n line-height: 24px;\n padding: 16px 24px;\n}\n\n.g-btn-has-icon-class, .g-btn-has-icon-svg {\n gap: 2px;\n padding: 6px 20px 6px 6px;\n\n &.g-btn--small {\n padding: 0 14px 1px 0;\n }\n &.g-btn--large {\n padding: 12px 24px 12px 10px;\n }\n\n &:hover {\n text-decoration: none;\n\n .g-btn--label {\n text-decoration: underline;\n }\n }\n}\n\n.g-btn--icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n overflow: hidden;\n text-decoration: none;\n\n span.g-btn--icon-span {\n max-width: 100%;\n max-height: 100%;\n }\n}\n\n.g-btn--label {\n display: block;\n}\n\n/* Visually balance leading icon by slightly reducing left offset */\n.g-btn > .g-btn--icon:first-child {\n margin-left: -0.1em;\n}\n\n.g-btn--icon > svg,\n.g-btn--icon > img {\n width: 1em;\n height: 1em;\n display: block;\n flex: 0 0 auto;\n}\n\n.g-btn--primary {\n --ilw-color--background: var(--g-primary-500);\n --ilw-color--heading: var(--g-primary-text);\n}\n.g-btn--accent {\n --ilw-color--background: var(--g-accent-500);\n --ilw-color--heading: var(--g-surface-0);\n}\n\n.g-btn--danger {\n --ilw-color--background: var(--g-danger-500);\n --ilw-color--heading: var(--g-danger-text);\n}\n\n.g-btn--secondary {\n --ilw-color--background: var(--g-surface-700);\n --ilw-color--heading: var(--g-surface-100);\n --ilw-color--heading-link-hover: var(--g-surface-900);\n}\n\n.g-btn--outlined {\n color: var(--ilw-color--background);\n background: var(--ilw-color--heading);\n border-color: var(--ilw-color--background);\n\n &:hover {\n background: var(--ilw-color--background);\n color: var(--ilw-color--heading);\n }\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n }\n}\n\n.g-btn--text {\n background: none;\n border: none;\n color: var(--ilw-color--background);\n padding: 0.25em 0.5em; /* lighter padding using ems for consistency */\n &:hover {\n color: var(--ilw-color--heading-link-hover);\n text-decoration: underline;\n }\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * The element or component can be set with the `component` prop, so it can be\n * a link or `router-link` component from vue-router. For example:\n *\n * ```vue-html\n * <GButton component=\"router-link\" to=\"/some-route\">\n * Click me\n * </GButton>\n * ```\n *\n * Note that grad-vue doesn't include vue-router as a dependency.\n *\n * **Icons** can be added with either the `icon` prop or a named slot `icon`:\n * - Use the `icon` prop to pass an icon class string, e.g., \"fa-solid fa-plus\".\n * - If using the `icon` prop, the icon will be rendered as a span with the `aria-hidden` attribute set to `true`.\n * - Use a named slot `icon` to provide custom icon content.\n * - If both `icon` prop and named slot `icon` are provided, the named slot takes precedence.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Button size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\";\n /**\n * Button color theme\n * @demo\n */\n theme?: \"primary\" | \"secondary\" | \"accent\" | \"danger\" | \"none\";\n /**\n * Use outlined style\n * @demo\n */\n outlined?: boolean;\n /**\n * Use text style\n * @demo\n */\n text?: boolean;\n\n /**\n * The to target for when using the button as a router-link\n */\n to?: string | Record<string, any>;\n\n /**\n * The component to use for the button\n */\n component?: string;\n\n /**\n * Optional icon classes to render an icon span before the label.\n * Example: \"fa-solid fa-plus\" or \"material-symbols:add\".\n * If a named slot `icon` is provided, it takes precedence over this prop.\n */\n icon?: string;\n /**\n * Native button type\n */\n type?: \"button\" | \"submit\" | \"reset\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n theme: \"primary\",\n outlined: false,\n text: false,\n to: undefined,\n component: undefined,\n icon: undefined,\n type: \"button\",\n});\n\nconst slots = defineSlots<{\n default(): any;\n icon?: () => any;\n}>();\n\ndefineEmits([\n \"click\",\n \"focus\",\n \"blur\",\n \"keydown\",\n \"keyup\",\n \"mousedown\",\n \"mouseup\",\n \"mouseenter\",\n \"mouseleave\",\n]);\nconst { forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\n\nconst classes = computed(() => [\n \"g-btn\",\n `g-btn--${props.size}`,\n `g-btn--${props.theme}`,\n {\n \"g-btn--outlined\": props.outlined,\n \"g-btn--text\": props.text,\n \"g-btn--primary\": props.theme === \"primary\",\n \"g-btn--accent\": props.theme === \"accent\",\n \"g-btn-has-text\": props.text,\n \"g-btn-has-icon-class\": props.icon,\n \"g-btn-has-icon-svg\": !!slots.icon,\n },\n]);\n</script>\n\n<template>\n <component\n :is=\"props.component ? props.component : 'button'\"\n v-bind=\"forwardedAttrs\"\n :to=\"props.to\"\n :class=\"classes\"\n :type=\"props.to ? undefined : props.type\"\n @click=\"$emit('click', $event)\"\n @focus=\"$emit('focus', $event)\"\n @blur=\"$emit('blur', $event)\"\n @keydown=\"$emit('keydown', $event)\"\n @keyup=\"$emit('keyup', $event)\"\n @mousedown=\"$emit('mousedown', $event)\"\n @mouseup=\"$emit('mouseup', $event)\"\n @mouseenter=\"$emit('mouseenter', $event)\"\n @mouseleave=\"$emit('mouseleave', $event)\"\n >\n <template v-if=\"icon || slots.icon\">\n <span class=\"g-btn--icon\">\n <slot v-if=\"slots.icon\" name=\"icon\" />\n <span v-else :class=\"icon + ' g-btn--icon-span'\" aria-hidden=\"true\"></span>\n </span>\n <span class=\"g-btn--label\">\n <slot />\n </span>\n </template>\n <template v-else>\n <slot />\n </template>\n </component>\n</template>\n\n<style>\ng-button {\n display: inline-block;\n}\n\n.g-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--il-font-sans);\n font-weight: 700;\n font-size: 19px;\n line-height: 20px;\n border: 2px solid var(--ilw-color--background);\n background: var(--ilw-color--background);\n color: var(--ilw-color--heading);\n cursor: pointer;\n padding: 12px 20px;\n border-radius: var(--g-border-radius-m);\n text-decoration: none;\n\n &:hover {\n color: var(--ilw-color--background);\n background: var(--ilw-color--heading);\n border-color: var(--ilw-color--background);\n text-decoration: underline;\n }\n\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--g-primary-500);\n }\n}\n\n\n.g-btn--small {\n font-size: 14px;\n padding: 6px 10px 7px;\n\n --g-accent-500: var(--il-altgeld);\n}\n\n.g-btn--large {\n font-size: 21px;\n line-height: 24px;\n padding: 16px 24px;\n}\n\n.g-btn-has-icon-class, .g-btn-has-icon-svg {\n gap: 2px;\n padding: 6px 20px 6px 6px;\n\n &.g-btn--small {\n padding: 0 14px 1px 0;\n }\n &.g-btn--large {\n padding: 12px 24px 12px 10px;\n }\n\n &:hover {\n text-decoration: none;\n\n .g-btn--label {\n text-decoration: underline;\n }\n }\n}\n\n.g-btn--icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n overflow: hidden;\n text-decoration: none;\n\n span.g-btn--icon-span {\n max-width: 100%;\n max-height: 100%;\n }\n}\n\n.g-btn--label {\n display: block;\n}\n\n/* Visually balance leading icon by slightly reducing left offset */\n.g-btn > .g-btn--icon:first-child {\n margin-left: -0.1em;\n}\n\n.g-btn--icon > svg,\n.g-btn--icon > img {\n width: 1em;\n height: 1em;\n display: block;\n flex: 0 0 auto;\n}\n\n.g-btn--primary {\n --ilw-color--background: var(--g-primary-500);\n --ilw-color--heading: var(--g-primary-text);\n}\n.g-btn--accent {\n --ilw-color--background: var(--g-accent-500);\n --ilw-color--heading: var(--g-surface-0);\n}\n\n.g-btn--danger {\n --ilw-color--background: var(--g-danger-500);\n --ilw-color--heading: var(--g-danger-text);\n}\n\n.g-btn--secondary {\n --ilw-color--background: var(--g-surface-700);\n --ilw-color--heading: var(--g-surface-100);\n --ilw-color--heading-link-hover: var(--g-surface-900);\n}\n\n.g-btn--outlined {\n color: var(--ilw-color--background);\n background: var(--ilw-color--heading);\n border-color: var(--ilw-color--background);\n\n &:hover {\n background: var(--ilw-color--background);\n color: var(--ilw-color--heading);\n }\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n }\n}\n\n.g-btn--text {\n background: none;\n border: none;\n color: var(--ilw-color--background);\n padding: 0.25em 0.5em; /* lighter padding using ems for consistency */\n &:hover {\n color: var(--ilw-color--heading-link-hover);\n text-decoration: underline;\n }\n &:active {\n background: var(--ilw-color--heading-link-hover);\n color: var(--ilw-color--heading);\n }\n &:focus-visible {\n border-color: var(--ilw-color--focus--outline);\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A hierarchical sidebar menu component suitable for book-like or nested-section\n * navigation. Items with children start collapsed and can be expanded/collapsed\n * individually.\n *\n * Links are authored directly in HTML for progressive enhancement — the page\n * works as a basic list of links even without JavaScript.\n *\n * Use `GTreeMenuList` and `GTreeMenuItem` sub-components to build the menu:\n *\n * ```vue-html\n * <GTreeMenu heading=\"Contents\">\n * <GTreeMenuList>\n * <GTreeMenuItem label=\"Chapter 1\">\n * <a href=\"#ch1\">Chapter 1</a>\n * <template #children>\n * <GTreeMenuItem><a href=\"#s1\">Section 1.1</a></GTreeMenuItem>\n * </template>\n * </GTreeMenuItem>\n * </GTreeMenuList>\n * </GTreeMenu>\n * ```\n *\n * > [!IMPORTANT]\n * > All items must have a focusable element for proper accessibility. If there\n * > is no link, it should be a button.\n * >\n * > To support progressive enhancement, the component applies ARIA attributes\n * > to the focusable elements in your HTML.\n *\n * **Props**:\n *\n * - `heading` - optional heading and accessible name for the nav landmark.\n * - `listType` - `ul` (default) or `ol`. Use `ol` for numbered\n * hierarchies such as book chapters. Inherited by nested `GTreeMenuList`\n * components via provide/inject.\n * - `theme` - `light` (default) or `dark`.\n * - `storageKey` - when provided, expanded/collapsed states are persisted to\n * `sessionStorage` under this key and restored on page load. This is useful\n * in Web Component / Drupal contexts where every page navigation is a full\n * refresh. Item states are keyed by the item's `label` prop.\n *\n * **Keyboard navigation** (tree-view style):\n *\n * - `Up Arrow` / `Down Arrow` - move between visible menu items.\n * - `Right Arrow` - expand a collapsed item; if already expanded, move to its first child.\n * - `Left Arrow` - collapse an expanded item; if already collapsed, move focus to its\n * parent.\n * - `Home` / `End` - jump to the first or last visible item.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, provide, reactive, ref, useId } from \"vue\";\nimport { useSessionStorage } from \"@vueuse/core\";\n\ntype Props = {\n /**\n * Heading and accessible name for the nav landmark\n * @demo Tree Menu\n */\n heading?: string;\n /**\n * List element type\n * @demo\n */\n listType?: \"ul\" | \"ol\";\n /**\n * Theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * When provided, expanded/collapsed states are saved to `sessionStorage`\n * under this key and restored on page load. Item states are keyed by each\n * the `label` prop.\n */\n storageKey?: string;\n /**\n * Show an expand/collapse all button\n * @demo\n */\n showExpandAll?: boolean;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n listType: \"ul\",\n theme: \"light\",\n showExpandAll: false,\n});\n\nconst id = useId();\n\nprovide(\"g-tree-menu-list-type\", props.listType);\n\nconst expandedStorage = props.storageKey\n ? useSessionStorage<Record<string, boolean>>(props.storageKey, {})\n : null;\n\nprovide(\"g-tree-menu-expanded-storage\", expandedStorage);\n\n// --- Expand / Collapse All ---\n\nconst expandableItems = reactive(new Map<symbol, boolean>());\nprovide(\"g-tree-menu-expandable-items\", expandableItems);\n\nconst expandAllSignal = ref<{ expanded: boolean; version: number }>({\n expanded: true,\n version: 0,\n});\nprovide(\"g-tree-menu-expand-all-signal\", expandAllSignal);\n\nconst allExpanded = computed(() => {\n if (expandableItems.size === 0) return false;\n for (const v of expandableItems.values()) {\n if (!v) return false;\n }\n return true;\n});\n\nfunction toggleExpandAll() {\n const target = !allExpanded.value;\n expandAllSignal.value = {\n expanded: target,\n version: expandAllSignal.value.version + 1,\n };\n}\n\n/**\n * Returns the best focusable element for the given [data-tree-primary] marker.\n */\nfunction getFocusTarget(primary: HTMLElement): HTMLElement {\n const anchor = primary.querySelector<HTMLElement>(\n \"a, button, [tabindex='0']\",\n );\n if (anchor) return anchor;\n return primary;\n}\n\n/**\n * Returns all visible primary focusable items ([data-tree-primary]) inside nav.\n * Because collapsed children are removed from the DOM via v-if, only currently\n * visible items are returned.\n */\nfunction getPrimaryItems(nav: HTMLElement): HTMLElement[] {\n return Array.from(nav.querySelectorAll<HTMLElement>(\"[data-tree-primary]\"));\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const nav = event.currentTarget as HTMLElement;\n const focused = document.activeElement as HTMLElement;\n if (!nav.contains(focused)) return;\n\n const handled = [\n \"ArrowUp\",\n \"ArrowDown\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Home\",\n \"End\",\n ];\n if (!handled.includes(event.key)) return;\n\n const currentLi = focused.closest<HTMLElement>(\".g-tree-menu__item\");\n const currentPrimary =\n currentLi?.querySelector<HTMLElement>(\"[data-tree-primary]\") ?? null;\n\n const primaries = getPrimaryItems(nav);\n const primaryIdx = currentPrimary ? primaries.indexOf(currentPrimary) : -1;\n\n switch (event.key) {\n case \"ArrowDown\": {\n const next = primaries[primaryIdx + 1];\n if (next) getFocusTarget(next).focus();\n break;\n }\n case \"ArrowUp\": {\n const prev = primaries[primaryIdx - 1];\n if (prev) getFocusTarget(prev).focus();\n break;\n }\n case \"ArrowRight\": {\n if (!currentLi) break;\n const isExpandable = currentLi.dataset.treeExpandable === \"true\";\n if (!isExpandable) break;\n const isExpanded =\n currentLi.querySelector(\"[aria-expanded='true']\") !== null;\n if (!isExpanded) {\n const toggleBtn = currentLi.querySelector<HTMLElement>(\n \".g-tree-menu__toggle-btn\",\n );\n if (toggleBtn) toggleBtn.click();\n } else {\n const next = primaries[primaryIdx + 1];\n if (next) getFocusTarget(next).focus();\n }\n break;\n }\n case \"ArrowLeft\": {\n if (!currentLi) break;\n const isExpanded =\n currentLi.querySelector(\"[aria-expanded='true']\") !== null;\n if (isExpanded) {\n const toggleBtn = currentLi.querySelector<HTMLElement>(\n \".g-tree-menu__toggle-btn\",\n );\n if (toggleBtn) toggleBtn.click();\n if (currentPrimary)\n nextTick(() => getFocusTarget(currentPrimary).focus());\n } else {\n const parentItem =\n currentLi.parentElement?.closest<HTMLElement>(\n \".g-tree-menu__item\",\n );\n if (parentItem) {\n const parentPrimary = parentItem.querySelector<HTMLElement>(\n \"[data-tree-primary]\",\n );\n if (parentPrimary) getFocusTarget(parentPrimary).focus();\n }\n }\n break;\n }\n case \"Home\": {\n if (primaries.length > 0) getFocusTarget(primaries[0]).focus();\n break;\n }\n case \"End\": {\n if (primaries.length > 0)\n getFocusTarget(primaries[primaries.length - 1]).focus();\n break;\n }\n }\n\n event.preventDefault();\n}\n</script>\n\n<template>\n <nav\n class=\"g-tree-menu\"\n :class=\"`g-tree-menu--${props.theme}`\"\n v-bind=\"{\n 'aria-labelledby': heading ? id : undefined,\n 'aria-label': heading ? undefined : 'Tree Menu',\n }\"\n @keydown=\"handleKeydown\"\n >\n <h2 v-if=\"heading\" :id=\"id\" class=\"g-tree-menu__title\">\n {{ heading }}\n </h2>\n <div class=\"g-tree-menu__divider\">\n <div class=\"g-tree-menu__divider-line\"></div>\n <div v-if=\"showExpandAll\" class=\"g-tree-menu__expand-all-wrapper\">\n <button\n class=\"g-tree-menu__expand-all-btn\"\n @click=\"toggleExpandAll\"\n >\n <svg\n class=\"g-tree-menu__expand-all-icon\"\n :class=\"{\n 'g-tree-menu__expand-all-icon--collapse':\n allExpanded,\n }\"\n role=\"none presentation\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"7 8 12 13 17 8\" />\n <polyline points=\"7 13 12 18 17 13\" />\n </svg>\n {{ allExpanded ? \"Collapse all\" : \"Expand all\" }}\n </button>\n </div>\n </div>\n <div class=\"g-tree-menu__content\">\n <slot />\n </div>\n </nav>\n</template>\n\n<style>\n.g-tree-menu {\n font-size: 1.125rem;\n line-height: 1.2;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.g-tree-menu--dark {\n color: var(--g-surface-0);\n\n .g-tree-menu__title {\n color: var(--g-surface-0);\n }\n}\n\n.g-tree-menu--light {\n background: var(--g-surface-50);\n\n .g-tree-menu__title {\n color: var(--g-primary-500);\n }\n}\n\n.g-tree-menu__title {\n margin: 2rem 2rem 0;\n font-size: 2rem;\n font-family: var(--il-font-heading);\n}\n\n.g-tree-menu__divider {\n display: flex;\n justify-content: space-between;\n}\n.g-tree-menu__divider-line {\n margin: 1rem 0 1rem 2rem;\n height: 4px;\n width: 60px;\n min-width: 60px;\n max-width: 60px;\n background: var(--g-accent-500);\n}\n\n.g-tree-menu__expand-all-wrapper {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n margin-right: 2rem;\n}\n\n.g-tree-menu__expand-all-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.25em;\n background: none;\n border: none;\n cursor: pointer;\n font-family: inherit;\n font-size: 0.85em;\n font-weight: 600;\n padding: 0.35em 0.5em;\n margin: 0;\n color: inherit;\n min-width: 120px;\n}\n\n.g-tree-menu__expand-all-icon {\n width: 1.2em;\n height: 1.2em;\n flex-shrink: 0;\n transition: transform 0.15s ease;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n}\n\n.g-tree-menu__expand-all-icon--collapse {\n transform: rotate(180deg);\n}\n.g-tree-menu__expand-all-btn:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\ng-tree-menu:not(:defined) {\n display: block;\n padding-top: 0;\n color: var(--g-surface-0);\n\n &[theme=\"light\"] {\n background: var(--g-surface-50);\n color: var(--g-primary-500);\n }\n\n &[theme=\"light\"] a {\n color: var(--g-primary-500);\n }\n\n &[theme=\"dark\"] a {\n color: var(--g-surface-0);\n }\n\n g-tree-menu-item {\n margin: 0.4em 0 0.4em 1.2em;\n }\n\n g-tree-menu-item[slot=\"children\"] {\n font-size: 0.95em;\n font-weight: 600;\n }\n\n g-tree-menu-item > a {\n color: inherit;\n text-decoration: none;\n }\n\n g-tree-menu-item > a:hover {\n text-decoration: underline;\n }\n g-tree-menu-list {\n display: block;\n margin-top: 1em;\n padding-left: 1em;\n }\n}\n\ng-tree-menu:not(:defined)[heading]::before {\n content: attr(heading);\n display: block;\n margin: 2rem 2rem 0.5rem;\n padding-bottom: 1rem;\n font-size: 2rem;\n line-height: 1.1;\n font-family: var(--il-font-heading);\n font-weight: 700;\n background-image: linear-gradient(var(--g-accent-500), var(--g-accent-500));\n background-repeat: no-repeat;\n background-size: 60px 4px;\n background-position: left bottom;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A hierarchical sidebar menu component suitable for book-like or nested-section\n * navigation. Items with children start collapsed and can be expanded/collapsed\n * individually.\n *\n * Links are authored directly in HTML for progressive enhancement — the page\n * works as a basic list of links even without JavaScript.\n *\n * Use `GTreeMenuList` and `GTreeMenuItem` sub-components to build the menu:\n *\n * ```vue-html\n * <GTreeMenu heading=\"Contents\">\n * <GTreeMenuList>\n * <GTreeMenuItem label=\"Chapter 1\">\n * <a href=\"#ch1\">Chapter 1</a>\n * <template #children>\n * <GTreeMenuItem><a href=\"#s1\">Section 1.1</a></GTreeMenuItem>\n * </template>\n * </GTreeMenuItem>\n * </GTreeMenuList>\n * </GTreeMenu>\n * ```\n *\n * > [!IMPORTANT]\n * > All items must have a focusable element for proper accessibility. If there\n * > is no link, it should be a button.\n * >\n * > To support progressive enhancement, the component applies ARIA attributes\n * > to the focusable elements in your HTML.\n *\n * **Props**:\n *\n * - `heading` - optional heading and accessible name for the nav landmark.\n * - `listType` - `ul` (default) or `ol`. Use `ol` for numbered\n * hierarchies such as book chapters. Inherited by nested `GTreeMenuList`\n * components via provide/inject.\n * - `theme` - `light` (default) or `dark`.\n * - `storageKey` - when provided, expanded/collapsed states are persisted to\n * `sessionStorage` under this key and restored on page load. This is useful\n * in Web Component / Drupal contexts where every page navigation is a full\n * refresh. Item states are keyed by the item's `label` prop.\n *\n * **Keyboard navigation** (tree-view style):\n *\n * - `Up Arrow` / `Down Arrow` - move between visible menu items.\n * - `Right Arrow` - expand a collapsed item; if already expanded, move to its first child.\n * - `Left Arrow` - collapse an expanded item; if already collapsed, move focus to its\n * parent.\n * - `Home` / `End` - jump to the first or last visible item.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, provide, reactive, ref, useId } from \"vue\";\nimport { useSessionStorage } from \"@vueuse/core\";\n\ntype Props = {\n /**\n * Heading and accessible name for the nav landmark\n * @demo Tree Menu\n */\n heading?: string;\n /**\n * List element type\n * @demo\n */\n listType?: \"ul\" | \"ol\";\n /**\n * Theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * When provided, expanded/collapsed states are saved to `sessionStorage`\n * under this key and restored on page load. Item states are keyed by each\n * the `label` prop.\n */\n storageKey?: string;\n /**\n * Show an expand/collapse all button\n * @demo\n */\n showExpandAll?: boolean;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n listType: \"ul\",\n theme: \"light\",\n showExpandAll: false,\n});\n\nconst id = useId();\n\nprovide(\"g-tree-menu-list-type\", props.listType);\n\nconst expandedStorage = props.storageKey\n ? useSessionStorage<Record<string, boolean>>(props.storageKey, {})\n : null;\n\nprovide(\"g-tree-menu-expanded-storage\", expandedStorage);\n\n// --- Expand / Collapse All ---\n\nconst expandableItems = reactive(new Map<symbol, boolean>());\nprovide(\"g-tree-menu-expandable-items\", expandableItems);\n\nconst expandAllSignal = ref<{ expanded: boolean; version: number }>({\n expanded: true,\n version: 0,\n});\nprovide(\"g-tree-menu-expand-all-signal\", expandAllSignal);\n\nconst allExpanded = computed(() => {\n if (expandableItems.size === 0) return false;\n for (const v of expandableItems.values()) {\n if (!v) return false;\n }\n return true;\n});\n\nfunction toggleExpandAll() {\n const target = !allExpanded.value;\n expandAllSignal.value = {\n expanded: target,\n version: expandAllSignal.value.version + 1,\n };\n}\n\n/**\n * Returns the best focusable element for the given [data-tree-primary] marker.\n */\nfunction getFocusTarget(primary: HTMLElement): HTMLElement {\n const anchor = primary.querySelector<HTMLElement>(\n \"a, button, [tabindex='0']\",\n );\n if (anchor) return anchor;\n return primary;\n}\n\n/**\n * Returns all visible primary focusable items ([data-tree-primary]) inside nav.\n * Because collapsed children are removed from the DOM via v-if, only currently\n * visible items are returned.\n */\nfunction getPrimaryItems(nav: HTMLElement): HTMLElement[] {\n return Array.from(nav.querySelectorAll<HTMLElement>(\"[data-tree-primary]\"));\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const nav = event.currentTarget as HTMLElement;\n const focused = document.activeElement as HTMLElement;\n if (!nav.contains(focused)) return;\n\n const handled = [\n \"ArrowUp\",\n \"ArrowDown\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Home\",\n \"End\",\n ];\n if (!handled.includes(event.key)) return;\n\n const currentLi = focused.closest<HTMLElement>(\".g-tree-menu__item\");\n const currentPrimary =\n currentLi?.querySelector<HTMLElement>(\"[data-tree-primary]\") ?? null;\n\n const primaries = getPrimaryItems(nav);\n const primaryIdx = currentPrimary ? primaries.indexOf(currentPrimary) : -1;\n\n switch (event.key) {\n case \"ArrowDown\": {\n const next = primaries[primaryIdx + 1];\n if (next) getFocusTarget(next).focus();\n break;\n }\n case \"ArrowUp\": {\n const prev = primaries[primaryIdx - 1];\n if (prev) getFocusTarget(prev).focus();\n break;\n }\n case \"ArrowRight\": {\n if (!currentLi) break;\n const isExpandable = currentLi.dataset.treeExpandable === \"true\";\n if (!isExpandable) break;\n const isExpanded =\n currentLi.querySelector(\"[aria-expanded='true']\") !== null;\n if (!isExpanded) {\n const toggleBtn = currentLi.querySelector<HTMLElement>(\n \".g-tree-menu__toggle-btn\",\n );\n if (toggleBtn) toggleBtn.click();\n } else {\n const next = primaries[primaryIdx + 1];\n if (next) getFocusTarget(next).focus();\n }\n break;\n }\n case \"ArrowLeft\": {\n if (!currentLi) break;\n const isExpanded =\n currentLi.querySelector(\"[aria-expanded='true']\") !== null;\n if (isExpanded) {\n const toggleBtn = currentLi.querySelector<HTMLElement>(\n \".g-tree-menu__toggle-btn\",\n );\n if (toggleBtn) toggleBtn.click();\n if (currentPrimary)\n nextTick(() => getFocusTarget(currentPrimary).focus());\n } else {\n const parentItem =\n currentLi.parentElement?.closest<HTMLElement>(\n \".g-tree-menu__item\",\n );\n if (parentItem) {\n const parentPrimary = parentItem.querySelector<HTMLElement>(\n \"[data-tree-primary]\",\n );\n if (parentPrimary) getFocusTarget(parentPrimary).focus();\n }\n }\n break;\n }\n case \"Home\": {\n if (primaries.length > 0) getFocusTarget(primaries[0]).focus();\n break;\n }\n case \"End\": {\n if (primaries.length > 0)\n getFocusTarget(primaries[primaries.length - 1]).focus();\n break;\n }\n }\n\n event.preventDefault();\n}\n</script>\n\n<template>\n <nav\n class=\"g-tree-menu\"\n :class=\"`g-tree-menu--${props.theme}`\"\n v-bind=\"{\n 'aria-labelledby': heading ? id : undefined,\n 'aria-label': heading ? undefined : 'Tree Menu',\n }\"\n @keydown=\"handleKeydown\"\n >\n <h2 v-if=\"heading\" :id=\"id\" class=\"g-tree-menu__title\">\n {{ heading }}\n </h2>\n <div class=\"g-tree-menu__divider\">\n <div class=\"g-tree-menu__divider-line\"></div>\n <div v-if=\"showExpandAll\" class=\"g-tree-menu__expand-all-wrapper\">\n <button\n class=\"g-tree-menu__expand-all-btn\"\n @click=\"toggleExpandAll\"\n >\n <svg\n class=\"g-tree-menu__expand-all-icon\"\n :class=\"{\n 'g-tree-menu__expand-all-icon--collapse':\n allExpanded,\n }\"\n role=\"none presentation\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"7 8 12 13 17 8\" />\n <polyline points=\"7 13 12 18 17 13\" />\n </svg>\n {{ allExpanded ? \"Collapse all\" : \"Expand all\" }}\n </button>\n </div>\n </div>\n <div class=\"g-tree-menu__content\">\n <slot />\n </div>\n </nav>\n</template>\n\n<style>\n.g-tree-menu {\n font-size: 1.125rem;\n line-height: 1.2;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.g-tree-menu--dark {\n color: var(--g-surface-0);\n\n .g-tree-menu__title {\n color: var(--g-surface-0);\n }\n}\n\n.g-tree-menu--light {\n background: var(--g-surface-50);\n\n .g-tree-menu__title {\n color: var(--g-primary-500);\n }\n}\n\n.g-tree-menu__title {\n margin: 2rem 2rem 0;\n font-size: 2rem;\n font-family: var(--il-font-heading);\n}\n\n.g-tree-menu__divider {\n display: flex;\n justify-content: space-between;\n}\n.g-tree-menu__divider-line {\n margin: 1rem 0 1rem 2rem;\n height: 4px;\n width: 60px;\n min-width: 60px;\n max-width: 60px;\n background: var(--g-accent-500);\n}\n\n.g-tree-menu__expand-all-wrapper {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n margin-right: 2rem;\n}\n\n.g-tree-menu__expand-all-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.25em;\n background: none;\n border: none;\n cursor: pointer;\n font-family: inherit;\n font-size: 0.85em;\n font-weight: 600;\n padding: 0.35em 0.5em;\n margin: 0;\n color: inherit;\n min-width: 120px;\n}\n\n.g-tree-menu__expand-all-icon {\n width: 1.2em;\n height: 1.2em;\n flex-shrink: 0;\n transition: transform 0.15s ease;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n}\n\n.g-tree-menu__expand-all-icon--collapse {\n transform: rotate(180deg);\n}\n.g-tree-menu__expand-all-btn:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\ng-tree-menu:not(:defined) {\n display: block;\n padding-top: 0;\n color: var(--g-surface-0);\n\n &[theme=\"light\"] {\n background: var(--g-surface-50);\n color: var(--g-primary-500);\n }\n\n &[theme=\"light\"] a {\n color: var(--g-primary-500);\n }\n\n &[theme=\"dark\"] a {\n color: var(--g-surface-0);\n }\n\n g-tree-menu-item {\n margin: 0.4em 0 0.4em 1.2em;\n }\n\n g-tree-menu-item[slot=\"children\"] {\n font-size: 0.95em;\n font-weight: 600;\n }\n\n g-tree-menu-item > a {\n color: inherit;\n text-decoration: none;\n }\n\n g-tree-menu-item > a:hover {\n text-decoration: underline;\n }\n g-tree-menu-list {\n display: block;\n margin-top: 1em;\n padding-left: 1em;\n }\n}\n\ng-tree-menu:not(:defined)[heading]::before {\n content: attr(heading);\n display: block;\n margin: 2rem 2rem 0.5rem;\n padding-bottom: 1rem;\n font-size: 2rem;\n line-height: 1.1;\n font-family: var(--il-font-heading);\n font-weight: 700;\n background-image: linear-gradient(var(--g-accent-500), var(--g-accent-500));\n background-repeat: no-repeat;\n background-size: 60px 4px;\n background-position: left bottom;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * List wrapper for `GTreeMenuItem` items inside a `GTreeMenu`.\n * Renders as `<ul>` or `<ol>` depending on the `listType` prop.\n * When no `listType` is specified the value provided by the parent\n * `GTreeMenu` (via provide/inject) is used, falling back to `ul`.\n */\nexport default { name: \"GTreeMenuList\" };\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, inject } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n /**\n * List element type (`ul` or `ol`).\n */\n listType?: \"ul\" | \"ol\";\n }>(),\n {\n listType: undefined,\n },\n);\n\nconst injectedListType = inject<string>(\"g-tree-menu-list-type\", \"ul\");\n\nconst resolvedListType = computed(\n () => props.listType ?? injectedListType ?? \"ul\",\n);\n</script>\n\n<template>\n <component :is=\"resolvedListType\" class=\"g-tree-menu__list\">\n <slot />\n </component>\n</template>\n\n<style>\ng-tree-menu-list,\n.g-tree-menu__list {\n list-style: none;\n margin: 0;\n padding: 0;\n font-weight: bold;\n display: block;\n}\n.g-tree-menu__list ul,\n.g-tree-menu__list ol {\n font-weight: 600 !important;\n}\n\n.g-tree-menu__list .g-tree-menu__list {\n padding-left: 1.25em;\n}\n\ng-tree-menu-list > g-tree-menu-item {\n margin-top: 0.4em;\n}\n\ng-tree-menu-list > g-tree-menu-item:first-of-type {\n margin-top: 0;\n}\n\n</style>","<script lang=\"ts\">\n/**\n * List wrapper for `GTreeMenuItem` items inside a `GTreeMenu`.\n * Renders as `<ul>` or `<ol>` depending on the `listType` prop.\n * When no `listType` is specified the value provided by the parent\n * `GTreeMenu` (via provide/inject) is used, falling back to `ul`.\n */\nexport default { name: \"GTreeMenuList\" };\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, inject } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n /**\n * List element type (`ul` or `ol`).\n */\n listType?: \"ul\" | \"ol\";\n }>(),\n {\n listType: undefined,\n },\n);\n\nconst injectedListType = inject<string>(\"g-tree-menu-list-type\", \"ul\");\n\nconst resolvedListType = computed(\n () => props.listType ?? injectedListType ?? \"ul\",\n);\n</script>\n\n<template>\n <component :is=\"resolvedListType\" class=\"g-tree-menu__list\">\n <slot />\n </component>\n</template>\n\n<style>\ng-tree-menu-list,\n.g-tree-menu__list {\n list-style: none;\n margin: 0;\n padding: 0;\n font-weight: bold;\n display: block;\n}\n.g-tree-menu__list ul,\n.g-tree-menu__list ol {\n font-weight: 600 !important;\n}\n\n.g-tree-menu__list .g-tree-menu__list {\n padding-left: 1.25em;\n}\n\ng-tree-menu-list > g-tree-menu-item {\n margin-top: 0.4em;\n}\n\ng-tree-menu-list > g-tree-menu-item:first-of-type {\n margin-top: 0;\n}\n\n</style>","<script lang=\"ts\">\nexport default { name: \"GTreeMenuItem\" };\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, getCurrentInstance, inject, onBeforeUnmount, onMounted, onUpdated, ref, useId, useSlots, watch } from \"vue\";\nimport type { Ref } from \"vue\";\nimport GTreeMenuList from \"./GTreeMenuList.vue\";\n\nconst props = withDefaults(\n defineProps<{\n /**\n * Label for the item. Used as a stable identifier for the item.\n * @demo\n '\n */\n label?: string;\n /**\n * Whether the item starts expanded. Only meaningful for items that\n * have a `#children` slot. When a `storageKey` is active on the parent\n * `GTreeMenu` and a stored value exists for this item's `label`, the\n * stored value takes precedence over this prop and subsequent prop\n * updates are ignored for that item.\n * @demo\n */\n expanded?: boolean;\n }>(),\n {\n expanded: false,\n },\n);\n\nconst emit = defineEmits<{\n /** Fired when the item is expanded. */\n expand: [];\n /** Fired when the item is collapsed. */\n collapse: [];\n}>();\n\nconst slots = useSlots();\nconst instance = getCurrentInstance();\n\nconst id = useId();\n\n// In CE mode without Shadow DOM, useSlots() doesn't detect slot=\"children\"\n// on child elements. Fall back to checking the CE host's parsed _slots.\n// This accesses Vue's internal CE implementation (VueElement._slots) which\n// is stable since Vue 3.2+ but is not a public API — verify compatibility\n// when upgrading Vue.\nconst ceHost = (instance as any)?.ce as any | undefined;\nconst hasCeChildren = ceHost?._slots?.children?.length > 0;\n\nconst hasChildren = computed(() => !!slots.children || hasCeChildren);\n\nconst expandedStorage = inject<Ref<Record<string, boolean>> | null>(\n \"g-tree-menu-expanded-storage\",\n null,\n);\n\nfunction resolveInitialExpanded(): boolean {\n if (expandedStorage && props.label !== undefined) {\n if (expandedStorage.value[props.label] === true) return true;\n if (props.expanded) {\n expandedStorage.value[props.label] = true;\n }\n }\n return props.expanded;\n}\n\nconst isExpanded = ref(resolveInitialExpanded());\n\nwatch(\n () => props.expanded,\n (val) => {\n if (expandedStorage && props.label !== undefined) return;\n isExpanded.value = val;\n },\n);\n\nwatch(isExpanded, (val) => {\n if (expandedStorage && props.label !== undefined) {\n if (val) {\n expandedStorage.value[props.label] = true;\n } else {\n delete expandedStorage.value[props.label];\n }\n }\n updateSlotAria();\n});\n\nconst contentRef = ref<HTMLElement | null>(null);\n\nfunction updateSlotAria() {\n if (!hasChildren.value || !contentRef.value) return;\n const focusable = contentRef.value.querySelector(\"a, button\");\n if (focusable) {\n focusable.setAttribute(\"aria-controls\", id + \"-children\");\n focusable.setAttribute(\"aria-expanded\", isExpanded.value ? \"true\" : \"false\");\n } else {\n console.warn(\"No focusable element found for GTreeMenuItem with label:\", props.label, \"Every item must at least have a plain button to properly work for accessibility.\");\n }\n}\n\nonMounted(updateSlotAria);\nonUpdated(updateSlotAria);\n\nfunction toggle() {\n isExpanded.value = !isExpanded.value;\n if (isExpanded.value) {\n emit(\"expand\");\n } else {\n emit(\"collapse\");\n }\n}\n\n// --- Expand / Collapse All registration ---\n\nconst itemId = Symbol();\nconst expandableItems = inject<Map<symbol, boolean> | null>(\n \"g-tree-menu-expandable-items\",\n null,\n);\nconst expandAllSignal = inject<Ref<{ expanded: boolean; version: number }> | null>(\n \"g-tree-menu-expand-all-signal\",\n null,\n);\nconst lastProcessedVersion = ref(0);\n\nfunction registerItem() {\n if (expandableItems && hasChildren.value) {\n expandableItems.set(itemId, isExpanded.value);\n }\n}\n\nfunction unregisterItem() {\n expandableItems?.delete(itemId);\n}\n\nwatch(isExpanded, (val) => {\n if (expandableItems && hasChildren.value) {\n expandableItems.set(itemId, val);\n }\n});\n\nif (expandAllSignal) {\n watch(\n () => expandAllSignal.value.version,\n () => {\n if (!hasChildren.value) return;\n isExpanded.value = expandAllSignal.value.expanded;\n lastProcessedVersion.value = expandAllSignal.value.version;\n },\n );\n}\n\nonMounted(() => {\n registerItem();\n if (\n expandAllSignal &&\n hasChildren.value &&\n expandAllSignal.value.version > lastProcessedVersion.value &&\n expandAllSignal.value.expanded\n ) {\n isExpanded.value = true;\n lastProcessedVersion.value = expandAllSignal.value.version;\n }\n});\n\nonBeforeUnmount(() => {\n unregisterItem();\n});\n\nfunction handleContentClick(event: MouseEvent) {\n if (!(event.target as Element).closest(\"a\")) {\n toggle();\n }\n}\n\nfunction handleContentKeydown(event: KeyboardEvent) {\n if (event.key === \"Enter\" || event.key === \" \") {\n toggle();\n event.preventDefault();\n }\n}\n</script>\n\n<template>\n <li\n class=\"g-tree-menu__item\"\n :data-tree-expandable=\"hasChildren ? 'true' : undefined\"\n >\n <!-- Parent: has children → toggle button + slot content (which may contain a link) -->\n <div v-if=\"hasChildren\" class=\"g-tree-menu__row\">\n <div\n class=\"g-tree-menu__toggle-btn\"\n @click=\"toggle\"\n >\n <svg\n class=\"g-tree-menu__chevron\"\n :class=\"{ 'g-tree-menu__chevron--expanded': isExpanded }\"\n role=\"none presentation\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n </div>\n <span\n ref=\"contentRef\"\n class=\"g-tree-menu__row-content\"\n data-tree-primary\n @click=\"handleContentClick\"\n @keydown=\"handleContentKeydown\"\n >\n <span class=\"g-tree-menu__row-content-text\">\n <slot />\n </span>\n </span>\n </div>\n\n <!-- Leaf: no children → just render the slot content -->\n <div v-else class=\"g-tree-menu__row g-tree-menu__row--leaf\">\n <span class=\"g-tree-menu__spacer\"></span>\n <span\n class=\"g-tree-menu__row-content\"\n data-tree-primary\n >\n <slot />\n </span>\n </div>\n\n <!-- Children (shown when expanded) -->\n <GTreeMenuList v-if=\"hasChildren && isExpanded\" :id=\"id + '-children'\">\n <slot name=\"children\" />\n </GTreeMenuList>\n </li>\n</template>\n\n<style>\ng-tree-menu-item,\n.g-tree-menu__item {\n display: block;\n}\n\ng-tree-menu-item > a {\n color: inherit;\n display: flex;\n align-items: flex-start;\n text-decoration: none;\n}\n\ng-tree-menu-item > a:hover {\n text-decoration: underline;\n}\n\n.g-tree-menu__row {\n display: flex;\n align-items: stretch;\n}\n\n.g-tree-menu__toggle-btn {\n flex-shrink: 0;\n align-self: center;\n background: none;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n box-sizing: border-box;\n border-radius: 2px;\n margin-right: 2px;\n font-size: inherit;\n font-weight: inherit;\n}\n\n.g-tree-menu__spacer {\n display: inline-block;\n width: 2em;\n height: 2em;\n min-width: 2em;\n min-height: 2em;\n margin-right: 2px;\n align-self: center;\n}\n\n.g-tree-menu__row-content {\n display: flex;\n align-items: stretch;\n flex: 1;\n padding: 0 0.5em 0 0;\n box-sizing: border-box;\n\n button, a {\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n padding: 2px 0;\n margin: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n flex: 1;\n text-decoration: none;\n height: 100%;\n\n &:hover {\n text-decoration: underline;\n }\n }\n}\n\n.g-tree-menu__row:not(.g-tree-menu__row--leaf) .g-tree-menu__row-content {\n cursor: pointer;\n}\n\n.g-tree-menu__row-content-text {\n flex: 1;\n display: flex;\n align-items: center;\n}\n\n.g-tree-menu__chevron {\n width: 2em;\n height: 2em;\n min-width: 2em;\n min-height: 2em;\n padding: 0.35em;\n box-sizing: border-box;\n flex-shrink: 0;\n transform: rotate(0deg);\n transition: transform 0.1s ease;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n}\n\n.g-tree-menu__chevron--expanded {\n transform: rotate(90deg);\n}\n\n.g-tree-menu__toggle-btn:focus-visible,\n.g-tree-menu__row-content:focus-visible,\n.g-tree-menu__row-content a:focus-visible,\n.g-tree-menu__row-content button:focus-visible{\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n\n.g-tree-menu__row .g-tree-menu__toggle-btn {\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n }\n}\n\n.g-tree-menu--dark {\n .g-tree-menu__toggle-btn,\n .g-tree-menu__row-content a {\n color: var(--g-surface-0);\n\n &:hover {\n color: var(--g-accent-500);\n }\n }\n}\n\n.g-tree-menu--light {\n .g-tree-menu__row-content,\n .g-tree-menu__row-content a {\n color: var(--g-primary-500);\n\n &:hover {\n color: var(--g-accent-700);\n }\n &:focus-visible {\n color: var(--ilw-color--focus--text);\n }\n }\n}\n\n</style>\n","<script lang=\"ts\">\nexport default { name: \"GTreeMenuItem\" };\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, getCurrentInstance, inject, onBeforeUnmount, onMounted, onUpdated, ref, useId, useSlots, watch } from \"vue\";\nimport type { Ref } from \"vue\";\nimport GTreeMenuList from \"./GTreeMenuList.vue\";\n\nconst props = withDefaults(\n defineProps<{\n /**\n * Label for the item. Used as a stable identifier for the item.\n * @demo\n '\n */\n label?: string;\n /**\n * Whether the item starts expanded. Only meaningful for items that\n * have a `#children` slot. When a `storageKey` is active on the parent\n * `GTreeMenu` and a stored value exists for this item's `label`, the\n * stored value takes precedence over this prop and subsequent prop\n * updates are ignored for that item.\n * @demo\n */\n expanded?: boolean;\n }>(),\n {\n expanded: false,\n },\n);\n\nconst emit = defineEmits<{\n /** Fired when the item is expanded. */\n expand: [];\n /** Fired when the item is collapsed. */\n collapse: [];\n}>();\n\nconst slots = useSlots();\nconst instance = getCurrentInstance();\n\nconst id = useId();\n\n// In CE mode without Shadow DOM, useSlots() doesn't detect slot=\"children\"\n// on child elements. Fall back to checking the CE host's parsed _slots.\n// This accesses Vue's internal CE implementation (VueElement._slots) which\n// is stable since Vue 3.2+ but is not a public API — verify compatibility\n// when upgrading Vue.\nconst ceHost = (instance as any)?.ce as any | undefined;\nconst hasCeChildren = ceHost?._slots?.children?.length > 0;\n\nconst hasChildren = computed(() => !!slots.children || hasCeChildren);\n\nconst expandedStorage = inject<Ref<Record<string, boolean>> | null>(\n \"g-tree-menu-expanded-storage\",\n null,\n);\n\nfunction resolveInitialExpanded(): boolean {\n if (expandedStorage && props.label !== undefined) {\n if (expandedStorage.value[props.label] === true) return true;\n if (props.expanded) {\n expandedStorage.value[props.label] = true;\n }\n }\n return props.expanded;\n}\n\nconst isExpanded = ref(resolveInitialExpanded());\n\nwatch(\n () => props.expanded,\n (val) => {\n if (expandedStorage && props.label !== undefined) return;\n isExpanded.value = val;\n },\n);\n\nwatch(isExpanded, (val) => {\n if (expandedStorage && props.label !== undefined) {\n if (val) {\n expandedStorage.value[props.label] = true;\n } else {\n delete expandedStorage.value[props.label];\n }\n }\n updateSlotAria();\n});\n\nconst contentRef = ref<HTMLElement | null>(null);\n\nfunction updateSlotAria() {\n if (!hasChildren.value || !contentRef.value) return;\n const focusable = contentRef.value.querySelector(\"a, button\");\n if (focusable) {\n focusable.setAttribute(\"aria-controls\", id + \"-children\");\n focusable.setAttribute(\"aria-expanded\", isExpanded.value ? \"true\" : \"false\");\n } else {\n console.warn(\"No focusable element found for GTreeMenuItem with label:\", props.label, \"Every item must at least have a plain button to properly work for accessibility.\");\n }\n}\n\nonMounted(updateSlotAria);\nonUpdated(updateSlotAria);\n\nfunction toggle() {\n isExpanded.value = !isExpanded.value;\n if (isExpanded.value) {\n emit(\"expand\");\n } else {\n emit(\"collapse\");\n }\n}\n\n// --- Expand / Collapse All registration ---\n\nconst itemId = Symbol();\nconst expandableItems = inject<Map<symbol, boolean> | null>(\n \"g-tree-menu-expandable-items\",\n null,\n);\nconst expandAllSignal = inject<Ref<{ expanded: boolean; version: number }> | null>(\n \"g-tree-menu-expand-all-signal\",\n null,\n);\nconst lastProcessedVersion = ref(0);\n\nfunction registerItem() {\n if (expandableItems && hasChildren.value) {\n expandableItems.set(itemId, isExpanded.value);\n }\n}\n\nfunction unregisterItem() {\n expandableItems?.delete(itemId);\n}\n\nwatch(isExpanded, (val) => {\n if (expandableItems && hasChildren.value) {\n expandableItems.set(itemId, val);\n }\n});\n\nif (expandAllSignal) {\n watch(\n () => expandAllSignal.value.version,\n () => {\n if (!hasChildren.value) return;\n isExpanded.value = expandAllSignal.value.expanded;\n lastProcessedVersion.value = expandAllSignal.value.version;\n },\n );\n}\n\nonMounted(() => {\n registerItem();\n if (\n expandAllSignal &&\n hasChildren.value &&\n expandAllSignal.value.version > lastProcessedVersion.value &&\n expandAllSignal.value.expanded\n ) {\n isExpanded.value = true;\n lastProcessedVersion.value = expandAllSignal.value.version;\n }\n});\n\nonBeforeUnmount(() => {\n unregisterItem();\n});\n\nfunction handleContentClick(event: MouseEvent) {\n if (!(event.target as Element).closest(\"a\")) {\n toggle();\n }\n}\n\nfunction handleContentKeydown(event: KeyboardEvent) {\n if (event.key === \"Enter\" || event.key === \" \") {\n toggle();\n event.preventDefault();\n }\n}\n</script>\n\n<template>\n <li\n class=\"g-tree-menu__item\"\n :data-tree-expandable=\"hasChildren ? 'true' : undefined\"\n >\n <!-- Parent: has children → toggle button + slot content (which may contain a link) -->\n <div v-if=\"hasChildren\" class=\"g-tree-menu__row\">\n <div\n class=\"g-tree-menu__toggle-btn\"\n @click=\"toggle\"\n >\n <svg\n class=\"g-tree-menu__chevron\"\n :class=\"{ 'g-tree-menu__chevron--expanded': isExpanded }\"\n role=\"none presentation\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n </div>\n <span\n ref=\"contentRef\"\n class=\"g-tree-menu__row-content\"\n data-tree-primary\n @click=\"handleContentClick\"\n @keydown=\"handleContentKeydown\"\n >\n <span class=\"g-tree-menu__row-content-text\">\n <slot />\n </span>\n </span>\n </div>\n\n <!-- Leaf: no children → just render the slot content -->\n <div v-else class=\"g-tree-menu__row g-tree-menu__row--leaf\">\n <span class=\"g-tree-menu__spacer\"></span>\n <span\n class=\"g-tree-menu__row-content\"\n data-tree-primary\n >\n <slot />\n </span>\n </div>\n\n <!-- Children (shown when expanded) -->\n <GTreeMenuList v-if=\"hasChildren && isExpanded\" :id=\"id + '-children'\">\n <slot name=\"children\" />\n </GTreeMenuList>\n </li>\n</template>\n\n<style>\ng-tree-menu-item,\n.g-tree-menu__item {\n display: block;\n}\n\ng-tree-menu-item > a {\n color: inherit;\n display: flex;\n align-items: flex-start;\n text-decoration: none;\n}\n\ng-tree-menu-item > a:hover {\n text-decoration: underline;\n}\n\n.g-tree-menu__row {\n display: flex;\n align-items: stretch;\n}\n\n.g-tree-menu__toggle-btn {\n flex-shrink: 0;\n align-self: center;\n background: none;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n box-sizing: border-box;\n border-radius: 2px;\n margin-right: 2px;\n font-size: inherit;\n font-weight: inherit;\n}\n\n.g-tree-menu__spacer {\n display: inline-block;\n width: 2em;\n height: 2em;\n min-width: 2em;\n min-height: 2em;\n margin-right: 2px;\n align-self: center;\n}\n\n.g-tree-menu__row-content {\n display: flex;\n align-items: stretch;\n flex: 1;\n padding: 0 0.5em 0 0;\n box-sizing: border-box;\n\n button, a {\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n padding: 2px 0;\n margin: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n flex: 1;\n text-decoration: none;\n height: 100%;\n\n &:hover {\n text-decoration: underline;\n }\n }\n}\n\n.g-tree-menu__row:not(.g-tree-menu__row--leaf) .g-tree-menu__row-content {\n cursor: pointer;\n}\n\n.g-tree-menu__row-content-text {\n flex: 1;\n display: flex;\n align-items: center;\n}\n\n.g-tree-menu__chevron {\n width: 2em;\n height: 2em;\n min-width: 2em;\n min-height: 2em;\n padding: 0.35em;\n box-sizing: border-box;\n flex-shrink: 0;\n transform: rotate(0deg);\n transition: transform 0.1s ease;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n}\n\n.g-tree-menu__chevron--expanded {\n transform: rotate(90deg);\n}\n\n.g-tree-menu__toggle-btn:focus-visible,\n.g-tree-menu__row-content:focus-visible,\n.g-tree-menu__row-content a:focus-visible,\n.g-tree-menu__row-content button:focus-visible{\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n\n.g-tree-menu__row .g-tree-menu__toggle-btn {\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n }\n}\n\n.g-tree-menu--dark {\n .g-tree-menu__toggle-btn,\n .g-tree-menu__row-content a {\n color: var(--g-surface-0);\n\n &:hover {\n color: var(--g-accent-500);\n }\n }\n}\n\n.g-tree-menu--light {\n .g-tree-menu__row-content,\n .g-tree-menu__row-content a {\n color: var(--g-primary-500);\n\n &:hover {\n color: var(--g-accent-700);\n }\n &:focus-visible {\n color: var(--ilw-color--focus--text);\n }\n }\n}\n\n</style>\n","import { ref, Ref, computed, ComputedRef, shallowReactive } from \"vue\";\n\nexport interface FormField {\n name: string;\n value: Ref<any>;\n errors: Ref<string[]> | ComputedRef<string[]>;\n}\n\nexport interface UseFormReturn {\n fields: Record<string, FormField>;\n values: Ref<Record<string, any>>;\n errors: Ref<Record<string, string[]>>;\n isSubmitting: Ref<boolean>;\n hasErrors: Ref<boolean>;\n registerField: (name: string, field: FormField) => void;\n unregisterField: (name: string) => void;\n submit: (handler: (values: Record<string, any>) => Promise<void> | void) => Promise<void>;\n}\n\n/**\n * Composable to manage form state and link form inputs together.\n * Uses reactive state pattern - errors are provided as reactive props to input components.\n */\nexport function useForm(): UseFormReturn {\n const fields: Record<string, FormField> = shallowReactive({});\n const isSubmitting = ref(false);\n\n const values = computed(() => {\n const vals: Record<string, any> = {};\n Object.entries(fields).forEach(([name, field]) => {\n if (field && field.value) {\n vals[name] = field.value.value;\n }\n });\n return vals;\n });\n\n const errors = computed(() => {\n const errs: Record<string, string[]> = {};\n Object.entries(fields).forEach(([name, field]) => {\n const errorValue = field.errors.value;\n if (errorValue && errorValue.length > 0) {\n errs[name] = errorValue;\n }\n });\n return errs;\n });\n\n const hasErrors = computed(() => {\n return Object.keys(errors.value).length > 0;\n });\n\n function registerField(name: string, field: FormField) {\n fields[name] = field;\n }\n\n function unregisterField(name: string) {\n delete fields[name];\n }\n\n async function submit(handler: (values: Record<string, any>) => Promise<void> | void) {\n if (isSubmitting.value) {\n return;\n }\n isSubmitting.value = true;\n try {\n await handler(values.value);\n } finally {\n isSubmitting.value = false;\n }\n }\n\n return {\n fields,\n values,\n errors,\n isSubmitting,\n hasErrors,\n registerField,\n unregisterField,\n submit,\n };\n}\n","import { useForm, UseFormReturn } from \"./useForm\";\n\nfunction getFormStore() {\n const globalScope = globalThis as typeof globalThis & {\n __GRAD_VUE_WC_FORMS__?: Map<string, UseFormReturn>;\n };\n\n if (!globalScope.__GRAD_VUE_WC_FORMS__) {\n globalScope.__GRAD_VUE_WC_FORMS__ = new Map();\n }\n\n return globalScope.__GRAD_VUE_WC_FORMS__;\n}\n\nexport function useWebComponentForm(key = \"default\") {\n const forms = getFormStore();\n const formKey = key || \"default\";\n\n if (!forms.has(formKey)) {\n forms.set(formKey, useForm());\n }\n\n return forms.get(formKey)!;\n}\n","import { Ref, computed, inject, onMounted, onBeforeUnmount, ComputedRef, useAttrs } from \"vue\";\nimport { UseFormReturn } from \"./useForm\";\nimport { useWebComponentForm } from \"./useWebComponentForm\";\nimport { isCustomElementMode } from \"./useCustomElementAttrs\";\n\nexport interface UseFormFieldOptions {\n /**\n * The name of the field (required for form registration)\n */\n name?: string;\n /**\n * The model value ref to register with the form\n */\n value: Ref<any>;\n /**\n * Error messages from props (optional) - should be a reactive reference\n */\n errors?: Ref<string[]> | ComputedRef<string[]>;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n}\n\nexport interface UseFormFieldReturn {\n /**\n * Combined filtered errors array\n */\n displayErrors: Ref<string[]>;\n /**\n * Whether the field has any errors\n */\n hasErrors: Ref<boolean>;\n}\n\n/**\n * Composable to handle form field registration and error management.\n */\nexport function useFormField(options: UseFormFieldOptions): UseFormFieldReturn {\n const attrs = useAttrs();\n const attrFormKey = typeof attrs[\"form-key\"] === \"string\" ? attrs[\"form-key\"] : undefined;\n const formKey = options.formKey ?? attrFormKey ?? \"default\";\n const injectedForm = inject<UseFormReturn | null>(\"form\", null);\n const form =\n injectedForm ??\n (isCustomElementMode() ? useWebComponentForm(formKey) : null);\n\n const displayErrors = computed(() => {\n const allErrors: string[] = [];\n \n // Add prop errors (now reactive)\n if (options.errors) {\n allErrors.push(...options.errors.value.filter(Boolean));\n }\n return allErrors;\n });\n\n const hasErrors = computed(() => displayErrors.value.length > 0);\n\n // Register field with form if name is provided\n const name = options.name;\n if (form && name) {\n onMounted(() => {\n // Note: FormField interface requires name field for consistency,\n // even though it's also used as the registration key\n form.registerField(name, {\n name: name,\n value: options.value,\n errors: displayErrors,\n });\n });\n\n onBeforeUnmount(() => {\n if (options.name) {\n form.unregisterField(options.name);\n }\n });\n }\n\n return {\n displayErrors,\n hasErrors\n };\n}\n","<script lang=\"ts\">\n/**\n * Internal component for displaying form field error messages.\n * This component is used by form input components to display validation errors\n * in a consistent way across all form fields.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Array of error messages to display\n */\n errors: string[];\n /**\n * ID for the error container (used for aria-describedby)\n */\n id: string;\n}\n\ndefineProps<Props>();\n</script>\n\n<template>\n <div\n v-if=\"errors.length > 0\"\n class=\"g-form-error-messages\"\n :id=\"id\"\n role=\"alert\"\n >\n <div\n v-for=\"(errorMsg, index) in errors\"\n :key=\"index\"\n class=\"g-form-error-message\"\n >\n <svg class=\"g-form-error-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\">\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M320 64C334.7 64 348.2 72.1 355.2 85L571.2 485C577.9 497.4 577.6 512.4 570.4 524.5C563.2 536.6 550.1 544 536 544L104 544C89.9 544 76.8 536.6 69.6 524.5C62.4 512.4 62.1 497.4 68.8 485L284.8 85C291.8 72.1 305.3 64 320 64zM320 416C302.3 416 288 430.3 288 448C288 465.7 302.3 480 320 480C337.7 480 352 465.7 352 448C352 430.3 337.7 416 320 416zM320 224C301.8 224 287.3 239.5 288.6 257.7L296 361.7C296.9 374.2 307.4 384 319.9 384C332.5 384 342.9 374.3 343.8 361.7L351.2 257.7C352.5 239.5 338.1 224 319.8 224z\"\n />\n </svg>\n {{ errorMsg }}\n </div>\n </div>\n</template>\n\n<style>\n.g-form-error-messages {\n display: flex;\n flex-direction: column;\n gap: 0.25em;\n margin-top: 0.25em;\n}\n\n.g-form-error-message {\n background: var(--g-surface-0);\n color: var(--g-danger-600);\n padding: 0.25em 0.5em;\n}\n\n.g-form-error-icon {\n height: 1.2em;\n padding: 0.2em 0;\n display: inline;\n margin: 0 0.2em 0 0;\n vertical-align: middle;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Internal component for displaying form field error messages.\n * This component is used by form input components to display validation errors\n * in a consistent way across all form fields.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Array of error messages to display\n */\n errors: string[];\n /**\n * ID for the error container (used for aria-describedby)\n */\n id: string;\n}\n\ndefineProps<Props>();\n</script>\n\n<template>\n <div\n v-if=\"errors.length > 0\"\n class=\"g-form-error-messages\"\n :id=\"id\"\n role=\"alert\"\n >\n <div\n v-for=\"(errorMsg, index) in errors\"\n :key=\"index\"\n class=\"g-form-error-message\"\n >\n <svg class=\"g-form-error-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\">\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M320 64C334.7 64 348.2 72.1 355.2 85L571.2 485C577.9 497.4 577.6 512.4 570.4 524.5C563.2 536.6 550.1 544 536 544L104 544C89.9 544 76.8 536.6 69.6 524.5C62.4 512.4 62.1 497.4 68.8 485L284.8 85C291.8 72.1 305.3 64 320 64zM320 416C302.3 416 288 430.3 288 448C288 465.7 302.3 480 320 480C337.7 480 352 465.7 352 448C352 430.3 337.7 416 320 416zM320 224C301.8 224 287.3 239.5 288.6 257.7L296 361.7C296.9 374.2 307.4 384 319.9 384C332.5 384 342.9 374.3 343.8 361.7L351.2 257.7C352.5 239.5 338.1 224 319.8 224z\"\n />\n </svg>\n {{ errorMsg }}\n </div>\n </div>\n</template>\n\n<style>\n.g-form-error-messages {\n display: flex;\n flex-direction: column;\n gap: 0.25em;\n margin-top: 0.25em;\n}\n\n.g-form-error-message {\n background: var(--g-surface-0);\n color: var(--g-danger-600);\n padding: 0.25em 0.5em;\n}\n\n.g-form-error-icon {\n height: 1.2em;\n padding: 0.2em 0;\n display: inline;\n margin: 0 0.2em 0 0;\n vertical-align: middle;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A text input with styling for a label, instructions, and error messages.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n * All non-prop attributes are passed through to the input element, including\n * `id`.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, ref, useId, watch, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Example Label\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Prefix text (displayed before input)\n * @demo\n */\n prefix?: string;\n /**\n * Suffix text (displayed after input)\n * @demo\n */\n suffix?: string;\n /**\n * Debounce in milliseconds\n * @demo\n */\n debounce?: number;\n\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n prefix: \"\",\n suffix: \"\",\n debounce: 100,\n name: undefined,\n formKey: undefined,\n});\nconst model = defineModel<string | null>({ type: String });\n\nconst id = useId();\nconst { attrs, isCustomElement, forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\nconst inputId = computed(() => {\n if (isCustomElement) {\n return id;\n }\n return (attrs.id as string) || id;\n});\n\n// Use form field composable for form registration and error handling\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [\n {\n was: string | null | undefined;\n to: string | null;\n },\n ];\n}>();\n\nconst lastInputValue = ref<string | null>(model.value ?? \"\");\nlet inputTimer: ReturnType<typeof setTimeout> | null = null;\n\nfunction emitChangeIfNeeded(val: string | null) {\n if (val !== model.value) {\n const prev = model.value;\n model.value = val;\n emit(\"change\", {\n was: prev,\n to: val,\n });\n }\n}\n\nfunction onInput(e: Event) {\n const value = (e.target as HTMLInputElement).value;\n lastInputValue.value = value;\n if (inputTimer) {\n clearTimeout(inputTimer);\n }\n inputTimer = setTimeout(() => {\n emitChangeIfNeeded(lastInputValue.value);\n inputTimer = null;\n }, props.debounce);\n}\n\nfunction onBlur(e: FocusEvent) {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n}\n\nfunction onPaste(e: ClipboardEvent) {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n // Wait for paste to update value\n setTimeout(() => {\n const value = (e.target as HTMLInputElement).value;\n emitChangeIfNeeded(value);\n }, 0);\n}\n\nfunction onKeydown(e: KeyboardEvent) {\n if (e.key === \"PageUp\" || e.key === \"PageDown\") {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n }\n if (e.key === \"Enter\") {\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n }\n}\n</script>\n\n<template>\n <div\n class=\"g-text-input-wrap\"\n :class=\"{ 'g-text-input-has-error': hasErrors }\"\n >\n <label\n v-if=\"props.label\"\n :for=\"inputId\"\n class=\"g-text-input-label\"\n >{{ props.label\n }}<span v-if=\"props.required\" class=\"g-text-input-required\" aria-hidden=\"true\"> *</span></label\n >\n <div\n v-if=\"$slots.instructions || instructions\"\n :id=\"'instructions-' + id\"\n class=\"g-text-input-instructions\"\n >\n <slot name=\"instructions\">{{ instructions }}</slot>\n </div>\n <div :class=\"[{\n 'g-text-input-field-wrapper': true,\n }, `g-text-input-field-wrapper--${name || 'nameless'}`]\">\n <span v-if=\"props.prefix\" class=\"g-text-input-prefix\">{{\n props.prefix\n }}</span>\n <input\n :value=\"model\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n @input=\"onInput\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n @keydown=\"onKeydown\"\n type=\"text\"\n class=\"g-text-input\"\n v-bind=\"{\n ...forwardedAttrs,\n id: inputId,\n 'aria-describedby':\n $slots.instructions || instructions\n ? 'instructions-' + id\n : undefined,\n 'aria-errormessage': hasErrors\n ? 'error-message-' + id\n : undefined,\n }\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n />\n <span v-if=\"props.suffix\" class=\"g-text-input-suffix\">{{\n props.suffix\n }}</span>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + id\"\n />\n </div>\n</template>\n\n<style>\ng-text-input {\n display: block;\n}\n.g-text-input-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n.g-text-input-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n.g-text-input-required {\n color: var(--g-danger-600);\n}\n.g-text-input-instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n}\n.g-text-input-field-wrapper {\n display: flex;\n align-items: center;\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n background: var(--g-surface-0);\n overflow: hidden;\n\n &:focus-within {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n.g-text-input-prefix,\n.g-text-input-suffix {\n padding: 0.5em;\n background: var(--g-surface-100);\n color: var(--g-surface-700);\n white-space: nowrap;\n font-family: var(--il-font-sans);\n}\n.g-text-input {\n width: 100%;\n padding: 0.5em;\n font-size: 1em;\n border: none;\n border-radius: 0;\n background: transparent;\n color: var(--g-surface-950);\n font-family: var(--il-font-sans);\n}\n.g-text-input:focus {\n outline: none;\n}\n.g-text-input-has-error {\n .g-text-input-field-wrapper {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n }\n}\n.g-text-input:disabled {\n background: transparent;\n color: var(--g-surface-700);\n}\n.g-text-input-field-wrapper:has(.g-text-input:disabled) {\n background: var(--g-surface-100);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A text input with styling for a label, instructions, and error messages.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n * All non-prop attributes are passed through to the input element, including\n * `id`.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, ref, useId, watch, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Example Label\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Prefix text (displayed before input)\n * @demo\n */\n prefix?: string;\n /**\n * Suffix text (displayed after input)\n * @demo\n */\n suffix?: string;\n /**\n * Debounce in milliseconds\n * @demo\n */\n debounce?: number;\n\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n prefix: \"\",\n suffix: \"\",\n debounce: 100,\n name: undefined,\n formKey: undefined,\n});\nconst model = defineModel<string | null>({ type: String });\n\nconst id = useId();\nconst { attrs, isCustomElement, forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\nconst inputId = computed(() => {\n if (isCustomElement) {\n return id;\n }\n return (attrs.id as string) || id;\n});\n\n// Use form field composable for form registration and error handling\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [\n {\n was: string | null | undefined;\n to: string | null;\n },\n ];\n}>();\n\nconst lastInputValue = ref<string | null>(model.value ?? \"\");\nlet inputTimer: ReturnType<typeof setTimeout> | null = null;\n\nfunction emitChangeIfNeeded(val: string | null) {\n if (val !== model.value) {\n const prev = model.value;\n model.value = val;\n emit(\"change\", {\n was: prev,\n to: val,\n });\n }\n}\n\nfunction onInput(e: Event) {\n const value = (e.target as HTMLInputElement).value;\n lastInputValue.value = value;\n if (inputTimer) {\n clearTimeout(inputTimer);\n }\n inputTimer = setTimeout(() => {\n emitChangeIfNeeded(lastInputValue.value);\n inputTimer = null;\n }, props.debounce);\n}\n\nfunction onBlur(e: FocusEvent) {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n}\n\nfunction onPaste(e: ClipboardEvent) {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n // Wait for paste to update value\n setTimeout(() => {\n const value = (e.target as HTMLInputElement).value;\n emitChangeIfNeeded(value);\n }, 0);\n}\n\nfunction onKeydown(e: KeyboardEvent) {\n if (e.key === \"PageUp\" || e.key === \"PageDown\") {\n if (inputTimer) {\n clearTimeout(inputTimer);\n inputTimer = null;\n }\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n }\n if (e.key === \"Enter\") {\n emitChangeIfNeeded((e.target as HTMLInputElement).value);\n }\n}\n</script>\n\n<template>\n <div\n class=\"g-text-input-wrap\"\n :class=\"{ 'g-text-input-has-error': hasErrors }\"\n >\n <label\n v-if=\"props.label\"\n :for=\"inputId\"\n class=\"g-text-input-label\"\n >{{ props.label\n }}<span v-if=\"props.required\" class=\"g-text-input-required\" aria-hidden=\"true\"> *</span></label\n >\n <div\n v-if=\"$slots.instructions || instructions\"\n :id=\"'instructions-' + id\"\n class=\"g-text-input-instructions\"\n >\n <slot name=\"instructions\">{{ instructions }}</slot>\n </div>\n <div :class=\"[{\n 'g-text-input-field-wrapper': true,\n }, `g-text-input-field-wrapper--${name || 'nameless'}`]\">\n <span v-if=\"props.prefix\" class=\"g-text-input-prefix\">{{\n props.prefix\n }}</span>\n <input\n :value=\"model\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n @input=\"onInput\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n @keydown=\"onKeydown\"\n type=\"text\"\n class=\"g-text-input\"\n v-bind=\"{\n ...forwardedAttrs,\n id: inputId,\n 'aria-describedby':\n $slots.instructions || instructions\n ? 'instructions-' + id\n : undefined,\n 'aria-errormessage': hasErrors\n ? 'error-message-' + id\n : undefined,\n }\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n />\n <span v-if=\"props.suffix\" class=\"g-text-input-suffix\">{{\n props.suffix\n }}</span>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + id\"\n />\n </div>\n</template>\n\n<style>\ng-text-input {\n display: block;\n}\n.g-text-input-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n.g-text-input-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n.g-text-input-required {\n color: var(--g-danger-600);\n}\n.g-text-input-instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n}\n.g-text-input-field-wrapper {\n display: flex;\n align-items: center;\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n background: var(--g-surface-0);\n overflow: hidden;\n\n &:focus-within {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n.g-text-input-prefix,\n.g-text-input-suffix {\n padding: 0.5em;\n background: var(--g-surface-100);\n color: var(--g-surface-700);\n white-space: nowrap;\n font-family: var(--il-font-sans);\n}\n.g-text-input {\n width: 100%;\n padding: 0.5em;\n font-size: 1em;\n border: none;\n border-radius: 0;\n background: transparent;\n color: var(--g-surface-950);\n font-family: var(--il-font-sans);\n}\n.g-text-input:focus {\n outline: none;\n}\n.g-text-input-has-error {\n .g-text-input-field-wrapper {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n }\n}\n.g-text-input:disabled {\n background: transparent;\n color: var(--g-surface-700);\n}\n.g-text-input-field-wrapper:has(.g-text-input:disabled) {\n background: var(--g-surface-100);\n}\n</style>\n\n","import { computed, onBeforeUnmount, Ref, ref } from \"vue\";\n\nconst OVERLAY_Z_INDEX_BASE = 100;\nconst MODAL_Z_INDEX_BASE = 200;\nconst DEFAULT_TOOLTIP_Z_INDEX = 102;\n\nexport type OverlayStack = {\n push: () => void;\n pop: () => void;\n isTop: Ref<boolean>;\n zIndex: Ref<number>;\n}\n\nexport type OverlayStackState = {\n hasModal: Ref<boolean>;\n hasOverlay: Ref<boolean>;\n hasScrollLock: Ref<boolean>;\n};\n\nconst stack = ref<string[]>([]);\nconst modalStack = ref<string[]>([]);\nconst scrollLockStack = ref<string[]>([]);\n\nfunction updateBodyScrollLock() {\n if (typeof document === \"undefined\") return;\n if (scrollLockStack.value.length > 0) {\n // Account for possible vertical scrollbar reducing viewport width\n const scrollbarWidth =\n window.innerWidth - document.documentElement.clientWidth;\n document.body.classList.add(\"g-scroll-lock\");\n document.body.style.paddingRight = `${scrollbarWidth}px`;\n document.body.style.setProperty(\"--g-scrollbar-width\", `${scrollbarWidth}px`);\n } else {\n document.body.style.paddingRight = \"0\";\n document.body.classList.remove(\"g-scroll-lock\");\n document.body.style.removeProperty(\"--g-scrollbar-width\");\n }\n}\n\nexport function useOverlayStack(id: string, modal = false, lockScroll = false): OverlayStack {\n if (typeof document === \"undefined\") {\n return {} as OverlayStack;\n }\n\n const stackRef = modal ? modalStack : stack;\n\n function push() {\n stackRef.value.push(id);\n if (lockScroll && !scrollLockStack.value.includes(id)) {\n scrollLockStack.value.push(id);\n updateBodyScrollLock();\n }\n }\n\n function pop() {\n const idx = stackRef.value.lastIndexOf(id);\n if (idx !== -1) {\n stackRef.value.splice(idx, 1);\n }\n const lockIdx = scrollLockStack.value.lastIndexOf(id);\n if (lockIdx !== -1) {\n scrollLockStack.value.splice(lockIdx, 1);\n updateBodyScrollLock();\n }\n }\n\n const isTop = computed(() => {\n if (!modal && modalStack.value.length > 0) {\n return false;\n }\n return (\n stackRef.value.length > 0 &&\n stackRef.value[stackRef.value.length - 1] === id\n );\n });\n\n const zIndex = computed(() => {\n const pos = stackRef.value.indexOf(id);\n return pos === -1 ? 0 : (modal ? MODAL_Z_INDEX_BASE : OVERLAY_Z_INDEX_BASE) + pos;\n });\n\n onBeforeUnmount(pop);\n\n return { push, pop, isTop, zIndex };\n}\n\nexport function useOverlayStackState(): OverlayStackState {\n if (typeof document === \"undefined\") {\n return {} as OverlayStackState;\n }\n\n const hasModal = computed(() => modalStack.value.length > 0);\n const hasOverlay = computed(\n () => stack.value.length > 0 || modalStack.value.length > 0,\n );\n const hasScrollLock = computed(() => scrollLockStack.value.length > 0);\n\n return { hasModal, hasOverlay, hasScrollLock };\n}\n\n/**\n * Returns a z-index value that is above all currently open overlays.\n * Uses the same base values as useOverlayStack: 100 + pos for non-modal,\n * 200 + pos for modal. Falls back to DEFAULT_TOOLTIP_Z_INDEX when no\n * overlays are open.\n *\n * This function can be called outside of a Vue component setup context,\n * which makes it suitable for use in Vue directives.\n */\nexport function getTopZIndex(): number {\n let max = 0;\n stack.value.forEach((_, idx) => {\n max = Math.max(max, OVERLAY_Z_INDEX_BASE + idx);\n });\n modalStack.value.forEach((_, idx) => {\n max = Math.max(max, MODAL_Z_INDEX_BASE + idx);\n });\n return max > 0 ? max + 1 : DEFAULT_TOOLTIP_Z_INDEX;\n}\n","import { computed, customRef, effectScope, getCurrentInstance, getCurrentScope, hasInjectionContext, inject, isReactive, isRef, nextTick, onBeforeMount, onBeforeUnmount, onMounted, onScopeDispose, onUnmounted, provide, reactive, readonly, ref, shallowReadonly, shallowRef, toRef as toRef$1, toRefs as toRefs$1, toValue, unref, watch, watchEffect } from \"vue\";\n\n//#region computedEager/index.ts\n/**\n*\n* @deprecated This function will be removed in future version.\n*\n* Note: If you are using Vue 3.4+, you can straight use computed instead.\n* Because in Vue 3.4+, if computed new value does not change,\n* computed, effect, watch, watchEffect, render dependencies will not be triggered.\n* refer: https://github.com/vuejs/core/pull/5912\n*\n* @param fn effect function\n* @param options WatchOptionsBase\n* @returns readonly shallowRef\n*/\nfunction computedEager(fn, options) {\n\tvar _options$flush;\n\tconst result = shallowRef();\n\twatchEffect(() => {\n\t\tresult.value = fn();\n\t}, {\n\t\t...options,\n\t\tflush: (_options$flush = options === null || options === void 0 ? void 0 : options.flush) !== null && _options$flush !== void 0 ? _options$flush : \"sync\"\n\t});\n\treturn readonly(result);\n}\n/** @deprecated use `computedEager` instead */\nconst eagerComputed = computedEager;\n\n//#endregion\n//#region computedWithControl/index.ts\n/**\n* Explicitly define the deps of computed.\n*\n* @param source\n* @param fn\n*/\nfunction computedWithControl(source, fn, options = {}) {\n\tlet v = void 0;\n\tlet track;\n\tlet trigger;\n\tlet dirty = true;\n\tconst update = () => {\n\t\tdirty = true;\n\t\ttrigger();\n\t};\n\twatch(source, update, {\n\t\tflush: \"sync\",\n\t\t...options\n\t});\n\tconst get$1 = typeof fn === \"function\" ? fn : fn.get;\n\tconst set$1 = typeof fn === \"function\" ? void 0 : fn.set;\n\tconst result = customRef((_track, _trigger) => {\n\t\ttrack = _track;\n\t\ttrigger = _trigger;\n\t\treturn {\n\t\t\tget() {\n\t\t\t\tif (dirty) {\n\t\t\t\t\tv = get$1(v);\n\t\t\t\t\tdirty = false;\n\t\t\t\t}\n\t\t\t\ttrack();\n\t\t\t\treturn v;\n\t\t\t},\n\t\t\tset(v$1) {\n\t\t\t\tset$1 === null || set$1 === void 0 || set$1(v$1);\n\t\t\t}\n\t\t};\n\t});\n\tresult.trigger = update;\n\treturn result;\n}\n/** @deprecated use `computedWithControl` instead */\nconst controlledComputed = computedWithControl;\n\n//#endregion\n//#region tryOnScopeDispose/index.ts\n/**\n* Call onScopeDispose() if it's inside an effect scope lifecycle, if not, do nothing\n*\n* @param fn\n*/\nfunction tryOnScopeDispose(fn, failSilently) {\n\tif (getCurrentScope()) {\n\t\tonScopeDispose(fn, failSilently);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n//#endregion\n//#region createEventHook/index.ts\n/**\n* Utility for creating event hooks\n*\n* @see https://vueuse.org/createEventHook\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction createEventHook() {\n\tconst fns = /* @__PURE__ */ new Set();\n\tconst off = (fn) => {\n\t\tfns.delete(fn);\n\t};\n\tconst clear = () => {\n\t\tfns.clear();\n\t};\n\tconst on = (fn) => {\n\t\tfns.add(fn);\n\t\tconst offFn = () => off(fn);\n\t\ttryOnScopeDispose(offFn);\n\t\treturn { off: offFn };\n\t};\n\tconst trigger = (...args) => {\n\t\treturn Promise.all(Array.from(fns).map((fn) => fn(...args)));\n\t};\n\treturn {\n\t\ton,\n\t\toff,\n\t\ttrigger,\n\t\tclear\n\t};\n}\n\n//#endregion\n//#region createGlobalState/index.ts\n/**\n* Keep states in the global scope to be reusable across Vue instances.\n*\n* @see https://vueuse.org/createGlobalState\n* @param stateFactory A factory function to create the state\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction createGlobalState(stateFactory) {\n\tlet initialized = false;\n\tlet state;\n\tconst scope = effectScope(true);\n\treturn ((...args) => {\n\t\tif (!initialized) {\n\t\t\tstate = scope.run(() => stateFactory(...args));\n\t\t\tinitialized = true;\n\t\t}\n\t\treturn state;\n\t});\n}\n\n//#endregion\n//#region provideLocal/map.ts\nconst localProvidedStateMap = /* @__PURE__ */ new WeakMap();\n\n//#endregion\n//#region injectLocal/index.ts\n/**\n* On the basis of `inject`, it is allowed to directly call inject to obtain the value after call provide in the same component.\n*\n* @example\n* ```ts\n* injectLocal('MyInjectionKey', 1)\n* const injectedValue = injectLocal('MyInjectionKey') // injectedValue === 1\n* ```\n*\n* @__NO_SIDE_EFFECTS__\n*/\nconst injectLocal = (...args) => {\n\tvar _getCurrentInstance;\n\tconst key = args[0];\n\tconst instance = (_getCurrentInstance = getCurrentInstance()) === null || _getCurrentInstance === void 0 ? void 0 : _getCurrentInstance.proxy;\n\tconst owner = instance !== null && instance !== void 0 ? instance : getCurrentScope();\n\tif (owner == null && !hasInjectionContext()) throw new Error(\"injectLocal must be called in setup\");\n\tif (owner && localProvidedStateMap.has(owner) && key in localProvidedStateMap.get(owner)) return localProvidedStateMap.get(owner)[key];\n\treturn inject(...args);\n};\n\n//#endregion\n//#region provideLocal/index.ts\n/**\n* On the basis of `provide`, it is allowed to directly call inject to obtain the value after call provide in the same component.\n*\n* @example\n* ```ts\n* provideLocal('MyInjectionKey', 1)\n* const injectedValue = injectLocal('MyInjectionKey') // injectedValue === 1\n* ```\n*/\nfunction provideLocal(key, value) {\n\tvar _getCurrentInstance;\n\tconst instance = (_getCurrentInstance = getCurrentInstance()) === null || _getCurrentInstance === void 0 ? void 0 : _getCurrentInstance.proxy;\n\tconst owner = instance !== null && instance !== void 0 ? instance : getCurrentScope();\n\tif (owner == null) throw new Error(\"provideLocal must be called in setup\");\n\tif (!localProvidedStateMap.has(owner)) localProvidedStateMap.set(owner, Object.create(null));\n\tconst localProvidedState = localProvidedStateMap.get(owner);\n\tlocalProvidedState[key] = value;\n\treturn provide(key, value);\n}\n\n//#endregion\n//#region createInjectionState/index.ts\n/**\n* Create global state that can be injected into components.\n*\n* @see https://vueuse.org/createInjectionState\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction createInjectionState(composable, options) {\n\tconst key = (options === null || options === void 0 ? void 0 : options.injectionKey) || Symbol(composable.name || \"InjectionState\");\n\tconst defaultValue = options === null || options === void 0 ? void 0 : options.defaultValue;\n\tconst useProvidingState = (...args) => {\n\t\tconst state = composable(...args);\n\t\tprovideLocal(key, state);\n\t\treturn state;\n\t};\n\tconst useInjectedState = () => injectLocal(key, defaultValue);\n\treturn [useProvidingState, useInjectedState];\n}\n\n//#endregion\n//#region createRef/index.ts\n/**\n* Returns a `deepRef` or `shallowRef` depending on the `deep` param.\n*\n* @example createRef(1) // ShallowRef<number>\n* @example createRef(1, false) // ShallowRef<number>\n* @example createRef(1, true) // Ref<number>\n* @example createRef(\"string\") // ShallowRef<string>\n* @example createRef<\"A\"|\"B\">(\"A\", true) // Ref<\"A\"|\"B\">\n*\n* @param value\n* @param deep\n* @returns the `deepRef` or `shallowRef`\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction createRef(value, deep) {\n\tif (deep === true) return ref(value);\n\telse return shallowRef(value);\n}\n\n//#endregion\n//#region utils/is.ts\nconst isClient = typeof window !== \"undefined\" && typeof document !== \"undefined\";\nconst isWorker = typeof WorkerGlobalScope !== \"undefined\" && globalThis instanceof WorkerGlobalScope;\nconst isDef = (val) => typeof val !== \"undefined\";\nconst notNullish = (val) => val != null;\nconst assert = (condition, ...infos) => {\n\tif (!condition) console.warn(...infos);\n};\nconst toString = Object.prototype.toString;\nconst isObject = (val) => toString.call(val) === \"[object Object]\";\nconst now = () => Date.now();\nconst timestamp = () => +Date.now();\nconst clamp = (n, min, max) => Math.min(max, Math.max(min, n));\nconst noop = () => {};\nconst rand = (min, max) => {\n\tmin = Math.ceil(min);\n\tmax = Math.floor(max);\n\treturn Math.floor(Math.random() * (max - min + 1)) + min;\n};\nconst hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);\nconst isIOS = /* @__PURE__ */ getIsIOS();\nfunction getIsIOS() {\n\tvar _window, _window2, _window3;\n\treturn isClient && !!((_window = window) === null || _window === void 0 || (_window = _window.navigator) === null || _window === void 0 ? void 0 : _window.userAgent) && (/iP(?:ad|hone|od)/.test(window.navigator.userAgent) || ((_window2 = window) === null || _window2 === void 0 || (_window2 = _window2.navigator) === null || _window2 === void 0 ? void 0 : _window2.maxTouchPoints) > 2 && /iPad|Macintosh/.test((_window3 = window) === null || _window3 === void 0 ? void 0 : _window3.navigator.userAgent));\n}\n\n//#endregion\n//#region toRef/index.ts\nfunction toRef(...args) {\n\tif (args.length !== 1) return toRef$1(...args);\n\tconst r = args[0];\n\treturn typeof r === \"function\" ? readonly(customRef(() => ({\n\t\tget: r,\n\t\tset: noop\n\t}))) : ref(r);\n}\n\n//#endregion\n//#region utils/filters.ts\n/**\n* @internal\n*/\nfunction createFilterWrapper(filter, fn) {\n\tfunction wrapper(...args) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tPromise.resolve(filter(() => fn.apply(this, args), {\n\t\t\t\tfn,\n\t\t\t\tthisArg: this,\n\t\t\t\targs\n\t\t\t})).then(resolve).catch(reject);\n\t\t});\n\t}\n\treturn wrapper;\n}\nconst bypassFilter = (invoke$1) => {\n\treturn invoke$1();\n};\n/**\n* Create an EventFilter that debounce the events\n*/\nfunction debounceFilter(ms, options = {}) {\n\tlet timer;\n\tlet maxTimer;\n\tlet lastRejector = noop;\n\tconst _clearTimeout = (timer$1) => {\n\t\tclearTimeout(timer$1);\n\t\tlastRejector();\n\t\tlastRejector = noop;\n\t};\n\tlet lastInvoker;\n\tconst filter = (invoke$1) => {\n\t\tconst duration = toValue(ms);\n\t\tconst maxDuration = toValue(options.maxWait);\n\t\tif (timer) _clearTimeout(timer);\n\t\tif (duration <= 0 || maxDuration !== void 0 && maxDuration <= 0) {\n\t\t\tif (maxTimer) {\n\t\t\t\t_clearTimeout(maxTimer);\n\t\t\t\tmaxTimer = void 0;\n\t\t\t}\n\t\t\treturn Promise.resolve(invoke$1());\n\t\t}\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlastRejector = options.rejectOnCancel ? reject : resolve;\n\t\t\tlastInvoker = invoke$1;\n\t\t\tif (maxDuration && !maxTimer) maxTimer = setTimeout(() => {\n\t\t\t\tif (timer) _clearTimeout(timer);\n\t\t\t\tmaxTimer = void 0;\n\t\t\t\tresolve(lastInvoker());\n\t\t\t}, maxDuration);\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tif (maxTimer) _clearTimeout(maxTimer);\n\t\t\t\tmaxTimer = void 0;\n\t\t\t\tresolve(invoke$1());\n\t\t\t}, duration);\n\t\t});\n\t};\n\treturn filter;\n}\nfunction throttleFilter(...args) {\n\tlet lastExec = 0;\n\tlet timer;\n\tlet isLeading = true;\n\tlet lastRejector = noop;\n\tlet lastValue;\n\tlet ms;\n\tlet trailing;\n\tlet leading;\n\tlet rejectOnCancel;\n\tif (!isRef(args[0]) && typeof args[0] === \"object\") ({delay: ms, trailing = true, leading = true, rejectOnCancel = false} = args[0]);\n\telse [ms, trailing = true, leading = true, rejectOnCancel = false] = args;\n\tconst clear = () => {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = void 0;\n\t\t\tlastRejector();\n\t\t\tlastRejector = noop;\n\t\t}\n\t};\n\tconst filter = (_invoke) => {\n\t\tconst duration = toValue(ms);\n\t\tconst elapsed = Date.now() - lastExec;\n\t\tconst invoke$1 = () => {\n\t\t\treturn lastValue = _invoke();\n\t\t};\n\t\tclear();\n\t\tif (duration <= 0) {\n\t\t\tlastExec = Date.now();\n\t\t\treturn invoke$1();\n\t\t}\n\t\tif (elapsed > duration) {\n\t\t\tlastExec = Date.now();\n\t\t\tif (leading || !isLeading) invoke$1();\n\t\t} else if (trailing) lastValue = new Promise((resolve, reject) => {\n\t\t\tlastRejector = rejectOnCancel ? reject : resolve;\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tlastExec = Date.now();\n\t\t\t\tisLeading = true;\n\t\t\t\tresolve(invoke$1());\n\t\t\t\tclear();\n\t\t\t}, Math.max(0, duration - elapsed));\n\t\t});\n\t\tif (!leading && !timer) timer = setTimeout(() => isLeading = true, duration);\n\t\tisLeading = false;\n\t\treturn lastValue;\n\t};\n\treturn filter;\n}\n/**\n* EventFilter that gives extra controls to pause and resume the filter\n*\n* @param extendFilter Extra filter to apply when the PausableFilter is active, default to none\n* @param options Options to configure the filter\n*/\nfunction pausableFilter(extendFilter = bypassFilter, options = {}) {\n\tconst { initialState = \"active\" } = options;\n\tconst isActive = toRef(initialState === \"active\");\n\tfunction pause() {\n\t\tisActive.value = false;\n\t}\n\tfunction resume() {\n\t\tisActive.value = true;\n\t}\n\tconst eventFilter = (...args) => {\n\t\tif (isActive.value) extendFilter(...args);\n\t};\n\treturn {\n\t\tisActive: readonly(isActive),\n\t\tpause,\n\t\tresume,\n\t\teventFilter\n\t};\n}\n\n//#endregion\n//#region utils/general.ts\nfunction promiseTimeout(ms, throwOnTimeout = false, reason = \"Timeout\") {\n\treturn new Promise((resolve, reject) => {\n\t\tif (throwOnTimeout) setTimeout(() => reject(reason), ms);\n\t\telse setTimeout(resolve, ms);\n\t});\n}\nfunction identity(arg) {\n\treturn arg;\n}\n/**\n* Create singleton promise function\n*\n* @example\n* ```\n* const promise = createSingletonPromise(async () => { ... })\n*\n* await promise()\n* await promise() // all of them will be bind to a single promise instance\n* await promise() // and be resolved together\n* ```\n*/\nfunction createSingletonPromise(fn) {\n\tlet _promise;\n\tfunction wrapper() {\n\t\tif (!_promise) _promise = fn();\n\t\treturn _promise;\n\t}\n\twrapper.reset = async () => {\n\t\tconst _prev = _promise;\n\t\t_promise = void 0;\n\t\tif (_prev) await _prev;\n\t};\n\treturn wrapper;\n}\nfunction invoke(fn) {\n\treturn fn();\n}\nfunction containsProp(obj, ...props) {\n\treturn props.some((k) => k in obj);\n}\nfunction increaseWithUnit(target, delta) {\n\tvar _target$match;\n\tif (typeof target === \"number\") return target + delta;\n\tconst value = ((_target$match = target.match(/^-?\\d+\\.?\\d*/)) === null || _target$match === void 0 ? void 0 : _target$match[0]) || \"\";\n\tconst unit = target.slice(value.length);\n\tconst result = Number.parseFloat(value) + delta;\n\tif (Number.isNaN(result)) return target;\n\treturn result + unit;\n}\n/**\n* Get a px value for SSR use, do not rely on this method outside of SSR as REM unit is assumed at 16px, which might not be the case on the client\n*/\nfunction pxValue(px) {\n\treturn px.endsWith(\"rem\") ? Number.parseFloat(px) * 16 : Number.parseFloat(px);\n}\n/**\n* Create a new subset object by giving keys\n*/\nfunction objectPick(obj, keys, omitUndefined = false) {\n\treturn keys.reduce((n, k) => {\n\t\tif (k in obj) {\n\t\t\tif (!omitUndefined || obj[k] !== void 0) n[k] = obj[k];\n\t\t}\n\t\treturn n;\n\t}, {});\n}\n/**\n* Create a new subset object by omit giving keys\n*/\nfunction objectOmit(obj, keys, omitUndefined = false) {\n\treturn Object.fromEntries(Object.entries(obj).filter(([key, value]) => {\n\t\treturn (!omitUndefined || value !== void 0) && !keys.includes(key);\n\t}));\n}\nfunction objectEntries(obj) {\n\treturn Object.entries(obj);\n}\nfunction toArray(value) {\n\treturn Array.isArray(value) ? value : [value];\n}\n\n//#endregion\n//#region utils/port.ts\nfunction cacheStringFunction(fn) {\n\tconst cache = Object.create(null);\n\treturn ((str) => {\n\t\treturn cache[str] || (cache[str] = fn(str));\n\t});\n}\nconst hyphenateRE = /\\B([A-Z])/g;\nconst hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, \"-$1\").toLowerCase());\nconst camelizeRE = /-(\\w)/g;\nconst camelize = cacheStringFunction((str) => {\n\treturn str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : \"\");\n});\n\n//#endregion\n//#region utils/vue.ts\nfunction getLifeCycleTarget(target) {\n\treturn target || getCurrentInstance();\n}\n\n//#endregion\n//#region createSharedComposable/index.ts\n/**\n* Make a composable function usable with multiple Vue instances.\n*\n* @see https://vueuse.org/createSharedComposable\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction createSharedComposable(composable) {\n\tif (!isClient) return composable;\n\tlet subscribers = 0;\n\tlet state;\n\tlet scope;\n\tconst dispose = () => {\n\t\tsubscribers -= 1;\n\t\tif (scope && subscribers <= 0) {\n\t\t\tscope.stop();\n\t\t\tstate = void 0;\n\t\t\tscope = void 0;\n\t\t}\n\t};\n\treturn ((...args) => {\n\t\tsubscribers += 1;\n\t\tif (!scope) {\n\t\t\tscope = effectScope(true);\n\t\t\tstate = scope.run(() => composable(...args));\n\t\t}\n\t\ttryOnScopeDispose(dispose);\n\t\treturn state;\n\t});\n}\n\n//#endregion\n//#region extendRef/index.ts\nfunction extendRef(ref$1, extend, { enumerable = false, unwrap = true } = {}) {\n\tfor (const [key, value] of Object.entries(extend)) {\n\t\tif (key === \"value\") continue;\n\t\tif (isRef(value) && unwrap) Object.defineProperty(ref$1, key, {\n\t\t\tget() {\n\t\t\t\treturn value.value;\n\t\t\t},\n\t\t\tset(v) {\n\t\t\t\tvalue.value = v;\n\t\t\t},\n\t\t\tenumerable\n\t\t});\n\t\telse Object.defineProperty(ref$1, key, {\n\t\t\tvalue,\n\t\t\tenumerable\n\t\t});\n\t}\n\treturn ref$1;\n}\n\n//#endregion\n//#region get/index.ts\nfunction get(obj, key) {\n\tif (key == null) return unref(obj);\n\treturn unref(obj)[key];\n}\n\n//#endregion\n//#region isDefined/index.ts\nfunction isDefined(v) {\n\treturn unref(v) != null;\n}\n\n//#endregion\n//#region makeDestructurable/index.ts\n/* @__NO_SIDE_EFFECTS__ */\nfunction makeDestructurable(obj, arr) {\n\tif (typeof Symbol !== \"undefined\") {\n\t\tconst clone = { ...obj };\n\t\tObject.defineProperty(clone, Symbol.iterator, {\n\t\t\tenumerable: false,\n\t\t\tvalue() {\n\t\t\t\tlet index = 0;\n\t\t\t\treturn { next: () => ({\n\t\t\t\t\tvalue: arr[index++],\n\t\t\t\t\tdone: index > arr.length\n\t\t\t\t}) };\n\t\t\t}\n\t\t});\n\t\treturn clone;\n\t} else return Object.assign([...arr], obj);\n}\n\n//#endregion\n//#region reactify/index.ts\n/**\n* Converts plain function into a reactive function.\n* The converted function accepts refs as it's arguments\n* and returns a ComputedRef, with proper typing.\n*\n* @param fn - Source function\n* @param options - Options\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction reactify(fn, options) {\n\tconst unrefFn = (options === null || options === void 0 ? void 0 : options.computedGetter) === false ? unref : toValue;\n\treturn function(...args) {\n\t\treturn computed(() => fn.apply(this, args.map((i) => unrefFn(i))));\n\t};\n}\n/** @deprecated use `reactify` instead */\nconst createReactiveFn = reactify;\n\n//#endregion\n//#region reactifyObject/index.ts\n/**\n* Apply `reactify` to an object\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction reactifyObject(obj, optionsOrKeys = {}) {\n\tlet keys = [];\n\tlet options;\n\tif (Array.isArray(optionsOrKeys)) keys = optionsOrKeys;\n\telse {\n\t\toptions = optionsOrKeys;\n\t\tconst { includeOwnProperties = true } = optionsOrKeys;\n\t\tkeys.push(...Object.keys(obj));\n\t\tif (includeOwnProperties) keys.push(...Object.getOwnPropertyNames(obj));\n\t}\n\treturn Object.fromEntries(keys.map((key) => {\n\t\tconst value = obj[key];\n\t\treturn [key, typeof value === \"function\" ? reactify(value.bind(obj), options) : value];\n\t}));\n}\n\n//#endregion\n//#region toReactive/index.ts\n/**\n* Converts ref to reactive.\n*\n* @see https://vueuse.org/toReactive\n* @param objectRef A ref of object\n*/\nfunction toReactive(objectRef) {\n\tif (!isRef(objectRef)) return reactive(objectRef);\n\treturn reactive(new Proxy({}, {\n\t\tget(_, p, receiver) {\n\t\t\treturn unref(Reflect.get(objectRef.value, p, receiver));\n\t\t},\n\t\tset(_, p, value) {\n\t\t\tif (isRef(objectRef.value[p]) && !isRef(value)) objectRef.value[p].value = value;\n\t\t\telse objectRef.value[p] = value;\n\t\t\treturn true;\n\t\t},\n\t\tdeleteProperty(_, p) {\n\t\t\treturn Reflect.deleteProperty(objectRef.value, p);\n\t\t},\n\t\thas(_, p) {\n\t\t\treturn Reflect.has(objectRef.value, p);\n\t\t},\n\t\townKeys() {\n\t\t\treturn Object.keys(objectRef.value);\n\t\t},\n\t\tgetOwnPropertyDescriptor() {\n\t\t\treturn {\n\t\t\t\tenumerable: true,\n\t\t\t\tconfigurable: true\n\t\t\t};\n\t\t}\n\t}));\n}\n\n//#endregion\n//#region reactiveComputed/index.ts\n/**\n* Computed reactive object.\n*/\nfunction reactiveComputed(fn) {\n\treturn toReactive(computed(fn));\n}\n\n//#endregion\n//#region reactiveOmit/index.ts\n/**\n* Reactively omit fields from a reactive object\n*\n* @see https://vueuse.org/reactiveOmit\n*/\nfunction reactiveOmit(obj, ...keys) {\n\tconst flatKeys = keys.flat();\n\tconst predicate = flatKeys[0];\n\treturn reactiveComputed(() => typeof predicate === \"function\" ? Object.fromEntries(Object.entries(toRefs$1(obj)).filter(([k, v]) => !predicate(toValue(v), k))) : Object.fromEntries(Object.entries(toRefs$1(obj)).filter((e) => !flatKeys.includes(e[0]))));\n}\n\n//#endregion\n//#region reactivePick/index.ts\n/**\n* Reactively pick fields from a reactive object\n*\n* @see https://vueuse.org/reactivePick\n*/\nfunction reactivePick(obj, ...keys) {\n\tconst flatKeys = keys.flat();\n\tconst predicate = flatKeys[0];\n\treturn reactiveComputed(() => typeof predicate === \"function\" ? Object.fromEntries(Object.entries(toRefs$1(obj)).filter(([k, v]) => predicate(toValue(v), k))) : Object.fromEntries(flatKeys.map((k) => [k, toRef(obj, k)])));\n}\n\n//#endregion\n//#region refAutoReset/index.ts\n/**\n* Create a ref which will be reset to the default value after some time.\n*\n* @see https://vueuse.org/refAutoReset\n* @param defaultValue The value which will be set.\n* @param afterMs A zero-or-greater delay in milliseconds.\n*/\nfunction refAutoReset(defaultValue, afterMs = 1e4) {\n\treturn customRef((track, trigger) => {\n\t\tlet value = toValue(defaultValue);\n\t\tlet timer;\n\t\tconst resetAfter = () => setTimeout(() => {\n\t\t\tvalue = toValue(defaultValue);\n\t\t\ttrigger();\n\t\t}, toValue(afterMs));\n\t\ttryOnScopeDispose(() => {\n\t\t\tclearTimeout(timer);\n\t\t});\n\t\treturn {\n\t\t\tget() {\n\t\t\t\ttrack();\n\t\t\t\treturn value;\n\t\t\t},\n\t\t\tset(newValue) {\n\t\t\t\tvalue = newValue;\n\t\t\t\ttrigger();\n\t\t\t\tclearTimeout(timer);\n\t\t\t\ttimer = resetAfter();\n\t\t\t}\n\t\t};\n\t});\n}\n/** @deprecated use `refAutoReset` instead */\nconst autoResetRef = refAutoReset;\n\n//#endregion\n//#region useDebounceFn/index.ts\n/**\n* Debounce execution of a function.\n*\n* @see https://vueuse.org/useDebounceFn\n* @param fn A function to be executed after delay milliseconds debounced.\n* @param ms A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.\n* @param options Options\n*\n* @return A new, debounce, function.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useDebounceFn(fn, ms = 200, options = {}) {\n\treturn createFilterWrapper(debounceFilter(ms, options), fn);\n}\n\n//#endregion\n//#region refDebounced/index.ts\n/**\n* Debounce updates of a ref.\n*\n* @return A new debounced ref.\n*/\nfunction refDebounced(value, ms = 200, options = {}) {\n\tconst debounced = ref(toValue(value));\n\tconst updater = useDebounceFn(() => {\n\t\tdebounced.value = value.value;\n\t}, ms, options);\n\twatch(value, () => updater());\n\treturn shallowReadonly(debounced);\n}\n/** @deprecated use `refDebounced` instead */\nconst debouncedRef = refDebounced;\n/** @deprecated use `refDebounced` instead */\nconst useDebounce = refDebounced;\n\n//#endregion\n//#region refDefault/index.ts\n/**\n* Apply default value to a ref.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction refDefault(source, defaultValue) {\n\treturn computed({\n\t\tget() {\n\t\t\tvar _source$value;\n\t\t\treturn (_source$value = source.value) !== null && _source$value !== void 0 ? _source$value : defaultValue;\n\t\t},\n\t\tset(value) {\n\t\t\tsource.value = value;\n\t\t}\n\t});\n}\n\n//#endregion\n//#region refManualReset/index.ts\n/**\n* Create a ref with manual reset functionality.\n*\n* @see https://vueuse.org/refManualReset\n* @param defaultValue The value which will be set.\n*/\nfunction refManualReset(defaultValue) {\n\tlet value = toValue(defaultValue);\n\tlet trigger;\n\tconst reset = () => {\n\t\tvalue = toValue(defaultValue);\n\t\ttrigger();\n\t};\n\tconst refValue = customRef((track, _trigger) => {\n\t\ttrigger = _trigger;\n\t\treturn {\n\t\t\tget() {\n\t\t\t\ttrack();\n\t\t\t\treturn value;\n\t\t\t},\n\t\t\tset(newValue) {\n\t\t\t\tvalue = newValue;\n\t\t\t\ttrigger();\n\t\t\t}\n\t\t};\n\t});\n\trefValue.reset = reset;\n\treturn refValue;\n}\n\n//#endregion\n//#region useThrottleFn/index.ts\n/**\n* Throttle execution of a function. Especially useful for rate limiting\n* execution of handlers on events like resize and scroll.\n*\n* @param fn A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is,\n* to `callback` when the throttled-function is executed.\n* @param ms A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.\n* (default value: 200)\n*\n* @param [trailing] if true, call fn again after the time is up (default value: false)\n*\n* @param [leading] if true, call fn on the leading edge of the ms timeout (default value: true)\n*\n* @param [rejectOnCancel] if true, reject the last call if it's been cancel (default value: false)\n*\n* @return A new, throttled, function.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useThrottleFn(fn, ms = 200, trailing = false, leading = true, rejectOnCancel = false) {\n\treturn createFilterWrapper(throttleFilter(ms, trailing, leading, rejectOnCancel), fn);\n}\n\n//#endregion\n//#region refThrottled/index.ts\n/**\n* Throttle execution of a function. Especially useful for rate limiting\n* execution of handlers on events like resize and scroll.\n*\n* @param value Ref value to be watched with throttle effect\n* @param delay A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.\n* @param trailing if true, update the value again after the delay time is up\n* @param leading if true, update the value on the leading edge of the ms timeout\n*/\nfunction refThrottled(value, delay = 200, trailing = true, leading = true) {\n\tif (delay <= 0) return value;\n\tconst throttled = ref(toValue(value));\n\tconst updater = useThrottleFn(() => {\n\t\tthrottled.value = value.value;\n\t}, delay, trailing, leading);\n\twatch(value, () => updater());\n\treturn throttled;\n}\n/** @deprecated use `refThrottled` instead */\nconst throttledRef = refThrottled;\n/** @deprecated use `refThrottled` instead */\nconst useThrottle = refThrottled;\n\n//#endregion\n//#region refWithControl/index.ts\n/**\n* Fine-grained controls over ref and its reactivity.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction refWithControl(initial, options = {}) {\n\tlet source = initial;\n\tlet track;\n\tlet trigger;\n\tconst ref$1 = customRef((_track, _trigger) => {\n\t\ttrack = _track;\n\t\ttrigger = _trigger;\n\t\treturn {\n\t\t\tget() {\n\t\t\t\treturn get$1();\n\t\t\t},\n\t\t\tset(v) {\n\t\t\t\tset$1(v);\n\t\t\t}\n\t\t};\n\t});\n\tfunction get$1(tracking = true) {\n\t\tif (tracking) track();\n\t\treturn source;\n\t}\n\tfunction set$1(value, triggering = true) {\n\t\tvar _options$onBeforeChan, _options$onChanged;\n\t\tif (value === source) return;\n\t\tconst old = source;\n\t\tif (((_options$onBeforeChan = options.onBeforeChange) === null || _options$onBeforeChan === void 0 ? void 0 : _options$onBeforeChan.call(options, value, old)) === false) return;\n\t\tsource = value;\n\t\t(_options$onChanged = options.onChanged) === null || _options$onChanged === void 0 || _options$onChanged.call(options, value, old);\n\t\tif (triggering) trigger();\n\t}\n\t/**\n\t* Get the value without tracked in the reactivity system\n\t*/\n\tconst untrackedGet = () => get$1(false);\n\t/**\n\t* Set the value without triggering the reactivity system\n\t*/\n\tconst silentSet = (v) => set$1(v, false);\n\t/**\n\t* Get the value without tracked in the reactivity system.\n\t*\n\t* Alias for `untrackedGet()`\n\t*/\n\tconst peek = () => get$1(false);\n\t/**\n\t* Set the value without triggering the reactivity system\n\t*\n\t* Alias for `silentSet(v)`\n\t*/\n\tconst lay = (v) => set$1(v, false);\n\treturn extendRef(ref$1, {\n\t\tget: get$1,\n\t\tset: set$1,\n\t\tuntrackedGet,\n\t\tsilentSet,\n\t\tpeek,\n\t\tlay\n\t}, { enumerable: true });\n}\n/** @deprecated use `refWithControl` instead */\nconst controlledRef = refWithControl;\n\n//#endregion\n//#region set/index.ts\n/**\n* Shorthand for `ref.value = x`\n*/\nfunction set(...args) {\n\tif (args.length === 2) {\n\t\tconst [ref$1, value] = args;\n\t\tref$1.value = value;\n\t}\n\tif (args.length === 3) {\n\t\tconst [target, key, value] = args;\n\t\ttarget[key] = value;\n\t}\n}\n\n//#endregion\n//#region watchWithFilter/index.ts\nfunction watchWithFilter(source, cb, options = {}) {\n\tconst { eventFilter = bypassFilter,...watchOptions } = options;\n\treturn watch(source, createFilterWrapper(eventFilter, cb), watchOptions);\n}\n\n//#endregion\n//#region watchPausable/index.ts\n/** @deprecated Use Vue's built-in `watch` instead. This function will be removed in future version. */\nfunction watchPausable(source, cb, options = {}) {\n\tconst { eventFilter: filter, initialState = \"active\",...watchOptions } = options;\n\tconst { eventFilter, pause, resume, isActive } = pausableFilter(filter, { initialState });\n\treturn {\n\t\tstop: watchWithFilter(source, cb, {\n\t\t\t...watchOptions,\n\t\t\teventFilter\n\t\t}),\n\t\tpause,\n\t\tresume,\n\t\tisActive\n\t};\n}\n/** @deprecated Use Vue's built-in `watch` instead. This function will be removed in future version. */\nconst pausableWatch = watchPausable;\n\n//#endregion\n//#region syncRef/index.ts\n/**\n* Two-way refs synchronization.\n* From the set theory perspective to restrict the option's type\n* Check in the following order:\n* 1. L = R\n* 2. L ∩ R ≠ ∅\n* 3. L ⊆ R\n* 4. L ∩ R = ∅\n*/\nfunction syncRef(left, right, ...[options]) {\n\tconst { flush = \"sync\", deep = false, immediate = true, direction = \"both\", transform = {} } = options || {};\n\tconst watchers = [];\n\tconst transformLTR = \"ltr\" in transform && transform.ltr || ((v) => v);\n\tconst transformRTL = \"rtl\" in transform && transform.rtl || ((v) => v);\n\tif (direction === \"both\" || direction === \"ltr\") watchers.push(watchPausable(left, (newValue) => {\n\t\twatchers.forEach((w) => w.pause());\n\t\tright.value = transformLTR(newValue);\n\t\twatchers.forEach((w) => w.resume());\n\t}, {\n\t\tflush,\n\t\tdeep,\n\t\timmediate\n\t}));\n\tif (direction === \"both\" || direction === \"rtl\") watchers.push(watchPausable(right, (newValue) => {\n\t\twatchers.forEach((w) => w.pause());\n\t\tleft.value = transformRTL(newValue);\n\t\twatchers.forEach((w) => w.resume());\n\t}, {\n\t\tflush,\n\t\tdeep,\n\t\timmediate\n\t}));\n\tconst stop = () => {\n\t\twatchers.forEach((w) => w.stop());\n\t};\n\treturn stop;\n}\n\n//#endregion\n//#region syncRefs/index.ts\n/**\n* Keep target ref(s) in sync with the source ref\n*\n* @param source source ref\n* @param targets\n*/\nfunction syncRefs(source, targets, options = {}) {\n\tconst { flush = \"sync\", deep = false, immediate = true } = options;\n\tconst targetsArray = toArray(targets);\n\treturn watch(source, (newValue) => targetsArray.forEach((target) => target.value = newValue), {\n\t\tflush,\n\t\tdeep,\n\t\timmediate\n\t});\n}\n\n//#endregion\n//#region toRefs/index.ts\n/**\n* Extended `toRefs` that also accepts refs of an object.\n*\n* @see https://vueuse.org/toRefs\n* @param objectRef A ref or normal object or array.\n* @param options Options\n*/\nfunction toRefs(objectRef, options = {}) {\n\tif (!isRef(objectRef)) return toRefs$1(objectRef);\n\tconst result = Array.isArray(objectRef.value) ? Array.from({ length: objectRef.value.length }) : {};\n\tfor (const key in objectRef.value) result[key] = customRef(() => ({\n\t\tget() {\n\t\t\treturn objectRef.value[key];\n\t\t},\n\t\tset(v) {\n\t\t\tvar _toValue;\n\t\t\tif ((_toValue = toValue(options.replaceRef)) !== null && _toValue !== void 0 ? _toValue : true) if (Array.isArray(objectRef.value)) {\n\t\t\t\tconst copy = [...objectRef.value];\n\t\t\t\tcopy[key] = v;\n\t\t\t\tobjectRef.value = copy;\n\t\t\t} else {\n\t\t\t\tconst newObject = {\n\t\t\t\t\t...objectRef.value,\n\t\t\t\t\t[key]: v\n\t\t\t\t};\n\t\t\t\tObject.setPrototypeOf(newObject, Object.getPrototypeOf(objectRef.value));\n\t\t\t\tobjectRef.value = newObject;\n\t\t\t}\n\t\t\telse objectRef.value[key] = v;\n\t\t}\n\t}));\n\treturn result;\n}\n\n//#endregion\n//#region tryOnBeforeMount/index.ts\n/**\n* Call onBeforeMount() if it's inside a component lifecycle, if not, just call the function\n*\n* @param fn\n* @param sync if set to false, it will run in the nextTick() of Vue\n* @param target\n*/\nfunction tryOnBeforeMount(fn, sync = true, target) {\n\tif (getLifeCycleTarget(target)) onBeforeMount(fn, target);\n\telse if (sync) fn();\n\telse nextTick(fn);\n}\n\n//#endregion\n//#region tryOnBeforeUnmount/index.ts\n/**\n* Call onBeforeUnmount() if it's inside a component lifecycle, if not, do nothing\n*\n* @param fn\n* @param target\n*/\nfunction tryOnBeforeUnmount(fn, target) {\n\tif (getLifeCycleTarget(target)) onBeforeUnmount(fn, target);\n}\n\n//#endregion\n//#region tryOnMounted/index.ts\n/**\n* Call onMounted() if it's inside a component lifecycle, if not, just call the function\n*\n* @param fn\n* @param sync if set to false, it will run in the nextTick() of Vue\n* @param target\n*/\nfunction tryOnMounted(fn, sync = true, target) {\n\tif (getLifeCycleTarget(target)) onMounted(fn, target);\n\telse if (sync) fn();\n\telse nextTick(fn);\n}\n\n//#endregion\n//#region tryOnUnmounted/index.ts\n/**\n* Call onUnmounted() if it's inside a component lifecycle, if not, do nothing\n*\n* @param fn\n* @param target\n*/\nfunction tryOnUnmounted(fn, target) {\n\tif (getLifeCycleTarget(target)) onUnmounted(fn, target);\n}\n\n//#endregion\n//#region until/index.ts\nfunction createUntil(r, isNot = false) {\n\tfunction toMatch(condition, { flush = \"sync\", deep = false, timeout, throwOnTimeout } = {}) {\n\t\tlet stop = null;\n\t\tconst promises = [new Promise((resolve) => {\n\t\t\tstop = watch(r, (v) => {\n\t\t\t\tif (condition(v) !== isNot) {\n\t\t\t\t\tif (stop) stop();\n\t\t\t\t\telse nextTick(() => stop === null || stop === void 0 ? void 0 : stop());\n\t\t\t\t\tresolve(v);\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\tflush,\n\t\t\t\tdeep,\n\t\t\t\timmediate: true\n\t\t\t});\n\t\t})];\n\t\tif (timeout != null) promises.push(promiseTimeout(timeout, throwOnTimeout).then(() => toValue(r)).finally(() => stop === null || stop === void 0 ? void 0 : stop()));\n\t\treturn Promise.race(promises);\n\t}\n\tfunction toBe(value, options) {\n\t\tif (!isRef(value)) return toMatch((v) => v === value, options);\n\t\tconst { flush = \"sync\", deep = false, timeout, throwOnTimeout } = options !== null && options !== void 0 ? options : {};\n\t\tlet stop = null;\n\t\tconst promises = [new Promise((resolve) => {\n\t\t\tstop = watch([r, value], ([v1, v2]) => {\n\t\t\t\tif (isNot !== (v1 === v2)) {\n\t\t\t\t\tif (stop) stop();\n\t\t\t\t\telse nextTick(() => stop === null || stop === void 0 ? void 0 : stop());\n\t\t\t\t\tresolve(v1);\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\tflush,\n\t\t\t\tdeep,\n\t\t\t\timmediate: true\n\t\t\t});\n\t\t})];\n\t\tif (timeout != null) promises.push(promiseTimeout(timeout, throwOnTimeout).then(() => toValue(r)).finally(() => {\n\t\t\tstop === null || stop === void 0 || stop();\n\t\t\treturn toValue(r);\n\t\t}));\n\t\treturn Promise.race(promises);\n\t}\n\tfunction toBeTruthy(options) {\n\t\treturn toMatch((v) => Boolean(v), options);\n\t}\n\tfunction toBeNull(options) {\n\t\treturn toBe(null, options);\n\t}\n\tfunction toBeUndefined(options) {\n\t\treturn toBe(void 0, options);\n\t}\n\tfunction toBeNaN(options) {\n\t\treturn toMatch(Number.isNaN, options);\n\t}\n\tfunction toContains(value, options) {\n\t\treturn toMatch((v) => {\n\t\t\tconst array = Array.from(v);\n\t\t\treturn array.includes(value) || array.includes(toValue(value));\n\t\t}, options);\n\t}\n\tfunction changed(options) {\n\t\treturn changedTimes(1, options);\n\t}\n\tfunction changedTimes(n = 1, options) {\n\t\tlet count = -1;\n\t\treturn toMatch(() => {\n\t\t\tcount += 1;\n\t\t\treturn count >= n;\n\t\t}, options);\n\t}\n\tif (Array.isArray(toValue(r))) return {\n\t\ttoMatch,\n\t\ttoContains,\n\t\tchanged,\n\t\tchangedTimes,\n\t\tget not() {\n\t\t\treturn createUntil(r, !isNot);\n\t\t}\n\t};\n\telse return {\n\t\ttoMatch,\n\t\ttoBe,\n\t\ttoBeTruthy,\n\t\ttoBeNull,\n\t\ttoBeNaN,\n\t\ttoBeUndefined,\n\t\tchanged,\n\t\tchangedTimes,\n\t\tget not() {\n\t\t\treturn createUntil(r, !isNot);\n\t\t}\n\t};\n}\nfunction until(r) {\n\treturn createUntil(r);\n}\n\n//#endregion\n//#region useArrayDifference/index.ts\nfunction defaultComparator(value, othVal) {\n\treturn value === othVal;\n}\n/**\n* Reactive get array difference of two array\n* @see https://vueuse.org/useArrayDifference\n* @returns - the difference of two array\n* @param args\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayDifference(...args) {\n\tvar _args$, _args$2;\n\tconst list = args[0];\n\tconst values = args[1];\n\tlet compareFn = (_args$ = args[2]) !== null && _args$ !== void 0 ? _args$ : defaultComparator;\n\tconst { symmetric = false } = (_args$2 = args[3]) !== null && _args$2 !== void 0 ? _args$2 : {};\n\tif (typeof compareFn === \"string\") {\n\t\tconst key = compareFn;\n\t\tcompareFn = (value, othVal) => value[key] === othVal[key];\n\t}\n\tconst diff1 = computed(() => toValue(list).filter((x) => toValue(values).findIndex((y) => compareFn(x, y)) === -1));\n\tif (symmetric) {\n\t\tconst diff2 = computed(() => toValue(values).filter((x) => toValue(list).findIndex((y) => compareFn(x, y)) === -1));\n\t\treturn computed(() => symmetric ? [...toValue(diff1), ...toValue(diff2)] : toValue(diff1));\n\t} else return diff1;\n}\n\n//#endregion\n//#region useArrayEvery/index.ts\n/**\n* Reactive `Array.every`\n*\n* @see https://vueuse.org/useArrayEvery\n* @param list - the array was called upon.\n* @param fn - a function to test each element.\n*\n* @returns **true** if the `fn` function returns a **truthy** value for every element from the array. Otherwise, **false**.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayEvery(list, fn) {\n\treturn computed(() => toValue(list).every((element, index, array) => fn(toValue(element), index, array)));\n}\n\n//#endregion\n//#region useArrayFilter/index.ts\n/**\n* Reactive `Array.filter`\n*\n* @see https://vueuse.org/useArrayFilter\n* @param list - the array was called upon.\n* @param fn - a function that is called for every element of the given `list`. Each time `fn` executes, the returned value is added to the new array.\n*\n* @returns a shallow copy of a portion of the given array, filtered down to just the elements from the given array that pass the test implemented by the provided function. If no elements pass the test, an empty array will be returned.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayFilter(list, fn) {\n\treturn computed(() => toValue(list).map((i) => toValue(i)).filter(fn));\n}\n\n//#endregion\n//#region useArrayFind/index.ts\n/**\n* Reactive `Array.find`\n*\n* @see https://vueuse.org/useArrayFind\n* @param list - the array was called upon.\n* @param fn - a function to test each element.\n*\n* @returns the first element in the array that satisfies the provided testing function. Otherwise, undefined is returned.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayFind(list, fn) {\n\treturn computed(() => toValue(toValue(list).find((element, index, array) => fn(toValue(element), index, array))));\n}\n\n//#endregion\n//#region useArrayFindIndex/index.ts\n/**\n* Reactive `Array.findIndex`\n*\n* @see https://vueuse.org/useArrayFindIndex\n* @param list - the array was called upon.\n* @param fn - a function to test each element.\n*\n* @returns the index of the first element in the array that passes the test. Otherwise, \"-1\".\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayFindIndex(list, fn) {\n\treturn computed(() => toValue(list).findIndex((element, index, array) => fn(toValue(element), index, array)));\n}\n\n//#endregion\n//#region useArrayFindLast/index.ts\nfunction findLast(arr, cb) {\n\tlet index = arr.length;\n\twhile (index-- > 0) if (cb(arr[index], index, arr)) return arr[index];\n}\n/**\n* Reactive `Array.findLast`\n*\n* @see https://vueuse.org/useArrayFindLast\n* @param list - the array was called upon.\n* @param fn - a function to test each element.\n*\n* @returns the last element in the array that satisfies the provided testing function. Otherwise, undefined is returned.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayFindLast(list, fn) {\n\treturn computed(() => toValue(!Array.prototype.findLast ? findLast(toValue(list), (element, index, array) => fn(toValue(element), index, array)) : toValue(list).findLast((element, index, array) => fn(toValue(element), index, array))));\n}\n\n//#endregion\n//#region useArrayIncludes/index.ts\nfunction isArrayIncludesOptions(obj) {\n\treturn isObject(obj) && containsProp(obj, \"formIndex\", \"comparator\");\n}\n/**\n* Reactive `Array.includes`\n*\n* @see https://vueuse.org/useArrayIncludes\n*\n* @returns true if the `value` is found in the array. Otherwise, false.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayIncludes(...args) {\n\tvar _comparator;\n\tconst list = args[0];\n\tconst value = args[1];\n\tlet comparator = args[2];\n\tlet formIndex = 0;\n\tif (isArrayIncludesOptions(comparator)) {\n\t\tvar _comparator$fromIndex;\n\t\tformIndex = (_comparator$fromIndex = comparator.fromIndex) !== null && _comparator$fromIndex !== void 0 ? _comparator$fromIndex : 0;\n\t\tcomparator = comparator.comparator;\n\t}\n\tif (typeof comparator === \"string\") {\n\t\tconst key = comparator;\n\t\tcomparator = (element, value$1) => element[key] === toValue(value$1);\n\t}\n\tcomparator = (_comparator = comparator) !== null && _comparator !== void 0 ? _comparator : ((element, value$1) => element === toValue(value$1));\n\treturn computed(() => toValue(list).slice(formIndex).some((element, index, array) => comparator(toValue(element), toValue(value), index, toValue(array))));\n}\n\n//#endregion\n//#region useArrayJoin/index.ts\n/**\n* Reactive `Array.join`\n*\n* @see https://vueuse.org/useArrayJoin\n* @param list - the array was called upon.\n* @param separator - a string to separate each pair of adjacent elements of the array. If omitted, the array elements are separated with a comma (\",\").\n*\n* @returns a string with all array elements joined. If arr.length is 0, the empty string is returned.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayJoin(list, separator) {\n\treturn computed(() => toValue(list).map((i) => toValue(i)).join(toValue(separator)));\n}\n\n//#endregion\n//#region useArrayMap/index.ts\n/**\n* Reactive `Array.map`\n*\n* @see https://vueuse.org/useArrayMap\n* @param list - the array was called upon.\n* @param fn - a function that is called for every element of the given `list`. Each time `fn` executes, the returned value is added to the new array.\n*\n* @returns a new array with each element being the result of the callback function.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayMap(list, fn) {\n\treturn computed(() => toValue(list).map((i) => toValue(i)).map(fn));\n}\n\n//#endregion\n//#region useArrayReduce/index.ts\n/**\n* Reactive `Array.reduce`\n*\n* @see https://vueuse.org/useArrayReduce\n* @param list - the array was called upon.\n* @param reducer - a \"reducer\" function.\n* @param args\n*\n* @returns the value that results from running the \"reducer\" callback function to completion over the entire array.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayReduce(list, reducer, ...args) {\n\tconst reduceCallback = (sum, value, index) => reducer(toValue(sum), toValue(value), index);\n\treturn computed(() => {\n\t\tconst resolved = toValue(list);\n\t\treturn args.length ? resolved.reduce(reduceCallback, typeof args[0] === \"function\" ? toValue(args[0]()) : toValue(args[0])) : resolved.reduce(reduceCallback);\n\t});\n}\n\n//#endregion\n//#region useArraySome/index.ts\n/**\n* Reactive `Array.some`\n*\n* @see https://vueuse.org/useArraySome\n* @param list - the array was called upon.\n* @param fn - a function to test each element.\n*\n* @returns **true** if the `fn` function returns a **truthy** value for any element from the array. Otherwise, **false**.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArraySome(list, fn) {\n\treturn computed(() => toValue(list).some((element, index, array) => fn(toValue(element), index, array)));\n}\n\n//#endregion\n//#region useArrayUnique/index.ts\nfunction uniq(array) {\n\treturn Array.from(new Set(array));\n}\nfunction uniqueElementsBy(array, fn) {\n\treturn array.reduce((acc, v) => {\n\t\tif (!acc.some((x) => fn(v, x, array))) acc.push(v);\n\t\treturn acc;\n\t}, []);\n}\n/**\n* reactive unique array\n* @see https://vueuse.org/useArrayUnique\n* @param list - the array was called upon.\n* @param compareFn\n* @returns A computed ref that returns a unique array of items.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useArrayUnique(list, compareFn) {\n\treturn computed(() => {\n\t\tconst resolvedList = toValue(list).map((element) => toValue(element));\n\t\treturn compareFn ? uniqueElementsBy(resolvedList, compareFn) : uniq(resolvedList);\n\t});\n}\n\n//#endregion\n//#region useCounter/index.ts\n/**\n* Basic counter with utility functions.\n*\n* @see https://vueuse.org/useCounter\n* @param [initialValue]\n* @param options\n*/\nfunction useCounter(initialValue = 0, options = {}) {\n\tlet _initialValue = unref(initialValue);\n\tconst count = shallowRef(initialValue);\n\tconst { max = Number.POSITIVE_INFINITY, min = Number.NEGATIVE_INFINITY } = options;\n\tconst inc = (delta = 1) => count.value = Math.max(Math.min(max, count.value + delta), min);\n\tconst dec = (delta = 1) => count.value = Math.min(Math.max(min, count.value - delta), max);\n\tconst get$1 = () => count.value;\n\tconst set$1 = (val) => count.value = Math.max(min, Math.min(max, val));\n\tconst reset = (val = _initialValue) => {\n\t\t_initialValue = val;\n\t\treturn set$1(val);\n\t};\n\treturn {\n\t\tcount: shallowReadonly(count),\n\t\tinc,\n\t\tdec,\n\t\tget: get$1,\n\t\tset: set$1,\n\t\treset\n\t};\n}\n\n//#endregion\n//#region useDateFormat/index.ts\nconst REGEX_PARSE = /^(\\d{4})[-/]?(\\d{1,2})?[-/]?(\\d{0,2})[T\\s]*(\\d{1,2})?:?(\\d{1,2})?:?(\\d{1,2})?[.:]?(\\d+)?$/i;\nconst REGEX_FORMAT = /[YMDHhms]o|\\[([^\\]]+)\\]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a{1,2}|A{1,2}|m{1,2}|s{1,2}|Z{1,2}|z{1,4}|SSS/g;\nfunction defaultMeridiem(hours, minutes, isLowercase, hasPeriod) {\n\tlet m = hours < 12 ? \"AM\" : \"PM\";\n\tif (hasPeriod) m = m.split(\"\").reduce((acc, curr) => acc += `${curr}.`, \"\");\n\treturn isLowercase ? m.toLowerCase() : m;\n}\nfunction formatOrdinal(num) {\n\tconst suffixes = [\n\t\t\"th\",\n\t\t\"st\",\n\t\t\"nd\",\n\t\t\"rd\"\n\t];\n\tconst v = num % 100;\n\treturn num + (suffixes[(v - 20) % 10] || suffixes[v] || suffixes[0]);\n}\nfunction formatDate(date, formatStr, options = {}) {\n\tvar _options$customMeridi;\n\tconst years = date.getFullYear();\n\tconst month = date.getMonth();\n\tconst days = date.getDate();\n\tconst hours = date.getHours();\n\tconst minutes = date.getMinutes();\n\tconst seconds = date.getSeconds();\n\tconst milliseconds = date.getMilliseconds();\n\tconst day = date.getDay();\n\tconst meridiem = (_options$customMeridi = options.customMeridiem) !== null && _options$customMeridi !== void 0 ? _options$customMeridi : defaultMeridiem;\n\tconst stripTimeZone = (dateString) => {\n\t\tvar _dateString$split$;\n\t\treturn (_dateString$split$ = dateString.split(\" \")[1]) !== null && _dateString$split$ !== void 0 ? _dateString$split$ : \"\";\n\t};\n\tconst matches = {\n\t\tYo: () => formatOrdinal(years),\n\t\tYY: () => String(years).slice(-2),\n\t\tYYYY: () => years,\n\t\tM: () => month + 1,\n\t\tMo: () => formatOrdinal(month + 1),\n\t\tMM: () => `${month + 1}`.padStart(2, \"0\"),\n\t\tMMM: () => date.toLocaleDateString(toValue(options.locales), { month: \"short\" }),\n\t\tMMMM: () => date.toLocaleDateString(toValue(options.locales), { month: \"long\" }),\n\t\tD: () => String(days),\n\t\tDo: () => formatOrdinal(days),\n\t\tDD: () => `${days}`.padStart(2, \"0\"),\n\t\tH: () => String(hours),\n\t\tHo: () => formatOrdinal(hours),\n\t\tHH: () => `${hours}`.padStart(2, \"0\"),\n\t\th: () => `${hours % 12 || 12}`.padStart(1, \"0\"),\n\t\tho: () => formatOrdinal(hours % 12 || 12),\n\t\thh: () => `${hours % 12 || 12}`.padStart(2, \"0\"),\n\t\tm: () => String(minutes),\n\t\tmo: () => formatOrdinal(minutes),\n\t\tmm: () => `${minutes}`.padStart(2, \"0\"),\n\t\ts: () => String(seconds),\n\t\tso: () => formatOrdinal(seconds),\n\t\tss: () => `${seconds}`.padStart(2, \"0\"),\n\t\tSSS: () => `${milliseconds}`.padStart(3, \"0\"),\n\t\td: () => day,\n\t\tdd: () => date.toLocaleDateString(toValue(options.locales), { weekday: \"narrow\" }),\n\t\tddd: () => date.toLocaleDateString(toValue(options.locales), { weekday: \"short\" }),\n\t\tdddd: () => date.toLocaleDateString(toValue(options.locales), { weekday: \"long\" }),\n\t\tA: () => meridiem(hours, minutes),\n\t\tAA: () => meridiem(hours, minutes, false, true),\n\t\ta: () => meridiem(hours, minutes, true),\n\t\taa: () => meridiem(hours, minutes, true, true),\n\t\tz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: \"shortOffset\" })),\n\t\tzz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: \"shortOffset\" })),\n\t\tzzz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: \"shortOffset\" })),\n\t\tzzzz: () => stripTimeZone(date.toLocaleDateString(toValue(options.locales), { timeZoneName: \"longOffset\" }))\n\t};\n\treturn formatStr.replace(REGEX_FORMAT, (match, $1) => {\n\t\tvar _ref, _matches$match;\n\t\treturn (_ref = $1 !== null && $1 !== void 0 ? $1 : (_matches$match = matches[match]) === null || _matches$match === void 0 ? void 0 : _matches$match.call(matches)) !== null && _ref !== void 0 ? _ref : match;\n\t});\n}\nfunction normalizeDate(date) {\n\tif (date === null) return /* @__PURE__ */ new Date(NaN);\n\tif (date === void 0) return /* @__PURE__ */ new Date();\n\tif (date instanceof Date) return new Date(date);\n\tif (typeof date === \"string\" && !/Z$/i.test(date)) {\n\t\tconst d = date.match(REGEX_PARSE);\n\t\tif (d) {\n\t\t\tconst m = d[2] - 1 || 0;\n\t\t\tconst ms = (d[7] || \"0\").substring(0, 3);\n\t\t\treturn new Date(d[1], m, d[3] || 1, d[4] || 0, d[5] || 0, d[6] || 0, ms);\n\t\t}\n\t}\n\treturn new Date(date);\n}\n/**\n* Get the formatted date according to the string of tokens passed in.\n*\n* @see https://vueuse.org/useDateFormat\n* @param date - The date to format, can either be a `Date` object, a timestamp, or a string\n* @param formatStr - The combination of tokens to format the date\n* @param options - UseDateFormatOptions\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useDateFormat(date, formatStr = \"HH:mm:ss\", options = {}) {\n\treturn computed(() => formatDate(normalizeDate(toValue(date)), toValue(formatStr), options));\n}\n\n//#endregion\n//#region useIntervalFn/index.ts\n/**\n* Wrapper for `setInterval` with controls\n*\n* @see https://vueuse.org/useIntervalFn\n* @param cb\n* @param interval\n* @param options\n*/\nfunction useIntervalFn(cb, interval = 1e3, options = {}) {\n\tconst { immediate = true, immediateCallback = false } = options;\n\tlet timer = null;\n\tconst isActive = shallowRef(false);\n\tfunction clean() {\n\t\tif (timer) {\n\t\t\tclearInterval(timer);\n\t\t\ttimer = null;\n\t\t}\n\t}\n\tfunction pause() {\n\t\tisActive.value = false;\n\t\tclean();\n\t}\n\tfunction resume() {\n\t\tconst intervalValue = toValue(interval);\n\t\tif (intervalValue <= 0) return;\n\t\tisActive.value = true;\n\t\tif (immediateCallback) cb();\n\t\tclean();\n\t\tif (isActive.value) timer = setInterval(cb, intervalValue);\n\t}\n\tif (immediate && isClient) resume();\n\tif (isRef(interval) || typeof interval === \"function\") tryOnScopeDispose(watch(interval, () => {\n\t\tif (isActive.value && isClient) resume();\n\t}));\n\ttryOnScopeDispose(pause);\n\treturn {\n\t\tisActive: shallowReadonly(isActive),\n\t\tpause,\n\t\tresume\n\t};\n}\n\n//#endregion\n//#region useInterval/index.ts\nfunction useInterval(interval = 1e3, options = {}) {\n\tconst { controls: exposeControls = false, immediate = true, callback } = options;\n\tconst counter = shallowRef(0);\n\tconst update = () => counter.value += 1;\n\tconst reset = () => {\n\t\tcounter.value = 0;\n\t};\n\tconst controls = useIntervalFn(callback ? () => {\n\t\tupdate();\n\t\tcallback(counter.value);\n\t} : update, interval, { immediate });\n\tif (exposeControls) return {\n\t\tcounter: shallowReadonly(counter),\n\t\treset,\n\t\t...controls\n\t};\n\telse return shallowReadonly(counter);\n}\n\n//#endregion\n//#region useLastChanged/index.ts\nfunction useLastChanged(source, options = {}) {\n\tvar _options$initialValue;\n\tconst ms = shallowRef((_options$initialValue = options.initialValue) !== null && _options$initialValue !== void 0 ? _options$initialValue : null);\n\twatch(source, () => ms.value = timestamp(), options);\n\treturn shallowReadonly(ms);\n}\n\n//#endregion\n//#region useTimeoutFn/index.ts\n/**\n* Wrapper for `setTimeout` with controls.\n*\n* @param cb\n* @param interval\n* @param options\n*/\nfunction useTimeoutFn(cb, interval, options = {}) {\n\tconst { immediate = true, immediateCallback = false } = options;\n\tconst isPending = shallowRef(false);\n\tlet timer;\n\tfunction clear() {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = void 0;\n\t\t}\n\t}\n\tfunction stop() {\n\t\tisPending.value = false;\n\t\tclear();\n\t}\n\tfunction start(...args) {\n\t\tif (immediateCallback) cb();\n\t\tclear();\n\t\tisPending.value = true;\n\t\ttimer = setTimeout(() => {\n\t\t\tisPending.value = false;\n\t\t\ttimer = void 0;\n\t\t\tcb(...args);\n\t\t}, toValue(interval));\n\t}\n\tif (immediate) {\n\t\tisPending.value = true;\n\t\tif (isClient) start();\n\t}\n\ttryOnScopeDispose(stop);\n\treturn {\n\t\tisPending: shallowReadonly(isPending),\n\t\tstart,\n\t\tstop\n\t};\n}\n\n//#endregion\n//#region useTimeout/index.ts\nfunction useTimeout(interval = 1e3, options = {}) {\n\tconst { controls: exposeControls = false, callback } = options;\n\tconst controls = useTimeoutFn(callback !== null && callback !== void 0 ? callback : noop, interval, options);\n\tconst ready = computed(() => !controls.isPending.value);\n\tif (exposeControls) return {\n\t\tready,\n\t\t...controls\n\t};\n\telse return ready;\n}\n\n//#endregion\n//#region useToNumber/index.ts\n/**\n* Reactively convert a string ref to number.\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useToNumber(value, options = {}) {\n\tconst { method = \"parseFloat\", radix, nanToZero } = options;\n\treturn computed(() => {\n\t\tlet resolved = toValue(value);\n\t\tif (typeof method === \"function\") resolved = method(resolved);\n\t\telse if (typeof resolved === \"string\") resolved = Number[method](resolved, radix);\n\t\tif (nanToZero && Number.isNaN(resolved)) resolved = 0;\n\t\treturn resolved;\n\t});\n}\n\n//#endregion\n//#region useToString/index.ts\n/**\n* Reactively convert a ref to string.\n*\n* @see https://vueuse.org/useToString\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useToString(value) {\n\treturn computed(() => `${toValue(value)}`);\n}\n\n//#endregion\n//#region useToggle/index.ts\n/**\n* A boolean ref with a toggler\n*\n* @see https://vueuse.org/useToggle\n* @param [initialValue]\n* @param options\n*\n* @__NO_SIDE_EFFECTS__\n*/\nfunction useToggle(initialValue = false, options = {}) {\n\tconst { truthyValue = true, falsyValue = false } = options;\n\tconst valueIsRef = isRef(initialValue);\n\tconst _value = shallowRef(initialValue);\n\tfunction toggle(value) {\n\t\tif (arguments.length) {\n\t\t\t_value.value = value;\n\t\t\treturn _value.value;\n\t\t} else {\n\t\t\tconst truthy = toValue(truthyValue);\n\t\t\t_value.value = _value.value === truthy ? toValue(falsyValue) : truthy;\n\t\t\treturn _value.value;\n\t\t}\n\t}\n\tif (valueIsRef) return toggle;\n\telse return [_value, toggle];\n}\n\n//#endregion\n//#region watchArray/index.ts\n/**\n* Watch for an array with additions and removals.\n*\n* @see https://vueuse.org/watchArray\n*/\nfunction watchArray(source, cb, options) {\n\tlet oldList = (options === null || options === void 0 ? void 0 : options.immediate) ? [] : [...typeof source === \"function\" ? source() : Array.isArray(source) ? source : toValue(source)];\n\treturn watch(source, (newList, _, onCleanup) => {\n\t\tconst oldListRemains = Array.from({ length: oldList.length });\n\t\tconst added = [];\n\t\tfor (const obj of newList) {\n\t\t\tlet found = false;\n\t\t\tfor (let i = 0; i < oldList.length; i++) if (!oldListRemains[i] && obj === oldList[i]) {\n\t\t\t\toldListRemains[i] = true;\n\t\t\t\tfound = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!found) added.push(obj);\n\t\t}\n\t\tconst removed = oldList.filter((_$1, i) => !oldListRemains[i]);\n\t\tcb(newList, oldList, added, removed, onCleanup);\n\t\toldList = [...newList];\n\t}, options);\n}\n\n//#endregion\n//#region watchAtMost/index.ts\nfunction watchAtMost(source, cb, options) {\n\tconst { count,...watchOptions } = options;\n\tconst current = shallowRef(0);\n\tconst { stop, resume, pause } = watchWithFilter(source, (...args) => {\n\t\tcurrent.value += 1;\n\t\tif (current.value >= toValue(count)) nextTick(() => stop());\n\t\tcb(...args);\n\t}, watchOptions);\n\treturn {\n\t\tcount: current,\n\t\tstop,\n\t\tresume,\n\t\tpause\n\t};\n}\n\n//#endregion\n//#region watchDebounced/index.ts\nfunction watchDebounced(source, cb, options = {}) {\n\tconst { debounce = 0, maxWait = void 0,...watchOptions } = options;\n\treturn watchWithFilter(source, cb, {\n\t\t...watchOptions,\n\t\teventFilter: debounceFilter(debounce, { maxWait })\n\t});\n}\n/** @deprecated use `watchDebounced` instead */\nconst debouncedWatch = watchDebounced;\n\n//#endregion\n//#region watchDeep/index.ts\n/**\n* Shorthand for watching value with {deep: true}\n*\n* @see https://vueuse.org/watchDeep\n*/\nfunction watchDeep(source, cb, options) {\n\treturn watch(source, cb, {\n\t\t...options,\n\t\tdeep: true\n\t});\n}\n\n//#endregion\n//#region watchIgnorable/index.ts\nfunction watchIgnorable(source, cb, options = {}) {\n\tconst { eventFilter = bypassFilter,...watchOptions } = options;\n\tconst filteredCb = createFilterWrapper(eventFilter, cb);\n\tlet ignoreUpdates;\n\tlet ignorePrevAsyncUpdates;\n\tlet stop;\n\tif (watchOptions.flush === \"sync\") {\n\t\tlet ignore = false;\n\t\tignorePrevAsyncUpdates = () => {};\n\t\tignoreUpdates = (updater) => {\n\t\t\tignore = true;\n\t\t\tupdater();\n\t\t\tignore = false;\n\t\t};\n\t\tstop = watch(source, (...args) => {\n\t\t\tif (!ignore) filteredCb(...args);\n\t\t}, watchOptions);\n\t} else {\n\t\tconst disposables = [];\n\t\tlet ignoreCounter = 0;\n\t\tlet syncCounter = 0;\n\t\tignorePrevAsyncUpdates = () => {\n\t\t\tignoreCounter = syncCounter;\n\t\t};\n\t\tdisposables.push(watch(source, () => {\n\t\t\tsyncCounter++;\n\t\t}, {\n\t\t\t...watchOptions,\n\t\t\tflush: \"sync\"\n\t\t}));\n\t\tignoreUpdates = (updater) => {\n\t\t\tconst syncCounterPrev = syncCounter;\n\t\t\tupdater();\n\t\t\tignoreCounter += syncCounter - syncCounterPrev;\n\t\t};\n\t\tdisposables.push(watch(source, (...args) => {\n\t\t\tconst ignore = ignoreCounter > 0 && ignoreCounter === syncCounter;\n\t\t\tignoreCounter = 0;\n\t\t\tsyncCounter = 0;\n\t\t\tif (ignore) return;\n\t\t\tfilteredCb(...args);\n\t\t}, watchOptions));\n\t\tstop = () => {\n\t\t\tdisposables.forEach((fn) => fn());\n\t\t};\n\t}\n\treturn {\n\t\tstop,\n\t\tignoreUpdates,\n\t\tignorePrevAsyncUpdates\n\t};\n}\n/** @deprecated use `watchIgnorable` instead */\nconst ignorableWatch = watchIgnorable;\n\n//#endregion\n//#region watchImmediate/index.ts\n/**\n* Shorthand for watching value with {immediate: true}\n*\n* @see https://vueuse.org/watchImmediate\n*/\nfunction watchImmediate(source, cb, options) {\n\treturn watch(source, cb, {\n\t\t...options,\n\t\timmediate: true\n\t});\n}\n\n//#endregion\n//#region watchOnce/index.ts\n/**\n* Shorthand for watching value with { once: true }\n*\n* @see https://vueuse.org/watchOnce\n*/\nfunction watchOnce(source, cb, options) {\n\treturn watch(source, cb, {\n\t\t...options,\n\t\tonce: true\n\t});\n}\n\n//#endregion\n//#region watchThrottled/index.ts\nfunction watchThrottled(source, cb, options = {}) {\n\tconst { throttle = 0, trailing = true, leading = true,...watchOptions } = options;\n\treturn watchWithFilter(source, cb, {\n\t\t...watchOptions,\n\t\teventFilter: throttleFilter(throttle, trailing, leading)\n\t});\n}\n/** @deprecated use `watchThrottled` instead */\nconst throttledWatch = watchThrottled;\n\n//#endregion\n//#region watchTriggerable/index.ts\nfunction watchTriggerable(source, cb, options = {}) {\n\tlet cleanupFn;\n\tfunction onEffect() {\n\t\tif (!cleanupFn) return;\n\t\tconst fn = cleanupFn;\n\t\tcleanupFn = void 0;\n\t\tfn();\n\t}\n\t/** Register the function `cleanupFn` */\n\tfunction onCleanup(callback) {\n\t\tcleanupFn = callback;\n\t}\n\tconst _cb = (value, oldValue) => {\n\t\tonEffect();\n\t\treturn cb(value, oldValue, onCleanup);\n\t};\n\tconst res = watchIgnorable(source, _cb, options);\n\tconst { ignoreUpdates } = res;\n\tconst trigger = () => {\n\t\tlet res$1;\n\t\tignoreUpdates(() => {\n\t\t\tres$1 = _cb(getWatchSources(source), getOldValue(source));\n\t\t});\n\t\treturn res$1;\n\t};\n\treturn {\n\t\t...res,\n\t\ttrigger\n\t};\n}\nfunction getWatchSources(sources) {\n\tif (isReactive(sources)) return sources;\n\tif (Array.isArray(sources)) return sources.map((item) => toValue(item));\n\treturn toValue(sources);\n}\nfunction getOldValue(source) {\n\treturn Array.isArray(source) ? source.map(() => void 0) : void 0;\n}\n\n//#endregion\n//#region whenever/index.ts\n/**\n* Shorthand for watching value to be truthy\n*\n* @see https://vueuse.org/whenever\n*/\nfunction whenever(source, cb, options) {\n\tconst stop = watch(source, (v, ov, onInvalidate) => {\n\t\tif (v) {\n\t\t\tif (options === null || options === void 0 ? void 0 : options.once) nextTick(() => stop());\n\t\t\tcb(v, ov, onInvalidate);\n\t\t}\n\t}, {\n\t\t...options,\n\t\tonce: false\n\t});\n\treturn stop;\n}\n\n//#endregion\nexport { assert, autoResetRef, bypassFilter, camelize, clamp, computedEager, computedWithControl, containsProp, controlledComputed, controlledRef, createEventHook, createFilterWrapper, createGlobalState, createInjectionState, createReactiveFn, createRef, createSharedComposable, createSingletonPromise, debounceFilter, debouncedRef, debouncedWatch, eagerComputed, extendRef, formatDate, get, getLifeCycleTarget, hasOwn, hyphenate, identity, ignorableWatch, increaseWithUnit, injectLocal, invoke, isClient, isDef, isDefined, isIOS, isObject, isWorker, makeDestructurable, noop, normalizeDate, notNullish, now, objectEntries, objectOmit, objectPick, pausableFilter, pausableWatch, promiseTimeout, provideLocal, pxValue, rand, reactify, reactifyObject, reactiveComputed, reactiveOmit, reactivePick, refAutoReset, refDebounced, refDefault, refManualReset, refThrottled, refWithControl, set, syncRef, syncRefs, throttleFilter, throttledRef, throttledWatch, timestamp, toArray, toReactive, toRef, toRefs, tryOnBeforeMount, tryOnBeforeUnmount, tryOnMounted, tryOnScopeDispose, tryOnUnmounted, until, useArrayDifference, useArrayEvery, useArrayFilter, useArrayFind, useArrayFindIndex, useArrayFindLast, useArrayIncludes, useArrayJoin, useArrayMap, useArrayReduce, useArraySome, useArrayUnique, useCounter, useDateFormat, useDebounce, useDebounceFn, useInterval, useIntervalFn, useLastChanged, useThrottle, useThrottleFn, useTimeout, useTimeoutFn, useToNumber, useToString, useToggle, watchArray, watchAtMost, watchDebounced, watchDeep, watchIgnorable, watchImmediate, watchOnce, watchPausable, watchThrottled, watchTriggerable, watchWithFilter, whenever };","import { notNullish } from \"@vueuse/shared\";\nimport { computed, shallowRef, toValue, watch } from \"vue\";\nimport { toArray, tryOnScopeDispose as tryOnScopeDispose$1, unrefElement } from \"@vueuse/core\";\nimport { createFocusTrap } from \"focus-trap\";\n\n//#region useFocusTrap/index.ts\n/**\n* Reactive focus-trap\n*\n* @see https://vueuse.org/useFocusTrap\n*/\nfunction useFocusTrap(target, options = {}) {\n\tlet trap;\n\tconst { immediate,...focusTrapOptions } = options;\n\tconst hasFocus = shallowRef(false);\n\tconst isPaused = shallowRef(false);\n\tconst activate = (opts) => trap && trap.activate(opts);\n\tconst deactivate = (opts) => trap && trap.deactivate(opts);\n\tconst pause = () => {\n\t\tif (trap) {\n\t\t\ttrap.pause();\n\t\t\tisPaused.value = true;\n\t\t}\n\t};\n\tconst unpause = () => {\n\t\tif (trap) {\n\t\t\ttrap.unpause();\n\t\t\tisPaused.value = false;\n\t\t}\n\t};\n\twatch(computed(() => {\n\t\treturn toArray(toValue(target)).map((el) => {\n\t\t\tconst _el = toValue(el);\n\t\t\treturn typeof _el === \"string\" ? _el : unrefElement(_el);\n\t\t}).filter(notNullish);\n\t}), (els) => {\n\t\tif (!els.length) return;\n\t\tif (!trap) {\n\t\t\ttrap = createFocusTrap(els, {\n\t\t\t\t...focusTrapOptions,\n\t\t\t\tonActivate() {\n\t\t\t\t\thasFocus.value = true;\n\t\t\t\t\tif (options.onActivate) options.onActivate();\n\t\t\t\t},\n\t\t\t\tonDeactivate() {\n\t\t\t\t\thasFocus.value = false;\n\t\t\t\t\tif (options.onDeactivate) options.onDeactivate();\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (immediate) activate();\n\t\t} else {\n\t\t\tconst isActive = trap === null || trap === void 0 ? void 0 : trap.active;\n\t\t\ttrap === null || trap === void 0 || trap.updateContainerElements(els);\n\t\t\tif (!isActive && immediate) activate();\n\t\t}\n\t}, { flush: \"post\" });\n\ttryOnScopeDispose$1(() => deactivate());\n\treturn {\n\t\thasFocus,\n\t\tisPaused,\n\t\tactivate,\n\t\tdeactivate,\n\t\tpause,\n\t\tunpause\n\t};\n}\n\n//#endregion\nexport { useFocusTrap as t };","import { nextTick, Ref, ref, watch } from \"vue\";\nimport { useFocusTrap } from \"@vueuse/integrations/useFocusTrap\";\n\nconst tabbableSelector = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n \"[contenteditable='true']\",\n].join(\",\");\n\nexport function useOverlayFocus(\n element: Ref<HTMLElement | null>,\n isTop: Ref<boolean>,\n clickOutsideDeactivates = false,\n) {\n const unpausing = ref(false);\n\n const hasTabbableNodes = () =>\n !!element.value?.querySelector(tabbableSelector);\n\n const { activate, deactivate, pause, unpause } = useFocusTrap(element, {\n immediate: false,\n clickOutsideDeactivates,\n initialFocus: () => {\n if (unpausing.value) {\n return false;\n }\n const focus = element.value?.querySelector(\"[popover-focus]\");\n if (focus) {\n return focus as HTMLElement;\n }\n const h2 = element.value?.querySelector(\"h2\");\n if (h2) {\n return h2 as HTMLElement;\n }\n const selected = element.value?.querySelector(\n \"[aria-selected='true']\",\n );\n if (selected) {\n return selected as HTMLElement;\n }\n },\n onPostPause: () => (unpausing.value = true),\n onPostUnpause: () => {\n nextTick(() => {\n unpausing.value = false;\n }).catch((err) => {\n console.error(err);\n });\n },\n });\n\n watch(isTop, (top) => {\n if (top) {\n nextTick(() => {\n if (hasTabbableNodes()) {\n unpause();\n }\n }).catch((err) => {\n console.error(err);\n });\n } else {\n pause();\n }\n });\n\n const activateWhenReady = () => {\n nextTick(() => {\n requestAnimationFrame(() => {\n if (hasTabbableNodes()) {\n activate();\n }\n });\n }).catch((err) => {\n console.error(err);\n });\n };\n\n return { activate: activateWhenReady, deactivate, pause, unpause };\n}\n","import { nextTick, onBeforeUnmount, onMounted, Ref } from \"vue\";\n\nexport function useOverlayEscape(\n containers: Ref<Element | null>[],\n isTop: Ref<boolean>,\n open: Ref<boolean>,\n hide: () => void,\n pop: () => void,\n) {\n function onDocumentClick(e: MouseEvent) {\n if (!open.value || !isTop.value) {\n return;\n }\n for (const ref of containers) {\n if (ref.value?.contains(e.target as Node)) {\n return;\n }\n }\n hide();\n }\n\n function onDocumentKeydown(e: KeyboardEvent) {\n if (e.key === \"Escape\" && open.value) {\n if (isTop.value) {\n e.preventDefault();\n nextTick(hide).catch((err) => {\n console.error(err);\n })\n }\n }\n }\n\n onMounted(() => {\n document.addEventListener(\"mousedown\", onDocumentClick);\n document.addEventListener(\"keydown\", onDocumentKeydown);\n });\n onBeforeUnmount(() => {\n document.removeEventListener(\"mousedown\", onDocumentClick);\n document.removeEventListener(\"keydown\", onDocumentKeydown);\n pop();\n });\n}\n","/**\n * Calculates the position for a popover based on the anchor element's position,\n * the popover's dimensions, and the viewport dimensions.\n *\n * @param anchorRect\n * @param popoverRect\n * @param viewportRect\n * @param options Optional config: { gap?: number, margin?: number }\n */\nexport function calculatePopoverPosition(\n anchorRect: DOMRect,\n popoverRect: DOMRect,\n viewportRect: DOMRect,\n options?: { gap?: number; margin?: number; preferAbove?: boolean },\n): { top: number; left: number; xOffset: number; placedAbove: boolean; overlay: boolean } {\n const gap = options?.gap ?? 8;\n const margin = options?.margin ?? 16;\n const preferAbove = options?.preferAbove ?? false;\n\n // Prefer below, but go above if not enough space\n let placedAbove = false;\n let overlay = false;\n let top: number;\n if (preferAbove) {\n if (anchorRect.top - popoverRect.height - gap > viewportRect.top + margin) {\n // Place above\n top = anchorRect.top - popoverRect.height - gap;\n placedAbove = true;\n } else if (anchorRect.bottom + popoverRect.height + gap <= viewportRect.bottom - margin) {\n // Place below if not enough room above\n top = anchorRect.bottom + gap;\n } else {\n // Not enough room above or below, overlay on anchor, margin from top\n top = viewportRect.top + margin;\n overlay = true;\n }\n } else {\n if (\n anchorRect.bottom + popoverRect.height + gap > viewportRect.bottom - margin &&\n anchorRect.top - popoverRect.height - gap > viewportRect.top + margin\n ) {\n // Place above\n top = anchorRect.top - popoverRect.height - gap;\n placedAbove = true;\n } else if (\n anchorRect.bottom + popoverRect.height + gap > viewportRect.bottom - margin &&\n anchorRect.top - popoverRect.height - gap <= viewportRect.top + margin\n ) {\n // Not enough room above or below, overlay on anchor, margin from top\n top = viewportRect.top + margin;\n overlay = true;\n } else {\n // Place below\n top = anchorRect.bottom + gap;\n }\n }\n\n // Center horizontally by default\n let left = anchorRect.left + (anchorRect.width - popoverRect.width) / 2;\n // Clamp to viewport\n if (left < viewportRect.left + margin) {\n left = viewportRect.left + margin;\n }\n if (left + popoverRect.width > viewportRect.right - margin) {\n left = viewportRect.right - popoverRect.width - margin;\n }\n // Final safety clamp (if popover is wider than viewport)\n if (left < viewportRect.left + margin) {\n left = viewportRect.left + margin;\n }\n\n // Calculate X offset from centered position (for arrow)\n const centeredLeft = anchorRect.left + (anchorRect.width - popoverRect.width) / 2;\n const xOffset = left - centeredLeft;\n\n return { top, left, xOffset, placedAbove, overlay };\n}\n","<script lang=\"ts\">\n/**\n * Popover that appears next to or over a trigger element, staying visible\n * in the viewport as much as possible.\n *\n * **Slot** `trigger` is optional. When provided, it should contain an\n * interactive element for opening the popover and it is used for\n * `aria-labelledby`. The trigger is passed a prop `toggle` which is a function\n * that toggles the popover's open state.\n *\n * Without a trigger slot, open the popover programmatically via `show()` or\n * `toggle()` on the component instance / custom element.\n *\n * **Slot** `default` is the content of the popover.\n *\n * Example:\n *\n * ```vue-html\n * <GPopover>\n * <template #trigger=\"{ toggle }\">\n * <GButton @click=\"toggle\">\n * Can Popovers have Popovers?\n * </GButton>\n * </template>\n * <div>In theory, but I wouldn't recommend it.</div>\n * </GPopover>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n ref,\n useId,\n useSlots,\n useTemplateRef,\n watch,\n toRef,\n} from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\nimport { calculatePopoverPosition } from \"../compose/popoverPosition.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Render without padding\n * @demo\n */\n minimal?: boolean;\n /**\n * v-model binding for the open state. Also works as a plain\n * prop/attribute in custom-element mode where `defineModel`\n * would revert local state.\n */\n modelValue?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n minimal: false,\n modelValue: false,\n});\nconst emit = defineEmits([\"show\", \"hide\", \"update:modelValue\"]);\n\nconst open = ref(props.modelValue);\nwatch(toRef(props, \"modelValue\"), (v) => {\n open.value = v;\n});\nconst slots = useSlots();\nconst hasTrigger = computed(() => !!slots.trigger);\n\nconst triggerRef = useTemplateRef<HTMLElement | null>(\"triggerRef\");\nconst popoverRef = useTemplateRef<HTMLElement | null>(\"popoverRef\");\n\n// Disable Teleport inside custom elements: scoped named slots\n// break CE slot distribution when content is teleported.\nconst { isCustomElement } = useCustomElementAttrs();\nconst disableTeleport = isCustomElement;\n\nconst id = useId();\nconst { push, pop, isTop, zIndex } = useOverlayStack(id, true);\nconst { activate, deactivate } = useOverlayFocus(popoverRef, isTop, true);\nuseOverlayEscape([popoverRef, triggerRef], isTop, open, hide, pop);\n\nwatch(open, (val) => {\n if (val) {\n nextTick(() => {\n nextTick(() => activate());\n });\n push();\n emit(\"show\");\n } else {\n deactivate();\n pop();\n emit(\"hide\");\n }\n});\n\nfunction show() {\n open.value = true;\n emit(\"update:modelValue\", true);\n}\n\nfunction hide() {\n open.value = false;\n emit(\"update:modelValue\", false);\n}\n\nfunction toggle() {\n open.value = !open.value;\n emit(\"update:modelValue\", open.value);\n}\n\nconst popoverPosition = ref<Record<string, any>>({ top: 0, left: 0 });\nconst arrowPosition = ref<Record<string, any>>({ left: \"50%\" });\nconst popoverAbove = ref(false);\nconst popoverOverlay = ref(false);\nlet resizeObserver: ResizeObserver | null = null;\n\nfunction getAnchorElement() {\n if (triggerRef.value) {\n return triggerRef.value;\n }\n\n let host: HTMLElement | null = popoverRef.value?.parentElement ?? null;\n while (host && host.tagName.toLowerCase() !== \"g-popover\") {\n host = host.parentElement;\n }\n const previousSibling = host?.previousElementSibling;\n\n if (previousSibling instanceof HTMLElement) {\n return previousSibling;\n }\n\n return null;\n}\n\nfunction updatePopoverPosition() {\n if (!popoverRef.value) {\n return;\n }\n\n // Use offsetWidth/offsetHeight for popover dimensions to avoid getting\n // scaled values during the CSS scale() enter transition.\n const popoverRect = new DOMRect(0, 0, popoverRef.value.offsetWidth, popoverRef.value.offsetHeight);\n const viewportRect = new DOMRect(0, 0, window.innerWidth, window.innerHeight);\n\n const anchorEl = getAnchorElement();\n\n if (!anchorEl) {\n popoverPosition.value = {\n top: Math.max((viewportRect.height - popoverRect.height) / 2, 8),\n left: Math.max((viewportRect.width - popoverRect.width) / 2, 8),\n };\n popoverOverlay.value = false;\n popoverAbove.value = false;\n arrowPosition.value = { left: \"50%\" };\n return;\n }\n\n const triggerRect = anchorEl.getBoundingClientRect();\n\n const { top, left, xOffset, placedAbove, overlay } =\n calculatePopoverPosition(triggerRect, popoverRect, viewportRect, {\n gap: props.minimal ? 0 : 8,\n });\n popoverPosition.value = { top, left };\n arrowPosition.value = {\n left: `${popoverRect.width / 2 - xOffset}px`,\n top: placedAbove ? \"auto\" : undefined,\n bottom: placedAbove ? \"-8px\" : undefined,\n };\n popoverAbove.value = placedAbove;\n popoverOverlay.value = overlay;\n}\n\nwatch(open, (val) => {\n if (val) {\n nextTick(() => {\n updatePopoverPosition();\n window.addEventListener(\"resize\", updatePopoverPosition);\n window.addEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (popoverRef.value) {\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n resizeObserver = new ResizeObserver(() =>\n updatePopoverPosition(),\n );\n resizeObserver.observe(popoverRef.value);\n }\n });\n } else {\n window.removeEventListener(\"resize\", updatePopoverPosition);\n window.removeEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n }\n});\n\nonBeforeUnmount(() => {\n window.removeEventListener(\"resize\", updatePopoverPosition);\n window.removeEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n});\ndefineExpose({\n show,\n hide,\n toggle\n})\n\n</script>\n\n<template>\n <div class=\"g-popover-wrap\">\n <div v-if=\"hasTrigger\" ref=\"triggerRef\" class=\"g-popover-trigger\" :id=\"`${id}-trigger`\">\n <slot name=\"trigger\" :toggle=\"toggle\"></slot>\n </div>\n <Teleport to=\"#modal-root\" :disabled=\"disableTeleport\">\n <transition name=\"g-popover-expand\" appear>\n <div\n v-if=\"isCustomElement || open\" v-show=\"open\"\n ref=\"popoverRef\"\n :class=\"{\n 'g-popover': true,\n 'g-popover-above': popoverAbove,\n 'g-popover-below': !popoverAbove,\n 'g-popover-minimal': minimal,\n }\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"hasTrigger ? `${id}-trigger` : undefined\"\n :aria-label=\"hasTrigger ? undefined : 'Popover'\"\n :style=\"{\n top: popoverPosition.top + 'px',\n left: popoverPosition.left + 'px',\n zIndex,\n }\"\n >\n <div\n v-if=\"!popoverOverlay && !minimal\"\n class=\"g-popover-arrow\"\n :class=\"{ 'g-popover-arrow-above': popoverAbove }\"\n :style=\"arrowPosition\"\n aria-hidden=\"true\"\n ></div>\n <slot></slot>\n <button\n v-if=\"!minimal\"\n class=\"g-popover-close\"\n type=\"button\"\n aria-label=\"Close popover\"\n @click=\"hide\"\n >\n <svg\n class=\"g-popover-close-icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n </div>\n </transition>\n </Teleport>\n </div>\n</template>\n\n<style>\ng-popover:not(:defined) {\n display: inline-block;\n}\n\n.g-popover {\n h2 {\n font-size: 1.25rem;\n margin: 0 0 0.75rem 0;\n }\n p {\n margin: 0 0 0.5rem 0;\n }\n}\n.g-popover-trigger {\n display: inline-block;\n}\n.g-popover {\n position: fixed;\n z-index: 1000;\n background: var(--g-surface-0);\n border: 1px solid var(--g-surface-200);\n color: var(--g-surface-900);\n font-weight: normal;\n font-size: 1rem;\n border-radius: 4px;\n box-shadow: var(--il-shadow);\n padding: 1.5rem 1rem 1rem;\n min-width: 200px;\n max-width: 500px;\n top: 0;\n left: 0;\n text-align: left;\n}\n.g-popover.g-popover-minimal {\n padding: 0;\n min-width: 0;\n}\n\n.g-popover-arrow {\n box-sizing: border-box;\n position: absolute;\n top: -8px;\n width: 20px;\n height: 8px;\n left: 50%;\n transform: translateX(-50%);\n pointer-events: none;\n z-index: 1;\n}\n\n.g-popover-arrow::after {\n box-sizing: border-box;\n content: \"\";\n display: block;\n margin: 0 auto;\n width: 16px;\n height: 8px;\n background: transparent;\n border-left: 8px solid transparent;\n border-right: 8px solid transparent;\n border-bottom: 8px solid white;\n /* Add border for the arrow */\n position: relative;\n z-index: 2;\n}\n\n.g-popover-arrow::before {\n box-sizing: border-box;\n content: \"\";\n display: block;\n position: absolute;\n top: -1px;\n left: 1px;\n width: 18px;\n height: 9px;\n border-left: 8px solid transparent;\n border-right: 8px solid transparent;\n border-bottom: 8px solid var(--g-surface-200, #ccc);\n z-index: 1;\n}\n\n.g-popover-arrow-above {\n transform: translateX(-50%) rotate(180deg);\n}\n.g-popover-close {\n position: absolute;\n display: block;\n top: 1px;\n right: 1px;\n border: none;\n padding: 0.25rem 0.25rem;\n border-radius: 7px;\n background: transparent;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n }\n &:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n .g-popover-close-icon {\n width: 1.25rem;\n height: 1.25rem;\n display: block;\n }\n}\n\n.g-popover-expand-enter-active,\n.g-popover-expand-leave-active {\n transition:\n opacity 0.18s cubic-bezier(0.4, 0, 0.2, 1),\n transform 0.18s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.g-popover-expand-enter-from,\n.g-popover-expand-leave-to {\n opacity: 0;\n transform: scale(0.95);\n}\n\n.g-popover-expand-enter-to,\n.g-popover-expand-leave-from {\n opacity: 1;\n transform: scale(1);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-popover-expand-enter-active,\n .g-popover-expand-leave-active {\n transition: none !important;\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Popover that appears next to or over a trigger element, staying visible\n * in the viewport as much as possible.\n *\n * **Slot** `trigger` is optional. When provided, it should contain an\n * interactive element for opening the popover and it is used for\n * `aria-labelledby`. The trigger is passed a prop `toggle` which is a function\n * that toggles the popover's open state.\n *\n * Without a trigger slot, open the popover programmatically via `show()` or\n * `toggle()` on the component instance / custom element.\n *\n * **Slot** `default` is the content of the popover.\n *\n * Example:\n *\n * ```vue-html\n * <GPopover>\n * <template #trigger=\"{ toggle }\">\n * <GButton @click=\"toggle\">\n * Can Popovers have Popovers?\n * </GButton>\n * </template>\n * <div>In theory, but I wouldn't recommend it.</div>\n * </GPopover>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n ref,\n useId,\n useSlots,\n useTemplateRef,\n watch,\n toRef,\n} from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\nimport { calculatePopoverPosition } from \"../compose/popoverPosition.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Render without padding\n * @demo\n */\n minimal?: boolean;\n /**\n * v-model binding for the open state. Also works as a plain\n * prop/attribute in custom-element mode where `defineModel`\n * would revert local state.\n */\n modelValue?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n minimal: false,\n modelValue: false,\n});\nconst emit = defineEmits([\"show\", \"hide\", \"update:modelValue\"]);\n\nconst open = ref(props.modelValue);\nwatch(toRef(props, \"modelValue\"), (v) => {\n open.value = v;\n});\nconst slots = useSlots();\nconst hasTrigger = computed(() => !!slots.trigger);\n\nconst triggerRef = useTemplateRef<HTMLElement | null>(\"triggerRef\");\nconst popoverRef = useTemplateRef<HTMLElement | null>(\"popoverRef\");\n\n// Disable Teleport inside custom elements: scoped named slots\n// break CE slot distribution when content is teleported.\nconst { isCustomElement } = useCustomElementAttrs();\nconst disableTeleport = isCustomElement;\n\nconst id = useId();\nconst { push, pop, isTop, zIndex } = useOverlayStack(id, true);\nconst { activate, deactivate } = useOverlayFocus(popoverRef, isTop, true);\nuseOverlayEscape([popoverRef, triggerRef], isTop, open, hide, pop);\n\nwatch(open, (val) => {\n if (val) {\n nextTick(() => {\n nextTick(() => activate());\n });\n push();\n emit(\"show\");\n } else {\n deactivate();\n pop();\n emit(\"hide\");\n }\n});\n\nfunction show() {\n open.value = true;\n emit(\"update:modelValue\", true);\n}\n\nfunction hide() {\n open.value = false;\n emit(\"update:modelValue\", false);\n}\n\nfunction toggle() {\n open.value = !open.value;\n emit(\"update:modelValue\", open.value);\n}\n\nconst popoverPosition = ref<Record<string, any>>({ top: 0, left: 0 });\nconst arrowPosition = ref<Record<string, any>>({ left: \"50%\" });\nconst popoverAbove = ref(false);\nconst popoverOverlay = ref(false);\nlet resizeObserver: ResizeObserver | null = null;\n\nfunction getAnchorElement() {\n if (triggerRef.value) {\n return triggerRef.value;\n }\n\n let host: HTMLElement | null = popoverRef.value?.parentElement ?? null;\n while (host && host.tagName.toLowerCase() !== \"g-popover\") {\n host = host.parentElement;\n }\n const previousSibling = host?.previousElementSibling;\n\n if (previousSibling instanceof HTMLElement) {\n return previousSibling;\n }\n\n return null;\n}\n\nfunction updatePopoverPosition() {\n if (!popoverRef.value) {\n return;\n }\n\n // Use offsetWidth/offsetHeight for popover dimensions to avoid getting\n // scaled values during the CSS scale() enter transition.\n const popoverRect = new DOMRect(0, 0, popoverRef.value.offsetWidth, popoverRef.value.offsetHeight);\n const viewportRect = new DOMRect(0, 0, window.innerWidth, window.innerHeight);\n\n const anchorEl = getAnchorElement();\n\n if (!anchorEl) {\n popoverPosition.value = {\n top: Math.max((viewportRect.height - popoverRect.height) / 2, 8),\n left: Math.max((viewportRect.width - popoverRect.width) / 2, 8),\n };\n popoverOverlay.value = false;\n popoverAbove.value = false;\n arrowPosition.value = { left: \"50%\" };\n return;\n }\n\n const triggerRect = anchorEl.getBoundingClientRect();\n\n const { top, left, xOffset, placedAbove, overlay } =\n calculatePopoverPosition(triggerRect, popoverRect, viewportRect, {\n gap: props.minimal ? 0 : 8,\n });\n popoverPosition.value = { top, left };\n arrowPosition.value = {\n left: `${popoverRect.width / 2 - xOffset}px`,\n top: placedAbove ? \"auto\" : undefined,\n bottom: placedAbove ? \"-8px\" : undefined,\n };\n popoverAbove.value = placedAbove;\n popoverOverlay.value = overlay;\n}\n\nwatch(open, (val) => {\n if (val) {\n nextTick(() => {\n updatePopoverPosition();\n window.addEventListener(\"resize\", updatePopoverPosition);\n window.addEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (popoverRef.value) {\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n resizeObserver = new ResizeObserver(() =>\n updatePopoverPosition(),\n );\n resizeObserver.observe(popoverRef.value);\n }\n });\n } else {\n window.removeEventListener(\"resize\", updatePopoverPosition);\n window.removeEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n }\n});\n\nonBeforeUnmount(() => {\n window.removeEventListener(\"resize\", updatePopoverPosition);\n window.removeEventListener(\"scroll\", updatePopoverPosition, { capture: true });\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n});\ndefineExpose({\n show,\n hide,\n toggle\n})\n\n</script>\n\n<template>\n <div class=\"g-popover-wrap\">\n <div v-if=\"hasTrigger\" ref=\"triggerRef\" class=\"g-popover-trigger\" :id=\"`${id}-trigger`\">\n <slot name=\"trigger\" :toggle=\"toggle\"></slot>\n </div>\n <Teleport to=\"#modal-root\" :disabled=\"disableTeleport\">\n <transition name=\"g-popover-expand\" appear>\n <div\n v-if=\"isCustomElement || open\" v-show=\"open\"\n ref=\"popoverRef\"\n :class=\"{\n 'g-popover': true,\n 'g-popover-above': popoverAbove,\n 'g-popover-below': !popoverAbove,\n 'g-popover-minimal': minimal,\n }\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"hasTrigger ? `${id}-trigger` : undefined\"\n :aria-label=\"hasTrigger ? undefined : 'Popover'\"\n :style=\"{\n top: popoverPosition.top + 'px',\n left: popoverPosition.left + 'px',\n zIndex,\n }\"\n >\n <div\n v-if=\"!popoverOverlay && !minimal\"\n class=\"g-popover-arrow\"\n :class=\"{ 'g-popover-arrow-above': popoverAbove }\"\n :style=\"arrowPosition\"\n aria-hidden=\"true\"\n ></div>\n <slot></slot>\n <button\n v-if=\"!minimal\"\n class=\"g-popover-close\"\n type=\"button\"\n aria-label=\"Close popover\"\n @click=\"hide\"\n >\n <svg\n class=\"g-popover-close-icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n </div>\n </transition>\n </Teleport>\n </div>\n</template>\n\n<style>\ng-popover:not(:defined) {\n display: inline-block;\n}\n\n.g-popover {\n h2 {\n font-size: 1.25rem;\n margin: 0 0 0.75rem 0;\n }\n p {\n margin: 0 0 0.5rem 0;\n }\n}\n.g-popover-trigger {\n display: inline-block;\n}\n.g-popover {\n position: fixed;\n z-index: 1000;\n background: var(--g-surface-0);\n border: 1px solid var(--g-surface-200);\n color: var(--g-surface-900);\n font-weight: normal;\n font-size: 1rem;\n border-radius: 4px;\n box-shadow: var(--il-shadow);\n padding: 1.5rem 1rem 1rem;\n min-width: 200px;\n max-width: 500px;\n top: 0;\n left: 0;\n text-align: left;\n}\n.g-popover.g-popover-minimal {\n padding: 0;\n min-width: 0;\n}\n\n.g-popover-arrow {\n box-sizing: border-box;\n position: absolute;\n top: -8px;\n width: 20px;\n height: 8px;\n left: 50%;\n transform: translateX(-50%);\n pointer-events: none;\n z-index: 1;\n}\n\n.g-popover-arrow::after {\n box-sizing: border-box;\n content: \"\";\n display: block;\n margin: 0 auto;\n width: 16px;\n height: 8px;\n background: transparent;\n border-left: 8px solid transparent;\n border-right: 8px solid transparent;\n border-bottom: 8px solid white;\n /* Add border for the arrow */\n position: relative;\n z-index: 2;\n}\n\n.g-popover-arrow::before {\n box-sizing: border-box;\n content: \"\";\n display: block;\n position: absolute;\n top: -1px;\n left: 1px;\n width: 18px;\n height: 9px;\n border-left: 8px solid transparent;\n border-right: 8px solid transparent;\n border-bottom: 8px solid var(--g-surface-200, #ccc);\n z-index: 1;\n}\n\n.g-popover-arrow-above {\n transform: translateX(-50%) rotate(180deg);\n}\n.g-popover-close {\n position: absolute;\n display: block;\n top: 1px;\n right: 1px;\n border: none;\n padding: 0.25rem 0.25rem;\n border-radius: 7px;\n background: transparent;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n }\n &:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n .g-popover-close-icon {\n width: 1.25rem;\n height: 1.25rem;\n display: block;\n }\n}\n\n.g-popover-expand-enter-active,\n.g-popover-expand-leave-active {\n transition:\n opacity 0.18s cubic-bezier(0.4, 0, 0.2, 1),\n transform 0.18s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.g-popover-expand-enter-from,\n.g-popover-expand-leave-to {\n opacity: 0;\n transform: scale(0.95);\n}\n\n.g-popover-expand-enter-to,\n.g-popover-expand-leave-from {\n opacity: 1;\n transform: scale(1);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-popover-expand-enter-active,\n .g-popover-expand-leave-active {\n transition: none !important;\n }\n}\n</style>\n\n","import { calculatePopoverPosition } from \"./popoverPosition.ts\";\nimport { getTopZIndex } from \"./useOverlayStack.ts\";\n\nlet tooltipIdCounter = 1;\n\nexport function nextTooltipId(prefix = \"v-gtooltip\") {\n return `${prefix}-${++tooltipIdCounter}`;\n}\n\nexport function resolveTooltipId(el: HTMLElement, prefix = \"v-gtooltip\") {\n const existing = el.getAttribute(\"aria-describedby\");\n if (existing) {\n return existing;\n }\n const id = nextTooltipId(prefix);\n el.setAttribute(\"aria-describedby\", id);\n return id;\n}\n\nexport function createTooltipEl(text: string, id: string) {\n const tooltip = document.createElement(\"div\");\n tooltip.className = \"v-gtooltip\";\n tooltip.textContent = text;\n tooltip.setAttribute(\"role\", \"tooltip\");\n tooltip.setAttribute(\"id\", id);\n return tooltip;\n}\n\nexport function appendTooltipEl(tooltip: HTMLElement) {\n const modalRoot = document.getElementById(\"modal-root\");\n (modalRoot ?? document.body).appendChild(tooltip);\n}\n\nexport function showTooltip(anchor: HTMLElement, tooltip: HTMLElement) {\n const anchorRect = anchor.getBoundingClientRect();\n const popoverRect = tooltip.getBoundingClientRect();\n const viewportRect = new DOMRect(0, 0, window.innerWidth, window.innerHeight);\n const { top, left, placedAbove } = calculatePopoverPosition(anchorRect, popoverRect, viewportRect, {\n gap: 8,\n margin: 8,\n preferAbove: true,\n });\n const anchorCenter = anchorRect.left + anchorRect.width / 2;\n const arrowX = ((anchorCenter - left) / popoverRect.width) * 100;\n tooltip.style.setProperty(\"--v-gtooltip-arrow-x\", `${arrowX}%`);\n tooltip.classList.remove(\"v-gtooltip-bottom\");\n if (!placedAbove) {\n tooltip.classList.add(\"v-gtooltip-bottom\");\n }\n tooltip.style.left = `${left}px`;\n tooltip.style.top = `${top}px`;\n tooltip.style.zIndex = `${getTopZIndex()}`;\n tooltip.style.opacity = \"1\";\n}\n\nexport function hideTooltip(tooltip: HTMLElement) {\n tooltip.style.opacity = \"0\";\n}\n","<script lang=\"ts\">\n/**\n * Tooltip for concise contextual help text.\n *\n * The `trigger` slot is optional. Without a trigger slot, the tooltip anchors\n * to the previous sibling element and can still be controlled via exposed\n * methods.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, useSlots, useTemplateRef, watch } from \"vue\";\nimport {\n appendTooltipEl,\n createTooltipEl,\n hideTooltip,\n nextTooltipId,\n resolveTooltipId,\n showTooltip,\n} from \"../compose/tooltipDom.ts\";\n\ntype Props = {\n /**\n * Tooltip text\n * @demo\n */\n text: string;\n}\n\nconst props = defineProps<Props>();\nconst emit = defineEmits([\"tooltip-hide\"]);\n\nconst slots = useSlots();\nconst hasTrigger = computed(() => !!slots.trigger);\n\nconst hostRef = useTemplateRef<HTMLElement | null>(\"hostRef\");\nconst triggerRef = useTemplateRef<HTMLElement | null>(\"triggerRef\");\nconst tooltipEl = ref<HTMLElement | null>(null);\nconst isHovered = ref(false);\nconst isFocused = ref(false);\n\nlet resizeObserver: ResizeObserver | null = null;\nlet scrollListenerActive = false;\nlet hideTimer: number | null = null;\nlet tooltipId: string | null = null;\nlet anchorEl: HTMLElement | null = null;\n\nfunction getAnchorElement() {\n if (hasTrigger.value && triggerRef.value) {\n const triggerChild = triggerRef.value.firstElementChild;\n if (triggerChild instanceof HTMLElement) {\n return triggerChild;\n }\n return triggerRef.value;\n }\n\n const host = hostRef.value;\n const previousSibling = host?.previousElementSibling ?? host?.parentElement?.previousElementSibling;\n\n if (previousSibling instanceof HTMLElement) {\n return previousSibling;\n }\n\n return null;\n}\n\nfunction ensureTooltip() {\n if (!anchorEl) {\n return;\n }\n\n if (!tooltipId) {\n tooltipId = hasTrigger.value ? nextTooltipId(\"g-tooltip\") : resolveTooltipId(anchorEl, \"g-tooltip\");\n if (hasTrigger.value) {\n anchorEl.setAttribute(\"aria-describedby\", tooltipId);\n }\n }\n\n if (!tooltipEl.value) {\n tooltipEl.value = createTooltipEl(props.text, tooltipId);\n appendTooltipEl(tooltipEl.value);\n resizeObserver = new ResizeObserver(() => {\n if (tooltipEl.value && (isHovered.value || isFocused.value) && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n });\n resizeObserver.observe(tooltipEl.value);\n }\n}\n\nfunction onScroll() {\n if (tooltipEl.value && (isHovered.value || isFocused.value) && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n}\n\nfunction show() {\n isHovered.value = true;\n}\n\nfunction hide() {\n isHovered.value = false;\n isFocused.value = false;\n}\n\nfunction toggle() {\n if (isHovered.value || isFocused.value) {\n hide();\n return;\n }\n show();\n}\n\nfunction attachAnchorEvents(nextAnchor: HTMLElement | null) {\n if (anchorEl === nextAnchor) {\n return;\n }\n\n detachAnchorEvents();\n anchorEl = nextAnchor;\n\n if (!anchorEl) {\n return;\n }\n\n anchorEl.addEventListener(\"mouseenter\", onMouseEnter);\n anchorEl.addEventListener(\"mouseleave\", onMouseLeave);\n anchorEl.addEventListener(\"focusin\", onFocus);\n anchorEl.addEventListener(\"focusout\", onBlur);\n anchorEl.addEventListener(\"keydown\", onKeyDown);\n}\n\nfunction detachAnchorEvents() {\n if (!anchorEl) {\n return;\n }\n\n anchorEl.removeEventListener(\"mouseenter\", onMouseEnter);\n anchorEl.removeEventListener(\"mouseleave\", onMouseLeave);\n anchorEl.removeEventListener(\"focusin\", onFocus);\n anchorEl.removeEventListener(\"focusout\", onBlur);\n anchorEl.removeEventListener(\"keydown\", onKeyDown);\n anchorEl = null;\n}\n\nfunction onMouseEnter() {\n isHovered.value = true;\n}\n\nfunction onMouseLeave() {\n isHovered.value = false;\n}\n\nfunction onFocus() {\n isFocused.value = true;\n}\n\nfunction onBlur() {\n isFocused.value = false;\n}\n\nfunction onKeyDown(e: KeyboardEvent) {\n if (e.key === \"Escape\" || e.key === \"Esc\") {\n isHovered.value = false;\n isFocused.value = false;\n }\n}\n\nwatch(\n () => [hostRef.value, triggerRef.value, hasTrigger.value],\n () => {\n attachAnchorEvents(getAnchorElement());\n ensureTooltip();\n },\n { immediate: true },\n);\n\nwatch(\n () => props.text,\n (value) => {\n if (tooltipEl.value) {\n tooltipEl.value.textContent = value;\n }\n },\n);\n\nwatch(\n () => isHovered.value || isFocused.value,\n (active) => {\n if (active) {\n ensureTooltip();\n if (tooltipEl.value && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n if (!scrollListenerActive) {\n window.addEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = true;\n }\n return;\n }\n\n if (scrollListenerActive) {\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = false;\n }\n if (tooltipEl.value) {\n hideTooltip(tooltipEl.value);\n if (hideTimer) {\n clearTimeout(hideTimer);\n }\n hideTimer = window.setTimeout(() => {\n emit(\"tooltip-hide\");\n }, 150);\n }\n },\n);\n\nonBeforeUnmount(() => {\n if (scrollListenerActive) {\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n }\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n if (tooltipEl.value) {\n tooltipEl.value.remove();\n tooltipEl.value = null;\n }\n if (hideTimer) {\n clearTimeout(hideTimer);\n }\n if (hasTrigger.value && anchorEl) {\n anchorEl.removeAttribute(\"aria-describedby\");\n }\n detachAnchorEvents();\n});\n\ndefineExpose({\n show,\n hide,\n toggle,\n});\n</script>\n\n<template>\n <div ref=\"hostRef\" class=\"g-tooltip-host\">\n <div v-if=\"hasTrigger\" ref=\"triggerRef\" class=\"g-tooltip-trigger\">\n <slot name=\"trigger\"></slot>\n </div>\n </div>\n</template>\n\n<style>\ng-tooltip {\n display: contents;\n}\n.g-tooltip-host {\n display: contents;\n}\n\n.g-tooltip-trigger {\n display: inline-block;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Tooltip for concise contextual help text.\n *\n * The `trigger` slot is optional. Without a trigger slot, the tooltip anchors\n * to the previous sibling element and can still be controlled via exposed\n * methods.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, useSlots, useTemplateRef, watch } from \"vue\";\nimport {\n appendTooltipEl,\n createTooltipEl,\n hideTooltip,\n nextTooltipId,\n resolveTooltipId,\n showTooltip,\n} from \"../compose/tooltipDom.ts\";\n\ntype Props = {\n /**\n * Tooltip text\n * @demo\n */\n text: string;\n}\n\nconst props = defineProps<Props>();\nconst emit = defineEmits([\"tooltip-hide\"]);\n\nconst slots = useSlots();\nconst hasTrigger = computed(() => !!slots.trigger);\n\nconst hostRef = useTemplateRef<HTMLElement | null>(\"hostRef\");\nconst triggerRef = useTemplateRef<HTMLElement | null>(\"triggerRef\");\nconst tooltipEl = ref<HTMLElement | null>(null);\nconst isHovered = ref(false);\nconst isFocused = ref(false);\n\nlet resizeObserver: ResizeObserver | null = null;\nlet scrollListenerActive = false;\nlet hideTimer: number | null = null;\nlet tooltipId: string | null = null;\nlet anchorEl: HTMLElement | null = null;\n\nfunction getAnchorElement() {\n if (hasTrigger.value && triggerRef.value) {\n const triggerChild = triggerRef.value.firstElementChild;\n if (triggerChild instanceof HTMLElement) {\n return triggerChild;\n }\n return triggerRef.value;\n }\n\n const host = hostRef.value;\n const previousSibling = host?.previousElementSibling ?? host?.parentElement?.previousElementSibling;\n\n if (previousSibling instanceof HTMLElement) {\n return previousSibling;\n }\n\n return null;\n}\n\nfunction ensureTooltip() {\n if (!anchorEl) {\n return;\n }\n\n if (!tooltipId) {\n tooltipId = hasTrigger.value ? nextTooltipId(\"g-tooltip\") : resolveTooltipId(anchorEl, \"g-tooltip\");\n if (hasTrigger.value) {\n anchorEl.setAttribute(\"aria-describedby\", tooltipId);\n }\n }\n\n if (!tooltipEl.value) {\n tooltipEl.value = createTooltipEl(props.text, tooltipId);\n appendTooltipEl(tooltipEl.value);\n resizeObserver = new ResizeObserver(() => {\n if (tooltipEl.value && (isHovered.value || isFocused.value) && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n });\n resizeObserver.observe(tooltipEl.value);\n }\n}\n\nfunction onScroll() {\n if (tooltipEl.value && (isHovered.value || isFocused.value) && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n}\n\nfunction show() {\n isHovered.value = true;\n}\n\nfunction hide() {\n isHovered.value = false;\n isFocused.value = false;\n}\n\nfunction toggle() {\n if (isHovered.value || isFocused.value) {\n hide();\n return;\n }\n show();\n}\n\nfunction attachAnchorEvents(nextAnchor: HTMLElement | null) {\n if (anchorEl === nextAnchor) {\n return;\n }\n\n detachAnchorEvents();\n anchorEl = nextAnchor;\n\n if (!anchorEl) {\n return;\n }\n\n anchorEl.addEventListener(\"mouseenter\", onMouseEnter);\n anchorEl.addEventListener(\"mouseleave\", onMouseLeave);\n anchorEl.addEventListener(\"focusin\", onFocus);\n anchorEl.addEventListener(\"focusout\", onBlur);\n anchorEl.addEventListener(\"keydown\", onKeyDown);\n}\n\nfunction detachAnchorEvents() {\n if (!anchorEl) {\n return;\n }\n\n anchorEl.removeEventListener(\"mouseenter\", onMouseEnter);\n anchorEl.removeEventListener(\"mouseleave\", onMouseLeave);\n anchorEl.removeEventListener(\"focusin\", onFocus);\n anchorEl.removeEventListener(\"focusout\", onBlur);\n anchorEl.removeEventListener(\"keydown\", onKeyDown);\n anchorEl = null;\n}\n\nfunction onMouseEnter() {\n isHovered.value = true;\n}\n\nfunction onMouseLeave() {\n isHovered.value = false;\n}\n\nfunction onFocus() {\n isFocused.value = true;\n}\n\nfunction onBlur() {\n isFocused.value = false;\n}\n\nfunction onKeyDown(e: KeyboardEvent) {\n if (e.key === \"Escape\" || e.key === \"Esc\") {\n isHovered.value = false;\n isFocused.value = false;\n }\n}\n\nwatch(\n () => [hostRef.value, triggerRef.value, hasTrigger.value],\n () => {\n attachAnchorEvents(getAnchorElement());\n ensureTooltip();\n },\n { immediate: true },\n);\n\nwatch(\n () => props.text,\n (value) => {\n if (tooltipEl.value) {\n tooltipEl.value.textContent = value;\n }\n },\n);\n\nwatch(\n () => isHovered.value || isFocused.value,\n (active) => {\n if (active) {\n ensureTooltip();\n if (tooltipEl.value && anchorEl) {\n showTooltip(anchorEl, tooltipEl.value);\n }\n if (!scrollListenerActive) {\n window.addEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = true;\n }\n return;\n }\n\n if (scrollListenerActive) {\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = false;\n }\n if (tooltipEl.value) {\n hideTooltip(tooltipEl.value);\n if (hideTimer) {\n clearTimeout(hideTimer);\n }\n hideTimer = window.setTimeout(() => {\n emit(\"tooltip-hide\");\n }, 150);\n }\n },\n);\n\nonBeforeUnmount(() => {\n if (scrollListenerActive) {\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n }\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n if (tooltipEl.value) {\n tooltipEl.value.remove();\n tooltipEl.value = null;\n }\n if (hideTimer) {\n clearTimeout(hideTimer);\n }\n if (hasTrigger.value && anchorEl) {\n anchorEl.removeAttribute(\"aria-describedby\");\n }\n detachAnchorEvents();\n});\n\ndefineExpose({\n show,\n hide,\n toggle,\n});\n</script>\n\n<template>\n <div ref=\"hostRef\" class=\"g-tooltip-host\">\n <div v-if=\"hasTrigger\" ref=\"triggerRef\" class=\"g-tooltip-trigger\">\n <slot name=\"trigger\"></slot>\n </div>\n </div>\n</template>\n\n<style>\ng-tooltip {\n display: contents;\n}\n.g-tooltip-host {\n display: contents;\n}\n\n.g-tooltip-trigger {\n display: inline-block;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This component is just a radio button group with special styling.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Use the `options` prop to provide a list of choices. Each option can\n * be a string or an object with `label` and `value` properties.\n *\n * In addition to `v-model`, a `change` event is emitted when the\n * option changes from user interaction.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, useId, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype OptionType = {\n label: string;\n value: string | number;\n}\n\ntype Props = {\n /**\n * List of options to select from\n */\n options: Array<string | OptionType>;\n /**\n * Accessible label\n * @demo Select Option\n */\n label: string;\n /**\n * Size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\";\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n name: undefined,\n disabled: false,\n required: false,\n errors: () => [],\n formKey: undefined,\n});\n\nconst emit = defineEmits([\"change\"]);\nconst model = defineModel<string | number>({default: () => \"\"});\n\nconst baseId = useId();\n\n// Use form field composable for form registration and error handling\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst normalizedOptions = computed(() => {\n return props.options.map((opt) => {\n if (typeof opt === \"string\") {\n return { label: opt, value: opt };\n } else {\n return opt;\n }\n });\n});\n\nconst groupClasses = computed(() => [\n \"g-select-btn-group\",\n `g-select-btn-group--${props.size}`,\n]);\n\nconst getBtnClasses = (selected: boolean) => [\n \"g-select-btn\",\n selected ? \"g-select-btn--selected\" : \"\",\n { \"g-select-btn--disabled\": props.disabled },\n];\n\nfunction onChange(val: string | number) {\n if (!props.disabled && val !== model.value) {\n model.value = val;\n emit(\"change\", val);\n }\n}\n</script>\n\n<template>\n <fieldset :class=\"groupClasses\" :disabled=\"props.disabled\">\n <legend class=\"g-select-btn-legend\">\n {{ props.label }}<span v-if=\"props.required\" class=\"g-select-btn-required\" aria-hidden=\"true\"> *</span>\n </legend>\n <div class=\"g-select-btn-wrapper\" :class=\"{ 'g-select-btn-has-error': hasErrors }\">\n <div class=\"g-select-btn-row\">\n <template\n v-for=\"(option, idx) in normalizedOptions\"\n :key=\"option.value\"\n >\n <input\n class=\"g-select-btn-radio\"\n type=\"radio\"\n :id=\"`${baseId}-${option.value}`\"\n :name=\"props.name || baseId\"\n :value=\"option.value\"\n :checked=\"option.value === model\"\n :disabled=\"props.disabled\"\n :required=\"props.required && idx === 0\"\n @change=\"onChange(option.value)\"\n />\n <label\n :for=\"`${baseId}-${option.value}`\"\n :class=\"getBtnClasses(option.value === model)\"\n >\n {{ option.label }}\n </label>\n </template>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + baseId\"\n />\n </div>\n </fieldset>\n</template>\n\n<style>\ng-select-button {\n display: block;\n}\n.g-select-btn-group {\n border: none;\n margin: 0;\n padding: 0;\n min-width: 0;\n border-radius: 4px;\n}\n\n.g-select-btn-legend {\n position: static;\n display: block;\n margin: 0 0 0.5rem 0;\n padding: 0;\n font-weight: 700;\n color: var(--g-surface-900);\n}\n\n.g-select-btn-required {\n color: var(--g-danger-600);\n}\n\n.g-select-btn-row {\n display: flex;\n align-items: stretch;\n border-radius: var(--g-border-radius-m);\n}\n\n.g-select-btn-row:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n background: var(--ilw-color--focus--background);\n box-shadow: 0 0 0 2px var(--ilw-color--focus--background);\n}\n\n.g-select-btn-group--small {\n font-size: 0.875rem;\n}\n\n.g-select-btn-group--medium {\n font-size: 1rem;\n}\n\n.g-select-btn-group--large {\n font-size: 1.125rem;\n}\n\n.g-select-btn-radio {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n width: 0;\n height: 0;\n margin: 0;\n}\n\n.g-select-btn {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--g-primary-500);\n border-left-width: 1px;\n border-right-width: 1px;\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n font-weight: 700;\n padding: 0.5em;\n cursor: pointer;\n outline: none;\n &:hover {\n text-decoration: underline;\n }\n}\n.g-select-btn--selected {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n}\n.g-select-btn:first-of-type {\n border-top-left-radius: var(--g-border-radius-m);\n border-bottom-left-radius: var(--g-border-radius-m);\n border-left-width: 2px;\n}\n.g-select-btn:last-of-type {\n border-top-right-radius: var(--g-border-radius-m);\n border-bottom-right-radius: var(--g-border-radius-m);\n border-right-width: 2px;\n}\n\n.g-select-btn-has-error .g-select-btn-row {\n border: 2px solid var(--g-danger-600);\n border-radius: var(--g-border-radius-m);\n}\n\n.g-select-btn-has-error .g-select-btn {\n background: var(--g-danger-100);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This component is just a radio button group with special styling.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Use the `options` prop to provide a list of choices. Each option can\n * be a string or an object with `label` and `value` properties.\n *\n * In addition to `v-model`, a `change` event is emitted when the\n * option changes from user interaction.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, useId, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype OptionType = {\n label: string;\n value: string | number;\n}\n\ntype Props = {\n /**\n * List of options to select from\n */\n options: Array<string | OptionType>;\n /**\n * Accessible label\n * @demo Select Option\n */\n label: string;\n /**\n * Size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\";\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n name: undefined,\n disabled: false,\n required: false,\n errors: () => [],\n formKey: undefined,\n});\n\nconst emit = defineEmits([\"change\"]);\nconst model = defineModel<string | number>({default: () => \"\"});\n\nconst baseId = useId();\n\n// Use form field composable for form registration and error handling\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst normalizedOptions = computed(() => {\n return props.options.map((opt) => {\n if (typeof opt === \"string\") {\n return { label: opt, value: opt };\n } else {\n return opt;\n }\n });\n});\n\nconst groupClasses = computed(() => [\n \"g-select-btn-group\",\n `g-select-btn-group--${props.size}`,\n]);\n\nconst getBtnClasses = (selected: boolean) => [\n \"g-select-btn\",\n selected ? \"g-select-btn--selected\" : \"\",\n { \"g-select-btn--disabled\": props.disabled },\n];\n\nfunction onChange(val: string | number) {\n if (!props.disabled && val !== model.value) {\n model.value = val;\n emit(\"change\", val);\n }\n}\n</script>\n\n<template>\n <fieldset :class=\"groupClasses\" :disabled=\"props.disabled\">\n <legend class=\"g-select-btn-legend\">\n {{ props.label }}<span v-if=\"props.required\" class=\"g-select-btn-required\" aria-hidden=\"true\"> *</span>\n </legend>\n <div class=\"g-select-btn-wrapper\" :class=\"{ 'g-select-btn-has-error': hasErrors }\">\n <div class=\"g-select-btn-row\">\n <template\n v-for=\"(option, idx) in normalizedOptions\"\n :key=\"option.value\"\n >\n <input\n class=\"g-select-btn-radio\"\n type=\"radio\"\n :id=\"`${baseId}-${option.value}`\"\n :name=\"props.name || baseId\"\n :value=\"option.value\"\n :checked=\"option.value === model\"\n :disabled=\"props.disabled\"\n :required=\"props.required && idx === 0\"\n @change=\"onChange(option.value)\"\n />\n <label\n :for=\"`${baseId}-${option.value}`\"\n :class=\"getBtnClasses(option.value === model)\"\n >\n {{ option.label }}\n </label>\n </template>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + baseId\"\n />\n </div>\n </fieldset>\n</template>\n\n<style>\ng-select-button {\n display: block;\n}\n.g-select-btn-group {\n border: none;\n margin: 0;\n padding: 0;\n min-width: 0;\n border-radius: 4px;\n}\n\n.g-select-btn-legend {\n position: static;\n display: block;\n margin: 0 0 0.5rem 0;\n padding: 0;\n font-weight: 700;\n color: var(--g-surface-900);\n}\n\n.g-select-btn-required {\n color: var(--g-danger-600);\n}\n\n.g-select-btn-row {\n display: flex;\n align-items: stretch;\n border-radius: var(--g-border-radius-m);\n}\n\n.g-select-btn-row:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n background: var(--ilw-color--focus--background);\n box-shadow: 0 0 0 2px var(--ilw-color--focus--background);\n}\n\n.g-select-btn-group--small {\n font-size: 0.875rem;\n}\n\n.g-select-btn-group--medium {\n font-size: 1rem;\n}\n\n.g-select-btn-group--large {\n font-size: 1.125rem;\n}\n\n.g-select-btn-radio {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n width: 0;\n height: 0;\n margin: 0;\n}\n\n.g-select-btn {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--g-primary-500);\n border-left-width: 1px;\n border-right-width: 1px;\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n font-weight: 700;\n padding: 0.5em;\n cursor: pointer;\n outline: none;\n &:hover {\n text-decoration: underline;\n }\n}\n.g-select-btn--selected {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n}\n.g-select-btn:first-of-type {\n border-top-left-radius: var(--g-border-radius-m);\n border-bottom-left-radius: var(--g-border-radius-m);\n border-left-width: 2px;\n}\n.g-select-btn:last-of-type {\n border-top-right-radius: var(--g-border-radius-m);\n border-bottom-right-radius: var(--g-border-radius-m);\n border-right-width: 2px;\n}\n\n.g-select-btn-has-error .g-select-btn-row {\n border: 2px solid var(--g-danger-600);\n border-radius: var(--g-border-radius-m);\n}\n\n.g-select-btn-has-error .g-select-btn {\n background: var(--g-danger-100);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A component that can show progress from 1 to 100 or an indeterminate spinner.\n * If a value is omitted, the progress will be indeterminate.\n *\n * If no `label` is specified, the default accessible label will be \"Loading\".\n *\n * If a value is provided, the element will have the ARIA role `progressbar`\n * with the appropriate ARIA value attributes.\n *\n * If no value is provided, the element will have the ARIA role `status`.\n *\n * > [!NOTE]\n * > This element announces to assistive technologies, so make sure to\n * > limit its use to cases where that's appropriate.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Progress 1-100 or blank\n * @demo\n */\n value?: number;\n /**\n * Progress circle size\n * @demo\n */\n size?: \"tiny\" | \"small\" | \"medium\" | \"large\";\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n value: undefined,\n label: \"Loading\",\n});\n\nconst isDeterminate = computed(\n () =>\n props.value &&\n props.value >= 1 &&\n props.value <= 100,\n);\nconst radius = computed(() => {\n switch (props.size) {\n case \"tiny\":\n return 9;\n case \"small\":\n return 12;\n case \"large\":\n return 24;\n default:\n return 18;\n }\n});\nconst stroke = 4;\nconst circumference = computed(() => 2 * Math.PI * radius.value);\nconst progress = computed(() =>\n isDeterminate.value ? (props.value! / 100) * circumference.value : 0,\n);\n\nconst ariaProps = computed(() =>\n isDeterminate.value\n ? {\n role: \"progressbar\",\n \"aria-valuenow\": props.value,\n \"aria-valuemin\": 1,\n \"aria-valuemax\": 100,\n \"aria-label\": props.label,\n }\n : {\n \"role\": \"status\",\n \"aria-label\": props.label },\n);\n</script>\n\n<template>\n <span class=\"g-progress\" v-bind=\"ariaProps\">\n <svg\n :width=\"radius * 2 + stroke\"\n :height=\"radius * 2 + stroke\"\n :class=\"[\n 'g-progress__svg',\n {\n 'g-progress--determinate': isDeterminate,\n 'g-progress--indeterminate': !isDeterminate,\n },\n ]\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <circle\n class=\"g-progress__track\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n />\n <circle\n v-if=\"isDeterminate\"\n class=\"g-progress__value\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"circumference - progress\"\n style=\"transform: rotate(-90deg); transform-origin: center\"\n />\n <circle\n v-else\n class=\"g-progress__spinner\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n />\n </svg>\n </span>\n</template>\n\n<style>\ng-progress:not(:defined) {\n display: inline-block;\n vertical-align: middle;\n}\n.g-progress {\n display: inline-block;\n vertical-align: middle;\n}\n\n.g-progress__svg {\n display: block;\n}\n.g-progress__track {\n stroke: var(--g-surface-200);\n}\n.g-progress__value {\n stroke: var(--g-primary-500);\n transition: stroke-dashoffset 0.2s linear;\n}\n.g-progress__spinner {\n animation: g-progress-spin 1s linear infinite;\n transform-box: fill-box;\n transform-origin: center;\n}\n.g-progress__spinner {\n stroke: var(--g-primary-500);\n stroke-dasharray: 40 80;\n}\n@media (prefers-reduced-motion: reduce) {\n .g-progress__spinner {\n animation: g-progress-spin-blink 1s both infinite;\n }\n}\n@keyframes g-progress-spin {\n 100% {\n transform: rotate(360deg);\n }\n}\n@keyframes g-progress-spin-blink {\n 0%,\n 100% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A component that can show progress from 1 to 100 or an indeterminate spinner.\n * If a value is omitted, the progress will be indeterminate.\n *\n * If no `label` is specified, the default accessible label will be \"Loading\".\n *\n * If a value is provided, the element will have the ARIA role `progressbar`\n * with the appropriate ARIA value attributes.\n *\n * If no value is provided, the element will have the ARIA role `status`.\n *\n * > [!NOTE]\n * > This element announces to assistive technologies, so make sure to\n * > limit its use to cases where that's appropriate.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Progress 1-100 or blank\n * @demo\n */\n value?: number;\n /**\n * Progress circle size\n * @demo\n */\n size?: \"tiny\" | \"small\" | \"medium\" | \"large\";\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n size: \"medium\",\n value: undefined,\n label: \"Loading\",\n});\n\nconst isDeterminate = computed(\n () =>\n props.value &&\n props.value >= 1 &&\n props.value <= 100,\n);\nconst radius = computed(() => {\n switch (props.size) {\n case \"tiny\":\n return 9;\n case \"small\":\n return 12;\n case \"large\":\n return 24;\n default:\n return 18;\n }\n});\nconst stroke = 4;\nconst circumference = computed(() => 2 * Math.PI * radius.value);\nconst progress = computed(() =>\n isDeterminate.value ? (props.value! / 100) * circumference.value : 0,\n);\n\nconst ariaProps = computed(() =>\n isDeterminate.value\n ? {\n role: \"progressbar\",\n \"aria-valuenow\": props.value,\n \"aria-valuemin\": 1,\n \"aria-valuemax\": 100,\n \"aria-label\": props.label,\n }\n : {\n \"role\": \"status\",\n \"aria-label\": props.label },\n);\n</script>\n\n<template>\n <span class=\"g-progress\" v-bind=\"ariaProps\">\n <svg\n :width=\"radius * 2 + stroke\"\n :height=\"radius * 2 + stroke\"\n :class=\"[\n 'g-progress__svg',\n {\n 'g-progress--determinate': isDeterminate,\n 'g-progress--indeterminate': !isDeterminate,\n },\n ]\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <circle\n class=\"g-progress__track\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n />\n <circle\n v-if=\"isDeterminate\"\n class=\"g-progress__value\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"circumference - progress\"\n style=\"transform: rotate(-90deg); transform-origin: center\"\n />\n <circle\n v-else\n class=\"g-progress__spinner\"\n :cx=\"radius + stroke / 2\"\n :cy=\"radius + stroke / 2\"\n :r=\"radius\"\n :stroke-width=\"stroke\"\n fill=\"none\"\n />\n </svg>\n </span>\n</template>\n\n<style>\ng-progress:not(:defined) {\n display: inline-block;\n vertical-align: middle;\n}\n.g-progress {\n display: inline-block;\n vertical-align: middle;\n}\n\n.g-progress__svg {\n display: block;\n}\n.g-progress__track {\n stroke: var(--g-surface-200);\n}\n.g-progress__value {\n stroke: var(--g-primary-500);\n transition: stroke-dashoffset 0.2s linear;\n}\n.g-progress__spinner {\n animation: g-progress-spin 1s linear infinite;\n transform-box: fill-box;\n transform-origin: center;\n}\n.g-progress__spinner {\n stroke: var(--g-primary-500);\n stroke-dasharray: 40 80;\n}\n@media (prefers-reduced-motion: reduce) {\n .g-progress__spinner {\n animation: g-progress-spin-blink 1s both infinite;\n }\n}\n@keyframes g-progress-spin {\n 100% {\n transform: rotate(360deg);\n }\n}\n@keyframes g-progress-spin-blink {\n 0%,\n 100% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Alert dialog for confirming or canceling actions.\n *\n * Clicking on the outside or pressing the escape key will close the dialog\n * and that counts as canceling.\n *\n * > [!IMPORTANT]\n * >\n * > The surrounding page **must** have an element with the id `modal-root`,\n * > this dialog will be teleported to it, so it can properly be over all\n * > other content. The `modal-root` should be somewhere near the end of the\n * > page structure.\n *\n * **Slot** `default` is used as the content of the alert, and also becomes\n * the ARIA description of the alert.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { onBeforeMount, onMounted, ref, useId } from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Dialog label\n * @demo\n */\n label?: string;\n /**\n * Accept button text\n * @demo\n */\n buttonText?: string;\n /**\n * Accept button color\n * @demo\n */\n buttonColor?: \"primary\" | \"secondary\" | \"danger\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"Confirmation\",\n buttonText: \"Continue\",\n buttonColor: \"primary\",\n});\n\nconst emit = defineEmits([\"cancel\", \"confirm\"]);\n\nconst dialog = ref<HTMLElement | null>(null);\nconst open = ref(true);\n\nconst id = useId();\nconst { pop, push, isTop, zIndex } = useOverlayStack(id, true, true);\n\nconst { deactivate, activate } = useOverlayFocus(dialog, isTop);\n\nfunction close() {\n emit(\"cancel\");\n}\n\nuseOverlayEscape([dialog], isTop, open, close, pop);\n\nonMounted(() => {\n push();\n activate();\n});\n\nonBeforeMount(() => {\n pop();\n deactivate();\n});\n</script>\n\n<template>\n <Teleport to=\"#modal-root\">\n <Transition name=\"g-fade\" appear>\n <div\n :id=\"'alertdialog-' + id\"\n class=\"g-alertdialog\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"'alertdialog-label-' + id\"\n :aria-describedby=\"'alertdialog-description-' + id\"\n ref=\"dialog\"\n :style=\"{ zIndex }\"\n >\n <div class=\"g-alertdialog-inner\">\n <h2\n :id=\"'alertdialog-label-' + id\"\n class=\"g-alertdialog-label\"\n >\n {{ props.label }}\n </h2>\n <div\n :id=\"'alertdialog-description-' + id\"\n class=\"g-alertdialog-content\"\n >\n <slot />\n </div>\n <div class=\"g-alertdialog-actions\">\n <GButton outlined @click=\"emit('cancel')\"\n >Cancel</GButton\n >\n <GButton\n :theme=\"props.buttonColor\"\n @click=\"emit('confirm')\"\n >{{ props.buttonText }}</GButton\n >\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n.g-alertdialog {\n position: fixed;\n left: 50vw;\n top: 50vh;\n transform: translate(-50%, -50%);\n max-width: 400px;\n min-width: 300px;\n height: auto;\n max-height: 90vh;\n overflow-y: auto;\n background: var(--g-surface-50);\n border-top: 8px solid var(--g-accent-500);\n padding: 2rem;\n box-shadow:\n 0 0 2px rgba(0, 0, 0, 0.4),\n 0 10px 20px rgba(0, 0, 0, 0.1);\n}\n.g-alertdialog-label {\n font-family: var(--il-font-heading);\n font-size: 2rem;\n margin-top: 0;\n color: var(--g-primary-500);\n}\n.g-alertdialog-content {\n margin-top: 1rem;\n font-size: 1.125rem;\n color: var(--g-surface-900);\n}\n.g-alertdialog-actions {\n margin-top: 2rem;\n display: flex;\n justify-content: flex-end;\n gap: 1rem;\n}\n\n.fade-enter-active,\n.fade-leave-active {\n transition: opacity 0.2s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n opacity: 0;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Alert dialog for confirming or canceling actions.\n *\n * Clicking on the outside or pressing the escape key will close the dialog\n * and that counts as canceling.\n *\n * > [!IMPORTANT]\n * >\n * > The surrounding page **must** have an element with the id `modal-root`,\n * > this dialog will be teleported to it, so it can properly be over all\n * > other content. The `modal-root` should be somewhere near the end of the\n * > page structure.\n *\n * **Slot** `default` is used as the content of the alert, and also becomes\n * the ARIA description of the alert.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { onBeforeMount, onMounted, ref, useId } from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Dialog label\n * @demo\n */\n label?: string;\n /**\n * Accept button text\n * @demo\n */\n buttonText?: string;\n /**\n * Accept button color\n * @demo\n */\n buttonColor?: \"primary\" | \"secondary\" | \"danger\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"Confirmation\",\n buttonText: \"Continue\",\n buttonColor: \"primary\",\n});\n\nconst emit = defineEmits([\"cancel\", \"confirm\"]);\n\nconst dialog = ref<HTMLElement | null>(null);\nconst open = ref(true);\n\nconst id = useId();\nconst { pop, push, isTop, zIndex } = useOverlayStack(id, true, true);\n\nconst { deactivate, activate } = useOverlayFocus(dialog, isTop);\n\nfunction close() {\n emit(\"cancel\");\n}\n\nuseOverlayEscape([dialog], isTop, open, close, pop);\n\nonMounted(() => {\n push();\n activate();\n});\n\nonBeforeMount(() => {\n pop();\n deactivate();\n});\n</script>\n\n<template>\n <Teleport to=\"#modal-root\">\n <Transition name=\"g-fade\" appear>\n <div\n :id=\"'alertdialog-' + id\"\n class=\"g-alertdialog\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"'alertdialog-label-' + id\"\n :aria-describedby=\"'alertdialog-description-' + id\"\n ref=\"dialog\"\n :style=\"{ zIndex }\"\n >\n <div class=\"g-alertdialog-inner\">\n <h2\n :id=\"'alertdialog-label-' + id\"\n class=\"g-alertdialog-label\"\n >\n {{ props.label }}\n </h2>\n <div\n :id=\"'alertdialog-description-' + id\"\n class=\"g-alertdialog-content\"\n >\n <slot />\n </div>\n <div class=\"g-alertdialog-actions\">\n <GButton outlined @click=\"emit('cancel')\"\n >Cancel</GButton\n >\n <GButton\n :theme=\"props.buttonColor\"\n @click=\"emit('confirm')\"\n >{{ props.buttonText }}</GButton\n >\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n.g-alertdialog {\n position: fixed;\n left: 50vw;\n top: 50vh;\n transform: translate(-50%, -50%);\n max-width: 400px;\n min-width: 300px;\n height: auto;\n max-height: 90vh;\n overflow-y: auto;\n background: var(--g-surface-50);\n border-top: 8px solid var(--g-accent-500);\n padding: 2rem;\n box-shadow:\n 0 0 2px rgba(0, 0, 0, 0.4),\n 0 10px 20px rgba(0, 0, 0, 0.1);\n}\n.g-alertdialog-label {\n font-family: var(--il-font-heading);\n font-size: 2rem;\n margin-top: 0;\n color: var(--g-primary-500);\n}\n.g-alertdialog-content {\n margin-top: 1rem;\n font-size: 1.125rem;\n color: var(--g-surface-900);\n}\n.g-alertdialog-actions {\n margin-top: 2rem;\n display: flex;\n justify-content: flex-end;\n gap: 1rem;\n}\n\n.fade-enter-active,\n.fade-leave-active {\n transition: opacity 0.2s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n opacity: 0;\n}\n</style>\n","import { computed, nextTick, onBeforeUnmount, ref, watch, type Ref } from \"vue\";\nimport { useOverlayStack } from \"./useOverlayStack.ts\";\n\n/**\n * A normalized select option with a label and a value.\n * Both GSelect and GMultiSelect accept `Array<string | SelectOption>` and\n * normalize the strings to this shape internally.\n */\nexport type SelectOption = {\n label: string;\n value: string | number;\n};\n\n/**\n * Normalizes a mixed `Array<string | SelectOption>` into a uniform\n * `SelectOption[]`. Strings are converted to `{ label: s, value: s }`.\n */\nexport function normalizeSelectOptions(\n options: Array<string | SelectOption>,\n): SelectOption[] {\n return options.map((opt) =>\n typeof opt === \"string\" ? { label: opt, value: opt } : opt,\n );\n}\n\nexport interface UseSelectDropdownOptions {\n /** Reactive flag that is `true` when the dropdown is visible. */\n open: Ref<boolean>;\n /**\n * The element whose bounding rect is used to decide whether the dropdown\n * opens above or below (the trigger / combobox control).\n */\n anchorRef: Ref<HTMLElement | null>;\n /** The listbox element – used to measure its natural scroll-height. */\n listboxRef: Ref<HTMLElement | null>;\n /**\n * A stable unique id that doubles as the overlay-stack entry and the\n * prefix for option element ids (`${baseId}-option-${index}`).\n */\n baseId: string;\n /** The currently-highlighted option index, used by scrollOptionIntoView. */\n activeIndex: Ref<number>;\n}\n\nexport interface UseSelectDropdownReturn {\n /** Current placement of the dropdown relative to the anchor. */\n menuPlacement: Ref<\"below\" | \"above\">;\n /** Inline styles to apply to the listbox element. */\n menuStyle: Readonly<Ref<Record<string, string>>>;\n /**\n * Whether this overlay is topmost in the global stack.\n * Use this to guard Escape-key handling.\n */\n isTop: Ref<boolean>;\n /** Scrolls the currently-active option into view. */\n scrollOptionIntoView: () => void;\n}\n\n/**\n * Shared dropdown behaviour for GSelect and GMultiSelect.\n *\n * Manages:\n * - Menu placement (above / below) and max-height constraints\n * - window resize / scroll listeners (added when open, removed when closed)\n * - Overlay-stack registration (push on open, pop on close / unmount)\n * - Scrolling the active option into view\n */\nexport function useSelectDropdown({\n open,\n anchorRef,\n listboxRef,\n baseId,\n activeIndex,\n}: UseSelectDropdownOptions): UseSelectDropdownReturn {\n const { push, pop, isTop } = useOverlayStack(baseId);\n\n const menuPlacement = ref<\"below\" | \"above\">(\"below\");\n const menuMaxHeight = ref<number | null>(null);\n\n const menuStyle = computed(() => {\n const style: Record<string, string> = {};\n if (menuMaxHeight.value !== null) {\n style.maxHeight = `${menuMaxHeight.value}px`;\n }\n if (menuPlacement.value === \"above\") {\n style.top = \"auto\";\n style.bottom = \"100%\";\n } else {\n style.top = \"100%\";\n style.bottom = \"auto\";\n }\n return style;\n });\n\n function updateMenuPlacement() {\n if (!open.value || !anchorRef.value) return;\n const rect = anchorRef.value.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const spaceAbove = rect.top;\n const listboxFullHeight = listboxRef.value?.scrollHeight ?? 200;\n const minSpaceToOpenBelow = Math.min(200, listboxFullHeight);\n const gap = 8;\n\n if (spaceBelow >= minSpaceToOpenBelow) {\n menuPlacement.value = \"below\";\n menuMaxHeight.value = Math.max(0, Math.floor(spaceBelow - gap));\n } else if (spaceAbove > spaceBelow) {\n menuPlacement.value = \"above\";\n menuMaxHeight.value = Math.max(0, Math.floor(spaceAbove - gap));\n } else {\n menuPlacement.value = \"below\";\n menuMaxHeight.value = Math.max(0, Math.floor(spaceBelow - gap));\n }\n }\n\n let removeWindowListeners: (() => void) | null = null;\n\n function addWindowListeners() {\n if (removeWindowListeners) return;\n const onChange = () => updateMenuPlacement();\n window.addEventListener(\"resize\", onChange, { passive: true });\n window.addEventListener(\"scroll\", onChange, {\n passive: true,\n capture: true,\n });\n removeWindowListeners = () => {\n window.removeEventListener(\"resize\", onChange);\n window.removeEventListener(\"scroll\", onChange, true);\n removeWindowListeners = null;\n };\n }\n\n function removeListeners() {\n if (removeWindowListeners) removeWindowListeners();\n }\n\n watch(open, (val) => {\n if (val) {\n push();\n addWindowListeners();\n nextTick(() => updateMenuPlacement());\n } else {\n pop();\n removeListeners();\n menuPlacement.value = \"below\";\n menuMaxHeight.value = null;\n }\n });\n\n // useOverlayStack already registers onBeforeUnmount(pop); we only need\n // to ensure window listeners are cleaned up if the component is destroyed\n // while the dropdown is still open.\n onBeforeUnmount(() => {\n removeListeners();\n });\n\n function scrollOptionIntoView() {\n nextTick(() => {\n const el = document.getElementById(\n `${baseId}-option-${activeIndex.value}`,\n );\n if (el) el.scrollIntoView({ block: \"nearest\" });\n });\n }\n\n return {\n menuPlacement,\n menuStyle,\n isTop,\n scrollOptionIntoView,\n };\n}\n","<script lang=\"ts\">\n/**\n * By default, this component behaves like a normal select element with\n * custom styling.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * The component can be marked `searchable` to enable search functionality.\n * This turns it into a text input that filters the options. Filtering is\n * done with a simple lower-case string search.\n *\n * The `options` prop can be an array of strings or objects with `label`\n * and `value` properties.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, ref, useId, watch, toRef } from \"vue\";\nimport { useSelectDropdown, normalizeSelectOptions, type SelectOption } from \"../compose/useSelectDropdown.ts\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype Props = {\n /**\n * List of options to choose from\n */\n options: Array<string | SelectOption>;\n /**\n * Accessible label\n * @demo Select Option\n */\n label: string;\n /**\n * Hide the label visually\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Placeholder\n *\n * Only used if the component is searchable.\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Searchable\n * @demo\n */\n searchable?: boolean;\n /**\n * Show clear button\n * @demo\n */\n clearButton?: boolean;\n /**\n * Compact\n * @demo\n */\n compact?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n required: false,\n searchable: false,\n compact: false,\n errors: () => [],\n});\nconst emit = defineEmits([\"change\"]);\nconst model = defineModel<string | number | null>();\n\nconst baseId = useId();\nconst comboRef = ref<HTMLElement | null>(null);\nconst listboxRef = ref<HTMLElement | null>(null);\nconst comboInputRef = ref<HTMLInputElement | null>(null);\nconst open = ref(false);\nconst activeIndex = ref(0);\nconst ignoreBlur = ref(false);\nconst ignoreFocus = ref(false);\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst { menuPlacement, menuStyle, isTop, scrollOptionIntoView } = useSelectDropdown({\n open,\n anchorRef: comboRef,\n listboxRef,\n baseId,\n activeIndex,\n});\n\nconst normalizedOptions = computed(() => normalizeSelectOptions(props.options));\n\nconst searchQuery = ref(\"\");\n\nconst filteredOptions = computed(() => {\n if (!props.searchable || !open.value || !searchQuery.value) {\n return normalizedOptions.value;\n }\n const q = searchQuery.value.toLowerCase();\n return normalizedOptions.value.filter((opt) =>\n opt.label.toLowerCase().includes(q),\n );\n});\n\nconst selectedIndex = computed(() => {\n return filteredOptions.value.findIndex((opt) => opt.value === model.value);\n});\n\nwatch(\n () => model.value,\n (val) => {\n const idx = filteredOptions.value.findIndex((opt) => opt.value === val);\n if (idx !== -1) {\n activeIndex.value = idx;\n }\n },\n);\n\nfunction openMenu() {\n if (props.disabled) {\n return;\n }\n open.value = true;\n if (props.searchable) {\n searchQuery.value = \"\";\n // If a value is selected, highlight it in filtered list\n const idx = filteredOptions.value.findIndex(\n (opt) => opt.value === model.value,\n );\n activeIndex.value = idx !== -1 ? idx : 0;\n nextTick(() => {\n if (comboInputRef.value) {\n comboInputRef.value.focus();\n }\n });\n }\n}\n\nfunction closeMenu() {\n open.value = false;\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n}\n\nfunction onComboFocus(e: FocusEvent) {\n if (props.disabled) {\n return;\n }\n if (props.searchable) {\n if (ignoreFocus.value) {\n ignoreFocus.value = false;\n return;\n }\n openMenu();\n }\n}\n\nfunction onComboInput(e: Event) {\n if (!props.searchable) return;\n // If closed and user types, open and start search\n if (!open.value) {\n openMenu();\n }\n searchQuery.value = (e.target as HTMLInputElement).value;\n // Always highlight the first filtered option, or selected if present\n const idx = filteredOptions.value.findIndex(\n (opt) => opt.value === model.value,\n );\n activeIndex.value = idx !== -1 ? idx : 0;\n}\n\nfunction onComboBlur(e: FocusEvent) {\n // Prevent closing if focus moves to the dropdown menu (e.g. scrollbar interaction)\n const relatedTarget = e.relatedTarget as HTMLElement | null;\n if (ignoreBlur.value) {\n ignoreBlur.value = false;\n return;\n }\n if (\n relatedTarget &&\n listboxRef.value &&\n listboxRef.value.contains(relatedTarget)\n ) {\n // Focus moved inside the dropdown, don't close\n return;\n }\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n closeMenu();\n}\n\nfunction selectOption(idx: number) {\n const opt = filteredOptions.value[idx];\n if (opt && opt.value !== model.value) {\n model.value = opt.value;\n emit(\"change\", opt.value);\n }\n ignoreFocus.value = true;\n closeMenu();\n // We want to ignore the focus just briefly so it doesn't re-open\n setTimeout(() => {\n ignoreFocus.value = false;\n }, 100);\n}\n\nfunction onComboClick() {\n if (props.disabled) {\n return;\n }\n if (!open.value) {\n openMenu();\n } else {\n closeMenu();\n }\n}\n\nfunction onComboKeydown(e: KeyboardEvent) {\n if (props.disabled) {\n return;\n }\n const max = filteredOptions.value.length - 1;\n if (!open.value && [\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(e.key)) {\n e.preventDefault();\n openMenu();\n return;\n }\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.min(max, activeIndex.value + 1);\n scrollOptionIntoView();\n }\n break;\n case \"ArrowUp\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.max(0, activeIndex.value - 1);\n scrollOptionIntoView();\n }\n break;\n case \"Home\":\n e.preventDefault();\n activeIndex.value = 0;\n scrollOptionIntoView();\n break;\n case \"End\":\n e.preventDefault();\n activeIndex.value = max;\n scrollOptionIntoView();\n break;\n case \"Enter\":\n case \" \":\n e.preventDefault();\n if (open.value) {\n selectOption(activeIndex.value);\n } else {\n openMenu();\n }\n break;\n case \"Escape\":\n if (isTop.value) {\n e.preventDefault();\n setTimeout(() => {\n closeMenu();\n }, 0);\n }\n break;\n }\n}\n\nfunction onOptionClick(idx: number) {\n selectOption(idx);\n}\n\nfunction onOptionMouseDown() {\n ignoreBlur.value = true;\n}\n\nconst showClearButton = computed(() => {\n return (\n props.clearButton &&\n model.value !== null &&\n model.value !== undefined &&\n !props.disabled\n );\n});\n\nfunction clearValue() {\n if (!props.disabled) {\n model.value = null;\n emit(\"change\", null);\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n }\n}\n</script>\n\n<template>\n <div\n class=\"g-select-root g-select-combo\"\n :class=\"{ 'g-select-open': open, 'g-select-compact': compact, 'g-select-has-error': hasErrors }\"\n >\n <div\n v-if=\"!hiddenLabel\"\n :id=\"baseId + '-label'\"\n class=\"g-select-combo-label g-select-label\"\n >\n {{ props.label }}<span v-if=\"props.required\" class=\"g-select-required\" aria-hidden=\"true\"> *</span>\n </div>\n <div class=\"g-select-input-wrap\">\n <div\n v-if=\"props.searchable\"\n class=\"g-select-combo-input g-select-control\"\n :id=\"baseId\"\n >\n <input\n ref=\"comboRef\"\n type=\"text\"\n name=\"comboInput\"\n class=\"g-select-search-input\"\n :class=\"{ 'g-select-clearable': clearButton }\"\n :value=\"\n open\n ? searchQuery\n : normalizedOptions[selectedIndex]\n ? normalizedOptions[selectedIndex].label\n : ''\n \"\n :placeholder=\"open ? '' : placeholder\"\n :disabled=\"props.disabled\"\n @focus=\"onComboFocus\"\n @input=\"onComboInput\"\n @keydown=\"onComboKeydown\"\n @blur=\"onComboBlur\"\n :aria-autocomplete=\"'list'\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-required=\"props.required ? 'true' : undefined\"\n aria-haspopup=\"listbox\"\n :aria-activedescendant=\"\n open ? baseId + '-option-' + activeIndex : undefined\n \"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n role=\"combobox\"\n autocomplete=\"off\"\n />\n <button\n v-if=\"showClearButton\"\n type=\"button\"\n class=\"g-select-clear-btn\"\n @click=\"clearValue\"\n >\n <svg\n role=\"img\"\n aria-label=\"Clear Selection\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1.125em\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n\n <svg\n class=\"g-select-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n </div>\n <div\n v-else\n ref=\"comboRef\"\n :id=\"baseId\"\n class=\"g-select-combo-button g-select-control\"\n :class=\"{ 'g-select-clearable': clearButton }\"\n role=\"combobox\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-required=\"props.required ? 'true' : undefined\"\n aria-haspopup=\"listbox\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n :aria-activedescendant=\"\n open ? baseId + '-option-' + activeIndex : undefined\n \"\n tabindex=\"0\"\n @click=\"onComboClick\"\n @keydown=\"onComboKeydown\"\n @focus=\"onComboFocus\"\n @blur=\"onComboBlur\"\n >\n {{\n normalizedOptions[selectedIndex]\n ? normalizedOptions[selectedIndex].label\n : \"\"\n }}\n <button\n v-if=\"showClearButton\"\n type=\"button\"\n class=\"g-select-clear-btn\"\n @click.stop=\"clearValue\"\n >\n <svg\n role=\"img\"\n aria-label=\"Clear Selection\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1.125em\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n\n <svg\n class=\"g-select-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n </div>\n <div\n v-show=\"open\"\n ref=\"listboxRef\"\n class=\"g-select-combo-menu g-select-list\"\n :class=\"{\n 'g-select-combo-menu--above': menuPlacement === 'above',\n }\"\n :style=\"menuStyle\"\n role=\"listbox\"\n :id=\"baseId + '-listbox'\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n tabindex=\"-1\"\n >\n <template v-if=\"filteredOptions.length > 0\">\n <div\n v-for=\"(option, idx) in filteredOptions\"\n :key=\"option.value\"\n :id=\"baseId + '-option-' + idx\"\n class=\"g-select-combo-option g-select-option\"\n :class=\"{\n 'g-select-option-current': idx === activeIndex,\n 'ilw-theme-blue': option.value === model,\n }\"\n role=\"option\"\n :aria-selected=\"\n option.value === model ? 'true' : 'false'\n \"\n @mousedown=\"onOptionMouseDown\"\n @click=\"onOptionClick(idx)\"\n >\n <slot\n name=\"option\"\n :option=\"option\"\n :selected=\"option.value === model\"\n :index=\"idx\"\n >\n {{ option.label }}\n </slot>\n </div>\n </template>\n <template v-else>\n <div\n aria-live=\"polite\"\n class=\"g-select-combo-option g-select-option g-select-no-results\"\n >\n No results found.\n </div>\n </template>\n </div>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + baseId\"\n />\n </div>\n</template>\n\n<style>\ng-select {\n display: block;\n}\n.g-select-root {\n position: relative;\n font-size: 1rem;\n}\n\n.g-select-label {\n font-weight: 700;\n color: var(--g-surface-900);\n margin-bottom: 0.5em;\n}\n\n.g-select-required {\n color: var(--g-danger-600);\n}\n\n.g-select-input-wrap {\n position: relative;\n}\n\n.g-select-control {\n line-height: 1.5;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-radius: var(--g-border-radius-m);\n text-align: left;\n position: relative;\n\n &:focus-visible {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n outline-color: var(--g-primary-500);\n\n .g-select-caret {\n color: var(--g-primary-500);\n }\n }\n\n &:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 1px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n}\n\n.g-select-combo-button {\n}\n\n.g-select-caret {\n position: absolute;\n right: 0.5em;\n line-height: 1.5em;\n top: calc(50% - 0.55em);\n color: var(--g-accent-700);\n pointer-events: none;\n\n transform: rotate(90deg);\n}\n\n.g-select-open .g-select-caret {\n transform: rotate(-90deg);\n}\n\n.g-select-combo-menu {\n background-color: var(--g-surface-0);\n border: 2px solid var(--g-surface-700);\n border-radius: 0 0 var(--g-border-radius-m) var(--g-border-radius-m);\n box-sizing: border-box;\n box-shadow:\n 0 4px 4px rgba(0, 0, 0, 0.2),\n 0 1px 0 1px rgba(0, 0, 0, 0.18);\n max-height: 50vh;\n overflow-y: auto;\n left: 0;\n position: absolute;\n top: 100%;\n width: 100%;\n z-index: 1000;\n display: none;\n}\n\n.g-select-combo-menu--above {\n border-radius: var(--g-border-radius-m) var(--g-border-radius-m) 0 0;\n}\n\n.g-select-open .g-select-combo-menu {\n display: block;\n}\n\n.g-select-combo-option {\n padding: 0.5em 0.5em;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n border-color: var(--g-accent-700);\n }\n}\n\n.g-select-option-current {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n\n &:hover {\n color: var(--g-primary-text);\n }\n}\n\n.g-select-search-input {\n box-sizing: border-box;\n display: block;\n width: 100%;\n font-family: var(--il-font-sans);\n border: none;\n box-sizing: border-box;\n text-overflow: ellipsis;\n font-size: 1em;\n\n &.g-select-clearable {\n padding-right: 3.5em; /* Space for clear button */\n }\n}\n\n.g-select-search-input,\n.g-select-combo-button {\n padding: 0.25em 2em 0.25em 0.75em;\n line-height: 1.875em;\n box-sizing: border-box;\n}\n.g-select-combo-button {\n /* Padding + line height + padding + border */\n min-height: calc(0.25em + 1.875em + 0.25em + 4px);\n min-width: 8rem;\n}\n\n.g-select-compact {\n font-size: 0.875rem;\n}\n\n.g-select-search-input,\n.g-select-combo-button {\n &.g-select-clearable {\n padding-right: 3em; /* Space for clear button */\n }\n}\n\n.g-select-no-results {\n padding: 0.25em 1em;\n text-align: center;\n color: var(--g-surface-900);\n font-style: italic;\n}\n\n.g-select-clear-btn {\n position: absolute;\n right: 1.25em;\n top: calc(50% - 1.15em);\n background: none;\n border: none;\n color: var(--g-accent-700);\n font-size: 1.25em;\n cursor: pointer;\n padding: 0.6em 0.3em 0.4em;\n line-height: 1;\n\n &:hover {\n color: var(--g-accent-700);\n }\n &:focus {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n outline-color: var(--g-primary-500);\n }\n}\n\n.g-select-has-error .g-select-control {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * By default, this component behaves like a normal select element with\n * custom styling.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * The component can be marked `searchable` to enable search functionality.\n * This turns it into a text input that filters the options. Filtering is\n * done with a simple lower-case string search.\n *\n * The `options` prop can be an array of strings or objects with `label`\n * and `value` properties.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, ref, useId, watch, toRef } from \"vue\";\nimport { useSelectDropdown, normalizeSelectOptions, type SelectOption } from \"../compose/useSelectDropdown.ts\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype Props = {\n /**\n * List of options to choose from\n */\n options: Array<string | SelectOption>;\n /**\n * Accessible label\n * @demo Select Option\n */\n label: string;\n /**\n * Hide the label visually\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Placeholder\n *\n * Only used if the component is searchable.\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Searchable\n * @demo\n */\n searchable?: boolean;\n /**\n * Show clear button\n * @demo\n */\n clearButton?: boolean;\n /**\n * Compact\n * @demo\n */\n compact?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n required: false,\n searchable: false,\n compact: false,\n errors: () => [],\n});\nconst emit = defineEmits([\"change\"]);\nconst model = defineModel<string | number | null>();\n\nconst baseId = useId();\nconst comboRef = ref<HTMLElement | null>(null);\nconst listboxRef = ref<HTMLElement | null>(null);\nconst comboInputRef = ref<HTMLInputElement | null>(null);\nconst open = ref(false);\nconst activeIndex = ref(0);\nconst ignoreBlur = ref(false);\nconst ignoreFocus = ref(false);\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nconst { menuPlacement, menuStyle, isTop, scrollOptionIntoView } = useSelectDropdown({\n open,\n anchorRef: comboRef,\n listboxRef,\n baseId,\n activeIndex,\n});\n\nconst normalizedOptions = computed(() => normalizeSelectOptions(props.options));\n\nconst searchQuery = ref(\"\");\n\nconst filteredOptions = computed(() => {\n if (!props.searchable || !open.value || !searchQuery.value) {\n return normalizedOptions.value;\n }\n const q = searchQuery.value.toLowerCase();\n return normalizedOptions.value.filter((opt) =>\n opt.label.toLowerCase().includes(q),\n );\n});\n\nconst selectedIndex = computed(() => {\n return filteredOptions.value.findIndex((opt) => opt.value === model.value);\n});\n\nwatch(\n () => model.value,\n (val) => {\n const idx = filteredOptions.value.findIndex((opt) => opt.value === val);\n if (idx !== -1) {\n activeIndex.value = idx;\n }\n },\n);\n\nfunction openMenu() {\n if (props.disabled) {\n return;\n }\n open.value = true;\n if (props.searchable) {\n searchQuery.value = \"\";\n // If a value is selected, highlight it in filtered list\n const idx = filteredOptions.value.findIndex(\n (opt) => opt.value === model.value,\n );\n activeIndex.value = idx !== -1 ? idx : 0;\n nextTick(() => {\n if (comboInputRef.value) {\n comboInputRef.value.focus();\n }\n });\n }\n}\n\nfunction closeMenu() {\n open.value = false;\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n}\n\nfunction onComboFocus(e: FocusEvent) {\n if (props.disabled) {\n return;\n }\n if (props.searchable) {\n if (ignoreFocus.value) {\n ignoreFocus.value = false;\n return;\n }\n openMenu();\n }\n}\n\nfunction onComboInput(e: Event) {\n if (!props.searchable) return;\n // If closed and user types, open and start search\n if (!open.value) {\n openMenu();\n }\n searchQuery.value = (e.target as HTMLInputElement).value;\n // Always highlight the first filtered option, or selected if present\n const idx = filteredOptions.value.findIndex(\n (opt) => opt.value === model.value,\n );\n activeIndex.value = idx !== -1 ? idx : 0;\n}\n\nfunction onComboBlur(e: FocusEvent) {\n // Prevent closing if focus moves to the dropdown menu (e.g. scrollbar interaction)\n const relatedTarget = e.relatedTarget as HTMLElement | null;\n if (ignoreBlur.value) {\n ignoreBlur.value = false;\n return;\n }\n if (\n relatedTarget &&\n listboxRef.value &&\n listboxRef.value.contains(relatedTarget)\n ) {\n // Focus moved inside the dropdown, don't close\n return;\n }\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n closeMenu();\n}\n\nfunction selectOption(idx: number) {\n const opt = filteredOptions.value[idx];\n if (opt && opt.value !== model.value) {\n model.value = opt.value;\n emit(\"change\", opt.value);\n }\n ignoreFocus.value = true;\n closeMenu();\n // We want to ignore the focus just briefly so it doesn't re-open\n setTimeout(() => {\n ignoreFocus.value = false;\n }, 100);\n}\n\nfunction onComboClick() {\n if (props.disabled) {\n return;\n }\n if (!open.value) {\n openMenu();\n } else {\n closeMenu();\n }\n}\n\nfunction onComboKeydown(e: KeyboardEvent) {\n if (props.disabled) {\n return;\n }\n const max = filteredOptions.value.length - 1;\n if (!open.value && [\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(e.key)) {\n e.preventDefault();\n openMenu();\n return;\n }\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.min(max, activeIndex.value + 1);\n scrollOptionIntoView();\n }\n break;\n case \"ArrowUp\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.max(0, activeIndex.value - 1);\n scrollOptionIntoView();\n }\n break;\n case \"Home\":\n e.preventDefault();\n activeIndex.value = 0;\n scrollOptionIntoView();\n break;\n case \"End\":\n e.preventDefault();\n activeIndex.value = max;\n scrollOptionIntoView();\n break;\n case \"Enter\":\n case \" \":\n e.preventDefault();\n if (open.value) {\n selectOption(activeIndex.value);\n } else {\n openMenu();\n }\n break;\n case \"Escape\":\n if (isTop.value) {\n e.preventDefault();\n setTimeout(() => {\n closeMenu();\n }, 0);\n }\n break;\n }\n}\n\nfunction onOptionClick(idx: number) {\n selectOption(idx);\n}\n\nfunction onOptionMouseDown() {\n ignoreBlur.value = true;\n}\n\nconst showClearButton = computed(() => {\n return (\n props.clearButton &&\n model.value !== null &&\n model.value !== undefined &&\n !props.disabled\n );\n});\n\nfunction clearValue() {\n if (!props.disabled) {\n model.value = null;\n emit(\"change\", null);\n if (props.searchable) {\n searchQuery.value = \"\";\n }\n }\n}\n</script>\n\n<template>\n <div\n class=\"g-select-root g-select-combo\"\n :class=\"{ 'g-select-open': open, 'g-select-compact': compact, 'g-select-has-error': hasErrors }\"\n >\n <div\n v-if=\"!hiddenLabel\"\n :id=\"baseId + '-label'\"\n class=\"g-select-combo-label g-select-label\"\n >\n {{ props.label }}<span v-if=\"props.required\" class=\"g-select-required\" aria-hidden=\"true\"> *</span>\n </div>\n <div class=\"g-select-input-wrap\">\n <div\n v-if=\"props.searchable\"\n class=\"g-select-combo-input g-select-control\"\n :id=\"baseId\"\n >\n <input\n ref=\"comboRef\"\n type=\"text\"\n name=\"comboInput\"\n class=\"g-select-search-input\"\n :class=\"{ 'g-select-clearable': clearButton }\"\n :value=\"\n open\n ? searchQuery\n : normalizedOptions[selectedIndex]\n ? normalizedOptions[selectedIndex].label\n : ''\n \"\n :placeholder=\"open ? '' : placeholder\"\n :disabled=\"props.disabled\"\n @focus=\"onComboFocus\"\n @input=\"onComboInput\"\n @keydown=\"onComboKeydown\"\n @blur=\"onComboBlur\"\n :aria-autocomplete=\"'list'\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-required=\"props.required ? 'true' : undefined\"\n aria-haspopup=\"listbox\"\n :aria-activedescendant=\"\n open ? baseId + '-option-' + activeIndex : undefined\n \"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n role=\"combobox\"\n autocomplete=\"off\"\n />\n <button\n v-if=\"showClearButton\"\n type=\"button\"\n class=\"g-select-clear-btn\"\n @click=\"clearValue\"\n >\n <svg\n role=\"img\"\n aria-label=\"Clear Selection\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1.125em\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n\n <svg\n class=\"g-select-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n </div>\n <div\n v-else\n ref=\"comboRef\"\n :id=\"baseId\"\n class=\"g-select-combo-button g-select-control\"\n :class=\"{ 'g-select-clearable': clearButton }\"\n role=\"combobox\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-required=\"props.required ? 'true' : undefined\"\n aria-haspopup=\"listbox\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n :aria-activedescendant=\"\n open ? baseId + '-option-' + activeIndex : undefined\n \"\n tabindex=\"0\"\n @click=\"onComboClick\"\n @keydown=\"onComboKeydown\"\n @focus=\"onComboFocus\"\n @blur=\"onComboBlur\"\n >\n {{\n normalizedOptions[selectedIndex]\n ? normalizedOptions[selectedIndex].label\n : \"\"\n }}\n <button\n v-if=\"showClearButton\"\n type=\"button\"\n class=\"g-select-clear-btn\"\n @click.stop=\"clearValue\"\n >\n <svg\n role=\"img\"\n aria-label=\"Clear Selection\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1.125em\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n\n <svg\n class=\"g-select-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n </div>\n <div\n v-show=\"open\"\n ref=\"listboxRef\"\n class=\"g-select-combo-menu g-select-list\"\n :class=\"{\n 'g-select-combo-menu--above': menuPlacement === 'above',\n }\"\n :style=\"menuStyle\"\n role=\"listbox\"\n :id=\"baseId + '-listbox'\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': props.label }\n : { 'aria-labelledby': baseId + '-label' }\n \"\n tabindex=\"-1\"\n >\n <template v-if=\"filteredOptions.length > 0\">\n <div\n v-for=\"(option, idx) in filteredOptions\"\n :key=\"option.value\"\n :id=\"baseId + '-option-' + idx\"\n class=\"g-select-combo-option g-select-option\"\n :class=\"{\n 'g-select-option-current': idx === activeIndex,\n 'ilw-theme-blue': option.value === model,\n }\"\n role=\"option\"\n :aria-selected=\"\n option.value === model ? 'true' : 'false'\n \"\n @mousedown=\"onOptionMouseDown\"\n @click=\"onOptionClick(idx)\"\n >\n <slot\n name=\"option\"\n :option=\"option\"\n :selected=\"option.value === model\"\n :index=\"idx\"\n >\n {{ option.label }}\n </slot>\n </div>\n </template>\n <template v-else>\n <div\n aria-live=\"polite\"\n class=\"g-select-combo-option g-select-option g-select-no-results\"\n >\n No results found.\n </div>\n </template>\n </div>\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + baseId\"\n />\n </div>\n</template>\n\n<style>\ng-select {\n display: block;\n}\n.g-select-root {\n position: relative;\n font-size: 1rem;\n}\n\n.g-select-label {\n font-weight: 700;\n color: var(--g-surface-900);\n margin-bottom: 0.5em;\n}\n\n.g-select-required {\n color: var(--g-danger-600);\n}\n\n.g-select-input-wrap {\n position: relative;\n}\n\n.g-select-control {\n line-height: 1.5;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-radius: var(--g-border-radius-m);\n text-align: left;\n position: relative;\n\n &:focus-visible {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n outline-color: var(--g-primary-500);\n\n .g-select-caret {\n color: var(--g-primary-500);\n }\n }\n\n &:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 1px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n}\n\n.g-select-combo-button {\n}\n\n.g-select-caret {\n position: absolute;\n right: 0.5em;\n line-height: 1.5em;\n top: calc(50% - 0.55em);\n color: var(--g-accent-700);\n pointer-events: none;\n\n transform: rotate(90deg);\n}\n\n.g-select-open .g-select-caret {\n transform: rotate(-90deg);\n}\n\n.g-select-combo-menu {\n background-color: var(--g-surface-0);\n border: 2px solid var(--g-surface-700);\n border-radius: 0 0 var(--g-border-radius-m) var(--g-border-radius-m);\n box-sizing: border-box;\n box-shadow:\n 0 4px 4px rgba(0, 0, 0, 0.2),\n 0 1px 0 1px rgba(0, 0, 0, 0.18);\n max-height: 50vh;\n overflow-y: auto;\n left: 0;\n position: absolute;\n top: 100%;\n width: 100%;\n z-index: 1000;\n display: none;\n}\n\n.g-select-combo-menu--above {\n border-radius: var(--g-border-radius-m) var(--g-border-radius-m) 0 0;\n}\n\n.g-select-open .g-select-combo-menu {\n display: block;\n}\n\n.g-select-combo-option {\n padding: 0.5em 0.5em;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n border-color: var(--g-accent-700);\n }\n}\n\n.g-select-option-current {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n\n &:hover {\n color: var(--g-primary-text);\n }\n}\n\n.g-select-search-input {\n box-sizing: border-box;\n display: block;\n width: 100%;\n font-family: var(--il-font-sans);\n border: none;\n box-sizing: border-box;\n text-overflow: ellipsis;\n font-size: 1em;\n\n &.g-select-clearable {\n padding-right: 3.5em; /* Space for clear button */\n }\n}\n\n.g-select-search-input,\n.g-select-combo-button {\n padding: 0.25em 2em 0.25em 0.75em;\n line-height: 1.875em;\n box-sizing: border-box;\n}\n.g-select-combo-button {\n /* Padding + line height + padding + border */\n min-height: calc(0.25em + 1.875em + 0.25em + 4px);\n min-width: 8rem;\n}\n\n.g-select-compact {\n font-size: 0.875rem;\n}\n\n.g-select-search-input,\n.g-select-combo-button {\n &.g-select-clearable {\n padding-right: 3em; /* Space for clear button */\n }\n}\n\n.g-select-no-results {\n padding: 0.25em 1em;\n text-align: center;\n color: var(--g-surface-900);\n font-style: italic;\n}\n\n.g-select-clear-btn {\n position: absolute;\n right: 1.25em;\n top: calc(50% - 1.15em);\n background: none;\n border: none;\n color: var(--g-accent-700);\n font-size: 1.25em;\n cursor: pointer;\n padding: 0.6em 0.3em 0.4em;\n line-height: 1;\n\n &:hover {\n color: var(--g-accent-700);\n }\n &:focus {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n outline-color: var(--g-primary-500);\n }\n}\n\n.g-select-has-error .g-select-control {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A combobox-style search that shows a list of results as an auto\n * complete dropdown.\n *\n * The component doesn't perform any real searching. It emits events\n * that can be used to trigger searches, and then the results are\n * passed back to the component.\n *\n * **Events**:\n *\n * - `submit` event is emitted when a search should be performed.\n * - `select` event is submitted when a user makes a selection\n * in the dropdown.\n *\n * > [!NOTE]\n * > The `v-model` value should *not* be used to trigger a search,\n * > but it can be used to get the current user input.\n *\n * **Props**:\n *\n * - `results` will be rendered in the dropdown. There are two options:\n * - Pass an array of objects that extend `{ id: string | number; title: string; }`.\n * - Pass an array of `GSearchGroup<T>` objects, where the `items` property extends the above type.\n * In this case the results are grouped.\n * - `auto` makes search submit on user input. Defaults to `true`.\n * if `false`, submission happens on Enter or clicking the search button.\n * - `loading` shows a loading indicator. Use if the search may take longer.\n *\n * **Slot**: `option` customizes how an option is rendered.\n * It receives the current item as `option`.\n *\n * **Slot**: `group` customizes the group label for each group.\n *\n * Here is a minimal implementation:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * interface SearchResult {\n * id: string;\n * title: string;\n * }\n *\n * const searchData = ref<SearchResult[]>([\n * { id: \"1\", title: \"The Quick Fox\" },\n * { id: \"2\", title: \"The Lazy Dog\" },\n * { id: \"3\", title: \"The Brown Bear\" },\n * ]);\n * const searchResults = ref<SearchResult[]>([]);\n *\n * function submit(query: string) {\n * searchResults.value = searchData.value.filter((result) =>\n * result.title.toLowerCase().includes(query.toLowerCase()),\n * );\n * }\n * function selected(item: SearchResult) {\n * console.log(\"Selected:\", item);\n * }\n * &lt;/script>\n * <template>\n * <GSearch\n * :results=\"searchResults\"\n * @submit=\"submit\"\n * @select=\"selected\">\n * </GSearch>\n * </template>\n * ```\n */\nexport default {};\n</script>\n\n<script\n setup\n lang=\"ts\"\n generic=\"\n T extends {\n id: string | number;\n title: string;\n }\n \"\n>\nimport { computed, nextTick, ref, useId, watch } from \"vue\";\nimport { useDebounceFn, useFocusWithin } from \"@vueuse/core\";\nimport GProgress from \"./GProgress.vue\";\n\nexport interface GSearchGroup<R> {\n type: string;\n label: string;\n items: R[];\n}\n\ntype Props = {\n results: GSearchGroup<T>[] | T[];\n /**\n * Placeholder\n * @demo\n */\n placeholder?: string;\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Automatic search\n * @demo\n */\n auto?: boolean;\n /**\n * Show search loading indicator\n * @demo\n */\n loading?: boolean;\n};\n\nconst modelValue = defineModel<string | null>({ default: () => \"\" });\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: \"Search...\",\n label: \"Search\",\n auto: true\n});\nconst emit = defineEmits([\"select\", \"submit\"]);\n\nconst inputRef = ref<HTMLInputElement | null>(null);\nconst listboxRef = ref<HTMLDivElement | null>(null);\nconst closed = ref(true);\nconst activeIndex = ref<number>(-1);\nconst flatResults = computed(() => {\n if (\n Array.isArray(props.results) &&\n props.results.length &&\n \"items\" in props.results[0]\n ) {\n // Grouped results\n return (props.results as GSearchGroup<T>[]).flatMap((g) => g.items);\n } else {\n return props.results as T[];\n }\n});\nconst resultCount = computed(() => flatResults.value.length);\n\nfunction onInput(ev: Event) {\n const value = (ev.target as HTMLInputElement).value;\n modelValue.value = value;\n if (props.auto && value.length > 1) {\n closed.value = false;\n }\n}\n\nfunction scrollOptionIntoView() {\n nextTick(() => {\n const el = listboxRef.value?.querySelector('[aria-selected=\"true\"]');\n if (el) {\n el.scrollIntoView({ block: \"nearest\" });\n }\n });\n}\n\nconst { focused } = useFocusWithin(inputRef);\n\nfunction onKeydown(ev: KeyboardEvent) {\n const altKey = ev.altKey;\n if (ev.key === \"ArrowDown\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n closed.value = false;\n if (!altKey) {\n activeIndex.value = (activeIndex.value + 1) % resultCount.value;\n scrollOptionIntoView();\n }\n } else if (ev.key === \"ArrowUp\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n closed.value = false;\n activeIndex.value =\n (activeIndex.value - 1 + resultCount.value) % resultCount.value;\n scrollOptionIntoView();\n } else if (ev.key === \"Enter\") {\n if (closed.value) {\n // Don't debounce on enter\n emit(\"submit\", modelValue.value);\n closed.value = false;\n ev.preventDefault();\n } else {\n selectResult(flatResults.value[activeIndex.value]);\n }\n } else if (ev.key === \"Escape\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n if (!expanded.value) {\n modelValue.value = \"\";\n }\n closed.value = true;\n activeIndex.value = -1;\n }\n\n if ([\"Backspace\", \"Delete\", \"Clear\", \"Undo\"].includes(ev.key)) {\n closed.value = true;\n }\n}\n\nfunction selectResult(result: T | null) {\n emit(\"select\", result);\n modelValue.value = \"\";\n closed.value = true;\n activeIndex.value = -1;\n}\n\nconst isLoading = computed(() => {\n return !!props.loading;\n});\n\nconst expanded = computed(() => {\n return focused.value && !closed.value;\n});\n\nconst submit = useDebounceFn(() => {\n emit(\"submit\", modelValue.value);\n}, 300);\n\nwatch(\n () => modelValue.value,\n (val) => {\n if (!val) {\n activeIndex.value = -1;\n } else if (props.auto) {\n // Auto-submit on input change with debounce\n submit();\n }\n },\n);\nconst id = useId();\n</script>\n\n<template>\n <div class=\"g-search\" role=\"search\" :aria-label=\"props.label\">\n <form class=\"g-search-form\" @submit.prevent=\"selectResult(null)\">\n <input\n ref=\"inputRef\"\n class=\"g-search-input\"\n name=\"search\"\n type=\"search\"\n :placeholder=\"props.placeholder\"\n :value=\"modelValue\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n role=\"combobox\"\n :aria-expanded=\"expanded\"\n aria-autocomplete=\"list\"\n :aria-controls=\"`${id}-list`\"\n :aria-activedescendant=\"\n activeIndex >= 0\n ? 'g-search-option-' + flatResults[activeIndex].id\n : undefined\n \"\n />\n <button\n type=\"submit\"\n class=\"g-search-submit\"\n aria-label=\"Submit search\"\n @keydown=\"onKeydown\"\n >\n <template v-if=\"isLoading\">\n <GProgress size=\"tiny\" />\n </template>\n <svg\n role=\"img\"\n aria-label=\"Search\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n >\n <path\n fill=\"currentColor\"\n d=\"M30 9.76A14.05 14.05 0 1 0 28.3 31l11.3 13a3.34 3.34 0 0 0 4.72-4.72L31.44 27.86A14.05 14.05 0 0 0 30 9.76ZM27.27 27a10.26 10.26 0 1 1 0-14.5 10.25 10.25 0 0 1 0 14.5Z\"\n />\n </svg>\n </button>\n </form>\n <div v-if=\"expanded\" class=\"g-search-dropdown\">\n <div aria-live=\"polite\" class=\"g-search-result-count\">\n <template v-if=\"!isLoading\">\n {{ resultCount }} result{{\n resultCount === 1 ? \"\" : \"s\"\n }}</template\n >\n </div>\n <div\n role=\"listbox\"\n :id=\"`${id}-list`\"\n ref=\"listboxRef\"\n aria-label=\"Search results\"\n >\n <template v-if=\"resultCount > 0 && 'items' in props.results[0]\">\n <template\n v-for=\"(\n group, gIdx\n ) in props.results as GSearchGroup<T>[]\"\n :key=\"group.type\"\n >\n <div\n class=\"g-search-group\"\n role=\"group\"\n :aria-label=\"group.label\"\n >\n <slot name=\"group\" :group=\"group\">\n <div class=\"g-search-group-label\">\n {{ group.label }}\n </div>\n </slot>\n <div\n v-for=\"(item, idx) in group.items\"\n :key=\"item.id\"\n :id=\"'g-search-option-' + item.id\"\n class=\"g-search-option\"\n :class=\"{\n 'g-search-option-active':\n flatResults[activeIndex] &&\n flatResults[activeIndex].id === item.id,\n }\"\n role=\"option\"\n @mousedown.prevent=\"selectResult(item)\"\n :aria-selected=\"\n flatResults[activeIndex] &&\n flatResults[activeIndex].id === item.id\n \"\n >\n <slot name=\"option\" :option=\"item\">\n {{ item.title }}\n </slot>\n </div>\n </div>\n </template>\n </template>\n <template v-else-if=\"resultCount > 0\">\n <div\n v-for=\"(item, idx) in flatResults\"\n :key=\"item.id\"\n :id=\"'g-search-option-' + item.id\"\n class=\"g-search-option\"\n :class=\"{\n 'g-search-option-active': activeIndex === idx,\n }\"\n role=\"option\"\n @mousedown.prevent=\"selectResult(item)\"\n :aria-selected=\"activeIndex === idx\"\n >\n <slot name=\"option\" :option=\"item\">\n {{ item.title }}\n </slot>\n </div>\n </template>\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-search {\n display: block;\n}\n\n.g-search {\n position: relative;\n min-width: 200px;\n width: 100%;\n}\n\n.g-search-form {\n display: flex;\n align-items: stretch;\n}\n\n.g-search-input {\n width: 100%;\n padding: 0.5rem 1rem;\n line-height: 1.33rem;\n font-size: 1rem;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-right-width: 1px;\n border-top-left-radius: var(--g-border-radius-m);\n border-bottom-left-radius: var(--g-border-radius-m);\n &:focus {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-search-submit {\n box-sizing: border-box;\n background: var(--g-surface-0);\n color: var(--g-accent-700);\n border: 2px solid var(--g-primary-500);\n border-left-width: 1px;\n border-top-right-radius: var(--g-border-radius-m);\n border-bottom-right-radius: var(--g-border-radius-m);\n padding: 0.2rem 0.5rem;\n font-size: 1rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n &:focus {\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--ilw-color--focus--outline);\n }\n}\n\n.g-search-dropdown {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n background: var(--g-surface-0);\n border: 2px solid var(--g-surface-200);\n z-index: 10;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n}\n\n.g-search-result-count {\n height: 28px;\n padding: 0.2rem 1rem;\n font-size: 0.95rem;\n color: var(--g-surface-900);\n background: var(--g-surface-100);\n}\n\n.g-search-group {\n}\n\n.g-search-group-label {\n font-weight: bold;\n padding: 0.5rem 1rem 0.25rem 1rem;\n color: var(--g-surface-900);\n background: var(--g-surface-100);\n}\n\n.g-search-option {\n padding: 0.3rem 0.8rem;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n }\n}\n\n.g-search-option.g-search-option-active {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n border: 2px solid var(--g-primary-500);\n}\n\n.fa-spin {\n color: var(--g-primary-300);\n}\n\n@media (max-width: 960px) {\n .g-search {\n min-width: 150px;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A combobox-style search that shows a list of results as an auto\n * complete dropdown.\n *\n * The component doesn't perform any real searching. It emits events\n * that can be used to trigger searches, and then the results are\n * passed back to the component.\n *\n * **Events**:\n *\n * - `submit` event is emitted when a search should be performed.\n * - `select` event is submitted when a user makes a selection\n * in the dropdown.\n *\n * > [!NOTE]\n * > The `v-model` value should *not* be used to trigger a search,\n * > but it can be used to get the current user input.\n *\n * **Props**:\n *\n * - `results` will be rendered in the dropdown. There are two options:\n * - Pass an array of objects that extend `{ id: string | number; title: string; }`.\n * - Pass an array of `GSearchGroup<T>` objects, where the `items` property extends the above type.\n * In this case the results are grouped.\n * - `auto` makes search submit on user input. Defaults to `true`.\n * if `false`, submission happens on Enter or clicking the search button.\n * - `loading` shows a loading indicator. Use if the search may take longer.\n *\n * **Slot**: `option` customizes how an option is rendered.\n * It receives the current item as `option`.\n *\n * **Slot**: `group` customizes the group label for each group.\n *\n * Here is a minimal implementation:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * interface SearchResult {\n * id: string;\n * title: string;\n * }\n *\n * const searchData = ref<SearchResult[]>([\n * { id: \"1\", title: \"The Quick Fox\" },\n * { id: \"2\", title: \"The Lazy Dog\" },\n * { id: \"3\", title: \"The Brown Bear\" },\n * ]);\n * const searchResults = ref<SearchResult[]>([]);\n *\n * function submit(query: string) {\n * searchResults.value = searchData.value.filter((result) =>\n * result.title.toLowerCase().includes(query.toLowerCase()),\n * );\n * }\n * function selected(item: SearchResult) {\n * console.log(\"Selected:\", item);\n * }\n * &lt;/script>\n * <template>\n * <GSearch\n * :results=\"searchResults\"\n * @submit=\"submit\"\n * @select=\"selected\">\n * </GSearch>\n * </template>\n * ```\n */\nexport default {};\n</script>\n\n<script\n setup\n lang=\"ts\"\n generic=\"\n T extends {\n id: string | number;\n title: string;\n }\n \"\n>\nimport { computed, nextTick, ref, useId, watch } from \"vue\";\nimport { useDebounceFn, useFocusWithin } from \"@vueuse/core\";\nimport GProgress from \"./GProgress.vue\";\n\nexport interface GSearchGroup<R> {\n type: string;\n label: string;\n items: R[];\n}\n\ntype Props = {\n results: GSearchGroup<T>[] | T[];\n /**\n * Placeholder\n * @demo\n */\n placeholder?: string;\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Automatic search\n * @demo\n */\n auto?: boolean;\n /**\n * Show search loading indicator\n * @demo\n */\n loading?: boolean;\n};\n\nconst modelValue = defineModel<string | null>({ default: () => \"\" });\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: \"Search...\",\n label: \"Search\",\n auto: true\n});\nconst emit = defineEmits([\"select\", \"submit\"]);\n\nconst inputRef = ref<HTMLInputElement | null>(null);\nconst listboxRef = ref<HTMLDivElement | null>(null);\nconst closed = ref(true);\nconst activeIndex = ref<number>(-1);\nconst flatResults = computed(() => {\n if (\n Array.isArray(props.results) &&\n props.results.length &&\n \"items\" in props.results[0]\n ) {\n // Grouped results\n return (props.results as GSearchGroup<T>[]).flatMap((g) => g.items);\n } else {\n return props.results as T[];\n }\n});\nconst resultCount = computed(() => flatResults.value.length);\n\nfunction onInput(ev: Event) {\n const value = (ev.target as HTMLInputElement).value;\n modelValue.value = value;\n if (props.auto && value.length > 1) {\n closed.value = false;\n }\n}\n\nfunction scrollOptionIntoView() {\n nextTick(() => {\n const el = listboxRef.value?.querySelector('[aria-selected=\"true\"]');\n if (el) {\n el.scrollIntoView({ block: \"nearest\" });\n }\n });\n}\n\nconst { focused } = useFocusWithin(inputRef);\n\nfunction onKeydown(ev: KeyboardEvent) {\n const altKey = ev.altKey;\n if (ev.key === \"ArrowDown\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n closed.value = false;\n if (!altKey) {\n activeIndex.value = (activeIndex.value + 1) % resultCount.value;\n scrollOptionIntoView();\n }\n } else if (ev.key === \"ArrowUp\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n closed.value = false;\n activeIndex.value =\n (activeIndex.value - 1 + resultCount.value) % resultCount.value;\n scrollOptionIntoView();\n } else if (ev.key === \"Enter\") {\n if (closed.value) {\n // Don't debounce on enter\n emit(\"submit\", modelValue.value);\n closed.value = false;\n ev.preventDefault();\n } else {\n selectResult(flatResults.value[activeIndex.value]);\n }\n } else if (ev.key === \"Escape\") {\n if (!resultCount.value) {\n return;\n }\n ev.preventDefault();\n if (!expanded.value) {\n modelValue.value = \"\";\n }\n closed.value = true;\n activeIndex.value = -1;\n }\n\n if ([\"Backspace\", \"Delete\", \"Clear\", \"Undo\"].includes(ev.key)) {\n closed.value = true;\n }\n}\n\nfunction selectResult(result: T | null) {\n emit(\"select\", result);\n modelValue.value = \"\";\n closed.value = true;\n activeIndex.value = -1;\n}\n\nconst isLoading = computed(() => {\n return !!props.loading;\n});\n\nconst expanded = computed(() => {\n return focused.value && !closed.value;\n});\n\nconst submit = useDebounceFn(() => {\n emit(\"submit\", modelValue.value);\n}, 300);\n\nwatch(\n () => modelValue.value,\n (val) => {\n if (!val) {\n activeIndex.value = -1;\n } else if (props.auto) {\n // Auto-submit on input change with debounce\n submit();\n }\n },\n);\nconst id = useId();\n</script>\n\n<template>\n <div class=\"g-search\" role=\"search\" :aria-label=\"props.label\">\n <form class=\"g-search-form\" @submit.prevent=\"selectResult(null)\">\n <input\n ref=\"inputRef\"\n class=\"g-search-input\"\n name=\"search\"\n type=\"search\"\n :placeholder=\"props.placeholder\"\n :value=\"modelValue\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n role=\"combobox\"\n :aria-expanded=\"expanded\"\n aria-autocomplete=\"list\"\n :aria-controls=\"`${id}-list`\"\n :aria-activedescendant=\"\n activeIndex >= 0\n ? 'g-search-option-' + flatResults[activeIndex].id\n : undefined\n \"\n />\n <button\n type=\"submit\"\n class=\"g-search-submit\"\n aria-label=\"Submit search\"\n @keydown=\"onKeydown\"\n >\n <template v-if=\"isLoading\">\n <GProgress size=\"tiny\" />\n </template>\n <svg\n role=\"img\"\n aria-label=\"Search\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n >\n <path\n fill=\"currentColor\"\n d=\"M30 9.76A14.05 14.05 0 1 0 28.3 31l11.3 13a3.34 3.34 0 0 0 4.72-4.72L31.44 27.86A14.05 14.05 0 0 0 30 9.76ZM27.27 27a10.26 10.26 0 1 1 0-14.5 10.25 10.25 0 0 1 0 14.5Z\"\n />\n </svg>\n </button>\n </form>\n <div v-if=\"expanded\" class=\"g-search-dropdown\">\n <div aria-live=\"polite\" class=\"g-search-result-count\">\n <template v-if=\"!isLoading\">\n {{ resultCount }} result{{\n resultCount === 1 ? \"\" : \"s\"\n }}</template\n >\n </div>\n <div\n role=\"listbox\"\n :id=\"`${id}-list`\"\n ref=\"listboxRef\"\n aria-label=\"Search results\"\n >\n <template v-if=\"resultCount > 0 && 'items' in props.results[0]\">\n <template\n v-for=\"(\n group, gIdx\n ) in props.results as GSearchGroup<T>[]\"\n :key=\"group.type\"\n >\n <div\n class=\"g-search-group\"\n role=\"group\"\n :aria-label=\"group.label\"\n >\n <slot name=\"group\" :group=\"group\">\n <div class=\"g-search-group-label\">\n {{ group.label }}\n </div>\n </slot>\n <div\n v-for=\"(item, idx) in group.items\"\n :key=\"item.id\"\n :id=\"'g-search-option-' + item.id\"\n class=\"g-search-option\"\n :class=\"{\n 'g-search-option-active':\n flatResults[activeIndex] &&\n flatResults[activeIndex].id === item.id,\n }\"\n role=\"option\"\n @mousedown.prevent=\"selectResult(item)\"\n :aria-selected=\"\n flatResults[activeIndex] &&\n flatResults[activeIndex].id === item.id\n \"\n >\n <slot name=\"option\" :option=\"item\">\n {{ item.title }}\n </slot>\n </div>\n </div>\n </template>\n </template>\n <template v-else-if=\"resultCount > 0\">\n <div\n v-for=\"(item, idx) in flatResults\"\n :key=\"item.id\"\n :id=\"'g-search-option-' + item.id\"\n class=\"g-search-option\"\n :class=\"{\n 'g-search-option-active': activeIndex === idx,\n }\"\n role=\"option\"\n @mousedown.prevent=\"selectResult(item)\"\n :aria-selected=\"activeIndex === idx\"\n >\n <slot name=\"option\" :option=\"item\">\n {{ item.title }}\n </slot>\n </div>\n </template>\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-search {\n display: block;\n}\n\n.g-search {\n position: relative;\n min-width: 200px;\n width: 100%;\n}\n\n.g-search-form {\n display: flex;\n align-items: stretch;\n}\n\n.g-search-input {\n width: 100%;\n padding: 0.5rem 1rem;\n line-height: 1.33rem;\n font-size: 1rem;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-right-width: 1px;\n border-top-left-radius: var(--g-border-radius-m);\n border-bottom-left-radius: var(--g-border-radius-m);\n &:focus {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-search-submit {\n box-sizing: border-box;\n background: var(--g-surface-0);\n color: var(--g-accent-700);\n border: 2px solid var(--g-primary-500);\n border-left-width: 1px;\n border-top-right-radius: var(--g-border-radius-m);\n border-bottom-right-radius: var(--g-border-radius-m);\n padding: 0.2rem 0.5rem;\n font-size: 1rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n &:focus {\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--ilw-color--focus--outline);\n }\n}\n\n.g-search-dropdown {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n background: var(--g-surface-0);\n border: 2px solid var(--g-surface-200);\n z-index: 10;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n}\n\n.g-search-result-count {\n height: 28px;\n padding: 0.2rem 1rem;\n font-size: 0.95rem;\n color: var(--g-surface-900);\n background: var(--g-surface-100);\n}\n\n.g-search-group {\n}\n\n.g-search-group-label {\n font-weight: bold;\n padding: 0.5rem 1rem 0.25rem 1rem;\n color: var(--g-surface-900);\n background: var(--g-surface-100);\n}\n\n.g-search-option {\n padding: 0.3rem 0.8rem;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n }\n}\n\n.g-search-option.g-search-option-active {\n background: var(--g-info-200);\n color: var(--g-primary-500);\n border: 2px solid var(--g-primary-500);\n}\n\n.fa-spin {\n color: var(--g-primary-300);\n}\n\n@media (max-width: 960px) {\n .g-search {\n min-width: 150px;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * This is a minimal header meant for web apps where a full Illinois\n * brand header would be too large.\n *\n * **Slot** `left` allows replacing the link element in the top-left corner.\n *\n * **Slot** `title` is to the right of the logo.\n *\n * **Slot** `app-controls` is the remaining area to the right.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Whether to show the Illinois logo\n */\n illinois?: boolean;\n /**\n * Top-left corner text\n *\n * You can customize this text element with the \"left\" slot.\n * @demo\n */\n brand?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n illinois: false,\n brand: \"GRAD\",\n});\n</script>\n\n<template>\n <header\n :class=\"{\n 'g-app-header': true,\n }\"\n >\n <div class=\"g-app-header__background\">\n <div class=\"g-app-header__background-pattern\"></div>\n <div class=\"g-app-header__background-gradient\"></div>\n </div>\n <div class=\"g-app-header__brand\">\n <slot name=\"left\">\n <a class=\"g-app-header__brand-text\" href=\"/\">{{ brand }}</a>\n </slot>\n </div>\n <div v-if=\"illinois\" class=\"g-app-header__block-i-container\">\n <svg\n class=\"g-app-header__block-i\"\n role=\"img\"\n width=\"55\"\n viewBox=\"0 0 55 79\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <!-- NO LICENSE GRANTED for this logo, must have the right to use it -->\n <title>Block I logo</title>\n <path\n class=\"g-app-header__block-i-outline\"\n d=\"M54.2 21.1V0H0v21.1h12v36.1H0v21.1h54.2V57.2h-12V21.1z\"\n ></path>\n <path\n class=\"g-app-header__block-i-fill\"\n d=\"M42.1 18.1h9V3H3v15h9c1.7 0 3 1.3 3 3v36.1c0 1.7-1.3 3-3 3H3v15h48.1v-15h-9c-1.7 0-3-1.3-3-3v-36c0-1.7 1.4-3 3-3z\"\n ></path>\n </svg>\n </div>\n <slot v-else name=\"icon\"></slot>\n <div class=\"g-app-header__title\">\n <slot name=\"title\"></slot>\n </div>\n <div class=\"g-app-header__app-controls-wrap\">\n <slot name=\"app-controls\" class=\"g-app-header__app-controls\"></slot>\n </div>\n </header>\n</template>\n\n<style>\n\n@layer base {\n :root {\n --g-toolbar-height: 48px;\n }\n}\n\nhtml {\n scroll-padding-top: 70px;\n}\n\ng-app-header:not(:defined),\n.g-app-header {\n box-sizing: border-box;\n background-color: var(--g-surface-100);\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n z-index: 2;\n height: var(--g-toolbar-height);\n border-bottom: 2px solid var(--g-accent-500);\n box-shadow:\n 0px 1px 2px 0px rgba(0, 0, 0, 0.25),\n 0px 1px 10px 5px rgba(0, 0, 0, 0.08);\n}\n\ng-app-header:not(:defined) > [slot=\"title\"] {\n margin: 0;\n margin-left: calc(78px + 20px);\n}\n\ng-app-header:not(:defined)[illinois] > [slot=\"title\"] {\n margin-left: calc(78px + 48px + 20px);\n}\n\n.g-app-header {\n .g-app-header__title {\n display: flex;\n align-items: center;\n margin-left: 20px;\n flex: 1;\n }\n\n .g-app-header__title > * {\n font-size: 20px;\n font-family: var(--il-font-sans);\n font-style: normal;\n line-height: 30px;\n color: var(--g-primary-500);\n margin: 0;\n font-weight: 700;\n text-decoration: none;\n }\n\n .g-app-header__title {\n\n a {\n color: var(--g-primary-500);\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n }\n }\n }\n\n .g-app-header__app-controls-wrap {\n margin-right: 20px;\n }\n}\n\n.g-app-header {\n /*noinspection CssUnresolvedCustomProperty*/\n padding-right: var(--g-scrollbar-width, 0px);\n}\n\n.g-app-header__background {\n position: absolute;\n z-index: -1;\n top: 0;\n left: 0;\n bottom: 0;\n width: 470px;\n\n @media screen and (max-width: 640px) {\n width: 80%;\n }\n}\n\n.g-app-header__background-pattern {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBoZWlnaHQ9IjQ2IiB2aWV3Qm94PSIwIDAgNzEyLjkyNSA2NjkuMTY1Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTE4MTEuMzA3IiB5Mj0iLTIyODQuNDA1IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTM2MjkuODQ4IC0yNzk4LjAxNSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNjOGM2YzciLz48c3RvcCBvZmZzZXQ9Ii45ODEiIHN0b3AtY29sb3I9IiNmZmYiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImIiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTE1NTguNjU1IiB5Mj0iLTIwMzEuNzUzIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTM4MDguNSAtMjM2Ni43MTEpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iYyIgeDE9Ii03MTk2Ljk3NyIgeDI9Ii03MjA0LjIiIHkxPSItMTMwNi4wMDMiIHkyPSItMTc3OS4xMDEiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMi4wNDY1IDAgMCAtMSAxMzk4Ny4xNTIgLTE5MzUuNDA3KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImQiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTEwNTMuMzUxIiB5Mj0iLTE1MjYuNDQ5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQxNjUuODAzIC0xNTA0LjEwMykiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJlIiB4MT0iLTcxOTYuOTc3IiB4Mj0iLTcyMDQuMiIgeTE9Ii04MDAuNjk5IiB5Mj0iLTEyNzMuNzk3IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQzNDQuNDU1IC0xMDcyLjc5OSkiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJmIiB4MT0iLTcxOTYuOTc3IiB4Mj0iLTcyMDQuMiIgeTE9Ii01NDguMDQ3IiB5Mj0iLTEwMjEuMTQ0IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQ1MjMuMTA3IC02NDEuNDk1KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImciIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9Ii0yOTUuMzcxIiB5Mj0iLTc2OC40NjkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMi4wNDY1IDAgMCAtMSAxNDcxMC41NTUgLTIxMy43ODcpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iaCIgeDE9Ii03MTk5LjQ5IiB4Mj0iLTcyMDYuNzEzIiB5MT0iLTQyLjcxOSIgeTI9Ii01MTUuODE3IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQ4ODkuMjA3IDIxNy41MTcpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iaSIgeDE9Ii03MTk5LjQ5IiB4Mj0iLTcyMDYuNzEzIiB5MT0iMjA5LjkzMyIgeTI9Ii0yNjMuMTY1IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTUwNjcuODU5IDY0OC44MikiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJqIiB4MT0iLTcxOTkuNDkiIHgyPSItNzIwNi43MTMiIHkxPSI0NjIuNTg1IiB5Mj0iLTEwLjUxMyIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyLjA0NjUgMCAwIC0xIDE1MjQ2LjUxIDEwODAuMTI1KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImsiIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9IjcxNS4yMzciIHkyPSIyNDIuMTM5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTU0MjUuMTYyIDE1MTEuNDI5KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImwiIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9Ijk2Ny44ODkiIHkyPSI0OTQuNzkxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTU2MDMuODE0IDE5NDIuNzMzKSIvPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjY2xpcHBhdGgpIj48cGF0aCBmaWxsPSJ1cmwoI2EpIiBkPSJNLTI1NDYuNjc0LTk3OC41MzNIMzQwLjY4djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0xMTAyLjk5NyAtODUyLjIwOCkiLz48cGF0aCBmaWxsPSJ1cmwoI2IpIiBkPSJNLTIzNjguMDIyLTc5OS44ODFINTE5LjMzMnYyNTIuNjUyaC0yODg3LjM1NHoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSAtOTI0LjM0NSAtNjczLjU1NikiLz48cGF0aCBmaWxsPSJ1cmwoI2MpIiBkPSJNLTIxODkuMzctNjIxLjIyOUg2OTcuOTg0djI1Mi42NTJILTIxODkuMzd6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTc0NS42OTMgLTQ5NC45MDMpIi8+PHBhdGggZmlsbD0idXJsKCNkKSIgZD0iTS0yMDEwLjcxOC00NDIuNTc3SDg3Ni42MzZ2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTU2Ny4wNDEgLTMxNi4yNTEpIi8+PHBhdGggZmlsbD0idXJsKCNlKSIgZD0iTS0xODMyLjA2Ni0yNjMuOTI1aDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0zODguMzkgLTEzNy42KSIvPjxwYXRoIGZpbGw9InVybCgjZikiIGQ9Ik0tMTY1My40MTQtODUuMjczSDEyMzMuOTR2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTIwOS43MzcgNDEuMDUzKSIvPjxwYXRoIGZpbGw9InVybCgjZykiIGQ9Ik0tMTQ3MS4xMDkgODkuNzU5aDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0yNy40MzIgMjE2LjA4NSkiLz48cGF0aCBmaWxsPSJ1cmwoI2gpIiBkPSJNLTEyOTIuNDU3IDI2OC40MTFoMjg4Ny4zNTR2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgMTUxLjIyIDM5NC43MzcpIi8+PHBhdGggZmlsbD0idXJsKCNpKSIgZD0iTS0xMTEzLjgwNiA0NDcuMDYzaDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IDMyOS44NzEgNTczLjM4OSkiLz48cGF0aCBmaWxsPSJ1cmwoI2opIiBkPSJNLTkzNS4xNTQgNjI1LjcxNUgxOTUyLjJ2MjUyLjY1MkgtOTM1LjE1NHoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSA1MDguNTIzIDc1Mi4wNCkiLz48cGF0aCBmaWxsPSJ1cmwoI2spIiBkPSJNLTc1Ni41MDIgODA0LjM2N2gyODg3LjM1NHYyNTIuNjUySC03NTYuNTAyeiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IDY4Ny4xNzUgOTMwLjY5MykiLz48cGF0aCBmaWxsPSJ1cmwoI2wpIiBkPSJNLTU3Ny44NSA5ODMuMDE5aDI4ODcuMzU0djI1Mi42NTJILTU3Ny44NXoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSA4NjUuODI3IDExMDkuMzQ1KSIvPjwvZz48L3N2Zz4=');\n background-repeat: repeat;\n opacity: 0.5;\n}\n\n.g-app-header__background-gradient {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n background: linear-gradient(\n 90deg,\n transparent -15.89%,\n var(--g-surface-100) 95.85%\n );\n}\n\n.g-app-header__brand {\n min-width: 1rem;\n margin-top: 2px;\n\n & a {\n text-decoration: none;\n padding: 10px 15px;\n font-family: var(--il-font-montserrat);\n letter-spacing: 0.98px;\n font-size: 14px;\n font-style: normal;\n font-weight: 800;\n color: var(--g-primary-300);\n\n &:hover {\n text-decoration: underline;\n color: var(--g-primary-500);\n }\n\n @media screen and (max-width: 740px) {\n display: none;\n }\n }\n}\n\n.g-app-header__block-i-container {\n height: calc(var(--g-toolbar-height) + 3px);\n box-sizing: border-box;\n margin-top: 6px;\n box-shadow:\n 0px 0px 1px 1px rgba(0, 0, 0, 0.08),\n 2px 1px 10px 0px rgba(0, 0, 0, 0.35);\n}\n\n.g-app-header__app-controls {\n flex: 1;\n display: flex;\n justify-content: flex-end;\n padding: 0 10px;\n gap: 10px;\n}\n.g-app-header__block-i-container {\n background-color: var(--il-blue);\n min-width: 40px;\n padding: 8px 10px;\n\n .g-app-header__block-i {\n display: block;\n width: 24px;\n }\n\n .g-app-header__block-i-outline {\n fill: #fff;\n }\n\n .g-app-header__block-i-fill {\n fill: var(--il-orange);\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This is a minimal header meant for web apps where a full Illinois\n * brand header would be too large.\n *\n * **Slot** `left` allows replacing the link element in the top-left corner.\n *\n * **Slot** `title` is to the right of the logo.\n *\n * **Slot** `app-controls` is the remaining area to the right.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Whether to show the Illinois logo\n */\n illinois?: boolean;\n /**\n * Top-left corner text\n *\n * You can customize this text element with the \"left\" slot.\n * @demo\n */\n brand?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n illinois: false,\n brand: \"GRAD\",\n});\n</script>\n\n<template>\n <header\n :class=\"{\n 'g-app-header': true,\n }\"\n >\n <div class=\"g-app-header__background\">\n <div class=\"g-app-header__background-pattern\"></div>\n <div class=\"g-app-header__background-gradient\"></div>\n </div>\n <div class=\"g-app-header__brand\">\n <slot name=\"left\">\n <a class=\"g-app-header__brand-text\" href=\"/\">{{ brand }}</a>\n </slot>\n </div>\n <div v-if=\"illinois\" class=\"g-app-header__block-i-container\">\n <svg\n class=\"g-app-header__block-i\"\n role=\"img\"\n width=\"55\"\n viewBox=\"0 0 55 79\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <!-- NO LICENSE GRANTED for this logo, must have the right to use it -->\n <title>Block I logo</title>\n <path\n class=\"g-app-header__block-i-outline\"\n d=\"M54.2 21.1V0H0v21.1h12v36.1H0v21.1h54.2V57.2h-12V21.1z\"\n ></path>\n <path\n class=\"g-app-header__block-i-fill\"\n d=\"M42.1 18.1h9V3H3v15h9c1.7 0 3 1.3 3 3v36.1c0 1.7-1.3 3-3 3H3v15h48.1v-15h-9c-1.7 0-3-1.3-3-3v-36c0-1.7 1.4-3 3-3z\"\n ></path>\n </svg>\n </div>\n <slot v-else name=\"icon\"></slot>\n <div class=\"g-app-header__title\">\n <slot name=\"title\"></slot>\n </div>\n <div class=\"g-app-header__app-controls-wrap\">\n <slot name=\"app-controls\" class=\"g-app-header__app-controls\"></slot>\n </div>\n </header>\n</template>\n\n<style>\n\n@layer base {\n :root {\n --g-toolbar-height: 48px;\n }\n}\n\nhtml {\n scroll-padding-top: 70px;\n}\n\ng-app-header:not(:defined),\n.g-app-header {\n box-sizing: border-box;\n background-color: var(--g-surface-100);\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n z-index: 2;\n height: var(--g-toolbar-height);\n border-bottom: 2px solid var(--g-accent-500);\n box-shadow:\n 0px 1px 2px 0px rgba(0, 0, 0, 0.25),\n 0px 1px 10px 5px rgba(0, 0, 0, 0.08);\n}\n\ng-app-header:not(:defined) > [slot=\"title\"] {\n margin: 0;\n margin-left: calc(78px + 20px);\n}\n\ng-app-header:not(:defined)[illinois] > [slot=\"title\"] {\n margin-left: calc(78px + 48px + 20px);\n}\n\n.g-app-header {\n .g-app-header__title {\n display: flex;\n align-items: center;\n margin-left: 20px;\n flex: 1;\n }\n\n .g-app-header__title > * {\n font-size: 20px;\n font-family: var(--il-font-sans);\n font-style: normal;\n line-height: 30px;\n color: var(--g-primary-500);\n margin: 0;\n font-weight: 700;\n text-decoration: none;\n }\n\n .g-app-header__title {\n\n a {\n color: var(--g-primary-500);\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n }\n }\n }\n\n .g-app-header__app-controls-wrap {\n margin-right: 20px;\n }\n}\n\n.g-app-header {\n /*noinspection CssUnresolvedCustomProperty*/\n padding-right: var(--g-scrollbar-width, 0px);\n}\n\n.g-app-header__background {\n position: absolute;\n z-index: -1;\n top: 0;\n left: 0;\n bottom: 0;\n width: 470px;\n\n @media screen and (max-width: 640px) {\n width: 80%;\n }\n}\n\n.g-app-header__background-pattern {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBoZWlnaHQ9IjQ2IiB2aWV3Qm94PSIwIDAgNzEyLjkyNSA2NjkuMTY1Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTE4MTEuMzA3IiB5Mj0iLTIyODQuNDA1IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTM2MjkuODQ4IC0yNzk4LjAxNSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNjOGM2YzciLz48c3RvcCBvZmZzZXQ9Ii45ODEiIHN0b3AtY29sb3I9IiNmZmYiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImIiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTE1NTguNjU1IiB5Mj0iLTIwMzEuNzUzIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTM4MDguNSAtMjM2Ni43MTEpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iYyIgeDE9Ii03MTk2Ljk3NyIgeDI9Ii03MjA0LjIiIHkxPSItMTMwNi4wMDMiIHkyPSItMTc3OS4xMDEiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMi4wNDY1IDAgMCAtMSAxMzk4Ny4xNTIgLTE5MzUuNDA3KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImQiIHgxPSItNzE5Ni45NzciIHgyPSItNzIwNC4yIiB5MT0iLTEwNTMuMzUxIiB5Mj0iLTE1MjYuNDQ5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQxNjUuODAzIC0xNTA0LjEwMykiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJlIiB4MT0iLTcxOTYuOTc3IiB4Mj0iLTcyMDQuMiIgeTE9Ii04MDAuNjk5IiB5Mj0iLTEyNzMuNzk3IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQzNDQuNDU1IC0xMDcyLjc5OSkiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJmIiB4MT0iLTcxOTYuOTc3IiB4Mj0iLTcyMDQuMiIgeTE9Ii01NDguMDQ3IiB5Mj0iLTEwMjEuMTQ0IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQ1MjMuMTA3IC02NDEuNDk1KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImciIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9Ii0yOTUuMzcxIiB5Mj0iLTc2OC40NjkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMi4wNDY1IDAgMCAtMSAxNDcxMC41NTUgLTIxMy43ODcpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iaCIgeDE9Ii03MTk5LjQ5IiB4Mj0iLTcyMDYuNzEzIiB5MT0iLTQyLjcxOSIgeTI9Ii01MTUuODE3IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTQ4ODkuMjA3IDIxNy41MTcpIi8+PGxpbmVhckdyYWRpZW50IHhsaW5rOmhyZWY9IiNhIiBpZD0iaSIgeDE9Ii03MTk5LjQ5IiB4Mj0iLTcyMDYuNzEzIiB5MT0iMjA5LjkzMyIgeTI9Ii0yNjMuMTY1IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTUwNjcuODU5IDY0OC44MikiLz48bGluZWFyR3JhZGllbnQgeGxpbms6aHJlZj0iI2EiIGlkPSJqIiB4MT0iLTcxOTkuNDkiIHgyPSItNzIwNi43MTMiIHkxPSI0NjIuNTg1IiB5Mj0iLTEwLjUxMyIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyLjA0NjUgMCAwIC0xIDE1MjQ2LjUxIDEwODAuMTI1KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImsiIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9IjcxNS4yMzciIHkyPSIyNDIuMTM5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTU0MjUuMTYyIDE1MTEuNDI5KSIvPjxsaW5lYXJHcmFkaWVudCB4bGluazpocmVmPSIjYSIgaWQ9ImwiIHgxPSItNzE5OS40OSIgeDI9Ii03MjA2LjcxMyIgeTE9Ijk2Ny44ODkiIHkyPSI0OTQuNzkxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDIuMDQ2NSAwIDAgLTEgMTU2MDMuODE0IDE5NDIuNzMzKSIvPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjY2xpcHBhdGgpIj48cGF0aCBmaWxsPSJ1cmwoI2EpIiBkPSJNLTI1NDYuNjc0LTk3OC41MzNIMzQwLjY4djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0xMTAyLjk5NyAtODUyLjIwOCkiLz48cGF0aCBmaWxsPSJ1cmwoI2IpIiBkPSJNLTIzNjguMDIyLTc5OS44ODFINTE5LjMzMnYyNTIuNjUyaC0yODg3LjM1NHoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSAtOTI0LjM0NSAtNjczLjU1NikiLz48cGF0aCBmaWxsPSJ1cmwoI2MpIiBkPSJNLTIxODkuMzctNjIxLjIyOUg2OTcuOTg0djI1Mi42NTJILTIxODkuMzd6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTc0NS42OTMgLTQ5NC45MDMpIi8+PHBhdGggZmlsbD0idXJsKCNkKSIgZD0iTS0yMDEwLjcxOC00NDIuNTc3SDg3Ni42MzZ2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTU2Ny4wNDEgLTMxNi4yNTEpIi8+PHBhdGggZmlsbD0idXJsKCNlKSIgZD0iTS0xODMyLjA2Ni0yNjMuOTI1aDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0zODguMzkgLTEzNy42KSIvPjxwYXRoIGZpbGw9InVybCgjZikiIGQ9Ik0tMTY1My40MTQtODUuMjczSDEyMzMuOTR2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgLTIwOS43MzcgNDEuMDUzKSIvPjxwYXRoIGZpbGw9InVybCgjZykiIGQ9Ik0tMTQ3MS4xMDkgODkuNzU5aDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IC0yNy40MzIgMjE2LjA4NSkiLz48cGF0aCBmaWxsPSJ1cmwoI2gpIiBkPSJNLTEyOTIuNDU3IDI2OC40MTFoMjg4Ny4zNTR2MjUyLjY1MmgtMjg4Ny4zNTR6IiB0cmFuc2Zvcm09InJvdGF0ZSgxMzUgMTUxLjIyIDM5NC43MzcpIi8+PHBhdGggZmlsbD0idXJsKCNpKSIgZD0iTS0xMTEzLjgwNiA0NDcuMDYzaDI4ODcuMzU0djI1Mi42NTJoLTI4ODcuMzU0eiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IDMyOS44NzEgNTczLjM4OSkiLz48cGF0aCBmaWxsPSJ1cmwoI2opIiBkPSJNLTkzNS4xNTQgNjI1LjcxNUgxOTUyLjJ2MjUyLjY1MkgtOTM1LjE1NHoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSA1MDguNTIzIDc1Mi4wNCkiLz48cGF0aCBmaWxsPSJ1cmwoI2spIiBkPSJNLTc1Ni41MDIgODA0LjM2N2gyODg3LjM1NHYyNTIuNjUySC03NTYuNTAyeiIgdHJhbnNmb3JtPSJyb3RhdGUoMTM1IDY4Ny4xNzUgOTMwLjY5MykiLz48cGF0aCBmaWxsPSJ1cmwoI2wpIiBkPSJNLTU3Ny44NSA5ODMuMDE5aDI4ODcuMzU0djI1Mi42NTJILTU3Ny44NXoiIHRyYW5zZm9ybT0icm90YXRlKDEzNSA4NjUuODI3IDExMDkuMzQ1KSIvPjwvZz48L3N2Zz4=');\n background-repeat: repeat;\n opacity: 0.5;\n}\n\n.g-app-header__background-gradient {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n background: linear-gradient(\n 90deg,\n transparent -15.89%,\n var(--g-surface-100) 95.85%\n );\n}\n\n.g-app-header__brand {\n min-width: 1rem;\n margin-top: 2px;\n\n & a {\n text-decoration: none;\n padding: 10px 15px;\n font-family: var(--il-font-montserrat);\n letter-spacing: 0.98px;\n font-size: 14px;\n font-style: normal;\n font-weight: 800;\n color: var(--g-primary-300);\n\n &:hover {\n text-decoration: underline;\n color: var(--g-primary-500);\n }\n\n @media screen and (max-width: 740px) {\n display: none;\n }\n }\n}\n\n.g-app-header__block-i-container {\n height: calc(var(--g-toolbar-height) + 3px);\n box-sizing: border-box;\n margin-top: 6px;\n box-shadow:\n 0px 0px 1px 1px rgba(0, 0, 0, 0.08),\n 2px 1px 10px 0px rgba(0, 0, 0, 0.35);\n}\n\n.g-app-header__app-controls {\n flex: 1;\n display: flex;\n justify-content: flex-end;\n padding: 0 10px;\n gap: 10px;\n}\n.g-app-header__block-i-container {\n background-color: var(--il-blue);\n min-width: 40px;\n padding: 8px 10px;\n\n .g-app-header__block-i {\n display: block;\n width: 24px;\n }\n\n .g-app-header__block-i-outline {\n fill: #fff;\n }\n\n .g-app-header__block-i-fill {\n fill: var(--il-orange);\n }\n}\n</style>\n\n","import { onBeforeUnmount, onMounted, Ref, ref, watch } from \"vue\";\nimport { useMediaQuery } from \"@vueuse/core\";\n\ntype SidebarChannel = {\n id: string;\n open: Ref<boolean>;\n isCollapsible: Ref<boolean>;\n toggle: () => void;\n};\n\nfunction normalizeKey(key: string) {\n return key.replace(/[^a-zA-Z0-9_-]/g, \"-\");\n}\n\nfunction getChannelsStore() {\n const globalScope = globalThis as typeof globalThis & {\n __GRAD_VUE_WC_SIDEBAR_CHANNELS__?: Map<string, SidebarChannel>;\n };\n\n if (!globalScope.__GRAD_VUE_WC_SIDEBAR_CHANNELS__) {\n globalScope.__GRAD_VUE_WC_SIDEBAR_CHANNELS__ = new Map();\n }\n\n return globalScope.__GRAD_VUE_WC_SIDEBAR_CHANNELS__;\n}\n\nexport function useWebComponentSidebar(\n key = \"default\",\n breakpoint: Ref<string> | string = \"(max-width: 800px)\",\n) {\n const channels = getChannelsStore();\n const channelKey = key || \"default\";\n\n if (!channels.has(channelKey)) {\n const safeKey = normalizeKey(channelKey);\n channels.set(channelKey, {\n id: `g-wc-sidebar-${safeKey}`,\n open: ref(false),\n isCollapsible: useMediaQuery(breakpoint, {\n ssrWidth: 1000,\n }),\n toggle: () => undefined,\n });\n }\n\n const channel = channels.get(channelKey)!;\n channel.toggle = () => (channel.open.value = !channel.open.value);\n\n function onDocumentClick(e: MouseEvent) {\n if (!channel.isCollapsible.value || !channel.open.value) {\n return;\n }\n const target = e.target as HTMLElement;\n const sidebarEl = document.getElementById(`${channel.id}-sidebar`);\n if (!sidebarEl) {\n return;\n }\n if (sidebarEl.contains(target)) {\n return;\n }\n setTimeout(() => {\n channel.open.value = false;\n }, 5);\n }\n\n function onDocumentFocus(e: FocusEvent) {\n if (!channel.isCollapsible.value || !channel.open.value) {\n return;\n }\n const target = e.target as HTMLElement;\n const sidebarEl = document.getElementById(`${channel.id}-sidebar`);\n const hamburgerEl = document.getElementById(`${channel.id}-hamburger`);\n if (!sidebarEl) {\n return;\n }\n if (sidebarEl.contains(target) || hamburgerEl?.contains(target)) {\n return;\n }\n setTimeout(() => {\n channel.open.value = false;\n }, 5);\n }\n\n onMounted(() => {\n watch(\n channel.isCollapsible,\n (val) => {\n if (val) {\n document.addEventListener(\"mousedown\", onDocumentClick);\n document.addEventListener(\"focusin\", onDocumentFocus);\n } else {\n document.removeEventListener(\"mousedown\", onDocumentClick);\n document.removeEventListener(\"focusin\", onDocumentFocus);\n }\n },\n { immediate: true },\n );\n });\n\n onBeforeUnmount(() => {\n document.removeEventListener(\"mousedown\", onDocumentClick);\n document.removeEventListener(\"focusin\", onDocumentFocus);\n });\n\n return channel;\n}\n","<script lang=\"ts\">\n/**\n * A simple sidebar that's fixed to the left side of the viewport.\n *\n * This includes the CSS for the `fixed` position and sizing, so the element\n * should be fairly high in the DOM tree.\n *\n * If neither `top-offset` nor `top-offset-var` are defined, the sidebar will be\n * offset by `var(--g-toolbar-height)`. If there is no toolbar, just pass\n * `0` as the `top-offset`.\n *\n * The sidebar can be made collapsible by providing the `sidebar` injected\n * object from `useSidebar`. See the [Hamburger Menu Documentation](#use-sidebar)\n * for details.\n *\n * In web components mode, use the `sidebar-key` prop to pair this sidebar\n * with a matching GHamburgerMenu instance.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, inject, useId } from \"vue\";\nimport { useSidebar } from \"../compose/useSidebar.ts\";\nimport { useWebComponentSidebar } from \"../compose/useWebComponentSidebar.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Custom background color\n * @demo\n */\n backgroundColor?: string;\n /**\n * Custom background image\n * @demo none\n */\n backgroundImage?: string;\n /**\n * Sidebar theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * Offset from the top of the viewport\n */\n topOffset?: string;\n /**\n * Top offset variable to use instead of topOffset\n */\n topOffsetVar?: string;\n /**\n * Width\n *\n * Width of the sidebar\n * @demo\n */\n width?: string;\n /**\n * Sidebar channel key for custom elements mode\n * @demo\n */\n sidebarKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n backgroundColor: \"\",\n backgroundImage: \"none\",\n theme: \"dark\",\n width: \"300px\",\n topOffset: \"\",\n topOffsetVar: \"\",\n sidebarKey: \"default\",\n});\n\nconst injectedSidebar = inject<ReturnType<typeof useSidebar>>(\n \"sidebar\",\n // This isn't required, so the default value just avoids compiler warnings\n () => undefined as any,\n true,\n);\n\nconst sidebar =\n injectedSidebar ??\n (isCustomElementMode() ? useWebComponentSidebar(props.sidebarKey) : undefined);\n\nconst bgImage = computed(() => {\n if (props.backgroundImage) {\n return props.backgroundImage;\n }\n if (props.theme === \"light\") {\n return \"none\";\n }\n return \"url('https://gradcdn.blob.core.windows.net/public/sidebar-bg2.jpg')\";\n});\n\nconst bgColor = computed(() => {\n if (props.backgroundColor) {\n return props.backgroundColor;\n }\n if (props.theme === \"light\") {\n return \"#f9f9f9\";\n }\n return \"#030913\";\n});\n\nconst topOff = computed(() => {\n if (props.topOffsetVar) {\n return `var(${props.topOffsetVar})`;\n }\n return props.topOffset ? props.topOffset : \"var(--g-toolbar-height)\";\n});\n\nconst fallbackId = useId();\n\nfunction handleEscapeKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n if (sidebar?.isCollapsible?.value && sidebar?.open?.value) {\n sidebar.open.value = false;\n document.getElementById(`${sidebar.id}-hamburger`)?.focus();\n }\n }\n}\n</script>\n\n<template>\n <div\n ref=\"sidebar-ref\"\n :id=\"`${sidebar?.id ?? fallbackId}-sidebar`\"\n class=\"g-sidebar\"\n :class=\"[\n `g-sidebar__${theme}`,\n {\n 'g-sidebar--collapsible': sidebar?.isCollapsible?.value,\n 'g-sidebar--closed':\n !sidebar?.open?.value && sidebar?.isCollapsible?.value,\n 'g-sidebar--open':\n sidebar?.open?.value && sidebar?.isCollapsible?.value,\n },\n ]\"\n :style=\"{\n backgroundImage: bgImage,\n backgroundColor: bgColor,\n '--g-sidebar-top-offset': topOff,\n '--g-sidebar-width': width ?? '300px',\n width: 'var(--g-sidebar-width)',\n }\"\n @keydown=\"handleEscapeKey\"\n >\n <slot></slot>\n </div>\n</template>\n\n<style>\ng-sidebar:not(:defined),\n.g-sidebar {\n box-sizing: border-box;\n background-size: cover;\n background-position: top;\n background-image: url('https://gradcdn.blob.core.windows.net/public/sidebar-bg2.jpg');\n background-color: #030913;\n position: fixed;\n left: 0;\n /*noinspection CssUnresolvedCustomProperty*/\n top: var(--g-sidebar-top-offset, var(--g-toolbar-height));\n bottom: 0;\n /*noinspection CssUnresolvedCustomProperty*/\n width: var(--g-sidebar-width, 300px);\n overflow-y: auto;\n}\n\ng-sidebar[theme=\"light\"] {\n background-image: none;\n background-color: #f9f9f9;\n}\n\n.g-sidebar--open {\n transition: opacity 0.1s ease-out;\n opacity: 1;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-sidebar--open {\n transition: none;\n }\n}\n\n.g-sidebar--closed {\n display: none;\n}\n.g-sidebar--collapsible {\n height: 100vh;\n top: 0;\n z-index: 198;\n box-shadow:\n 0 2px 10px rgba(0, 0, 0, 0.1),\n 0 0 10px rgba(0, 0, 0, 0.1);\n}\n@starting-style {\n .g-sidebar {\n opacity: 0;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A simple sidebar that's fixed to the left side of the viewport.\n *\n * This includes the CSS for the `fixed` position and sizing, so the element\n * should be fairly high in the DOM tree.\n *\n * If neither `top-offset` nor `top-offset-var` are defined, the sidebar will be\n * offset by `var(--g-toolbar-height)`. If there is no toolbar, just pass\n * `0` as the `top-offset`.\n *\n * The sidebar can be made collapsible by providing the `sidebar` injected\n * object from `useSidebar`. See the [Hamburger Menu Documentation](#use-sidebar)\n * for details.\n *\n * In web components mode, use the `sidebar-key` prop to pair this sidebar\n * with a matching GHamburgerMenu instance.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, inject, useId } from \"vue\";\nimport { useSidebar } from \"../compose/useSidebar.ts\";\nimport { useWebComponentSidebar } from \"../compose/useWebComponentSidebar.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Custom background color\n * @demo\n */\n backgroundColor?: string;\n /**\n * Custom background image\n * @demo none\n */\n backgroundImage?: string;\n /**\n * Sidebar theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * Offset from the top of the viewport\n */\n topOffset?: string;\n /**\n * Top offset variable to use instead of topOffset\n */\n topOffsetVar?: string;\n /**\n * Width\n *\n * Width of the sidebar\n * @demo\n */\n width?: string;\n /**\n * Sidebar channel key for custom elements mode\n * @demo\n */\n sidebarKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n backgroundColor: \"\",\n backgroundImage: \"none\",\n theme: \"dark\",\n width: \"300px\",\n topOffset: \"\",\n topOffsetVar: \"\",\n sidebarKey: \"default\",\n});\n\nconst injectedSidebar = inject<ReturnType<typeof useSidebar>>(\n \"sidebar\",\n // This isn't required, so the default value just avoids compiler warnings\n () => undefined as any,\n true,\n);\n\nconst sidebar =\n injectedSidebar ??\n (isCustomElementMode() ? useWebComponentSidebar(props.sidebarKey) : undefined);\n\nconst bgImage = computed(() => {\n if (props.backgroundImage) {\n return props.backgroundImage;\n }\n if (props.theme === \"light\") {\n return \"none\";\n }\n return \"url('https://gradcdn.blob.core.windows.net/public/sidebar-bg2.jpg')\";\n});\n\nconst bgColor = computed(() => {\n if (props.backgroundColor) {\n return props.backgroundColor;\n }\n if (props.theme === \"light\") {\n return \"#f9f9f9\";\n }\n return \"#030913\";\n});\n\nconst topOff = computed(() => {\n if (props.topOffsetVar) {\n return `var(${props.topOffsetVar})`;\n }\n return props.topOffset ? props.topOffset : \"var(--g-toolbar-height)\";\n});\n\nconst fallbackId = useId();\n\nfunction handleEscapeKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n if (sidebar?.isCollapsible?.value && sidebar?.open?.value) {\n sidebar.open.value = false;\n document.getElementById(`${sidebar.id}-hamburger`)?.focus();\n }\n }\n}\n</script>\n\n<template>\n <div\n ref=\"sidebar-ref\"\n :id=\"`${sidebar?.id ?? fallbackId}-sidebar`\"\n class=\"g-sidebar\"\n :class=\"[\n `g-sidebar__${theme}`,\n {\n 'g-sidebar--collapsible': sidebar?.isCollapsible?.value,\n 'g-sidebar--closed':\n !sidebar?.open?.value && sidebar?.isCollapsible?.value,\n 'g-sidebar--open':\n sidebar?.open?.value && sidebar?.isCollapsible?.value,\n },\n ]\"\n :style=\"{\n backgroundImage: bgImage,\n backgroundColor: bgColor,\n '--g-sidebar-top-offset': topOff,\n '--g-sidebar-width': width ?? '300px',\n width: 'var(--g-sidebar-width)',\n }\"\n @keydown=\"handleEscapeKey\"\n >\n <slot></slot>\n </div>\n</template>\n\n<style>\ng-sidebar:not(:defined),\n.g-sidebar {\n box-sizing: border-box;\n background-size: cover;\n background-position: top;\n background-image: url('https://gradcdn.blob.core.windows.net/public/sidebar-bg2.jpg');\n background-color: #030913;\n position: fixed;\n left: 0;\n /*noinspection CssUnresolvedCustomProperty*/\n top: var(--g-sidebar-top-offset, var(--g-toolbar-height));\n bottom: 0;\n /*noinspection CssUnresolvedCustomProperty*/\n width: var(--g-sidebar-width, 300px);\n overflow-y: auto;\n}\n\ng-sidebar[theme=\"light\"] {\n background-image: none;\n background-color: #f9f9f9;\n}\n\n.g-sidebar--open {\n transition: opacity 0.1s ease-out;\n opacity: 1;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-sidebar--open {\n transition: none;\n }\n}\n\n.g-sidebar--closed {\n display: none;\n}\n.g-sidebar--collapsible {\n height: 100vh;\n top: 0;\n z-index: 198;\n box-shadow:\n 0 2px 10px rgba(0, 0, 0, 0.1),\n 0 0 10px rgba(0, 0, 0, 0.1);\n}\n@starting-style {\n .g-sidebar {\n opacity: 0;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A sidebar menu component for use with `GSidebar`. Displays a title and\n * a list of links.\n *\n * This component also supports showing active links for in-page navigation.\n *\n * **Props**:\n *\n * - `items` with a list of `MenuItem` objects. The objects should have:\n * - `label` for the link text\n * - `href` or `to` for the destination. If `to` is used, the links will\n * be rendered as `router-link` for vue-router.\n * - `spy` to enable tracking active links for in-page navigation\n * - `offset` to adjust the active link tracking position\n * - `theme` to set the menu theme\n *\n * For tracking the active link, the `spy` prop must be set to `true` and\n * a `v-model` must be provided that has the ID of the target element (without #).\n *\n * The composable function `useActiveLinkContent` can be used to track active links.\n * It takes the following parameters:\n *\n * - `Ref<HTMLElement>` Children of this element will be observed\n * - `number` Offset from the top of the window to consider not visible\n * - `Ref<string>` Ref to store the active element ID\n *\n * The direct children of the element must all have an ID to properly work with\n * in-page navigation, and the matching menu item's `href` should be set to\n * `#<id>`.\n *\n * Here's a minimal example of a page using `useActiveLinkContent`:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * import { computed, onMounted, ref, useTemplateRef } from \"vue\";\n * import { useActiveLinkContent } from \"@illinois-grad/grad-vue\";\n *\n * const activeId = ref<string>(\"\");\n * const main = useTemplateRef(\"main\");\n * // onMounted is for Nuxt compatibility\n * onMounted(() => {\n * useActiveLinkContent(main, 70, activeId);\n * });\n * &lt;/script>\n *\n * <template>\n * <GSidebar>\n * <GSidebarMenu\n * :items=\"[\n * { label: 'Buttons', href: '#buttons' },\n * { label: 'More Buttons', href: '#more-buttons' }\n * ]\"\n * v-model=\"activeId\"\n * />\n * </GSidebar>\n * <main class=\"main\" ref=\"main\">\n * <section id=\"buttons\">\n * <h2>Buttons</h2>\n * <p>Some buttons</p>\n * </section>\n * <section id=\"more-buttons\">\n * <h2>More Buttons</h2>\n * <p>Some more buttons</p>\n * </section>\n * </main>\n * </template>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n getCurrentInstance,\n nextTick,\n onMounted,\n useId,\n useTemplateRef,\n watch,\n} from \"vue\";\n\ntype MenuItem = {\n label: string;\n href?: string;\n to?: string;\n};\n\ntype Props = {\n /**\n * Heading and accessible name\n * @demo Sidebar Menu\n */\n heading?: string;\n /**\n * Items for the menu\n */\n items: MenuItem[];\n /**\n * Offset for tracking active position to account for toolbars\n */\n offset?: number;\n /**\n * Track active position for in-page links\n */\n spy?: boolean;\n /**\n * Sidebar theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * Use compact layout\n * @demo\n */\n compact?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n offset: 70,\n spy: true,\n theme: \"light\",\n compact: false,\n});\n\nconst activeId = defineModel<string | null>({ default: null, type: String });\n\nconst activeLink = computed(() => {\n if (props.spy && activeId.value) {\n return \"#\" + activeId.value;\n }\n return null;\n});\n\nconst content = useTemplateRef(\"content\");\n\nonMounted(() => {\n watch(\n activeId,\n () => {\n nextTick(() => {\n const activeItem = content.value?.querySelector<HTMLElement>(\n `.g-sidebar-menu__is-active`,\n );\n if (activeItem) {\n activeItem.scrollIntoView({ block: \"nearest\" });\n }\n });\n },\n { immediate: true },\n );\n});\n\n// Detect vue-router without adding it as a dependency\nconst instance = getCurrentInstance();\nconst RouterLinkComp = computed<any | null>(() => {\n const comp = instance?.appContext?.components?.RouterLink as\n | any\n | undefined;\n return comp ?? null;\n});\n\nfunction onLinkClick(e: MouseEvent, item: MenuItem) {\n // Only handle in-page hash links here\n if (!item.href || !item.href.startsWith(\"#\")) {\n return;\n }\n\n const id = item.href.slice(1);\n const el = document\n .getElementById(id)\n ?.querySelector<HTMLElement>(\"h2, h3, h4, h5\");\n if (!el) {\n return;\n }\n e.preventDefault();\n el.setAttribute(\"tabindex\", \"-1\");\n el.focus();\n el.scrollIntoView({ block: \"start\" });\n\n history.replaceState(null, \"\", item.href);\n}\n\nconst id = useId();\n</script>\n\n<template>\n <nav\n class=\"g-sidebar-menu\"\n :class=\"[\n `g-sidebar-menu__${props.theme}`,\n { 'g-sidebar-menu--compact': props.compact },\n ]\"\n v-bind=\"{\n 'aria-labelledby': heading ? id : undefined,\n 'aria-label': heading ? undefined : 'Sidebar Menu',\n }\"\n >\n <h2 v-if=\"heading\" :id=\"id\" class=\"g-sidebar-menu__title\">{{ heading }}</h2>\n <div class=\"g-sidebar-menu__divider\"></div>\n <div class=\"g-sidebar-menu__content\" ref=\"content\">\n <ul class=\"g-sidebar-menu__list\">\n <li\n v-for=\"item in items\"\n :key=\"item.href || item.to\"\n class=\"g-sidebar-menu__item\"\n ref=\"listItems\"\n >\n <!-- Prefer RouterLink when available and item uses 'to' -->\n <component\n v-if=\"item.to && RouterLinkComp\"\n :is=\"RouterLinkComp\"\n class=\"g-sidebar-menu__link\"\n :to=\"item.to\"\n >\n {{ item.label }}\n </component>\n <a\n v-else\n class=\"g-sidebar-menu__link\"\n :href=\"item.href || item.to || '#'\"\n :class=\"{\n 'g-sidebar-menu__is-active':\n activeLink === (item.href || ''),\n }\"\n :aria-current=\"\n activeLink === (item.href || '')\n ? 'location'\n : undefined\n \"\n @click=\"(e) => onLinkClick(e, item)\"\n >\n {{ item.label }}\n </a>\n </li>\n </ul>\n </div>\n </nav>\n</template>\n\n<style>\ng-sidebar-menu,\n.g-sidebar-menu {\n box-sizing: border-box;\n padding-top: 2rem;\n display: flex;\n flex-direction: column;\n}\n.g-sidebar-menu {\n color: var(--g-surface-0);\n}\n.g-sidebar-menu__title {\n margin: 2rem 2rem 0.5rem;\n font-size: 2rem;\n font-family: var(--il-font-heading);\n color: var(--g-surface-0);\n}\n.g-sidebar-menu__divider {\n margin-left: 2rem;\n margin-top: 2px;\n height: 4px;\n width: 60px;\n flex: 0 0 4px;\n background: var(--g-accent-500);\n}\n.g-sidebar-menu__content {\n margin: 0 0 0;\n}\n.g-sidebar-menu__list {\n list-style: none;\n margin: 1rem 0 0;\n padding: 0;\n}\n.g-sidebar-menu__item {\n display: block;\n margin: 0;\n}\n.g-sidebar-menu__link {\n display: block;\n padding: 0.5rem calc(2rem - 8px);\n border-left: 8px solid transparent;\n color: var(--g-surface-0);\n text-decoration: none;\n font-size: 1.25rem;\n font-weight: bold;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-500);\n }\n\n &.g-sidebar-menu__is-active {\n background: var(--g-primary-500);\n border-left: 8px solid var(--g-accent-500);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n}\n.g-sidebar-menu--compact {\n .g-sidebar-menu__link {\n padding: 0.1rem calc(2rem - 8px);\n font-size: 1.125rem;\n font-weight: 700;\n }\n}\n\n.g-sidebar-menu__light {\n background: var(--g-surface-50);\n\n .g-sidebar-menu__title,\n .g-sidebar-menu__link {\n color: var(--g-primary-500);\n }\n\n .g-sidebar-menu__link {\n &:hover {\n color: var(--g-accent-700);\n }\n\n &.g-sidebar-menu__is-active {\n background: var(--g-accent-500);\n color: var(--g-surface-0);\n border-left: 8px solid var(--g-primary-500);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A sidebar menu component for use with `GSidebar`. Displays a title and\n * a list of links.\n *\n * This component also supports showing active links for in-page navigation.\n *\n * **Props**:\n *\n * - `items` with a list of `MenuItem` objects. The objects should have:\n * - `label` for the link text\n * - `href` or `to` for the destination. If `to` is used, the links will\n * be rendered as `router-link` for vue-router.\n * - `spy` to enable tracking active links for in-page navigation\n * - `offset` to adjust the active link tracking position\n * - `theme` to set the menu theme\n *\n * For tracking the active link, the `spy` prop must be set to `true` and\n * a `v-model` must be provided that has the ID of the target element (without #).\n *\n * The composable function `useActiveLinkContent` can be used to track active links.\n * It takes the following parameters:\n *\n * - `Ref<HTMLElement>` Children of this element will be observed\n * - `number` Offset from the top of the window to consider not visible\n * - `Ref<string>` Ref to store the active element ID\n *\n * The direct children of the element must all have an ID to properly work with\n * in-page navigation, and the matching menu item's `href` should be set to\n * `#<id>`.\n *\n * Here's a minimal example of a page using `useActiveLinkContent`:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * import { computed, onMounted, ref, useTemplateRef } from \"vue\";\n * import { useActiveLinkContent } from \"@illinois-grad/grad-vue\";\n *\n * const activeId = ref<string>(\"\");\n * const main = useTemplateRef(\"main\");\n * // onMounted is for Nuxt compatibility\n * onMounted(() => {\n * useActiveLinkContent(main, 70, activeId);\n * });\n * &lt;/script>\n *\n * <template>\n * <GSidebar>\n * <GSidebarMenu\n * :items=\"[\n * { label: 'Buttons', href: '#buttons' },\n * { label: 'More Buttons', href: '#more-buttons' }\n * ]\"\n * v-model=\"activeId\"\n * />\n * </GSidebar>\n * <main class=\"main\" ref=\"main\">\n * <section id=\"buttons\">\n * <h2>Buttons</h2>\n * <p>Some buttons</p>\n * </section>\n * <section id=\"more-buttons\">\n * <h2>More Buttons</h2>\n * <p>Some more buttons</p>\n * </section>\n * </main>\n * </template>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n getCurrentInstance,\n nextTick,\n onMounted,\n useId,\n useTemplateRef,\n watch,\n} from \"vue\";\n\ntype MenuItem = {\n label: string;\n href?: string;\n to?: string;\n};\n\ntype Props = {\n /**\n * Heading and accessible name\n * @demo Sidebar Menu\n */\n heading?: string;\n /**\n * Items for the menu\n */\n items: MenuItem[];\n /**\n * Offset for tracking active position to account for toolbars\n */\n offset?: number;\n /**\n * Track active position for in-page links\n */\n spy?: boolean;\n /**\n * Sidebar theme\n * @demo\n */\n theme?: \"light\" | \"dark\";\n /**\n * Use compact layout\n * @demo\n */\n compact?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n offset: 70,\n spy: true,\n theme: \"light\",\n compact: false,\n});\n\nconst activeId = defineModel<string | null>({ default: null, type: String });\n\nconst activeLink = computed(() => {\n if (props.spy && activeId.value) {\n return \"#\" + activeId.value;\n }\n return null;\n});\n\nconst content = useTemplateRef(\"content\");\n\nonMounted(() => {\n watch(\n activeId,\n () => {\n nextTick(() => {\n const activeItem = content.value?.querySelector<HTMLElement>(\n `.g-sidebar-menu__is-active`,\n );\n if (activeItem) {\n activeItem.scrollIntoView({ block: \"nearest\" });\n }\n });\n },\n { immediate: true },\n );\n});\n\n// Detect vue-router without adding it as a dependency\nconst instance = getCurrentInstance();\nconst RouterLinkComp = computed<any | null>(() => {\n const comp = instance?.appContext?.components?.RouterLink as\n | any\n | undefined;\n return comp ?? null;\n});\n\nfunction onLinkClick(e: MouseEvent, item: MenuItem) {\n // Only handle in-page hash links here\n if (!item.href || !item.href.startsWith(\"#\")) {\n return;\n }\n\n const id = item.href.slice(1);\n const el = document\n .getElementById(id)\n ?.querySelector<HTMLElement>(\"h2, h3, h4, h5\");\n if (!el) {\n return;\n }\n e.preventDefault();\n el.setAttribute(\"tabindex\", \"-1\");\n el.focus();\n el.scrollIntoView({ block: \"start\" });\n\n history.replaceState(null, \"\", item.href);\n}\n\nconst id = useId();\n</script>\n\n<template>\n <nav\n class=\"g-sidebar-menu\"\n :class=\"[\n `g-sidebar-menu__${props.theme}`,\n { 'g-sidebar-menu--compact': props.compact },\n ]\"\n v-bind=\"{\n 'aria-labelledby': heading ? id : undefined,\n 'aria-label': heading ? undefined : 'Sidebar Menu',\n }\"\n >\n <h2 v-if=\"heading\" :id=\"id\" class=\"g-sidebar-menu__title\">{{ heading }}</h2>\n <div class=\"g-sidebar-menu__divider\"></div>\n <div class=\"g-sidebar-menu__content\" ref=\"content\">\n <ul class=\"g-sidebar-menu__list\">\n <li\n v-for=\"item in items\"\n :key=\"item.href || item.to\"\n class=\"g-sidebar-menu__item\"\n ref=\"listItems\"\n >\n <!-- Prefer RouterLink when available and item uses 'to' -->\n <component\n v-if=\"item.to && RouterLinkComp\"\n :is=\"RouterLinkComp\"\n class=\"g-sidebar-menu__link\"\n :to=\"item.to\"\n >\n {{ item.label }}\n </component>\n <a\n v-else\n class=\"g-sidebar-menu__link\"\n :href=\"item.href || item.to || '#'\"\n :class=\"{\n 'g-sidebar-menu__is-active':\n activeLink === (item.href || ''),\n }\"\n :aria-current=\"\n activeLink === (item.href || '')\n ? 'location'\n : undefined\n \"\n @click=\"(e) => onLinkClick(e, item)\"\n >\n {{ item.label }}\n </a>\n </li>\n </ul>\n </div>\n </nav>\n</template>\n\n<style>\ng-sidebar-menu,\n.g-sidebar-menu {\n box-sizing: border-box;\n padding-top: 2rem;\n display: flex;\n flex-direction: column;\n}\n.g-sidebar-menu {\n color: var(--g-surface-0);\n}\n.g-sidebar-menu__title {\n margin: 2rem 2rem 0.5rem;\n font-size: 2rem;\n font-family: var(--il-font-heading);\n color: var(--g-surface-0);\n}\n.g-sidebar-menu__divider {\n margin-left: 2rem;\n margin-top: 2px;\n height: 4px;\n width: 60px;\n flex: 0 0 4px;\n background: var(--g-accent-500);\n}\n.g-sidebar-menu__content {\n margin: 0 0 0;\n}\n.g-sidebar-menu__list {\n list-style: none;\n margin: 1rem 0 0;\n padding: 0;\n}\n.g-sidebar-menu__item {\n display: block;\n margin: 0;\n}\n.g-sidebar-menu__link {\n display: block;\n padding: 0.5rem calc(2rem - 8px);\n border-left: 8px solid transparent;\n color: var(--g-surface-0);\n text-decoration: none;\n font-size: 1.25rem;\n font-weight: bold;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-500);\n }\n\n &.g-sidebar-menu__is-active {\n background: var(--g-primary-500);\n border-left: 8px solid var(--g-accent-500);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n}\n.g-sidebar-menu--compact {\n .g-sidebar-menu__link {\n padding: 0.1rem calc(2rem - 8px);\n font-size: 1.125rem;\n font-weight: 700;\n }\n}\n\n.g-sidebar-menu__light {\n background: var(--g-surface-50);\n\n .g-sidebar-menu__title,\n .g-sidebar-menu__link {\n color: var(--g-primary-500);\n }\n\n .g-sidebar-menu__link {\n &:hover {\n color: var(--g-accent-700);\n }\n\n &.g-sidebar-menu__is-active {\n background: var(--g-accent-500);\n color: var(--g-surface-0);\n border-left: 8px solid var(--g-primary-500);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n }\n}\n</style>\n\n","import type { Directive, DirectiveBinding } from \"vue\";\nimport { ref, watchEffect } from \"vue\";\nimport {\n appendTooltipEl,\n createTooltipEl,\n hideTooltip,\n resolveTooltipId,\n showTooltip,\n} from \"../compose/tooltipDom.ts\";\n\nexport type VGtooltipDirective = Directive<HTMLElement, string>;\n\nconst VGtooltip: VGtooltipDirective = {\n mounted(el: HTMLElement, binding: DirectiveBinding) {\n const tooltip = ref<HTMLElement | null>(null);\n const isHovered = ref(false);\n const isFocused = ref(false);\n const tooltipText = ref(binding.value);\n let resizeObserver: ResizeObserver | null = null;\n let scrollListenerActive = false;\n // Generate unique id for tooltip\n const tooltipId = resolveTooltipId(el);\n\n const ensureTooltip = () => {\n if (!tooltip.value) {\n tooltip.value = createTooltipEl(tooltipText.value, tooltipId);\n // Append to #modal-root (with document.body fallback) so the tooltip\n // shares the same DOM container as GModal and GPopover. This ensures\n // consistent z-index stacking context and avoids CSS transform issues\n // when the trigger is inside a transformed ancestor (e.g., GModal uses\n // transform: translate(-50%, -50%) for centering).\n appendTooltipEl(tooltip.value);\n resizeObserver = new ResizeObserver(() => {\n if (tooltip.value && (isHovered.value || isFocused.value)) {\n showTooltip(el, tooltip.value);\n }\n });\n resizeObserver.observe(tooltip.value);\n }\n };\n\n watchEffect(() => {\n if (tooltip.value) {\n tooltip.value.textContent = tooltipText.value;\n }\n });\n\n // Watch for changes in hover and focus states,\n // and show/hide the tooltip accordingly\n const onScroll = () => {\n if (tooltip.value && (isHovered.value || isFocused.value)) {\n showTooltip(el, tooltip.value);\n }\n };\n\n watchEffect(() => {\n if (isHovered.value || isFocused.value) {\n ensureTooltip();\n if (tooltip.value) {\n showTooltip(el, tooltip.value);\n }\n if (!scrollListenerActive) {\n window.addEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = true;\n }\n } else {\n if (scrollListenerActive) {\n window.removeEventListener(\"scroll\", onScroll, { capture: true });\n scrollListenerActive = false;\n }\n if (tooltip.value) {\n hideTooltip(tooltip.value);\n\n setTimeout(() => {\n // After the fade out, emit an event in case the user needs\n // to perform any cleanup\n el.dispatchEvent(new CustomEvent(\"tooltip-hide\"));\n }, 150);\n }\n }\n });\n\n const onMouseEnter = () => {\n isHovered.value = true;\n };\n const onMouseLeave = () => {\n isHovered.value = false;\n };\n const onFocus = () => {\n isFocused.value = true;\n };\n const onBlur = () => {\n isFocused.value = false;\n };\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" || e.key === \"Esc\") {\n isHovered.value = false;\n isFocused.value = false;\n }\n };\n\n el.addEventListener(\"mouseenter\", onMouseEnter);\n el.addEventListener(\"mouseleave\", onMouseLeave);\n el.addEventListener(\"focus\", onFocus);\n el.addEventListener(\"blur\", onBlur);\n el.addEventListener(\"keydown\", onKeyDown);\n\n ensureTooltip();\n\n // Since this is a directive, we need to store the variables on the element\n (el as any)._v_gtooltip = {\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n onKeyDown,\n onScroll,\n tooltip,\n tooltipText,\n isHovered,\n isFocused,\n resizeObserver,\n tooltipId,\n };\n },\n updated(el: HTMLElement, binding: DirectiveBinding) {\n const data = (el as any)._v_gtooltip;\n if (data && data.tooltipText) {\n data.tooltipText.value = binding.value;\n }\n },\n unmounted(el: HTMLElement) {\n const data = (el as any)._v_gtooltip;\n if (data && data.tooltip && data.tooltip.value) {\n if (data.resizeObserver) {\n data.resizeObserver.disconnect();\n }\n data.tooltip.value.remove();\n data.tooltip.value = null;\n }\n if (data && data.onScroll) {\n window.removeEventListener(\"scroll\", data.onScroll, { capture: true });\n }\n el.removeEventListener(\"mouseenter\", data.onMouseEnter);\n el.removeEventListener(\"mouseleave\", data.onMouseLeave);\n el.removeEventListener(\"focus\", data.onFocus);\n el.removeEventListener(\"blur\", data.onBlur);\n el.removeEventListener(\"keydown\", data.onKeyDown);\n el.removeAttribute(\"aria-describedby\");\n },\n};\n\nexport default VGtooltip;\n","<script lang=\"ts\">\n/**\n * Displays text with a clipboard button that copies the text to the clipboard.\n * The text can be hidden in cases where it's already displayed differently.\n *\n * If for some reason the user's browser doesn't support a clipboard,\n * the tooltip will indicate that when they try to copy.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useClipboard } from \"@vueuse/core\";\nimport { ref } from \"vue\";\nimport VGtooltip from \"../directives/v-gtooltip.ts\";\n\ntype Props = {\n /**\n * Text\n * @demo This is some text to get copied\n */\n text: string;\n\n /**\n * Hide the visible text\n * @demo\n */\n hideText?: boolean;\n\n /**\n * Copy button label\n * @demo\n */\n copyLabel?: string;\n}\n\nconst props = defineProps<Props>();\n\nconst vGtooltip = VGtooltip;\n\nconst { text, copy, copied, isSupported } = useClipboard({\n source: props.text,\n});\n\nconst tooltip = ref<string>(props.copyLabel ?? \"Copy to clipboard\");\n\nconst handleCopy = () => {\n if (isSupported.value) {\n copy();\n tooltip.value = \"Copied\";\n } else {\n tooltip.value = \"Copy not supported\";\n }\n};\nconst handleTooltipHide = () => {\n tooltip.value = props.copyLabel ?? \"Copy to clipboard\";\n};\n</script>\n\n<template>\n <div class=\"g-clipboard-text\">\n <template v-if=\"!hideText\">{{ props.text }}</template>\n\n <button\n type=\"button\"\n aria-label=\"Copy\"\n @click=\"handleCopy\"\n v-gtooltip=\"tooltip\"\n @tooltip-hide=\"handleTooltipHide\"\n class=\"g-clipboard-text-button\"\n >\n <svg\n class=\"g-clipboard-svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.125rem\"\n role=\"none presentation\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M480 400L288 400C279.2 400 272 392.8 272 384L272 128C272 119.2 279.2 112 288 112L421.5 112C425.7 112 429.8 113.7 432.8 116.7L491.3 175.2C494.3 178.2 496 182.3 496 186.5L496 384C496 392.8 488.8 400 480 400zM288 448L480 448C515.3 448 544 419.3 544 384L544 186.5C544 169.5 537.3 153.2 525.3 141.2L466.7 82.7C454.7 70.7 438.5 64 421.5 64L288 64C252.7 64 224 92.7 224 128L224 384C224 419.3 252.7 448 288 448zM160 192C124.7 192 96 220.7 96 256L96 512C96 547.3 124.7 576 160 576L352 576C387.3 576 416 547.3 416 512L416 496L368 496L368 512C368 520.8 360.8 528 352 528L160 528C151.2 528 144 520.8 144 512L144 256C144 247.2 151.2 240 160 240L176 240L176 192L160 192z\"\n />\n </svg>\n </button>\n </div>\n</template>\n\n<style>\ng-clipboard {\n display: inline-block;\n}\n.g-clipboard-text-button {\n display: inline-flex;\n align-items: center;\n margin-left: 0.5rem;\n cursor: pointer;\n color: var(--g-surface-0);\n background: var(--g-primary-500);\n border: 1px solid var(--g-primary-500);\n border-radius: 4px;\n padding: 0.4rem;\n font-size: 14px;\n\n .g-clipboard-svg {\n display: block;\n }\n\n &:hover {\n color: var(--g-primary-500);\n background: var(--g-surface-0);\n }\n\n &:focus {\n color: var(--g-primary-500);\n background: var(--g-info-200);\n outline-color: var(--g-primary-500);\n }\n\n span {\n display: block;\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Displays text with a clipboard button that copies the text to the clipboard.\n * The text can be hidden in cases where it's already displayed differently.\n *\n * If for some reason the user's browser doesn't support a clipboard,\n * the tooltip will indicate that when they try to copy.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useClipboard } from \"@vueuse/core\";\nimport { ref } from \"vue\";\nimport VGtooltip from \"../directives/v-gtooltip.ts\";\n\ntype Props = {\n /**\n * Text\n * @demo This is some text to get copied\n */\n text: string;\n\n /**\n * Hide the visible text\n * @demo\n */\n hideText?: boolean;\n\n /**\n * Copy button label\n * @demo\n */\n copyLabel?: string;\n}\n\nconst props = defineProps<Props>();\n\nconst vGtooltip = VGtooltip;\n\nconst { text, copy, copied, isSupported } = useClipboard({\n source: props.text,\n});\n\nconst tooltip = ref<string>(props.copyLabel ?? \"Copy to clipboard\");\n\nconst handleCopy = () => {\n if (isSupported.value) {\n copy();\n tooltip.value = \"Copied\";\n } else {\n tooltip.value = \"Copy not supported\";\n }\n};\nconst handleTooltipHide = () => {\n tooltip.value = props.copyLabel ?? \"Copy to clipboard\";\n};\n</script>\n\n<template>\n <div class=\"g-clipboard-text\">\n <template v-if=\"!hideText\">{{ props.text }}</template>\n\n <button\n type=\"button\"\n aria-label=\"Copy\"\n @click=\"handleCopy\"\n v-gtooltip=\"tooltip\"\n @tooltip-hide=\"handleTooltipHide\"\n class=\"g-clipboard-text-button\"\n >\n <svg\n class=\"g-clipboard-svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.125rem\"\n role=\"none presentation\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M480 400L288 400C279.2 400 272 392.8 272 384L272 128C272 119.2 279.2 112 288 112L421.5 112C425.7 112 429.8 113.7 432.8 116.7L491.3 175.2C494.3 178.2 496 182.3 496 186.5L496 384C496 392.8 488.8 400 480 400zM288 448L480 448C515.3 448 544 419.3 544 384L544 186.5C544 169.5 537.3 153.2 525.3 141.2L466.7 82.7C454.7 70.7 438.5 64 421.5 64L288 64C252.7 64 224 92.7 224 128L224 384C224 419.3 252.7 448 288 448zM160 192C124.7 192 96 220.7 96 256L96 512C96 547.3 124.7 576 160 576L352 576C387.3 576 416 547.3 416 512L416 496L368 496L368 512C368 520.8 360.8 528 352 528L160 528C151.2 528 144 520.8 144 512L144 256C144 247.2 151.2 240 160 240L176 240L176 192L160 192z\"\n />\n </svg>\n </button>\n </div>\n</template>\n\n<style>\ng-clipboard {\n display: inline-block;\n}\n.g-clipboard-text-button {\n display: inline-flex;\n align-items: center;\n margin-left: 0.5rem;\n cursor: pointer;\n color: var(--g-surface-0);\n background: var(--g-primary-500);\n border: 1px solid var(--g-primary-500);\n border-radius: 4px;\n padding: 0.4rem;\n font-size: 14px;\n\n .g-clipboard-svg {\n display: block;\n }\n\n &:hover {\n color: var(--g-primary-500);\n background: var(--g-surface-0);\n }\n\n &:focus {\n color: var(--g-primary-500);\n background: var(--g-info-200);\n outline-color: var(--g-primary-500);\n }\n\n span {\n display: block;\n }\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A scroller that is used for content that's typically shown like a chat log,\n * meaning it starts at the bottom, and you scroll up for older entries.\n *\n * The scroller automatically starts at the bottom. When scrolled up, a button\n * appears that jumps to the bottom. This button is also the first focusable\n * element for accessibility.\n *\n * If the `label` is provided, the scroller will have the ARIA `role=\"log\"` and\n * the label as the `aria-label`.\n *\n * **Slot** `default` is what will be rendered for each entry in the\n * `entries`. For example:\n *\n * ```vue-html\n * <GHistoryScroller\n * v-bind=\"props\"\n * :entries=\"historyEntries\"\n * class=\"history-scroller\">\n * <template #default=\"{ entry }\">\n * <div class=\"history-entry\">\n * This is history for: {{ entry.id }}\n * </div>\n * </template>\n * </GHistoryScroller>\n * ```\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup generic=\"T extends { id: string | number }\">\n\n\nimport { computed, nextTick, onMounted, ref, watch } from \"vue\";\nimport { useResizeObserver } from \"@vueuse/core\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo History\n */\n label?: string;\n\n /**\n * History entries passed to default slot\n * @demo\n */\n entries: T[];\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n entries: () => [],\n});\n\nconst scrollerRef = ref<HTMLElement | null>(null);\nconst contentRef = ref<HTMLElement | null>(null);\nconst isAtBottom = ref(true);\nconst isAtTop = ref(true);\n\nasync function scrollToBottom({ focusLast = false } = {}) {\n if (scrollerRef.value) {\n scrollerRef.value.scrollTop = scrollerRef.value.scrollHeight;\n }\n if (focusLast) {\n if (contentRef.value) {\n const items = contentRef.value.querySelectorAll(\".g-history-entry\");\n if (items.length > 0) {\n const last = items[items.length - 1] as HTMLElement;\n await nextTick();\n last.focus();\n }\n }\n }\n}\n\nfunction handleScroll() {\n if (!scrollerRef.value) return;\n const { scrollTop, scrollHeight, clientHeight } = scrollerRef.value;\n // Consider within 2px of bottom/top as \"at bottom/top\"\n isAtBottom.value = scrollTop + clientHeight >= scrollHeight - 2;\n isAtTop.value = scrollTop <= 2;\n}\n\nonMounted(() => {\n nextTick(scrollToBottom);\n});\n\n// Use VueUse's useResizeObserver for scroller and content\nuseResizeObserver(scrollerRef, () => {\n if (isAtBottom.value) {\n scrollToBottom();\n }\n});\nuseResizeObserver(contentRef, () => {\n if (isAtBottom.value) {\n scrollToBottom();\n }\n});\n\nwatch(\n () => props.entries,\n async () => {\n // Only scroll if at bottom\n if (isAtBottom.value) {\n await nextTick();\n scrollToBottom();\n }\n },\n);\n\nconst reversedEntries = computed(() => [...props.entries].reverse());\n</script>\n\n<template>\n <div class=\"g-history-scroller-wrapper\">\n <div\n v-if=\"!isAtTop\"\n class=\"g-history-shadow g-history-shadow--top\"\n aria-hidden=\"true\"\n ></div>\n <div\n v-if=\"!isAtBottom\"\n class=\"g-history-shadow g-history-shadow--bottom\"\n aria-hidden=\"true\"\n ></div>\n <div\n ref=\"scrollerRef\"\n class=\"g-history-scroller\"\n :role=\"label ? 'log' : undefined\"\n :aria-label=\"label\"\n @scroll=\"handleScroll\"\n >\n <GButton\n class=\"g-scroll-to-bottom-btn\"\n :class=\"{ 'scroll-to-bottom-btn--hidden': isAtBottom }\"\n size=\"small\"\n type=\"button\"\n @click=\"() => scrollToBottom({ focusLast: true })\"\n aria-label=\"Jump to Latest\"\n >\n <svg\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.25rem\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M303.5 569C312.9 578.4 328.1 578.4 337.4 569L505.4 401C514.8 391.6 514.8 376.4 505.4 367.1C496 357.8 480.8 357.7 471.5 367.1L344.5 494.1L344.5 88C344.5 74.7 333.8 64 320.5 64C307.2 64 296.5 74.7 296.5 88L296.5 494.1L169.5 367.1C160.1 357.7 144.9 357.7 135.6 367.1C126.3 376.5 126.2 391.7 135.6 401L303.6 569z\"\n />\n </svg>\n </GButton>\n <div role=\"list\" ref=\"contentRef\" class=\"g-history-list\">\n <div\n v-for=\"entry in reversedEntries\"\n role=\"listitem\"\n :key=\"entry.id\"\n class=\"g-history-entry\"\n tabindex=\"-1\"\n >\n <slot :entry=\"entry\" />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-history-scroller {\n display: block;\n}\n.g-history-scroller-wrapper {\n position: relative;\n}\n.g-history-list {\n padding: 1rem;\n margin: 0;\n list-style: none;\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n.g-history-scroller {\n overflow-y: auto;\n height: 100%;\n width: 100%;\n}\n.g-history-entry {\n margin: 0;\n padding: 0;\n}\n.g-scroll-to-bottom-btn {\n position: absolute;\n right: 2rem;\n bottom: 2rem;\n z-index: 100;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n.scroll-to-bottom-btn--hidden {\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s;\n}\n.scroll-to-bottom-btn--hidden:focus {\n opacity: 1;\n pointer-events: auto;\n}\n\n.g-history-shadow {\n position: absolute;\n left: 0;\n right: 0;\n height: 2rem;\n pointer-events: none;\n position: absolute;\n}\n.g-history-shadow--top {\n top: 0;\n background: linear-gradient(\n to bottom,\n rgba(0, 0, 0, 0.12),\n rgba(0, 0, 0, 0)\n );\n}\n.g-history-shadow--bottom {\n bottom: 0;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0));\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A scroller that is used for content that's typically shown like a chat log,\n * meaning it starts at the bottom, and you scroll up for older entries.\n *\n * The scroller automatically starts at the bottom. When scrolled up, a button\n * appears that jumps to the bottom. This button is also the first focusable\n * element for accessibility.\n *\n * If the `label` is provided, the scroller will have the ARIA `role=\"log\"` and\n * the label as the `aria-label`.\n *\n * **Slot** `default` is what will be rendered for each entry in the\n * `entries`. For example:\n *\n * ```vue-html\n * <GHistoryScroller\n * v-bind=\"props\"\n * :entries=\"historyEntries\"\n * class=\"history-scroller\">\n * <template #default=\"{ entry }\">\n * <div class=\"history-entry\">\n * This is history for: {{ entry.id }}\n * </div>\n * </template>\n * </GHistoryScroller>\n * ```\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup generic=\"T extends { id: string | number }\">\n\n\nimport { computed, nextTick, onMounted, ref, watch } from \"vue\";\nimport { useResizeObserver } from \"@vueuse/core\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo History\n */\n label?: string;\n\n /**\n * History entries passed to default slot\n * @demo\n */\n entries: T[];\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n entries: () => [],\n});\n\nconst scrollerRef = ref<HTMLElement | null>(null);\nconst contentRef = ref<HTMLElement | null>(null);\nconst isAtBottom = ref(true);\nconst isAtTop = ref(true);\n\nasync function scrollToBottom({ focusLast = false } = {}) {\n if (scrollerRef.value) {\n scrollerRef.value.scrollTop = scrollerRef.value.scrollHeight;\n }\n if (focusLast) {\n if (contentRef.value) {\n const items = contentRef.value.querySelectorAll(\".g-history-entry\");\n if (items.length > 0) {\n const last = items[items.length - 1] as HTMLElement;\n await nextTick();\n last.focus();\n }\n }\n }\n}\n\nfunction handleScroll() {\n if (!scrollerRef.value) return;\n const { scrollTop, scrollHeight, clientHeight } = scrollerRef.value;\n // Consider within 2px of bottom/top as \"at bottom/top\"\n isAtBottom.value = scrollTop + clientHeight >= scrollHeight - 2;\n isAtTop.value = scrollTop <= 2;\n}\n\nonMounted(() => {\n nextTick(scrollToBottom);\n});\n\n// Use VueUse's useResizeObserver for scroller and content\nuseResizeObserver(scrollerRef, () => {\n if (isAtBottom.value) {\n scrollToBottom();\n }\n});\nuseResizeObserver(contentRef, () => {\n if (isAtBottom.value) {\n scrollToBottom();\n }\n});\n\nwatch(\n () => props.entries,\n async () => {\n // Only scroll if at bottom\n if (isAtBottom.value) {\n await nextTick();\n scrollToBottom();\n }\n },\n);\n\nconst reversedEntries = computed(() => [...props.entries].reverse());\n</script>\n\n<template>\n <div class=\"g-history-scroller-wrapper\">\n <div\n v-if=\"!isAtTop\"\n class=\"g-history-shadow g-history-shadow--top\"\n aria-hidden=\"true\"\n ></div>\n <div\n v-if=\"!isAtBottom\"\n class=\"g-history-shadow g-history-shadow--bottom\"\n aria-hidden=\"true\"\n ></div>\n <div\n ref=\"scrollerRef\"\n class=\"g-history-scroller\"\n :role=\"label ? 'log' : undefined\"\n :aria-label=\"label\"\n @scroll=\"handleScroll\"\n >\n <GButton\n class=\"g-scroll-to-bottom-btn\"\n :class=\"{ 'scroll-to-bottom-btn--hidden': isAtBottom }\"\n size=\"small\"\n type=\"button\"\n @click=\"() => scrollToBottom({ focusLast: true })\"\n aria-label=\"Jump to Latest\"\n >\n <svg\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.25rem\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M303.5 569C312.9 578.4 328.1 578.4 337.4 569L505.4 401C514.8 391.6 514.8 376.4 505.4 367.1C496 357.8 480.8 357.7 471.5 367.1L344.5 494.1L344.5 88C344.5 74.7 333.8 64 320.5 64C307.2 64 296.5 74.7 296.5 88L296.5 494.1L169.5 367.1C160.1 357.7 144.9 357.7 135.6 367.1C126.3 376.5 126.2 391.7 135.6 401L303.6 569z\"\n />\n </svg>\n </GButton>\n <div role=\"list\" ref=\"contentRef\" class=\"g-history-list\">\n <div\n v-for=\"entry in reversedEntries\"\n role=\"listitem\"\n :key=\"entry.id\"\n class=\"g-history-entry\"\n tabindex=\"-1\"\n >\n <slot :entry=\"entry\" />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-history-scroller {\n display: block;\n}\n.g-history-scroller-wrapper {\n position: relative;\n}\n.g-history-list {\n padding: 1rem;\n margin: 0;\n list-style: none;\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n.g-history-scroller {\n overflow-y: auto;\n height: 100%;\n width: 100%;\n}\n.g-history-entry {\n margin: 0;\n padding: 0;\n}\n.g-scroll-to-bottom-btn {\n position: absolute;\n right: 2rem;\n bottom: 2rem;\n z-index: 100;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n.scroll-to-bottom-btn--hidden {\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s;\n}\n.scroll-to-bottom-btn--hidden:focus {\n opacity: 1;\n pointer-events: auto;\n}\n\n.g-history-shadow {\n position: absolute;\n left: 0;\n right: 0;\n height: 2rem;\n pointer-events: none;\n position: absolute;\n}\n.g-history-shadow--top {\n top: 0;\n background: linear-gradient(\n to bottom,\n rgba(0, 0, 0, 0.12),\n rgba(0, 0, 0, 0)\n );\n}\n.g-history-shadow--bottom {\n bottom: 0;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0));\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This component acts like a radio button group condensed into a compact\n * element with the goal of making it easy to go over many of them at once.\n *\n * In addition to the arrow keys changing the selected value, special key\n * bindings exist for 'y' and 'n' to set yes and no respectively.\n *\n * A `describedby` prop can be passed with an ID to an element to be used as\n * the `aria-describedby` for the group element.\n *\n * When the value changes, `v-model` is updated. A `change` event is also emitted\n * if the value changed from user interaction.\n *\n * Slots:\n * - `label`: Custom label content. Defaults to `label` prop if not provided.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, useId } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo Three-way toggle\n */\n label: string;\n\n /**\n * ID of an element that describes the input\n */\n describedby?: string;\n\n /**\n * Error message\n * @demo\n */\n error?: string;\n\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n}\n\nconst props = defineProps<Props>();\nconst model = defineModel<boolean | null>({default: () => null});\n\nconst emit = defineEmits([\"change\"]);\n\nfunction change(val: boolean | null) {\n const prev = model.value;\n model.value = val;\n if (val !== prev) {\n emit(\"change\", {\n was: prev,\n to: val,\n });\n }\n}\n\nfunction onChange(val: boolean | null) {\n if (props.disabled) {\n return;\n }\n if (model.value === val) {\n change(null);\n } else {\n change(val);\n }\n}\n\nfunction onClickLabel(val: boolean) {\n if (props.disabled) {\n return;\n }\n if (model.value === val) {\n change(null);\n }\n}\n\nconst id = useId();\nconst radioName = computed(() => `g-three-way-toggle-${id}`);\n\nconst leftId = useId();\nconst centerId = useId();\nconst rightId = useId();\n\nconst thumbPosition = computed(() => {\n if (model.value === false) {\n return \"g-left\";\n } else if (model.value === true) {\n return \"g-right\";\n } else {\n return \"g-center\";\n }\n});\n\nfunction onLabelKeydown(e: KeyboardEvent) {\n if (props.disabled) {\n return;\n }\n if (e.key === \"n\" || e.key === \"N\") {\n change(false);\n e.preventDefault();\n } else if (e.key === \"y\" || e.key === \"Y\") {\n change(true);\n e.preventDefault();\n }\n}\n</script>\n\n<template>\n <div class=\"g-three-way-toggle-wrapper\">\n <div class=\"g-three-way-toggle-control\">\n <span class=\"g-label\" :id=\"id\">\n <slot name=\"label\">\n {{ label }}\n </slot>\n </span>\n <fieldset\n class=\"g-three-way-toggle\"\n :class=\"{ 'g-has-error': error }\"\n role=\"radiogroup\"\n :aria-labelledby=\"id\"\n :aria-describedby=\"describedby\"\n :disabled=\"disabled\"\n :aria-invalid=\"error ? 'true' : undefined\"\n :aria-errormessage=\"error ? id + '-error' : undefined\"\n >\n <div\n class=\"g-toggle-track\"\n :class=\"[thumbPosition, { 'g-disabled': disabled }]\"\n >\n <span\n class=\"g-toggle-thumb\"\n :class=\"thumbPosition\"\n aria-hidden=\"true\"\n >\n <span v-if=\"model === false\">NO</span>\n <span v-else-if=\"model === true\">YES</span>\n <span v-else></span>\n </span>\n <label\n :for=\"leftId\"\n class=\"g-toggle-option g-left\"\n @click=\"onClickLabel(false)\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"leftId\"\n :name=\"radioName\"\n :checked=\"model === false\"\n value=\"false\"\n :disabled=\"disabled\"\n @change=\"onChange(false)\"\n /><span class=\"ilw-sr-only\">No</span>\n </label>\n <label\n :for=\"centerId\"\n class=\"g-toggle-option g-center\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"centerId\"\n :name=\"radioName\"\n :checked=\"model === null\"\n :disabled=\"disabled\"\n @change=\"onChange(null)\"\n /><span class=\"ilw-sr-only\">Unset</span>\n </label>\n <label\n :for=\"rightId\"\n class=\"g-toggle-option g-right\"\n @click=\"onClickLabel(true)\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"rightId\"\n :name=\"radioName\"\n value=\"true\"\n :checked=\"model === true\"\n :disabled=\"disabled\"\n @change=\"onChange(true)\"\n /><span class=\"ilw-sr-only\">Yes</span>\n </label>\n </div>\n </fieldset>\n </div>\n <div\n v-if=\"error\"\n :id=\"`${id}-error`\"\n class=\"g-form-error\"\n role=\"alert\"\n aria-atomic=\"true\"\n >\n {{ error }}\n </div>\n </div>\n</template>\n\n<style>\ng-three-way-toggle {\n display: block;\n}\n.g-three-way-toggle-control {\n display: flex;\n margin-bottom: 4px;\n column-gap: 8px;\n\n .g-label {\n flex: 1;\n font-weight: 600;\n font-size: 0.875rem;\n line-height: 1;\n align-self: center;\n display: block;\n }\n}\n\n.g-three-way-toggle {\n border: none;\n padding: 0;\n margin: 0;\n width: 50px;\n background: none;\n border-radius: 12px;\n\n &:focus-within {\n outline: 2px solid var(--il-blue);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-toggle-track {\n display: flex;\n position: relative;\n background: var(--g-surface-700);\n border-radius: 14px;\n height: 24px;\n width: 100%;\n min-width: 50px;\n box-sizing: border-box;\n font-family: var(--il-font-sans);\n\n &.g-left {\n background: var(--il-industrial);\n }\n &.g-right {\n background: var(--il-prairie);\n }\n &.g-disabled {\n pointer-events: none;\n background: var(--g-surface-400);\n }\n}\n\n.g-toggle-thumb {\n position: absolute;\n top: 2px;\n width: 20px;\n height: 20px;\n background: var(--g-surface-100);\n border-radius: 50%;\n left: 2px;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n color: var(--g-surface-0);\n font-weight: 700;\n transition:\n left 0.1s ease-in-out,\n background-color 0.1s ease-in-out;\n letter-spacing: -0.5px;\n &.g-left {\n color: var(--il-industrial);\n }\n &.g-right {\n color: var(--il-prairie);\n letter-spacing: -1.5px;\n }\n}\n.g-toggle-track.g-left .g-toggle-thumb {\n left: 2px;\n}\n.g-toggle-track.g-center .g-toggle-thumb {\n left: calc(50% - 10px);\n}\n.g-toggle-track.g-right .g-toggle-thumb {\n left: calc(100% - 20px - 2px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-toggle-thumb {\n transition: none;\n }\n}\n\n.g-toggle-option {\n flex: 1 1 0;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n &.g-center {\n flex: 0 0 1%;\n }\n}\n.g-toggle-option input[type=\"radio\"] {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n left: 0;\n top: 0;\n margin: 0;\n cursor: pointer;\n z-index: 2;\n}\n\n.g-has-error {\n .g-toggle-track {\n background: var(--g-danger-500);\n }\n}\n.g-form-error {\n color: var(--g-danger-600);\n font-size: 0.875rem;\n font-weight: bold;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This component acts like a radio button group condensed into a compact\n * element with the goal of making it easy to go over many of them at once.\n *\n * In addition to the arrow keys changing the selected value, special key\n * bindings exist for 'y' and 'n' to set yes and no respectively.\n *\n * A `describedby` prop can be passed with an ID to an element to be used as\n * the `aria-describedby` for the group element.\n *\n * When the value changes, `v-model` is updated. A `change` event is also emitted\n * if the value changed from user interaction.\n *\n * Slots:\n * - `label`: Custom label content. Defaults to `label` prop if not provided.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, useId } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo Three-way toggle\n */\n label: string;\n\n /**\n * ID of an element that describes the input\n */\n describedby?: string;\n\n /**\n * Error message\n * @demo\n */\n error?: string;\n\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n}\n\nconst props = defineProps<Props>();\nconst model = defineModel<boolean | null>({default: () => null});\n\nconst emit = defineEmits([\"change\"]);\n\nfunction change(val: boolean | null) {\n const prev = model.value;\n model.value = val;\n if (val !== prev) {\n emit(\"change\", {\n was: prev,\n to: val,\n });\n }\n}\n\nfunction onChange(val: boolean | null) {\n if (props.disabled) {\n return;\n }\n if (model.value === val) {\n change(null);\n } else {\n change(val);\n }\n}\n\nfunction onClickLabel(val: boolean) {\n if (props.disabled) {\n return;\n }\n if (model.value === val) {\n change(null);\n }\n}\n\nconst id = useId();\nconst radioName = computed(() => `g-three-way-toggle-${id}`);\n\nconst leftId = useId();\nconst centerId = useId();\nconst rightId = useId();\n\nconst thumbPosition = computed(() => {\n if (model.value === false) {\n return \"g-left\";\n } else if (model.value === true) {\n return \"g-right\";\n } else {\n return \"g-center\";\n }\n});\n\nfunction onLabelKeydown(e: KeyboardEvent) {\n if (props.disabled) {\n return;\n }\n if (e.key === \"n\" || e.key === \"N\") {\n change(false);\n e.preventDefault();\n } else if (e.key === \"y\" || e.key === \"Y\") {\n change(true);\n e.preventDefault();\n }\n}\n</script>\n\n<template>\n <div class=\"g-three-way-toggle-wrapper\">\n <div class=\"g-three-way-toggle-control\">\n <span class=\"g-label\" :id=\"id\">\n <slot name=\"label\">\n {{ label }}\n </slot>\n </span>\n <fieldset\n class=\"g-three-way-toggle\"\n :class=\"{ 'g-has-error': error }\"\n role=\"radiogroup\"\n :aria-labelledby=\"id\"\n :aria-describedby=\"describedby\"\n :disabled=\"disabled\"\n :aria-invalid=\"error ? 'true' : undefined\"\n :aria-errormessage=\"error ? id + '-error' : undefined\"\n >\n <div\n class=\"g-toggle-track\"\n :class=\"[thumbPosition, { 'g-disabled': disabled }]\"\n >\n <span\n class=\"g-toggle-thumb\"\n :class=\"thumbPosition\"\n aria-hidden=\"true\"\n >\n <span v-if=\"model === false\">NO</span>\n <span v-else-if=\"model === true\">YES</span>\n <span v-else></span>\n </span>\n <label\n :for=\"leftId\"\n class=\"g-toggle-option g-left\"\n @click=\"onClickLabel(false)\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"leftId\"\n :name=\"radioName\"\n :checked=\"model === false\"\n value=\"false\"\n :disabled=\"disabled\"\n @change=\"onChange(false)\"\n /><span class=\"ilw-sr-only\">No</span>\n </label>\n <label\n :for=\"centerId\"\n class=\"g-toggle-option g-center\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"centerId\"\n :name=\"radioName\"\n :checked=\"model === null\"\n :disabled=\"disabled\"\n @change=\"onChange(null)\"\n /><span class=\"ilw-sr-only\">Unset</span>\n </label>\n <label\n :for=\"rightId\"\n class=\"g-toggle-option g-right\"\n @click=\"onClickLabel(true)\"\n @keydown=\"onLabelKeydown\"\n >\n <input\n type=\"radio\"\n :id=\"rightId\"\n :name=\"radioName\"\n value=\"true\"\n :checked=\"model === true\"\n :disabled=\"disabled\"\n @change=\"onChange(true)\"\n /><span class=\"ilw-sr-only\">Yes</span>\n </label>\n </div>\n </fieldset>\n </div>\n <div\n v-if=\"error\"\n :id=\"`${id}-error`\"\n class=\"g-form-error\"\n role=\"alert\"\n aria-atomic=\"true\"\n >\n {{ error }}\n </div>\n </div>\n</template>\n\n<style>\ng-three-way-toggle {\n display: block;\n}\n.g-three-way-toggle-control {\n display: flex;\n margin-bottom: 4px;\n column-gap: 8px;\n\n .g-label {\n flex: 1;\n font-weight: 600;\n font-size: 0.875rem;\n line-height: 1;\n align-self: center;\n display: block;\n }\n}\n\n.g-three-way-toggle {\n border: none;\n padding: 0;\n margin: 0;\n width: 50px;\n background: none;\n border-radius: 12px;\n\n &:focus-within {\n outline: 2px solid var(--il-blue);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-toggle-track {\n display: flex;\n position: relative;\n background: var(--g-surface-700);\n border-radius: 14px;\n height: 24px;\n width: 100%;\n min-width: 50px;\n box-sizing: border-box;\n font-family: var(--il-font-sans);\n\n &.g-left {\n background: var(--il-industrial);\n }\n &.g-right {\n background: var(--il-prairie);\n }\n &.g-disabled {\n pointer-events: none;\n background: var(--g-surface-400);\n }\n}\n\n.g-toggle-thumb {\n position: absolute;\n top: 2px;\n width: 20px;\n height: 20px;\n background: var(--g-surface-100);\n border-radius: 50%;\n left: 2px;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n color: var(--g-surface-0);\n font-weight: 700;\n transition:\n left 0.1s ease-in-out,\n background-color 0.1s ease-in-out;\n letter-spacing: -0.5px;\n &.g-left {\n color: var(--il-industrial);\n }\n &.g-right {\n color: var(--il-prairie);\n letter-spacing: -1.5px;\n }\n}\n.g-toggle-track.g-left .g-toggle-thumb {\n left: 2px;\n}\n.g-toggle-track.g-center .g-toggle-thumb {\n left: calc(50% - 10px);\n}\n.g-toggle-track.g-right .g-toggle-thumb {\n left: calc(100% - 20px - 2px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-toggle-thumb {\n transition: none;\n }\n}\n\n.g-toggle-option {\n flex: 1 1 0;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n &.g-center {\n flex: 0 0 1%;\n }\n}\n.g-toggle-option input[type=\"radio\"] {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n left: 0;\n top: 0;\n margin: 0;\n cursor: pointer;\n z-index: 2;\n}\n\n.g-has-error {\n .g-toggle-track {\n background: var(--g-danger-500);\n }\n}\n.g-form-error {\n color: var(--g-danger-600);\n font-size: 0.875rem;\n font-weight: bold;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Internal component for rendering the table body.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\" generic=\"T extends TableRow, C extends TableColumn<T>\">\nimport { computed, ref, type VNode } from \"vue\";\nimport { TableColumn, TableRow } from \"./TableColumn.ts\";\nimport { UseTableChangesReturn } from \"../../compose/useTableChanges.ts\";\n\ntype Props = {\n data: T[];\n groupBy?: keyof T;\n columns: C[];\n groupRender?: (groupValue: any, row: T) => VNode;\n rowClickable?: boolean;\n rowClass?: (row: T) => string | string[] | undefined;\n startIndex: number;\n bulkSelectionEnabled?: boolean;\n selectedRows?: string[];\n tableId: string;\n changeTracker?: UseTableChangesReturn<T>;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n (e: \"row-click\", link: string): void;\n (e: \"toggle-row\", rowKey: string, shiftKey: boolean): void;\n (e: \"cell-change\", payload: { row: T; column: C; value: any }): void;\n}>();\n\n\nfunction handleMouseDown(event: MouseEvent, rowKey: string) {\n // Prevent text selection when shift-clicking for bulk selection\n // Only if bulk selection is enabled and shift is held and we're not on an input\n if (\n props.bulkSelectionEnabled &&\n event.shiftKey &&\n !(event.target as HTMLElement).closest(\"a,button,[tabindex],input\")\n ) {\n event.preventDefault();\n }\n}\n\nfunction handleRowClick(event: MouseEvent, rowKey: string) {\n if (!props.rowClickable && !props.bulkSelectionEnabled) {\n return; // Do nothing if rows are not clickable\n }\n // Only trigger if not clicking on a link or button directly\n if ((event.target as HTMLElement).closest(\"a,button,[tabindex],input\")) {\n return;\n }\n const row = (event.target as HTMLElement).closest(\n \"tr\",\n ) as HTMLTableRowElement;\n if (row) {\n if (props.bulkSelectionEnabled) {\n let checkbox = row.querySelector(\n \"input[type=checkbox]\",\n ) as HTMLInputElement | null;\n if (checkbox) {\n // Trigger the checkbox change with shift key info\n handleCheckboxChange(rowKey, event.shiftKey);\n }\n } else if (props.rowClickable) {\n const firstLink = row.querySelector(\"a[href]\");\n const link = firstLink?.getAttribute(\"href\");\n if (link) {\n emit(\"row-click\", link);\n }\n }\n }\n}\n\nfunction isRowSelected(rowKey: string): boolean {\n return props.selectedRows?.includes(rowKey) ?? false;\n}\n\nfunction handleCheckboxChange(rowKey: string, shiftKey: boolean = false) {\n emit(\"toggle-row\", rowKey, shiftKey);\n}\n\nfunction handleCellChange(event: Event, row: T, col: C) {\n const target = event.target as unknown as\n | HTMLInputElement\n | HTMLSelectElement;\n const value = target.value;\n emit(\"cell-change\", { row, column: col, value });\n}\n\nfunction buildAriaLabelledBy(row: T, col: C): string {\n const columnHeaderId = `${props.tableId}-th-${String(col.key)}`;\n\n // If labelKey is specified, add the label cell ID\n if (col.editable?.labelKey) {\n const labelCellId = `${props.tableId}-td-${row.key}-${col.editable.labelKey}`;\n return `${labelCellId} ${columnHeaderId} `;\n }\n\n return columnHeaderId;\n}\n\nconst labelCellColumn = computed(() => {\n for (const col of props.columns) {\n if (col.editable?.labelKey) {\n return col.editable.labelKey;\n }\n }\n return undefined;\n }\n);\n\nfunction shouldAddCellId(col: C): boolean {\n // Check if this column is used as a label for any editable column\n return col.key === labelCellColumn.value;\n}\n\nfunction hasCellChange(row: T, col: C): boolean {\n if (!props.changeTracker) return false;\n return props.changeTracker.hasChange(row.key, col.key);\n}\n\nfunction hasCellError(row: T, col: C): boolean {\n if (!props.changeTracker) return false;\n return props.changeTracker.hasError(row.key, col.key);\n}\nfunction getCellError(row: T, col: C): string | undefined {\n if (!props.changeTracker) return undefined;\n return props.changeTracker.getError(row.key, col.key);\n}\n\n</script>\n\n<template>\n <tbody ref=\"tableBodyRef\" class=\"efficient-table-body\">\n <template v-for=\"(row, idx) in data\" :key=\"row.key\">\n <tr\n v-if=\"\n groupBy &&\n (idx === 0 || row[groupBy] !== data[idx - 1][groupBy])\n \"\n :aria-rowindex=\"startIndex + idx + 2\"\n >\n <td\n v-if=\"bulkSelectionEnabled\"\n class=\"table-group-checkbox\"\n ></td>\n <td :colspan=\"columns.length\" class=\"table-group-row\">\n <template v-if=\"groupRender\">\n <component :is=\"groupRender(row[groupBy], row)\" />\n </template>\n <template v-else>\n {{ row[groupBy] }}\n </template>\n </td>\n </tr>\n\n <tr\n :class=\"[\n 'efficient-table-row',\n {\n 'row-striped': idx % 2 === 1,\n 'row-clickable': rowClickable || bulkSelectionEnabled,\n },\n rowClass ? rowClass(row) : undefined,\n ]\"\n :aria-rowindex=\"startIndex + idx + 2\"\n @mousedown=\"handleMouseDown($event, row.key)\"\n @click=\"handleRowClick($event, row.key)\"\n >\n <td v-if=\"bulkSelectionEnabled\" class=\"td-checkbox\" @click.stop>\n <input\n type=\"checkbox\"\n :checked=\"isRowSelected(row.key)\"\n @click=\"(e) => handleCheckboxChange(row.key, e.shiftKey)\"\n :aria-label=\"`Select row ${row.key}`\"\n :name=\"`row-${row.key}-checkbox`\"\n class=\"g-bulk-select-checkbox\"\n />\n </td>\n\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n :id=\"\n shouldAddCellId(col)\n ? `${tableId}-td-${row.key}-${String(col.key)}`\n : undefined\n \"\n :class=\"[\n col.editable ? 'editable-td' : '',\n hasCellChange(row, col) ? 'g-cell-changed' : '',\n hasCellError(row, col) ? 'g-cell-error' : '',\n typeof col.tdClass === 'function'\n ? col.tdClass(row)\n : col.tdClass,\n ]\"\n >\n <div v-if=\"col.editable\" class=\"editable-cell\">\n <span v-if=\"col.editable.prefix\" class=\"cell-prefix\">{{\n col.editable.prefix\n }}</span>\n <select\n v-if=\"col.editable.type === 'select'\"\n :value=\"row[col.key]\"\n @change=\"handleCellChange($event, row, col)\"\n :aria-labelledby=\"buildAriaLabelledBy(row, col)\"\n :aria-invalid=\"hasCellError(row, col)\"\n :name=\"`row-${row.key}-${String(col.key)}-select`\"\n class=\"editable-input editable-select\"\n >\n <option\n v-for=\"option in col.editable.options\"\n :key=\"option.value\"\n :value=\"option.value\"\n >\n {{ option.label }}\n </option>\n </select>\n <input\n v-else\n :value=\"row[col.key]\"\n v-bind=\"col.editable.inputAttributes\"\n @input=\"handleCellChange($event, row, col)\"\n :aria-labelledby=\"buildAriaLabelledBy(row, col)\"\n :aria-invalid=\"hasCellError(row, col)\"\n :aria-errormessage=\"hasCellError(row, col) ? `${tableId}-error-${row.key}-${String(col.key)}` : undefined\"\n :name=\"`row-${row.key}-${String(col.key)}-input`\"\n class=\"editable-input\"\n :style=\"{\n paddingLeft: col.editable.prefix\n ? '1.5rem'\n : undefined,\n paddingRight: col.editable.suffix\n ? '2rem'\n : undefined,\n }\"\n />\n <span v-if=\"col.editable.suffix\" class=\"cell-suffix\">{{\n col.editable.suffix\n }}</span>\n </div>\n <component v-else-if=\"col.display\" :is=\"col.display(row)\" />\n <template v-else>{{ row[col.key] }}</template>\n <div v-if=\"hasCellError(row, col)\" role=\"alert\" class=\"g-cell-error-message\" :id=\"`${tableId}-error-${row.key}-${String(col.key)}`\">\n {{ getCellError(row, col) }}\n </div>\n </td>\n </tr>\n </template>\n </tbody>\n</template>\n\n<style>\n.efficient-table-body {\n th,\n td {\n padding: 0.4rem 0.2rem;\n }\n\n td.editable-td {\n padding: 0;\n border-right: 1px solid var(--g-surface-300);\n border-bottom: 1px solid var(--g-surface-300);\n }\n\n /* Add left border to first editable cell or after non-editable cell */\n td.editable-td:first-child,\n td:not(.editable-td) + td.editable-td {\n border-left: 1px solid var(--g-surface-300);\n }\n\n /* Add top border to editable cells in first row */\n tr:first-child td.editable-td {\n border-top: 1px solid var(--g-surface-300);\n }\n\n .table-group-row {\n font-weight: 600;\n background: var(--g-surface-0);\n padding: 1rem;\n font-size: 1.25rem;\n border-bottom: 1px solid var(--g-accent-500);\n }\n\n .table-group-checkbox {\n width: 50px;\n background: var(--g-surface-0);\n }\n\n .td-checkbox {\n width: 50px;\n text-align: center;\n padding: 0.4rem;\n }\n\n .g-bulk-select-checkbox {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--g-primary-500);\n }\n}\n\n.row-striped {\n background-color: var(--g-surface-100);\n}\n\n.efficient-table-row.row-clickable {\n cursor: pointer;\n}\n\n.efficient-table-row.row-clickable:hover {\n background-color: var(--ilw-color--focus--background);\n}\n\n.efficient-table-row.row-clickable:hover td,\n.efficient-table-row.row-clickable:hover a,\n.efficient-table-row.row-clickable:hover span,\n.efficient-table-row.row-clickable:hover strong {\n text-decoration: underline;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .efficient-table-row.row-clickable,\n .efficient-table-row.row-clickable:hover {\n transition: none !important;\n }\n}\n\n.editable-cell {\n position: relative;\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.editable-input {\n width: 100%;\n padding: 0.4rem 0.5rem 0.4rem 0.75rem;\n border: none;\n border-radius: 0;\n font-size: 1rem;\n font-family: var(--il-font-sans);\n background: transparent;\n box-sizing: border-box;\n}\n\n.editable-input:focus {\n outline: 2px solid var(--g-primary-500);\n outline-offset: -2px;\n background: var(--g-surface-0);\n}\n\n.editable-select {\n cursor: pointer;\n}\n\n.cell-prefix,\n.cell-suffix {\n position: absolute;\n font-size: 1rem;\n color: var(--g-surface-600);\n pointer-events: none;\n top: 50%;\n transform: translateY(-50%);\n}\n\n.cell-prefix {\n left: 0.5rem;\n}\n\n.cell-suffix {\n right: 0.5rem;\n}\n\n/* Highlight cells that have been changed by the user */\n.g-cell-changed {\n background: rgba(101, 199, 255, 0.29);\n}\n\n/* Highlight cells with errors */\n.g-cell-error {\n background: var(--g-danger-100);\n position: relative;\n}\n\n.g-cell-error-message {\n background: var(--g-danger-500);\n color: var(--g-surface-0);\n font-size: 0.875rem;\n padding: 0.25rem 0.5rem;\n}\n\n.g-cell-error .editable-input[aria-invalid=\"true\"]:focus {\n outline-color: var(--g-danger-500);\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Internal component for rendering the table body.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\" generic=\"T extends TableRow, C extends TableColumn<T>\">\nimport { computed, ref, type VNode } from \"vue\";\nimport { TableColumn, TableRow } from \"./TableColumn.ts\";\nimport { UseTableChangesReturn } from \"../../compose/useTableChanges.ts\";\n\ntype Props = {\n data: T[];\n groupBy?: keyof T;\n columns: C[];\n groupRender?: (groupValue: any, row: T) => VNode;\n rowClickable?: boolean;\n rowClass?: (row: T) => string | string[] | undefined;\n startIndex: number;\n bulkSelectionEnabled?: boolean;\n selectedRows?: string[];\n tableId: string;\n changeTracker?: UseTableChangesReturn<T>;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n (e: \"row-click\", link: string): void;\n (e: \"toggle-row\", rowKey: string, shiftKey: boolean): void;\n (e: \"cell-change\", payload: { row: T; column: C; value: any }): void;\n}>();\n\n\nfunction handleMouseDown(event: MouseEvent, rowKey: string) {\n // Prevent text selection when shift-clicking for bulk selection\n // Only if bulk selection is enabled and shift is held and we're not on an input\n if (\n props.bulkSelectionEnabled &&\n event.shiftKey &&\n !(event.target as HTMLElement).closest(\"a,button,[tabindex],input\")\n ) {\n event.preventDefault();\n }\n}\n\nfunction handleRowClick(event: MouseEvent, rowKey: string) {\n if (!props.rowClickable && !props.bulkSelectionEnabled) {\n return; // Do nothing if rows are not clickable\n }\n // Only trigger if not clicking on a link or button directly\n if ((event.target as HTMLElement).closest(\"a,button,[tabindex],input\")) {\n return;\n }\n const row = (event.target as HTMLElement).closest(\n \"tr\",\n ) as HTMLTableRowElement;\n if (row) {\n if (props.bulkSelectionEnabled) {\n let checkbox = row.querySelector(\n \"input[type=checkbox]\",\n ) as HTMLInputElement | null;\n if (checkbox) {\n // Trigger the checkbox change with shift key info\n handleCheckboxChange(rowKey, event.shiftKey);\n }\n } else if (props.rowClickable) {\n const firstLink = row.querySelector(\"a[href]\");\n const link = firstLink?.getAttribute(\"href\");\n if (link) {\n emit(\"row-click\", link);\n }\n }\n }\n}\n\nfunction isRowSelected(rowKey: string): boolean {\n return props.selectedRows?.includes(rowKey) ?? false;\n}\n\nfunction handleCheckboxChange(rowKey: string, shiftKey: boolean = false) {\n emit(\"toggle-row\", rowKey, shiftKey);\n}\n\nfunction handleCellChange(event: Event, row: T, col: C) {\n const target = event.target as unknown as\n | HTMLInputElement\n | HTMLSelectElement;\n const value = target.value;\n emit(\"cell-change\", { row, column: col, value });\n}\n\nfunction buildAriaLabelledBy(row: T, col: C): string {\n const columnHeaderId = `${props.tableId}-th-${String(col.key)}`;\n\n // If labelKey is specified, add the label cell ID\n if (col.editable?.labelKey) {\n const labelCellId = `${props.tableId}-td-${row.key}-${col.editable.labelKey}`;\n return `${labelCellId} ${columnHeaderId} `;\n }\n\n return columnHeaderId;\n}\n\nconst labelCellColumn = computed(() => {\n for (const col of props.columns) {\n if (col.editable?.labelKey) {\n return col.editable.labelKey;\n }\n }\n return undefined;\n }\n);\n\nfunction shouldAddCellId(col: C): boolean {\n // Check if this column is used as a label for any editable column\n return col.key === labelCellColumn.value;\n}\n\nfunction hasCellChange(row: T, col: C): boolean {\n if (!props.changeTracker) return false;\n return props.changeTracker.hasChange(row.key, col.key);\n}\n\nfunction hasCellError(row: T, col: C): boolean {\n if (!props.changeTracker) return false;\n return props.changeTracker.hasError(row.key, col.key);\n}\nfunction getCellError(row: T, col: C): string | undefined {\n if (!props.changeTracker) return undefined;\n return props.changeTracker.getError(row.key, col.key);\n}\n\n</script>\n\n<template>\n <tbody ref=\"tableBodyRef\" class=\"efficient-table-body\">\n <template v-for=\"(row, idx) in data\" :key=\"row.key\">\n <tr\n v-if=\"\n groupBy &&\n (idx === 0 || row[groupBy] !== data[idx - 1][groupBy])\n \"\n :aria-rowindex=\"startIndex + idx + 2\"\n >\n <td\n v-if=\"bulkSelectionEnabled\"\n class=\"table-group-checkbox\"\n ></td>\n <td :colspan=\"columns.length\" class=\"table-group-row\">\n <template v-if=\"groupRender\">\n <component :is=\"groupRender(row[groupBy], row)\" />\n </template>\n <template v-else>\n {{ row[groupBy] }}\n </template>\n </td>\n </tr>\n\n <tr\n :class=\"[\n 'efficient-table-row',\n {\n 'row-striped': idx % 2 === 1,\n 'row-clickable': rowClickable || bulkSelectionEnabled,\n },\n rowClass ? rowClass(row) : undefined,\n ]\"\n :aria-rowindex=\"startIndex + idx + 2\"\n @mousedown=\"handleMouseDown($event, row.key)\"\n @click=\"handleRowClick($event, row.key)\"\n >\n <td v-if=\"bulkSelectionEnabled\" class=\"td-checkbox\" @click.stop>\n <input\n type=\"checkbox\"\n :checked=\"isRowSelected(row.key)\"\n @click=\"(e) => handleCheckboxChange(row.key, e.shiftKey)\"\n :aria-label=\"`Select row ${row.key}`\"\n :name=\"`row-${row.key}-checkbox`\"\n class=\"g-bulk-select-checkbox\"\n />\n </td>\n\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n :id=\"\n shouldAddCellId(col)\n ? `${tableId}-td-${row.key}-${String(col.key)}`\n : undefined\n \"\n :class=\"[\n col.editable ? 'editable-td' : '',\n hasCellChange(row, col) ? 'g-cell-changed' : '',\n hasCellError(row, col) ? 'g-cell-error' : '',\n typeof col.tdClass === 'function'\n ? col.tdClass(row)\n : col.tdClass,\n ]\"\n >\n <div v-if=\"col.editable\" class=\"editable-cell\">\n <span v-if=\"col.editable.prefix\" class=\"cell-prefix\">{{\n col.editable.prefix\n }}</span>\n <select\n v-if=\"col.editable.type === 'select'\"\n :value=\"row[col.key]\"\n @change=\"handleCellChange($event, row, col)\"\n :aria-labelledby=\"buildAriaLabelledBy(row, col)\"\n :aria-invalid=\"hasCellError(row, col)\"\n :name=\"`row-${row.key}-${String(col.key)}-select`\"\n class=\"editable-input editable-select\"\n >\n <option\n v-for=\"option in col.editable.options\"\n :key=\"option.value\"\n :value=\"option.value\"\n >\n {{ option.label }}\n </option>\n </select>\n <input\n v-else\n :value=\"row[col.key]\"\n v-bind=\"col.editable.inputAttributes\"\n @input=\"handleCellChange($event, row, col)\"\n :aria-labelledby=\"buildAriaLabelledBy(row, col)\"\n :aria-invalid=\"hasCellError(row, col)\"\n :aria-errormessage=\"hasCellError(row, col) ? `${tableId}-error-${row.key}-${String(col.key)}` : undefined\"\n :name=\"`row-${row.key}-${String(col.key)}-input`\"\n class=\"editable-input\"\n :style=\"{\n paddingLeft: col.editable.prefix\n ? '1.5rem'\n : undefined,\n paddingRight: col.editable.suffix\n ? '2rem'\n : undefined,\n }\"\n />\n <span v-if=\"col.editable.suffix\" class=\"cell-suffix\">{{\n col.editable.suffix\n }}</span>\n </div>\n <component v-else-if=\"col.display\" :is=\"col.display(row)\" />\n <template v-else>{{ row[col.key] }}</template>\n <div v-if=\"hasCellError(row, col)\" role=\"alert\" class=\"g-cell-error-message\" :id=\"`${tableId}-error-${row.key}-${String(col.key)}`\">\n {{ getCellError(row, col) }}\n </div>\n </td>\n </tr>\n </template>\n </tbody>\n</template>\n\n<style>\n.efficient-table-body {\n th,\n td {\n padding: 0.4rem 0.2rem;\n }\n\n td.editable-td {\n padding: 0;\n border-right: 1px solid var(--g-surface-300);\n border-bottom: 1px solid var(--g-surface-300);\n }\n\n /* Add left border to first editable cell or after non-editable cell */\n td.editable-td:first-child,\n td:not(.editable-td) + td.editable-td {\n border-left: 1px solid var(--g-surface-300);\n }\n\n /* Add top border to editable cells in first row */\n tr:first-child td.editable-td {\n border-top: 1px solid var(--g-surface-300);\n }\n\n .table-group-row {\n font-weight: 600;\n background: var(--g-surface-0);\n padding: 1rem;\n font-size: 1.25rem;\n border-bottom: 1px solid var(--g-accent-500);\n }\n\n .table-group-checkbox {\n width: 50px;\n background: var(--g-surface-0);\n }\n\n .td-checkbox {\n width: 50px;\n text-align: center;\n padding: 0.4rem;\n }\n\n .g-bulk-select-checkbox {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--g-primary-500);\n }\n}\n\n.row-striped {\n background-color: var(--g-surface-100);\n}\n\n.efficient-table-row.row-clickable {\n cursor: pointer;\n}\n\n.efficient-table-row.row-clickable:hover {\n background-color: var(--ilw-color--focus--background);\n}\n\n.efficient-table-row.row-clickable:hover td,\n.efficient-table-row.row-clickable:hover a,\n.efficient-table-row.row-clickable:hover span,\n.efficient-table-row.row-clickable:hover strong {\n text-decoration: underline;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .efficient-table-row.row-clickable,\n .efficient-table-row.row-clickable:hover {\n transition: none !important;\n }\n}\n\n.editable-cell {\n position: relative;\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.editable-input {\n width: 100%;\n padding: 0.4rem 0.5rem 0.4rem 0.75rem;\n border: none;\n border-radius: 0;\n font-size: 1rem;\n font-family: var(--il-font-sans);\n background: transparent;\n box-sizing: border-box;\n}\n\n.editable-input:focus {\n outline: 2px solid var(--g-primary-500);\n outline-offset: -2px;\n background: var(--g-surface-0);\n}\n\n.editable-select {\n cursor: pointer;\n}\n\n.cell-prefix,\n.cell-suffix {\n position: absolute;\n font-size: 1rem;\n color: var(--g-surface-600);\n pointer-events: none;\n top: 50%;\n transform: translateY(-50%);\n}\n\n.cell-prefix {\n left: 0.5rem;\n}\n\n.cell-suffix {\n right: 0.5rem;\n}\n\n/* Highlight cells that have been changed by the user */\n.g-cell-changed {\n background: rgba(101, 199, 255, 0.29);\n}\n\n/* Highlight cells with errors */\n.g-cell-error {\n background: var(--g-danger-100);\n position: relative;\n}\n\n.g-cell-error-message {\n background: var(--g-danger-500);\n color: var(--g-surface-0);\n font-size: 0.875rem;\n padding: 0.25rem 0.5rem;\n}\n\n.g-cell-error .editable-input[aria-invalid=\"true\"]:focus {\n outline-color: var(--g-danger-500);\n}\n</style>\n","import {\n computed,\n Reactive,\n reactive,\n ref,\n type Ref,\n toRaw,\n toValue,\n unref,\n watch,\n} from \"vue\";\n\nexport type FilterLocationQueryValueRaw = string | number;\n\n/**\n * Query representation for filtering, compatible with vue-router\n */\nexport type FilterLocationQuery = {\n [p: string]:\n | string\n | null\n | number\n | undefined\n | FilterLocationQueryValueRaw[];\n};\n\nexport interface FilteringOptions {\n syncWith?: Ref<{\n [p: string]: string | null | (string | null)[] | undefined;\n }>;\n}\n\n/**\n * Represents a type that defines a set of filters for a given record type.\n * The keys are based on the record, and the values are possible values\n * for a filter.\n */\nexport type FiltersForRecord<\n T extends object,\n F extends { [K in keyof T]?: any },\n> = {\n [K in keyof T]?: T[K] extends string | number | boolean | undefined | null\n ? T[K] | string | string[]\n : T[K] extends string[] | number[]\n ? T[K][]\n : never;\n};\n\n/**\n * Represents the return type of a composition function used for handling\n * filtering logic in a data structure.\n */\nexport interface UseFilteringReturn<\n T extends Record<string, any> = Record<string, any>,\n F extends { [K in keyof T]?: any } = Record<keyof T, any>,\n> {\n filters: Reactive<F>;\n isFiltered: Ref<boolean>;\n clearFilters: () => void;\n filteredColumns: Ref<Partial<Record<keyof T, boolean>>>;\n}\n\n/**\n * Returns the value if it's not empty, or undefined if it's empty.\n */\nexport function emptyAsUndefined<\n T extends\n | string\n | number\n | boolean\n | string[]\n | number[]\n | undefined\n | null,\n>(value: T) {\n if (Array.isArray(value)) {\n if (value.length === 0) {\n return undefined;\n }\n }\n if (value === null || value === false || value === \"\") {\n return undefined;\n }\n return value;\n}\n\nexport function filterOmitEmpty<T extends object>(value: T): Partial<T> {\n return Object.fromEntries(\n Object.entries(value).filter(([k, v]) => {\n return v && (!Array.isArray(v) || v.length > 0);\n }),\n ) as Partial<T>;\n}\n\n/**\n * Return a value as an array if it's not already one, or\n * undefined if it's undefined.\n */\nexport function asArray<T>(value: T | T[]): NonNullable<T>[] | undefined {\n if (value === undefined || value === null) {\n return undefined;\n }\n if (Array.isArray(value)) {\n // Exclude null and undefined from array\n return value.filter(\n (v) => v !== null && v !== undefined,\n ) as NonNullable<T>[];\n }\n return [value];\n}\n\n/**\n * Converts filter criteria into a format suitable for use as a query object\n * in vue-router.\n */\nexport function filterAsQuery<\n T extends Record<string, any>,\n F extends { [K in keyof T]?: any } = Record<keyof T, any>,\n>(filters: FiltersForRecord<T, F>): FilterLocationQuery {\n let query: FilterLocationQuery = {};\n for (let [key, value] of Object.entries(toRaw(filters))) {\n if (Array.isArray(value)) {\n if (value.length > 0) {\n query[key] = value;\n }\n } else if (value === true) {\n query[key] = \"true\";\n } else {\n query[key] = value || undefined;\n }\n }\n return query;\n}\n\n/**\n * Converts an object of filters into a query parameters object for API calls.\n *\n * Transforms the values into strings or arrays of strings. Excludes fields with undefined,\n * null, empty string, or false values. Supports single values and arrays.\n */\nexport function filtersToQueryParams<T extends Record<string, any>>(\n filters: T,\n): Record<keyof T, string | string[]> {\n const query: Record<string, string | string[]> = {};\n Object.keys(filters).forEach((key) => {\n const value = filters[key];\n if (\n value !== undefined &&\n value !== null &&\n value !== \"\" &&\n value !== false\n ) {\n if (Array.isArray(value)) {\n if (value.length > 0) {\n query[key] = value.map((v) => String(v));\n }\n } else {\n query[key] = String(value);\n }\n }\n });\n return query as Record<keyof T, string | string[]>;\n}\n\n/**\n * Provides a mechanism to manage and synchronize filterable data with given filters and options.\n *\n * @param filters An object that defines the filters applicable to the data record.\n * @param options Configuration options for filtering, such as synchronization.\n * @return Returns an object that can be used with GTable.\n */\nexport function useFiltering<\n T extends Record<string, any> = Record<string, any>,\n F extends { [K in keyof T]?: any } = Record<keyof T, any>,\n>(filters: F, options: FilteringOptions = {}): UseFilteringReturn<T, F> {\n const values = reactive<T>(\n Object.fromEntries(\n Object.entries(filters).map(([key, val]) => [key, val]),\n ) as any,\n );\n const syncWith = options.syncWith;\n\n if (syncWith) {\n if (syncWith.value) {\n const queryParams = toValue(syncWith);\n Object.keys(filters).forEach((key) => {\n if (queryParams[key] !== undefined) {\n // Handle arrays as a comma-separated string\n const val = queryParams[key];\n if (typeof val === \"string\") {\n if (val.includes(\",\")) {\n values[key] = val.split(\",\");\n } else {\n values[key] = val;\n }\n }\n }\n });\n }\n\n watch(\n values,\n (newValues) => {\n syncWith.value = filtersToQueryParams(newValues);\n },\n { deep: true },\n );\n }\n\n const isFiltered = computed(() => {\n for (const key of Object.keys(filters)) {\n if (!!emptyAsUndefined(values[key])) {\n return true;\n }\n }\n return false;\n });\n\n const clearFilters = () => {\n Object.keys(values).forEach((key) => {\n values[key] = undefined;\n });\n };\n\n const filteredColumns = computed(() => {\n const result: Record<string, boolean> = {};\n for (const key of Object.keys(filters)) {\n result[key] = !!emptyAsUndefined(values[key]);\n }\n return result as Record<keyof T, boolean>;\n });\n\n return {\n filters: values as any,\n isFiltered,\n clearFilters,\n filteredColumns,\n };\n}\n","<script lang=\"ts\">\n/**\n * A data table component with support for grouping, sorting, filtering, and pagination.\n *\n * A heavy focus has been on performance. The table body doesn't use any\n * Vue components, it's pure render functions. We've used it with\n * 4000 rows and 14 columns loaded without issues.\n *\n * This is a bit complicated to use, so an example has been omitted here.\n * Instead, look at the source for this demo: [GTable Demo Source](https://github.com/graduatecollege/grad-vue/blob/main/demo/components/demo/GTableDemo.vue).\n *\n * Here are some of the key points.\n *\n * Table content is provided with:\n * - `columns` configuration using the `TableColumn` type.\n * - At minimum the configuration must include `key` for which field of the data\n * objects to use, and `label` for the column header.\n * - `sortable: true` makes the column sortable.\n * - `filter` can be used to provide a `TableColumnFilter` configuration.\n * - `display` accepts a custom render function for the column data.\n * - `trClass` and `tdClass` can be used to provide custom classes for table rows and cells.\n * - `data` array with objects containing fields for the columns.\n *\n * Rows can be made clickable with `row-clickable`. In this case, one of the\n * cells must contain a link. Clicking a row will emit a `row-click` event\n * with the link `href` from the first link in the row.\n *\n * Grouping can be enabled by passing a column key to `groupBy`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\" generic=\"T extends TableRow, C extends TableColumn<T>\">\nimport GTableBody from \"./table/GTableBody.vue\";\nimport GPopover from \"./GPopover.vue\";\nimport { TableColumn, TableRow } from \"./table/TableColumn.ts\";\nimport {\n computed,\n onMounted,\n ref,\n toRaw,\n useId,\n useSlots,\n useTemplateRef,\n VNode,\n watch,\n} from \"vue\";\nimport GSelect from \"./GSelect.vue\";\nimport { useFiltering, UseFilteringReturn } from \"../compose/useFiltering.ts\";\nimport {\n CellChangePayload,\n UseTableChangesReturn,\n} from \"../compose/useTableChanges.ts\";\nimport GButton from \"./GButton.vue\";\n\nexport interface BulkAction {\n /**\n * Action identifier\n */\n id: string;\n /**\n * Action label\n */\n label: string;\n /**\n * Action theme/color\n */\n theme?: \"primary\" | \"secondary\" | \"accent\" | \"danger\";\n}\n\ntype Props = {\n /**\n * Accessible label\n * @demo Colleges\n */\n label: string;\n /**\n * The data to display in the table\n *\n * The data should be an array of objects, each representing a row in the table.\n * Each object should have a unique `key` property that can be used to identify the row.\n */\n data: T[];\n /**\n * The columns to display in the table\n *\n * Each column's key needs to match the key of a property in the data objects,\n * which determines the data to display in that column by default. You can also\n * provide a custom display function to customize the data display.\n */\n columns: C[];\n /**\n * Result count for all of the possible results (not just the current page)\n *\n * This is shown in the toolbar.\n */\n resultCount?: number;\n /**\n * A column key to group the data by\n *\n * If provided, the `groupRender` render function will be used to render the group header.\n */\n groupBy?: keyof T;\n /**\n * A render function to customize the display of the group header.\n *\n * @param groupValue The value of the group key for the current row\n * @param row The row object for the first row in the group\n */\n groupRender?: (groupValue: any, row: T) => VNode;\n /**\n * Filtering object created with useFiltering()\n */\n filtering?: UseFilteringReturn<any>;\n /**\n * Make the table rows clickable\n */\n rowClickable?: boolean;\n /**\n * A function to customize the classes applied to table rows\n * @param row The row object\n */\n rowClass?: (row: T) => string | string[] | undefined;\n /**\n * The starting index for this page\n *\n * This is used for the ARIA rowindex attribute, and is VERY important\n * to not get wrong.\n */\n startIndex: number;\n /**\n * Enable bulk selection with checkboxes\n * @demo\n */\n bulkSelectionEnabled?: boolean;\n /**\n * Array of actions to show in the sticky toolbar when rows are selected\n */\n bulkActions?: BulkAction[];\n\n /**\n * Optional change tracker for editable tables.\n * Pass a composable from useTableChanges() to track user edits.\n */\n changeTracker?: UseTableChangesReturn<T>;\n\n /**\n * Explicitly show the pagination bar even if the slot is empty\n * @demo\n */\n showPagination?: boolean;\n};\n\nconst sortField = defineModel<keyof T>(\"sortField\");\nconst sortOrder = defineModel<1 | -1>(\"sortOrder\");\nconst filter = defineModel<Partial<Record<keyof T, any>>>(\"filter\", {\n default: () => ({}),\n});\nconst selectedRows = defineModel<string[]>(\"selectedRows\", {\n default: () => [],\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n bulkSelectionEnabled: false,\n bulkActions: () => [],\n showPagination: false,\n});\n\nconst emit = defineEmits<{\n (e: \"row-click\", link: string): void;\n (e: \"bulk-action\", actionId: string, selectedKeys: string[]): void;\n (e: \"cell-change\", payload: CellChangePayload<T>): void;\n}>();\n\nfunction onSort(col: TableColumn<T>) {\n if (!col.sortable) {\n return;\n }\n if (sortField.value === col.key) {\n if (sortOrder.value === 1) {\n sortOrder.value = -1;\n } else if (sortOrder.value === -1) {\n sortField.value = undefined as any;\n sortOrder.value = 1;\n }\n } else {\n sortField.value = col.key;\n sortOrder.value = 1;\n }\n}\n\nlet filtering: UseFilteringReturn<any> = props.filtering!;\n\nif (!filtering) {\n filtering = useFiltering({}) as any;\n}\n\nconst { filters, filteredColumns, isFiltered, clearFilters } = filtering;\n\n// Bulk selection logic\nconst allRowKeys = computed(() => props.data.map((row) => row.key));\nconst selectedRowsOnPage = computed(() => {\n return selectedRows.value.filter((key) => allRowKeys.value.includes(key));\n});\nconst allSelected = computed(() => {\n if (!props.bulkSelectionEnabled || props.data.length === 0) {\n return false;\n }\n return selectedRowsOnPage.value.length === allRowKeys.value.length;\n});\nconst someSelected = computed(() => {\n if (!props.bulkSelectionEnabled || props.data.length === 0) {\n return false;\n }\n return (\n selectedRowsOnPage.value.length > 0 &&\n selectedRowsOnPage.value.length < allRowKeys.value.length\n );\n});\n\nconst lastClickedRowKey = ref<string | null>(null);\n\nfunction toggleAllRows() {\n if (allSelected.value) {\n // Deselect all rows on current page\n selectedRows.value = selectedRows.value.filter(\n (key) => !allRowKeys.value.includes(key),\n );\n } else {\n // Select all rows on current page\n const newSelected = new Set(selectedRows.value);\n allRowKeys.value.forEach((key) => newSelected.add(key));\n selectedRows.value = Array.from(newSelected);\n }\n}\n\nfunction toggleRow(rowKey: string, shiftKey: boolean = false) {\n if (shiftKey && lastClickedRowKey.value) {\n // Handle shift-click range selection\n const lastIndex = allRowKeys.value.indexOf(lastClickedRowKey.value);\n const currentIndex = allRowKeys.value.indexOf(rowKey);\n\n if (lastIndex !== -1 && currentIndex !== -1) {\n const start = Math.min(lastIndex, currentIndex);\n const end = Math.max(lastIndex, currentIndex);\n const rowsInRange = allRowKeys.value.slice(start, end + 1);\n\n // Select all rows in the range\n const newSelected = new Set(selectedRows.value);\n rowsInRange.forEach((key) => newSelected.add(key));\n selectedRows.value = Array.from(newSelected);\n }\n } else {\n // Normal toggle behavior\n if (selectedRows.value.includes(rowKey)) {\n selectedRows.value = selectedRows.value.filter(\n (key) => key !== rowKey,\n );\n } else {\n selectedRows.value = [...selectedRows.value, rowKey];\n }\n }\n\n // Update last clicked row\n lastClickedRowKey.value = rowKey;\n}\n\nfunction clickRow(link: string) {\n emit(\"row-click\", link);\n}\n\nfunction handleBulkAction(actionId: string) {\n emit(\"bulk-action\", actionId, selectedRows.value);\n}\n\nfunction handleCellChange(change: { row: T; column: C; value: any }) {\n // Update the reactive data\n // Convert the value to the appropriate type based on input attributes\n let convertedValue: any = change.value;\n const columnKey = change.column.key;\n const previousValue = toRaw(change.row[columnKey]);\n if (change.column.editable?.inputAttributes?.type === \"number\") {\n convertedValue = change.value === \"\" ? null : Number(change.value);\n }\n change.row[columnKey] = convertedValue;\n\n const payload: CellChangePayload<T> = {\n row: change.row,\n column: change.column,\n value: convertedValue,\n previousValue,\n };\n\n emit(\"cell-change\", payload);\n}\n\nconst id = useId();\nconst slots = useSlots();\n\nconst shouldShowPagination = computed(() => {\n // Show if explicitly requested via prop\n if (props.showPagination) {\n return true;\n }\n // Show if the pagination slot has content\n return !!slots.pagination;\n});\n\nconst shouldShowControls = computed(() => {\n // Show if filters are active (clear filters button is visible)\n if (isFiltered.value) {\n return true;\n }\n // Show if pagination should be shown\n if (shouldShowPagination.value) {\n return true;\n }\n // Otherwise hide the entire controls bar\n return false;\n});\n\nonMounted(() => {\n if (props.rowClickable && props.bulkSelectionEnabled) {\n console.warn(\n \"GTable: rowClickable and bulkSelectionEnabled cannot be used together. rowClickable will be ignored.\",\n );\n }\n for (const col of props.columns) {\n if (col.editable && col.display) {\n console.warn(\n `GTable: Column \"${String(col.key)}\" has both 'editable' and 'display' configured. 'display' will be ignored.`,\n );\n }\n if (col.filter && col.filter.type === \"multi-select\") {\n if (!Array.isArray(filter.value[col.key])) {\n let val = filter.value[col.key];\n filter.value[col.key] = val ? [val] : [];\n }\n }\n }\n});\n\nwatch(\n () => props.columns,\n (newColumns) => {\n for (const col of newColumns) {\n if (col.filter && col.filter.type === \"multi-select\") {\n if (!Array.isArray(filter.value[col.key])) {\n let val = filter.value[col.key];\n filter.value[col.key] = val ? [val] : [];\n }\n }\n }\n },\n { immediate: true },\n);\n</script>\n\n<template>\n <div class=\"g-table-outer-wrap\">\n <div v-if=\"shouldShowControls\" class=\"g-table-controls\">\n <div class=\"g-clear-filters-wrap\">\n <GButton\n v-if=\"isFiltered\"\n outlined\n size=\"small\"\n class=\"clear-filters\"\n @click=\"clearFilters\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n height=\"1em\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n <span class=\"g-clear-filters-text\"> Clear Filters </span>\n </GButton>\n </div>\n <div v-if=\"shouldShowPagination\" class=\"pagination\">\n <slot name=\"pagination\"></slot>\n </div>\n <span class=\"g-result-count\"\n >{{ props.resultCount || data.length }} results</span\n >\n </div>\n <table\n class=\"g-table\"\n ref=\"tableRef\"\n :aria-label=\"label\"\n :aria-rowcount=\"props.resultCount || data.length\"\n >\n <thead class=\"g-table-head\">\n <tr aria-rowindex=\"1\">\n <th\n v-if=\"bulkSelectionEnabled\"\n scope=\"col\"\n class=\"g-th g-th-checkbox\"\n >\n <input\n type=\"checkbox\"\n :checked=\"allSelected\"\n :indeterminate=\"someSelected\"\n @change=\"toggleAllRows\"\n :aria-label=\"\n allSelected\n ? 'Deselect all rows'\n : 'Select all rows'\n \"\n class=\"g-bulk-select-checkbox\"\n />\n </th>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n :id=\"`${id}-th-${String(col.key)}`\"\n :aria-sort=\"\n sortField === col.key\n ? sortOrder === 1\n ? 'ascending'\n : 'descending'\n : 'none'\n \"\n :class=\"[\n 'g-th',\n { sorted: sortField === col.key },\n { filtered: filteredColumns[col.key] },\n ]\"\n scope=\"col\"\n >\n <div class=\"th-inner\">\n <button\n v-if=\"col.sortable\"\n type=\"button\"\n class=\"g-column-head\"\n @click=\"onSort(col)\"\n >\n {{ col.label }}\n <span\n v-if=\"sortField === col.key\"\n class=\"sort-indicator\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.5em\"\n role=\"img\"\n :aria-label=\"\n sortOrder === 1\n ? 'Sorted ascending'\n : 'Sorted descending'\n \"\n :style=\"{\n transform: `rotate(${sortOrder === 1 ? 0 : 180}deg)`,\n }\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M300.3 199.2C312.9 188.9 331.4 189.7 343.1 201.4L471.1 329.4C480.3 338.6 483 352.3 478 364.3C473 376.3 461.4 384 448.5 384L192.5 384C179.6 384 167.9 376.2 162.9 364.2C157.9 352.2 160.7 338.5 169.9 329.4L297.9 201.4L300.3 199.2z\"\n />\n </svg>\n </span>\n </button>\n <span v-else class=\"g-column-head\">{{\n col.label\n }}</span>\n <GPopover v-if=\"col.filter\">\n <template #trigger=\"{ toggle }\">\n <button\n @click.stop=\"toggle\"\n :aria-label=\"\n filteredColumns[col.key]\n ? 'Column Filtered'\n : 'Filter Column'\n \"\n class=\"g-filter-btn\"\n :class=\"{\n 'g-active':\n filteredColumns[col.key],\n }\"\n type=\"button\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.5em\"\n aria-hidden=\"true\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M96 128C83.1 128 71.4 135.8 66.4 147.8C61.4 159.8 64.2 173.5 73.4 182.6L256 365.3L256 480C256 488.5 259.4 496.6 265.4 502.6L329.4 566.6C338.6 575.8 352.3 578.5 364.3 573.5C376.3 568.5 384 556.9 384 544L384 365.3L566.6 182.7C575.8 173.5 578.5 159.8 573.5 147.8C568.5 135.8 556.9 128 544 128L96 128z\"\n />\n </svg>\n </button>\n </template>\n <GSelect\n v-if=\"col.filter.type === 'select'\"\n v-model=\"filter[col.key]\"\n :options=\"col.filter.options\"\n class=\"g-filter-select\"\n label=\"Filter select\"\n searchable\n clear-button\n />\n <div v-else-if=\"col.filter.type === 'toggle'\">\n <div class=\"g-filter-toggle\">\n <input\n type=\"checkbox\"\n v-model=\"filter[col.key]\"\n :id=\"`${id}-filter-${String(col.key)}`\"\n :aria-describedby=\"\n col.filter.description\n ? `${id}-filter-description-${String(col.key)}`\n : undefined\n \"\n />\n <label\n :for=\"`${id}-filter-${String(col.key)}`\"\n >{{ col.filter.label }}</label\n >\n <span\n class=\"g-filter-description\"\n v-if=\"col.filter.description\"\n :id=\"`${id}-filter-description-${String(col.key)}`\"\n >\n {{ col.filter.description }}\n </span>\n </div>\n </div>\n <fieldset\n v-else-if=\"\n col.filter.type === 'multi-select'\n \"\n class=\"g-multi-select\"\n >\n <legend class=\"g-multi-select-legend\">\n Include values\n </legend>\n <div\n v-for=\"opt in col.filter.options\"\n :key=\"opt.value\"\n >\n <input\n type=\"checkbox\"\n v-model=\"filter[col.key]\"\n :id=\"`filter-${String(col.key)}-${opt.value}`\"\n :value=\"opt.value\"\n name=\"filter-multiselect\"\n />\n <label\n :for=\"`filter-${String(col.key)}-${opt.value}`\"\n >{{ opt.label }}</label\n >\n </div>\n <GButton\n class=\"clear-multiselect-btn\"\n theme=\"accent\"\n size=\"small\"\n @click=\"filter[col.key] = []\"\n v-if=\"\n filter[col.key] &&\n filter[col.key].length\n \"\n >\n Clear\n </GButton>\n </fieldset>\n </GPopover>\n </div>\n </th>\n </tr>\n </thead>\n <!-- @vue-generic {T, C} -->\n <GTableBody\n :data=\"data\"\n :columns=\"columns\"\n :group-by=\"groupBy\"\n :group-render=\"groupRender\"\n :row-clickable=\"rowClickable\"\n :row-class=\"rowClass as any\"\n :start-index=\"startIndex\"\n :bulk-selection-enabled=\"bulkSelectionEnabled\"\n :selected-rows=\"selectedRows\"\n :table-id=\"id\"\n :change-tracker=\"changeTracker\"\n @row-click=\"clickRow\"\n @toggle-row=\"toggleRow\"\n @cell-change=\"handleCellChange\"\n />\n </table>\n <div\n v-if=\"bulkSelectionEnabled && selectedRows.length > 0\"\n class=\"g-bulk-actions-toolbar\"\n >\n <span class=\"g-selected-count\"\n >{{ selectedRows.length }} row{{\n selectedRows.length === 1 ? \"\" : \"s\"\n }}\n selected</span\n >\n <ul class=\"g-bulk-actions\">\n <li v-for=\"action in bulkActions\" :key=\"action.id\">\n <GButton\n :theme=\"action.theme || 'accent'\"\n @click=\"handleBulkAction(action.id)\"\n size=\"small\"\n >\n {{ action.label }} {{ selectedRows.length }} row{{\n selectedRows.length === 1 ? \"\" : \"s\"\n }}\n </GButton>\n </li>\n </ul>\n </div>\n </div>\n</template>\n\n<style>\ng-table {\n display: block;\n}\n.g-table-outer-wrap {\n}\n\n.g-table-controls {\n height: 40px;\n position: sticky;\n display: flex;\n top: 0;\n left: 0;\n padding: 2px 6px;\n}\n\n.g-table-head {\n background: var(--g-surface-0);\n position: sticky;\n top: 40px;\n z-index: 1;\n}\n\n.g-th {\n text-align: left;\n padding: 0.5rem 0.2rem;\n border: 0;\n border-bottom: 2px solid var(--g-surface-900);\n background: var(--g-surface-0);\n\n &.sorted {\n color: var(--ilw-color--link-hover);\n }\n\n &.filtered {\n .g-filter-btn {\n color: var(--ilw-color--link-hover);\n }\n }\n\n .th-inner {\n display: flex;\n align-items: center;\n }\n}\n\n.g-column-head {\n color: currentColor;\n position: relative;\n border: none;\n font-weight: 700;\n font-family: var(--il-font-sans);\n font-size: 1rem;\n line-height: 1.3;\n white-space: nowrap;\n padding-left: 4px;\n background: var(--g-surface-0);\n\n .sort-indicator {\n position: absolute;\n bottom: -1.1em;\n left: calc(50% - 0.7em);\n }\n}\n\nth:first-of-type .g-column-head {\n padding-left: 0;\n}\n\nbutton.g-column-head {\n cursor: pointer;\n height: 2rem;\n}\n\nbutton.g-column-head:hover {\n text-decoration: underline;\n color: var(--ilw-color--link-hover);\n}\n\n.g-table {\n border-spacing: 0;\n min-width: 100%;\n}\n\n.g-filter-btn {\n border: none;\n background: transparent;\n border-radius: 50%;\n width: 2rem;\n height: 2rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n &.g-active {\n border: 2px solid var(--ilw-color--link-hover);\n }\n}\n\n.g-clear-filters-text {\n white-space: nowrap;\n}\n\n@media screen and (max-width: 600px) {\n .g-clear-filters-text {\n opacity: 0;\n width: 1px;\n height: 1px;\n overflow: hidden;\n }\n}\n\n.g-filter-select {\n min-width: 200px;\n}\n\n.g-table-controls {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1.5rem;\n padding: 0.2rem 1rem;\n background: var(--g-surface-150);\n\n .g-result-count {\n font-size: 1rem;\n line-height: 1.2;\n }\n}\n\n.g-multi-select {\n border: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n\n .clear-multiselect-btn {\n margin-top: 0.5rem;\n }\n\n legend {\n font-size: 1.125rem;\n font-weight: bold;\n margin-bottom: 0.5rem;\n }\n\n div {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n\n &:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n }\n }\n\n input {\n width: 24px;\n height: 24px;\n accent-color: var(--g-primary-500);\n display: block;\n }\n\n label {\n font-size: 1.125rem;\n flex: 1;\n }\n}\n\n.g-multi-select-legend {\n margin: 0;\n padding: 0;\n font-size: 1rem;\n line-height: 1.2;\n}\n\n.g-filter-toggle {\n display: grid;\n grid-template-areas:\n \"label input\"\n \"description description\";\n\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: 0.5rem;\n\n input {\n width: 24px;\n height: 24px;\n }\n\n label {\n font-size: 1.125rem;\n font-weight: bold;\n }\n\n .g-filter-description {\n grid-area: description;\n }\n}\n\n.g-clear-filters-wrap,\n.g-result-count {\n}\n\n/* Bulk selection styles */\n.g-th-checkbox {\n width: 50px;\n text-align: center;\n}\n\n.g-bulk-select-checkbox {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--g-primary-500);\n}\n\n.g-bulk-actions-toolbar {\n position: sticky;\n bottom: 0;\n left: 0;\n right: 0;\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n padding: 0.75rem 1rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);\n z-index: 1;\n\n ul {\n display: flex;\n gap: 1rem;\n list-style: none;\n padding: 0;\n margin: 0;\n }\n\n li {\n margin: 0;\n }\n}\n\n.g-selected-count {\n font-weight: 600;\n font-size: 1rem;\n}\n\n.g-bulk-actions {\n display: flex;\n gap: 0.5rem;\n align-items: center;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A data table component with support for grouping, sorting, filtering, and pagination.\n *\n * A heavy focus has been on performance. The table body doesn't use any\n * Vue components, it's pure render functions. We've used it with\n * 4000 rows and 14 columns loaded without issues.\n *\n * This is a bit complicated to use, so an example has been omitted here.\n * Instead, look at the source for this demo: [GTable Demo Source](https://github.com/graduatecollege/grad-vue/blob/main/demo/components/demo/GTableDemo.vue).\n *\n * Here are some of the key points.\n *\n * Table content is provided with:\n * - `columns` configuration using the `TableColumn` type.\n * - At minimum the configuration must include `key` for which field of the data\n * objects to use, and `label` for the column header.\n * - `sortable: true` makes the column sortable.\n * - `filter` can be used to provide a `TableColumnFilter` configuration.\n * - `display` accepts a custom render function for the column data.\n * - `trClass` and `tdClass` can be used to provide custom classes for table rows and cells.\n * - `data` array with objects containing fields for the columns.\n *\n * Rows can be made clickable with `row-clickable`. In this case, one of the\n * cells must contain a link. Clicking a row will emit a `row-click` event\n * with the link `href` from the first link in the row.\n *\n * Grouping can be enabled by passing a column key to `groupBy`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\" generic=\"T extends TableRow, C extends TableColumn<T>\">\nimport GTableBody from \"./table/GTableBody.vue\";\nimport GPopover from \"./GPopover.vue\";\nimport { TableColumn, TableRow } from \"./table/TableColumn.ts\";\nimport {\n computed,\n onMounted,\n ref,\n toRaw,\n useId,\n useSlots,\n useTemplateRef,\n VNode,\n watch,\n} from \"vue\";\nimport GSelect from \"./GSelect.vue\";\nimport { useFiltering, UseFilteringReturn } from \"../compose/useFiltering.ts\";\nimport {\n CellChangePayload,\n UseTableChangesReturn,\n} from \"../compose/useTableChanges.ts\";\nimport GButton from \"./GButton.vue\";\n\nexport interface BulkAction {\n /**\n * Action identifier\n */\n id: string;\n /**\n * Action label\n */\n label: string;\n /**\n * Action theme/color\n */\n theme?: \"primary\" | \"secondary\" | \"accent\" | \"danger\";\n}\n\ntype Props = {\n /**\n * Accessible label\n * @demo Colleges\n */\n label: string;\n /**\n * The data to display in the table\n *\n * The data should be an array of objects, each representing a row in the table.\n * Each object should have a unique `key` property that can be used to identify the row.\n */\n data: T[];\n /**\n * The columns to display in the table\n *\n * Each column's key needs to match the key of a property in the data objects,\n * which determines the data to display in that column by default. You can also\n * provide a custom display function to customize the data display.\n */\n columns: C[];\n /**\n * Result count for all of the possible results (not just the current page)\n *\n * This is shown in the toolbar.\n */\n resultCount?: number;\n /**\n * A column key to group the data by\n *\n * If provided, the `groupRender` render function will be used to render the group header.\n */\n groupBy?: keyof T;\n /**\n * A render function to customize the display of the group header.\n *\n * @param groupValue The value of the group key for the current row\n * @param row The row object for the first row in the group\n */\n groupRender?: (groupValue: any, row: T) => VNode;\n /**\n * Filtering object created with useFiltering()\n */\n filtering?: UseFilteringReturn<any>;\n /**\n * Make the table rows clickable\n */\n rowClickable?: boolean;\n /**\n * A function to customize the classes applied to table rows\n * @param row The row object\n */\n rowClass?: (row: T) => string | string[] | undefined;\n /**\n * The starting index for this page\n *\n * This is used for the ARIA rowindex attribute, and is VERY important\n * to not get wrong.\n */\n startIndex: number;\n /**\n * Enable bulk selection with checkboxes\n * @demo\n */\n bulkSelectionEnabled?: boolean;\n /**\n * Array of actions to show in the sticky toolbar when rows are selected\n */\n bulkActions?: BulkAction[];\n\n /**\n * Optional change tracker for editable tables.\n * Pass a composable from useTableChanges() to track user edits.\n */\n changeTracker?: UseTableChangesReturn<T>;\n\n /**\n * Explicitly show the pagination bar even if the slot is empty\n * @demo\n */\n showPagination?: boolean;\n};\n\nconst sortField = defineModel<keyof T>(\"sortField\");\nconst sortOrder = defineModel<1 | -1>(\"sortOrder\");\nconst filter = defineModel<Partial<Record<keyof T, any>>>(\"filter\", {\n default: () => ({}),\n});\nconst selectedRows = defineModel<string[]>(\"selectedRows\", {\n default: () => [],\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n bulkSelectionEnabled: false,\n bulkActions: () => [],\n showPagination: false,\n});\n\nconst emit = defineEmits<{\n (e: \"row-click\", link: string): void;\n (e: \"bulk-action\", actionId: string, selectedKeys: string[]): void;\n (e: \"cell-change\", payload: CellChangePayload<T>): void;\n}>();\n\nfunction onSort(col: TableColumn<T>) {\n if (!col.sortable) {\n return;\n }\n if (sortField.value === col.key) {\n if (sortOrder.value === 1) {\n sortOrder.value = -1;\n } else if (sortOrder.value === -1) {\n sortField.value = undefined as any;\n sortOrder.value = 1;\n }\n } else {\n sortField.value = col.key;\n sortOrder.value = 1;\n }\n}\n\nlet filtering: UseFilteringReturn<any> = props.filtering!;\n\nif (!filtering) {\n filtering = useFiltering({}) as any;\n}\n\nconst { filters, filteredColumns, isFiltered, clearFilters } = filtering;\n\n// Bulk selection logic\nconst allRowKeys = computed(() => props.data.map((row) => row.key));\nconst selectedRowsOnPage = computed(() => {\n return selectedRows.value.filter((key) => allRowKeys.value.includes(key));\n});\nconst allSelected = computed(() => {\n if (!props.bulkSelectionEnabled || props.data.length === 0) {\n return false;\n }\n return selectedRowsOnPage.value.length === allRowKeys.value.length;\n});\nconst someSelected = computed(() => {\n if (!props.bulkSelectionEnabled || props.data.length === 0) {\n return false;\n }\n return (\n selectedRowsOnPage.value.length > 0 &&\n selectedRowsOnPage.value.length < allRowKeys.value.length\n );\n});\n\nconst lastClickedRowKey = ref<string | null>(null);\n\nfunction toggleAllRows() {\n if (allSelected.value) {\n // Deselect all rows on current page\n selectedRows.value = selectedRows.value.filter(\n (key) => !allRowKeys.value.includes(key),\n );\n } else {\n // Select all rows on current page\n const newSelected = new Set(selectedRows.value);\n allRowKeys.value.forEach((key) => newSelected.add(key));\n selectedRows.value = Array.from(newSelected);\n }\n}\n\nfunction toggleRow(rowKey: string, shiftKey: boolean = false) {\n if (shiftKey && lastClickedRowKey.value) {\n // Handle shift-click range selection\n const lastIndex = allRowKeys.value.indexOf(lastClickedRowKey.value);\n const currentIndex = allRowKeys.value.indexOf(rowKey);\n\n if (lastIndex !== -1 && currentIndex !== -1) {\n const start = Math.min(lastIndex, currentIndex);\n const end = Math.max(lastIndex, currentIndex);\n const rowsInRange = allRowKeys.value.slice(start, end + 1);\n\n // Select all rows in the range\n const newSelected = new Set(selectedRows.value);\n rowsInRange.forEach((key) => newSelected.add(key));\n selectedRows.value = Array.from(newSelected);\n }\n } else {\n // Normal toggle behavior\n if (selectedRows.value.includes(rowKey)) {\n selectedRows.value = selectedRows.value.filter(\n (key) => key !== rowKey,\n );\n } else {\n selectedRows.value = [...selectedRows.value, rowKey];\n }\n }\n\n // Update last clicked row\n lastClickedRowKey.value = rowKey;\n}\n\nfunction clickRow(link: string) {\n emit(\"row-click\", link);\n}\n\nfunction handleBulkAction(actionId: string) {\n emit(\"bulk-action\", actionId, selectedRows.value);\n}\n\nfunction handleCellChange(change: { row: T; column: C; value: any }) {\n // Update the reactive data\n // Convert the value to the appropriate type based on input attributes\n let convertedValue: any = change.value;\n const columnKey = change.column.key;\n const previousValue = toRaw(change.row[columnKey]);\n if (change.column.editable?.inputAttributes?.type === \"number\") {\n convertedValue = change.value === \"\" ? null : Number(change.value);\n }\n change.row[columnKey] = convertedValue;\n\n const payload: CellChangePayload<T> = {\n row: change.row,\n column: change.column,\n value: convertedValue,\n previousValue,\n };\n\n emit(\"cell-change\", payload);\n}\n\nconst id = useId();\nconst slots = useSlots();\n\nconst shouldShowPagination = computed(() => {\n // Show if explicitly requested via prop\n if (props.showPagination) {\n return true;\n }\n // Show if the pagination slot has content\n return !!slots.pagination;\n});\n\nconst shouldShowControls = computed(() => {\n // Show if filters are active (clear filters button is visible)\n if (isFiltered.value) {\n return true;\n }\n // Show if pagination should be shown\n if (shouldShowPagination.value) {\n return true;\n }\n // Otherwise hide the entire controls bar\n return false;\n});\n\nonMounted(() => {\n if (props.rowClickable && props.bulkSelectionEnabled) {\n console.warn(\n \"GTable: rowClickable and bulkSelectionEnabled cannot be used together. rowClickable will be ignored.\",\n );\n }\n for (const col of props.columns) {\n if (col.editable && col.display) {\n console.warn(\n `GTable: Column \"${String(col.key)}\" has both 'editable' and 'display' configured. 'display' will be ignored.`,\n );\n }\n if (col.filter && col.filter.type === \"multi-select\") {\n if (!Array.isArray(filter.value[col.key])) {\n let val = filter.value[col.key];\n filter.value[col.key] = val ? [val] : [];\n }\n }\n }\n});\n\nwatch(\n () => props.columns,\n (newColumns) => {\n for (const col of newColumns) {\n if (col.filter && col.filter.type === \"multi-select\") {\n if (!Array.isArray(filter.value[col.key])) {\n let val = filter.value[col.key];\n filter.value[col.key] = val ? [val] : [];\n }\n }\n }\n },\n { immediate: true },\n);\n</script>\n\n<template>\n <div class=\"g-table-outer-wrap\">\n <div v-if=\"shouldShowControls\" class=\"g-table-controls\">\n <div class=\"g-clear-filters-wrap\">\n <GButton\n v-if=\"isFiltered\"\n outlined\n size=\"small\"\n class=\"clear-filters\"\n @click=\"clearFilters\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n height=\"1em\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n <span class=\"g-clear-filters-text\"> Clear Filters </span>\n </GButton>\n </div>\n <div v-if=\"shouldShowPagination\" class=\"pagination\">\n <slot name=\"pagination\"></slot>\n </div>\n <span class=\"g-result-count\"\n >{{ props.resultCount || data.length }} results</span\n >\n </div>\n <table\n class=\"g-table\"\n ref=\"tableRef\"\n :aria-label=\"label\"\n :aria-rowcount=\"props.resultCount || data.length\"\n >\n <thead class=\"g-table-head\">\n <tr aria-rowindex=\"1\">\n <th\n v-if=\"bulkSelectionEnabled\"\n scope=\"col\"\n class=\"g-th g-th-checkbox\"\n >\n <input\n type=\"checkbox\"\n :checked=\"allSelected\"\n :indeterminate=\"someSelected\"\n @change=\"toggleAllRows\"\n :aria-label=\"\n allSelected\n ? 'Deselect all rows'\n : 'Select all rows'\n \"\n class=\"g-bulk-select-checkbox\"\n />\n </th>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n :id=\"`${id}-th-${String(col.key)}`\"\n :aria-sort=\"\n sortField === col.key\n ? sortOrder === 1\n ? 'ascending'\n : 'descending'\n : 'none'\n \"\n :class=\"[\n 'g-th',\n { sorted: sortField === col.key },\n { filtered: filteredColumns[col.key] },\n ]\"\n scope=\"col\"\n >\n <div class=\"th-inner\">\n <button\n v-if=\"col.sortable\"\n type=\"button\"\n class=\"g-column-head\"\n @click=\"onSort(col)\"\n >\n {{ col.label }}\n <span\n v-if=\"sortField === col.key\"\n class=\"sort-indicator\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.5em\"\n role=\"img\"\n :aria-label=\"\n sortOrder === 1\n ? 'Sorted ascending'\n : 'Sorted descending'\n \"\n :style=\"{\n transform: `rotate(${sortOrder === 1 ? 0 : 180}deg)`,\n }\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M300.3 199.2C312.9 188.9 331.4 189.7 343.1 201.4L471.1 329.4C480.3 338.6 483 352.3 478 364.3C473 376.3 461.4 384 448.5 384L192.5 384C179.6 384 167.9 376.2 162.9 364.2C157.9 352.2 160.7 338.5 169.9 329.4L297.9 201.4L300.3 199.2z\"\n />\n </svg>\n </span>\n </button>\n <span v-else class=\"g-column-head\">{{\n col.label\n }}</span>\n <GPopover v-if=\"col.filter\">\n <template #trigger=\"{ toggle }\">\n <button\n @click.stop=\"toggle\"\n :aria-label=\"\n filteredColumns[col.key]\n ? 'Column Filtered'\n : 'Filter Column'\n \"\n class=\"g-filter-btn\"\n :class=\"{\n 'g-active':\n filteredColumns[col.key],\n }\"\n type=\"button\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n height=\"1.5em\"\n aria-hidden=\"true\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M96 128C83.1 128 71.4 135.8 66.4 147.8C61.4 159.8 64.2 173.5 73.4 182.6L256 365.3L256 480C256 488.5 259.4 496.6 265.4 502.6L329.4 566.6C338.6 575.8 352.3 578.5 364.3 573.5C376.3 568.5 384 556.9 384 544L384 365.3L566.6 182.7C575.8 173.5 578.5 159.8 573.5 147.8C568.5 135.8 556.9 128 544 128L96 128z\"\n />\n </svg>\n </button>\n </template>\n <GSelect\n v-if=\"col.filter.type === 'select'\"\n v-model=\"filter[col.key]\"\n :options=\"col.filter.options\"\n class=\"g-filter-select\"\n label=\"Filter select\"\n searchable\n clear-button\n />\n <div v-else-if=\"col.filter.type === 'toggle'\">\n <div class=\"g-filter-toggle\">\n <input\n type=\"checkbox\"\n v-model=\"filter[col.key]\"\n :id=\"`${id}-filter-${String(col.key)}`\"\n :aria-describedby=\"\n col.filter.description\n ? `${id}-filter-description-${String(col.key)}`\n : undefined\n \"\n />\n <label\n :for=\"`${id}-filter-${String(col.key)}`\"\n >{{ col.filter.label }}</label\n >\n <span\n class=\"g-filter-description\"\n v-if=\"col.filter.description\"\n :id=\"`${id}-filter-description-${String(col.key)}`\"\n >\n {{ col.filter.description }}\n </span>\n </div>\n </div>\n <fieldset\n v-else-if=\"\n col.filter.type === 'multi-select'\n \"\n class=\"g-multi-select\"\n >\n <legend class=\"g-multi-select-legend\">\n Include values\n </legend>\n <div\n v-for=\"opt in col.filter.options\"\n :key=\"opt.value\"\n >\n <input\n type=\"checkbox\"\n v-model=\"filter[col.key]\"\n :id=\"`filter-${String(col.key)}-${opt.value}`\"\n :value=\"opt.value\"\n name=\"filter-multiselect\"\n />\n <label\n :for=\"`filter-${String(col.key)}-${opt.value}`\"\n >{{ opt.label }}</label\n >\n </div>\n <GButton\n class=\"clear-multiselect-btn\"\n theme=\"accent\"\n size=\"small\"\n @click=\"filter[col.key] = []\"\n v-if=\"\n filter[col.key] &&\n filter[col.key].length\n \"\n >\n Clear\n </GButton>\n </fieldset>\n </GPopover>\n </div>\n </th>\n </tr>\n </thead>\n <!-- @vue-generic {T, C} -->\n <GTableBody\n :data=\"data\"\n :columns=\"columns\"\n :group-by=\"groupBy\"\n :group-render=\"groupRender\"\n :row-clickable=\"rowClickable\"\n :row-class=\"rowClass as any\"\n :start-index=\"startIndex\"\n :bulk-selection-enabled=\"bulkSelectionEnabled\"\n :selected-rows=\"selectedRows\"\n :table-id=\"id\"\n :change-tracker=\"changeTracker\"\n @row-click=\"clickRow\"\n @toggle-row=\"toggleRow\"\n @cell-change=\"handleCellChange\"\n />\n </table>\n <div\n v-if=\"bulkSelectionEnabled && selectedRows.length > 0\"\n class=\"g-bulk-actions-toolbar\"\n >\n <span class=\"g-selected-count\"\n >{{ selectedRows.length }} row{{\n selectedRows.length === 1 ? \"\" : \"s\"\n }}\n selected</span\n >\n <ul class=\"g-bulk-actions\">\n <li v-for=\"action in bulkActions\" :key=\"action.id\">\n <GButton\n :theme=\"action.theme || 'accent'\"\n @click=\"handleBulkAction(action.id)\"\n size=\"small\"\n >\n {{ action.label }} {{ selectedRows.length }} row{{\n selectedRows.length === 1 ? \"\" : \"s\"\n }}\n </GButton>\n </li>\n </ul>\n </div>\n </div>\n</template>\n\n<style>\ng-table {\n display: block;\n}\n.g-table-outer-wrap {\n}\n\n.g-table-controls {\n height: 40px;\n position: sticky;\n display: flex;\n top: 0;\n left: 0;\n padding: 2px 6px;\n}\n\n.g-table-head {\n background: var(--g-surface-0);\n position: sticky;\n top: 40px;\n z-index: 1;\n}\n\n.g-th {\n text-align: left;\n padding: 0.5rem 0.2rem;\n border: 0;\n border-bottom: 2px solid var(--g-surface-900);\n background: var(--g-surface-0);\n\n &.sorted {\n color: var(--ilw-color--link-hover);\n }\n\n &.filtered {\n .g-filter-btn {\n color: var(--ilw-color--link-hover);\n }\n }\n\n .th-inner {\n display: flex;\n align-items: center;\n }\n}\n\n.g-column-head {\n color: currentColor;\n position: relative;\n border: none;\n font-weight: 700;\n font-family: var(--il-font-sans);\n font-size: 1rem;\n line-height: 1.3;\n white-space: nowrap;\n padding-left: 4px;\n background: var(--g-surface-0);\n\n .sort-indicator {\n position: absolute;\n bottom: -1.1em;\n left: calc(50% - 0.7em);\n }\n}\n\nth:first-of-type .g-column-head {\n padding-left: 0;\n}\n\nbutton.g-column-head {\n cursor: pointer;\n height: 2rem;\n}\n\nbutton.g-column-head:hover {\n text-decoration: underline;\n color: var(--ilw-color--link-hover);\n}\n\n.g-table {\n border-spacing: 0;\n min-width: 100%;\n}\n\n.g-filter-btn {\n border: none;\n background: transparent;\n border-radius: 50%;\n width: 2rem;\n height: 2rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n &.g-active {\n border: 2px solid var(--ilw-color--link-hover);\n }\n}\n\n.g-clear-filters-text {\n white-space: nowrap;\n}\n\n@media screen and (max-width: 600px) {\n .g-clear-filters-text {\n opacity: 0;\n width: 1px;\n height: 1px;\n overflow: hidden;\n }\n}\n\n.g-filter-select {\n min-width: 200px;\n}\n\n.g-table-controls {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1.5rem;\n padding: 0.2rem 1rem;\n background: var(--g-surface-150);\n\n .g-result-count {\n font-size: 1rem;\n line-height: 1.2;\n }\n}\n\n.g-multi-select {\n border: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n\n .clear-multiselect-btn {\n margin-top: 0.5rem;\n }\n\n legend {\n font-size: 1.125rem;\n font-weight: bold;\n margin-bottom: 0.5rem;\n }\n\n div {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n\n &:has(:focus-visible) {\n outline: 2px solid var(--g-primary-500);\n }\n }\n\n input {\n width: 24px;\n height: 24px;\n accent-color: var(--g-primary-500);\n display: block;\n }\n\n label {\n font-size: 1.125rem;\n flex: 1;\n }\n}\n\n.g-multi-select-legend {\n margin: 0;\n padding: 0;\n font-size: 1rem;\n line-height: 1.2;\n}\n\n.g-filter-toggle {\n display: grid;\n grid-template-areas:\n \"label input\"\n \"description description\";\n\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: 0.5rem;\n\n input {\n width: 24px;\n height: 24px;\n }\n\n label {\n font-size: 1.125rem;\n font-weight: bold;\n }\n\n .g-filter-description {\n grid-area: description;\n }\n}\n\n.g-clear-filters-wrap,\n.g-result-count {\n}\n\n/* Bulk selection styles */\n.g-th-checkbox {\n width: 50px;\n text-align: center;\n}\n\n.g-bulk-select-checkbox {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--g-primary-500);\n}\n\n.g-bulk-actions-toolbar {\n position: sticky;\n bottom: 0;\n left: 0;\n right: 0;\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n padding: 0.75rem 1rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);\n z-index: 1;\n\n ul {\n display: flex;\n gap: 1rem;\n list-style: none;\n padding: 0;\n margin: 0;\n }\n\n li {\n margin: 0;\n }\n}\n\n.g-selected-count {\n font-weight: 600;\n font-size: 1rem;\n}\n\n.g-bulk-actions {\n display: flex;\n gap: 0.5rem;\n align-items: center;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * Pagination component for GTable.\n */\nexport default {};\n</script>\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n /**\n * The index of the first item\n */\n start: number;\n /**\n * The value of the page size control\n */\n pageSize: number;\n /**\n * The total number of items\n */\n total: number;\n /**\n * Available page sizes for the dropdown\n */\n pageSizes?: number[];\n}>();\n\nconst totalPages = computed(() => {\n return Math.max(1, Math.ceil(props.total / props.pageSize));\n});\n\nconst startModel = defineModel<number>(\"start\");\nconst pageSizeModel = defineModel<number>(\"pageSize\");\n\nconst startVal = computed(() => startModel.value ?? props.start);\nconst pageSizeVal = computed(() => pageSizeModel.value ?? props.pageSize);\n\nconst startDisplay = computed(() => {\n if (props.total === 0) {\n return 0;\n }\n return startVal.value + 1;\n});\n\nconst end = computed(() => {\n if (props.total === 0) {\n return 0;\n }\n return Math.min(startVal.value + pageSizeVal.value, props.total);\n});\n\nconst currentPage = computed(() => {\n return Math.floor(startVal.value / pageSizeVal.value) + 1;\n});\n\nfunction goToPage(p: number) {\n if (p < 1 || p > totalPages.value) {\n return;\n }\n startModel.value = (p - 1) * pageSizeVal.value;\n}\n\nfunction onPageSizeChange(e: Event) {\n pageSizeModel.value = parseInt((e.target as HTMLSelectElement).value, 10);\n}\n</script>\n\n<template>\n <nav class=\"g-pagination\" aria-label=\"Pagination\">\n <button\n class=\"first-page g-pagination-button\"\n :disabled=\"currentPage === 1\"\n @click=\"goToPage(1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"First Page\"\n height=\"2em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M11 7l-5 5l5 5\" />\n <path d=\"M17 7l-5 5l5 5\" />\n </svg>\n </button>\n <button\n class=\"prev-page g-pagination-button\"\n :disabled=\"currentPage === 1\"\n @click=\"goToPage(currentPage - 1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Previous Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M15 6l-6 6l6 6\" />\n </svg>\n </button>\n <span class=\"page-range\"> {{ startDisplay }} to {{ end }} </span>\n <button\n class=\"next-page g-pagination-button\"\n :disabled=\"currentPage === totalPages\"\n @click=\"goToPage(currentPage + 1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Next Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M9 6l6 6l-6 6\" />\n </svg>\n </button>\n <button\n class=\"last-page g-pagination-button\"\n :disabled=\"currentPage === totalPages\"\n @click=\"goToPage(totalPages)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Last Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M7 7l5 5l-5 5\" />\n <path d=\"M13 7l5 5l-5 5\" />\n </svg>\n </button>\n <select\n id=\"page-size-select\"\n class=\"page-size-select\"\n :value=\"pageSizeModel\"\n @change=\"onPageSizeChange\"\n >\n <option\n v-for=\"size in props.pageSizes || [10, 25, 50, 100]\"\n :key=\"size\"\n :value=\"size\"\n >\n {{ size }}\n </option>\n </select>\n <label class=\"page-size-label\" for=\"page-size-select\">per page</label>\n </nav>\n</template>\n\n<style>\n.g-pagination {\n display: flex;\n align-items: center;\n gap: 0.1rem;\n font-size: 1rem;\n\n .g-pagination-button {\n background: transparent;\n border: none;\n color: var(--g-surface-900);\n padding: 0.2rem 0.4rem;\n border-radius: 4px;\n cursor: pointer;\n\n &:not(:disabled) {\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n }\n }\n}\n.g-pagination button:disabled {\n cursor: auto;\n color: var(--g-surface-600);\n}\n.g-pagination .page-range {\n min-width: 3rem;\n text-align: center;\n}\n\n.g-pagination .page-size-select {\n margin-left: 1rem;\n margin-right: 0.5rem;\n padding: 0.2em 0.5em 0.2em 0.5em;\n border-radius: 0.2em;\n border: 2px solid var(--g-primary-500);\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n cursor: pointer;\n font-size: 1rem;\n font-family: var(--il-font-sans);\n}\n.g-pagination .page-size-select:hover {\n}\n.g-pagination .page-size-select:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n.page-size-label {\n line-height: 1.2;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-pagination .page-size-select {\n transition: none;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Pagination component for GTable.\n */\nexport default {};\n</script>\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n /**\n * The index of the first item\n */\n start: number;\n /**\n * The value of the page size control\n */\n pageSize: number;\n /**\n * The total number of items\n */\n total: number;\n /**\n * Available page sizes for the dropdown\n */\n pageSizes?: number[];\n}>();\n\nconst totalPages = computed(() => {\n return Math.max(1, Math.ceil(props.total / props.pageSize));\n});\n\nconst startModel = defineModel<number>(\"start\");\nconst pageSizeModel = defineModel<number>(\"pageSize\");\n\nconst startVal = computed(() => startModel.value ?? props.start);\nconst pageSizeVal = computed(() => pageSizeModel.value ?? props.pageSize);\n\nconst startDisplay = computed(() => {\n if (props.total === 0) {\n return 0;\n }\n return startVal.value + 1;\n});\n\nconst end = computed(() => {\n if (props.total === 0) {\n return 0;\n }\n return Math.min(startVal.value + pageSizeVal.value, props.total);\n});\n\nconst currentPage = computed(() => {\n return Math.floor(startVal.value / pageSizeVal.value) + 1;\n});\n\nfunction goToPage(p: number) {\n if (p < 1 || p > totalPages.value) {\n return;\n }\n startModel.value = (p - 1) * pageSizeVal.value;\n}\n\nfunction onPageSizeChange(e: Event) {\n pageSizeModel.value = parseInt((e.target as HTMLSelectElement).value, 10);\n}\n</script>\n\n<template>\n <nav class=\"g-pagination\" aria-label=\"Pagination\">\n <button\n class=\"first-page g-pagination-button\"\n :disabled=\"currentPage === 1\"\n @click=\"goToPage(1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"First Page\"\n height=\"2em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M11 7l-5 5l5 5\" />\n <path d=\"M17 7l-5 5l5 5\" />\n </svg>\n </button>\n <button\n class=\"prev-page g-pagination-button\"\n :disabled=\"currentPage === 1\"\n @click=\"goToPage(currentPage - 1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Previous Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M15 6l-6 6l6 6\" />\n </svg>\n </button>\n <span class=\"page-range\"> {{ startDisplay }} to {{ end }} </span>\n <button\n class=\"next-page g-pagination-button\"\n :disabled=\"currentPage === totalPages\"\n @click=\"goToPage(currentPage + 1)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Next Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M9 6l6 6l-6 6\" />\n </svg>\n </button>\n <button\n class=\"last-page g-pagination-button\"\n :disabled=\"currentPage === totalPages\"\n @click=\"goToPage(totalPages)\"\n >\n <svg\n role=\"img\"\n aria-label=\"Last Page\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"2em\"\n height=\"2em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <!-- MIT License https://github.com/tabler/tabler-icons -->\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M7 7l5 5l-5 5\" />\n <path d=\"M13 7l5 5l-5 5\" />\n </svg>\n </button>\n <select\n id=\"page-size-select\"\n class=\"page-size-select\"\n :value=\"pageSizeModel\"\n @change=\"onPageSizeChange\"\n >\n <option\n v-for=\"size in props.pageSizes || [10, 25, 50, 100]\"\n :key=\"size\"\n :value=\"size\"\n >\n {{ size }}\n </option>\n </select>\n <label class=\"page-size-label\" for=\"page-size-select\">per page</label>\n </nav>\n</template>\n\n<style>\n.g-pagination {\n display: flex;\n align-items: center;\n gap: 0.1rem;\n font-size: 1rem;\n\n .g-pagination-button {\n background: transparent;\n border: none;\n color: var(--g-surface-900);\n padding: 0.2rem 0.4rem;\n border-radius: 4px;\n cursor: pointer;\n\n &:not(:disabled) {\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n }\n }\n}\n.g-pagination button:disabled {\n cursor: auto;\n color: var(--g-surface-600);\n}\n.g-pagination .page-range {\n min-width: 3rem;\n text-align: center;\n}\n\n.g-pagination .page-size-select {\n margin-left: 1rem;\n margin-right: 0.5rem;\n padding: 0.2em 0.5em 0.2em 0.5em;\n border-radius: 0.2em;\n border: 2px solid var(--g-primary-500);\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n cursor: pointer;\n font-size: 1rem;\n font-family: var(--il-font-sans);\n}\n.g-pagination .page-size-select:hover {\n}\n.g-pagination .page-size-select:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n.page-size-label {\n line-height: 1.2;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .g-pagination .page-size-select {\n transition: none;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Generic modal component.\n *\n * Clicking on the outside or pressing the escape key will close the modal.\n *\n * > [!IMPORTANT]\n * >\n * > The surrounding page **must** have an element with the id `modal-root`,\n * > this modal will be teleported to it, so it can properly be over all\n * > other content. The `modal-root` should be somewhere near the end of the\n * > page structure.\n *\n * **Props**:\n *\n * - `label`: Modal accessible label.\n * - `describedby`: Element ID to pass to aria-describedby. Use this if there's\n * specific important text to describe the modal.\n * - `hiddenLabel`: Hide label visually. It will still be used as `aria-label`.\n * - `size`: Modal size\n *\n * **Slot** `default` is used as the content of the modal.\n *\n * When the modal is opened, focus is placed on the H2 label element. This\n * can be overridden by providing a `popover-focus` attribute on an element\n * inside the modal.\n *\n * Adding a dimming overlay behind modals can be done by placing `GOverlay`\n * at the end of the page structure.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n onBeforeMount,\n onMounted,\n ref,\n useId,\n useTemplateRef,\n} from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\n\ntype Props = {\n /**\n * Modal label\n * @demo Basic Modal\n */\n label: string;\n /**\n * ID for aria-describedby\n * @demo\n */\n describedby?: string;\n /**\n * Hide label\n *\n * The label is still used as the `aria-label` for accessibility, but it will not be visible in the UI.\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Modal size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n /**\n * Modal classes\n * @demo\n */\n classes?: string | string[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n describedby: undefined,\n hiddenLabel: false,\n size: \"medium\",\n});\n\nconst emit = defineEmits([\"close\"]);\n\nconst dialog = useTemplateRef(\"dialog\");\nconst open = ref(true);\n\nconst id = useId();\nconst { pop, push, isTop, zIndex } = useOverlayStack(id, true, true);\n\nconst { deactivate, activate } = useOverlayFocus(dialog, isTop);\n\nfunction close() {\n emit(\"close\");\n}\n\nuseOverlayEscape([dialog], isTop, open, close, pop);\n\nonMounted(() => {\n push();\n activate();\n});\n\nonBeforeMount(() => {\n pop();\n deactivate();\n});\n\nconst useClasses = computed(() => {\n let modalClasses = [`g-modal--${props.size}`];\n if (props.classes) {\n modalClasses = modalClasses.concat(Array.isArray(props.classes) ? props.classes : [props.classes]);\n }\n return modalClasses;\n});\n</script>\n\n<template>\n <Teleport to=\"#modal-root\">\n <Transition name=\"g-fade\" appear>\n <div\n :id=\"'modal-' + id\"\n class=\"g-modal\"\n :class=\"useClasses\"\n role=\"dialog\"\n aria-modal=\"true\"\n v-bind=\"{\n 'aria-labelledby': !hiddenLabel\n ? 'modal-label-' + id\n : undefined,\n 'aria-label': hiddenLabel ? label : undefined,\n 'aria-describedby': describedby ? describedby : undefined,\n }\"\n ref=\"dialog\"\n :style=\"{ zIndex }\"\n >\n <div class=\"g-modal-inner\">\n <div class=\"g-modal-header\">\n <h2\n v-if=\"!hiddenLabel\"\n :id=\"'modal-label-' + id\"\n class=\"g-modal-label\"\n tabindex=\"-1\"\n >\n {{ label }}\n </h2>\n <button\n class=\"g-modal-close\"\n @click=\"close\"\n aria-label=\"Close\"\n >\n <svg\n viewBox=\"0 0 24 24\"\n width=\"24\"\n height=\"24\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\"\n />\n </svg>\n </button>\n </div>\n <div\n :id=\"'modal-description-' + id\"\n class=\"g-modal-content\"\n >\n <slot />\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n.g-modal {\n position: fixed;\n left: 50vw;\n top: 50vh;\n transform: translate(-50%, -50%);\n height: auto;\n max-height: 90vh;\n overflow-y: auto;\n background: var(--g-surface-50);\n border-top: 8px solid var(--g-accent-500);\n padding: 2rem;\n box-sizing: border-box;\n box-shadow:\n 0 0 2px rgba(0, 0, 0, 0.4),\n 0 10px 20px rgba(0, 0, 0, 0.1);\n}\n.g-modal--small {\n width: 400px;\n max-width: 90vw;\n}\n.g-modal--medium {\n width: 600px;\n max-width: 90vw;\n}\n.g-modal--large {\n width: 900px;\n max-width: 90vw;\n}\n.g-modal--full {\n width: 100vw;\n height: 100vh;\n max-width: none;\n max-height: none;\n border-top: none;\n}\n.g-modal-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n}\n.g-modal-label {\n font-family: var(--il-font-heading);\n font-size: 2rem;\n margin-top: 0;\n color: var(--g-primary-500);\n}\n.g-modal-close {\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 0.5rem;\n margin: -1.25rem -1rem -1rem 1rem;\n color: var(--g-surface-600);\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Generic modal component.\n *\n * Clicking on the outside or pressing the escape key will close the modal.\n *\n * > [!IMPORTANT]\n * >\n * > The surrounding page **must** have an element with the id `modal-root`,\n * > this modal will be teleported to it, so it can properly be over all\n * > other content. The `modal-root` should be somewhere near the end of the\n * > page structure.\n *\n * **Props**:\n *\n * - `label`: Modal accessible label.\n * - `describedby`: Element ID to pass to aria-describedby. Use this if there's\n * specific important text to describe the modal.\n * - `hiddenLabel`: Hide label visually. It will still be used as `aria-label`.\n * - `size`: Modal size\n *\n * **Slot** `default` is used as the content of the modal.\n *\n * When the modal is opened, focus is placed on the H2 label element. This\n * can be overridden by providing a `popover-focus` attribute on an element\n * inside the modal.\n *\n * Adding a dimming overlay behind modals can be done by placing `GOverlay`\n * at the end of the page structure.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n onBeforeMount,\n onMounted,\n ref,\n useId,\n useTemplateRef,\n} from \"vue\";\nimport { useOverlayStack } from \"../compose/useOverlayStack.ts\";\nimport { useOverlayFocus } from \"../compose/useOverlayFocus.ts\";\nimport { useOverlayEscape } from \"../compose/useOverlayEscape.ts\";\n\ntype Props = {\n /**\n * Modal label\n * @demo Basic Modal\n */\n label: string;\n /**\n * ID for aria-describedby\n * @demo\n */\n describedby?: string;\n /**\n * Hide label\n *\n * The label is still used as the `aria-label` for accessibility, but it will not be visible in the UI.\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Modal size\n * @demo\n */\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n /**\n * Modal classes\n * @demo\n */\n classes?: string | string[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n describedby: undefined,\n hiddenLabel: false,\n size: \"medium\",\n});\n\nconst emit = defineEmits([\"close\"]);\n\nconst dialog = useTemplateRef(\"dialog\");\nconst open = ref(true);\n\nconst id = useId();\nconst { pop, push, isTop, zIndex } = useOverlayStack(id, true, true);\n\nconst { deactivate, activate } = useOverlayFocus(dialog, isTop);\n\nfunction close() {\n emit(\"close\");\n}\n\nuseOverlayEscape([dialog], isTop, open, close, pop);\n\nonMounted(() => {\n push();\n activate();\n});\n\nonBeforeMount(() => {\n pop();\n deactivate();\n});\n\nconst useClasses = computed(() => {\n let modalClasses = [`g-modal--${props.size}`];\n if (props.classes) {\n modalClasses = modalClasses.concat(Array.isArray(props.classes) ? props.classes : [props.classes]);\n }\n return modalClasses;\n});\n</script>\n\n<template>\n <Teleport to=\"#modal-root\">\n <Transition name=\"g-fade\" appear>\n <div\n :id=\"'modal-' + id\"\n class=\"g-modal\"\n :class=\"useClasses\"\n role=\"dialog\"\n aria-modal=\"true\"\n v-bind=\"{\n 'aria-labelledby': !hiddenLabel\n ? 'modal-label-' + id\n : undefined,\n 'aria-label': hiddenLabel ? label : undefined,\n 'aria-describedby': describedby ? describedby : undefined,\n }\"\n ref=\"dialog\"\n :style=\"{ zIndex }\"\n >\n <div class=\"g-modal-inner\">\n <div class=\"g-modal-header\">\n <h2\n v-if=\"!hiddenLabel\"\n :id=\"'modal-label-' + id\"\n class=\"g-modal-label\"\n tabindex=\"-1\"\n >\n {{ label }}\n </h2>\n <button\n class=\"g-modal-close\"\n @click=\"close\"\n aria-label=\"Close\"\n >\n <svg\n viewBox=\"0 0 24 24\"\n width=\"24\"\n height=\"24\"\n aria-hidden=\"true\"\n >\n <path\n fill=\"currentColor\"\n d=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\"\n />\n </svg>\n </button>\n </div>\n <div\n :id=\"'modal-description-' + id\"\n class=\"g-modal-content\"\n >\n <slot />\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n.g-modal {\n position: fixed;\n left: 50vw;\n top: 50vh;\n transform: translate(-50%, -50%);\n height: auto;\n max-height: 90vh;\n overflow-y: auto;\n background: var(--g-surface-50);\n border-top: 8px solid var(--g-accent-500);\n padding: 2rem;\n box-sizing: border-box;\n box-shadow:\n 0 0 2px rgba(0, 0, 0, 0.4),\n 0 10px 20px rgba(0, 0, 0, 0.1);\n}\n.g-modal--small {\n width: 400px;\n max-width: 90vw;\n}\n.g-modal--medium {\n width: 600px;\n max-width: 90vw;\n}\n.g-modal--large {\n width: 900px;\n max-width: 90vw;\n}\n.g-modal--full {\n width: 100vw;\n height: 100vh;\n max-width: none;\n max-height: none;\n border-top: none;\n}\n.g-modal-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n}\n.g-modal-label {\n font-family: var(--il-font-heading);\n font-size: 2rem;\n margin-top: 0;\n color: var(--g-primary-500);\n}\n.g-modal-close {\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 0.5rem;\n margin: -1.25rem -1rem -1rem 1rem;\n color: var(--g-surface-600);\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n &:hover {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n }\n &:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A hamburger menu button that toggles a sidebar, intended for the\n * GAppHeader and GSidebar components.\n *\n * <span id=\"use-sidebar\">Use with the `useSidebar`</span> composable function\n * that takes care of passing state between the different components.\n *\n * Here's an example, this could be your App.vue or a layout file:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * import { computed, h, onMounted, provide, ref, useTemplateRef } from \"vue\";\n * import { useSidebar } from \"../src/compose/useSidebar\";\n *\n * const sidebar = useSidebar();\n * provide(\"sidebar\", sidebar);\n *\n * // Or optionally a custom breakpoint\n * // const sidebar = useSidebar(\"(max-width: 600px)\");\n * &lt;/script>\n * ```\n *\n * As long as GHamburgerMenu and GSidebar are descendants of the component that\n * provides the sidebar, they will be able to communicate with each other.\n *\n * > [!NOTE]\n * > This button hides itself automatically according to the useSidebar media query.\n * > In web components mode, use the `sidebar-key` prop to pair this menu with a\n * > matching GSidebar instance.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useSidebar } from \"../compose/useSidebar.ts\";\nimport { useWebComponentSidebar } from \"../compose/useWebComponentSidebar.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\nimport { inject, useId } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Sidebar channel key for custom elements mode\n * @demo\n */\n sidebarKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"Main Navigation\",\n sidebarKey: \"default\",\n});\n\nconst injectedSidebar = inject<ReturnType<typeof useSidebar>>(\"sidebar\");\nconst sidebar =\n injectedSidebar ??\n (isCustomElementMode() ? useWebComponentSidebar(props.sidebarKey) : undefined);\n\nconst emit = defineEmits<{\n toggle: [];\n}>();\n\nfunction toggle() {\n emit(\"toggle\");\n sidebar?.toggle();\n}\n\n// Close menu on escape\nfunction handleEscapeKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n if (sidebar?.open?.value) {\n sidebar.open.value = false;\n }\n }\n}\n\nconst fallbackId = useId();\n</script>\n<template>\n <button\n :id=\"`${sidebar?.id ?? fallbackId}-hamburger`\"\n class=\"g-hamburger-button\"\n :class=\"{\n 'g-hamburger-button--open': sidebar?.open?.value,\n 'g-hamburger-button--collapsible': sidebar?.isCollapsible?.value\n }\"\n @click=\"toggle\"\n @keydown=\"handleEscapeKey\"\n :aria-expanded=\"sidebar?.open?.value ? 'true' : 'false'\"\n :aria-label=\"label\"\n :aria-controls=\"sidebar ? `${sidebar.id}-sidebar` : undefined\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 51.26 51.26\">\n <g fill=\"currentColor\">\n <path\n d=\"M11.6 16.52h28.06a3.24 3.24 0 1 0 0-6.48H11.6a3.24 3.24 0 0 0 0 6.48ZM39.66 22.07H11.6a3.24 3.24 0 0 0 0 6.48h28.06a3.24 3.24 0 1 0 0-6.48ZM39.66 34.1H11.6a3.24 3.24 0 0 0 0 6.48h28.06a3.24 3.24 0 1 0 0-6.48Z\"\n />\n </g>\n </svg>\n </button>\n</template>\n\n<style>\ng-hamburger-menu:not(:defined) {\n display: none;\n}\n\n.g-hamburger-button {\n svg {\n width: 1.6rem;\n }\n}\n.g-hamburger-button {\n width: 34px;\n height: 34px;\n padding: 0;\n display: none;\n justify-content: center;\n align-items: center;\n text-decoration: none;\n border: 2px solid var(--g-primary-500);\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n border-radius: 4px;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-text);\n color: var(--g-primary-500);\n }\n &:active {\n background: var(--g-accent-500);\n color: var(--g-primary-text);\n }\n &:focus-visible {\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--g-primary-500);\n }\n}\n.g-hamburger-button--collapsible {\n display: flex;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A hamburger menu button that toggles a sidebar, intended for the\n * GAppHeader and GSidebar components.\n *\n * <span id=\"use-sidebar\">Use with the `useSidebar`</span> composable function\n * that takes care of passing state between the different components.\n *\n * Here's an example, this could be your App.vue or a layout file:\n *\n * ```vue\n * <script setup lang=\"ts\">\n * import { computed, h, onMounted, provide, ref, useTemplateRef } from \"vue\";\n * import { useSidebar } from \"../src/compose/useSidebar\";\n *\n * const sidebar = useSidebar();\n * provide(\"sidebar\", sidebar);\n *\n * // Or optionally a custom breakpoint\n * // const sidebar = useSidebar(\"(max-width: 600px)\");\n * &lt;/script>\n * ```\n *\n * As long as GHamburgerMenu and GSidebar are descendants of the component that\n * provides the sidebar, they will be able to communicate with each other.\n *\n * > [!NOTE]\n * > This button hides itself automatically according to the useSidebar media query.\n * > In web components mode, use the `sidebar-key` prop to pair this menu with a\n * > matching GSidebar instance.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useSidebar } from \"../compose/useSidebar.ts\";\nimport { useWebComponentSidebar } from \"../compose/useWebComponentSidebar.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\nimport { inject, useId } from \"vue\";\n\ntype Props = {\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n /**\n * Sidebar channel key for custom elements mode\n * @demo\n */\n sidebarKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"Main Navigation\",\n sidebarKey: \"default\",\n});\n\nconst injectedSidebar = inject<ReturnType<typeof useSidebar>>(\"sidebar\");\nconst sidebar =\n injectedSidebar ??\n (isCustomElementMode() ? useWebComponentSidebar(props.sidebarKey) : undefined);\n\nconst emit = defineEmits<{\n toggle: [];\n}>();\n\nfunction toggle() {\n emit(\"toggle\");\n sidebar?.toggle();\n}\n\n// Close menu on escape\nfunction handleEscapeKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n if (sidebar?.open?.value) {\n sidebar.open.value = false;\n }\n }\n}\n\nconst fallbackId = useId();\n</script>\n<template>\n <button\n :id=\"`${sidebar?.id ?? fallbackId}-hamburger`\"\n class=\"g-hamburger-button\"\n :class=\"{\n 'g-hamburger-button--open': sidebar?.open?.value,\n 'g-hamburger-button--collapsible': sidebar?.isCollapsible?.value\n }\"\n @click=\"toggle\"\n @keydown=\"handleEscapeKey\"\n :aria-expanded=\"sidebar?.open?.value ? 'true' : 'false'\"\n :aria-label=\"label\"\n :aria-controls=\"sidebar ? `${sidebar.id}-sidebar` : undefined\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 51.26 51.26\">\n <g fill=\"currentColor\">\n <path\n d=\"M11.6 16.52h28.06a3.24 3.24 0 1 0 0-6.48H11.6a3.24 3.24 0 0 0 0 6.48ZM39.66 22.07H11.6a3.24 3.24 0 0 0 0 6.48h28.06a3.24 3.24 0 1 0 0-6.48ZM39.66 34.1H11.6a3.24 3.24 0 0 0 0 6.48h28.06a3.24 3.24 0 1 0 0-6.48Z\"\n />\n </g>\n </svg>\n </button>\n</template>\n\n<style>\ng-hamburger-menu:not(:defined) {\n display: none;\n}\n\n.g-hamburger-button {\n svg {\n width: 1.6rem;\n }\n}\n.g-hamburger-button {\n width: 34px;\n height: 34px;\n padding: 0;\n display: none;\n justify-content: center;\n align-items: center;\n text-decoration: none;\n border: 2px solid var(--g-primary-500);\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n border-radius: 4px;\n cursor: pointer;\n\n &:hover {\n background: var(--g-primary-text);\n color: var(--g-primary-500);\n }\n &:active {\n background: var(--g-accent-500);\n color: var(--g-primary-text);\n }\n &:focus-visible {\n color: var(--ilw-color--focus--text);\n background: var(--ilw-color--focus--background);\n outline-color: var(--g-primary-500);\n }\n}\n.g-hamburger-button--collapsible {\n display: flex;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * This component is used with the `GDetailListItem` component to display\n * a list of key-value pairs in a grid or vertical layout.\n *\n * For example:\n *\n * ```vue-html\n * <GDetailList>\n * <GDetailListItem label=\"Name\">John Doe</GDetailListItem>\n * <GDetailListItem label=\"Age\">30</GDetailListItem>\n * <GDetailListItem label=\"City\">New York</GDetailListItem>\n * </GDetailList>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Layout style for the items.\n * @demo\n */\n variant?: \"grid\" | \"vertical\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: \"grid\",\n});\n</script>\n\n<template>\n <dl\n class=\"g-detail-list\"\n :class=\"`g-detail-list--${props.variant}`\"\n >\n <slot />\n </dl>\n</template>\n\n<style>\ng-detail-list,\n.g-detail-list {\n margin: 0;\n display: block;\n}\n.g-detail-list--grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--g-detail-list-item-min-width, 10rem), var(--g-detail-list-item-max-width, 1fr)));\n column-gap: 2.5rem;\n row-gap: 1.5rem;\n align-items: start;\n}\n\n.g-detail-list--grid .g-detail-list-item {\n display: flex;\n flex-direction: column;\n border-left: 2px solid var(--g-accent-500);\n padding-left: 0.75rem;\n}\n\n.g-detail-list--grid .g-detail-list-item__label {\n font-size: 0.875rem;\n}\n\n.g-detail-list--vertical {\n display: flex;\n flex-direction: column;\n}\n\n.g-detail-list--vertical .g-detail-list-item {\n display: grid;\n grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);\n column-gap: 1rem;\n row-gap: 0.25rem;\n padding: 0.75rem 0;\n border-bottom: 1px solid var(--g-surface-200);\n}\n\n.g-detail-list--vertical .g-detail-list-item:last-child {\n border-bottom: none;\n}\n\n.g-detail-list--vertical .g-detail-list-item__label {\n font-size: 1rem;\n align-self: start;\n}\n\n.g-detail-list--vertical .g-detail-list-item__value {\n justify-self: end;\n text-align: right;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * This component is used with the `GDetailListItem` component to display\n * a list of key-value pairs in a grid or vertical layout.\n *\n * For example:\n *\n * ```vue-html\n * <GDetailList>\n * <GDetailListItem label=\"Name\">John Doe</GDetailListItem>\n * <GDetailListItem label=\"Age\">30</GDetailListItem>\n * <GDetailListItem label=\"City\">New York</GDetailListItem>\n * </GDetailList>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\ntype Props = {\n /**\n * Layout style for the items.\n * @demo\n */\n variant?: \"grid\" | \"vertical\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: \"grid\",\n});\n</script>\n\n<template>\n <dl\n class=\"g-detail-list\"\n :class=\"`g-detail-list--${props.variant}`\"\n >\n <slot />\n </dl>\n</template>\n\n<style>\ng-detail-list,\n.g-detail-list {\n margin: 0;\n display: block;\n}\n.g-detail-list--grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--g-detail-list-item-min-width, 10rem), var(--g-detail-list-item-max-width, 1fr)));\n column-gap: 2.5rem;\n row-gap: 1.5rem;\n align-items: start;\n}\n\n.g-detail-list--grid .g-detail-list-item {\n display: flex;\n flex-direction: column;\n border-left: 2px solid var(--g-accent-500);\n padding-left: 0.75rem;\n}\n\n.g-detail-list--grid .g-detail-list-item__label {\n font-size: 0.875rem;\n}\n\n.g-detail-list--vertical {\n display: flex;\n flex-direction: column;\n}\n\n.g-detail-list--vertical .g-detail-list-item {\n display: grid;\n grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);\n column-gap: 1rem;\n row-gap: 0.25rem;\n padding: 0.75rem 0;\n border-bottom: 1px solid var(--g-surface-200);\n}\n\n.g-detail-list--vertical .g-detail-list-item:last-child {\n border-bottom: none;\n}\n\n.g-detail-list--vertical .g-detail-list-item__label {\n font-size: 1rem;\n align-self: start;\n}\n\n.g-detail-list--vertical .g-detail-list-item__value {\n justify-self: end;\n text-align: right;\n}\n</style>\n\n","<script setup lang=\"ts\">\ninterface Props {\n /**\n * Label shown for the item.\n */\n label: string;\n}\n\ndefineProps<Props>();\n</script>\n\n<template>\n <div class=\"g-detail-list-item\">\n <dt class=\"g-detail-list-item__label\">\n <slot name=\"label\">{{ label }}</slot>\n </dt>\n <dd class=\"g-detail-list-item__value\">\n <slot />\n </dd>\n </div>\n</template>\n\n<style>\n.g-detail-list-item {\n margin: 0;\n}\n\n.g-detail-list-item__label {\n margin: 0;\n font-weight: 700;\n color: var(--g-primary-500);\n}\n\n.g-detail-list-item__value {\n margin: 0;\n}\n</style>\n","<script setup lang=\"ts\">\ninterface Props {\n /**\n * Label shown for the item.\n */\n label: string;\n}\n\ndefineProps<Props>();\n</script>\n\n<template>\n <div class=\"g-detail-list-item\">\n <dt class=\"g-detail-list-item__label\">\n <slot name=\"label\">{{ label }}</slot>\n </dt>\n <dd class=\"g-detail-list-item__value\">\n <slot />\n </dd>\n </div>\n</template>\n\n<style>\n.g-detail-list-item {\n margin: 0;\n}\n\n.g-detail-list-item__label {\n margin: 0;\n font-weight: 700;\n color: var(--g-primary-500);\n}\n\n.g-detail-list-item__value {\n margin: 0;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Client-side overlay for behind modal dialogs.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useOverlayStackState } from \"../compose/useOverlayStack.ts\";\n\ntype Props = {\n\n}\n\nconst { hasScrollLock } = useOverlayStackState();\n</script>\n\n<template>\n <Transition name=\"g-fade\">\n <div v-if=\"hasScrollLock\" class=\"g-scroll-lock-overlay\"></div>\n </Transition>\n</template>\n\n<style>\n@layer override {\n body.g-scroll-lock {\n overflow: hidden;\n }\n\n .g-scroll-lock-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.25);\n z-index: 199;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Client-side overlay for behind modal dialogs.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { useOverlayStackState } from \"../compose/useOverlayStack.ts\";\n\ntype Props = {\n\n}\n\nconst { hasScrollLock } = useOverlayStackState();\n</script>\n\n<template>\n <Transition name=\"g-fade\">\n <div v-if=\"hasScrollLock\" class=\"g-scroll-lock-overlay\"></div>\n </Transition>\n</template>\n\n<style>\n@layer override {\n body.g-scroll-lock {\n overflow: hidden;\n }\n\n .g-scroll-lock-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.25);\n z-index: 199;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Prebuilt GSelect and GSelectButton components that can be used to\n * select a term.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GSelect from \"../GSelect.vue\";\nimport GSelectButton from \"../GSelectButton.vue\";\n\ntype Props = {\n /**\n * List of possible term years. Defaults to [\"2026\"].\n */\n termYears?: string[];\n /**\n * List of possible term names. Defaults to [\"Spring\", \"Summer\", \"Fall\"].\n */\n termNames?: string[];\n\n /**\n * Label for year select. Defaults to \"Select Year\".\n */\n yearLabel?: string;\n\n /**\n * Label for period select. Defaults to \"Term\".\n */\n periodLabel?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n termYears: () => [\"2026\"],\n termNames: () => [\"Spring\", \"Summer\", \"Fall\"],\n yearLabel: \"Select Year\",\n periodLabel: \"Term\",\n});\n\nconst term = defineModel<{year: string, name: string}>({\n default: () => ({year: \"2026\", name: \"Spring\"}),\n});\n</script>\n\n<template>\n <div class=\"popover-content\">\n <div class=\"year-dropdown\">\n <GSelect\n v-model=\"term.year\"\n :options=\"termYears\"\n :label=\"yearLabel\"\n />\n </div>\n <div class=\"month-selector\">\n <GSelectButton\n v-model=\"term.name\"\n :options=\"termNames\"\n :allow-empty=\"false\"\n :label=\"periodLabel\"\n />\n </div>\n </div>\n</template>\n\n<style>\n.year-dropdown {\n display: flex;\n justify-content: left;\n margin: 1rem 0;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * Prebuilt GSelect and GSelectButton components that can be used to\n * select a term.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GSelect from \"../GSelect.vue\";\nimport GSelectButton from \"../GSelectButton.vue\";\n\ntype Props = {\n /**\n * List of possible term years. Defaults to [\"2026\"].\n */\n termYears?: string[];\n /**\n * List of possible term names. Defaults to [\"Spring\", \"Summer\", \"Fall\"].\n */\n termNames?: string[];\n\n /**\n * Label for year select. Defaults to \"Select Year\".\n */\n yearLabel?: string;\n\n /**\n * Label for period select. Defaults to \"Term\".\n */\n periodLabel?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n termYears: () => [\"2026\"],\n termNames: () => [\"Spring\", \"Summer\", \"Fall\"],\n yearLabel: \"Select Year\",\n periodLabel: \"Term\",\n});\n\nconst term = defineModel<{year: string, name: string}>({\n default: () => ({year: \"2026\", name: \"Spring\"}),\n});\n</script>\n\n<template>\n <div class=\"popover-content\">\n <div class=\"year-dropdown\">\n <GSelect\n v-model=\"term.year\"\n :options=\"termYears\"\n :label=\"yearLabel\"\n />\n </div>\n <div class=\"month-selector\">\n <GSelectButton\n v-model=\"term.name\"\n :options=\"termNames\"\n :allow-empty=\"false\"\n :label=\"periodLabel\"\n />\n </div>\n </div>\n</template>\n\n<style>\n.year-dropdown {\n display: flex;\n justify-content: left;\n margin: 1rem 0;\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A button that shows the selected term. Clicking it opens a popover\n * that allows jumping to a different term.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTermSelectorControl from \"./term/GTermSelectorControl.vue\";\nimport GButton from \"./GButton.vue\";\nimport GPopover from \"./GPopover.vue\";\n\ntype Props = {\n /**\n * Heading for the popover.\n * @demo Period Selection\n */\n heading?: string;\n\n /**\n * Label for year select.\n * @demo Select Year\n */\n yearLabel?: string;\n\n /**\n * Label for period select.\n * @demo Term\n */\n periodLabel?: string;\n\n /**\n * List of possible term years\n */\n termYears?: string[];\n\n /**\n * List of possible term names\n */\n termNames?: string[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n heading: \"Period Selection\",\n termYears: () => [\"2026\"],\n termNames: () => [\"Spring\", \"Summer\", \"Fall\"],\n});\n\nconst term = defineModel<{year: string, name: string}>({\n default: () => ({year: \"2026\", name: \"Spring\"}),\n});\n</script>\n\n<template>\n <div class=\"g-term-selector\">\n <GPopover>\n <template #trigger=\"{ toggle }\">\n <GButton class=\"g-term-selector-button\" theme=\"none\" outlined @click=\"toggle\">\n <span class=\"g-calendar-icon\">\n <svg role=\"none presentation\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d=\"M224 64C206.3 64 192 78.3 192 96L192 128L160 128C124.7 128 96 156.7 96 192L96 240L544 240L544 192C544 156.7 515.3 128 480 128L448 128L448 96C448 78.3 433.7 64 416 64C398.3 64 384 78.3 384 96L384 128L256 128L256 96C256 78.3 241.7 64 224 64zM96 288L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 288L96 288z\"/></svg>\n </span>\n <span class=\"g-term-label\"> {{ term?.name }} {{ term?.year }} </span>\n <span class=\"g-caret\">\n <svg role=\"none presentation\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d=\"M300.3 440.8C312.9 451 331.4 450.3 343.1 438.6L471.1 310.6C480.3 301.4 483 287.7 478 275.7C473 263.7 461.4 256 448.5 256L192.5 256C179.6 256 167.9 263.8 162.9 275.8C157.9 287.8 160.7 301.5 169.9 310.6L297.9 438.6L300.3 440.8z\"/></svg>\n </span>\n </GButton>\n </template>\n <h2 class=\"g-popover-title\" tabindex=\"-1\">{{ heading}}</h2>\n <GTermSelectorControl v-bind=\"$props\" />\n </GPopover>\n </div>\n</template>\n\n<style>\n\n.g-popover-title {\n font-size: 1rem;\n display: block;\n margin: -1.5rem -1rem 0;\n padding: 0.5rem 1rem;\n background: var(--g-surface-50);\n font-weight: 600;\n color: var(--g-accent-700);\n text-align: center;\n}\n.g-term-selector .g-term-selector-button {\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n border-color: var(--g-primary-500);\n height: 2.35rem;\n padding: 0 8px 0 0;\n text-decoration: none;\n font-size: 1rem;\n\n .g-calendar-icon {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n display: flex;\n align-items: center;\n\n padding: 0 10px;\n height: 100%;\n\n svg {\n width: 1.5rem;\n fill: currentColor;\n stroke: currentColor;\n }\n }\n\n &:hover .g-term-label {\n text-decoration: underline;\n }\n\n &:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n .g-caret {\n pointer-events: none;\n color: var(--ilw-color--link-hover);\n width: 20px;\n }\n}\n\n\n.g-term-label {\n width: 120px;\n padding-top: 2px;\n @media screen and (max-width: 1000px) {\n width: 70px;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * A button that shows the selected term. Clicking it opens a popover\n * that allows jumping to a different term.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTermSelectorControl from \"./term/GTermSelectorControl.vue\";\nimport GButton from \"./GButton.vue\";\nimport GPopover from \"./GPopover.vue\";\n\ntype Props = {\n /**\n * Heading for the popover.\n * @demo Period Selection\n */\n heading?: string;\n\n /**\n * Label for year select.\n * @demo Select Year\n */\n yearLabel?: string;\n\n /**\n * Label for period select.\n * @demo Term\n */\n periodLabel?: string;\n\n /**\n * List of possible term years\n */\n termYears?: string[];\n\n /**\n * List of possible term names\n */\n termNames?: string[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n heading: \"Period Selection\",\n termYears: () => [\"2026\"],\n termNames: () => [\"Spring\", \"Summer\", \"Fall\"],\n});\n\nconst term = defineModel<{year: string, name: string}>({\n default: () => ({year: \"2026\", name: \"Spring\"}),\n});\n</script>\n\n<template>\n <div class=\"g-term-selector\">\n <GPopover>\n <template #trigger=\"{ toggle }\">\n <GButton class=\"g-term-selector-button\" theme=\"none\" outlined @click=\"toggle\">\n <span class=\"g-calendar-icon\">\n <svg role=\"none presentation\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d=\"M224 64C206.3 64 192 78.3 192 96L192 128L160 128C124.7 128 96 156.7 96 192L96 240L544 240L544 192C544 156.7 515.3 128 480 128L448 128L448 96C448 78.3 433.7 64 416 64C398.3 64 384 78.3 384 96L384 128L256 128L256 96C256 78.3 241.7 64 224 64zM96 288L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 288L96 288z\"/></svg>\n </span>\n <span class=\"g-term-label\"> {{ term?.name }} {{ term?.year }} </span>\n <span class=\"g-caret\">\n <svg role=\"none presentation\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 640\"><!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d=\"M300.3 440.8C312.9 451 331.4 450.3 343.1 438.6L471.1 310.6C480.3 301.4 483 287.7 478 275.7C473 263.7 461.4 256 448.5 256L192.5 256C179.6 256 167.9 263.8 162.9 275.8C157.9 287.8 160.7 301.5 169.9 310.6L297.9 438.6L300.3 440.8z\"/></svg>\n </span>\n </GButton>\n </template>\n <h2 class=\"g-popover-title\" tabindex=\"-1\">{{ heading}}</h2>\n <GTermSelectorControl v-bind=\"$props\" />\n </GPopover>\n </div>\n</template>\n\n<style>\n\n.g-popover-title {\n font-size: 1rem;\n display: block;\n margin: -1.5rem -1rem 0;\n padding: 0.5rem 1rem;\n background: var(--g-surface-50);\n font-weight: 600;\n color: var(--g-accent-700);\n text-align: center;\n}\n.g-term-selector .g-term-selector-button {\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n border-color: var(--g-primary-500);\n height: 2.35rem;\n padding: 0 8px 0 0;\n text-decoration: none;\n font-size: 1rem;\n\n .g-calendar-icon {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n display: flex;\n align-items: center;\n\n padding: 0 10px;\n height: 100%;\n\n svg {\n width: 1.5rem;\n fill: currentColor;\n stroke: currentColor;\n }\n }\n\n &:hover .g-term-label {\n text-decoration: underline;\n }\n\n &:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n }\n\n .g-caret {\n pointer-events: none;\n color: var(--ilw-color--link-hover);\n width: 20px;\n }\n}\n\n\n.g-term-label {\n width: 120px;\n padding-top: 2px;\n @media screen and (max-width: 1000px) {\n width: 70px;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * User menu component for toolbars. Displays a button with the user's initials\n * inside a colored circle. When clicked, it opens a popover with the user's\n * email and a menu for account-related links.\n *\n * **Slots**:\n * - `default` contains menu items (links or buttons) that will be wrapped in\n * an unordered list for accessibility.\n *\n * **Props**:\n * - `initials` - User's initials to display in the avatar\n * - `email` - User's email to display in the popover\n * - `color` - Background color for the avatar (should be deterministic)\n * - `label` - Accessible label for the menu button. The initial will be prepended to this for the full label.\n *\n * Example:\n *\n * ```vue-html\n * <GUserMenu\n * initials=\"J\"\n * email=\"j@example.com\"\n * color=\"#4A90E2\"\n * >\n * <router-link to=\"/profile\">Profile</router-link>\n * <a href=\"/settings\">Settings</a>\n * <button @click=\"handleLogout\">Logout</button>\n * </GUserMenu>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { getCurrentInstance, ref, useId, useSlots, useTemplateRef } from \"vue\";\nimport GPopover from \"./GPopover.vue\";\n\ntype Props = {\n /**\n * User initial(s)\n * @demo J\n */\n initials: string;\n /**\n * User email\n * @demo j@example.org\n */\n email: string;\n /**\n * Background color\n * @demo\n */\n color?: string;\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"User menu\",\n color: \"var(--g-surface-700)\",\n});\n\nconst id = useId();\nconst emailHeadingId = `${id}-email`;\nconst open = ref(false);\n\n// Get the heading ref to focus it when the popover opens\nconst emailHeading = useTemplateRef<HTMLElement>(\"emailHeading\");\n\n// Detect vue-router without adding it as a dependency\nconst instance = getCurrentInstance();\nconst RouterLinkComp = instance?.appContext?.components?.RouterLink as\n | any\n | undefined;\n\nconst slots = defineSlots<{\n default(): any\n}>();\n\n</script>\n\n<template>\n <div class=\"g-user-menu\">\n <GPopover v-model=\"open\" minimal>\n <template #trigger=\"{ toggle }\">\n <button\n class=\"g-user-menu__avatar\"\n :style=\"{ backgroundColor: color }\"\n :aria-label=\"initials + ' - ' + label\"\n :aria-expanded=\"open\"\n aria-haspopup=\"menu\"\n @click=\"toggle\"\n >\n {{ initials }}\n </button>\n </template>\n <div class=\"g-user-menu__popover\">\n <h2\n :id=\"emailHeadingId\"\n ref=\"emailHeading\"\n class=\"g-user-menu__email\"\n tabindex=\"-1\"\n >\n {{ email }}\n </h2>\n <nav class=\"g-user-menu__nav\" :aria-labelledby=\"emailHeadingId\">\n <ul class=\"g-user-menu__list\">\n <li v-for=\"(link, index) in slots.default()\" :key=\"index\">\n <component :is=\"link\"></component>\n </li>\n </ul>\n </nav>\n </div>\n </GPopover>\n </div>\n</template>\n\n<style>\ng-user-menu {\n display: inline-block;\n}\n.g-user-menu {\n display: inline-block;\n}\n\n.g-user-menu__avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n color: var(--g-surface-0);\n font-weight: 700;\n font-size: 1rem;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition:\n transform 0.2s,\n box-shadow 0.2s;\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__avatar:hover {\n text-decoration: underline;\n}\n\n.g-user-menu__avatar:focus-visible {\n background-color: var(--ilw-color--focus--background) !important;\n color: var(--ilw-color--focus--text) !important;\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__popover {\n min-width: 200px;\n}\n\n.g-user-menu__email {\n margin: 0.75rem 1rem 0.25rem;\n font-size: 1rem;\n font-weight: normal;\n color: var(--g-primary-500);\n word-break: break-word;\n}\n\n.g-user-menu__nav {\n margin: 0;\n}\n\n.g-user-menu__list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 0;\n}\n\n.g-user-menu__list a,\n.g-user-menu__list button {\n display: block;\n padding: 0.75rem 1rem;\n box-sizing: border-box;\n color: var(--g-primary-500);\n text-decoration: none;\n border: none;\n background: none;\n font-size: 1rem;\n font-weight: 600;\n font-family: var(--il-font-sans);\n text-align: left;\n cursor: pointer;\n width: 100%;\n}\n\n.g-user-menu__list a:hover,\n.g-user-menu__list button:hover {\n color: var(--g-accent-700);\n text-decoration: underline;\n}\n\n.g-user-menu__list a:focus,\n.g-user-menu__list button:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__list a:active,\n.g-user-menu__list button:active {\n background-color: var(--g-accent-700);\n color: var(--g-surface-0);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * User menu component for toolbars. Displays a button with the user's initials\n * inside a colored circle. When clicked, it opens a popover with the user's\n * email and a menu for account-related links.\n *\n * **Slots**:\n * - `default` contains menu items (links or buttons) that will be wrapped in\n * an unordered list for accessibility.\n *\n * **Props**:\n * - `initials` - User's initials to display in the avatar\n * - `email` - User's email to display in the popover\n * - `color` - Background color for the avatar (should be deterministic)\n * - `label` - Accessible label for the menu button. The initial will be prepended to this for the full label.\n *\n * Example:\n *\n * ```vue-html\n * <GUserMenu\n * initials=\"J\"\n * email=\"j@example.com\"\n * color=\"#4A90E2\"\n * >\n * <router-link to=\"/profile\">Profile</router-link>\n * <a href=\"/settings\">Settings</a>\n * <button @click=\"handleLogout\">Logout</button>\n * </GUserMenu>\n * ```\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { getCurrentInstance, ref, useId, useSlots, useTemplateRef } from \"vue\";\nimport GPopover from \"./GPopover.vue\";\n\ntype Props = {\n /**\n * User initial(s)\n * @demo J\n */\n initials: string;\n /**\n * User email\n * @demo j@example.org\n */\n email: string;\n /**\n * Background color\n * @demo\n */\n color?: string;\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: \"User menu\",\n color: \"var(--g-surface-700)\",\n});\n\nconst id = useId();\nconst emailHeadingId = `${id}-email`;\nconst open = ref(false);\n\n// Get the heading ref to focus it when the popover opens\nconst emailHeading = useTemplateRef<HTMLElement>(\"emailHeading\");\n\n// Detect vue-router without adding it as a dependency\nconst instance = getCurrentInstance();\nconst RouterLinkComp = instance?.appContext?.components?.RouterLink as\n | any\n | undefined;\n\nconst slots = defineSlots<{\n default(): any\n}>();\n\n</script>\n\n<template>\n <div class=\"g-user-menu\">\n <GPopover v-model=\"open\" minimal>\n <template #trigger=\"{ toggle }\">\n <button\n class=\"g-user-menu__avatar\"\n :style=\"{ backgroundColor: color }\"\n :aria-label=\"initials + ' - ' + label\"\n :aria-expanded=\"open\"\n aria-haspopup=\"menu\"\n @click=\"toggle\"\n >\n {{ initials }}\n </button>\n </template>\n <div class=\"g-user-menu__popover\">\n <h2\n :id=\"emailHeadingId\"\n ref=\"emailHeading\"\n class=\"g-user-menu__email\"\n tabindex=\"-1\"\n >\n {{ email }}\n </h2>\n <nav class=\"g-user-menu__nav\" :aria-labelledby=\"emailHeadingId\">\n <ul class=\"g-user-menu__list\">\n <li v-for=\"(link, index) in slots.default()\" :key=\"index\">\n <component :is=\"link\"></component>\n </li>\n </ul>\n </nav>\n </div>\n </GPopover>\n </div>\n</template>\n\n<style>\ng-user-menu {\n display: inline-block;\n}\n.g-user-menu {\n display: inline-block;\n}\n\n.g-user-menu__avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n color: var(--g-surface-0);\n font-weight: 700;\n font-size: 1rem;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition:\n transform 0.2s,\n box-shadow 0.2s;\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__avatar:hover {\n text-decoration: underline;\n}\n\n.g-user-menu__avatar:focus-visible {\n background-color: var(--ilw-color--focus--background) !important;\n color: var(--ilw-color--focus--text) !important;\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__popover {\n min-width: 200px;\n}\n\n.g-user-menu__email {\n margin: 0.75rem 1rem 0.25rem;\n font-size: 1rem;\n font-weight: normal;\n color: var(--g-primary-500);\n word-break: break-word;\n}\n\n.g-user-menu__nav {\n margin: 0;\n}\n\n.g-user-menu__list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 0;\n}\n\n.g-user-menu__list a,\n.g-user-menu__list button {\n display: block;\n padding: 0.75rem 1rem;\n box-sizing: border-box;\n color: var(--g-primary-500);\n text-decoration: none;\n border: none;\n background: none;\n font-size: 1rem;\n font-weight: 600;\n font-family: var(--il-font-sans);\n text-align: left;\n cursor: pointer;\n width: 100%;\n}\n\n.g-user-menu__list a:hover,\n.g-user-menu__list button:hover {\n color: var(--g-accent-700);\n text-decoration: underline;\n}\n\n.g-user-menu__list a:focus,\n.g-user-menu__list button:focus {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline-color: var(--g-primary-500);\n}\n\n.g-user-menu__list a:active,\n.g-user-menu__list button:active {\n background-color: var(--g-accent-700);\n color: var(--g-surface-0);\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A currency input component for US dollars.\n *\n * This component is a wrapper around a text input with a prefix and\n * appropriate input type for currency values.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"props.name\"\n :label=\"props.label\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n :errors=\"props.errors\"\n :instructions=\"props.instructions\"\n :form-key=\"props.formKey\"\n prefix=\"$\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-currency-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * A currency input component for US dollars.\n *\n * This component is a wrapper around a text input with a prefix and\n * appropriate input type for currency values.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"props.name\"\n :label=\"props.label\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n :errors=\"props.errors\"\n :instructions=\"props.instructions\"\n :form-key=\"props.formKey\"\n prefix=\"$\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-currency-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * An email input component.\n * \n * This component is a wrapper around GTextInput with type=\"email\" for\n * proper email validation and mobile keyboard optimization.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"name\"\n :label=\"label\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :required=\"required\"\n :errors=\"errors\"\n :instructions=\"instructions\"\n type=\"email\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-email-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * An email input component.\n * \n * This component is a wrapper around GTextInput with type=\"email\" for\n * proper email validation and mobile keyboard optimization.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"name\"\n :label=\"label\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :required=\"required\"\n :errors=\"errors\"\n :instructions=\"instructions\"\n type=\"email\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-email-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * A file input component for accessible file uploads.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings. Multiple errors will all be\n * displayed. Client-side validation errors from `maxFileSize` and `maxFiles`\n * are shown alongside any provided `errors`.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, ref, useId, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Upload File\n */\n label?: string;\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n /**\n * Accepted file types (e.g. \".pdf,.docx\" or \"image/*\")\n * @demo\n */\n accept?: string;\n /**\n * Allow multiple file selection\n * @demo\n */\n multiple?: boolean;\n /**\n * Maximum file size in bytes for client-side validation\n * @demo\n */\n maxFileSize?: number;\n /**\n * Maximum number of files allowed for client-side validation\n * @demo\n */\n maxFiles?: number;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n instructions: \"\",\n disabled: false,\n errors: () => [],\n required: false,\n multiple: false,\n});\n\nconst model = defineModel<File[]>({ default: () => [] });\n\nconst id = useId();\n\nconst { displayErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [files: File[]];\n}>();\n\nconst validationErrors = ref<string[]>([]);\n\nfunction validateFiles(files: File[]): string[] {\n const errs: string[] = [];\n\n if (props.maxFiles !== undefined && files.length > props.maxFiles) {\n errs.push(\n `You may select at most ${props.maxFiles} file${props.maxFiles === 1 ? \"\" : \"s\"}.`,\n );\n }\n\n if (props.maxFileSize !== undefined) {\n const oversized = files.filter((f) => f.size > props.maxFileSize!);\n if (oversized.length > 0) {\n const maxMB = (props.maxFileSize / (1024 * 1024)).toFixed(1);\n errs.push(\n `${oversized.length === 1 ? \"One file exceeds\" : `${oversized.length} files exceed`} the maximum size of ${maxMB} MB.`,\n );\n }\n }\n\n return errs;\n}\n\nfunction onFileChange(e: Event) {\n const input = e.target as HTMLInputElement;\n const files = Array.from(input.files ?? []);\n validationErrors.value = validateFiles(files);\n model.value = files;\n emit(\"change\", files);\n}\n\nconst allErrors = computed(() => [\n ...displayErrors.value,\n ...validationErrors.value,\n]);\n\nconst hasErrors = computed(() => allErrors.value.length > 0);\n\nconst selectedFileNames = computed(() => model.value.map((f) => f.name));\n</script>\n\n<template>\n <div\n class=\"g-file-input-wrap\"\n :class=\"{ 'g-file-input-has-error': hasErrors }\"\n >\n <label v-if=\"label\" :for=\"id\" class=\"g-file-input-label\">\n {{ label }}\n <span v-if=\"required\" class=\"g-file-input-required\">*</span>\n </label>\n\n <div\n class=\"g-file-input-box\"\n :class=\"{ 'g-file-input-box--disabled': disabled }\"\n >\n <div v-if=\"instructions\" class=\"g-file-input-box-header\">\n <span\n :id=\"'instructions-' + id\"\n class=\"g-file-input-instructions\"\n >{{ instructions }}</span\n >\n </div>\n\n <input\n :id=\"id\"\n type=\"file\"\n class=\"g-file-input\"\n :disabled=\"disabled\"\n :required=\"required\"\n :accept=\"accept || undefined\"\n :multiple=\"multiple\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n :aria-describedby=\"\n instructions ? 'instructions-' + id : undefined\n \"\n :aria-errormessage=\"\n hasErrors ? 'error-message-' + id : undefined\n \"\n @change=\"onFileChange\"\n />\n\n <ul\n v-if=\"multiple && selectedFileNames.length > 0\"\n class=\"g-file-input-pills\"\n aria-label=\"Selected files\"\n >\n <li\n v-for=\"name in selectedFileNames\"\n :key=\"name\"\n class=\"g-file-input-pill\"\n >\n <svg\n class=\"g-file-input-pill-icon\"\n role=\"none presentation\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n >\n <!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n d=\"M192 64C156.7 64 128 92.7 128 128L128 512C128 547.3 156.7 576 192 576L448 576C483.3 576 512 547.3 512 512L512 234.5C512 217.5 505.3 201.2 493.3 189.2L386.7 82.7C374.7 70.7 358.5 64 341.5 64L192 64zM453.5 240L360 240C346.7 240 336 229.3 336 216L336 122.5L453.5 240z\"\n />\n </svg>\n {{ name }}\n </li>\n </ul>\n </div>\n\n <GFormErrorMessages :errors=\"allErrors\" :id=\"'error-message-' + id\" />\n </div>\n</template>\n\n<style>\ng-file-input {\n display: block;\n}\n.g-file-input-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n.g-file-input-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n\n.g-file-input-required {\n color: var(--g-danger-600);\n margin-left: 0.2em;\n}\n\n.g-file-input-box {\n display: flex;\n max-width: 360px;\n flex-direction: column;\n gap: 0.75em;\n border: 1px solid var(--g-primary-500);\n background: var(--g-surface-50);\n padding: 0.75em;\n}\n\n.g-file-input-box--disabled {\n border-color: var(--g-surface-400);\n background: var(--g-surface-100);\n}\n\n.g-file-input-has-error .g-file-input-box {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-file-input-box-header {\n display: flex;\n align-items: center;\n gap: 0.5em;\n color: var(--g-surface-700);\n}\n\n.g-file-input-upload-icon {\n flex-shrink: 0;\n height: 2em;\n width: 2em;\n color: var(--g-primary-500);\n}\n\n.g-file-input-box--disabled .g-file-input-upload-icon {\n color: var(--g-surface-600);\n}\n\n.g-file-input-instructions {\n font-size: 0.9em;\n color: var(--g-surface-900);\n}\n\n.g-file-input {\n font-size: 1em;\n font-family: var(--il-font-sans);\n color: var(--g-surface-950);\n background: transparent;\n border: none;\n padding: 0;\n cursor: pointer;\n width: 100%;\n}\n\n.g-file-input::file-selector-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--il-font-sans);\n font-weight: 700;\n font-size: 19px;\n line-height: 20px;\n border: 2px solid var(--g-primary-500);\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n cursor: pointer;\n padding: 12px 20px;\n border-radius: var(--g-border-radius-m);\n text-decoration: none;\n}\n\n.g-file-input::file-selector-button:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n}\n\n.g-file-input:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n border-radius: var(--g-border-radius-s);\n}\n\n.g-file-input:disabled {\n cursor: not-allowed;\n color: var(--g-surface-800);\n}\n\n.g-file-input:disabled::file-selector-button {\n background: var(--g-surface-200);\n color: var(--g-surface-900);\n border-color: var(--g-surface-200);\n cursor: not-allowed;\n}\n\n.g-file-input-pills {\n display: flex;\n flex-wrap: wrap;\n gap: 0.4em;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.g-file-input-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.3em;\n border: 1px solid var(--g-primary-500);\n color: var(--g-surface-950);\n border-radius: 999px;\n padding: 0.2em 0.7em;\n font-size: 0.85em;\n max-width: 24em;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.g-file-input-pill-icon {\n flex-shrink: 0;\n height: 1em;\n width: 1em;\n opacity: 0.7;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A file input component for accessible file uploads.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings. Multiple errors will all be\n * displayed. Client-side validation errors from `maxFileSize` and `maxFiles`\n * are shown alongside any provided `errors`.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, ref, useId, toRef } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Upload File\n */\n label?: string;\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n /**\n * Accepted file types (e.g. \".pdf,.docx\" or \"image/*\")\n * @demo\n */\n accept?: string;\n /**\n * Allow multiple file selection\n * @demo\n */\n multiple?: boolean;\n /**\n * Maximum file size in bytes for client-side validation\n * @demo\n */\n maxFileSize?: number;\n /**\n * Maximum number of files allowed for client-side validation\n * @demo\n */\n maxFiles?: number;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n instructions: \"\",\n disabled: false,\n errors: () => [],\n required: false,\n multiple: false,\n});\n\nconst model = defineModel<File[]>({ default: () => [] });\n\nconst id = useId();\n\nconst { displayErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [files: File[]];\n}>();\n\nconst validationErrors = ref<string[]>([]);\n\nfunction validateFiles(files: File[]): string[] {\n const errs: string[] = [];\n\n if (props.maxFiles !== undefined && files.length > props.maxFiles) {\n errs.push(\n `You may select at most ${props.maxFiles} file${props.maxFiles === 1 ? \"\" : \"s\"}.`,\n );\n }\n\n if (props.maxFileSize !== undefined) {\n const oversized = files.filter((f) => f.size > props.maxFileSize!);\n if (oversized.length > 0) {\n const maxMB = (props.maxFileSize / (1024 * 1024)).toFixed(1);\n errs.push(\n `${oversized.length === 1 ? \"One file exceeds\" : `${oversized.length} files exceed`} the maximum size of ${maxMB} MB.`,\n );\n }\n }\n\n return errs;\n}\n\nfunction onFileChange(e: Event) {\n const input = e.target as HTMLInputElement;\n const files = Array.from(input.files ?? []);\n validationErrors.value = validateFiles(files);\n model.value = files;\n emit(\"change\", files);\n}\n\nconst allErrors = computed(() => [\n ...displayErrors.value,\n ...validationErrors.value,\n]);\n\nconst hasErrors = computed(() => allErrors.value.length > 0);\n\nconst selectedFileNames = computed(() => model.value.map((f) => f.name));\n</script>\n\n<template>\n <div\n class=\"g-file-input-wrap\"\n :class=\"{ 'g-file-input-has-error': hasErrors }\"\n >\n <label v-if=\"label\" :for=\"id\" class=\"g-file-input-label\">\n {{ label }}\n <span v-if=\"required\" class=\"g-file-input-required\">*</span>\n </label>\n\n <div\n class=\"g-file-input-box\"\n :class=\"{ 'g-file-input-box--disabled': disabled }\"\n >\n <div v-if=\"instructions\" class=\"g-file-input-box-header\">\n <span\n :id=\"'instructions-' + id\"\n class=\"g-file-input-instructions\"\n >{{ instructions }}</span\n >\n </div>\n\n <input\n :id=\"id\"\n type=\"file\"\n class=\"g-file-input\"\n :disabled=\"disabled\"\n :required=\"required\"\n :accept=\"accept || undefined\"\n :multiple=\"multiple\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n :aria-describedby=\"\n instructions ? 'instructions-' + id : undefined\n \"\n :aria-errormessage=\"\n hasErrors ? 'error-message-' + id : undefined\n \"\n @change=\"onFileChange\"\n />\n\n <ul\n v-if=\"multiple && selectedFileNames.length > 0\"\n class=\"g-file-input-pills\"\n aria-label=\"Selected files\"\n >\n <li\n v-for=\"name in selectedFileNames\"\n :key=\"name\"\n class=\"g-file-input-pill\"\n >\n <svg\n class=\"g-file-input-pill-icon\"\n role=\"none presentation\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n >\n <!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n d=\"M192 64C156.7 64 128 92.7 128 128L128 512C128 547.3 156.7 576 192 576L448 576C483.3 576 512 547.3 512 512L512 234.5C512 217.5 505.3 201.2 493.3 189.2L386.7 82.7C374.7 70.7 358.5 64 341.5 64L192 64zM453.5 240L360 240C346.7 240 336 229.3 336 216L336 122.5L453.5 240z\"\n />\n </svg>\n {{ name }}\n </li>\n </ul>\n </div>\n\n <GFormErrorMessages :errors=\"allErrors\" :id=\"'error-message-' + id\" />\n </div>\n</template>\n\n<style>\ng-file-input {\n display: block;\n}\n.g-file-input-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n.g-file-input-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n\n.g-file-input-required {\n color: var(--g-danger-600);\n margin-left: 0.2em;\n}\n\n.g-file-input-box {\n display: flex;\n max-width: 360px;\n flex-direction: column;\n gap: 0.75em;\n border: 1px solid var(--g-primary-500);\n background: var(--g-surface-50);\n padding: 0.75em;\n}\n\n.g-file-input-box--disabled {\n border-color: var(--g-surface-400);\n background: var(--g-surface-100);\n}\n\n.g-file-input-has-error .g-file-input-box {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-file-input-box-header {\n display: flex;\n align-items: center;\n gap: 0.5em;\n color: var(--g-surface-700);\n}\n\n.g-file-input-upload-icon {\n flex-shrink: 0;\n height: 2em;\n width: 2em;\n color: var(--g-primary-500);\n}\n\n.g-file-input-box--disabled .g-file-input-upload-icon {\n color: var(--g-surface-600);\n}\n\n.g-file-input-instructions {\n font-size: 0.9em;\n color: var(--g-surface-900);\n}\n\n.g-file-input {\n font-size: 1em;\n font-family: var(--il-font-sans);\n color: var(--g-surface-950);\n background: transparent;\n border: none;\n padding: 0;\n cursor: pointer;\n width: 100%;\n}\n\n.g-file-input::file-selector-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--il-font-sans);\n font-weight: 700;\n font-size: 19px;\n line-height: 20px;\n border: 2px solid var(--g-primary-500);\n background: var(--g-surface-0);\n color: var(--g-primary-500);\n cursor: pointer;\n padding: 12px 20px;\n border-radius: var(--g-border-radius-m);\n text-decoration: none;\n}\n\n.g-file-input::file-selector-button:hover {\n background: var(--g-primary-500);\n color: var(--g-surface-0);\n}\n\n.g-file-input:focus-visible {\n background: var(--ilw-color--focus--background);\n color: var(--ilw-color--focus--text);\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n border-radius: var(--g-border-radius-s);\n}\n\n.g-file-input:disabled {\n cursor: not-allowed;\n color: var(--g-surface-800);\n}\n\n.g-file-input:disabled::file-selector-button {\n background: var(--g-surface-200);\n color: var(--g-surface-900);\n border-color: var(--g-surface-200);\n cursor: not-allowed;\n}\n\n.g-file-input-pills {\n display: flex;\n flex-wrap: wrap;\n gap: 0.4em;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.g-file-input-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.3em;\n border: 1px solid var(--g-primary-500);\n color: var(--g-surface-950);\n border-radius: 999px;\n padding: 0.2em 0.7em;\n font-size: 0.85em;\n max-width: 24em;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.g-file-input-pill-icon {\n flex-shrink: 0;\n height: 1em;\n width: 1em;\n opacity: 0.7;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A date input component.\n *\n * This component is a wrapper around GTextInput with type=\"date\" for\n * proper date selection using the browser's native date picker.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"props.name\"\n :label=\"props.label\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n :errors=\"props.errors\"\n :instructions=\"props.instructions\"\n :form-key=\"props.formKey\"\n type=\"date\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-date-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * A date input component.\n *\n * This component is a wrapper around GTextInput with type=\"date\" for\n * proper date selection using the browser's native date picker.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport GTextInput from \"./GTextInput.vue\";\n\ntype Props = {\n /**\n * Label\n * @demo\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n</script>\n\n<template>\n <GTextInput\n v-model=\"model\"\n :name=\"props.name\"\n :label=\"props.label\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n :errors=\"props.errors\"\n :instructions=\"props.instructions\"\n :form-key=\"props.formKey\"\n type=\"date\"\n v-bind=\"$attrs\"\n />\n</template>\n\n<style>\ng-date-input {\n display: block;\n}\n/* No additional styles needed, using GTextInput styles */\n</style>\n\n","<script lang=\"ts\">\n/**\n * A date range input component with start and end dates.\n *\n * This component uses two GDateInput components laid out horizontally\n * to allow selecting a date range.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { ref, watch, toRef } from \"vue\";\nimport GDateInput from \"./GDateInput.vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\n\ntype Props = {\n /**\n * Label for the component\n * @demo\n */\n label?: string;\n /**\n * Label for the start date input\n * @demo\n */\n startLabel?: string;\n /**\n * Label for the end date input\n * @demo\n */\n endLabel?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n startLabel: \"Start Date\",\n endLabel: \"End Date\",\n instructions: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\ntype DateRange = {\n start: string | null;\n end: string | null;\n};\n\nconst model = defineModel<DateRange>({\n default: () => ({ start: null, end: null }),\n});\n\nconst startDate = ref<string | null>(model.value.start || null);\nconst endDate = ref<string | null>(model.value.end || null);\n\n// Use form field composable for form registration and error handling\nconst { displayErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nwatch([startDate, endDate], () => {\n model.value = {\n start: startDate.value,\n end: endDate.value,\n };\n});\n\nwatch(\n model,\n (newValue) => {\n if (newValue.start !== startDate.value) {\n startDate.value = newValue.start;\n }\n if (newValue.end !== endDate.value) {\n endDate.value = newValue.end;\n }\n },\n { deep: true }\n);\n</script>\n\n<template>\n <div class=\"g-date-range-input\">\n <div v-if=\"props.label\" class=\"g-date-range-input__label\">\n {{ props.label }}<span v-if=\"props.required\" class=\"g-date-range-input__required\" aria-hidden=\"true\"> *</span>\n </div>\n <div\n v-if=\"props.instructions\"\n class=\"g-date-range-input__instructions\"\n >\n {{ props.instructions }}\n </div>\n <div class=\"g-date-range-input__fields\">\n <GDateInput\n v-model=\"startDate\"\n :label=\"props.startLabel\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n class=\"g-date-range-input__field\"\n />\n <GDateInput\n v-model=\"endDate\"\n :label=\"props.endLabel\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n class=\"g-date-range-input__field\"\n />\n </div>\n <div v-if=\"displayErrors.length > 0\" class=\"g-date-range-input__errors\" role=\"alert\">\n <div\n v-for=\"(errorMsg, index) in displayErrors\"\n :key=\"index\"\n class=\"g-date-range-input__error\"\n >\n {{ errorMsg }}\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-date-range-input {\n display: block;\n}\n.g-date-range-input {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.g-date-range-input__label {\n font-size: 1.25em;\n font-weight: 600;\n}\n\n.g-date-range-input__required {\n color: var(--g-danger-600);\n}\n\n.g-date-range-input__instructions {\n margin: 0 0 0.25em 0.5em;\n color: var(--g-surface-800);\n}\n\n.g-date-range-input__fields {\n display: flex;\n flex-direction: row;\n gap: 1rem;\n align-items: flex-start;\n}\n\n.g-date-range-input__field {\n flex: 1;\n min-width: 0;\n}\n\n.g-date-range-input__errors {\n display: flex;\n flex-direction: column;\n gap: 0.25em;\n}\n\n.g-date-range-input__error {\n background: var(--g-surface-0);\n color: var(--g-danger-600);\n padding: 0.25em 0.5em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A date range input component with start and end dates.\n *\n * This component uses two GDateInput components laid out horizontally\n * to allow selecting a date range.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { ref, watch, toRef } from \"vue\";\nimport GDateInput from \"./GDateInput.vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\n\ntype Props = {\n /**\n * Label for the component\n * @demo\n */\n label?: string;\n /**\n * Label for the start date input\n * @demo\n */\n startLabel?: string;\n /**\n * Label for the end date input\n * @demo\n */\n endLabel?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n startLabel: \"Start Date\",\n endLabel: \"End Date\",\n instructions: \"\",\n disabled: false,\n required: false,\n errors: () => [],\n name: undefined,\n formKey: undefined,\n});\n\ntype DateRange = {\n start: string | null;\n end: string | null;\n};\n\nconst model = defineModel<DateRange>({\n default: () => ({ start: null, end: null }),\n});\n\nconst startDate = ref<string | null>(model.value.start || null);\nconst endDate = ref<string | null>(model.value.end || null);\n\n// Use form field composable for form registration and error handling\nconst { displayErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, 'errors'),\n formKey: props.formKey,\n});\n\nwatch([startDate, endDate], () => {\n model.value = {\n start: startDate.value,\n end: endDate.value,\n };\n});\n\nwatch(\n model,\n (newValue) => {\n if (newValue.start !== startDate.value) {\n startDate.value = newValue.start;\n }\n if (newValue.end !== endDate.value) {\n endDate.value = newValue.end;\n }\n },\n { deep: true }\n);\n</script>\n\n<template>\n <div class=\"g-date-range-input\">\n <div v-if=\"props.label\" class=\"g-date-range-input__label\">\n {{ props.label }}<span v-if=\"props.required\" class=\"g-date-range-input__required\" aria-hidden=\"true\"> *</span>\n </div>\n <div\n v-if=\"props.instructions\"\n class=\"g-date-range-input__instructions\"\n >\n {{ props.instructions }}\n </div>\n <div class=\"g-date-range-input__fields\">\n <GDateInput\n v-model=\"startDate\"\n :label=\"props.startLabel\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n class=\"g-date-range-input__field\"\n />\n <GDateInput\n v-model=\"endDate\"\n :label=\"props.endLabel\"\n :disabled=\"props.disabled\"\n :required=\"props.required\"\n class=\"g-date-range-input__field\"\n />\n </div>\n <div v-if=\"displayErrors.length > 0\" class=\"g-date-range-input__errors\" role=\"alert\">\n <div\n v-for=\"(errorMsg, index) in displayErrors\"\n :key=\"index\"\n class=\"g-date-range-input__error\"\n >\n {{ errorMsg }}\n </div>\n </div>\n </div>\n</template>\n\n<style>\ng-date-range-input {\n display: block;\n}\n.g-date-range-input {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.g-date-range-input__label {\n font-size: 1.25em;\n font-weight: 600;\n}\n\n.g-date-range-input__required {\n color: var(--g-danger-600);\n}\n\n.g-date-range-input__instructions {\n margin: 0 0 0.25em 0.5em;\n color: var(--g-surface-800);\n}\n\n.g-date-range-input__fields {\n display: flex;\n flex-direction: row;\n gap: 1rem;\n align-items: flex-start;\n}\n\n.g-date-range-input__field {\n flex: 1;\n min-width: 0;\n}\n\n.g-date-range-input__errors {\n display: flex;\n flex-direction: column;\n gap: 0.25em;\n}\n\n.g-date-range-input__error {\n background: var(--g-surface-0);\n color: var(--g-danger-600);\n padding: 0.25em 0.5em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A form wrapper component that automatically manages form state and\n * connects to child input components.\n *\n * Child input components that have a `name` prop will automatically\n * register with the form, and their values will be tracked in the form model.\n *\n * ### Features\n *\n * - Automatic value tracking for child input components with the `name` prop\n * - Reactive error handling by providing a computed list of errors\n * - Optionally manage your own form state in a parent component by providing a\n * `form` injection\n * - In web components mode, use the `form-key` prop to pair a form with\n * matching inputs/buttons across custom element app boundaries\n *\n * ### Basic example\n *\n * ```vue-html\n * <GForm v-model=\"formData\" @submit=\"handleSubmit\">\n * <template #default=\"{ isSubmitting, hasErrors }\">\n * <GTextInput name=\"firstName\" label=\"First Name\" :errors=\"firstNameErrors\" />\n * <GSubmitButton :disabled=\"hasErrors\">Submit</GSubmitButton>\n * </template>\n * </GForm>\n * ```\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { provide, watch, inject } from \"vue\";\nimport { useForm, UseFormReturn } from \"../compose/useForm.ts\";\nimport { useWebComponentForm } from \"../compose/useWebComponentForm.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Action URL (optional, for native form submission)\n * @demo\n */\n action?: string;\n /**\n * HTTP method (optional, for native form submission)\n * @demo\n */\n method?: string;\n /**\n * Form channel key for custom elements mode\n * @demo\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n action: undefined,\n method: \"post\",\n formKey: \"default\",\n});\n\nconst model = defineModel<Record<string, any>>({ default: () => ({}) });\n\nconst emit = defineEmits<{\n submit: [values: Record<string, any>];\n}>();\n\n// Check if a form is already injected from a parent\nconst parentForm = inject<UseFormReturn | null>(\"form\", null);\n\nconst form =\n parentForm ??\n (isCustomElementMode() ? useWebComponentForm(props.formKey) : useForm());\n\n// Only provide the form if we created it (not if we're using a parent's form)\nif (!parentForm) {\n provide(\"form\", form);\n}\n\n// Sync form values to model\nwatch(\n () => form.values.value,\n (newValues) => {\n model.value = { ...newValues };\n },\n { deep: true },\n);\n\n// Initialize fields from model value\nwatch(\n () => model.value,\n (newModel) => {\n if (newModel) {\n Object.entries(newModel).forEach(([name, value]) => {\n const field = form.fields[name];\n if (field && field.value.value !== value) {\n field.value.value = value;\n }\n });\n }\n },\n { deep: true, immediate: true },\n);\n\nasync function handleSubmit(e: Event) {\n e.preventDefault();\n await form.submit(async (values) => {\n emit(\"submit\", values);\n });\n}\n\n</script>\n\n<template>\n <form\n @submit=\"handleSubmit\"\n :action=\"props.action\"\n :method=\"props.method\"\n class=\"g-form\"\n novalidate\n >\n <slot\n :isSubmitting=\"form.isSubmitting.value\"\n :hasErrors=\"form.hasErrors.value\"\n :values=\"form.values.value\"\n :errors=\"form.errors.value\"\n ></slot>\n </form>\n</template>\n\n<style>\ng-form {\n display: block;\n}\n.g-form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A form wrapper component that automatically manages form state and\n * connects to child input components.\n *\n * Child input components that have a `name` prop will automatically\n * register with the form, and their values will be tracked in the form model.\n *\n * ### Features\n *\n * - Automatic value tracking for child input components with the `name` prop\n * - Reactive error handling by providing a computed list of errors\n * - Optionally manage your own form state in a parent component by providing a\n * `form` injection\n * - In web components mode, use the `form-key` prop to pair a form with\n * matching inputs/buttons across custom element app boundaries\n *\n * ### Basic example\n *\n * ```vue-html\n * <GForm v-model=\"formData\" @submit=\"handleSubmit\">\n * <template #default=\"{ isSubmitting, hasErrors }\">\n * <GTextInput name=\"firstName\" label=\"First Name\" :errors=\"firstNameErrors\" />\n * <GSubmitButton :disabled=\"hasErrors\">Submit</GSubmitButton>\n * </template>\n * </GForm>\n * ```\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { provide, watch, inject } from \"vue\";\nimport { useForm, UseFormReturn } from \"../compose/useForm.ts\";\nimport { useWebComponentForm } from \"../compose/useWebComponentForm.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\n\ntype Props = {\n /**\n * Action URL (optional, for native form submission)\n * @demo\n */\n action?: string;\n /**\n * HTTP method (optional, for native form submission)\n * @demo\n */\n method?: string;\n /**\n * Form channel key for custom elements mode\n * @demo\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n action: undefined,\n method: \"post\",\n formKey: \"default\",\n});\n\nconst model = defineModel<Record<string, any>>({ default: () => ({}) });\n\nconst emit = defineEmits<{\n submit: [values: Record<string, any>];\n}>();\n\n// Check if a form is already injected from a parent\nconst parentForm = inject<UseFormReturn | null>(\"form\", null);\n\nconst form =\n parentForm ??\n (isCustomElementMode() ? useWebComponentForm(props.formKey) : useForm());\n\n// Only provide the form if we created it (not if we're using a parent's form)\nif (!parentForm) {\n provide(\"form\", form);\n}\n\n// Sync form values to model\nwatch(\n () => form.values.value,\n (newValues) => {\n model.value = { ...newValues };\n },\n { deep: true },\n);\n\n// Initialize fields from model value\nwatch(\n () => model.value,\n (newModel) => {\n if (newModel) {\n Object.entries(newModel).forEach(([name, value]) => {\n const field = form.fields[name];\n if (field && field.value.value !== value) {\n field.value.value = value;\n }\n });\n }\n },\n { deep: true, immediate: true },\n);\n\nasync function handleSubmit(e: Event) {\n e.preventDefault();\n await form.submit(async (values) => {\n emit(\"submit\", values);\n });\n}\n\n</script>\n\n<template>\n <form\n @submit=\"handleSubmit\"\n :action=\"props.action\"\n :method=\"props.method\"\n class=\"g-form\"\n novalidate\n >\n <slot\n :isSubmitting=\"form.isSubmitting.value\"\n :hasErrors=\"form.hasErrors.value\"\n :values=\"form.values.value\"\n :errors=\"form.errors.value\"\n ></slot>\n </form>\n</template>\n\n<style>\ng-form {\n display: block;\n}\n.g-form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A submit button that integrates with GForm.\n *\n * When used inside a GForm, the button will automatically:\n * - Show a loading state during form submission\n * - Be disabled when specified\n *\n * In standard Vue usage, this resolves the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * @example\n * <GForm v-model=\"formData\" @submit=\"handleSubmit\">\n * <GTextInput name=\"email\" label=\"Email\" />\n * <GSubmitButton>Submit</GSubmitButton>\n * </GForm>\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { inject, computed, useAttrs } from \"vue\";\nimport { UseFormReturn } from \"../compose/useForm.ts\";\nimport { useWebComponentForm } from \"../compose/useWebComponentForm.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Disabled state\n * @demo\n */\n disabled?: boolean;\n /**\n * Loading text to show during submission\n * @demo\n */\n loadingText?: string;\n /**\n * Variant\n * @demo\n */\n variant?: \"primary\" | \"secondary\" | \"danger\";\n /**\n * Form channel key for custom elements mode\n * @demo\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n loadingText: \"Submitting...\",\n variant: \"primary\",\n formKey: \"default\",\n});\n\nconst attrs = useAttrs();\nconst attrFormKey = typeof attrs[\"form-key\"] === \"string\" ? attrs[\"form-key\"] : undefined;\nconst formKey = props.formKey || attrFormKey || \"default\";\n\nconst injectedForm = inject<UseFormReturn | null>(\"form\", null);\nconst form =\n injectedForm ??\n (isCustomElementMode() ? useWebComponentForm(formKey) : null);\n\nconst isDisabled = computed(() => {\n return props.disabled || (form?.isSubmitting.value ?? false);\n});\n\nconst isSubmitting = computed(() => {\n return form?.isSubmitting.value ?? false;\n});\n</script>\n\n<template>\n <GButton\n type=\"submit\"\n :disabled=\"isDisabled\"\n :variant=\"props.variant\"\n class=\"g-submit-button\"\n >\n <span v-show=\"isSubmitting\">{{ props.loadingText }}</span>\n <span v-show=\"!isSubmitting\">\n <slot>Submit</slot>\n </span>\n </GButton>\n</template>\n\n<style>\ng-submit-button {\n display: inline-block;\n}\n.g-submit-button {\n align-self: flex-start;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A submit button that integrates with GForm.\n *\n * When used inside a GForm, the button will automatically:\n * - Show a loading state during form submission\n * - Be disabled when specified\n *\n * In standard Vue usage, this resolves the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * @example\n * <GForm v-model=\"formData\" @submit=\"handleSubmit\">\n * <GTextInput name=\"email\" label=\"Email\" />\n * <GSubmitButton>Submit</GSubmitButton>\n * </GForm>\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { inject, computed, useAttrs } from \"vue\";\nimport { UseFormReturn } from \"../compose/useForm.ts\";\nimport { useWebComponentForm } from \"../compose/useWebComponentForm.ts\";\nimport { isCustomElementMode } from \"../compose/useCustomElementAttrs.ts\";\nimport GButton from \"./GButton.vue\";\n\ntype Props = {\n /**\n * Disabled state\n * @demo\n */\n disabled?: boolean;\n /**\n * Loading text to show during submission\n * @demo\n */\n loadingText?: string;\n /**\n * Variant\n * @demo\n */\n variant?: \"primary\" | \"secondary\" | \"danger\";\n /**\n * Form channel key for custom elements mode\n * @demo\n */\n formKey?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n loadingText: \"Submitting...\",\n variant: \"primary\",\n formKey: \"default\",\n});\n\nconst attrs = useAttrs();\nconst attrFormKey = typeof attrs[\"form-key\"] === \"string\" ? attrs[\"form-key\"] : undefined;\nconst formKey = props.formKey || attrFormKey || \"default\";\n\nconst injectedForm = inject<UseFormReturn | null>(\"form\", null);\nconst form =\n injectedForm ??\n (isCustomElementMode() ? useWebComponentForm(formKey) : null);\n\nconst isDisabled = computed(() => {\n return props.disabled || (form?.isSubmitting.value ?? false);\n});\n\nconst isSubmitting = computed(() => {\n return form?.isSubmitting.value ?? false;\n});\n</script>\n\n<template>\n <GButton\n type=\"submit\"\n :disabled=\"isDisabled\"\n :variant=\"props.variant\"\n class=\"g-submit-button\"\n >\n <span v-show=\"isSubmitting\">{{ props.loadingText }}</span>\n <span v-show=\"!isSubmitting\">\n <slot>Submit</slot>\n </span>\n </GButton>\n</template>\n\n<style>\ng-submit-button {\n display: inline-block;\n}\n.g-submit-button {\n align-self: flex-start;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A group of checkboxes (or radio buttons) with styling for a label,\n * instructions, and error messages.\n *\n * When more than one option is provided (or `radio` mode is used), a\n * `fieldset` + `legend` provides semantic grouping. With a single checkbox\n * a plain `div` is rendered instead.\n *\n * Each option renders as a native `<input type=\"checkbox\">` (or\n * `type=\"radio\"` when `radio` is `true`) so that keyboard navigation and\n * browser/assistive-technology support come for free.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, toRef, useId } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\nexport interface CheckboxOption {\n label: string;\n value: string;\n disabled?: boolean;\n hint?: string;\n}\n\ntype Props = {\n /**\n * Legend / accessible label for the group\n * @demo Checkbox Group\n */\n label?: string;\n /**\n * List of checkbox options\n */\n options: CheckboxOption[];\n /**\n * Instructions shown below the legend\n * @demo\n */\n instructions?: string;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Mark the group as required\n * @demo\n */\n required?: boolean;\n /**\n * Render as radio buttons (single-select)\n * @demo\n */\n radio?: boolean;\n /**\n * Name for form registration and native input `name` attribute\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n errors: () => [],\n required: false,\n radio: false,\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string[]>({ default: () => [] });\n\nconst id = useId();\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [value: string[]];\n}>();\n\nfunction isChecked(value: string): boolean {\n return model.value.includes(value);\n}\n\nfunction handleChange(optionValue: string, checked: boolean) {\n let next: string[];\n if (props.radio) {\n next = checked ? [optionValue] : [];\n } else {\n if (checked) {\n next = model.value.includes(optionValue)\n ? model.value\n : [...model.value, optionValue];\n } else {\n next = model.value.filter((v) => v !== optionValue);\n }\n }\n model.value = next;\n emit(\"change\", next);\n}\n\nconst inputType = computed(() => (props.radio ? \"radio\" : \"checkbox\"));\nconst errorId = computed(() => `error-message-${id}`);\nconst instructionsId = computed(() => `instructions-${id}`);\n\n// Use a fieldset for radio mode or multiple checkboxes; a plain div for a single checkbox\nconst useFieldset = computed(() => props.radio || props.options.length > 1);\n\n// Attributes added to the outer element when native required semantics are unavailable\n// or when radio mode needs grouped error metadata.\nconst groupAttrs = computed(() => {\n const describedParts: string[] = [];\n if (props.instructions) describedParts.push(instructionsId.value);\n if (props.radio) {\n return {\n role: \"radiogroup\",\n \"aria-invalid\": hasErrors.value ? \"true\" : undefined,\n \"aria-errormessage\": hasErrors.value ? errorId.value : undefined,\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n }\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n});\n\nfunction hintId(index: number): string {\n return `${id}-hint-${index}`;\n}\n\nfunction inputId(index: number): string {\n return `${id}-input-${index}`;\n}\n\n// Per-input aria attributes.\n// - For checkbox inputs: aria-invalid and aria-errormessage live here.\n// - For radio inputs: the radiogroup fieldset carries those; inputs only reference hints.\nfunction inputAriaAttrs(option: CheckboxOption, index: number): Record<string, string | undefined> {\n const describedParts: string[] = [];\n if (!props.radio && props.instructions) describedParts.push(instructionsId.value);\n if (option.hint) describedParts.push(hintId(index));\n\n if (props.radio) {\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n }\n\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n \"aria-required\":\n props.required && props.options.length > 1 ? \"true\" : undefined,\n \"aria-invalid\": hasErrors.value ? \"true\" : \"false\",\n \"aria-errormessage\": hasErrors.value ? errorId.value : undefined,\n };\n}\n</script>\n\n<template>\n <component\n :is=\"useFieldset ? 'fieldset' : 'div'\"\n class=\"g-checkbox-group\"\n :class=\"{ 'g-checkbox-group--error': hasErrors }\"\n v-bind=\"groupAttrs\"\n >\n <!-- fieldset uses <legend>; single-checkbox div uses a plain heading div -->\n <legend v-if=\"useFieldset && label\" class=\"g-checkbox-group__legend\">\n {{ label }}<span v-if=\"required\" class=\"g-checkbox-group__required\" aria-hidden=\"true\">&nbsp;*</span>\n </legend>\n <div v-else-if=\"!useFieldset && label\" class=\"g-checkbox-group__label\">\n {{ label }}<span v-if=\"required\" class=\"g-checkbox-group__required\" aria-hidden=\"true\">&nbsp;*</span>\n </div>\n\n <div\n v-if=\"instructions\"\n :id=\"instructionsId\"\n class=\"g-checkbox-group__instructions\"\n >{{ instructions }}</div>\n\n <div class=\"g-checkbox-group__options\">\n <div\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n class=\"g-checkbox-group__option-wrapper\"\n :class=\"{ 'g-checkbox-group__option-wrapper--disabled': option.disabled }\"\n >\n <label\n class=\"g-checkbox-group__option\"\n :for=\"inputId(index)\"\n :class=\"{ 'g-checkbox-group__option--checked': isChecked(option.value) }\"\n >\n <input\n :id=\"inputId(index)\"\n :type=\"inputType\"\n :name=\"name || id\"\n :value=\"option.value\"\n :checked=\"isChecked(option.value)\"\n :disabled=\"option.disabled\"\n :required=\"\n required &&\n ((radio && index === 0) || (!radio && options.length === 1))\n \"\n class=\"g-checkbox-group__input\"\n v-bind=\"inputAriaAttrs(option, index)\"\n @change=\"handleChange(option.value, ($event.target as HTMLInputElement).checked)\"\n />\n <span class=\"g-checkbox-group__label-text\">{{ option.label }}</span>\n </label>\n <!-- Hint is outside the label and referenced via aria-describedby on the input -->\n <div\n v-if=\"option.hint\"\n :id=\"hintId(index)\"\n class=\"g-checkbox-group__hint\"\n >{{ option.hint }}</div>\n </div>\n </div>\n\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"errorId\"\n />\n </component>\n</template>\n\n<style>\ng-checkbox-group {\n display: block;\n}\n.g-checkbox-group {\n border: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n}\n\n.g-checkbox-group__legend,\n.g-checkbox-group__label {\n font-size: 1.25em;\n margin-bottom: 0.5em;\n padding: 0;\n float: left;\n width: 100%;\n}\n\n.g-checkbox-group__required {\n color: var(--g-danger-600);\n}\n\n.g-checkbox-group__instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n clear: both;\n}\n\n.g-checkbox-group__options {\n display: flex;\n flex-direction: column;\n gap: 0.5em;\n clear: both;\n}\n\n.g-checkbox-group__option-wrapper {\n display: flex;\n flex-direction: column;\n}\n\n.g-checkbox-group__option-wrapper--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.g-checkbox-group__option {\n display: flex;\n align-items: flex-start;\n gap: 0.5em;\n cursor: pointer;\n padding: 0.375em 0.5em;\n border-radius: 4px;\n}\n\n.g-checkbox-group__option-wrapper--disabled .g-checkbox-group__option {\n cursor: not-allowed;\n}\n\n.g-checkbox-group__input {\n margin-top: 0.2em;\n flex-shrink: 0;\n accent-color: var(--g-primary-500);\n width: 1.1em;\n height: 1.1em;\n cursor: pointer;\n\n &:focus-visible {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-checkbox-group__option-wrapper--disabled .g-checkbox-group__input {\n cursor: not-allowed;\n}\n\n.g-checkbox-group__label-text {\n font-family: var(--il-font-sans);\n line-height: 1.4;\n}\n\n/* Indent hint to align with the label text, accounting for the checkbox width\n (1.1em) + flex gap (0.5em) + option left-padding (0.5em). */\n.g-checkbox-group__hint {\n padding-left: calc(0.5em + 1.1em + 0.5em);\n padding-right: 0.5em;\n font-size: 0.875em;\n color: var(--g-surface-700);\n margin-top: 0.125em;\n}\n\n.g-checkbox-group--error .g-checkbox-group__options {\n border-left: 3px solid var(--g-danger-600);\n padding-left: 0.75em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A group of checkboxes (or radio buttons) with styling for a label,\n * instructions, and error messages.\n *\n * When more than one option is provided (or `radio` mode is used), a\n * `fieldset` + `legend` provides semantic grouping. With a single checkbox\n * a plain `div` is rendered instead.\n *\n * Each option renders as a native `<input type=\"checkbox\">` (or\n * `type=\"radio\"` when `radio` is `true`) so that keyboard navigation and\n * browser/assistive-technology support come for free.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, toRef, useId } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\nexport interface CheckboxOption {\n label: string;\n value: string;\n disabled?: boolean;\n hint?: string;\n}\n\ntype Props = {\n /**\n * Legend / accessible label for the group\n * @demo Checkbox Group\n */\n label?: string;\n /**\n * List of checkbox options\n */\n options: CheckboxOption[];\n /**\n * Instructions shown below the legend\n * @demo\n */\n instructions?: string;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Mark the group as required\n * @demo\n */\n required?: boolean;\n /**\n * Render as radio buttons (single-select)\n * @demo\n */\n radio?: boolean;\n /**\n * Name for form registration and native input `name` attribute\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n errors: () => [],\n required: false,\n radio: false,\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string[]>({ default: () => [] });\n\nconst id = useId();\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [value: string[]];\n}>();\n\nfunction isChecked(value: string): boolean {\n return model.value.includes(value);\n}\n\nfunction handleChange(optionValue: string, checked: boolean) {\n let next: string[];\n if (props.radio) {\n next = checked ? [optionValue] : [];\n } else {\n if (checked) {\n next = model.value.includes(optionValue)\n ? model.value\n : [...model.value, optionValue];\n } else {\n next = model.value.filter((v) => v !== optionValue);\n }\n }\n model.value = next;\n emit(\"change\", next);\n}\n\nconst inputType = computed(() => (props.radio ? \"radio\" : \"checkbox\"));\nconst errorId = computed(() => `error-message-${id}`);\nconst instructionsId = computed(() => `instructions-${id}`);\n\n// Use a fieldset for radio mode or multiple checkboxes; a plain div for a single checkbox\nconst useFieldset = computed(() => props.radio || props.options.length > 1);\n\n// Attributes added to the outer element when native required semantics are unavailable\n// or when radio mode needs grouped error metadata.\nconst groupAttrs = computed(() => {\n const describedParts: string[] = [];\n if (props.instructions) describedParts.push(instructionsId.value);\n if (props.radio) {\n return {\n role: \"radiogroup\",\n \"aria-invalid\": hasErrors.value ? \"true\" : undefined,\n \"aria-errormessage\": hasErrors.value ? errorId.value : undefined,\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n }\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n});\n\nfunction hintId(index: number): string {\n return `${id}-hint-${index}`;\n}\n\nfunction inputId(index: number): string {\n return `${id}-input-${index}`;\n}\n\n// Per-input aria attributes.\n// - For checkbox inputs: aria-invalid and aria-errormessage live here.\n// - For radio inputs: the radiogroup fieldset carries those; inputs only reference hints.\nfunction inputAriaAttrs(option: CheckboxOption, index: number): Record<string, string | undefined> {\n const describedParts: string[] = [];\n if (!props.radio && props.instructions) describedParts.push(instructionsId.value);\n if (option.hint) describedParts.push(hintId(index));\n\n if (props.radio) {\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n };\n }\n\n return {\n \"aria-describedby\": describedParts.length ? describedParts.join(\" \") : undefined,\n \"aria-required\":\n props.required && props.options.length > 1 ? \"true\" : undefined,\n \"aria-invalid\": hasErrors.value ? \"true\" : \"false\",\n \"aria-errormessage\": hasErrors.value ? errorId.value : undefined,\n };\n}\n</script>\n\n<template>\n <component\n :is=\"useFieldset ? 'fieldset' : 'div'\"\n class=\"g-checkbox-group\"\n :class=\"{ 'g-checkbox-group--error': hasErrors }\"\n v-bind=\"groupAttrs\"\n >\n <!-- fieldset uses <legend>; single-checkbox div uses a plain heading div -->\n <legend v-if=\"useFieldset && label\" class=\"g-checkbox-group__legend\">\n {{ label }}<span v-if=\"required\" class=\"g-checkbox-group__required\" aria-hidden=\"true\">&nbsp;*</span>\n </legend>\n <div v-else-if=\"!useFieldset && label\" class=\"g-checkbox-group__label\">\n {{ label }}<span v-if=\"required\" class=\"g-checkbox-group__required\" aria-hidden=\"true\">&nbsp;*</span>\n </div>\n\n <div\n v-if=\"instructions\"\n :id=\"instructionsId\"\n class=\"g-checkbox-group__instructions\"\n >{{ instructions }}</div>\n\n <div class=\"g-checkbox-group__options\">\n <div\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n class=\"g-checkbox-group__option-wrapper\"\n :class=\"{ 'g-checkbox-group__option-wrapper--disabled': option.disabled }\"\n >\n <label\n class=\"g-checkbox-group__option\"\n :for=\"inputId(index)\"\n :class=\"{ 'g-checkbox-group__option--checked': isChecked(option.value) }\"\n >\n <input\n :id=\"inputId(index)\"\n :type=\"inputType\"\n :name=\"name || id\"\n :value=\"option.value\"\n :checked=\"isChecked(option.value)\"\n :disabled=\"option.disabled\"\n :required=\"\n required &&\n ((radio && index === 0) || (!radio && options.length === 1))\n \"\n class=\"g-checkbox-group__input\"\n v-bind=\"inputAriaAttrs(option, index)\"\n @change=\"handleChange(option.value, ($event.target as HTMLInputElement).checked)\"\n />\n <span class=\"g-checkbox-group__label-text\">{{ option.label }}</span>\n </label>\n <!-- Hint is outside the label and referenced via aria-describedby on the input -->\n <div\n v-if=\"option.hint\"\n :id=\"hintId(index)\"\n class=\"g-checkbox-group__hint\"\n >{{ option.hint }}</div>\n </div>\n </div>\n\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"errorId\"\n />\n </component>\n</template>\n\n<style>\ng-checkbox-group {\n display: block;\n}\n.g-checkbox-group {\n border: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n}\n\n.g-checkbox-group__legend,\n.g-checkbox-group__label {\n font-size: 1.25em;\n margin-bottom: 0.5em;\n padding: 0;\n float: left;\n width: 100%;\n}\n\n.g-checkbox-group__required {\n color: var(--g-danger-600);\n}\n\n.g-checkbox-group__instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n clear: both;\n}\n\n.g-checkbox-group__options {\n display: flex;\n flex-direction: column;\n gap: 0.5em;\n clear: both;\n}\n\n.g-checkbox-group__option-wrapper {\n display: flex;\n flex-direction: column;\n}\n\n.g-checkbox-group__option-wrapper--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.g-checkbox-group__option {\n display: flex;\n align-items: flex-start;\n gap: 0.5em;\n cursor: pointer;\n padding: 0.375em 0.5em;\n border-radius: 4px;\n}\n\n.g-checkbox-group__option-wrapper--disabled .g-checkbox-group__option {\n cursor: not-allowed;\n}\n\n.g-checkbox-group__input {\n margin-top: 0.2em;\n flex-shrink: 0;\n accent-color: var(--g-primary-500);\n width: 1.1em;\n height: 1.1em;\n cursor: pointer;\n\n &:focus-visible {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-checkbox-group__option-wrapper--disabled .g-checkbox-group__input {\n cursor: not-allowed;\n}\n\n.g-checkbox-group__label-text {\n font-family: var(--il-font-sans);\n line-height: 1.4;\n}\n\n/* Indent hint to align with the label text, accounting for the checkbox width\n (1.1em) + flex gap (0.5em) + option left-padding (0.5em). */\n.g-checkbox-group__hint {\n padding-left: calc(0.5em + 1.1em + 0.5em);\n padding-right: 0.5em;\n font-size: 0.875em;\n color: var(--g-surface-700);\n margin-top: 0.125em;\n}\n\n.g-checkbox-group--error .g-checkbox-group__options {\n border-left: 3px solid var(--g-danger-600);\n padding-left: 0.75em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A multi-line plain text input with styling for a label, instructions,\n * and error messages.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n * All non-prop attributes are passed through to the textarea element, including\n * `id`.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, nextTick, ref, toRef, useId, useTemplateRef, watch } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Example Label\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Read-only\n * @demo\n */\n readonly?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Number of visible text rows\n * @demo\n */\n rows?: number;\n /**\n * Maximum number of characters allowed\n * @demo\n */\n maxlength?: number;\n /**\n * Automatically grow the textarea height to fit content\n * @demo\n */\n autoGrow?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n readonly: false,\n required: false,\n errors: () => [],\n rows: 4,\n maxlength: undefined,\n autoGrow: false,\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n\nconst id = useId();\nconst { attrs, isCustomElement, forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\nconst inputId = computed(() => {\n if (isCustomElement) {\n return id;\n }\n return (attrs.id as string) || id;\n});\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [\n {\n was: string | null | undefined;\n to: string | null;\n },\n ];\n}>();\n\nconst textareaRef = useTemplateRef<HTMLTextAreaElement>(\"textareaEl\");\n\nfunction adjustHeight() {\n const el = textareaRef.value;\n if (!el) return;\n el.style.height = \"auto\";\n el.style.height = `${el.scrollHeight}px`;\n}\n\nwatch(\n () => model.value,\n () => {\n if (props.autoGrow) {\n nextTick(adjustHeight);\n }\n },\n);\n\nfunction emitChangeIfNeeded(val: string | null) {\n if (val !== model.value) {\n const prev = model.value;\n model.value = val;\n emit(\"change\", { was: prev, to: val });\n }\n}\n\nfunction onInput(e: Event) {\n const value = (e.target as HTMLTextAreaElement).value;\n model.value = value;\n if (props.autoGrow) {\n adjustHeight();\n }\n}\n\nfunction onBlur(e: FocusEvent) {\n emitChangeIfNeeded((e.target as HTMLTextAreaElement).value);\n}\n\nfunction onPaste(e: ClipboardEvent) {\n setTimeout(() => {\n const value = (e.target as HTMLTextAreaElement).value;\n emitChangeIfNeeded(value);\n if (props.autoGrow) {\n adjustHeight();\n }\n }, 0);\n}\n</script>\n\n<template>\n <div\n class=\"g-textarea-wrap\"\n :class=\"{ 'g-textarea-has-error': hasErrors }\"\n >\n <label\n v-if=\"props.label\"\n :for=\"inputId\"\n class=\"g-textarea-label\"\n >{{ props.label\n }}<span v-if=\"props.required\" class=\"g-textarea-required\" aria-hidden=\"true\"> *</span></label\n >\n <div\n v-if=\"$slots.instructions || instructions\"\n :id=\"'instructions-' + id\"\n class=\"g-textarea-instructions\"\n >\n <slot name=\"instructions\">{{ instructions }}</slot>\n </div>\n <textarea\n ref=\"textareaEl\"\n :value=\"model ?? ''\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :readonly=\"props.readonly\"\n :required=\"props.required\"\n :rows=\"props.rows\"\n :maxlength=\"props.maxlength ?? undefined\"\n class=\"g-textarea\"\n v-bind=\"{\n ...forwardedAttrs,\n id: inputId,\n 'aria-describedby':\n $slots.instructions || instructions\n ? 'instructions-' + id\n : undefined,\n 'aria-errormessage': hasErrors\n ? 'error-message-' + id\n : undefined,\n }\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n @input=\"onInput\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n />\n <div\n v-if=\"props.maxlength !== undefined\"\n class=\"g-textarea-char-count\"\n aria-live=\"polite\"\n >\n {{ (model ?? '').length }} / {{ props.maxlength }}\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + id\"\n />\n </div>\n</template>\n\n<style>\ng-textarea {\n display: block;\n}\n.g-textarea-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n.g-textarea-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n\n.g-textarea-required {\n color: var(--g-danger-600);\n}\n\n.g-textarea-instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n}\n\n.g-textarea {\n width: 100%;\n padding: 0.5em;\n font-size: 1em;\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n background: var(--g-surface-0);\n color: var(--g-surface-950);\n font-family: var(--il-font-sans);\n resize: vertical;\n box-sizing: border-box;\n}\n\n.g-textarea:focus {\n outline: 2px solid var(--g-primary-500);\n box-shadow: 0 0 0 2px var(--g-info-200);\n outline-offset: 2px;\n}\n\n.g-textarea-has-error .g-textarea {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-textarea:disabled {\n background: var(--g-surface-100);\n color: var(--g-surface-700);\n cursor: not-allowed;\n}\n\n.g-textarea[readonly] {\n background: var(--g-surface-50);\n color: var(--g-surface-800);\n}\n\n.g-textarea-char-count {\n font-size: 0.875em;\n color: var(--g-surface-700);\n text-align: right;\n margin-top: 0.25em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A multi-line plain text input with styling for a label, instructions,\n * and error messages.\n *\n * If `label` is omitted, an accessible label must be provided some other way.\n * All non-prop attributes are passed through to the textarea element, including\n * `id`.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Errors are provided as an array of strings or computed values.\n * Multiple errors will all be displayed.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed, nextTick, ref, toRef, useId, useTemplateRef, watch } from \"vue\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport { useCustomElementAttrs } from \"../compose/useCustomElementAttrs.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ntype Props = {\n /**\n * Label\n * @demo Example Label\n */\n label?: string;\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Read-only\n * @demo\n */\n readonly?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions\n * @demo\n */\n instructions?: string;\n /**\n * Number of visible text rows\n * @demo\n */\n rows?: number;\n /**\n * Maximum number of characters allowed\n * @demo\n */\n maxlength?: number;\n /**\n * Automatically grow the textarea height to fit content\n * @demo\n */\n autoGrow?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n instructions: \"\",\n placeholder: \"\",\n disabled: false,\n readonly: false,\n required: false,\n errors: () => [],\n rows: 4,\n maxlength: undefined,\n autoGrow: false,\n name: undefined,\n formKey: undefined,\n});\n\nconst model = defineModel<string | null>({ type: String });\n\nconst id = useId();\nconst { attrs, isCustomElement, forwardedAttrs } = useCustomElementAttrs({\n omitInCustomElement: [\"id\"],\n});\nconst inputId = computed(() => {\n if (isCustomElement) {\n return id;\n }\n return (attrs.id as string) || id;\n});\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst emit = defineEmits<{\n change: [\n {\n was: string | null | undefined;\n to: string | null;\n },\n ];\n}>();\n\nconst textareaRef = useTemplateRef<HTMLTextAreaElement>(\"textareaEl\");\n\nfunction adjustHeight() {\n const el = textareaRef.value;\n if (!el) return;\n el.style.height = \"auto\";\n el.style.height = `${el.scrollHeight}px`;\n}\n\nwatch(\n () => model.value,\n () => {\n if (props.autoGrow) {\n nextTick(adjustHeight);\n }\n },\n);\n\nfunction emitChangeIfNeeded(val: string | null) {\n if (val !== model.value) {\n const prev = model.value;\n model.value = val;\n emit(\"change\", { was: prev, to: val });\n }\n}\n\nfunction onInput(e: Event) {\n const value = (e.target as HTMLTextAreaElement).value;\n model.value = value;\n if (props.autoGrow) {\n adjustHeight();\n }\n}\n\nfunction onBlur(e: FocusEvent) {\n emitChangeIfNeeded((e.target as HTMLTextAreaElement).value);\n}\n\nfunction onPaste(e: ClipboardEvent) {\n setTimeout(() => {\n const value = (e.target as HTMLTextAreaElement).value;\n emitChangeIfNeeded(value);\n if (props.autoGrow) {\n adjustHeight();\n }\n }, 0);\n}\n</script>\n\n<template>\n <div\n class=\"g-textarea-wrap\"\n :class=\"{ 'g-textarea-has-error': hasErrors }\"\n >\n <label\n v-if=\"props.label\"\n :for=\"inputId\"\n class=\"g-textarea-label\"\n >{{ props.label\n }}<span v-if=\"props.required\" class=\"g-textarea-required\" aria-hidden=\"true\"> *</span></label\n >\n <div\n v-if=\"$slots.instructions || instructions\"\n :id=\"'instructions-' + id\"\n class=\"g-textarea-instructions\"\n >\n <slot name=\"instructions\">{{ instructions }}</slot>\n </div>\n <textarea\n ref=\"textareaEl\"\n :value=\"model ?? ''\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :readonly=\"props.readonly\"\n :required=\"props.required\"\n :rows=\"props.rows\"\n :maxlength=\"props.maxlength ?? undefined\"\n class=\"g-textarea\"\n v-bind=\"{\n ...forwardedAttrs,\n id: inputId,\n 'aria-describedby':\n $slots.instructions || instructions\n ? 'instructions-' + id\n : undefined,\n 'aria-errormessage': hasErrors\n ? 'error-message-' + id\n : undefined,\n }\"\n :aria-invalid=\"hasErrors ? 'true' : 'false'\"\n @input=\"onInput\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n />\n <div\n v-if=\"props.maxlength !== undefined\"\n class=\"g-textarea-char-count\"\n aria-live=\"polite\"\n >\n {{ (model ?? '').length }} / {{ props.maxlength }}\n </div>\n <GFormErrorMessages\n :errors=\"displayErrors\"\n :id=\"'error-message-' + id\"\n />\n </div>\n</template>\n\n<style>\ng-textarea {\n display: block;\n}\n.g-textarea-wrap {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n.g-textarea-label {\n margin-bottom: 0.5em;\n font-size: 1.25em;\n}\n\n.g-textarea-required {\n color: var(--g-danger-600);\n}\n\n.g-textarea-instructions {\n margin: 0 0 0.75em 0.5em;\n color: var(--g-surface-800);\n}\n\n.g-textarea {\n width: 100%;\n padding: 0.5em;\n font-size: 1em;\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n background: var(--g-surface-0);\n color: var(--g-surface-950);\n font-family: var(--il-font-sans);\n resize: vertical;\n box-sizing: border-box;\n}\n\n.g-textarea:focus {\n outline: 2px solid var(--g-primary-500);\n box-shadow: 0 0 0 2px var(--g-info-200);\n outline-offset: 2px;\n}\n\n.g-textarea-has-error .g-textarea {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-textarea:disabled {\n background: var(--g-surface-100);\n color: var(--g-surface-700);\n cursor: not-allowed;\n}\n\n.g-textarea[readonly] {\n background: var(--g-surface-50);\n color: var(--g-surface-800);\n}\n\n.g-textarea-char-count {\n font-size: 0.875em;\n color: var(--g-surface-700);\n text-align: right;\n margin-top: 0.25em;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A multi-select combobox that allows selecting multiple values with\n * optional search/filter support.\n *\n * Selected values are displayed as removable chips inside the control.\n * The dropdown listbox shows all (or filtered) options with a checkmark\n * next to each selected option.\n *\n * The `options` prop accepts an array of strings or `{ label, value }`\n * objects. The `v-model` binds to an array of `string | number` values.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Keyboard navigation:\n * - `Down Arrow` / `Up Arrow`: move through options (opens menu if closed)\n * - `Enter`: toggle the focused option\n * - `Space`: toggle the focused option when the search field is empty\n * - `Escape`: close the dropdown\n * - `Home` / `End`: jump to first / last option\n * - `Backspace`: remove the last chip when the search field is empty\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n nextTick,\n ref,\n toRef,\n useId,\n} from \"vue\";\nimport { useSelectDropdown, normalizeSelectOptions, type SelectOption } from \"../compose/useSelectDropdown.ts\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype Props = {\n /**\n * List of options to choose from\n */\n options: Array<string | SelectOption>;\n /**\n * Accessible label\n * @demo Select Fruits\n */\n label: string;\n /**\n * Hide the label visually\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Placeholder text shown when no values are selected\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions shown below the label\n * @demo\n */\n instructions?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n hiddenLabel: false,\n required: false,\n errors: () => [],\n});\n\nconst emit = defineEmits<{\n change: [value: Array<string | number>];\n}>();\n\nconst model = defineModel<Array<string | number>>({ default: () => [] });\n\nconst baseId = useId();\nconst inputRef = ref<HTMLInputElement | null>(null);\nconst controlRef = ref<HTMLElement | null>(null);\nconst listboxRef = ref<HTMLElement | null>(null);\n\nconst open = ref(false);\nconst activeIndex = ref(0);\nconst searchQuery = ref(\"\");\nconst ignoreBlur = ref(false);\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst { menuPlacement, menuStyle, isTop, scrollOptionIntoView } = useSelectDropdown({\n open,\n anchorRef: controlRef,\n listboxRef,\n baseId,\n activeIndex,\n});\n\nconst normalizedOptions = computed<SelectOption[]>(() =>\n normalizeSelectOptions(props.options),\n);\n\nconst filteredOptions = computed<SelectOption[]>(() => {\n if (!searchQuery.value) return normalizedOptions.value;\n const q = searchQuery.value.toLowerCase();\n return normalizedOptions.value.filter((opt) =>\n opt.label.toLowerCase().includes(q),\n );\n});\n\nfunction isSelected(value: string | number): boolean {\n return model.value.includes(value);\n}\n\nfunction labelForValue(value: string | number): string {\n const opt = normalizedOptions.value.find((o) => o.value === value);\n return opt ? opt.label : String(value);\n}\n\n// ----- Open / close -----\n\nfunction openMenu() {\n if (props.disabled) return;\n open.value = true;\n activeIndex.value = 0;\n}\n\nfunction closeMenu() {\n open.value = false;\n searchQuery.value = \"\";\n}\n\n// ----- Option interaction -----\n\nfunction toggleOption(idx: number) {\n const opt = filteredOptions.value[idx];\n if (!opt) return;\n const current = model.value;\n const next: Array<string | number> = isSelected(opt.value)\n ? current.filter((v) => v !== opt.value)\n : [...current, opt.value];\n model.value = next;\n emit(\"change\", next);\n nextTick(() => inputRef.value?.focus());\n}\n\nfunction removeValue(value: string | number) {\n if (props.disabled) return;\n const next = model.value.filter((v) => v !== value);\n model.value = next;\n emit(\"change\", next);\n}\n\n// ----- Event handlers -----\n\nfunction onControlClick() {\n if (props.disabled) return;\n inputRef.value?.focus();\n if (!open.value) openMenu();\n}\n\nfunction onInput(e: Event) {\n searchQuery.value = (e.target as HTMLInputElement).value;\n activeIndex.value = 0;\n if (!open.value) openMenu();\n}\n\nfunction onFocus() {\n if (props.disabled) return;\n if (!open.value) openMenu();\n}\n\nfunction onBlur(e: FocusEvent) {\n if (ignoreBlur.value) {\n ignoreBlur.value = false;\n return;\n }\n const relatedTarget = e.relatedTarget as HTMLElement | null;\n if (relatedTarget && listboxRef.value?.contains(relatedTarget)) {\n return;\n }\n if (relatedTarget && controlRef.value?.contains(relatedTarget)) {\n return;\n }\n closeMenu();\n}\n\nfunction onOptionMouseDown(e: MouseEvent) {\n e.preventDefault();\n //ignoreBlur.value = true;\n}\n\nfunction onChipMouseDown() {\n ignoreBlur.value = true;\n}\n\nfunction onKeydown(e: KeyboardEvent) {\n if (props.disabled) return;\n const max = filteredOptions.value.length - 1;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.min(max, activeIndex.value + 1);\n scrollOptionIntoView();\n }\n break;\n case \"ArrowUp\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.max(0, activeIndex.value - 1);\n scrollOptionIntoView();\n }\n break;\n case \"Home\":\n e.preventDefault();\n activeIndex.value = 0;\n scrollOptionIntoView();\n break;\n case \"End\":\n e.preventDefault();\n activeIndex.value = max;\n scrollOptionIntoView();\n break;\n case \"Enter\":\n e.preventDefault();\n if (open.value && filteredOptions.value.length > 0) {\n toggleOption(activeIndex.value);\n } else if (!open.value) {\n openMenu();\n }\n break;\n case \" \":\n if (open.value && !searchQuery.value) {\n e.preventDefault();\n if (filteredOptions.value.length > 0) {\n toggleOption(activeIndex.value);\n }\n }\n break;\n case \"Escape\":\n if (isTop.value) {\n e.preventDefault();\n setTimeout(() => closeMenu(), 0);\n }\n break;\n case \"Backspace\":\n if (!searchQuery.value && model.value.length > 0) {\n removeValue(model.value[model.value.length - 1]);\n }\n break;\n }\n}\n\n// ----- IDs -----\n\nconst inputId = computed(() => `${baseId}-input`);\nconst labelId = computed(() => `${baseId}-label`);\nconst instructionsId = computed(() => `${baseId}-instructions`);\nconst errorId = computed(() => `error-message-${baseId}`);\n\nconst describedBy = computed(() => {\n const parts: string[] = [];\n if (props.instructions) parts.push(instructionsId.value);\n if (hasErrors.value) parts.push(errorId.value);\n return parts.length > 0 ? parts.join(\" \") : undefined;\n});\n</script>\n\n<template>\n <div\n class=\"g-multiselect-root\"\n :class=\"{\n 'g-multiselect-open': open,\n 'g-multiselect-has-error': hasErrors,\n }\"\n >\n <label v-if=\"!hiddenLabel\" :id=\"labelId\" :for=\"inputId\" class=\"g-multiselect-label\">\n {{ label }}<span v-if=\"required\" class=\"g-multiselect-required\" aria-hidden=\"true\"> *</span>\n </label>\n <div\n v-if=\"instructions\"\n :id=\"instructionsId\"\n class=\"g-multiselect-instructions\"\n >\n {{ instructions }}\n </div>\n <div\n ref=\"controlRef\"\n class=\"g-multiselect-control\"\n :class=\"{ 'g-multiselect-control--disabled': disabled }\"\n @click=\"onControlClick\"\n >\n <ul class=\"g-multiselect-chips\" :aria-labelledby=\"labelId\">\n <li v-for=\"val in model\" :key=\"val\" class=\"g-multiselect-chip\">\n <span class=\"g-multiselect-chip-label\">{{\n labelForValue(val)\n }}</span>\n <button\n type=\"button\"\n class=\"g-multiselect-chip-remove\"\n :aria-label=\"`Remove ${labelForValue(val)}`\"\n :disabled=\"disabled\"\n @mousedown=\"onChipMouseDown\"\n @click.stop=\"removeValue(val)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1em\"\n role=\"none presentation\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n </li>\n </ul>\n\n <input\n ref=\"inputRef\"\n type=\"text\"\n role=\"combobox\"\n class=\"g-multiselect-input\"\n :id=\"inputId\"\n :value=\"searchQuery\"\n :placeholder=\"model.length === 0 ? placeholder : undefined\"\n :disabled=\"disabled\"\n autocomplete=\"off\"\n aria-autocomplete=\"list\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n aria-haspopup=\"listbox\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-activedescendant=\"\n open && filteredOptions.length > 0\n ? baseId + '-option-' + activeIndex\n : undefined\n \"\n v-bind=\"hiddenLabel ? { 'aria-label': label } : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"required ? 'true' : undefined\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n />\n\n <svg\n class=\"g-multiselect-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n\n <div\n v-show=\"open\"\n ref=\"listboxRef\"\n :id=\"baseId + '-listbox'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': label }\n : { 'aria-labelledby': labelId }\n \"\n class=\"g-multiselect-listbox\"\n :class=\"{\n 'g-multiselect-listbox--above': menuPlacement === 'above',\n }\"\n :style=\"menuStyle\"\n tabindex=\"-1\"\n >\n <template v-if=\"filteredOptions.length > 0\">\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"opt.value\"\n :id=\"baseId + '-option-' + idx\"\n role=\"option\"\n class=\"g-multiselect-option\"\n :class=\"{\n 'g-multiselect-option--active': idx === activeIndex,\n 'g-multiselect-option--selected': isSelected(opt.value),\n }\"\n :aria-selected=\"isSelected(opt.value) ? 'true' : 'false'\"\n @mousedown=\"onOptionMouseDown\"\n @click=\"toggleOption(idx)\"\n >\n <span class=\"g-multiselect-option-check\" aria-hidden=\"true\">\n <svg\n v-if=\"isSelected(opt.value)\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n width=\"1.25em\"\n >\n <!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n d=\"M530.8 134.1C545.1 144.5 548.3 164.5 537.9 178.8L281.9 530.8C276.4 538.4 267.9 543.1 258.5 543.9C249.1 544.7 240 541.2 233.4 534.6L105.4 406.6C92.9 394.1 92.9 373.8 105.4 361.3C117.9 348.8 138.2 348.8 150.7 361.3L252.2 462.8L486.2 141.1C496.6 126.8 516.6 123.6 530.9 134z\"\n />\n </svg>\n </span>\n {{ opt.label }}\n </div>\n </template>\n <template v-else>\n <div\n aria-live=\"polite\"\n class=\"g-multiselect-option g-multiselect-no-results\"\n >\n No results found.\n </div>\n </template>\n </div>\n </div>\n\n\n <GFormErrorMessages :errors=\"displayErrors\" :id=\"errorId\" />\n </div>\n</template>\n\n<style>\ng-multi-select {\n display: block;\n}\n.g-multiselect-root {\n position: relative;\n font-size: 1rem;\n}\n\n.g-multiselect-label {\n font-weight: 700;\n color: var(--g-surface-900);\n margin-bottom: 0.5em;\n}\n\n.g-multiselect-required {\n color: var(--g-danger-600);\n}\n\n.g-multiselect-instructions {\n margin: 0 0 0.5em 0;\n color: var(--g-surface-800);\n}\n\n.g-multiselect-control {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n list-style: none;\n gap: 0.375em;\n min-height: calc(0.25em + 1.875em + 0.25em + 4px);\n padding: 0.3em 2.25em 0.3em 0.5em;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-radius: var(--g-border-radius-m);\n cursor: text;\n position: relative;\n box-sizing: border-box;\n\n &:focus-within {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 1px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-multiselect-control--disabled {\n cursor: not-allowed;\n border-color: var(--g-surface-400);\n}\n\n.g-multiselect-control--disabled .g-multiselect-input {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.g-multiselect-control--disabled .g-multiselect-caret {\n opacity: 0.6;\n}\n\n.g-multiselect-has-error .g-multiselect-control {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-multiselect-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 0.375em;\n list-style: none;\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\n.g-multiselect-chip {\n display: inline-flex;\n align-items: center;\n gap: 0.2em;\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n border-radius: 1em;\n padding: 0.1em 0.4em 0.1em 0.6em;\n line-height: 1.5;\n max-width: 100%;\n}\n\n.g-multiselect-chip-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.g-multiselect-chip-remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: none;\n border: none;\n color: inherit;\n cursor: pointer;\n padding: 0.15em;\n box-sizing: border-box;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n flex-shrink: 0;\n line-height: 1;\n\n &:hover {\n background: rgba(0, 0, 0, 0.15);\n }\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.7;\n }\n}\n\n.g-multiselect-input {\n flex: 1;\n min-width: 4em;\n border: none;\n background: transparent;\n font-family: var(--il-font-sans);\n font-size: 1em;\n line-height: 1.875em;\n padding: 0;\n outline: none;\n color: var(--g-surface-900);\n cursor: text;\n\n &:disabled {\n cursor: not-allowed;\n }\n\n &::placeholder {\n color: var(--g-surface-600);\n }\n}\n\n.g-multiselect-caret {\n position: absolute;\n right: 0.5em;\n top: calc(50% - 0.55em);\n color: var(--g-accent-700);\n pointer-events: none;\n transform: rotate(90deg);\n transition: transform 0.15s ease;\n}\n\n.g-multiselect-open .g-multiselect-caret {\n transform: rotate(-90deg);\n}\n\n.g-multiselect-listbox {\n position: absolute;\n left: 0;\n top: 100%;\n width: 100%;\n z-index: 1000;\n background-color: var(--g-surface-0);\n border: 2px solid var(--g-surface-700);\n border-radius: 0 0 var(--g-border-radius-m) var(--g-border-radius-m);\n box-shadow:\n 0 4px 4px rgba(0, 0, 0, 0.2),\n 0 1px 0 1px rgba(0, 0, 0, 0.18);\n max-height: 50vh;\n overflow-y: auto;\n box-sizing: border-box;\n display: none;\n}\n\n.g-multiselect-open .g-multiselect-listbox {\n display: block;\n}\n\n.g-multiselect-listbox--above {\n top: auto;\n bottom: 100%;\n border-radius: var(--g-border-radius-m) var(--g-border-radius-m) 0 0;\n}\n\n.g-multiselect-option {\n display: flex;\n align-items: center;\n gap: 0.5em;\n padding: 0.5em 0.5em;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n border-color: var(--g-accent-700);\n }\n}\n\n.g-multiselect-option--active {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n\n &:hover {\n color: var(--g-primary-text);\n }\n}\n\n.g-multiselect-option-check {\n display: block;\n align-items: center;\n justify-content: center;\n width: 1.25em;\n flex-shrink: 0;\n}\n.g-multiselect-option-check svg {\n display: block;\n fill: currentColor;\n}\n\n.g-multiselect-no-results {\n padding: 0.25em 1em;\n text-align: center;\n color: var(--g-surface-900);\n font-style: italic;\n}\n</style>\n\n","<script lang=\"ts\">\n/**\n * A multi-select combobox that allows selecting multiple values with\n * optional search/filter support.\n *\n * Selected values are displayed as removable chips inside the control.\n * The dropdown listbox shows all (or filtered) options with a checkmark\n * next to each selected option.\n *\n * The `options` prop accepts an array of strings or `{ label, value }`\n * objects. The `v-model` binds to an array of `string | number` values.\n *\n * In standard Vue usage, this registers with the nearest parent `GForm` via\n * injection. In custom-elements mode, use matching `form-key` values to pair\n * with a `GForm`.\n *\n * Keyboard navigation:\n * - `Down Arrow` / `Up Arrow`: move through options (opens menu if closed)\n * - `Enter`: toggle the focused option\n * - `Space`: toggle the focused option when the search field is empty\n * - `Escape`: close the dropdown\n * - `Home` / `End`: jump to first / last option\n * - `Backspace`: remove the last chip when the search field is empty\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport {\n computed,\n nextTick,\n ref,\n toRef,\n useId,\n} from \"vue\";\nimport { useSelectDropdown, normalizeSelectOptions, type SelectOption } from \"../compose/useSelectDropdown.ts\";\nimport { useFormField } from \"../compose/useFormField.ts\";\nimport GFormErrorMessages from \"./form/GFormErrorMessages.vue\";\n\ntype Props = {\n /**\n * List of options to choose from\n */\n options: Array<string | SelectOption>;\n /**\n * Accessible label\n * @demo Select Fruits\n */\n label: string;\n /**\n * Hide the label visually\n * @demo\n */\n hiddenLabel?: boolean;\n /**\n * Placeholder text shown when no values are selected\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Required\n * @demo\n */\n required?: boolean;\n /**\n * Name for form registration\n */\n name?: string;\n /**\n * Error messages array (supports multiple validation errors)\n */\n errors?: string[];\n /**\n * Instructions shown below the label\n * @demo\n */\n instructions?: string;\n /**\n * Form channel key for custom elements mode\n */\n formKey?: string;\n};\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n hiddenLabel: false,\n required: false,\n errors: () => [],\n});\n\nconst emit = defineEmits<{\n change: [value: Array<string | number>];\n}>();\n\nconst model = defineModel<Array<string | number>>({ default: () => [] });\n\nconst baseId = useId();\nconst inputRef = ref<HTMLInputElement | null>(null);\nconst controlRef = ref<HTMLElement | null>(null);\nconst listboxRef = ref<HTMLElement | null>(null);\n\nconst open = ref(false);\nconst activeIndex = ref(0);\nconst searchQuery = ref(\"\");\nconst ignoreBlur = ref(false);\n\nconst { displayErrors, hasErrors } = useFormField({\n name: props.name,\n value: model,\n errors: toRef(props, \"errors\"),\n formKey: props.formKey,\n});\n\nconst { menuPlacement, menuStyle, isTop, scrollOptionIntoView } = useSelectDropdown({\n open,\n anchorRef: controlRef,\n listboxRef,\n baseId,\n activeIndex,\n});\n\nconst normalizedOptions = computed<SelectOption[]>(() =>\n normalizeSelectOptions(props.options),\n);\n\nconst filteredOptions = computed<SelectOption[]>(() => {\n if (!searchQuery.value) return normalizedOptions.value;\n const q = searchQuery.value.toLowerCase();\n return normalizedOptions.value.filter((opt) =>\n opt.label.toLowerCase().includes(q),\n );\n});\n\nfunction isSelected(value: string | number): boolean {\n return model.value.includes(value);\n}\n\nfunction labelForValue(value: string | number): string {\n const opt = normalizedOptions.value.find((o) => o.value === value);\n return opt ? opt.label : String(value);\n}\n\n// ----- Open / close -----\n\nfunction openMenu() {\n if (props.disabled) return;\n open.value = true;\n activeIndex.value = 0;\n}\n\nfunction closeMenu() {\n open.value = false;\n searchQuery.value = \"\";\n}\n\n// ----- Option interaction -----\n\nfunction toggleOption(idx: number) {\n const opt = filteredOptions.value[idx];\n if (!opt) return;\n const current = model.value;\n const next: Array<string | number> = isSelected(opt.value)\n ? current.filter((v) => v !== opt.value)\n : [...current, opt.value];\n model.value = next;\n emit(\"change\", next);\n nextTick(() => inputRef.value?.focus());\n}\n\nfunction removeValue(value: string | number) {\n if (props.disabled) return;\n const next = model.value.filter((v) => v !== value);\n model.value = next;\n emit(\"change\", next);\n}\n\n// ----- Event handlers -----\n\nfunction onControlClick() {\n if (props.disabled) return;\n inputRef.value?.focus();\n if (!open.value) openMenu();\n}\n\nfunction onInput(e: Event) {\n searchQuery.value = (e.target as HTMLInputElement).value;\n activeIndex.value = 0;\n if (!open.value) openMenu();\n}\n\nfunction onFocus() {\n if (props.disabled) return;\n if (!open.value) openMenu();\n}\n\nfunction onBlur(e: FocusEvent) {\n if (ignoreBlur.value) {\n ignoreBlur.value = false;\n return;\n }\n const relatedTarget = e.relatedTarget as HTMLElement | null;\n if (relatedTarget && listboxRef.value?.contains(relatedTarget)) {\n return;\n }\n if (relatedTarget && controlRef.value?.contains(relatedTarget)) {\n return;\n }\n closeMenu();\n}\n\nfunction onOptionMouseDown(e: MouseEvent) {\n e.preventDefault();\n //ignoreBlur.value = true;\n}\n\nfunction onChipMouseDown() {\n ignoreBlur.value = true;\n}\n\nfunction onKeydown(e: KeyboardEvent) {\n if (props.disabled) return;\n const max = filteredOptions.value.length - 1;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.min(max, activeIndex.value + 1);\n scrollOptionIntoView();\n }\n break;\n case \"ArrowUp\":\n e.preventDefault();\n if (!open.value) {\n openMenu();\n } else {\n activeIndex.value = Math.max(0, activeIndex.value - 1);\n scrollOptionIntoView();\n }\n break;\n case \"Home\":\n e.preventDefault();\n activeIndex.value = 0;\n scrollOptionIntoView();\n break;\n case \"End\":\n e.preventDefault();\n activeIndex.value = max;\n scrollOptionIntoView();\n break;\n case \"Enter\":\n e.preventDefault();\n if (open.value && filteredOptions.value.length > 0) {\n toggleOption(activeIndex.value);\n } else if (!open.value) {\n openMenu();\n }\n break;\n case \" \":\n if (open.value && !searchQuery.value) {\n e.preventDefault();\n if (filteredOptions.value.length > 0) {\n toggleOption(activeIndex.value);\n }\n }\n break;\n case \"Escape\":\n if (isTop.value) {\n e.preventDefault();\n setTimeout(() => closeMenu(), 0);\n }\n break;\n case \"Backspace\":\n if (!searchQuery.value && model.value.length > 0) {\n removeValue(model.value[model.value.length - 1]);\n }\n break;\n }\n}\n\n// ----- IDs -----\n\nconst inputId = computed(() => `${baseId}-input`);\nconst labelId = computed(() => `${baseId}-label`);\nconst instructionsId = computed(() => `${baseId}-instructions`);\nconst errorId = computed(() => `error-message-${baseId}`);\n\nconst describedBy = computed(() => {\n const parts: string[] = [];\n if (props.instructions) parts.push(instructionsId.value);\n if (hasErrors.value) parts.push(errorId.value);\n return parts.length > 0 ? parts.join(\" \") : undefined;\n});\n</script>\n\n<template>\n <div\n class=\"g-multiselect-root\"\n :class=\"{\n 'g-multiselect-open': open,\n 'g-multiselect-has-error': hasErrors,\n }\"\n >\n <label v-if=\"!hiddenLabel\" :id=\"labelId\" :for=\"inputId\" class=\"g-multiselect-label\">\n {{ label }}<span v-if=\"required\" class=\"g-multiselect-required\" aria-hidden=\"true\"> *</span>\n </label>\n <div\n v-if=\"instructions\"\n :id=\"instructionsId\"\n class=\"g-multiselect-instructions\"\n >\n {{ instructions }}\n </div>\n <div\n ref=\"controlRef\"\n class=\"g-multiselect-control\"\n :class=\"{ 'g-multiselect-control--disabled': disabled }\"\n @click=\"onControlClick\"\n >\n <ul class=\"g-multiselect-chips\" :aria-labelledby=\"labelId\">\n <li v-for=\"val in model\" :key=\"val\" class=\"g-multiselect-chip\">\n <span class=\"g-multiselect-chip-label\">{{\n labelForValue(val)\n }}</span>\n <button\n type=\"button\"\n class=\"g-multiselect-chip-remove\"\n :aria-label=\"`Remove ${labelForValue(val)}`\"\n :disabled=\"disabled\"\n @mousedown=\"onChipMouseDown\"\n @click.stop=\"removeValue(val)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n width=\"1em\"\n role=\"none presentation\"\n >\n <path\n fill=\"currentColor\"\n d=\"m37.84 32.94-7.63-7.63 7.63-7.63a3.24 3.24 0 0 0-4.58-4.58l-7.63 7.63L18 13.1a3.24 3.24 0 0 0-4.58 4.58L21 25.31l-7.62 7.63A3.24 3.24 0 1 0 18 37.52l7.63-7.63 7.63 7.63a3.24 3.24 0 0 0 4.58-4.58Z\"\n />\n </svg>\n </button>\n </li>\n </ul>\n\n <input\n ref=\"inputRef\"\n type=\"text\"\n role=\"combobox\"\n class=\"g-multiselect-input\"\n :id=\"inputId\"\n :value=\"searchQuery\"\n :placeholder=\"model.length === 0 ? placeholder : undefined\"\n :disabled=\"disabled\"\n autocomplete=\"off\"\n aria-autocomplete=\"list\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n aria-haspopup=\"listbox\"\n :aria-controls=\"baseId + '-listbox'\"\n :aria-activedescendant=\"\n open && filteredOptions.length > 0\n ? baseId + '-option-' + activeIndex\n : undefined\n \"\n v-bind=\"hiddenLabel ? { 'aria-label': label } : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"required ? 'true' : undefined\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n />\n\n <svg\n class=\"g-multiselect-caret\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 51.26 51.26\"\n aria-hidden=\"true\"\n width=\"1.125em\"\n >\n <!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n fill=\"currentColor\"\n d=\"M38.75 24.13a1.36 1.36 0 0 1 0 2.36l-12.44 7.18-12.43 7.18a1.36 1.36 0 0 1-2.05-1.18V11a1.36 1.36 0 0 1 2.05-1.18L26.31 17Z\"\n />\n </svg>\n\n <div\n v-show=\"open\"\n ref=\"listboxRef\"\n :id=\"baseId + '-listbox'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n v-bind=\"\n hiddenLabel\n ? { 'aria-label': label }\n : { 'aria-labelledby': labelId }\n \"\n class=\"g-multiselect-listbox\"\n :class=\"{\n 'g-multiselect-listbox--above': menuPlacement === 'above',\n }\"\n :style=\"menuStyle\"\n tabindex=\"-1\"\n >\n <template v-if=\"filteredOptions.length > 0\">\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"opt.value\"\n :id=\"baseId + '-option-' + idx\"\n role=\"option\"\n class=\"g-multiselect-option\"\n :class=\"{\n 'g-multiselect-option--active': idx === activeIndex,\n 'g-multiselect-option--selected': isSelected(opt.value),\n }\"\n :aria-selected=\"isSelected(opt.value) ? 'true' : 'false'\"\n @mousedown=\"onOptionMouseDown\"\n @click=\"toggleOption(idx)\"\n >\n <span class=\"g-multiselect-option-check\" aria-hidden=\"true\">\n <svg\n v-if=\"isSelected(opt.value)\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 640 640\"\n width=\"1.25em\"\n >\n <!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->\n <path\n d=\"M530.8 134.1C545.1 144.5 548.3 164.5 537.9 178.8L281.9 530.8C276.4 538.4 267.9 543.1 258.5 543.9C249.1 544.7 240 541.2 233.4 534.6L105.4 406.6C92.9 394.1 92.9 373.8 105.4 361.3C117.9 348.8 138.2 348.8 150.7 361.3L252.2 462.8L486.2 141.1C496.6 126.8 516.6 123.6 530.9 134z\"\n />\n </svg>\n </span>\n {{ opt.label }}\n </div>\n </template>\n <template v-else>\n <div\n aria-live=\"polite\"\n class=\"g-multiselect-option g-multiselect-no-results\"\n >\n No results found.\n </div>\n </template>\n </div>\n </div>\n\n\n <GFormErrorMessages :errors=\"displayErrors\" :id=\"errorId\" />\n </div>\n</template>\n\n<style>\ng-multi-select {\n display: block;\n}\n.g-multiselect-root {\n position: relative;\n font-size: 1rem;\n}\n\n.g-multiselect-label {\n font-weight: 700;\n color: var(--g-surface-900);\n margin-bottom: 0.5em;\n}\n\n.g-multiselect-required {\n color: var(--g-danger-600);\n}\n\n.g-multiselect-instructions {\n margin: 0 0 0.5em 0;\n color: var(--g-surface-800);\n}\n\n.g-multiselect-control {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n list-style: none;\n gap: 0.375em;\n min-height: calc(0.25em + 1.875em + 0.25em + 4px);\n padding: 0.3em 2.25em 0.3em 0.5em;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid var(--g-primary-500);\n border-radius: var(--g-border-radius-m);\n cursor: text;\n position: relative;\n box-sizing: border-box;\n\n &:focus-within {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 1px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n }\n}\n\n.g-multiselect-control--disabled {\n cursor: not-allowed;\n border-color: var(--g-surface-400);\n}\n\n.g-multiselect-control--disabled .g-multiselect-input {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.g-multiselect-control--disabled .g-multiselect-caret {\n opacity: 0.6;\n}\n\n.g-multiselect-has-error .g-multiselect-control {\n border-color: var(--g-danger-600);\n background: var(--g-danger-100);\n}\n\n.g-multiselect-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 0.375em;\n list-style: none;\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\n.g-multiselect-chip {\n display: inline-flex;\n align-items: center;\n gap: 0.2em;\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n border-radius: 1em;\n padding: 0.1em 0.4em 0.1em 0.6em;\n line-height: 1.5;\n max-width: 100%;\n}\n\n.g-multiselect-chip-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.g-multiselect-chip-remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: none;\n border: none;\n color: inherit;\n cursor: pointer;\n padding: 0.15em;\n box-sizing: border-box;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n flex-shrink: 0;\n line-height: 1;\n\n &:hover {\n background: rgba(0, 0, 0, 0.15);\n }\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.7;\n }\n}\n\n.g-multiselect-input {\n flex: 1;\n min-width: 4em;\n border: none;\n background: transparent;\n font-family: var(--il-font-sans);\n font-size: 1em;\n line-height: 1.875em;\n padding: 0;\n outline: none;\n color: var(--g-surface-900);\n cursor: text;\n\n &:disabled {\n cursor: not-allowed;\n }\n\n &::placeholder {\n color: var(--g-surface-600);\n }\n}\n\n.g-multiselect-caret {\n position: absolute;\n right: 0.5em;\n top: calc(50% - 0.55em);\n color: var(--g-accent-700);\n pointer-events: none;\n transform: rotate(90deg);\n transition: transform 0.15s ease;\n}\n\n.g-multiselect-open .g-multiselect-caret {\n transform: rotate(-90deg);\n}\n\n.g-multiselect-listbox {\n position: absolute;\n left: 0;\n top: 100%;\n width: 100%;\n z-index: 1000;\n background-color: var(--g-surface-0);\n border: 2px solid var(--g-surface-700);\n border-radius: 0 0 var(--g-border-radius-m) var(--g-border-radius-m);\n box-shadow:\n 0 4px 4px rgba(0, 0, 0, 0.2),\n 0 1px 0 1px rgba(0, 0, 0, 0.18);\n max-height: 50vh;\n overflow-y: auto;\n box-sizing: border-box;\n display: none;\n}\n\n.g-multiselect-open .g-multiselect-listbox {\n display: block;\n}\n\n.g-multiselect-listbox--above {\n top: auto;\n bottom: 100%;\n border-radius: var(--g-border-radius-m) var(--g-border-radius-m) 0 0;\n}\n\n.g-multiselect-option {\n display: flex;\n align-items: center;\n gap: 0.5em;\n padding: 0.5em 0.5em;\n cursor: pointer;\n background: var(--g-surface-0);\n color: var(--g-surface-900);\n border: 2px solid transparent;\n\n &:hover {\n text-decoration: underline;\n color: var(--g-accent-700);\n border-color: var(--g-accent-700);\n }\n}\n\n.g-multiselect-option--active {\n background: var(--g-primary-500);\n color: var(--g-primary-text);\n\n &:hover {\n color: var(--g-primary-text);\n }\n}\n\n.g-multiselect-option-check {\n display: block;\n align-items: center;\n justify-content: center;\n width: 1.25em;\n flex-shrink: 0;\n}\n.g-multiselect-option-check svg {\n display: block;\n fill: currentColor;\n}\n\n.g-multiselect-no-results {\n padding: 0.25em 1em;\n text-align: center;\n color: var(--g-surface-900);\n font-style: italic;\n}\n</style>\n\n","import { unrefElement, useIntersectionObserver, useMutationObserver } from \"@vueuse/core\";\nimport { ref, Ref } from \"vue\";\n\n/**\n * Monitor a list of elements' intersection with the viewport to update active links.\n *\n * This updates the activeLink store with the active content's ID for use in menus.\n *\n * @param element Children of this element will be observed\n * @param topOffset Offset from the top of the window to consider not visible\n * @param activeId Ref to store the active element ID\n */\nexport function useActiveLinkContent(\n element: Ref<HTMLElement | null>,\n topOffset: number,\n activeId: Ref<string>,\n) {\n const thresholds = [0, 0.25, 0.5, 0.75, 1];\n const rootMargin = `${-topOffset}px 0px 0px 0px`;\n // Track most visible section in viewport\n const visibility = new Map<Element, number>();\n\n // Get the children elements to observe\n const elements = ref<HTMLElement[]>(\n Array.from(unrefElement(element)?.children || []) as HTMLElement[],\n );\n\n // To maintain reactivity, observe for changes in child elements.\n // This works better with Nuxt's rendering than passing an array of refs.\n useMutationObserver(\n element,\n () => {\n elements.value = Array.from(\n unrefElement(element)?.children || [],\n ) as HTMLElement[];\n },\n { childList: true },\n );\n\n const { stop } = useIntersectionObserver(\n elements,\n (entries) => {\n const lastElement = elements.value[elements.value.length - 1];\n\n for (const entry of entries) {\n // Use intersection ratio as visibility score\n // Round up to 2 decimal places since it can sometimes be 0.99x\n // when jumping to it with anchor links.\n visibility.set(\n entry.target,\n entry.isIntersecting ? Math.ceil(100 * entry.intersectionRatio)/100 : 0,\n );\n if (\n entry.target === lastElement &&\n entry.intersectionRatio === 1\n ) {\n // If last element is fully visible, prioritize that\n visibility.set(entry.target, Number.POSITIVE_INFINITY);\n }\n }\n // Pick the element with highest visibility\n let bestEl: Element | null = null;\n let bestScore = 0;\n for (const el of visibility.keys()) {\n const score = visibility.get(el) || 0;\n if (score <= bestScore) {\n continue;\n }\n const rect = (el as HTMLElement).getBoundingClientRect();\n bestEl = el;\n bestScore = score;\n }\n if (bestEl instanceof HTMLElement) {\n activeId.value = bestEl.id;\n } else {\n activeId.value = \"\";\n }\n },\n {\n threshold: thresholds,\n root: null,\n rootMargin,\n immediate: true,\n },\n );\n\n return { stop };\n}\n","import { onBeforeUnmount, onMounted, Ref, ref, useId, watch } from \"vue\";\nimport { useMediaQuery } from \"@vueuse/core\";\n\n/**\n * Composable to manage sidebar state and link components together.\n *\n * @param breakpoint Media query string for when the sidebar should be collapsible\n */\nexport function useSidebar(\n breakpoint: Ref<string> | string = \"(max-width: 800px)\",\n) {\n const id = useId();\n const open = ref(false);\n const isCollapsible: Ref<boolean> = useMediaQuery(breakpoint, {\n ssrWidth: 1000\n });\n\n function onDocumentClick(e: MouseEvent) {\n if (!isCollapsible.value || !open.value) {\n return;\n }\n const target = e.target as HTMLElement;\n const sidebarEl = document.getElementById(`${id}-sidebar`);\n if (!sidebarEl) {\n return;\n }\n if (sidebarEl.contains(target)) {\n return;\n }\n // Very slight delay means if they click the menu button to close it,\n // it won't re-open\n setTimeout(() => {\n open.value = false;\n }, 5);\n }\n function onDocumentFocus(e: FocusEvent) {\n if (!isCollapsible.value || !open.value) {\n return;\n }\n const target = e.target as HTMLElement;\n const sidebarEl = document.getElementById(`${id}-sidebar`);\n const hamburgerEl = document.getElementById(`${id}-hamburger`);\n if (!sidebarEl) {\n return;\n }\n if (sidebarEl.contains(target) || hamburgerEl?.contains(target)) {\n return;\n }\n // Very slight delay means if they click the menu button to close it,\n // it won't re-open\n setTimeout(() => {\n open.value = false;\n }, 5);\n }\n\n onMounted(() => {\n watch(\n isCollapsible,\n (val) => {\n if (val) {\n document.addEventListener(\"mousedown\", onDocumentClick);\n document.addEventListener(\"focusin\", onDocumentFocus);\n } else {\n document.removeEventListener(\"mousedown\", onDocumentClick);\n document.removeEventListener(\"focusin\", onDocumentFocus);\n }\n },\n { immediate: true },\n );\n });\n\n onBeforeUnmount(() => {\n document.removeEventListener(\"mousedown\", onDocumentClick);\n document.removeEventListener(\"focusin\", onDocumentFocus);\n });\n\n return {\n id,\n open,\n isCollapsible,\n toggle: () => (open.value = !open.value),\n };\n}\n","import { computed, ComputedRef, shallowReactive } from \"vue\";\nimport { TableColumn, TableRow } from \"../components/table/TableColumn.ts\";\nimport { createEventHook, EventHook, EventHookOn } from \"@vueuse/core\";\n\nexport type ColumnKey<R extends TableRow> = Extract<keyof R, string>;\n\n/**\n * Represents a single cell change in the table\n */\nexport interface CellChange<\n R extends TableRow = TableRow,\n K extends ColumnKey<R> = ColumnKey<R>,\n> {\n rowKey: string;\n columnKey: K;\n row: R;\n /**\n * Represents the original value before user changes.\n */\n previousValue: R[K];\n /**\n * New value from the change. This will be null in events\n * if the new value is the same as the original.\n */\n newValue: R[K];\n error?: string;\n}\n\nexport interface CellChangeEvent<\n R extends TableRow = TableRow,\n K extends ColumnKey<R> = ColumnKey<R>,\n> {\n rowKey: string;\n columnKey: K;\n row: R;\n previousValue: R[K];\n newValue: R[K] | null;\n error?: string;\n}\n\n/**\n * Payload for the `trackChange` function\n */\nexport interface CellChangePayload<\n T extends TableRow = TableRow,\n K extends ColumnKey<T> = ColumnKey<T>,\n> {\n row: T;\n column: TableColumn<T, K>;\n value: T[K];\n previousValue: T[K];\n}\n\n/**\n * Map of changes organized by row key and column key\n */\nexport type ChangeMap<T extends TableRow> = Map<\n string,\n Map<ColumnKey<T>, CellChange<T>>\n>;\n\n/**\n * Return type for the useTableChanges composable\n */\nexport interface UseTableChangesReturn<T extends TableRow> {\n /**\n * Track a change to a cell\n */\n trackChange: <K extends ColumnKey<T>>(payload: CellChangePayload<T, K>) => void;\n\n /**\n * Reactive array of all current changes\n */\n changes: ComputedRef<CellChange<T>[]>;\n\n /**\n * Get changes organized by row\n */\n getChangesByRow: () => Map<string, Partial<T>>;\n\n /**\n * Reactive flag indicating whether there are any changes\n */\n hasChanges: ComputedRef<boolean>;\n\n /**\n * Check if a specific cell has changes\n */\n hasChange: <K extends ColumnKey<T>>(rowKey: string, columnKey: K) => boolean;\n\n /**\n * Get the changed value for a specific cell, or undefined if no change\n */\n getChange: <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ) => T[K] | undefined;\n\n /**\n * Clear all tracked changes\n */\n clearChanges: () => void;\n\n /**\n * Clear changes for a specific row\n */\n clearRowChanges: (rowKey: string) => void;\n\n /**\n * Apply tracked changes to a new dataset (for merging with updated data)\n */\n applyChangesToData: (data: T[]) => T[];\n\n /**\n * Reactive count of changed cells\n */\n changeCount: ComputedRef<number>;\n\n /**\n * Reactive flag indicating whether any cell has an error\n */\n hasErrors: ComputedRef<boolean>;\n\n /**\n * Set an error message for a specific cell\n */\n setError: <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n error: string,\n ) => void;\n\n /**\n * Clear the error for a specific cell\n */\n clearError: <K extends ColumnKey<T>>(rowKey: string, columnKey: K) => void;\n\n /**\n * Get the error message for a specific cell, or undefined if no error\n */\n getError: <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ) => string | undefined;\n\n /**\n * Check if a specific cell has an error\n */\n hasError: <K extends ColumnKey<T>>(rowKey: string, columnKey: K) => boolean;\n\n /**\n * Event emitted when a cell changes.\n *\n * `newValue` will be null if the cell's value is the same as the original.\n */\n onChange: EventHookOn<CellChangeEvent<T>>;\n}\n\n/**\n * Composable for tracking changes to table data\n *\n * This composable helps manage user edits to table data by:\n * - Tracking which cells have been modified\n * - Storing both old and new values\n * - Providing methods to retrieve just the changes (not full data)\n * - Allowing changes to be applied to updated data for real-time syncing\n *\n * @example\n * ```typescript\n * const changes = useTableChanges<ProductRow>();\n *\n * // Track a change when user edits a cell\n * function handleCellChange(row, column, newValue) {\n * changes.trackChange({\n * row,\n * column,\n * value: newValue,\n * previousValue: row[column.key],\n * });\n * }\n *\n * // Get all changes to submit\n * const changedData = changes.changes.value;\n * await api.updateProducts(changedData);\n *\n * // Apply user changes to fresh data from server\n * const freshData = await api.getProducts();\n * const mergedData = changes.applyChangesToData(freshData);\n * ```\n */\nexport function useTableChanges<\n T extends TableRow,\n>(): UseTableChangesReturn<T> {\n // Store changes in a reactive map: rowKey -> columnKey -> CellChange\n const changeMap = shallowReactive<ChangeMap<T>>(new Map());\n\n const changeEvent = createEventHook<CellChangeEvent<T>>();\n\n /**\n * Track a change to a specific cell\n */\n const trackChange = <K extends ColumnKey<T>>(\n payload: CellChangePayload<T, K>,\n ) => {\n const colKey: K = payload.column.key;\n const rowKey = payload.row.key;\n const newValue: T[K] = payload.value;\n const previousValue: T[K] = payload.previousValue;\n\n if (!changeMap.has(rowKey)) {\n changeMap.set(\n rowKey,\n shallowReactive(new Map<ColumnKey<T>, CellChange<T>>()),\n );\n }\n\n const rowChanges = changeMap.get(\n rowKey,\n )!;\n\n // If there's already a change for this cell, preserve the original previousValue\n const existingChange = rowChanges.get(colKey);\n const originalpreviousValue: T[K] = (existingChange\n ? existingChange.previousValue\n : previousValue) as T[K];\n const existingError = existingChange?.error;\n\n // If the new value equals the original value, remove the change\n if (newValue === originalpreviousValue) {\n rowChanges.delete(colKey);\n // Clean up empty row maps\n if (rowChanges.size === 0) {\n changeMap.delete(rowKey);\n }\n // null newValue means the value was reverted to the original\n changeEvent.trigger({\n rowKey,\n columnKey: colKey,\n row: payload.row,\n previousValue: originalpreviousValue,\n newValue: null,\n });\n } else {\n // Store or update the change, preserving error if it exists\n const updatedChange: CellChange<T, K> = {\n rowKey,\n columnKey: colKey,\n row: payload.row,\n previousValue: originalpreviousValue,\n newValue,\n };\n\n // Preserve error if it exists\n if (existingError !== undefined) {\n updatedChange.error = existingError;\n }\n\n rowChanges.set(colKey, updatedChange);\n changeEvent.trigger(updatedChange);\n }\n };\n\n const changes = computed(() => {\n const result: CellChange<T>[] = [];\n changeMap.forEach((rowChanges) => {\n rowChanges.forEach((change) => {\n result.push({ ...change });\n });\n });\n return result;\n });\n\n /**\n * Get changes organized by row key\n * Returns a map of rowKey -> object with changed fields\n */\n const getChangesByRow = (): Map<string, Partial<T>> => {\n const result = new Map<string, Partial<T>>();\n changeMap.forEach((rowChanges, rowKey) => {\n const rowData: Partial<T> = { key: rowKey } as Partial<T>;\n rowChanges.forEach((change, columnKey) => {\n rowData[columnKey] = change.newValue;\n });\n result.set(rowKey, rowData);\n });\n return result;\n };\n\n const hasChanges = computed<boolean>(() => changeMap.size > 0);\n\n /**\n * Check if a specific cell has changes\n */\n const hasChange = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ): boolean => {\n const rowChanges = changeMap.get(rowKey);\n if (!rowChanges) return false;\n return rowChanges.has(columnKey);\n };\n\n /**\n * Get the changed value for a specific cell\n */\n const getChange = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ): T[K] | undefined => {\n const rowChanges = changeMap.get(rowKey);\n if (!rowChanges) return undefined;\n const change = rowChanges.get(columnKey);\n return change?.newValue as T[K] | undefined;\n };\n\n /**\n * Clear all tracked changes\n */\n const clearChanges = () => {\n changeMap.clear();\n };\n\n /**\n * Clear changes for a specific row\n */\n const clearRowChanges = (rowKey: string) => {\n changeMap.delete(rowKey);\n };\n\n /**\n * Apply tracked changes to a new dataset\n * This is useful when you have fresh data from the server but want to preserve user edits\n */\n const applyChangesToData = (data: T[]): T[] => {\n return data.map((row) => {\n const rowChanges = changeMap.get(row.key);\n if (!rowChanges || rowChanges.size === 0) {\n return row;\n }\n\n // Create a new object with the row data and apply changes\n const updatedRow = { ...row };\n rowChanges.forEach((change, columnKey) => {\n updatedRow[columnKey] = change.newValue;\n });\n\n return updatedRow;\n });\n };\n\n const changeCount = computed<number>(() => {\n let count = 0;\n changeMap.forEach((rowChanges) => {\n count += rowChanges.size;\n });\n return count;\n });\n\n const hasErrors = computed<boolean>(() =>\n changes.value.some(\n (change) => change.error !== undefined && change.error !== \"\",\n ),\n );\n\n /**\n * Set an error message for a specific cell\n */\n const setError = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n error: string,\n ) => {\n const rowChanges = changeMap.get(rowKey);\n if (!rowChanges) return;\n\n const change = rowChanges.get(columnKey);\n if (!change) return;\n\n // With shallowReactive(Map), mutating nested objects won't trigger updates.\n // Replace the map entry so Vue can observe the change.\n const updatedChange: CellChange<T> = { ...change, error };\n rowChanges.set(columnKey, updatedChange);\n };\n\n /**\n * Clear the error for a specific cell\n */\n const clearError = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ) => {\n const rowChanges = changeMap.get(rowKey);\n if (!rowChanges) return;\n\n const change = rowChanges.get(columnKey);\n if (!change) return;\n\n // Replace the map entry so Vue can observe the change.\n const updatedChange: CellChange<T> = { ...change };\n delete updatedChange.error;\n rowChanges.set(columnKey, updatedChange);\n };\n\n /**\n * Get the error message for a specific cell\n */\n const getError = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ): string | undefined => {\n const rowChanges = changeMap.get(rowKey);\n if (!rowChanges) return undefined;\n\n const change = rowChanges.get(columnKey);\n return change?.error;\n };\n\n /**\n * Check if a specific cell has an error\n */\n const hasError = <K extends ColumnKey<T>>(\n rowKey: string,\n columnKey: K,\n ): boolean => {\n const error = getError(rowKey, columnKey);\n return error !== undefined && error !== \"\";\n };\n\n return {\n trackChange,\n changes,\n getChangesByRow,\n hasChanges,\n hasChange,\n getChange,\n clearChanges,\n clearRowChanges,\n applyChangesToData,\n changeCount,\n hasErrors,\n setError,\n clearError,\n getError,\n hasError,\n onChange: changeEvent.on,\n };\n}\n"],"x_google_ignoreList":[17,18],"mappings":"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"}