From 9480b4c0f7ba360c6739c6b7af4e1ecb08437298 Mon Sep 17 00:00:00 2001 From: Shehab Rahal Date: Sat, 23 Sep 2023 19:15:00 +0800 Subject: [PATCH 01/79] feat: menu component --- README.md | 5 +- package.json | 2 + packages/components/menu/README.md | 14 + packages/components/menu/build.config.ts | 7 + packages/components/menu/package.json | 60 ++ packages/components/menu/src/index.ts | 134 ++++ packages/components/menu/src/menu-anchor.ts | 41 ++ packages/components/menu/src/menu-arrow.ts | 35 + .../components/menu/src/menu-checkbox-item.ts | 55 ++ .../components/menu/src/menu-content-impl.ts | 240 +++++++ packages/components/menu/src/menu-content.ts | 74 ++ packages/components/menu/src/menu-group.ts | 34 + .../components/menu/src/menu-item-impl.ts | 106 +++ .../menu/src/menu-item-indicator.ts | 49 ++ packages/components/menu/src/menu-item.ts | 88 +++ packages/components/menu/src/menu-label.ts | 33 + packages/components/menu/src/menu-portal.ts | 52 ++ .../components/menu/src/menu-radio-group.ts | 48 ++ .../components/menu/src/menu-radio-item.ts | 57 ++ .../menu/src/menu-root-content-non-modal.ts | 42 ++ .../menu/src/menu-root-content-type.ts | 66 ++ .../components/menu/src/menu-separator.ts | 33 + .../components/menu/src/menu-sub-content.ts | 104 +++ .../components/menu/src/menu-sub-trigger.ts | 157 +++++ packages/components/menu/src/menu-sub.ts | 62 ++ packages/components/menu/src/menu.test..ts | 7 + packages/components/menu/src/menu.ts | 80 +++ packages/components/menu/src/props.ts | 655 ++++++++++++++++++ packages/components/menu/src/utils.ts | 102 +++ packages/components/menu/tests/menu.test.ts | 7 + packages/components/menu/tsconfig.json | 8 + packages/components/primitives/package.json | 1 + playground/nuxt3/package.json | 1 + playground/vue3/package.json | 1 + pnpm-lock.yaml | 268 +++---- vitest.config.ts | 1 + 36 files changed, 2558 insertions(+), 171 deletions(-) create mode 100644 packages/components/menu/README.md create mode 100644 packages/components/menu/build.config.ts create mode 100644 packages/components/menu/package.json create mode 100644 packages/components/menu/src/index.ts create mode 100644 packages/components/menu/src/menu-anchor.ts create mode 100644 packages/components/menu/src/menu-arrow.ts create mode 100644 packages/components/menu/src/menu-checkbox-item.ts create mode 100644 packages/components/menu/src/menu-content-impl.ts create mode 100644 packages/components/menu/src/menu-content.ts create mode 100644 packages/components/menu/src/menu-group.ts create mode 100644 packages/components/menu/src/menu-item-impl.ts create mode 100644 packages/components/menu/src/menu-item-indicator.ts create mode 100644 packages/components/menu/src/menu-item.ts create mode 100644 packages/components/menu/src/menu-label.ts create mode 100644 packages/components/menu/src/menu-portal.ts create mode 100644 packages/components/menu/src/menu-radio-group.ts create mode 100644 packages/components/menu/src/menu-radio-item.ts create mode 100644 packages/components/menu/src/menu-root-content-non-modal.ts create mode 100644 packages/components/menu/src/menu-root-content-type.ts create mode 100644 packages/components/menu/src/menu-separator.ts create mode 100644 packages/components/menu/src/menu-sub-content.ts create mode 100644 packages/components/menu/src/menu-sub-trigger.ts create mode 100644 packages/components/menu/src/menu-sub.ts create mode 100644 packages/components/menu/src/menu.test..ts create mode 100644 packages/components/menu/src/menu.ts create mode 100644 packages/components/menu/src/props.ts create mode 100644 packages/components/menu/src/utils.ts create mode 100644 packages/components/menu/tests/menu.test.ts create mode 100644 packages/components/menu/tsconfig.json diff --git a/README.md b/README.md index ac9731542..751d73e46 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Website: [Oku Website](https://oku-ui.com) Please read our [contributing guide](https://github.com/oku-ui/primitives/blob/master/CONTRIBUTING.md) -# TODO Components - 19/28 +# TODO Components - 23/30 Enter the component you want most in the components, leave the emojis and follow. @@ -38,8 +38,9 @@ Enter the component you want most in the components, leave the emojis and follow | [Form](https://github.com/oku-ui/primitives/issues/11) | A group of form controls | Not Started | - | | [Hover Card](https://oku-ui.com/primitives/components/hover-card) | Version | Downloads | Website | | [Label](https://oku-ui.com/primitives/components/label) | Version | Downloads | Website | +| [Menu](https://oku-ui.com/primitives/components/menu) | Version | Downloads | Website | | [Menubar](https://github.com/oku-ui/primitives/issues/13) | A menu that appears when a user interacts with an element's trigger | 🚧 In Progress | - | -| [Navigation Menu](https://github.com/oku-ui/primitives/issues/14) | A menu that appears when a user interacts with an element's trigger | Not Started | - | +| [Navigation Menu](https://github.com/oku-ui/primitives/issues/14) | A collection of links for navigating websites | Not Started | - | | [Popover](https://oku-ui.com/primitives/components/popover) | Version | Downloads | Website | | [Progress](https://oku-ui.com/primitives/components/progress) | Version | Downloads | Website | | [Radio Group](https://oku-ui.com/primitives/components/radio-group) | Version | Downloads | Website | diff --git a/package.json b/package.json index 8385bd363..8f77039a1 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@oku-ui/focus-scope": "workspace:^", "@oku-ui/hover-card": "workspace:^", "@oku-ui/label": "workspace:^", + "@oku-ui/menu": "workspace:^", "@oku-ui/popover": "workspace:^", "@oku-ui/popper": "workspace:^", "@oku-ui/portal": "workspace:^", @@ -135,6 +136,7 @@ "@oku-ui/focus-scope": "workspace:^", "@oku-ui/hover-card": "workspace:^", "@oku-ui/label": "workspace:^", + "@oku-ui/menu": "workspace:^", "@oku-ui/popover": "workspace:^", "@oku-ui/popper": "workspace:^", "@oku-ui/portal": "workspace:^", diff --git a/packages/components/menu/README.md b/packages/components/menu/README.md new file mode 100644 index 000000000..5aaa6a0cd --- /dev/null +++ b/packages/components/menu/README.md @@ -0,0 +1,14 @@ +# `@oku-ui/menu` +A top level menu item, contains a trigger with content combination. + + + +Version | Downloads | Website + +## Installation + +```sh +$ pnpm add @oku-ui/menu +``` + +[Documentation](https://oku-ui.com/primitives/components/menu) diff --git a/packages/components/menu/build.config.ts b/packages/components/menu/build.config.ts new file mode 100644 index 000000000..e20ccb0a1 --- /dev/null +++ b/packages/components/menu/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild' + +const isClean = (process.env.CLEAN || 'false') === 'true' +export default defineBuildConfig({ + declaration: true, + clean: isClean, +}) diff --git a/packages/components/menu/package.json b/packages/components/menu/package.json new file mode 100644 index 000000000..10560eed0 --- /dev/null +++ b/packages/components/menu/package.json @@ -0,0 +1,60 @@ +{ + "name": "@oku-ui/menu", + "type": "module", + "version": "0.4.1", + "license": "MIT", + "source": "src/index.ts", + "funding": "https://github.com/sponsors/productdevbook", + "homepage": "https://oku-ui.com/primitives", + "repository": { + "type": "git", + "url": "git+https://github.com/oku-ui/primitives.git", + "directory": "packages/components/menu" + }, + "bugs": { + "url": "https://github.com/oku-ui/primitives/issues" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs" + } + }, + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "unbuild", + "dev": "unbuild --stub", + "clean": "rimraf ./dist && rimraf ./node_modules" + }, + "peerDependencies": { + "vue": "^3.3.0" + }, + "dependencies": { + "@oku-ui/collection": "latest", + "@oku-ui/direction": "latest", + "@oku-ui/dismissable-layer": "latest", + "@oku-ui/focus-guards": "latest", + "@oku-ui/popper": "latest", + "@oku-ui/portal": "latest", + "@oku-ui/presence": "latest", + "@oku-ui/primitive": "latest", + "@oku-ui/provide": "latest", + "@oku-ui/roving-focus": "latest", + "@oku-ui/slot": "latest", + "@oku-ui/use-composable": "latest", + "@oku-ui/utils": "latest" + }, + "devDependencies": { + "tsconfig": "workspace:^" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/components/menu/src/index.ts b/packages/components/menu/src/index.ts new file mode 100644 index 000000000..067824369 --- /dev/null +++ b/packages/components/menu/src/index.ts @@ -0,0 +1,134 @@ +// export { OkuMenu } from './menu' +export { OkuMenuAnchor } from './menu-anchor' +export { OkuMenuArrow } from './menu-arrow' +export { OkuMenuCheckboxItem } from './menu-checkbox-item' +export { OkuMenuContent } from './menu-content' +// export { OkuMenuContentImpl } from './menu-content-impl' +// export { OkuMenuRootContentTypel } from './menu-content-modal' +export { OkuMenuGroup } from './menu-group' +export { OkuMenuItemIndicator } from './menu-item-indicator' +export { OkuMenuItem } from './menu-item' +export { OkuMenuItemImpl } from './menu-item-impl' +export { OkuMenuLabel } from './menu-label' +export { OkuMenuPortal } from './menu-portal' +export { OkuMenuRadioGroup } from './menu-radio-group' +// export { OkuMenuRadioItem } from './menu-radio-item' +export { OkuMenuRootContentNonModal } from './menu-root-content-non-modal' +export { OkuMenuSeparator } from './menu-separator' +export { OkuMenuSub } from './menu-sub' +export { OkuMenuSubContent } from './menu-sub-content' +export { OkuMenuSubTrigger } from './menu-sub-trigger' + +export type { + MenuProps, +} from './menu' + +export type { + MenuAnchorProps, + MenuAnchorElement, + MenuAnchorNaviteElement, +} from './menu-anchor' + +export type { + MenuArrowProps, + MenuArrowElement, + MenuArrowNaviteElement, +} from './menu-arrow' + +export type { + MenuCheckboxItemProps, + MenuCheckboxItemElement, + MenuCheckboxItemNaviteElement, +} from './menu-checkbox-item' + +export type { + MenuContentProps, + MenuContentElement, + MenuContentNaviteElement, +} from './menu-content' + +export type { + // MenuContentImplProps, + MenuContentImplElement, + MenuContentImplNaviteElement, +} from './menu-content-impl' + +export type { + MenuRootContentTypeProps, + MenuRootContentTypeElement, + MenuRootContentTypeNaviteElement, +} from './menu-root-content-type' + +export type { + MenuGroupProps, + MenuGroupElement, + MenuGroupNaviteElement, +} from './menu-group' + +export type { + MenuItemIndicatorProps, + MenuItemIndicatorElement, + MenuItemIndicatorNaviteElement, +} from './menu-item-indicator' + +export type { + MenuItemProps, + MenuItemElement, + MenuItemNaviteElement, +} from './menu-item' + +export type { + MenuItemImplProps, + MenuItemImplElement, + MenuItemImplNaviteElement, +} from './menu-item-impl' + +export type { + MenuLabelProps, + MenuLabelElement, + MenuLabelNaviteElement, +} from './menu-label' + +export type { + MenuPortalProps, + MenuPortalElement, + MenuPortalNaviteElement, +} from './menu-portal' + +export type { + MenuRadioGroupProps, + MenuRadioGroupElement, + MenuRadioGroupNaviteElement, +} from './menu-radio-group' + +export type { + MenuRadioItemProps, + MenuRadioItemElement, + MenuRadioItemNaviteElement, +} from './menu-radio-item' + +// export type { } from './menu-root-content-non-modal' + +export type { + MenuSeparatorProps, + MenuSeparatorElement, + MenuSeparatorNaviteElement, +} from './menu-separator' + +export type { + MenuSubProps, + // MenuSubElement, + // MenuSubNaviteElement, +} from './menu-sub' + +export type { + menuSubContentProps, + // menuSubContentElement, + // menuSubContentNaviteElement, +} from './menu-sub-content' + +export type { + MenuSubTriggerProps, + MenuSubTriggerElement, + MenuSubTriggerNaviteElement, +} from './menu-sub-trigger' diff --git a/packages/components/menu/src/menu-anchor.ts b/packages/components/menu/src/menu-anchor.ts new file mode 100644 index 000000000..39065f86c --- /dev/null +++ b/packages/components/menu/src/menu-anchor.ts @@ -0,0 +1,41 @@ +import { defineComponent, h, toRefs } from 'vue' +import { OkuPopperAnchor } from '@oku-ui/popper' +import { useForwardRef } from '@oku-ui/use-composable' +import { primitiveProps } from '@oku-ui/primitive' +import { MENU_ANCHOR_NAME, menuAnchorProps, scopedMenuProps, usePopperScope } from './props' +import type { MenuAnchorNaviteElement } from './props' + +const menuAnchor = defineComponent({ + name: MENU_ANCHOR_NAME, + components: { + OkuPopperAnchor, + }, + inheritAttrs: false, + props: { + ...menuAnchorProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuAnchorProps.emits, + setup(props, { attrs, slots }) { + const { scopeOkuMenu } = toRefs(props) + + const forwardedRef = useForwardRef() + + const popperScope = usePopperScope(scopeOkuMenu.value) + + return () => h(OkuPopperAnchor, + { + ...attrs, + ...popperScope, + ref: forwardedRef, + }, + { + default: () => slots.default?.(), + }, + ) + }, +}) + +export const OkuMenuAnchor = menuAnchor as typeof menuAnchor & +(new () => { $props: MenuAnchorNaviteElement }) diff --git a/packages/components/menu/src/menu-arrow.ts b/packages/components/menu/src/menu-arrow.ts new file mode 100644 index 000000000..9fffa9f4b --- /dev/null +++ b/packages/components/menu/src/menu-arrow.ts @@ -0,0 +1,35 @@ +import { defineComponent, h, toRefs } from 'vue' +import { OkuPopperArrow } from '@oku-ui/popper' +import { primitiveProps } from '@oku-ui/primitive' +import { useForwardRef } from '@oku-ui/use-composable' +import type { MenuArrowNaviteElement } from './props' +import { MENU_ARROW_NAME, menuArrowProps, scopedMenuProps, usePopperScope } from './props' + +const menuArrow = defineComponent({ + name: MENU_ARROW_NAME, + inheritAttrs: false, + props: { + ...menuArrowProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuArrowProps.emits, + setup(props, { attrs, slots }) { + const { scopeOkuMenu } = toRefs(props) + + const forwardedRef = useForwardRef() + + const popperScope = usePopperScope(scopeOkuMenu.value) + + return () => h(OkuPopperArrow, + { + ...popperScope, + ...attrs, + ref: forwardedRef, + }, slots, + ) + }, +}) + +export const OkuMenuArrow = menuArrow as typeof menuArrow & +(new () => { $props: MenuArrowNaviteElement }) diff --git a/packages/components/menu/src/menu-checkbox-item.ts b/packages/components/menu/src/menu-checkbox-item.ts new file mode 100644 index 000000000..35cad1976 --- /dev/null +++ b/packages/components/menu/src/menu-checkbox-item.ts @@ -0,0 +1,55 @@ +import { defineComponent, h, toRefs } from 'vue' +import { primitiveProps } from '@oku-ui/primitive' +import { useForwardRef } from '@oku-ui/use-composable' +import { composeEventHandlers } from '@oku-ui/utils' +import { itemIndicatorProvider } from './menu-item-indicator' +import { getCheckedState, isIndeterminate } from './utils' +import type { MenuCheckboxItemEmits, MenuCheckboxItemNaviteElement } from './props' +import { MENU_CHECKBOX_ITEM_NAME, menuCheckboxItemProps, scopedMenuProps } from './props' +import { OkuMenuItem } from './menu-item' + +const menuCheckboxItem = defineComponent({ + name: MENU_CHECKBOX_ITEM_NAME, + components: { + OkuMenuItem, + }, + inheritAttrs: false, + props: { + ...menuCheckboxItemProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuCheckboxItemProps.emits, + setup(props, { attrs, emit, slots }) { + const { + scopeOkuMenu, + checked, + } = toRefs(props) + + const forwardedRef = useForwardRef() + + itemIndicatorProvider( + { + scope: scopeOkuMenu.value, + checked, + }, + ) + + return () => h(OkuMenuItem, + { + 'role': 'menuitemcheckbox', + 'aria-checked': isIndeterminate(checked) ? 'mixed' : checked, + ...attrs, + 'ref': forwardedRef, + 'data-state': getCheckedState(checked), + 'onSelect': composeEventHandlers(() => { + emit('checkedChange', isIndeterminate(checked) ? true : !checked.value) + }, (event) => { + }, { checkForDefaultPrevented: false }), + }, slots, + ) + }, +}) + +export const OkuMenuCheckboxItem = menuCheckboxItem as typeof menuCheckboxItem & +(new () => { $props: MenuCheckboxItemNaviteElement }) diff --git a/packages/components/menu/src/menu-content-impl.ts b/packages/components/menu/src/menu-content-impl.ts new file mode 100644 index 000000000..c255c2173 --- /dev/null +++ b/packages/components/menu/src/menu-content-impl.ts @@ -0,0 +1,240 @@ +// import { Fragment, defineComponent, h, onBeforeUnmount, ref, toRefs } from 'vue' +// import { primitiveProps } from '@oku-ui/primitive' +// import { useComposedRefs, useForwardRef } from '@oku-ui/use-composable' +// import { useFocusGuards } from '@oku-ui/focus-guards' +// import { composeEventHandlers } from '@oku-ui/utils' +// import { OkuFocusScope } from '@oku-ui/focus-scope' +// import { OkuDismissableLayer } from '@oku-ui/dismissable-layer' +// import { OkuRovingFocusGroup } from '@oku-ui/roving-focus' +// import { OkuPopperContent } from '@oku-ui/popper' +// import type { MenuContentImplEmits, MenuContentImplNaviteElement } from './props' +// import { FIRST_LAST_KEYS, LAST_KEYS, MENU_CONTENT_IMPL_NAME, MENU_CONTENT_NAME, menuContentImplProps, menuContentProvider, scopedMenuProps, useCollection, useMenuInject, useMenuRootInject, usePopperScope, useRovingFocusGroupScope } from './props' +// import type { GraceIntent, Side } from './utils' +// import { focusFirst, getNextMatch, getOpenState, isPointerInGraceArea, whenMouse } from './utils' + +// const menuContentImpl = defineComponent({ +// name: MENU_CONTENT_IMPL_NAME, +// components: { +// OkuFocusScope, +// OkuDismissableLayer, +// OkuRovingFocusGroup, +// OkuPopperContent, +// }, +// inheritAttrs: false, +// props: { +// ...menuContentImplProps.props, +// ...primitiveProps, +// ...scopedMenuProps, +// }, +// emits: menuContentImplProps.emits, + +// setup(props, { attrs, emit, slots }) { +// const { +// scopeOkuMenu, +// loop, +// trapFocus, +// } = toRefs(props) + +// const forwardedRef = useForwardRef() + +// const inject = useMenuInject(MENU_CONTENT_NAME, scopeOkuMenu.value) +// const rootInject = useMenuRootInject(MENU_CONTENT_NAME, scopeOkuMenu.value) +// const popperScope = usePopperScope(scopeOkuMenu.value) +// const rovingFocusGroupScope = useRovingFocusGroupScope(scopeOkuMenu.value) +// const getItems = useCollection(scopeOkuMenu.value) +// const currentItemId = ref(null) +// const contentRef = ref(null) +// const composedRefs = useComposedRefs(forwardedRef, contentRef, inject.onContentChange) +// const timerRef = ref(0) +// const searchRef = ref('') +// const pointerGraceTimerRef = ref(0) +// const pointerGraceIntentRef = ref(null) +// const pointerDirRef = ref('right') +// const lastPointerXRef = ref(0) + +// const ScrollLockWrapper = disableOutsideScroll ? RemoveScroll : Fragment +// const scrollLockWrapperProps = disableOutsideScroll +// ? { as: Slot, allowPinchZoom: true } +// : undefined + +// const handleTypeaheadSearch = (key: string) => { +// const search = searchRef.value + key +// const items = getItems().filter(item => !item.disabled) +// const currentItem = document.activeElement +// const currentMatch = items.find(item => item.ref.value === currentItem)?.textValue +// const values = items.map(item => item.textValue) +// const nextMatch = getNextMatch(values, search, currentMatch) +// const newItem = items.find(item => item.textValue === nextMatch)?.ref.value + +// // Reset `searchRef` 1 second after it was last updated +// (function updateSearch(value: string) { +// searchRef.value = value +// window.clearTimeout(timerRef.value) +// if (value !== '') +// timerRef.value = window.setTimeout(() => updateSearch(''), 1000) +// })(search) + +// if (newItem) { +// /** +// * Imperative focus during keydown is risky so we prevent React's batching updates +// * to avoid potential bugs. See: https://github.com/facebook/react/issues/20332 +// */ +// setTimeout(() => (newItem as HTMLElement).focus()) +// } +// } + +// onBeforeUnmount(() => { +// window.clearTimeout(timerRef.value) +// }) + +// // Make sure the whole tree has focus guards as our `MenuContent` may be +// // the last element in the DOM (beacuse of the `Portal`) +// useFocusGuards() + +// const isPointerMovingToSubmenu = (event: PointerEvent) => { +// const isMovingTowards = pointerDirRef.value === pointerGraceIntentRef.value?.side +// return isMovingTowards && isPointerInGraceArea(event, pointerGraceIntentRef.value?.area) +// } + +// menuContentProvider({ +// scope: scopeOkuMenu.value, +// searchRef, +// onItemEnter: (event) => { +// if (isPointerMovingToSubmenu(event)) +// event.preventDefault() +// }, +// onItemLeave: (event) => { +// if (isPointerMovingToSubmenu(event)) +// return +// contentRef.value?.focus() +// currentItemId.value = null +// }, +// onTriggerLeave: (event) => { +// if (isPointerMovingToSubmenu(event)) +// event.preventDefault() +// }, +// pointerGraceTimerRef, +// onPointerGraceIntentChange: (intent) => { +// pointerGraceIntentRef.value = intent +// }, +// }) + +// return () => h(ScrollLockWrapper, +// { +// ...scrollLockWrapperProps, +// }, +// { +// default: () => h(OkuFocusScope, +// { +// asChild: true, +// trapped: trapFocus.value, +// onMountAutoFocus: composeEventHandlers((event) => { +// // when opening, explicitly focus the content area only and leave +// // `onEntryFocus` in control of focusing first item +// event.preventDefault() +// contentRef.value?.focus() +// }), +// onUnmountAutoFocus: emit('closeAutoFocus'), +// }, +// { +// default: () => h(OkuDismissableLayer, +// { +// asChild: true, +// disableOutsidePointerEvents: emit('disableOutsidePointerEvents'), +// onEscapeKeyDown: emit('escapeKeyDown'), +// onPointerDownOutside: emit('pointerDownOutside'), +// onFocusOutside: emit('focusOutside'), +// onInteractOutside: emit('interactOutside'), +// onDismiss: emit('dismiss'), +// }, +// { +// default: () => h(OkuRovingFocusGroup, +// { +// asChild: true, +// ...rovingFocusGroupScope, +// dir: rootInject.dir.value, +// orientation: 'vertical', +// loop: loop.value, +// currentTabStopId: currentItemId.value, +// onCurrentTabStopIdChange: _currentItemId => currentItemId.value = _currentItemId, +// onEntryFocus: composeEventHandlers((event) => { +// // only focus first item when using keyboard +// if (!rootInject.isUsingKeyboardRef.value) +// event.preventDefault() +// }), +// }, +// { +// default: () => h(OkuPopperContent, +// { +// 'asChild': true, +// 'role': 'menu', +// 'aria-orientation': 'vertical', +// 'data-state': getOpenState(inject.open.value), +// 'data-oku-menu-content': '', +// 'dir': rootInject.dir, +// ...popperScope, +// ...attrs, +// 'ref': composedRefs, +// 'style': { outline: 'none', ...attrs.style as any }, +// 'onKeyDown': composeEventHandlers((event) => { +// // submenu key events bubble through portals. We only care about keys in this menu. +// const target = event.target as HTMLElement +// const isKeyDownInside +// = target.closest('[data-oku-menu-content]') === event.valueTarget +// const isModifierKey = event.ctrlKey || event.altKey || event.metaKey +// const isCharacterKey = event.key.length === 1 +// if (isKeyDownInside) { +// // menus should not be navigated using tab key so we prevent it +// if (event.key === 'Tab') +// event.preventDefault() +// if (!isModifierKey && isCharacterKey) +// handleTypeaheadSearch(event.key) +// } +// // focus first/last item based on key pressed +// const content = contentRef.value +// if (event.target !== content) +// return +// if (!FIRST_LAST_KEYS.includes(event.key)) +// return +// event.preventDefault() +// const items = getItems().filter(item => !item.disabled) +// const candidateNodes = items.map(item => item.ref.value!) +// if (LAST_KEYS.includes(event.key)) +// candidateNodes.reverse() +// focusFirst(candidateNodes) +// }), +// 'onBlur': composeEventHandlers((event) => { +// // clear search buffer when leaving the menu +// if (!event.valueTarget.contains(event.target)) { +// window.clearTimeout(timerRef.value) +// searchRef.value = '' +// } +// }), +// 'onPointerMove': composeEventHandlers(whenMouse((event) => { +// const target = event.target as HTMLElement +// const pointerXHasChanged = lastPointerXRef.value !== event.clientX + +// // We don't use `event.movementX` for this check because Safari will +// // always return `0` on a pointer event. +// if (event.valueTarget.contains(target) && pointerXHasChanged) { +// const newDir = event.clientX > lastPointerXRef.value ? 'right' : 'left' +// pointerDirRef.value = newDir +// lastPointerXRef.value = event.clientX +// } +// })), +// }, slots, +// ), +// }, +// ), +// }, +// ), +// }, +// ), +// }, +// ) +// }, +// }) + +// // TODO: https://github.com/vuejs/core/pull/7444 after delete +// export const OkuMenuContentImpl = menuContentImpl as typeof menuContentImpl & +// (new () => { $props: MenuContentImplNaviteElement }) diff --git a/packages/components/menu/src/menu-content.ts b/packages/components/menu/src/menu-content.ts new file mode 100644 index 000000000..a4c87fa00 --- /dev/null +++ b/packages/components/menu/src/menu-content.ts @@ -0,0 +1,74 @@ +import { computed, defineComponent, h, toRefs } from 'vue' +import { OkuPresence } from '@oku-ui/presence' +import { primitiveProps } from '@oku-ui/primitive' +import { useForwardRef } from '@oku-ui/use-composable' +import type { MenuContentNaviteElement } from './props' +import { CollectionProvider, CollectionSlot, MENU_CONTENT_NAME, menuContentProps, scopedMenuProps, useMenuInject, useMenuRootInject, usePortalInject } from './props' + +const menuContent = defineComponent({ + name: MENU_CONTENT_NAME, + components: { + OkuPresence, + // OkuMenuContentModal, + // OkuMenuContentNonModal, + }, + inheritAttrs: false, + props: { + ...menuContentProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuContentProps.emits, + setup(props, { attrs, slots }) { + const { + scopeOkuMenu, + forceMount, + } = toRefs(props) + + const portalInject = usePortalInject(MENU_CONTENT_NAME, props.scopeOkuMenu) + forceMount.value = portalInject.forceMount.value + + const forwardedRef = useForwardRef() + + const inject = useMenuInject(MENU_CONTENT_NAME, scopeOkuMenu.value) + const rootInject = useMenuRootInject(MENU_CONTENT_NAME, scopeOkuMenu.value) + + return () => h(CollectionProvider, + { + scope: scopeOkuMenu.value, + }, + { + default: () => h(OkuPresence, + { present: computed(() => forceMount.value || inject.open.value).value }, + { + default: () => h(CollectionSlot, + { + scope: scopeOkuMenu.value, + }, + { + default: () => rootInject.modal.value + ? h('OkuMenuContentModal', + { + ...attrs, + ref: forwardedRef, + }, slots, + ) + : h('OkuMenuContentNonModal', + { + ...attrs, + ref: forwardedRef, + }, slots, + ), + }, + ), + }, + ), + }, + ) + }, + +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuMenuContent = menuContent as typeof menuContent & +(new () => { $props: MenuContentNaviteElement }) diff --git a/packages/components/menu/src/menu-group.ts b/packages/components/menu/src/menu-group.ts new file mode 100644 index 000000000..0c8fc026f --- /dev/null +++ b/packages/components/menu/src/menu-group.ts @@ -0,0 +1,34 @@ +import { defineComponent, h } from 'vue' +import { Primitive, primitiveProps } from '@oku-ui/primitive' +import { useForwardRef } from '@oku-ui/use-composable' +import type { MenuGroupNaviteElement } from './props' +import { MENU_GROUP_NAME, menuGroupProps, scopedMenuProps } from './props' + +const menuGroup = defineComponent({ + name: MENU_GROUP_NAME, + inheritAttrs: false, + props: { + ...menuGroupProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuGroupProps.emits, + + setup(props, { attrs, slots }) { + // const { scopeOkuMenu } = toRefs(props) + + const forwardedRef = useForwardRef() + + return () => h(Primitive.div, + { + ...attrs, + role: 'group', + ref: forwardedRef, + }, slots, + ) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuMenuGroup = menuGroup as typeof menuGroup & +(new () => { $props: MenuGroupNaviteElement }) diff --git a/packages/components/menu/src/menu-item-impl.ts b/packages/components/menu/src/menu-item-impl.ts new file mode 100644 index 000000000..c0f175ead --- /dev/null +++ b/packages/components/menu/src/menu-item-impl.ts @@ -0,0 +1,106 @@ +import { defineComponent, h, ref, toRefs, watchEffect } from 'vue' +import { Primitive, primitiveProps } from '@oku-ui/primitive' +import { OkuRovingFocusGroupItem } from '@oku-ui/roving-focus' +import { composeEventHandlers } from '@oku-ui/utils' +import { useComposedRefs, useForwardRef } from '@oku-ui/use-composable' +import type { MenuItemImplEmits, MenuItemImplNaviteElement } from './props' +import { CollectionItemSlot, MENU_ITEM_IMPL_NAME, MENU_ITEM_NAME, menuItemImplProps, scopedMenuProps, useMenuContentInject, useRovingFocusGroupScope } from './props' +import { whenMouse } from './utils' + +const menuItemImpl = defineComponent({ + name: MENU_ITEM_IMPL_NAME, + components: { + OkuRovingFocusGroupItem, + }, + inheritAttrs: false, + props: { + ...menuItemImplProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuItemImplProps.emits, + setup(props, { attrs, slots }) { + const { + scopeOkuMenu, + disabled, + textValue, + } = toRefs(props) + + const forwardedRef = useForwardRef() + + const contentInject = useMenuContentInject(MENU_ITEM_NAME, scopeOkuMenu.value) + const rovingFocusGroupScope = useRovingFocusGroupScope(scopeOkuMenu.value) + const menuItemRef = ref(null) + const composedRefs = useComposedRefs(forwardedRef, el => menuItemRef.value = (el as HTMLDivElement)) + const isFocused = ref(false) + + // get the item's `.textContent` as default strategy for typeahead `textValue` + const textContent = ref('') + + watchEffect(() => { + const menuItem = menuItemRef.value + if (menuItem) + textContent.value = (menuItem.textContent ?? '').trim() + }) + + return () => h(CollectionItemSlot, + { + scope: scopeOkuMenu.value, + disabled: disabled.value, + textValue: textValue.value ?? textContent, + }, + { + default: () => h(OkuRovingFocusGroupItem, + { + asChild: true, + ...rovingFocusGroupScope, + focusable: !disabled.value, + }, + { + default: () => h(Primitive.div, + { + 'role': 'menuitem', + 'data-highlighted': isFocused.value ? '' : undefined, + 'aria-disabled': disabled.value || undefined, + 'data-disabled': disabled.value ? '' : undefined, + ...attrs, + 'ref': composedRefs, + /** + * We focus items on `pointerMove` to achieve the following: + * + * - Mouse over an item (it focuses) + * - Leave mouse where it is and use keyboard to focus a different item + * - Wiggle mouse without it leaving previously focused item + * - Previously focused item should re-focus + * + * If we used `mouseOver`/`mouseEnter` it would not re-focus when the mouse + * wiggles. This is to match native menu implementation. + */ + 'onPointermove': composeEventHandlers(whenMouse((event) => { + if (disabled.value) { + contentInject.onItemLeave(event) + } + else { + contentInject.onItemEnter(event) + if (!event.defaultPrevented) { + const item = event.currentTarget + item.focus() + } + } + }), + ), + 'onPointerleave': composeEventHandlers(whenMouse(event => contentInject.onItemLeave(event))), + 'onFocus': composeEventHandlers(() => isFocused.value = true), + 'onBlur': composeEventHandlers(() => isFocused.value = false), + }, slots, + ), + }, + ), + }, + ) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuMenuItemImpl = menuItemImpl as typeof menuItemImpl & +(new () => { $props: MenuItemImplNaviteElement }) diff --git a/packages/components/menu/src/menu-item-indicator.ts b/packages/components/menu/src/menu-item-indicator.ts new file mode 100644 index 000000000..99210bb1b --- /dev/null +++ b/packages/components/menu/src/menu-item-indicator.ts @@ -0,0 +1,49 @@ +import { OkuPresence } from '@oku-ui/presence' +import { computed, defineComponent, h, toRefs } from 'vue' +import { Primitive, primitiveProps } from '@oku-ui/primitive' +import { useForwardRef } from '@oku-ui/use-composable' +import { getCheckedState, isIndeterminate } from './utils' +import type { MenuItemIndicatorNaviteElement } from './props' +import { MENU_ITEM_INDICATOR_NAME, menuItemIndicatorProps, scopedMenuProps, useItemIndicatorInject } from './props' + +const menuItemIndicator = defineComponent({ + name: MENU_ITEM_INDICATOR_NAME, + components: { + OkuPresence, + }, + inheritAttrs: false, + props: { + ...menuItemIndicatorProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuItemIndicatorProps.emits, + setup(props, { attrs, slots }) { + const { + scopeOkuMenu, + forceMount, + } = toRefs(props) + + const forwardedRef = useForwardRef() + + const indicatorInject = useItemIndicatorInject(MENU_ITEM_INDICATOR_NAME, scopeOkuMenu.value) + + return () => h(OkuPresence, + { + present: computed(() => forceMount.value || isIndeterminate(indicatorInject.checked) || indicatorInject.checked === true).value, + }, + { + default: () => h(Primitive.span, + { + ...attrs, + 'ref': forwardedRef, + 'data-state': getCheckedState(indicatorInject.checked), + }, slots, + ), + }, + ) + }, +}) + +export const OkuMenuItemIndicator = menuItemIndicator as typeof menuItemIndicator & +(new () => { $props: MenuItemIndicatorNaviteElement }) diff --git a/packages/components/menu/src/menu-item.ts b/packages/components/menu/src/menu-item.ts new file mode 100644 index 000000000..9b0fba0bf --- /dev/null +++ b/packages/components/menu/src/menu-item.ts @@ -0,0 +1,88 @@ +import { defineComponent, h, ref, toRefs } from 'vue' +import { composeEventHandlers } from '@oku-ui/utils' +import { useComposedRefs, useForwardRef } from '@oku-ui/use-composable' +import { dispatchDiscreteCustomEvent, primitiveProps } from '@oku-ui/primitive' +import { ITEM_SELECT, MENU_ITEM_NAME, SELECTION_KEYS, menuItemProps, scopedMenuProps, useMenuContentInject, useMenuRootInject } from './props' +import type { MenuItemEmits, MenuItemNaviteElement } from './props' +import { OkuMenuItemImpl } from './menu-item-impl' + +const menuItem = defineComponent({ + name: MENU_ITEM_NAME, + components: { + OkuMenuItemImpl, + }, + inheritAttrs: false, + props: { + ...menuItemProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuItemProps.emits, + + setup(props, { attrs, emit, slots }) { + const { + scopeOkuMenu, + disabled, + } = toRefs(props) + + const forwardedRef = useForwardRef() + + const menuItemRef = ref(null) + const rootInject = useMenuRootInject(MENU_ITEM_NAME, scopeOkuMenu.value) + const contentInject = useMenuContentInject(MENU_ITEM_NAME, scopeOkuMenu.value) + const composedRefs = useComposedRefs(forwardedRef, el => menuItemRef.value = (el as HTMLDivElement)) + const isPointerDownRef = ref(false) + + function handleSelect() { + const menuItem = menuItemRef.value + if (!disabled.value && menuItem) { + const itemSelectEvent = new CustomEvent(ITEM_SELECT, { bubbles: true, cancelable: true }) + menuItem.addEventListener(ITEM_SELECT, event => emit('select', event), { once: true }) + dispatchDiscreteCustomEvent(menuItem, itemSelectEvent) + if (itemSelectEvent.defaultPrevented) + isPointerDownRef.value = false + else + rootInject.onClose() + } + } + + return () => h(OkuMenuItemImpl, + { + ...attrs, + ref: composedRefs, + disabled: disabled.value, + onClick: composeEventHandlers(props.onClick, handleSelect), + onPointerDown: (event) => { + props.onPointerDown?.(event) + isPointerDownRef.value = true + }, + onPointerUp: composeEventHandlers((event) => { + // Pointer down can move to a different menu item which should activate it on pointer up. + // We dispatch a click for selection to allow composition with click based triggers and to + // prevent Firefox from getting stuck in text selection mode when the menu closes. + if (!isPointerDownRef.value) + event.currentTarget?.click() + }), + onKeyDown: composeEventHandlers((event) => { + const isTypingAhead = contentInject.searchRef.value !== '' + if (disabled.value || (isTypingAhead && event.key === ' ')) + return + if (SELECTION_KEYS.includes(event.key)) { + event.currentTarget.click() + /** + * We prevent default browser behaviour for selection keys as they should trigger + * a selection only: + * - prevents space from scrolling the page. + * - if keydown causes focus to move, prevents keydown from firing on the new target. + */ + event.preventDefault() + } + }), + }, slots, + ) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuMenuItem = menuItem as typeof menuItem & +(new () => { $props: MenuItemNaviteElement }) diff --git a/packages/components/menu/src/menu-label.ts b/packages/components/menu/src/menu-label.ts new file mode 100644 index 000000000..14b205d11 --- /dev/null +++ b/packages/components/menu/src/menu-label.ts @@ -0,0 +1,33 @@ +import { defineComponent, h } from 'vue' +import { Primitive, primitiveProps } from '@oku-ui/primitive' +import { useForwardRef } from '@oku-ui/use-composable' +import type { MenuLabelNaviteElement } from './props' +import { MENU_LABEL_NAME, menuLabelProps, scopedMenuProps } from './props' + +const menuLabel = defineComponent({ + name: MENU_LABEL_NAME, + inheritAttrs: false, + props: { + ...menuLabelProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuLabelProps.emits, + + setup(props, { attrs, slots }) { + // const { scopeOkuMenu } = toRefs(props) + + const forwardedRef = useForwardRef() + + return () => h(Primitive.div, + { + ...attrs, + ref: forwardedRef, + }, slots, + ) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuMenuLabel = menuLabel as typeof menuLabel & +(new () => { $props: MenuLabelNaviteElement }) diff --git a/packages/components/menu/src/menu-portal.ts b/packages/components/menu/src/menu-portal.ts new file mode 100644 index 000000000..5b6d3b930 --- /dev/null +++ b/packages/components/menu/src/menu-portal.ts @@ -0,0 +1,52 @@ +import { computed, defineComponent, h, toRefs } from 'vue' +import { primitiveProps } from '@oku-ui/primitive' +import { OkuPresence } from '@oku-ui/presence' +import { OkuPortal } from '@oku-ui/portal' +import type { MenuPortalNaviteElement } from './props' +import { MENU_PORTAL_NAME, menuPortalProps, portalProvider, scopedMenuProps, useMenuInject } from './props' + +const menuPortal = defineComponent({ + name: MENU_PORTAL_NAME, + components: { + OkuPresence, + OkuPortal, + }, + inheritAttrs: false, + props: { + ...menuPortalProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuPortalProps.emits, + setup(props, { attrs, slots }) { + const { + scopeOkuMenu, + forceMount, + container, + } = toRefs(props) + + const inject = useMenuInject(MENU_PORTAL_NAME, scopeOkuMenu.value) + + portalProvider({ + scope: props.scopeOkuMenu, + forceMount, + }) + + return () => h(OkuPresence, + { present: computed(() => forceMount.value || inject.open.value).value }, + { + default: () => h(OkuPortal, + { + ...attrs, + asChild: true, + container: container.value, + }, slots, + ), + }, + ) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuMenuPortal = menuPortal as typeof menuPortal & +(new () => { $props: MenuPortalNaviteElement }) diff --git a/packages/components/menu/src/menu-radio-group.ts b/packages/components/menu/src/menu-radio-group.ts new file mode 100644 index 000000000..df605539f --- /dev/null +++ b/packages/components/menu/src/menu-radio-group.ts @@ -0,0 +1,48 @@ +import { defineComponent, h, toRefs } from 'vue' +import { useForwardRef } from '@oku-ui/use-composable' +import { primitiveProps } from '@oku-ui/primitive' +import type { MenuRadioGroupNaviteElement } from './props' +import { MENU_RADIO_GROUP_NAME, menuRadioGroupProps, radioGroupProvider, scopedMenuProps } from './props' +import { OkuMenuGroup } from './menu-group' + +const menuRadioGroup = defineComponent({ + name: MENU_RADIO_GROUP_NAME, + components: { + OkuMenuGroup, + }, + inheritAttrs: false, + props: { + ...menuRadioGroupProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuRadioGroupProps.emits, + setup(props, { attrs, emit, slots }) { + const { + scopeOkuMenu, + value, + } = toRefs(props) + + const forwardedRef = useForwardRef() + + const handleValueChange = value => emit('valueChange', value) + + radioGroupProvider( + { + scope: scopeOkuMenu.value, + value, + onValueChange: () => handleValueChange, + }, + ) + + return () => h(OkuMenuGroup, + { + ...attrs, + ref: forwardedRef, + }, slots, + ) + }, +}) + +export const OkuMenuRadioGroup = menuRadioGroup as typeof menuRadioGroup & +(new () => { $props: MenuRadioGroupNaviteElement }) diff --git a/packages/components/menu/src/menu-radio-item.ts b/packages/components/menu/src/menu-radio-item.ts new file mode 100644 index 000000000..52d230ac1 --- /dev/null +++ b/packages/components/menu/src/menu-radio-item.ts @@ -0,0 +1,57 @@ +import { defineComponent, h, toRefs } from 'vue' +import { primitiveProps } from '@oku-ui/primitive' +import { composeEventHandlers } from '@oku-ui/utils' +import { useForwardRef } from '@oku-ui/use-composable' +import { itemIndicatorProvider } from './menu-item-indicator' +import { getCheckedState } from './utils' +import { OkuMenuItem } from './menu-item' +import type { MenuRadioItemEmits, MenuRadioItemNaviteElement } from './props' +import { MENU_RADIO_ITEM_NAME, menuRadioItemProps, scopedMenuProps, useRadioGroupInject } from './props' + +const menuRadioItem = defineComponent({ + name: MENU_RADIO_ITEM_NAME, + components: { + OkuMenuItem, + }, + inheritAttrs: false, + props: { + ...menuRadioItemProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuRadioItemProps.emits, + setup(props, { attrs, emit, slots }) { + const { + scopeOkuMenu, + value, + } = toRefs(props) + + const forwardedRef = useForwardRef() + + const inject = useRadioGroupInject(MENU_RADIO_ITEM_NAME, scopeOkuMenu.value) + const checked = value.value === inject.value + + itemIndicatorProvider({ + scope: scopeOkuMenu.value, + checked, + }) + + return h(OkuMenuItem, + { + 'role': 'menuitemradio', + 'aria-checked': checked, + ...attrs, + 'ref': forwardedRef, + 'data-state': getCheckedState(checked), + 'onSelect': composeEventHandlers((event) => { + emit('select', event) + }, () => { + inject.onValueChange?.(value.value) + }, { checkForDefaultPrevented: false }), + }, slots, + ) + }, +}) + +export const OkuMenuRadioItemProps = menuRadioItem as typeof menuRadioItem & +(new () => { $props: MenuRadioItemNaviteElement }) diff --git a/packages/components/menu/src/menu-root-content-non-modal.ts b/packages/components/menu/src/menu-root-content-non-modal.ts new file mode 100644 index 000000000..ea0c21653 --- /dev/null +++ b/packages/components/menu/src/menu-root-content-non-modal.ts @@ -0,0 +1,42 @@ +import { defineComponent, h, toRefs } from 'vue' +import { useForwardRef } from '@oku-ui/use-composable' +import { primitiveProps } from '@oku-ui/primitive' +import { MENU_CONTENT_NAME, MENU_NON_MODEL_NAME, menuRootContentNonModalProps, scopedMenuProps, useMenuInject } from './props' +import type { MenuPortalNaviteElement } from './props' + +const menuRootContentNonModal = defineComponent({ + name: MENU_NON_MODEL_NAME, + components: { + // OkuMenuContentImpl, + }, + inheritAttrs: false, + props: { + ...menuRootContentNonModalProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuRootContentNonModalProps.emits, + + setup(props, { attrs, slots }) { + const { scopeOkuMenu } = toRefs(props) + + const forwardedRef = useForwardRef() + + const inject = useMenuInject(MENU_CONTENT_NAME, scopeOkuMenu.value) + + return () => h('OkuMenuContentImpl', + { + ...attrs, + ref: forwardedRef, + trapFocus: false, + disableOutsidePointerEvents: false, + disableOutsideScroll: false, + onDismiss: () => inject.onOpenChange(false), + }, slots, + ) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuMenuRootContentNonModal = menuRootContentNonModal as typeof menuRootContentNonModal & +(new () => { $props: MenuPortalNaviteElement }) diff --git a/packages/components/menu/src/menu-root-content-type.ts b/packages/components/menu/src/menu-root-content-type.ts new file mode 100644 index 000000000..0c03bf19a --- /dev/null +++ b/packages/components/menu/src/menu-root-content-type.ts @@ -0,0 +1,66 @@ +import { defineComponent, h, ref, toRefs, watchEffect } from 'vue' +import { primitiveProps } from '@oku-ui/primitive' +import { useComposedRefs, useForwardRef } from '@oku-ui/use-composable' +import { composeEventHandlers } from '@oku-ui/utils' +import { hideOthers } from 'aria-hidden' +import type { MenuRootContentTypeElement, MenuRootContentTypeEmits, MenuRootContentTypeNaviteElement } from './props' +import { MENU_CONTENT_NAME, MENU_ROOT_CONTENT_TYPE_NAME, menuRootContentTypeProps, scopedMenuProps, useMenuInject } from './props' + +const menuRootContentType = defineComponent({ + name: MENU_ROOT_CONTENT_TYPE_NAME, + components: { + // OkuMenuContentImpl, + }, + inheritAttrs: false, + props: { + ...menuRootContentTypeProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuRootContentTypeProps.emits, + + setup(props, { attrs, emit, slots }) { + const { + scopeOkuMenu, + } = toRefs(props) + + const forwardedRef = useForwardRef() + + const inject = useMenuInject(MENU_CONTENT_NAME, scopeOkuMenu.value) + const menuRootContentRef = ref(null) + const composedRefs = useComposedRefs(forwardedRef, el => menuRootContentRef.value = (el as MenuRootContentTypeElement)) + + // Hide everything from ARIA except the `MenuContent` + watchEffect(() => { + const content = menuRootContentRef.value + if (content) + return hideOthers(content) + }) + + return () => h('OkuMenuContentImpl', + { + ...attrs, + ref: composedRefs, + // we make sure we're not trapping once it's been closed + // (closed !== unmounted when animating out) + trapFocus: inject.open.value, + // make sure to only disable pointer events when open + // this avoids blocking interactions while animating out + disableOutsidePointerEvents: inject.open.value, + // disableOutsideScroll, + // When focus is trapped, a `focusout` event may still happen. + // We make sure we don't trigger our `onDismiss` in such case. + onFocusOutside: composeEventHandlers((event) => { + emit('focusOutside', event) + }, (event) => { + event.preventDefault() + }, { checkForDefaultPrevented: false }), + onDismiss: () => inject.onOpenChange(false), + }, slots, + ) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuMenuRootContentTypel = menuRootContentType as typeof menuRootContentType & +(new () => { $props: MenuRootContentTypeNaviteElement }) diff --git a/packages/components/menu/src/menu-separator.ts b/packages/components/menu/src/menu-separator.ts new file mode 100644 index 000000000..07935a670 --- /dev/null +++ b/packages/components/menu/src/menu-separator.ts @@ -0,0 +1,33 @@ +import { defineComponent, h } from 'vue' +import { Primitive, primitiveProps } from '@oku-ui/primitive' +import { useForwardRef } from '@oku-ui/use-composable' +import type { MenuSeparatorNaviteElement } from './props' +import { MENU_SEPARATOR_NAME, menuSeparatorProps, scopedMenuProps } from './props' + +const menuSeparator = defineComponent({ + name: MENU_SEPARATOR_NAME, + inheritAttrs: false, + props: { + ...menuSeparatorProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuSeparatorProps.emits, + setup(props, { attrs, slots }) { + // const { scopeOkuMenu } = toRefs(props) + + const forwardedRef = useForwardRef() + + return () => h(Primitive.div, + { + 'role': 'separator', + 'aria-orientation': 'horizontal', + ...attrs, + 'ref': forwardedRef, + }, slots, + ) + }, +}) + +export const OkuMenuSeparator = menuSeparator as typeof menuSeparator & +(new () => { $props: MenuSeparatorNaviteElement }) diff --git a/packages/components/menu/src/menu-sub-content.ts b/packages/components/menu/src/menu-sub-content.ts new file mode 100644 index 000000000..a329d07cd --- /dev/null +++ b/packages/components/menu/src/menu-sub-content.ts @@ -0,0 +1,104 @@ +import { computed, defineComponent, h, ref, toRefs } from 'vue' +import { useComposedRefs, useForwardRef } from '@oku-ui/use-composable' +import { primitiveProps } from '@oku-ui/primitive' +import { composeEventHandlers } from '@oku-ui/utils' +import { OkuPresence } from '@oku-ui/presence' +import type { MenuSubContentElement, MenuSubContentEmits, MenuSubContentNaviteElement } from './props' +import { CollectionProvider, CollectionSlot, MENU_SUB_CONTENT_NAME, SUB_CLOSE_KEYS, menuSubContentProps, scopedMenuProps, useMenuInject, useMenuRootInject, useMenuSubInject, usePortalInject } from './props' + +const menuSubContent = defineComponent({ + name: MENU_SUB_CONTENT_NAME, + components: { + OkuPresence, + }, + inheritAttrs: false, + props: { + ...menuSubContentProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuSubContentProps.emits, + // eslint-disable-next-line unused-imports/no-unused-vars + setup(props, { attrs, emit, slots }) { + const { + scopeOkuMenu, + forceMount, + } = toRefs(props) + + const portalInject = usePortalInject(MENU_SUB_CONTENT_NAME, scopeOkuMenu.value) + forceMount.value = portalInject.forceMount.value + + const forwardedRef = useForwardRef() + + const inject = useMenuInject(MENU_SUB_CONTENT_NAME, scopeOkuMenu.value) + const rootInject = useMenuRootInject(MENU_SUB_CONTENT_NAME, scopeOkuMenu.value) + const subInject = useMenuSubInject(MENU_SUB_CONTENT_NAME, scopeOkuMenu.value) + const menuSubContentRef = ref(null) + const composedRefs = useComposedRefs(forwardedRef, ref) + + return h(CollectionProvider, + { scope: scopeOkuMenu.value }, + { + default: () => h(OkuPresence, + { present: computed(() => forceMount.value || inject.open.value).value }, + { + default: () => h(CollectionSlot, + { scope: scopeOkuMenu.value }, + { + default: () => h('OkuMenuContentImpl', + { + 'id': subInject.contentId.value, + 'aria-labelledby': subInject.triggerId.value, + ...attrs, + 'ref': composedRefs, + 'align': 'start', + 'side': rootInject.dir.value === 'rtl' ? 'left' : 'right', + 'disableOutsidePointerEvents': false, + 'disableOutsideScroll': false, + 'trapFocus': false, + 'onOpenAutoFocus': (event) => { + // when opening a submenu, focus content for keyboard users only + if (rootInject.isUsingKeyboardRef.value) + menuSubContentRef.value?.focus() + event.preventDefault() + }, + // The menu might close because of focusing another menu item in the parent menu. We + // don't want it to refocus the trigger in that case so we handle trigger focus ourselves. + 'onCloseAutoFocus': event => event.preventDefault(), + 'onFocusOutside': composeEventHandlers((event: FocusEvent) => { + // We prevent closing when the trigger is focused to avoid triggering a re-open animation + // on pointer interaction. + if (event.target !== subInject.trigger.value) + inject.onOpenChange(false) + }), + 'onEscapeKeydown': composeEventHandlers((event: KeyboardEvent) => { + rootInject.onClose() + // ensure pressing escape in submenu doesn't escape full screen mode + event.preventDefault() + }), + 'onKeydown': composeEventHandlers((event: KeyboardEvent) => { + // Submenu key events bubble through portals. We only care about keys in this menu. + const isKeyDownInside = event.valueTarget.contains(event.target as HTMLElement) + const isCloseKey = SUB_CLOSE_KEYS[rootInject.dir.value].includes(event.key) + if (isKeyDownInside && isCloseKey) { + inject.onOpenChange(false) + // We focus manually because we prevented it in `onCloseAutoFocus` + subInject.trigger.value?.focus() + // prevent window from scrolling + event.preventDefault() + } + }), + }, slots, + ), + }, + ), + }, + ), + }, + ) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuMenuSubContent = menuSubContent as typeof menuSubContent & +(new () => { $props: MenuSubContentNaviteElement }) diff --git a/packages/components/menu/src/menu-sub-trigger.ts b/packages/components/menu/src/menu-sub-trigger.ts new file mode 100644 index 000000000..4f73174e5 --- /dev/null +++ b/packages/components/menu/src/menu-sub-trigger.ts @@ -0,0 +1,157 @@ +import { defineComponent, h, onBeforeUnmount, ref, toRefs, watchEffect } from 'vue' +import { useComposedRefs, useForwardRef } from '@oku-ui/use-composable' +import { primitiveProps } from '@oku-ui/primitive' +import { composeEventHandlers } from '@oku-ui/utils' +import type { Side } from './utils' +import { getOpenState, whenMouse } from './utils' +import type { MenuItemImplElement, MenuSubTriggerEmits, MenuSubTriggerNaviteElement } from './props' +import { MENU_SUB_TRIGGER_NAME, SUB_OPEN_KEYS, scopedMenuProps, useMenuContentInject, useMenuInject, useMenuRootInject, useMenuSubInject } from './props' +import { OkuMenuAnchor } from './menu-anchor' +import { OkuMenuItemImpl } from './menu-item-impl' + +const menuSubTrigger = defineComponent({ + name: MENU_SUB_TRIGGER_NAME, + components: { + OkuMenuAnchor, + OkuMenuItemImpl, + }, + inheritAttrs: false, + props: { + ...primitiveProps, + ...scopedMenuProps, + }, + setup(props, { attrs, slots }) { + const { scopeOkuMenu } = toRefs(props) + + const forwardedRef = useForwardRef() + + const inject = useMenuInject(MENU_SUB_TRIGGER_NAME, scopeOkuMenu.value) + const rootInject = useMenuRootInject(MENU_SUB_TRIGGER_NAME, scopeOkuMenu.value) + const subInject = useMenuSubInject(MENU_SUB_TRIGGER_NAME, scopeOkuMenu.value) + const contentInject = useMenuContentInject(MENU_SUB_TRIGGER_NAME, scopeOkuMenu.value) + const openTimerRef = ref(null) + const { pointerGraceTimerRef, onPointerGraceIntentChange } = contentInject + const scope = { scope: scopeOkuMenu.value } + + function clearOpenTimer() { + if (openTimerRef.value) + window.clearTimeout(openTimerRef.value) + openTimerRef.value = null + } + + onBeforeUnmount(() => clearOpenTimer) + + watchEffect(() => { + const pointerGraceTimer = pointerGraceTimerRef.value + return () => { + window.clearTimeout(pointerGraceTimer) + onPointerGraceIntentChange(null) + } + }) + + return h(OkuMenuAnchor, + { + asChild: true, + ...scope, + + }, + { + default: () => h(OkuMenuItemImpl, + { + 'id': subInject.triggerId.value, + 'aria-haspopup': 'menu', + 'aria-expanded': inject.open.value, + 'aria-controls': subInject.contentId.value, + 'data-state': getOpenState(inject.open.value), + ...attrs, + 'ref': useComposedRefs(forwardedRef, el => subInject.onTriggerChange(el as MenuItemImplElement)), + // This is redundant for mouse users but we cannot determine pointer type from + // click event and we cannot use pointerup event (see git history for reasons why) + 'onClick': (event) => { + // props.onClick?.(event) + if (props.disabled || event.defaultPrevented) + return + /** + * We manually focus because iOS Safari doesn't always focus on click (e.g. buttons) + * and we rely heavily on `onFocusOutside` for submenus to close when switching + * between separate submenus. + */ + event.valueTarget.focus() + if (!inject.open.value) + inject.onOpenChange(true) + }, + 'onPointermove': composeEventHandlers(whenMouse((event: PointerEvent) => { + contentInject.onItemEnter(event) + if (event.defaultPrevented) + return + if (!props.disabled && !inject.open.value && !openTimerRef.value) { + contentInject.onPointerGraceIntentChange(null) + openTimerRef.value = window.setTimeout(() => { + inject.onOpenChange(true) + clearOpenTimer() + }, 100) + } + })), + 'onPointerleave': composeEventHandlers(whenMouse((event: PointerEvent) => { + clearOpenTimer() + + const contentRect = inject.content.value?.getBoundingClientRect() + if (contentRect) { + // TODO: make sure to update this when we change positioning logic + const side = inject.content.value?.dataset.side as Side + const rightSide = side === 'right' + const bleed = rightSide ? -5 : +5 + const contentNearEdge = contentRect[rightSide ? 'left' : 'right'] + const contentFarEdge = contentRect[rightSide ? 'right' : 'left'] + + contentInject.onPointerGraceIntentChange({ + area: [ + // Apply a bleed on clientX to ensure that our exit point is + // consistently within polygon bounds + { x: event.clientX + bleed, y: event.clientY }, + { x: contentNearEdge, y: contentRect.top }, + { x: contentFarEdge, y: contentRect.top }, + { x: contentFarEdge, y: contentRect.bottom }, + { x: contentNearEdge, y: contentRect.bottom }, + ], + side, + }) + + window.clearTimeout(pointerGraceTimerRef.value) + pointerGraceTimerRef.value = window.setTimeout( + () => contentInject.onPointerGraceIntentChange(null), + 300, + ) + } + else { + contentInject.onTriggerLeave(event) + if (event.defaultPrevented) + return + + // There's 100ms where the user may leave an item before the submenu was opened. + contentInject.onPointerGraceIntentChange(null) + } + })), + 'onKeydown': composeEventHandlers((event) => { + const isTypingAhead = contentInject.searchRef.value !== '' + if (props.disabled || (isTypingAhead && event.key === ' ')) + return + if (SUB_OPEN_KEYS[rootInject.dir.value].includes(event.key)) { + inject.onOpenChange(true) + // The trigger may hold focus if opened via pointer interaction + // so we ensure content is given focus again when switching to keyboard. + inject.content.value?.focus() + // prevent window from scrolling + event.preventDefault() + } + }), + }, slots, + ), + }, + ) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuMenuSubTrigger = menuSubTrigger as typeof menuSubTrigger & +(new () => { $props: MenuSubTriggerNaviteElement }) diff --git a/packages/components/menu/src/menu-sub.ts b/packages/components/menu/src/menu-sub.ts new file mode 100644 index 000000000..6bdc00ebf --- /dev/null +++ b/packages/components/menu/src/menu-sub.ts @@ -0,0 +1,62 @@ +import { computed, defineComponent, h, ref, toRefs, watchEffect } from 'vue' +import { primitiveProps } from '@oku-ui/primitive' +import { useId } from '@oku-ui/use-composable' +import { OkuPopper } from '@oku-ui/popper' +import type { MenuSubTriggerElement } from './menu-sub-trigger' +import type { MenuContentElement } from './props' +import { MENU_SUB_NAME, menuProvider, menuSubProps, menuSubProvider, scopedMenuProps, useMenuInject, usePopperScope } from './props' + +const menuSub = defineComponent({ + name: MENU_SUB_NAME, + components: { + OkuPopper, + }, + inheritAttrs: false, + props: { + ...menuSubProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuSubProps.emits, + // eslint-disable-next-line unused-imports/no-unused-vars + setup(props, { attrs, emit, slots }) { + const { + scopeOkuMenu, + open, + } = toRefs(props) + + const parentMenuInject = useMenuInject(MENU_SUB_NAME, scopeOkuMenu.value) + const popperScope = usePopperScope(scopeOkuMenu.value) + const trigger = ref(null) + const content = ref(null) + const handleOpenChange = open => emit('openChange', open) + + // Prevent the parent menu from reopening with open submenus. + watchEffect((onInvalidate) => { + if (parentMenuInject.open.value === false) + handleOpenChange(false) + + onInvalidate(() => handleOpenChange(false)) + }) + + menuProvider({ + scope: scopeOkuMenu.value, + open, + onOpenChange: handleOpenChange, + content, + onContentChange: _content => content.value = _content, + }) + + menuSubProvider({ + scope: scopeOkuMenu.value, + contentId: computed(() => useId()), + triggerId: computed(() => useId()), + trigger, + onTriggerChange: _trigger => trigger.value = _trigger, + }) + + return () => h(OkuPopper, { ...popperScope }, slots) + }, +}) + +export const OkuMenuSub = menuSub diff --git a/packages/components/menu/src/menu.test..ts b/packages/components/menu/src/menu.test..ts new file mode 100644 index 000000000..c0ae3b93e --- /dev/null +++ b/packages/components/menu/src/menu.test..ts @@ -0,0 +1,7 @@ +import { describe, expect, it } from 'vitest' + +describe('OkuMenu', () => { + it('should be ok', () => { + expect(1).toBe(1) + }) +}) diff --git a/packages/components/menu/src/menu.ts b/packages/components/menu/src/menu.ts new file mode 100644 index 000000000..91cd8fa53 --- /dev/null +++ b/packages/components/menu/src/menu.ts @@ -0,0 +1,80 @@ +import { defineComponent, h, ref, toRefs, watchEffect } from 'vue' +import { OkuPopper } from '@oku-ui/popper' +import { useDirection } from '@oku-ui/direction' +import { primitiveProps } from '@oku-ui/primitive' +import { MENU_NAME, menuProps, menuProvider, menuRootProvider, scopedMenuProps, usePopperScope } from './props' +import type { MenuContentElement } from './props' + +const menu = defineComponent({ + name: MENU_NAME, + components: { + OkuPopper, + }, + inheritAttrs: false, + props: { + ...menuProps.props, + ...primitiveProps, + ...scopedMenuProps, + }, + emits: menuProps.emits, + setup(props, { attrs, emit, slots }) { + const { + scopeOkuMenu, + open, + dir, + modal, + } = toRefs(props) + + const popperScope = usePopperScope(scopeOkuMenu.value) + const content = ref(null) + const isUsingKeyboardRef = ref(false) + const handleOpenChange = (open: boolean) => emit('openChange', open) + const direction = useDirection(dir.value) + + watchEffect((onInvalidate) => { + // Capture phase ensures we set the boolean before any side effects execute + // in response to the key or pointer event as they might depend on this value. + const handlePointer = () => (isUsingKeyboardRef.value = false) + const handleKeyDown = () => { + isUsingKeyboardRef.value = true + document.addEventListener('pointerdown', handlePointer, { capture: true, once: true }) + document.addEventListener('pointermove', handlePointer, { capture: true, once: true }) + } + document.addEventListener('keydown', handleKeyDown, { capture: true }) + + onInvalidate(() => { + document.removeEventListener('keydown', handleKeyDown, { capture: true }) + document.removeEventListener('pointerdown', handlePointer, { capture: true }) + document.removeEventListener('pointermove', handlePointer, { capture: true }) + }) + }) + + menuProvider({ + scope: scopeOkuMenu.value, + open, + onOpenChange: handleOpenChange, + content, + onContentChange: (_content: MenuContentElement) => content.value = _content, + }) + + menuRootProvider({ + scope: scopeOkuMenu.value, + onClose: () => handleOpenChange(false), + isUsingKeyboardRef, + dir: direction, + modal, + }) + + return () => h(OkuPopper, + { + ...attrs, + ...popperScope, + }, + { + default: () => slots.default?.(), + }, + ) + }, +}) + +export const OkuMenu = menu diff --git a/packages/components/menu/src/props.ts b/packages/components/menu/src/props.ts new file mode 100644 index 000000000..032fb832f --- /dev/null +++ b/packages/components/menu/src/props.ts @@ -0,0 +1,655 @@ +import { primitiveProps } from '@oku-ui/primitive' +import { ScopePropObject, createProvideScope } from '@oku-ui/provide' +import type { PropType, Ref } from 'vue' +import type { PopperAnchorElement, PopperAnchorNaviteElement, PopperAnchorProps, PopperContentElement, PopperContentNaviteElement } from '@oku-ui/popper' +import type { Direction } from '@oku-ui/direction' +import type { OkuElement, PrimitiveProps } from '@oku-ui/primitive' +import type { Scope } from '@oku-ui/provide' +import { createCollection } from '@oku-ui/collection' +import type { PortalProps } from '@oku-ui/portal' +import { createPopperScope } from '@oku-ui/popper' +import { createRovingFocusGroupScope } from '@oku-ui/roving-focus' +import type { GraceIntent } from './utils' + +export type ScopedMenu

= P & { scopeOkuMenu?: Scope } + +export const scopedMenuProps = { + scopeOkuMenu: { + ...ScopePropObject, + }, +} + +// NAMES +export const MENU_NAME = 'OkuMenu' +export const MENU_ANCHOR_NAME = 'OkuMenuAnchor' +export const MENU_PORTAL_NAME = 'OkuMenuPortal' +export const MENU_CONTENT_NAME = 'OkuMenuContent' +export const MENU_NON_MODEL_NAME = 'OkuMenuContentNonModel' +export const MENU_ROOT_CONTENT_TYPE_NAME = 'OkuMenuRootContentType' +export const MENU_CONTENT_IMPL_NAME = 'OkuMenuContentImpl' +export const MENU_GROUP_NAME = 'OkuMenuGroup' +export const MENU_LABEL_NAME = 'OkuMenuLabel' +export const MENU_ITEM_NAME = 'OkuMenuItem' +export const MENU_ITEM_IMPL_NAME = 'OkuMenuItemImpl' +export const MENU_CHECKBOX_ITEM_NAME = 'OkuMenuCheckboxItem' +export const MENU_RADIO_GROUP_NAME = 'OkuMenuRadioGroup' +export const MENU_RADIO_ITEM_NAME = 'OkuMenuRadioItem' +export const MENU_ITEM_INDICATOR_NAME = 'OkuMenuItemIndicator' +export const MENU_SEPARATOR_NAME = 'OkuMenuSeparator' +export const MENU_ARROW_NAME = 'OkuMenuArrow' +export const MENU_SUB_NAME = 'OkuMenuSub' +export const MENU_SUB_TRIGGER_NAME = 'OkuMenuSubTrigger' +export const MENU_SUB_CONTENT_NAME = 'OkuMenuSubContent' + +/* ------------------------------------------------------------------------------------------------- + * Menu - menu.ts + * ----------------------------------------------------------------------------------------------- */ + +export const SELECTION_KEYS = ['Enter', ' '] +export const FIRST_KEYS = ['ArrowDown', 'PageUp', 'Home'] +export const LAST_KEYS = ['ArrowUp', 'PageDown', 'End'] +export const FIRST_LAST_KEYS = [...FIRST_KEYS, ...LAST_KEYS] +export const SUB_OPEN_KEYS: Record = { + ltr: [...SELECTION_KEYS, 'ArrowRight'], + rtl: [...SELECTION_KEYS, 'ArrowLeft'], +} +export const SUB_CLOSE_KEYS: Record = { + ltr: ['ArrowLeft'], + rtl: ['ArrowRight'], +} + +type ItemData = { disabled: boolean; textValue: string } + +export const { CollectionProvider, CollectionSlot, CollectionItemSlot, useCollection, createCollectionScope } = createCollection(MENU_NAME) + +export const [createMenuProvide, createMenuScope] = createProvideScope(MENU_NAME, + [ + createCollectionScope, + createPopperScope, + createRovingFocusGroupScope, + ], +) + +export const usePopperScope = createPopperScope() +export const useRovingFocusGroupScope = createRovingFocusGroupScope() + +type MenuInjectValue = { + open: Ref + onOpenChange(open: boolean): void + content: Ref + onContentChange(content: MenuContentElement | null): void +} + +export const [menuProvider, useMenuInject] = createMenuProvide(MENU_NAME) + +type MenuRootInjectValue = { + onClose(): void + isUsingKeyboardRef: Ref + dir: Ref + modal: Ref +} + +export const [menuRootProvider, useMenuRootInject] = createMenuProvide(MENU_NAME) + +export interface MenuProps { + open?: Ref + onOpenChange?(open: boolean): void + dir: Ref + modal: Ref +} + +export const menuProps = { + props: { + open: { + type: Boolean as unknown as PropType, + default: false, + }, + dir: { + type: String as PropType, + }, + modal: { + type: Boolean, + default: true, + }, + }, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + openChange: (open: boolean) => true, + }, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuAnchor - menu-anchor.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuAnchorElement = PopperAnchorElement +export type MenuAnchorNaviteElement = PopperAnchorNaviteElement + +export interface MenuAnchorProps extends PopperAnchorProps { } + +export const menuAnchorProps = { + props: {}, + emits: {}, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuPortal - menu-portal.ts +* ----------------------------------------------------------------------------------------------- */ + +export type MenuPortalNaviteElement = OkuElement<'div'> +export type MenuPortalElement = HTMLDivElement + +type PortalInjectValue = { + forceMount?: Ref +} + +export const [portalProvider, usePortalInject] = createMenuProvide(MENU_PORTAL_NAME, + { + forceMount: undefined, + }, +) + +export interface MenuPortalProps { + /** + * Specify a container element to portal the content into. + */ + container?: PortalProps['container'] + /** + * Used to force mounting when more control is needed. Useful when + * controlling animation with React animation libraries. + */ + forceMount?: true +} + +export const menuPortalProps = { + props: { + container: { + type: Object as PropType, + }, + forceMount: { + type: Boolean as PropType, + default: undefined, + }, + ...primitiveProps, + }, + emits: {}, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuContent - menu-content.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuContentElement = MenuRootContentTypeElement +export type MenuContentNaviteElement = MenuRootContentTypeNaviteElement + +type MenuContentInjectValue = { + onItemEnter(event: PointerEvent): void + onItemLeave(event: PointerEvent): void + onTriggerLeave(event: PointerEvent): void + searchRef: Ref + pointerGraceTimerRef: Ref + onPointerGraceIntentChange(intent: GraceIntent | null): void +} +export const [menuContentProvider, useMenuContentInject] = createMenuProvide(MENU_CONTENT_NAME) + +/** + * We purposefully don't union MenuRootContent and MenuSubContent props here because + * they have conflicting prop types. We agreed that we would allow MenuSubContent to + * accept props that it would just ignore. + */ +export interface MenuContentProps extends MenuRootContentTypeProps { + /** + * Used to force mounting when more control is needed. Useful when + * controlling animation with React animation libraries. + */ + forceMount?: true +} + +export const menuContentProps = { + props: { + forceMount: { + type: Boolean as PropType, + default: undefined, + }, + }, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + itemEnter: (event: PointerEvent) => true, + // eslint-disable-next-line unused-imports/no-unused-vars + itemLeave: (event: PointerEvent) => true, + // eslint-disable-next-line unused-imports/no-unused-vars + triggerLeave: (event: PointerEvent) => true, + // eslint-disable-next-line unused-imports/no-unused-vars + pointerGraceIntentChange: (intent: GraceIntent | null) => true, + }, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuContentNonModel - menu-root-content-non-modal.ts + * ----------------------------------------------------------------------------------------------- */ + +export const menuRootContentNonModalProps = { + props: {}, + emits: {}, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuRootContentType - menu-root-content-type.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuRootContentTypeElement = MenuContentImplElement +export type MenuRootContentTypeNaviteElement = MenuContentImplNaviteElement + +export interface MenuRootContentTypeProps extends Omit { } +// TODO +export type MenuRootContentTypeEmits = { + focusOutside: [event: FocusEvent] +} + +export const menuRootContentTypeProps = { + props: {}, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + focusOutside: (event: FocusEvent) => true, + }, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuContentImpl - menu-content-impl + * ----------------------------------------------------------------------------------------------- */ + +export type MenuContentImplElement = PopperContentElement +export type MenuContentImplNaviteElement = PopperContentNaviteElement + +export type FocusScopeProps = FocusScopeElement +export type DismissableLayerProps = DismissableLayerElement +export type RovingFocusGroupProps = RovingFocusGroupElement +export type PopperContentProps = PopperContentElement + +export type MenuContentImplPrivateProps = { + onOpenAutoFocus?: FocusScopeProps['onMountAutoFocus'] + onDismiss?: DismissableLayerProps['onDismiss'] + disableOutsidePointerEvents?: DismissableLayerProps['disableOutsidePointerEvents'] + + /** + * Whether scrolling outside the `MenuContent` should be prevented + * (default: `false`) + */ + disableOutsideScroll?: boolean + + /** + * Whether focus should be trapped within the `MenuContent` + * (default: false) + */ + trapFocus?: FocusScopeProps['trapped'] +} + +export interface MenuContentImplProps extends MenuContentImplPrivateProps, Omit { + /** + * Event handler called when auto-focusing on close. + * Can be prevented. + */ + onCloseAutoFocus?: FocusScopeProps['onUnmountAutoFocus'] + + /** + * Whether keyboard navigation should loop around + * @defaultValue false + */ + loop?: RovingFocusGroupProps['loop'] + + onEntryFocus?: RovingFocusGroupProps['onEntryFocus'] + onEscapeKeyDown?: DismissableLayerProps['onEscapeKeyDown'] + onPointerDownOutside?: DismissableLayerProps['onPointerDownOutside'] + onFocusOutside?: DismissableLayerProps['onFocusOutside'] + onInteractOutside?: DismissableLayerProps['onInteractOutside'] +} +// TODO +export type MenuContentImplEmits = { + mountAutoFocus: [event: FocusEvent] + entryFocus: [event: FocusEvent] + keydown: [event: KeyboardEvent] + blur: [event: FocusEvent] + pointermove: [event: PointerEvent] +} + +export const menuContentImplProps = { + props: { + loop: { + type: Boolean as PropType, + default: undefined, + }, + }, + emits: { + + // openAutoFocus?: FocusScopeProps['onMountAutoFocus'] + // dismiss?: DismissableLayerProps['onDismiss'] + // disableOutsidePointerEvents?: DismissableLayerProps['disableOutsidePointerEvents'] + // closeAutoFocus?: FocusScopeProps['onUnmountAutoFocus'] + // entryFocus?: RovingFocusGroupProps['onEntryFocus'] + // escapeKeyDown?: DismissableLayerProps['onEscapeKeyDown'] + // pointerDownOutside?: DismissableLayerProps['onPointerDownOutside'] + // focusOutside?: DismissableLayerProps['onFocusOutside'] + // interactOutside?: DismissableLayerProps['onInteractOutside'] + }, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuGroup - menu-group.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuGroupElement = OkuElement<'div'> +export type MenuGroupNaviteElement = HTMLDivElement + +export interface MenuGroupProps extends PrimitiveProps {} + +export const menuGroupProps = { + props: {}, + emits: {}, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuLabel - menu-label.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuLabelElement = OkuElement<'div'> +export type MenuLabelNaviteElement = HTMLDivElement + +export interface MenuLabelProps extends PrimitiveProps { } + +export const menuLabelProps = { + props: {}, + emits: {}, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuItem - menu-item.ts + * ----------------------------------------------------------------------------------------------- */ + +export const ITEM_SELECT = 'menu.itemSelect' + +export type MenuItemElement = MenuItemImplElement +export type MenuItemNaviteElement = MenuItemImplNaviteElement + +export interface MenuItemProps extends Omit { + onSelect?: (event: Event) => void +} +// TODO +export type MenuItemEmits = { + pointerup: [event: FocusEvent] + keydown: [event: KeyboardEvent] +} + +export const menuItemProps = { + props: { + disabled: { + type: Boolean as PropType, + default: undefined, + }, + }, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + select: (event: Event) => true, + }, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuItemImpl - menu-item-impl.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuItemImplElement = OkuElement<'div'> +export type MenuItemImplNaviteElement = HTMLDivElement + +export interface MenuItemImplProps extends PrimitiveProps { + disabled?: boolean + textValue?: string +} +// TODO +export type MenuItemImplEmits = { + pointermove: [event: PointerEvent] + pointerleave: [event: PointerEvent] + focus: [event: FocusEvent] + blur: [event: FocusEvent] +} + +export const menuItemImplProps = { + props: { + textValue: { + type: String as PropType, + }, + disabled: { + type: Boolean as PropType, + default: undefined, + }, + }, + emits: {}, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuCheckboxItem - menu-checkbox-item.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuCheckboxItemElement = MenuItemElement +export type MenuCheckboxItemNaviteElement = MenuItemNaviteElement + +export type CheckedState = boolean | 'indeterminate' + +export interface MenuCheckboxItemProps extends MenuItemProps { + checked?: CheckedState + // `onCheckedChange` can never be called with `"indeterminate"` from the inside + onCheckedChange?: (checked: boolean) => void +} +// TODO +export type MenuCheckboxItemEmits = { + select: [event: Event] +} + +export const menuCheckboxItemProps = { + props: { + checked: { + type: Boolean as PropType, + default: false, + }, + }, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + checkedChange: (checked: boolean) => true, + }, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuRadioGroup - menu-radio-group.ts + * ----------------------------------------------------------------------------------------------- */ + +export const [radioGroupProvider, useRadioGroupInject] = createMenuProvide( + MENU_RADIO_GROUP_NAME, + { value: undefined, onValueChange: () => {} }, +) + +export type MenuRadioGroupElement = MenuGroupElement +export type MenuRadioGroupNaviteElement = MenuGroupNaviteElement + +export interface MenuRadioGroupProps extends MenuGroupProps { + value?: Ref + onValueChange?: (value: string) => void +} + +export const menuRadioGroupProps = { + props: { + value: { + type: String, + }, + }, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + valueChange: (value: string) => true, + }, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuRadioItem - menu-radio-item.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuRadioItemElement = MenuItemElement +export type MenuRadioItemNaviteElement = MenuItemNaviteElement + +export interface MenuRadioItemProps extends MenuItemProps { + value: Ref +} +// TODO +export type MenuRadioItemEmits = { + select: [event: Event] +} + +export const menuRadioItemProps = { + props: { + value: { + type: String, + }, + }, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + openChange: (open: boolean) => true, + }, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuItemIndicator - menu-item-indicator.ts + * ----------------------------------------------------------------------------------------------- */ + +type CheckboxInjectValue = { checked: CheckedState } + +export const [itemIndicatorProvider, useItemIndicatorInject] = createMenuProvide( + MENU_ITEM_INDICATOR_NAME, + { checked: false }, +) + +export type MenuItemIndicatorElement = OkuElement<'span'> +export type MenuItemIndicatorNaviteElement = HTMLSpanElement + +// export type PrimitiveSpanProps = Radix.ComponentPropsWithoutRef; +export interface MenuItemIndicatorProps extends PrimitiveProps { + /** + * Used to force mounting when more control is needed. Useful when + * controlling animation with React animation libraries. + */ + forceMount?: true +} + +export const menuItemIndicatorProps = { + props: { + forceMount: { + type: Boolean as PropType, + default: undefined, + }, + }, + emits: {}, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuSeparator - menu-separator.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuSeparatorElement = OkuElement<'div'> +export type MenuSeparatorNaviteElement = HTMLDivElement + +export interface MenuSeparatorProps extends PrimitiveProps { } + +export const menuSeparatorProps = { + props: {}, + emits: {}, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuArrow - menu-arrow.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuArrowElement = PopperArrowElement +export type MenuArrowNaviteElement = PopperArrowNaviteElement +export interface MenuArrowProps extends PopperArrowProps {} + +export const menuArrowProps = { + props: {}, + emits: {}, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuSub - menu-sub.ts + * ----------------------------------------------------------------------------------------------- */ + +type MenuSubInjectValue = { + contentId: Ref + triggerId: Ref + trigger: Ref + onTriggerChange(trigger: MenuSubTriggerElement | null): void +} + +export const [menuSubProvider, useMenuSubInject] = createMenuProvide(MENU_SUB_NAME) + +export interface MenuSubProps { + open?: Ref + onOpenChange?(open: boolean): void +} + +export const menuSubProps = { + props: { + open: { + type: Boolean, + default: false, + }, + }, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + openChange: (open: boolean) => true, + // eslint-disable-next-line unused-imports/no-unused-vars + triggerChange: (trigger: MenuSubTriggerElement | null) => true, + }, +} + +/* ------------------------------------------------------------------------------------------------- + * MenuSubTrigger - menu-sub-trigger.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuSubTriggerElement = MenuItemImplElement +export type MenuSubTriggerNaviteElement = MenuItemImplNaviteElement +export interface MenuSubTriggerProps extends MenuItemImplProps {} +// TODO +export type MenuSubTriggerEmits = { + Keydown: [event: KeyboardEvent] + pointermove: [event: PointerEvent] + pointerleave: [event: PointerEvent] +} + +/* ------------------------------------------------------------------------------------------------- + * MenuSubContent - menu-sub-content.ts + * ----------------------------------------------------------------------------------------------- */ + +export type MenuSubContentElement = MenuContentImplElement +export type MenuSubContentNaviteElement = MenuContentImplNaviteElement + +interface MenuSubContentProps extends Omit { + /** + * Used to force mounting when more control is needed. Useful when + * controlling animation with React animation libraries. + */ + forceMount?: true +} + +export const menuSubContentProps = { + props: { + forceMount: { + type: Boolean as PropType, + default: undefined, + }, + // ...propsOmit(menuContentImplProps.props, ['onCloseAutoFocus' | 'onEntryFocus' | 'side' | 'align']), + }, + emits: { + // ...menuContentImplProps.emits, + }, +} + +// TODO +export type MenuSubContentEmits = { + focusOutside: [event: FocusEvent] + keydown: [event: KeyboardEvent] + escapeKeydown: [event: KeyboardEvent] +} diff --git a/packages/components/menu/src/utils.ts b/packages/components/menu/src/utils.ts new file mode 100644 index 000000000..c124ab23b --- /dev/null +++ b/packages/components/menu/src/utils.ts @@ -0,0 +1,102 @@ +import type { CheckedState } from './props' + +function getOpenState(open: boolean) { + return open ? 'open' : 'closed' +} + +function isIndeterminate(checked?: CheckedState): checked is 'indeterminate' { + return checked === 'indeterminate' +} + +function getCheckedState(checked: CheckedState) { + return isIndeterminate(checked) ? 'indeterminate' : checked ? 'checked' : 'unchecked' +} + +function focusFirst(candidates: HTMLElement[]) { + const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement + for (const candidate of candidates) { + // if focus is already where we want to go, we don't want to keep going through the candidates + if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) + return + candidate.focus() + if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) + return + } +} + +/** + * Wraps an array around itself at a given start index + * Example: `wrapArray(['a', 'b', 'c', 'd'], 2) === ['c', 'd', 'a', 'b']` + */ +function wrapArray(array: T[], startIndex: number) { + return array.map((_, index) => array[(startIndex + index) % array.length]) +} + +/** + * This is the "meat" of the typeahead matching logic. It takes in all the values, + * the search and the current match, and returns the next match (or `undefined`). + * + * We normalize the search because if a user has repeatedly pressed a character, + * we want the exact same behavior as if we only had that one character + * (ie. cycle through options starting with that character) + * + * We also reorder the values by wrapping the array around the current match. + * This is so we always look forward from the current match, and picking the first + * match will always be the correct one. + * + * Finally, if the normalized search is exactly one character, we exclude the + * current match from the values because otherwise it would be the first to match always + * and focus would never move. This is as opposed to the regular case, where we + * don't want focus to move if the current match still matches. + */ +function getNextMatch(values: string[], search: string, currentMatch?: string) { + const isRepeated = search.length > 1 && Array.from(search).every(char => char === search[0]) + const normalizedSearch = isRepeated ? search[0] : search + const currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1 + let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0)) + const excludeCurrentMatch = normalizedSearch.length === 1 + if (excludeCurrentMatch) + wrappedValues = wrappedValues.filter(v => v !== currentMatch) + const nextMatch = wrappedValues.find(value => + value.toLowerCase().startsWith(normalizedSearch.toLowerCase()), + ) + return nextMatch !== currentMatch ? nextMatch : undefined +} + +export type Point = { x: number; y: number } +export type Polygon = Point[] +export type Side = 'left' | 'right' +export type GraceIntent = { area: Polygon; side: Side } + +// Determine if a point is inside of a polygon. +// Based on https://github.com/substack/point-in-polygon +function isPointInPolygon(point: Point, polygon: Polygon) { + const { x, y } = point + let inside = false + for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + const xi = polygon[i].x + const yi = polygon[i].y + const xj = polygon[j].x + const yj = polygon[j].y + + // prettier-ignore + const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi) + if (intersect) + inside = !inside + } + + return inside +} + +function isPointerInGraceArea(event: PointerEvent, area?: Polygon) { + if (!area) + return false + const cursorPos = { x: event.clientX, y: event.clientY } + return isPointInPolygon(cursorPos, area) +} + +function whenMouse(handler: any) { + return (event: any) => (event.pointerType === 'mouse' ? handler(event) : undefined) +} + +export { getOpenState, isIndeterminate, getCheckedState, focusFirst, getNextMatch, isPointerInGraceArea, whenMouse } diff --git a/packages/components/menu/tests/menu.test.ts b/packages/components/menu/tests/menu.test.ts new file mode 100644 index 000000000..2a89156f0 --- /dev/null +++ b/packages/components/menu/tests/menu.test.ts @@ -0,0 +1,7 @@ +import { afterEach, describe } from 'vitest' +import { enableAutoUnmount } from '@vue/test-utils' + +enableAutoUnmount(afterEach) + +describe('OkuMenu', () => { +}) diff --git a/packages/components/menu/tsconfig.json b/packages/components/menu/tsconfig.json new file mode 100644 index 000000000..887182612 --- /dev/null +++ b/packages/components/menu/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "tsconfig/node18.json", + "include": [ + "src/**/*", + "tests/**/*", + "node_modules/tsconfig" + ] +} diff --git a/packages/components/primitives/package.json b/packages/components/primitives/package.json index 88794a3d4..332eae8ea 100644 --- a/packages/components/primitives/package.json +++ b/packages/components/primitives/package.json @@ -44,6 +44,7 @@ "@oku-ui/dialog": "latest", "@oku-ui/hover-card": "latest", "@oku-ui/label": "latest", + "@oku-ui/menu": "latest", "@oku-ui/popover": "latest", "@oku-ui/progress": "latest", "@oku-ui/radio-group": "latest", diff --git a/playground/nuxt3/package.json b/playground/nuxt3/package.json index fdadc5e24..a419813ec 100644 --- a/playground/nuxt3/package.json +++ b/playground/nuxt3/package.json @@ -17,6 +17,7 @@ "@oku-ui/collapsible": "workspace:^", "@oku-ui/collection": "workspace:^", "@oku-ui/label": "workspace:^", + "@oku-ui/menu": "workspace:^", "@oku-ui/primitives": "workspace:^", "@oku-ui/progress": "workspace:^", "@oku-ui/roving-focus": "workspace:^", diff --git a/playground/vue3/package.json b/playground/vue3/package.json index b9d95704d..28ee4e075 100644 --- a/playground/vue3/package.json +++ b/playground/vue3/package.json @@ -15,6 +15,7 @@ "@oku-ui/avatar": "workspace:^", "@oku-ui/checkbox": "workspace:^", "@oku-ui/label": "workspace:^", + "@oku-ui/menu": "workspace:^", "@oku-ui/primitives": "workspace:^", "@oku-ui/progress": "workspace:^", "@oku-ui/roving-focus": "workspace:^", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6616b39e..50638c9c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,7 @@ overrides: '@oku-ui/tooltip': workspace:^ '@oku-ui/use-composable': workspace:^ '@oku-ui/utils': workspace:^ + '@oku-ui/menu': workspace:^ '@oku-ui/visually-hidden': workspace:^ '@vue/runtime-core': 3.3.4 vite: 4.3.5 @@ -97,6 +98,9 @@ importers: '@oku-ui/label': specifier: workspace:^ version: link:packages/components/label + '@oku-ui/menu': + specifier: workspace:^ + version: link:packages/components/menu '@oku-ui/popover': specifier: workspace:^ version: link:packages/components/popover @@ -609,6 +613,55 @@ importers: specifier: workspace:^ version: link:../../tsconfig + packages/components/menu: + dependencies: + '@oku-ui/collection': + specifier: workspace:^ + version: link:../collection + '@oku-ui/direction': + specifier: workspace:^ + version: link:../direction + '@oku-ui/dismissable-layer': + specifier: workspace:^ + version: link:../dismissable-layer + '@oku-ui/focus-guards': + specifier: workspace:^ + version: link:../../core/focus-guards + '@oku-ui/popper': + specifier: workspace:^ + version: link:../popper + '@oku-ui/portal': + specifier: workspace:^ + version: link:../portal + '@oku-ui/presence': + specifier: workspace:^ + version: link:../presence + '@oku-ui/primitive': + specifier: workspace:^ + version: link:../../core/primitive + '@oku-ui/provide': + specifier: workspace:^ + version: link:../../core/provide + '@oku-ui/roving-focus': + specifier: workspace:^ + version: link:../roving-focus + '@oku-ui/slot': + specifier: workspace:^ + version: link:../slot + '@oku-ui/use-composable': + specifier: workspace:^ + version: link:../../core/use-composable + '@oku-ui/utils': + specifier: workspace:^ + version: link:../../core/utils + vue: + specifier: 3.3.4 + version: 3.3.4 + devDependencies: + tsconfig: + specifier: workspace:^ + version: link:../../tsconfig + packages/components/popover: dependencies: '@floating-ui/vue': @@ -747,6 +800,9 @@ importers: '@oku-ui/label': specifier: workspace:^ version: link:../label + '@oku-ui/menu': + specifier: workspace:^ + version: link:../menu '@oku-ui/popover': specifier: workspace:^ version: link:../popover @@ -1268,7 +1324,7 @@ importers: version: 0.21.8 '@nuxt/kit': specifier: ^3.7.3 - version: 3.7.3(rollup@3.29.1) + version: 3.7.3(rollup@3.29.0) chalk: specifier: ^5.3.0 version: 5.3.0 @@ -1299,7 +1355,7 @@ importers: devDependencies: '@nuxt/devtools': specifier: latest - version: 0.8.3(nuxt@3.7.3)(rollup@3.29.1)(vite@4.3.5) + version: 0.8.3(nuxt@3.7.3)(rollup@3.29.0)(vite@4.3.5) '@nuxt/eslint-config': specifier: ^0.2.0 version: 0.2.0(eslint@8.49.0) @@ -1308,10 +1364,10 @@ importers: version: 0.5.1(@nuxt/kit@3.7.3)(nuxi@3.8.3)(typescript@5.2.2) '@nuxt/schema': specifier: ^3.7.3 - version: 3.7.3(rollup@3.29.1) + version: 3.7.3(rollup@3.29.0) '@nuxt/test-utils': specifier: ^3.7.3 - version: 3.7.3(rollup@3.29.1)(vitest@0.34.4)(vue@3.3.4) + version: 3.7.3(rollup@3.29.0)(vitest@0.34.4)(vue@3.3.4) '@types/node': specifier: ^20.6.0 version: 20.6.0 @@ -1326,7 +1382,7 @@ importers: version: 8.49.0 nuxt: specifier: ^3.7.3 - version: 3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.1)(typescript@5.2.2) + version: 3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.0)(typescript@5.2.2) vitest: specifier: ^0.34.4 version: 0.34.4(happy-dom@12.1.2)(jsdom@22.1.0) @@ -1372,6 +1428,9 @@ importers: '@oku-ui/label': specifier: workspace:^ version: link:../../packages/components/label + '@oku-ui/menu': + specifier: workspace:^ + version: link:../../packages/components/menu '@oku-ui/primitives': specifier: workspace:^ version: link:../../packages/components/primitives @@ -1427,6 +1486,9 @@ importers: '@oku-ui/label': specifier: workspace:^ version: link:../../packages/components/label + '@oku-ui/menu': + specifier: workspace:^ + version: link:../../packages/components/menu '@oku-ui/primitives': specifier: workspace:^ version: link:../../packages/components/primitives @@ -4051,16 +4113,16 @@ packages: resolution: {integrity: sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==} dev: true - /@nuxt/devtools-kit@0.8.3(nuxt@3.7.3)(rollup@3.29.1)(vite@4.3.5): + /@nuxt/devtools-kit@0.8.3(nuxt@3.7.3)(rollup@3.29.0)(vite@4.3.5): resolution: {integrity: sha512-qF5xJSvGzPMcWNTSPOpCWQfoDVqR+S56Ux/ZTm2nydHsXkJfS2k2iztJfbHlPquWdH4uS3lVxcfF4CFtgdJqkw==} peerDependencies: nuxt: ^3.6.5 vite: 4.3.5 dependencies: - '@nuxt/kit': 3.7.3(rollup@3.29.1) - '@nuxt/schema': 3.7.3(rollup@3.29.1) + '@nuxt/kit': 3.7.3(rollup@3.29.0) + '@nuxt/schema': 3.7.3(rollup@3.29.0) execa: 7.2.0 - nuxt: 3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.1)(typescript@5.2.2) + nuxt: 3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.0)(typescript@5.2.2) vite: 4.3.5(@types/node@20.6.0) transitivePeerDependencies: - rollup @@ -4084,7 +4146,7 @@ packages: semver: 7.5.4 dev: true - /@nuxt/devtools@0.8.3(nuxt@3.7.3)(rollup@3.29.1)(vite@4.3.5): + /@nuxt/devtools@0.8.3(nuxt@3.7.3)(rollup@3.29.0)(vite@4.3.5): resolution: {integrity: sha512-jkLsLBRb0nf7P46MHqZlvmMhi82oS5PAmmu326L5OH91thYBDyFAK4+L/NOUSResQNcs2t6/qz5i/ls5E/+sqA==} hasBin: true peerDependencies: @@ -4092,9 +4154,9 @@ packages: vite: 4.3.5 dependencies: '@antfu/utils': 0.7.6 - '@nuxt/devtools-kit': 0.8.3(nuxt@3.7.3)(rollup@3.29.1)(vite@4.3.5) + '@nuxt/devtools-kit': 0.8.3(nuxt@3.7.3)(rollup@3.29.0)(vite@4.3.5) '@nuxt/devtools-wizard': 0.8.3 - '@nuxt/kit': 3.7.3(rollup@3.29.1) + '@nuxt/kit': 3.7.3(rollup@3.29.0) birpc: 0.2.14 boxen: 7.1.1 consola: 3.2.3 @@ -4111,7 +4173,7 @@ packages: launch-editor: 2.6.0 local-pkg: 0.4.3 magicast: 0.2.10 - nuxt: 3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.1)(typescript@5.2.2) + nuxt: 3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.0)(typescript@5.2.2) nypm: 0.3.3 ofetch: 1.3.3 ohash: 1.1.3 @@ -4124,9 +4186,9 @@ packages: semver: 7.5.4 simple-git: 3.19.1 sirv: 2.0.3 - unimport: 3.3.0(rollup@3.29.1) + unimport: 3.3.0(rollup@3.29.0) vite: 4.3.5(@types/node@20.6.0) - vite-plugin-inspect: 0.7.38(@nuxt/kit@3.7.3)(rollup@3.29.1)(vite@4.3.5) + vite-plugin-inspect: 0.7.38(@nuxt/kit@3.7.3)(rollup@3.29.0)(vite@4.3.5) vite-plugin-vue-inspector: 3.7.1(vite@4.3.5) wait-on: 7.0.1 which: 3.0.1 @@ -4180,33 +4242,6 @@ packages: transitivePeerDependencies: - rollup - supports-color - dev: true - - /@nuxt/kit@3.7.3(rollup@3.29.1): - resolution: {integrity: sha512-bhP02i6CNti15Z4ix3LpR3fd1ANtTcpfS3CDSaCja24hDt3UxIasyp52mqD9LRC+OxrUVHJziB18EwUtS6RLDQ==} - engines: {node: ^14.18.0 || >=16.10.0} - dependencies: - '@nuxt/schema': 3.7.3(rollup@3.29.1) - c12: 1.4.2 - consola: 3.2.3 - defu: 6.1.2 - globby: 13.2.2 - hash-sum: 2.0.0 - ignore: 5.2.4 - jiti: 1.20.0 - knitwork: 1.0.0 - mlly: 1.4.2 - pathe: 1.1.1 - pkg-types: 1.0.3 - scule: 1.0.0 - semver: 7.5.4 - ufo: 1.3.0 - unctx: 2.3.1 - unimport: 3.3.0(rollup@3.29.1) - untyped: 1.4.0 - transitivePeerDependencies: - - rollup - - supports-color /@nuxt/module-builder@0.5.1(@nuxt/kit@3.7.3)(nuxi@3.8.3)(typescript@5.2.2): resolution: {integrity: sha512-O39UrOFbNgL27urwDqeMgXeYiNIUvp73nsmtt7jm9JDcMVIWykuUzyBPYtHif7gq5ElzIjjmDw9zdRGgyMzo+w==} @@ -4215,7 +4250,7 @@ packages: '@nuxt/kit': ^3.7.0 nuxi: ^3.7.3 dependencies: - '@nuxt/kit': 3.7.3(rollup@3.29.1) + '@nuxt/kit': 3.7.3(rollup@3.29.0) consola: 3.2.3 mlly: 1.4.2 mri: 1.2.0 @@ -4260,25 +4295,6 @@ packages: transitivePeerDependencies: - rollup - supports-color - dev: true - - /@nuxt/schema@3.7.3(rollup@3.29.1): - resolution: {integrity: sha512-Uqe3Z9RnAROzv5owQo//PztD9d4csKK6ulwQO1hIAinCh34X7z2zrv9lhm14hlRYU1n7ISEi4S7UeHgL/r8d8A==} - engines: {node: ^14.18.0 || >=16.10.0} - dependencies: - '@nuxt/ui-templates': 1.3.1 - defu: 6.1.2 - hookable: 5.5.3 - pathe: 1.1.1 - pkg-types: 1.0.3 - postcss-import-resolver: 2.0.0 - std-env: 3.4.3 - ufo: 1.3.0 - unimport: 3.3.0(rollup@3.29.1) - untyped: 1.4.0 - transitivePeerDependencies: - - rollup - - supports-color /@nuxt/telemetry@2.4.1(rollup@3.29.0): resolution: {integrity: sha512-Cj+4sXjO5pZNW2sX7Y+djYpf4pZwgYF3rV/YHLWIOq9nAjo2UcDXjh1z7qnhkoUkvJN3lHnvhnCNhfAioe6k/A==} @@ -4309,36 +4325,7 @@ packages: - supports-color dev: true - /@nuxt/telemetry@2.4.1(rollup@3.29.1): - resolution: {integrity: sha512-Cj+4sXjO5pZNW2sX7Y+djYpf4pZwgYF3rV/YHLWIOq9nAjo2UcDXjh1z7qnhkoUkvJN3lHnvhnCNhfAioe6k/A==} - hasBin: true - dependencies: - '@nuxt/kit': 3.7.3(rollup@3.29.1) - chalk: 5.3.0 - ci-info: 3.8.0 - consola: 3.2.3 - create-require: 1.1.1 - defu: 6.1.2 - destr: 2.0.1 - dotenv: 16.3.1 - fs-extra: 11.1.1 - git-url-parse: 13.1.0 - is-docker: 3.0.0 - jiti: 1.20.0 - mri: 1.2.0 - nanoid: 4.0.2 - node-fetch: 3.3.2 - ofetch: 1.3.3 - parse-git-config: 3.0.0 - pathe: 1.1.1 - rc9: 2.1.1 - std-env: 3.4.3 - transitivePeerDependencies: - - rollup - - supports-color - dev: true - - /@nuxt/test-utils@3.7.3(rollup@3.29.1)(vitest@0.34.4)(vue@3.3.4): + /@nuxt/test-utils@3.7.3(rollup@3.29.0)(vitest@0.34.4)(vue@3.3.4): resolution: {integrity: sha512-KHe5NLnPh10gT62tFZtQZ7h40yrlsjxfVYrfpD8WchzdeC4lHtZPMaYFTB8K43il/L6U3m2i+kC7zIFMGEpzWQ==} engines: {node: ^14.18.0 || >=16.10.0} peerDependencies: @@ -4354,8 +4341,8 @@ packages: vitest: optional: true dependencies: - '@nuxt/kit': 3.7.3(rollup@3.29.1) - '@nuxt/schema': 3.7.3(rollup@3.29.1) + '@nuxt/kit': 3.7.3(rollup@3.29.0) + '@nuxt/schema': 3.7.3(rollup@3.29.0) consola: 3.2.3 defu: 6.1.2 execa: 7.2.0 @@ -4434,14 +4421,14 @@ packages: - vue-tsc dev: true - /@nuxt/vite-builder@3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.1)(typescript@5.2.2)(vue@3.3.4): + /@nuxt/vite-builder@3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.0)(typescript@5.2.2)(vue@3.3.4): resolution: {integrity: sha512-WbPYku1YKtdqLo5t3Vcs/2xOP8Es9K0OR0uGirdVMp74l4ZOMWBGSW9s4psiihjnNdHURdodD0cuE3tse9t7PA==} engines: {node: ^14.18.0 || >=16.10.0} peerDependencies: vue: 3.3.4 dependencies: - '@nuxt/kit': 3.7.3(rollup@3.29.1) - '@rollup/plugin-replace': 5.0.2(rollup@3.29.1) + '@nuxt/kit': 3.7.3(rollup@3.29.0) + '@rollup/plugin-replace': 5.0.2(rollup@3.29.0) '@vitejs/plugin-vue': 4.3.4(vite@4.3.5)(vue@3.3.4) '@vitejs/plugin-vue-jsx': 3.0.2(vite@4.3.5)(vue@3.3.4) autoprefixer: 10.4.15(postcss@8.4.30) @@ -4466,7 +4453,7 @@ packages: postcss: 8.4.30 postcss-import: 15.1.0(postcss@8.4.30) postcss-url: 10.1.3(postcss@8.4.30) - rollup-plugin-visualizer: 5.9.2(rollup@3.29.1) + rollup-plugin-visualizer: 5.9.2(rollup@3.29.0) std-env: 3.4.3 strip-literal: 1.3.0 ufo: 1.3.0 @@ -5515,7 +5502,6 @@ packages: estree-walker: 2.0.2 picomatch: 2.3.1 rollup: 3.29.0 - dev: true /@rollup/pluginutils@5.0.4(rollup@3.29.1): resolution: {integrity: sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==} @@ -5530,6 +5516,7 @@ packages: estree-walker: 2.0.2 picomatch: 2.3.1 rollup: 3.29.1 + dev: true /@rushstack/eslint-patch@1.3.3: resolution: {integrity: sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==} @@ -7407,26 +7394,6 @@ packages: - rollup dev: true - /@vue-macros/common@1.8.0(rollup@3.29.1)(vue@3.3.4): - resolution: {integrity: sha512-auDJJzE0z3uRe3867e0DsqcseKImktNf5ojCZgUKqiVxb2yTlwlgOVAYCgoep9oITqxkXQymSvFeKhedi8PhaA==} - engines: {node: '>=16.14.0'} - peerDependencies: - vue: 3.3.4 - peerDependenciesMeta: - vue: - optional: true - dependencies: - '@babel/types': 7.22.17 - '@rollup/pluginutils': 5.0.4(rollup@3.29.1) - '@vue/compiler-sfc': 3.3.4 - ast-kit: 0.11.2(rollup@3.29.1) - local-pkg: 0.4.3 - magic-string-ast: 0.3.0 - vue: 3.3.4 - transitivePeerDependencies: - - rollup - dev: true - /@vue/babel-helper-vue-transform-on@1.1.5: resolution: {integrity: sha512-SgUymFpMoAyWeYWLAY+MkCK3QEROsiUnfaw5zxOVD/M64KQs8D/4oK6Q5omVA2hnvEOE0SCkH2TZxs/jnnUj7w==} dev: true @@ -8069,17 +8036,6 @@ packages: - rollup dev: true - /ast-kit@0.11.2(rollup@3.29.1): - resolution: {integrity: sha512-Q0DjXK4ApbVoIf9GLyCo252tUH44iTnD/hiJ2TQaJeydYWSpKk0sI34+WMel8S9Wt5pbLgG02oJ+gkgX5DV3sQ==} - engines: {node: '>=16.14.0'} - dependencies: - '@babel/parser': 7.22.16 - '@rollup/pluginutils': 5.0.4(rollup@3.29.1) - pathe: 1.1.1 - transitivePeerDependencies: - - rollup - dev: true - /ast-types@0.15.2: resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} engines: {node: '>=4'} @@ -13103,7 +13059,7 @@ packages: - xml2js dev: true - /nuxt@3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.1)(typescript@5.2.2): + /nuxt@3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.0)(typescript@5.2.2): resolution: {integrity: sha512-fh3l3PhL79pHJckHVGebTFYlqXDq1jHAXUcNmS3RTfmJRb1s4qi5kSRgmYUWEI5I4Iu+S0u8wWh2ChvnZMQRog==} engines: {node: ^14.18.0 || >=16.10.0} hasBin: true @@ -13117,11 +13073,11 @@ packages: optional: true dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/kit': 3.7.3(rollup@3.29.1) - '@nuxt/schema': 3.7.3(rollup@3.29.1) - '@nuxt/telemetry': 2.4.1(rollup@3.29.1) + '@nuxt/kit': 3.7.3(rollup@3.29.0) + '@nuxt/schema': 3.7.3(rollup@3.29.0) + '@nuxt/telemetry': 2.4.1(rollup@3.29.0) '@nuxt/ui-templates': 1.3.1 - '@nuxt/vite-builder': 3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.1)(typescript@5.2.2)(vue@3.3.4) + '@nuxt/vite-builder': 3.7.3(@types/node@20.6.0)(eslint@8.49.0)(rollup@3.29.0)(typescript@5.2.2)(vue@3.3.4) '@types/node': 20.6.0 '@unhead/dom': 1.7.3 '@unhead/ssr': 1.7.3 @@ -13163,9 +13119,9 @@ packages: uncrypto: 0.1.3 unctx: 2.3.1 unenv: 1.7.4 - unimport: 3.3.0(rollup@3.29.1) + unimport: 3.3.0(rollup@3.29.0) unplugin: 1.4.0 - unplugin-vue-router: 0.6.4(rollup@3.29.1)(vue-router@4.2.4)(vue@3.3.4) + unplugin-vue-router: 0.6.4(rollup@3.29.0)(vue-router@4.2.4)(vue@3.3.4) untyped: 1.4.0 vue: 3.3.4 vue-bundle-renderer: 2.0.0 @@ -14948,6 +14904,7 @@ packages: hasBin: true optionalDependencies: fsevents: 2.3.3 + dev: true /rrweb-cssom@0.6.0: resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} @@ -16162,7 +16119,6 @@ packages: unplugin: 1.4.0 transitivePeerDependencies: - rollup - dev: true /unimport@3.3.0(rollup@3.29.1): resolution: {integrity: sha512-3jhq3ZG5hFZzrWGDCpx83kjPzefP/EeuKkIO1T0MA4Zwj+dO/Og1mFvZ4aZ5WSDm0FVbbdVIRH1zKBG7c4wOpg==} @@ -16180,6 +16136,7 @@ packages: unplugin: 1.4.0 transitivePeerDependencies: - rollup + dev: true /unique-filename@3.0.0: resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} @@ -16325,33 +16282,6 @@ packages: - vue dev: true - /unplugin-vue-router@0.6.4(rollup@3.29.1)(vue-router@4.2.4)(vue@3.3.4): - resolution: {integrity: sha512-9THVhhtbVFxbsIibjK59oPwMI1UCxRWRPX7azSkTUABsxovlOXJys5SJx0kd/0oKIqNJuYgkRfAgPuO77SqCOg==} - peerDependencies: - vue-router: ^4.1.0 - peerDependenciesMeta: - vue-router: - optional: true - dependencies: - '@babel/types': 7.22.17 - '@rollup/pluginutils': 5.0.4(rollup@3.29.1) - '@vue-macros/common': 1.8.0(rollup@3.29.1)(vue@3.3.4) - ast-walker-scope: 0.4.2 - chokidar: 3.5.3 - fast-glob: 3.3.1 - json5: 2.2.3 - local-pkg: 0.4.3 - mlly: 1.4.2 - pathe: 1.1.1 - scule: 1.0.0 - unplugin: 1.4.0 - vue-router: 4.2.4(vue@3.3.4) - yaml: 2.3.2 - transitivePeerDependencies: - - rollup - - vue - dev: true - /unplugin@1.4.0: resolution: {integrity: sha512-5x4eIEL6WgbzqGtF9UV8VEC/ehKptPXDS6L2b0mv4FRMkJxRtjaJfOWDd6a8+kYbqsjklix7yWP0N3SUepjXcg==} dependencies: @@ -16709,7 +16639,7 @@ packages: - supports-color dev: true - /vite-plugin-inspect@0.7.38(@nuxt/kit@3.7.3)(rollup@3.29.1)(vite@4.3.5): + /vite-plugin-inspect@0.7.38(@nuxt/kit@3.7.3)(rollup@3.29.0)(vite@4.3.5): resolution: {integrity: sha512-+p6pJVtBOLGv+RBrcKAFUdx+euizg0bjL35HhPyM0MjtKlqoC5V9xkCmO9Ctc8JrTyXqODbHqiLWJKumu5zJ7g==} engines: {node: '>=14'} peerDependencies: @@ -16720,8 +16650,8 @@ packages: optional: true dependencies: '@antfu/utils': 0.7.6 - '@nuxt/kit': 3.7.3(rollup@3.29.1) - '@rollup/pluginutils': 5.0.4(rollup@3.29.1) + '@nuxt/kit': 3.7.3(rollup@3.29.0) + '@rollup/pluginutils': 5.0.4(rollup@3.29.0) debug: 4.3.4 error-stack-parser-es: 0.1.1 fs-extra: 11.1.1 @@ -16838,7 +16768,7 @@ packages: '@types/node': 20.6.0 esbuild: 0.17.19 postcss: 8.4.30 - rollup: 3.29.1 + rollup: 3.29.0 optionalDependencies: fsevents: 2.3.3 dev: true diff --git a/vitest.config.ts b/vitest.config.ts index 6b0fb107b..7420537ce 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -18,6 +18,7 @@ export default defineConfig({ setupFiles: ['./vitest-setup.ts'], globals: true, alias: { + '@oku-ui/menu': 'packages/components/menu/src', '@oku-ui/checkbox': 'packages/components/checkbox/src', '@oku-ui/alert-dialog': 'packages/components/alert-dialog/src', '@oku-ui/portal': 'packages/components/portal/src', From a9f06bd9b25cbbd747f173c00ca367a4a2d42cff Mon Sep 17 00:00:00 2001 From: Shehab Rahal Date: Mon, 2 Oct 2023 21:02:48 +0800 Subject: [PATCH 02/79] Merge branch 'main' into feat/menu-316 --- .github/assets/og/oku-accordion.jpg | Bin 0 -> 54701 bytes .github/assets/og/oku-alert-dialog.jpg | Bin 0 -> 53754 bytes .github/assets/og/oku-scroll-area.jpg | Bin 0 -> 50126 bytes CONTRIBUTING.md | 3 + README.md | 2 +- nx.json | 3 + package.json | 7 +- packages/components/accordion/README.md | 14 + .../{arrow => accordion}/build.config.ts | 0 packages/components/accordion/package.json | 55 + .../components/accordion/src/accordion.ts | 68 + .../accordion/src/accordionContent.ts | 54 + .../accordion/src/accordionHeader.ts | 50 + .../components/accordion/src/accordionImpl.ts | 131 + .../accordion/src/accordionImplMultiple.ts | 75 + .../accordion/src/accordionImplSingle.ts | 73 + .../components/accordion/src/accordionItem.ts | 69 + .../accordion/src/accordionTrigger.ts | 58 + packages/components/accordion/src/index.ts | 44 + packages/components/accordion/src/props.ts | 383 ++ .../accordion/src/stories/AccordionDemo.vue | 336 ++ .../accordion/src/stories/Animated.vue | 72 + .../accordion/src/stories/Animated2D.vue | 33 + .../src/stories/AnimatedControlled.vue | 63 + .../accordion/src/stories/Chromatic.vue | 210 + .../accordion/src/stories/Horizontal.vue | 57 + .../accordion/src/stories/Multiple.vue | 112 + .../accordion/src/stories/OutsideViewport.vue | 62 + .../accordion/src/stories/Single.vue | 162 + .../src/stories/accordion.stories.ts | 141 + packages/components/accordion/src/utils.ts | 3 + packages/components/accordion/tsconfig.json | 11 + packages/components/alert-dialog/package.json | 4 +- .../src/alert-dialog-description-warning.ts | 4 +- .../src/alert-dialog-description.ts | 4 +- packages/components/alert-dialog/src/index.ts | 2 +- packages/components/alert-dialog/src/props.ts | 2 +- .../__snapshots__/alert-dialog.test.ts.snap | 4 +- .../alert-dialog/tests/alert-dialog.test.ts | 3 +- packages/components/aspect-ratio/package.json | 2 +- packages/components/avatar/package.json | 2 +- packages/components/checkbox/package.json | 2 +- packages/components/collapsible/package.json | 2 +- .../components/collapsible/src/collapsible.ts | 2 +- .../collapsible/src/collapsibleContent.ts | 6 +- .../collapsible/src/collapsibleContentImpl.ts | 6 +- packages/components/dialog/package.json | 4 +- packages/components/hover-card/package.json | 2 +- packages/components/label/package.json | 2 +- packages/components/popover/package.json | 2 +- packages/components/popover/src/index.ts | 69 +- packages/components/popover/src/popover.ts | 90 +- .../components/popover/src/popoverAnchor.ts | 24 +- .../components/popover/src/popoverArrow.ts | 24 +- .../components/popover/src/popoverClose.ts | 29 +- .../components/popover/src/popoverContent.ts | 37 +- .../popover/src/popoverContentImpl.ts | 67 +- .../popover/src/popoverContentModal.ts | 34 +- .../popover/src/popoverContentNonModal.ts | 91 +- .../components/popover/src/popoverPortal.ts | 49 +- .../components/popover/src/popoverTrigger.ts | 32 +- packages/components/popover/src/props.ts | 308 ++ .../popover/src/stories/Animated.vue | 35 + .../popover/src/stories/Boundary.vue | 46 + .../popover/src/stories/Chromatic.vue | 813 +++ .../popover/src/stories/Controlled.vue | 42 + .../popover/src/stories/CustomAnchor.vue | 43 + .../popover/src/stories/ForcedMount.vue | 35 + .../popover/src/stories/Modality.vue | 82 + .../components/popover/src/stories/Nested.vue | 96 + .../popover/src/stories/PopoverApi.json | 66 + .../popover/src/stories/PopoverDemo.vue | 102 +- .../components/popover/src/stories/Styled.vue | 37 + .../popover/src/stories/VControlled.vue | 38 + .../src/stories/WithSlottedTrigger.vue | 28 + .../popover/src/stories/popover.stories.ts | 161 +- packages/components/popover/src/utils.ts | 11 - .../tests/__snapshots__/popover.test.ts.snap | 1700 ++++++ .../components/popover/tests/popover.test.ts | 364 ++ packages/components/popover/tsconfig.json | 7 +- .../tests/__snapshots__/popper.test.ts.snap | 60 - .../tests/__snapshots__/presence.test.ts.snap | 436 -- packages/components/primitives/package.json | 4 +- packages/components/primitives/src/index.ts | 3 + packages/components/progress/package.json | 2 +- packages/components/radio-group/package.json | 2 +- packages/components/scroll-area/README.md | 2 +- packages/components/scroll-area/package.json | 2 +- packages/components/separator/package.json | 2 +- packages/components/slider/package.json | 2 +- packages/components/switch/package.json | 2 +- packages/components/tabs/package.json | 2 +- packages/components/toast/package.json | 2 +- packages/components/toggle-group/package.json | 2 +- packages/components/toggle/package.json | 2 +- packages/components/toolbar/package.json | 2 +- packages/components/tooltip/package.json | 2 +- .../tooltip/src/stories/CustomDurations.vue | 2 +- .../tooltip/src/stories/TooltipDemo.vue | 2 +- packages/{components => core}/arrow/README.md | 0 .../collection => core/arrow}/build.config.ts | 0 .../{components => core}/arrow/package.json | 2 +- .../{components => core}/arrow/src/arrow.ts | 0 .../{components => core}/arrow/src/index.ts | 0 .../arrow/src/stories/ArrowDemo.vue | 0 .../arrow/src/stories/arrow.stories.ts | 0 .../tests/__snapshots__/arrow.test.ts.snap | 0 .../arrow/tests/arrow.test.ts | 0 .../{components => core}/arrow/tsconfig.json | 0 .../{components => core}/collection/README.md | 0 .../collection}/build.config.ts | 0 .../collection/package.json | 2 +- .../collection/src/collection.test.ts | 0 .../collection/src/collection.ts | 0 .../collection/src/index.ts | 0 .../collection/src/stories/CollectionDemo.vue | 0 .../collection/src/stories/Countries.vue | 0 .../collection/src/stories/Item.vue | 0 .../collection/src/stories/List.vue | 0 .../collection/src/stories/LogItems.ts | 0 .../collection/src/stories/Tomato.vue | 0 .../src/stories/collection.stories.ts | 0 .../collection/src/stories/utils.ts | 0 .../collection/tsconfig.json | 0 .../{components => core}/direction/README.md | 0 .../direction}/build.config.ts | 0 .../direction/package.json | 2 +- .../direction/src/Direction.ts | 0 .../direction/src/direction.test.ts | 0 .../direction/src/index.ts | 0 .../direction/tsconfig.json | 0 .../dismissable-layer/README.md | 0 .../dismissable-layer}/build.config.ts | 0 .../dismissable-layer/package.json | 2 +- .../src/DismissableLayer.test.ts | 0 .../dismissable-layer/src/DismissableLayer.ts | 113 +- .../src/DismissableLayerBranch.ts | 2 +- .../dismissable-layer/src/index.ts | 7 +- packages/core/dismissable-layer/src/props.ts | 101 + .../src/stories/DismissableBox.vue | 0 .../src/stories/DismissableLayer.stories.ts | 0 .../src/stories/DismissableLayerDemo.vue | 0 .../src/stories/DummyDialog.vue | 0 .../src/stories/DummyPopover.vue | 0 .../dismissable-layer/src/util.test.ts | 3 +- .../dismissable-layer/src/util.ts | 134 +- .../dismissable-layer/tsconfig.json | 0 packages/core/focus-guards/package.json | 2 +- .../focus-scope/README.md | 0 .../focus-scope}/build.config.ts | 0 .../focus-scope/package.json | 2 +- .../focus-scope/src/focus-scope-stack.ts | 0 .../focus-scope/src/focus-scope.test.ts | 0 .../focus-scope/src/focus-scope.ts | 0 .../focus-scope/src/index.ts | 0 .../src/stories/FocusScope.stories.ts | 0 .../src/stories/FocusScopeDemo.vue | 0 .../focus-scope/src/utils.ts | 0 .../focus-scope/tsconfig.json | 0 .../{components => core}/popper/README.md | 0 .../portal => core/popper}/build.config.ts | 0 .../{components => core}/popper/package.json | 2 +- .../{components => core}/popper/src/index.ts | 5 + .../{components => core}/popper/src/popper.ts | 0 .../popper/src/popperAnchor.ts | 0 .../popper/src/popperArrow.ts | 0 .../popper/src/popperContent.ts | 1 + .../{components => core}/popper/src/props.ts | 13 +- .../popper/src/stories/Animated.vue | 0 .../popper/src/stories/Chromatic.vue | 0 .../popper/src/stories/CustomArrow.vue | 0 .../popper/src/stories/OneScroll.vue | 0 .../popper/src/stories/PopperDemo.vue | 0 .../popper/src/stories/Scrollable.vue | 0 .../popper/src/stories/Styled.vue | 0 .../popper/src/stories/Transition.vue | 2 +- .../popper/src/stories/WithCustomArrow.vue | 0 .../popper/src/stories/WithPortal.vue | 0 .../WithUpdatePositionStrategyAlways.vue | 0 .../popper/src/stories/popper.stories.ts | 0 .../{components => core}/popper/src/utils.ts | 0 .../tests/__snapshots__/popper.test.ts.snap | 579 +++ .../popper/tests/popper.test.ts | 18 +- .../{components => core}/popper/tsconfig.json | 0 .../{components => core}/portal/README.md | 0 .../presence => core/portal}/build.config.ts | 0 .../{components => core}/portal/package.json | 2 +- .../{components => core}/portal/src/Portal.ts | 4 +- .../{components => core}/portal/src/index.ts | 0 .../portal/src/portal.test.ts | 0 .../portal/src/stories/Portal.stories.ts | 0 .../portal/src/stories/PortalDemo.vue | 0 .../{components => core}/portal/tsconfig.json | 0 .../{components => core}/presence/README.md | 0 .../presence}/build.config.ts | 0 .../presence/package.json | 2 +- .../presence/src/index.ts | 0 .../presence/src/presence.ts | 0 .../presence/src/stories/Basic.vue | 0 .../presence/src/stories/PresenceDemo.vue | 0 .../presence/src/stories/Toggles.vue | 0 .../stories/WithDeferredMountAnimation.vue | 0 .../src/stories/WithMountAnimation.vue | 0 .../stories/WithMultipleMountAnimations.vue | 0 .../WithMultipleOpenAndCloseAnimations.vue | 0 .../src/stories/WithOpenAndCloseAnimation.vue | 0 .../src/stories/WithUnmountAnimation.vue | 0 .../presence/src/stories/presence.stories.ts | 0 .../presence/src/usePresence.ts | 0 .../presence/src/useStateMachine.ts | 0 .../tests/__snapshots__/presence.test.ts.snap | 436 ++ .../presence/tests/presence.test.ts | 0 .../presence/tsconfig.json | 0 packages/core/primitive/package.json | 2 +- packages/core/provide/package.json | 2 +- .../roving-focus/README.md | 0 .../roving-focus}/build.config.ts | 0 .../roving-focus/package.json | 2 +- .../roving-focus/src/RovingFocusGroup.ts | 0 .../roving-focus/src/RovingFocusGroupImpl.ts | 0 .../roving-focus/src/RovingFocusGroupItem.ts | 0 .../roving-focus/src/index.ts | 0 .../roving-focus/src/stories/Button.vue | 0 .../roving-focus/src/stories/ButtonGroup.vue | 0 .../src/stories/RovingFocusDemo.stories.ts | 0 .../src/stories/RovingFocusDemo.vue | 0 .../roving-focus/src/types.ts | 0 .../roving-focus/src/utils.ts | 0 .../__snapshots__/roving-focus.test.ts.snap | 0 .../roving-focus/tests/roving-focus.test.ts | 0 .../roving-focus/tsconfig.json | 0 packages/{components => core}/slot/README.md | 0 .../slot}/build.config.ts | 0 .../{components => core}/slot/package.json | 2 +- .../{components => core}/slot/src/index.ts | 0 .../slot/src/slot.test.ts | 0 .../{components => core}/slot/src/slot.ts | 0 .../slot/src/stories/SlotDemo.vue | 0 .../slot/src/stories/slot.stories.ts | 0 .../{components => core}/slot/src/utils.ts | 0 .../{components => core}/slot/tsconfig.json | 0 packages/core/use-composable/package.json | 4 +- .../use-composable/src/useEscapeKeydown.ts | 23 +- .../tests/useEscapeKeydown.test.ts | 86 +- .../use-composable/tests/useForwardRef.ts | 62 - packages/core/utils/package.json | 2 +- .../visually-hidden/README.md | 0 packages/core/visually-hidden/build.config.ts | 7 + .../visually-hidden/package.json | 2 +- .../visually-hidden/src/VisuallyHidden.ts | 0 .../visually-hidden/src/index.ts | 0 .../src/stories/VisuallyHidden.stories.ts | 0 .../src/stories/VisuallyHiddenDemo.vue | 0 .../src/visually-hidden.test.ts | 0 .../visually-hidden/tsconfig.json | 0 packages/primitives-nuxt/package.json | 10 +- packages/primitives-nuxt/src/types.ts | 5 +- packages/primitives-nuxt/src/utils.ts | 6 + packages/tsconfig/node18.json | 1 + playground/nuxt3/app.vue | 16 +- playground/nuxt3/components.json | 126 + playground/nuxt3/nuxt.config.ts | 20 +- playground/nuxt3/package.json | 22 +- playground/nuxt3/pages/accordion.vue | 3 + playground/nuxt3/pages/alert-dialog.vue | 3 + playground/nuxt3/pages/arrow.vue | 7 +- playground/nuxt3/pages/aspect-ratio.vue | 7 +- playground/nuxt3/pages/avatar.vue | 7 +- playground/nuxt3/pages/checkbox.vue | 6 +- playground/nuxt3/pages/collapsible.vue | 4 +- playground/nuxt3/pages/collection.vue | 4 +- playground/nuxt3/pages/dialog.vue | 8 +- playground/nuxt3/pages/dismissable-layer.vue | 8 +- playground/nuxt3/pages/focus-scope.vue | 8 +- playground/nuxt3/pages/hover-card.vue | 8 +- playground/nuxt3/pages/index.vue | 130 +- playground/nuxt3/pages/label.vue | 4 +- playground/nuxt3/pages/popover.vue | 6 +- playground/nuxt3/pages/popper.vue | 6 +- playground/nuxt3/pages/portal.vue | 6 +- playground/nuxt3/pages/presence.vue | 6 +- playground/nuxt3/pages/progress.vue | 4 +- playground/nuxt3/pages/radio-group.vue | 6 +- playground/nuxt3/pages/roving-focus.vue | 4 +- playground/nuxt3/pages/separator.vue | 7 +- playground/nuxt3/pages/slider.vue | 4 +- playground/nuxt3/pages/slot.vue | 4 +- playground/nuxt3/pages/switch.vue | 4 +- playground/nuxt3/pages/tabs.vue | 4 +- playground/nuxt3/pages/toast.vue | 8 +- playground/nuxt3/pages/toggle-group.vue | 5 +- playground/nuxt3/pages/toggle.vue | 5 +- playground/nuxt3/pages/toolbar.vue | 3 + playground/nuxt3/pages/tooltip.vue | 5 +- playground/nuxt3/pages/visually-hidden.vue | 4 +- playground/vue3/postcss.config.js | 8 - playground/vue3/src/components.json | 126 + playground/vue3/src/pages/accordion.vue | 3 + playground/vue3/src/pages/alert-dialog.vue | 3 + playground/vue3/src/pages/arrow.vue | 3 + playground/vue3/src/pages/aspect-ratio.vue | 7 +- playground/vue3/src/pages/avatar.vue | 7 +- playground/vue3/src/pages/checkbox.vue | 6 +- playground/vue3/src/pages/collapsible.vue | 4 +- playground/vue3/src/pages/collection.vue | 3 + playground/vue3/src/pages/dialog.vue | 3 + .../vue3/src/pages/dismissable-layer.vue | 3 + playground/vue3/src/pages/focus-scope.vue | 3 + playground/vue3/src/pages/hover-card.vue | 3 + playground/vue3/src/pages/index.vue | 71 +- playground/vue3/src/pages/label.vue | 14 +- playground/vue3/src/pages/popover.vue | 3 + playground/vue3/src/pages/popper.vue | 6 +- playground/vue3/src/pages/portal.vue | 3 + playground/vue3/src/pages/presence.vue | 3 + playground/vue3/src/pages/progress.vue | 4 +- playground/vue3/src/pages/radio-group.vue | 4 +- playground/vue3/src/pages/roving-focus.vue | 4 +- playground/vue3/src/pages/separator.vue | 7 +- playground/vue3/src/pages/slider.vue | 3 + playground/vue3/src/pages/slot.vue | 4 +- playground/vue3/src/pages/switch.vue | 4 +- playground/vue3/src/pages/tabs.vue | 6 +- playground/vue3/src/pages/toast.vue | 3 + playground/vue3/src/pages/toggle-group.vue | 3 + playground/vue3/src/pages/toggle.vue | 5 +- playground/vue3/src/pages/toolbar.vue | 3 + playground/vue3/src/pages/tooltip.vue | 3 + playground/vue3/src/pages/visually-hidden.vue | 3 + playground/vue3/src/style.css | 13 +- playground/vue3/tailwind.config.js | 14 - playground/vue3/vite.config.ts | 5 +- pnpm-lock.yaml | 4567 +++++++++-------- scripts/README.md | 13 - scripts/commands/publish.ts | 265 + scripts/index.ts | 16 + scripts/playground-generator.ts | 51 + scripts/publish.sh | 23 - scripts/update-version.ts | 60 - vitest.config.ts | 24 +- 340 files changed, 10900 insertions(+), 4122 deletions(-) create mode 100644 .github/assets/og/oku-accordion.jpg create mode 100644 .github/assets/og/oku-alert-dialog.jpg create mode 100644 .github/assets/og/oku-scroll-area.jpg create mode 100644 packages/components/accordion/README.md rename packages/components/{arrow => accordion}/build.config.ts (100%) create mode 100644 packages/components/accordion/package.json create mode 100644 packages/components/accordion/src/accordion.ts create mode 100644 packages/components/accordion/src/accordionContent.ts create mode 100644 packages/components/accordion/src/accordionHeader.ts create mode 100644 packages/components/accordion/src/accordionImpl.ts create mode 100644 packages/components/accordion/src/accordionImplMultiple.ts create mode 100644 packages/components/accordion/src/accordionImplSingle.ts create mode 100644 packages/components/accordion/src/accordionItem.ts create mode 100644 packages/components/accordion/src/accordionTrigger.ts create mode 100644 packages/components/accordion/src/index.ts create mode 100644 packages/components/accordion/src/props.ts create mode 100644 packages/components/accordion/src/stories/AccordionDemo.vue create mode 100644 packages/components/accordion/src/stories/Animated.vue create mode 100644 packages/components/accordion/src/stories/Animated2D.vue create mode 100644 packages/components/accordion/src/stories/AnimatedControlled.vue create mode 100644 packages/components/accordion/src/stories/Chromatic.vue create mode 100644 packages/components/accordion/src/stories/Horizontal.vue create mode 100644 packages/components/accordion/src/stories/Multiple.vue create mode 100644 packages/components/accordion/src/stories/OutsideViewport.vue create mode 100644 packages/components/accordion/src/stories/Single.vue create mode 100644 packages/components/accordion/src/stories/accordion.stories.ts create mode 100644 packages/components/accordion/src/utils.ts create mode 100644 packages/components/accordion/tsconfig.json create mode 100644 packages/components/popover/src/props.ts create mode 100644 packages/components/popover/src/stories/Animated.vue create mode 100644 packages/components/popover/src/stories/Boundary.vue create mode 100644 packages/components/popover/src/stories/Chromatic.vue create mode 100644 packages/components/popover/src/stories/Controlled.vue create mode 100644 packages/components/popover/src/stories/CustomAnchor.vue create mode 100644 packages/components/popover/src/stories/ForcedMount.vue create mode 100644 packages/components/popover/src/stories/Modality.vue create mode 100644 packages/components/popover/src/stories/Nested.vue create mode 100644 packages/components/popover/src/stories/PopoverApi.json create mode 100644 packages/components/popover/src/stories/Styled.vue create mode 100644 packages/components/popover/src/stories/VControlled.vue create mode 100644 packages/components/popover/src/stories/WithSlottedTrigger.vue create mode 100644 packages/components/popover/tests/__snapshots__/popover.test.ts.snap create mode 100644 packages/components/popover/tests/popover.test.ts delete mode 100644 packages/components/popper/tests/__snapshots__/popper.test.ts.snap delete mode 100644 packages/components/presence/tests/__snapshots__/presence.test.ts.snap rename packages/{components => core}/arrow/README.md (100%) rename packages/{components/collection => core/arrow}/build.config.ts (100%) rename packages/{components => core}/arrow/package.json (97%) rename packages/{components => core}/arrow/src/arrow.ts (100%) rename packages/{components => core}/arrow/src/index.ts (100%) rename packages/{components => core}/arrow/src/stories/ArrowDemo.vue (100%) rename packages/{components => core}/arrow/src/stories/arrow.stories.ts (100%) rename packages/{components => core}/arrow/tests/__snapshots__/arrow.test.ts.snap (100%) rename packages/{components => core}/arrow/tests/arrow.test.ts (100%) rename packages/{components => core}/arrow/tsconfig.json (100%) rename packages/{components => core}/collection/README.md (100%) rename packages/{components/direction => core/collection}/build.config.ts (100%) rename packages/{components => core}/collection/package.json (98%) rename packages/{components => core}/collection/src/collection.test.ts (100%) rename packages/{components => core}/collection/src/collection.ts (100%) rename packages/{components => core}/collection/src/index.ts (100%) rename packages/{components => core}/collection/src/stories/CollectionDemo.vue (100%) rename packages/{components => core}/collection/src/stories/Countries.vue (100%) rename packages/{components => core}/collection/src/stories/Item.vue (100%) rename packages/{components => core}/collection/src/stories/List.vue (100%) rename packages/{components => core}/collection/src/stories/LogItems.ts (100%) rename packages/{components => core}/collection/src/stories/Tomato.vue (100%) rename packages/{components => core}/collection/src/stories/collection.stories.ts (100%) rename packages/{components => core}/collection/src/stories/utils.ts (100%) rename packages/{components => core}/collection/tsconfig.json (100%) rename packages/{components => core}/direction/README.md (100%) rename packages/{components/dismissable-layer => core/direction}/build.config.ts (100%) rename packages/{components => core}/direction/package.json (97%) rename packages/{components => core}/direction/src/Direction.ts (100%) rename packages/{components => core}/direction/src/direction.test.ts (100%) rename packages/{components => core}/direction/src/index.ts (100%) rename packages/{components => core}/direction/tsconfig.json (100%) rename packages/{components => core}/dismissable-layer/README.md (100%) rename packages/{components/focus-scope => core/dismissable-layer}/build.config.ts (100%) rename packages/{components => core}/dismissable-layer/package.json (98%) rename packages/{components => core}/dismissable-layer/src/DismissableLayer.test.ts (100%) rename packages/{components => core}/dismissable-layer/src/DismissableLayer.ts (60%) rename packages/{components => core}/dismissable-layer/src/DismissableLayerBranch.ts (95%) rename packages/{components => core}/dismissable-layer/src/index.ts (81%) create mode 100644 packages/core/dismissable-layer/src/props.ts rename packages/{components => core}/dismissable-layer/src/stories/DismissableBox.vue (100%) rename packages/{components => core}/dismissable-layer/src/stories/DismissableLayer.stories.ts (100%) rename packages/{components => core}/dismissable-layer/src/stories/DismissableLayerDemo.vue (100%) rename packages/{components => core}/dismissable-layer/src/stories/DummyDialog.vue (100%) rename packages/{components => core}/dismissable-layer/src/stories/DummyPopover.vue (100%) rename packages/{components => core}/dismissable-layer/src/util.test.ts (97%) rename packages/{components => core}/dismissable-layer/src/util.ts (60%) rename packages/{components => core}/dismissable-layer/tsconfig.json (100%) rename packages/{components => core}/focus-scope/README.md (100%) rename packages/{components/popper => core/focus-scope}/build.config.ts (100%) rename packages/{components => core}/focus-scope/package.json (97%) rename packages/{components => core}/focus-scope/src/focus-scope-stack.ts (100%) rename packages/{components => core}/focus-scope/src/focus-scope.test.ts (100%) rename packages/{components => core}/focus-scope/src/focus-scope.ts (100%) rename packages/{components => core}/focus-scope/src/index.ts (100%) rename packages/{components => core}/focus-scope/src/stories/FocusScope.stories.ts (100%) rename packages/{components => core}/focus-scope/src/stories/FocusScopeDemo.vue (100%) rename packages/{components => core}/focus-scope/src/utils.ts (100%) rename packages/{components => core}/focus-scope/tsconfig.json (100%) rename packages/{components => core}/popper/README.md (100%) rename packages/{components/portal => core/popper}/build.config.ts (100%) rename packages/{components => core}/popper/package.json (98%) rename packages/{components => core}/popper/src/index.ts (91%) rename packages/{components => core}/popper/src/popper.ts (100%) rename packages/{components => core}/popper/src/popperAnchor.ts (100%) rename packages/{components => core}/popper/src/popperArrow.ts (100%) rename packages/{components => core}/popper/src/popperContent.ts (99%) rename packages/{components => core}/popper/src/props.ts (99%) rename packages/{components => core}/popper/src/stories/Animated.vue (100%) rename packages/{components => core}/popper/src/stories/Chromatic.vue (100%) rename packages/{components => core}/popper/src/stories/CustomArrow.vue (100%) rename packages/{components => core}/popper/src/stories/OneScroll.vue (100%) rename packages/{components => core}/popper/src/stories/PopperDemo.vue (100%) rename packages/{components => core}/popper/src/stories/Scrollable.vue (100%) rename packages/{components => core}/popper/src/stories/Styled.vue (100%) rename packages/{components => core}/popper/src/stories/Transition.vue (96%) rename packages/{components => core}/popper/src/stories/WithCustomArrow.vue (100%) rename packages/{components => core}/popper/src/stories/WithPortal.vue (100%) rename packages/{components => core}/popper/src/stories/WithUpdatePositionStrategyAlways.vue (100%) rename packages/{components => core}/popper/src/stories/popper.stories.ts (100%) rename packages/{components => core}/popper/src/utils.ts (100%) create mode 100644 packages/core/popper/tests/__snapshots__/popper.test.ts.snap rename packages/{components => core}/popper/tests/popper.test.ts (93%) rename packages/{components => core}/popper/tsconfig.json (100%) rename packages/{components => core}/portal/README.md (100%) rename packages/{components/presence => core/portal}/build.config.ts (100%) rename packages/{components => core}/portal/package.json (98%) rename packages/{components => core}/portal/src/Portal.ts (95%) rename packages/{components => core}/portal/src/index.ts (100%) rename packages/{components => core}/portal/src/portal.test.ts (100%) rename packages/{components => core}/portal/src/stories/Portal.stories.ts (100%) rename packages/{components => core}/portal/src/stories/PortalDemo.vue (100%) rename packages/{components => core}/portal/tsconfig.json (100%) rename packages/{components => core}/presence/README.md (100%) rename packages/{components/roving-focus => core/presence}/build.config.ts (100%) rename packages/{components => core}/presence/package.json (97%) rename packages/{components => core}/presence/src/index.ts (100%) rename packages/{components => core}/presence/src/presence.ts (100%) rename packages/{components => core}/presence/src/stories/Basic.vue (100%) rename packages/{components => core}/presence/src/stories/PresenceDemo.vue (100%) rename packages/{components => core}/presence/src/stories/Toggles.vue (100%) rename packages/{components => core}/presence/src/stories/WithDeferredMountAnimation.vue (100%) rename packages/{components => core}/presence/src/stories/WithMountAnimation.vue (100%) rename packages/{components => core}/presence/src/stories/WithMultipleMountAnimations.vue (100%) rename packages/{components => core}/presence/src/stories/WithMultipleOpenAndCloseAnimations.vue (100%) rename packages/{components => core}/presence/src/stories/WithOpenAndCloseAnimation.vue (100%) rename packages/{components => core}/presence/src/stories/WithUnmountAnimation.vue (100%) rename packages/{components => core}/presence/src/stories/presence.stories.ts (100%) rename packages/{components => core}/presence/src/usePresence.ts (100%) rename packages/{components => core}/presence/src/useStateMachine.ts (100%) create mode 100644 packages/core/presence/tests/__snapshots__/presence.test.ts.snap rename packages/{components => core}/presence/tests/presence.test.ts (100%) rename packages/{components => core}/presence/tsconfig.json (100%) rename packages/{components => core}/roving-focus/README.md (100%) rename packages/{components/slot => core/roving-focus}/build.config.ts (100%) rename packages/{components => core}/roving-focus/package.json (98%) rename packages/{components => core}/roving-focus/src/RovingFocusGroup.ts (100%) rename packages/{components => core}/roving-focus/src/RovingFocusGroupImpl.ts (100%) rename packages/{components => core}/roving-focus/src/RovingFocusGroupItem.ts (100%) rename packages/{components => core}/roving-focus/src/index.ts (100%) rename packages/{components => core}/roving-focus/src/stories/Button.vue (100%) rename packages/{components => core}/roving-focus/src/stories/ButtonGroup.vue (100%) rename packages/{components => core}/roving-focus/src/stories/RovingFocusDemo.stories.ts (100%) rename packages/{components => core}/roving-focus/src/stories/RovingFocusDemo.vue (100%) rename packages/{components => core}/roving-focus/src/types.ts (100%) rename packages/{components => core}/roving-focus/src/utils.ts (100%) rename packages/{components => core}/roving-focus/tests/__snapshots__/roving-focus.test.ts.snap (100%) rename packages/{components => core}/roving-focus/tests/roving-focus.test.ts (100%) rename packages/{components => core}/roving-focus/tsconfig.json (100%) rename packages/{components => core}/slot/README.md (100%) rename packages/{components/visually-hidden => core/slot}/build.config.ts (100%) rename packages/{components => core}/slot/package.json (97%) rename packages/{components => core}/slot/src/index.ts (100%) rename packages/{components => core}/slot/src/slot.test.ts (100%) rename packages/{components => core}/slot/src/slot.ts (100%) rename packages/{components => core}/slot/src/stories/SlotDemo.vue (100%) rename packages/{components => core}/slot/src/stories/slot.stories.ts (100%) rename packages/{components => core}/slot/src/utils.ts (100%) rename packages/{components => core}/slot/tsconfig.json (100%) delete mode 100644 packages/core/use-composable/tests/useForwardRef.ts rename packages/{components => core}/visually-hidden/README.md (100%) create mode 100644 packages/core/visually-hidden/build.config.ts rename packages/{components => core}/visually-hidden/package.json (97%) rename packages/{components => core}/visually-hidden/src/VisuallyHidden.ts (100%) rename packages/{components => core}/visually-hidden/src/index.ts (100%) rename packages/{components => core}/visually-hidden/src/stories/VisuallyHidden.stories.ts (100%) rename packages/{components => core}/visually-hidden/src/stories/VisuallyHiddenDemo.vue (100%) rename packages/{components => core}/visually-hidden/src/visually-hidden.test.ts (100%) rename packages/{components => core}/visually-hidden/tsconfig.json (100%) create mode 100644 playground/nuxt3/components.json create mode 100644 playground/nuxt3/pages/accordion.vue create mode 100644 playground/nuxt3/pages/alert-dialog.vue create mode 100644 playground/nuxt3/pages/toolbar.vue delete mode 100644 playground/vue3/postcss.config.js create mode 100644 playground/vue3/src/components.json create mode 100644 playground/vue3/src/pages/accordion.vue create mode 100644 playground/vue3/src/pages/alert-dialog.vue create mode 100644 playground/vue3/src/pages/arrow.vue create mode 100644 playground/vue3/src/pages/collection.vue create mode 100644 playground/vue3/src/pages/dialog.vue create mode 100644 playground/vue3/src/pages/dismissable-layer.vue create mode 100644 playground/vue3/src/pages/focus-scope.vue create mode 100644 playground/vue3/src/pages/hover-card.vue create mode 100644 playground/vue3/src/pages/popover.vue create mode 100644 playground/vue3/src/pages/portal.vue create mode 100644 playground/vue3/src/pages/presence.vue create mode 100644 playground/vue3/src/pages/slider.vue create mode 100644 playground/vue3/src/pages/toast.vue create mode 100644 playground/vue3/src/pages/toggle-group.vue create mode 100644 playground/vue3/src/pages/toolbar.vue create mode 100644 playground/vue3/src/pages/tooltip.vue create mode 100644 playground/vue3/src/pages/visually-hidden.vue delete mode 100644 playground/vue3/tailwind.config.js delete mode 100644 scripts/README.md create mode 100644 scripts/commands/publish.ts create mode 100644 scripts/index.ts create mode 100644 scripts/playground-generator.ts delete mode 100644 scripts/publish.sh delete mode 100644 scripts/update-version.ts diff --git a/.github/assets/og/oku-accordion.jpg b/.github/assets/og/oku-accordion.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f956a0867279f7a7087b5e9b0e6d3a139c451e5b GIT binary patch literal 54701 zcmb5Vc_5VE_XqyWm|+MFhHPcV5@V^xPGO9F>@x_FeJ3H6LW3cDGqUg5m9m$l2r-c* zYpWI_X%!Vwzw7<}d_Ldb@6X@u!FV3eJ@;&{bM85}zfbAUGe4jSu#> zAKJUKR5%-KXTkp&FgP26odb#D1dm0)gPrl;XW-|~W8~jw5H}lm5zU4Mx3)^UeC(?6 zOtN=;Hp?C^NTxXp%3UqMEAI@8St%(Mz>oAbvJD#51c$>7Z|+yXDdI9bNyZ9sDREK) zVDKNn_S3N`rv36ViA?T*B9tddn9}Z+hfpXO%2Qua+K6u9V3%%jlwb5Ax*v?R6ch=~5KJe@ z3+}-fI-!%WJ#<3+TNFh2KT2#_L26>7CKlZ3aZHQU>}n-SZyi#Ol$Ba-ZdY%Sp6c!G z>+8KU?5^oEun;YtP-mK&>m)-r?U+h75bRGvB)fYW{D-SFM?);-EyJ7>kV=v!BsbA$ zXxjg1i4vHD>zE!1!#8AcV202b7p#SUgHk2UluNfIbm{gYv|r>vAO7 z#5|9_$kVf_Uzo9AA`S>( zNTjM3+>X))Bzxfy_x~uty2e9Q*~%s{)p&-oJSDe2+qt~FP7a=!;*(~8wHsFym(=-Y?jZ^+1uyUfOr|APjEEb~wmloiP zSAS4zn?MGns+E-}*?>KLGRjt;s}6||9#ZlVu}@zC4y9SGX%GRL^M@A1Hr=U`@j!kSb>5Wt0&F$3 zOQ|l!%Hqzbe$xXD(b;z|dJ78dRe*bO`EWs;rY|}r(dhW6fF#|~I|#=qJe6(+H0J1z z@}#0@bV(LmQJRFN^$7PnqKN;oL8OK#q%M+Qm_a|pZlY0x#4~NElBuj}WjT5KYBE%D zA}>cQWMO}+eg6LayRyPFTHO?!nsdCVQgZ)TK15HyCQPRc#UrpdR1;=~1dIV#$$s2P z9JfWKt3I0U?1fgu5|Vc`cK}O=`Z52i&O1aE!|GwZ@CWpP_Vj_1VOVWDLajzR7xB8> z$yiSfJY=CDfVrp@xj!LbZSmgz*5X}G#>Vzkd;r-7LMXeSo6PZO8XfD%NHPcVpvXD%aBwCok+=J#OViEspk{VT))MKDr>`q-iH2_tUfx%q$2H(gL zdzj)0+Vj$f4mC^`9Nagu^z2f3x6QBm$%aRpR5zg|d83BQQc`l&%C)|051O-qDkUnc+5rJez0$jSUe5fTut-T>o)+G44260ZY@ELvCqpP&^S2=qgbP zFy+t60$tVJ-QB}8`Ep`ZZW#xSrmrLyCtQ6xlzGk9Z-3Cjin_9pkBEb@ZD4_sy(8q9 zeMzkqe{gE1rUJ2q4+vAt5>9^MG=6g0l&F`U`lGAfRs}*PEwAJ*D%P$K^Vm#wL>u~2mi4AnxHmgh&PsNIl8fWToc%om1B z;R?c`ydWbQKubdal#=)PxNtD&KPCrAhg-fpq-7$01y6Pc!lsl_go4<|eSJfuJA)^} zCPkeLci9o!nUvlZWxSWfr8P(Sy;(l zP9IYmswWBOtsrc}sQ-UqJ-p1IhOk3UPH6`?K%iW&9&NHr%yX*g_~CAgwUw)q&nC5S zf)s@E;HtNi5fWEGb0o>pk|kMax@*$E)Y8qp0Sp!trkn9sdA3Ww)t93q_)@s_aly%H zwi#F=4E0}`N>FRmiZ5r{WS6%yfhThnh|Qh~Y_%n;-gZXpQp9tKpqw0PNWjvKuHHsf zBiW5%)u!3W#4KA*8}pPV02&}bnhsPoCGFpD+#;MjP-VmyrZ0m=8zZoeDel;M{&*P* z@qdsXb3A_{JX4EgL&}Wtc|zLv0d(|PDI*OM|)1Pql2-LVp}y*PQ^9A)CjG} z3{Ig4_0uzwszD^<)|V0;pcwVD7R335vjo$P&;S_Yp&mLSJCQCV#82w;2}(4j{fD*P z^^>BRwu4AAmveJqHs+z6GG3p|MY-aWd!GogjOy&Fb9{|lDy9`}9gr$6aAJ#wLxN0? zT?#)F73^w=r{PjyfSe<@Db)$(X-eM(!ke@{8zT&b5=1Ar^H(@|AhDkR72{?Mp<$sA zM$Q(hw}=Bkh$nN&=gL(mL->0?f|aj+bp-HGg0U4 z%b?HU%i56uZyp%p5K8?_1h*-QC_Sc};7Qt_y(COek{rn1|Vl_ayT zAYp-U%6K!9%SU+j{XN(7qml2<52k!6toJMcfJ9pKgm%GjB*VIT8Pk!-1i;5m(h0|*SemC}DJz5U+y;;vxl=t~L$+`uG@4P5 ze$tstf}@4wg@@ly{3#yj)(F+L_q)C1e2Hf!jG*Z0Ss@%Rk4sK0)_NEj5t(f(*);d;3VM(`b=>!SPCrNXs!N&?Yvvztuo106{i$#PsJ8b?u2Heq=ATyt=^`hy>aEQO?hfufOz$XRbK3egDS=cMp6&2DzMb(Cf7UN6NH6c4 z*F59Vwphmb#mFbsE0J$R#}+^NacvTQQ%nCsdMnyvk40t~P8+RJUnqal2JZBCc@4K8 znH!rFo?YZwVg+wgGyL_GLJz$g{@k)@9IE+YDI+i{lJRNvclNzKepQ2&=1uQw-~Hpp?E6yJ=)AM5H;Vs4 z#MPCTt&Kx59p8d$uXvXKj$}l~aKJXVZ@`_O(!_S;|x%aLl zQbCXX)Y4$%Q}Lf)3TBxfqU=jk_T4Etd$aY|WYp%;+TlE_%YH$5)lXj+mhg`~>-sr( z#N6rE(pB@HK8q5$XUg`r{ut00)DsF7^IE?b`K)^NnD*Us(sxUbNKprafhf{Pel7KC zE0x@EGt}nWxYvWgbxeRwlyE(lVN-VEzx)9NVCH0_G1cQB4Z<^!cpEM*E=V6Cw2PxP zVtLu|)naT{tWL>a=n!E--9NeR8~Ib} z+vnO4J)_07XIm5bm~FzrlX?NrUk~z z%pvrux;jsp>v692OR|1!y_$}iT^Lz;VIdfbi-hFc#ZNB(Cy$XhQ=?I{zjG=?eVV{-tR5&Q zrz|H2q64~u#@}G3SXJ`s@XykiLuzR8gs_c-TN2F&mp{8kpFjKUM1J`Y-r9Mz+b&{x z<5JC$P$vksX&?$5j&jf@-`Iux$g)Rw#@24-S&u#&o4>w@{!_HN{O)F~ z@N%+|NvrwhZ_%x^u3s5tcbf0X$L^WCaMbH~<-ymVmKJLicDv=Rv4<{(Scz;JXnfx; zJx)9ywUYJ6s`U*VL)+Qw!?-H=;Z^_W<#!3;#%;W%uW{iEU#i$44Pjw;e1T)Ood`oR zl`&0(XtH;JH$^mxERVr4n-iM@8Vef%!__}dat$Ql#&%!>?qJy zd)aUdLI1t-=4dD*Fy`0xt+Cq0>+`EWTH$|6N4wp;+>*7u20#8O3SZtBblM8&O1?mf z`qE~;HNSExGs`iyXY_8P#wz>wH$P&(5Bz%g%RFkEqa-$NWQ*Cb5tDyIcDdn|S>CtB z+826ei+`e9hfS%^tzGk0Bg;?_3PM0^XcWdU%~LXq2H4ZBJsy2o|Dk%Z^FpDS%Vp;f z)dDddY_*adM0wDpFGz%l5mIY^p?xn+oWAlFXXr;1ch_!2zLh9EAO5q{RmVho_2vA~ zdRa~P6|FBf%{L?(545{o*IHYPeubDjGEDtw@*zF+lld=|Mm?tAmr~cm^zIyuOG;Dg z+^?QEzUpt?csMEkee=R_Er7^xr@trb{M6pA&WNfmyS!d@G4$5rpOlfASe?0O^|85M zuLSitLiNJkp8uNV-s)~xr%ucK*oYn&8}2-Zh|L|@`sSC5uDw}$=itJME*e5t;Q_N) zoPKhSclJN<7<0VqXQ4%=STVqa7y1uW=>TI5f?0OMhq$l@f zUr^nRk)q!wrQ5u&i{C4s{z^FeLGZ`e@?YpcSJwh3X&TD~7_4W+r5ZjpP(mJrO{+%gI6I2pAHL3!q7pobfhh zrq=}hEw44y5K%nnY!uNTpo&II!qU>P2K*w0k4#PJlF6RU(neWA|CFFWY7HpC6%j-` zIRpg#c8z-N-0sCtuu-;LOl8gat7B$DEKq<7DU|}J$L~TnvHIzX9>HYE3CYI}?8beN za}z|0w?%Ott0b~x=~!uyqo6lySvXFx2?sl6MKsO=wZj4FpW-XX7@UOqD+B>Yw~n7` zCx=X6aug$5VOMx>Im$m9-28owf1i4$@*QZM|FZB%7rMr13E z{u->xK+_b{^-(C2NMQ<}C$R(6aOxEaG-VtODu+mb)Q-B)oDMSl3#E(ZD8v7y(b{I8#3PVL2?M`xiQXZkNNY{34zJ`GhQg z_C6`+Jq&;Lg(>({Jq8FJjvgWqC`R;T2#>@%KNYrrSRMZa=U+uOgq{RqL9LA+0SaNh zfhoaR$Ruf;42_urV-T#ojY&Z{+ z13ONQs+Rm>DX&i^(IBMmR7aUGmhMO-?YmgCG~)^BE}XXiu)Mm0b_z+Xkb{0BbqTP7 zhTkn;cb9ofUu7nP>R0jIRPC6OT8(-qAwJ2sW*#VRPjuqpxXEM)2(+9gB9^I1P*E3! zaBzmtfhlJTsa;_--R=|TA7w=H+&`pb1D1jB8n=7InhF-A2s~`1>nC$TY)o-! zz#Swb1wlGPZms*6YV)FFrmas@Zd~-8>@uHGT69JY-(kuHM(M~s~h`vFJohAzqZJGj$R%7Ij%5Xk$x@NP;J{)8Uzt}^~+y) zQZvJOH4!+=2ZZ#%-YO$H$IQy;^p&nBVH3G^z+R|~upxn{nyI1@n1dq*B65%UF5SJD z8p;=QQIz$OFnO)DjO;dcXYksROY5n#x!+&-e4g0$Un=CScA>VJEZtpt7IDKRs$2D_!Xy#M;rnw&SP__p+Z(c_?rcgv@JO;?qy)(=7- zzcsvH|MqcR{8jlq9lyHahF|qRk2Vbs9;vvyPxt=dk%rB|pd)qbHi5dm>r)>GlL1R; zu@P*~f_Owg3Ju@(sJ}mnZ&!%nRc6Z1^ASnLLvvvQe0JpvqFhvlKWE5XA?%(jhE!F4 z#SA*WQPlKYoc#V=>76m>k4wXokQCWcFV`IHZJmjT zR-iTwKll33=wemmCOYOY+H5V;=$x0g_xLT*v5F|8^RaG6rlQ#x5xk{m4Cvt_CdV~O zq(!o4AOup2L;R7b{{$NFg|$wT~)TIa!xb5 zY&g`__9h|kAn$EL!^~;T6}3m1JTazI(Ua}wvzK$b64EG|k(;u6z9-d0dfnPf{LO** z$nk)nd;cpr{EAC`ta8kQHxfrCPP4r$wC;$kd@rt||Kn(KA&;#>bkiI0R+#v^*EI@# z*0l#X#&nx|G?z3d0#r1YUg_c*04Si@JW^;HjCbg9_rOCCied}t4xTu*P`DCSUhhvS zPSDq9soK=vJ%yd~=i+qdYKD1Y66xfQWEzdhO>{h=n!GoCB@vTq10x~;Fi3vPuuCSA zKpjoQxEfUbI2FwlaVQ&aPZT5vVL(uVc%AZ9MNyyw#8+Z8AhWx9woLA^-GV6pG*Uf# z#wJ{Wv_~p1VAow$q)dF45{YIi1jh^@uvh{%*HCK^!RFC`!N6%;C|J`JQHGAiVsV^o zGBb=+0!|Vy`PLMT*+DxP5g;mDq_|JTtYN1wXYPa4+E1fKx_c%rfI9QTeq6pW%iw*P+^4Lq6>ZCsx#RZ_Qd*1l>D6~_AIvy)Oe?Kp z`A5wA>EqXnHi9A+W#1oJiCVgOdLuUAr~X%~4e^N8f!b>(7mnYf=0<%w{j0`yI_R!> z(`#8;;F;y~>0PRy!?)zWdoSC6(dufgKRS__ea?2uB4hBuVOZSE$ib6Q?8C5H@7`@kj*Lf`cjNEq} ztA_J8BWD{v2%Q^<{2n}W`u16qr4ql#)$0YZ9)p@@@0GX6Tayi&*&QV}-FRAOWZs{S zO&|F#|F)z;Gw0>I@4GZio*$mN7wNZjN9VX(`I(ehFD_uacgh+i_-wkOC8T=tpo4M*_*xL=LjX@+F0WduuSxnJL&|k4!IyY?`Ylr zTm*#pYIB1uf4OU7E2j9437%TH`TaR_0TvZGk?2oP??zBR`)+TXlF%Kyeh{fO+_7QC z6{)@6_57gF80|DZy3gsCN#pQ+u+g47ZuZ0L?a`-~4o&^4y|-kuJaYzr*0}AX!jeOD z<(GXSu8Z2uucX==^JLGs>9%&7KdBs1n3jqfR~WiH{_e90GL{@w@XA`d@%%{t@8QyQQlGs<1O=O*KfQmSDM9hBpUxE^GvP#irjiACDuVVZ!n7y_Yra4A-r?nxW}X zjWO73>f14lY*f_ZA>i@xI23oD6N;11J(&oKJRrG&Ad&1Tu2A-P(D5xw_J_nqeBoLO z1G-Bm&Rvtte)Qs)GN|q2@3kd>h~*uk#esU}kIkr7g@`bip(l}~$X8IsghA}SE%A)< zL|8C{YwDpxbS$66PUeQBr`Y?Kbpp-0@=Wp zDI3=lTg+{VxTYUb(W#>#N0Ua8;C$if@`_m$Cw?Z#4)K<>2>>)~Csh*yEFp**U}@Zh zqOdXiv05DacC7=npCk9zOlnUQWgzjE>s2p;2`h&wHPJcbaTu;bjN zN&Fzyqpl^XjeqDV+kBx`5$9lA_X|Bk=d#-0_KB%WT#SGrb*B~Mnkqcs< zw6$CkN3-UFRO5m5JoDz&N8YCN;y4SFS$&^t4<4Uf7H^Ig@kaId$zs=#rB4)YE&e zt{t0U8W|Eh-+skM>&yqOhkwMD<$XG6Y@Hl(d^|SDZ;d{yx=Ouv)Qt};5sm?A zG@i?w3M%9Gc?L|xX=sSc=7eey4k!mW#Aq?W`lx0z2VE|sZX49I&Y zK{)(&&7d^8Z8MEm>xoxmL7D0%du4IAyyhYn4cInC0SSeu6^IljhaOU6z%kFfVJHKS z0gd95Jc+I*Q_G`83Ss6pQWl47ALQ_yxU$b3k`vQ+5=S46+0a4T7~zf!=&n!wd!eBm`I055YS6 zpM<3nPvq8W+&Ht}zOLP3RKMI7#IkTM9;jv_L4SvJG?8wDDOdm&JY(;3>Y6Vi2SAUW1p#1zpeG@!g*`~O`bn`2eBd=9!H-~d^O7|zW@Wcze#bf@x8l>$wGXXFR?wLLUiI5#-}2VvP+ z)268i1jO7CnQd}1i6J6P1200--03?!GODIbCGFE4AmDKSZne}4GZqt~s`|w*o6#WJ z&b=4D?m-0IneP?#lxeW{uml~>P7pL?G;UNqp%QVBWELlp1`I{U)DU}IziJw^A$PJS zFm})hfq;2~?#AXIi;EP;-vCvC_9O(cg#mxn;t&?Ute*@4>jXiW-MF{-Pr(dTCD6+; z_oDQ;_X#!3P@X7TY3Izv>CM zr8n4E0|ZBLLL?&Hai{l-=c1w^1dVFUP% zD)!50h+}*%iaiyBSr8St?3_vdV3Foh$tESh@W-X>jAR^Ep2UPnis#JdgK7Z+2f@5^ z0zHv3ShyU{)Gv>nE^D_1ZY734#g9s#H2fUlKKQd3}l4Gu{2);PqYf? z*LptMXQRXgTG-MP;TdKvbEm>BsPgZMBLoE41O%M8QQRKKeEK&Y!jp;mI|l=>O_0F3 z*Fi8-EV&6{5`_quI0UK+`B25x6p5ukTo5AlUt?#4wBFh5irM|}(|mCyTPAF?XR8#} zH4n0f9$>#(?0)FYV)Z*|ue%IN1;Dz+8o3qm{G zUDsGUoENuw@^kHy!Ft$+;%C`FH<-2i5yT7s9eFP>=F7~Zgy9Kci6=7DSckOOWv4W2 z=ATx!49_&IEXl2`uX%Nuzt%3jEy}YpV0CW&)OY76e&V8_q5`%vGIYSLDG|Nc)2n?zY)E8wsCZ`TVbzm!EfxAY3%2`Pi5j;1i#NJ z-@-QYx6XyJAc*$p(W7|@^Qa_{*l~z2%p~N9u7t&{OSy(yo95-0iO-$Pj60quK3OsA zlc{z0+@~xR?T}zX=3d0kE-owGj%mt8L}DQtBqLdc!PyXZSgaEC(C1>Bf^Z1#17m>B z4c=P%@fR}554Ilt3n_d$p%*i}u{dUT7&l$X3R*{W7dNa$e7g@eoCuA=jDdvrUo`XM zv*!%hJa`rhS&Jr*k1K~~sDXsk)7{;GO@JSAe9^7^5DiE3c_+q&aU!n4h?ryw0-lA$ z8PcwTZ62V@g#?13l_&1l2UvrIb@cXMXnE^W`(z%+;jP)#*g28emxu4alRc^J^o-l; zbCgFfSSc~wiqO%s^hHxRzWA~}OLI^zNm^a}vOw0I5wrG_7T~xhraws`l~^f|%E0u; zd2%WuaEPY*An)4_wgdHo*otyF90fvgt6sanMpRRE~mn&O;#}h}WazZE-|E98IHeM+Yz% z@WF-R>wcH)K-lS+3Sn;wx0Jy@RK)=Q%%}nA0$xg$MiIG*xolzA59J)o2+P3YVfyf$ z#p7W_9Fa(YPo+XKST0gLh6wDg3e4KHBp#9KOSSEqap$S&(@$xdf}o37;CA8ds*iPEz^_47PSX4tB8PsM4r%UqzAlrXD33m0jx0~ z8w4KwZXk75t&jz?OzMlOdqbmU8K~GY>w4zgPiPK6v=>3Ru9x+7a|Rx%Ue2RtJcdxU zii#RA8!7T)u@8EVYTNATFPSU}N72_xApD; zG}knrOruMO^SoyDyvYYs0xH10Aui(Y!TUrC7X+Wm2DNZ_1Bt=aBy9uDxd$P&rhAeB zR9XdqYA5Phu)4T19L@mAFr*CfpFwce*d0VAIM_q5p?Ek@CvlX^GeJ8$3g~F&+v$^(lV-Q8d<$>_UyN_caOjP&Csi& z)gdAKt)|gQdf&t(II;|ygb+**_7_?+JIwc-H|)L1?Q4H_EnIk=7>q|R?MwQ}{0kvh zbq!{}wWv3cF5NUJJ_l?5wZdH8XTpk-$S8a}z9jW+NowK9CB~|rm{gpzM(fjWOG<1f zKK)T1n0;5vTt#+Ox6#px8IQpfF%O;j+VFb&!gUQ;8@r{xHT@L0vVSNsO=14t&U84j zO9Oo`>}ylHO@FO;2e$=3Zc#TLt$Qk_UFjX1!EC*%9=Y@Dw3w8iU2tU?2a`BR&!cSrj@@^}|#*YwGk`%#}(y{qcv z=M27DC!cd^n>3dToFEhXoP(q7D0 zb>{M64|7UKw&&aDm8!mJ%bpa^!0pdNFK!j$W?VdaFEXkW!)?a%L#w#X4W!o|IC0$Q zt2JXw0H{96p@NomPU_49==mv^gRy-`X`-!IrREW{H0jUkpVCwL1{BX$p7)Lr4Qas? z*Rm!R-|Uf+TXA4Vf$?a3;B@hyHNAuK+rK6tqtK@F&<+3v@~KHU-z|S&d9YWp>LJ}^NhHq#J=DCcEkdTec)Be8sZA!(XtgfwOLLl0rJ_&z&9~|qUI9z7ER}tM#Q2KJ zwpTsx7NqDKsMikJ(h6$RLAymfLt9VBb!>%~W`*a!IIk-Gq%WcR!oG7BH!nEwrbcXk zqUN2+I(n0j{aJg4zOH@j?@K;5 z%3sA@FXclI-C2Y8%2Q-W?Hjii?w()xP4}+W(U+xCd5PzXO8zLGEW?R&eKV3dLd<-{ zeL`>V`HC2As~MBZ!QpGlyP|(4OOe5u>lItu6@q^oqDMdN%Hb(p|ASR0Yqp!*oL;uJ zaLPt?=kf|HNW~q;&*rY)cyqhGOD{e;;Z)=ng(s6GIi$_k-oASuPe#AY)SU<3jkCxW zDMf_9Ud%T+)GYP{;(q?o`%UrH3dxt}H==gm*ClBR0^w|(zNd2KMl31MAyZKJ%SG+M z$6R57Z*5m~cMO1$iHt(Mh{d&U)sO1%gz5=|{{x&S6SKRV{Tqd(4Rg0B5PLh z6}lSe;`?f?=9=2qd;5XhX}|QP?Cgs|ew|_q`p7=tpNS0EzjShMspiQtv^w+VT$#Wu zR|wpRmjmW>*mn9G`PV4@eAcDhoR{V4l^(C&M)~`UP_IOhuCwlI*y_ZBw}AKn`9|Ez zuWO|V&E>lL-(9)sdU~^F=1%BP(O{J2g;t&IzFh$!3JP_U-IT;G|JO$W181N4Xg#a? za{f@2jJH7K=S!@VztH4xjO^Fc>q^fIpE<_(Q?k>yc_)AQ*LpnskRUoGqtteND8lD$ z0M|lqDd4qFB8n+mcTMo=fXK|f_DYuO(xrfeF3$~n{`_cC^#)jKVOL6t16*fel zbz{_Rutw2y;Qw=Z;c2{75epwe_PQ~3TEyDUN5}U6^)biwKfl>eiwN#qFj@JV#`cjr zm(hPO@E&;l&#R38%;LUKHl+IJlWi`c^Fo=^hURZ_in>AT>ZjX1xA+C0o<(sA!Fx`? zF;6IDy92zv+?J|hB{4i-WxRQZ2dF}X?4563|97Pc-~4s!f$96Ix(&}Po8T*6Z#Rr| z?M9~@-J{h)J}#kJD9{rGo4{(txx%g6TW=K)N0H)4srF47^t1PSqs5lH+>Ew%y%*<7 zd+_$#+3w`~$2XY2+l?IuFU)@2Dvqf(VjbqD5-N2*@=;0a^;>U|y%qHtBFZ05hJtMdHJ@ z(Rxm$S2Y@CyBps&MwnD>*|Hu;QoHIERZXmBj|QYRyo#c@o!(ZwX&sQFl}NT~+0?jM z?s1DPquO7CoOnO|m~3Me$K-PynOxmQ1JsZ+(zZx>fo8;dn{&O_HEp!@MPz% zpXW?gk|wfu;c!ypxTj80aREu9r(!!{skxlBRycC@-VZ-NoZRNKtFnB&xUQ0XtFXEE z-UwTqvz#z&c}ixpGe_~^3)Z|I-PEJgNJr}Kg3}{i(SgXA{b;yR7PObNA3s4&X?sz1 z=T%p*aKlecMiD8`Oy~VyDBtqA-r3X)^}x&8w*!8dTh*?tWj0r%sA*&|Gs;vjKQr7j z?!`zb?%-^0P269o@79Cw`B{D0h2bH1+<~-c$@HO3t0yd_px~5eJj&%+veGgywu5tz zPTo^^ITDNvj2q$ z<$IU+P9m!Vy4$+%7;)?xJ(^e09hf6-eeLr~>S>;;$?*e&(RY@*CpK@9ghR5Q){MtZ zG5upLQ~oF2dq`$6DG#@m14;t0jY=+43u^PN^Hx$)bs=tgHOhmhB%&>%^MN%~d2AkA z(~@<~3NSy$DX-)E*r{*r;W^Ao!$Z}aE7h2~@JR*vt&c;FIU!cs*B|nk_k2`kbXKh2 zh|Lh=*Aw%4U9@M1LIuRhAh zrlLcWA(>VC$8Q#$a+;FyswaK3?aiY4?|0Jq_){`dqC(c&t#$;TlKsv(Hep!;Bapvk zYmohA=#JbGl-BeURnO(S4dkjno%gm_38M+#5bjin94 zOugK&D8K65!IknQJ|*R;NzQYw{T&lO@v`1_%MJGRD>(&Xg1t8abUbCNN@}g|DiA(r z$PZ2~$Mw|pbOazduUSiJ{MO$6Yzr&g@b+rtu1e*W+n2`M+uii+4t6FKY+ubUE^GMl z%8v58ie;%NS>C(w@Yk8q{D3L#373EXXQ2Z*(l7Xg^LDo;2gv)Jwir@x`cilS6_qpm zNOSlhKCB&6FUu4?T;?G?xxln5Gg+OTJe_qxwXFX^#of$hI}^K}5e?OnPp!9gYIaAS ziTDfIOUZuu3k}KT|H(NTrKnR#_BfW%CHu#JSa^wRew=-1X@$~mTo9D+Q<6p9MSNt=sn|OFm!PKQZL({qn+kZbQPCq&wB8w(k`^-ZtqTlKYifa_p(x=c7qKT%Od0 z8|BG56;fjEGD!CCk5nq2G5iU3PShVc6?DDn&d`UCKctiNrmtT5Ecju3(mZNPeS)0Z znu8aAWFx0!>kU6GAK>-e<@X(&1KG?L;#qiLB4$RuZkioJB};H^P}h3HR==+;2;_}_ z*)UQ-o7rzF%3b;L_+T`j@J+hNpOe`phGi#A-z`6;Z-3MH&9&}-Lchv>U&-7GO**!Y z&#ph~)PvlcUB7;z-aYeLefjaZ^yARyslEz1rKeOk&%d1PTwK=EO}DiWn9Avl`?NZ* zshjE5*P=%1U%K}tIoJ#TfdFD1h=SCqSm|C$fy~}Qo1fGiodhGRpzgp+cfZ=*xa6lK zObNAhn7aHpMtx)8Jv-r4X-!QE{p-DwyOjq1)EigY!W5qvrng8}IpqZbIVy^?|7Kx`)u!FvV!8myB^11oQu)C zOPVQ>&VdWEf`z^or;aXFZdEFJrJ7c~O=-JXq(bmxe*d!h1rxfmd1mO?Z<3P0K{dSm z%6Bf-q?tVB5>|@evwmpn*=As%>AnQEnRBDXoaX$(_IbNIM}7x(_pOtLj)j;zagvQ5 z9;w82$<;mY_5AT-0waS=Po(@vC#FT;)J z&GG57UsAfC9KB{NSzj0;mzz;`ru>pg4X3>HN1h99+nkqVttC&d3lABCW4t8HdbN>PAFVx?a`xkofM*{BP zVs3V*_lznn?mKFrRsQn*%PS`Q6Jme9HE(w9TWpmPTrRjk`@U!6{$EH1sL}dfz=bT? zPmM-wa}QJ&SicD=IxAD%CmS8op-~IXm9nbnO*)^T-=pck$oa2xM;m*3tp7stC0<#K zJ!=={Q0eY7jr`gdOzGD?|E@&+o)7&Y_~CIzao5L6m#p~4s=rV!bM%nb9}`VImve0n zdX0X6q1bM>{*BbLubqyii^Was;bY3bIizpFv0JA}@vLRKz|y5sjfO&F zO81xWd?d-7m!@|&bQ7v0H^Q)4mwe<5k>=f~6az8o)D^^VE!zqfw%f-Ep|m_xME@7;3e%9Bg=1FKaN zL(Q*pEPp-3uUE2vcCj_|2Dg{J8{e^!0Leoa?*R+;I9?}7Xk71;==eR#>=@wqlcx1M z;IZSMyJg#(+ZP0?ojT<+>g;gCUlJvM-|BG^6TGR+)-KB5qVb6@_qUCg&JRU>6V*>> zjG?RitET&wcaFH;bQ^q=V)e5s2%{ZZ{7OTP5|6g4^saKrcQ{Zq8m>HW{3yq_OGC?J z6BEu`@s_|+F;l*iHBoMIlDe!lN<)8IZ zk8c~*i3UNth!o3-D<{49CP$1@+u9C%H*t)ctr$FT;#iw4p4?zBpeV=`l6U?TU7`2* zx7!!wUufblq}gxBUg!Eb+j39tryoA;v~)FXX##iUkh*d7)sKg^D-UDF;cAwS9aOUUJ*g^6 z;HOHSFv^?U=uZ#~u=-P_JJ#1e`ApS=+?0R$i|nOa%GweQWvevLc6$HwYx%X9E7KuV zldz8${Ug=weM}!oJTVTnGkcR-^>%PIeCWu}PQ4|JwgSeW=bk zX_w1G3hQn@E4=q~Sf@lKuCc}0(pTae%~6qVLr+BhAG+Q;s*Nt}9;H;UQrrtIPJ(;! z;t(_hcc*x9hvM$;MS>(qad&rjYjKC-{@%Rr_xpW++`H~tXGJEH%*>gYbDn4K{p{#o zz0}$h3W!FmztQj}$zT!7TpeLr*$+QtR&$CF?v-zND6dr&E-CsQ(fZKeRB>fJ;dQ=& zuPsrBo0V=UdZ0aiozs|^Z7wFeR0`cTD_oNT&Jh9UJkl2$bJR{|`LOa4=r(^U85lHa zm%vBd!@Uc?Qs0FbK6tWHkEsgwKvc%rIf5ET0)cVmy^?NCgS6=&-6R0N6>$$dlIiOp?c!+uxu+?_-g92eIXG|WS0RJbFC!qBU>%XWgZv=j|n zoff>lz+vY&-n!)EWMuT%LDXt;$SXfzK!jB{mPrLFy;kDxHU7Pf7jWs)A$mLx9%dF; zqSC+lUK~=`rLYrw%P=ix6{-ENVoR>WO5B7i2bp$~&XPQif&U<>-&vq&Z0M`16iJ^% z*)6N~5 zJDid!<&%N4e;HrXTF(9AgahCieM*FIz{h9ufA@)rW<+HyrYlUlQ6Q2Gpe9u*)gO+) z_Vmm6Dgz1X4{EFWV8!%~BWl(P2KSG(hvtkV3Jr3__P6;{k=C+BGm)5#_o|)0o>i~+ zyVmbBo`Vsi=fI~0Cg0ya1;z1ESkHuRH#7NF&t4*r*AFiYN==L)MI$>CX9jbH{%16` zzGQSoy;oYn$#41O-(IK!2#=?}S1><+mu$^M16kASr3+*bDp^iN6xGdr?)+mUqV<9j z9Athu)W%eBD$U{bA!57OW8UEFZ>{m19!&mv3c0cpXfIg+RcwT2rkHjPW$MEOdIzd( z;}TLQJNuX3MB1g|xM>t_;@&S%7r7CT9OV-#h)a}O@R{EMk0WyMkSa8?5LPN@ z&mgC8A1OevXh{6-jVka$HQmSNao@&KiQ5&m_pbcmvAGa=Gy+SqLtxr@iqT1xG{L-Z z?$I`Tgj|Y&xH?<^o7KlT1*cT41Mju(?_VSJc@fUA9igCxhPbCwj50#2Wyr9OK#QEu>))QjBsd3(}Sd zq0;!!tLTSd$%$>}H_TXmZGN;hKG4SO3i#ZoUqvmEz)3(9Xn6;?le>C_ziPm)dm63i z;5$d3E@K%pn_WEfM%o3-M`eTh-)T_bOg;HR{bkhH&ofym5k6Opx#_mgn)Fzfaku<9 zPz0Fs0Je}05ly$(dehv5QK1GL0ip}6o*6q zAnl;~k!aApZMpr7NOZr-^FP*nAo}EB_r>Hu2+~THohkKop_2r83$I(}kJXCKnkkFAt@D z*Va$Qj+NjN9vl%7UXGy5pw^3&SNBgqAM*l`-oZ>|)jW1v=x)ek;U*V7lw&5he%NUq_!({) znt{Fn*HYM8vI>3lUU)VWn=dAWHs(W1R!g#rH=PSI8pM**?p|9T7OTo{vT>D8O?Tlkjw~e5f7zZJza`&zOa!sBuK$VDA!lp2LpMsnpHby3YFp(C! zLyXeCiIp#*D>T?mCJuHH3d;@Nk#Ss(ZAl}y1`F@D3FYtw$pID3l=xLHY~eB=^$|q0 zm#!4v0LB(SxQ>=zN5B^GE%hL>D|sA~f-q0%czMb~0ZTXZDcfz!ZEtZIBb5oj0!U8@ z*|q}#*}HEsJ(xk*ic*NRs&(tTos0c_#lEO*3w@99f|-S?tAyyQD_0xsUMn$^1XO35 ze|&-{0QNYjy>X01cmCpeAxB|ue%zW4G!yGfh3e55$R6ZB^+!)uesQ``)JG5yTd4wU zO>{eihfysS96H+{;!myrgZQ^i*hf-!d%piQTs*$m&MYpr7OlInJI9i2ap`ssVvX(8 z-_B;fBvrO4QLe6~F1d_emmWmWJQ{JaP6q!Gk_LT>jugmvqo)B*#5T@9Pv|?J2B)(l zu&ZXKJry?Kp!y$`)c@1p(oL0v(&(*0c5H9QTEhWjD>dOQx&JS0+dv( z=141Lq5{W%1l>>aTQ;!2rLq6)5wCI$~=-2)uQcWp#C-( zDH%U|%hcwoe{jElun_W`3?CVy^{jp<&pN54#w6GhF|2zQh_4uVNN;($Xb;R9V0}8W3XBzZ6XxzK1V44^4EicuFQG8B|O;UlTWbMmhig zHxLNsYTM+B=SJu%sS*}p+HR)Ewbg)Bo4xtA%uspu+dF8vq50GA0NP2#yUBl#vw(vU=M3wvQ!m%^zf; zs(XD$*-fo!nBcZAX6J)lkah>SrFBPH4M{py4*r{s#-qxhB|Tttw%f`JhBL0=bKCrb zMBZM~Td>5B$x{)-)ZE|~v$CI@%IOr%7EB9t5&c~0vRSO)nVq?RUUZS{O7*2-PHntTz(71R~6B-^2kLeX3v>ndK z8h#FTc3r*3l2_U1=1U*WYK`h9)y+m^HuV5Xb$Yn(zEP-o5%y?}mBRvNLQr%O>8XN;`Mi?KM$(>($qu{nBf zTAo^aOXx`y)%5a!>P@z+*yLYy*>%h)&gaHYD;h7ON`DTTUpOkLByB8=rpsc0eR}ap z1^lW^KW#)Kn33(8Ht1AqgXHKrBb^tq;F2uKyqej#)0lgX3qrEw=JelR4};!v-y1)E zyP2Ei(9V{o1sgwCWwavv~@ohUM!jr6lNI+CPgMx8U2IA5Rr^*l}I--Gh&AB z6`Zvuk`K_-_buyh`xOHH2MP4|7rQ`U@eWkJ2gBU9@N5>ii;eCN5~W%0lxRmQVbK-f z*lsO`Tz9^ecoUh}h-&?If>5P2bq6}z zHt67GTq=`UC`^|1_27HCATYv=)Uo1^e5McnUuGR#N_P%7sz^NC%5s6p;FEB4rZusj z$C*aV?b8UlsBzD}ol(=+*dzRn>>*x>J@Bmhu?R9UZ0m>-dDb+^Sp8zJpFZwVmSh{cN407c z^q(>cUdW|mhG$<}Oh?^L*D%4mxkW{`amjgdgVS4&G(Tm5)* z?P2t#m-q+K^2;3X_-!vi6J4nfGpMgE1q(Ed4DokK$yS{2bbj+LRI>z~u8_>SNvTnQ z;esb5(%FPcS~0Oj{K9EV2hlm`VBbNl1_^gr#Gv&l{ruxPS7rR)Qd#ZkAdFI}PARJ7 zG~-w$=#X6NA(^5DI<`#5U-zErL_99^XcmXi%yG_IT~XwJkZzIj(N)EQ!`3N*0A=NM zYCLUtsAXM{!;0$d-xc?tew>C6(HfT%j}YNX0~iAbo=BXP8sM9qql?sO$#N%|{e9jS ziVC7^*5Tmw`?~&@l72_{Q)*F%U?I>a`7ouHf;sd&%2FdOzHkN#CDc9(d9YS|#ISkT z)Uwijd}{^d?>(WPA)ofSlzWO7v2|EQFe~G}N_YLQ#2uXv=Zs{=j|+sbekR^Zto|2) zkMkGne_t(CE`xB~5OR6+&#*-;*zV*cP(>d%(t+z~{BMR@mz*xCU#7lx)qxZ| zRcr}>_M#^I*bfzjmmk@MyGc(M0C75+Dm5z!#4l?~C-8re414D6jRar{W4VrJ$&?#Q z0*JI@pf=Woove~WByl|2k$K^~0|BWR9CkQ`(0s8t%;xn$+q=aomU>k>Br77qEcg$S z*X<^<^8He(pGb}fm32~6uhP`Ixjj`)o^3@hV&bUM&Cbj%6TFs9s^P6-l#$Y#TH}4# zl)nAsZG^TQRQ7lR-j3(wdSFgBK=LCD{;ge|Ge0^pn0MhsZnDSy64qZ`QH(mR##1Zw zB{*cHgcp53$B!+_>SF(D*o9Q)aB)s=#9L&`24clJy|;%T zs(RAmUq5iC^y>Rwzn^EgcFkEBIb=O4k!zKGph^cBx=EGn$P5mPmym`r{sz~o?_>0G zMwF<~Yx-cw+*DT(mkVg77F8+1+08R~ZM#{No+hs;9NeXH0U!rV6rAj9f}Jr3+&if% z09z<@BSpMqRlH+|_)w1_CoygRNZ)Q`WS*MjG5FK%r;sRmy6{nnNoPG~{;@h za`_{C7h9%0)NQ0CVjc&p9LJn9MXhZL|21MygSM)gge@n@-e(*Q1iX1K_A=$)T_SyB zw|CajOC@IMsQGGP`Bu3a*R#U*RbQx!zt#L}?}ZijKHn9k!ZF4*a(n32>kobwOfR=j zQf!4VbL*jT77|gX)5lHdSD32Bn$LnJuMm>%A99`iNSVy!l=P&M#_1h$$pEo46JoQ| ztDZUDhQn|XF=Jy#W6M4kqn&cl5qk-20f2kLGomuBfEfhqa`>7*4gva`+o{QyVAt~e zp?L;NC0Ji{7q%8KRnbzHB;s98sL~TYb%z55P%`-Q)$5P&Q@6>#dZbE??gdt}xxH1o z6+iPIXJ@^Bi+5}A2S?=TBw)wkMREhPKrd}e*b8;Le)Z}e!Yc=r(H+F(aML3*!`zdv$ z6~fm@kha+U*x6GjRP|mbZ)tkiMD<=qQWX4;QiZhcfAuM|CK|qYq;+3yI#DSSiI(3m zhTR#YpNnw*W)0@|;}W#PPdI}ee$;q_5YMfHg(83$CBkNO5yTUdNSIUdeVhjf!>H{k z-F7b~Y+fm*_%r@Tn9`ep`q*5*g#rx5C7=)}>d%%`RmH~RGP_fFQfl#3nh=`$CF@N` zrciURzS?1mHf zqkK-C?N@V~N%Sj>>h59h*puIRHi-a4WNqX*%(`8fKA2vfL?H)rTst)IFuz}nEn;I< z)GDSgg&Yp|iIRx52}rhk_1>VBgA@TpP$IV2A4fW2kNKq`+xuU%SkwqB&2xq4yNRl( zXRtt{>MbcVX+NDK)fcLRHM4IYZu&{OogC}Tf!zI%;~+I7-<3A24vAMQ1WG4G;#DH1 zy&X3v{~+;q+`KnF^HnoQ0=_gpTDmFn zGj+NjUn5owRP5z&njUYhKMW&*KKKxLuT{zQXu_l_rp+|dQj|5DFRz4sC8V8Gch>q) zcI~djoX{AI zBG>dx`}{VTm39~(!LoJ=hc%e9Nrj-YC6H`gb9*Dq2$&Qs34+!hO%GTr^0X>W1Y&w)i5PL3(^VJ;Qdp<^vdOE!1`LU#&0%Fhn3_XX? z^;;H#8{$f%@Vbd6K6z>H;n8fvxr z^yt^_XX_=pYtddU9Z|%LBok3jnk9d@9&g`Ho6Q%evtm84ePLR1rZBHwq;a2;AUSl^ zhqdlwviHc({=RHXC6{W4iO9_HF+;edC&|Lp3Uf?VsxT*#Is1_3iAp6u{Jp*4Cmyrw zBwP;{N$K~BBV(0qB&;JPYv|Idz<@dau;Ve^Qas?|2tYZCwDz|5xJfPLQt#8&UidQ8 zHFy5@3ZG89?vj72vB4oa{b@&cTDLvaUIAio3#cD!@jD^-2Wc1fCK^CrVI8*2fu2osVrK{<3P^{O9Ih07~wz86}`yftK4 zbm{KIprqTX?8S_ea&Tzp$SDD#ZTu3O^9lSuxxqPC;Qj7te`hzW+2egbztxqL5+C@) zWQJsBh`mfmj5{Lg@F;4af|Id=6aH`Gj+jcj&PF4VGC^H#xw9}4M}QCKM4j2xtFh7M zmiOPQST4&gHV5-rq4&kRj<+MHEvccJ)$ngddu9t|sV=r(cme2Dq52NDaCF|5kv?+RmTN4r! znF_{ir{k~1IcFS?9OM;*m$PrhLQyAH7V@H`HBu~8$E3^{$U=N3pL%ALh|KkLDzc9p zH0WqxklMNAK?VNq zD)2T`mD0-A$hHJ(xs0D^{CP8Db8!v`=S{o~`3Ff}e><@@Ed@3)L`{=*HG_Jo?IHU~ z6)km!djrj*i#?O4mSML;@6VI8W=1Mg2RL0v+SBRmBv62>R826Ch`pf(Sm zzXNHlllchi9BU@(H`0iHA^IsI`>cQmL83*EAAK;o?1^Dsz~6$a%-6U8q_(qh>xPpu zwVN#WCKp$;9^c51jVcP2WqJ%`U8cZ$SX-FRUG@6vH{Ag5dd24s3w!t*ig)n?TP-&7 z^?mKYG@vozLBY)LMIRmP`HRW5Q#R|98CJ7#leG@;cakza3O!2IC-Uiv%`G%?Y;P%) z*RnNg|HU(p5O}7d$Dmpivz3Dt0+H5^9!_#id>u64BeGs(pnF>23+EfTl198+JF9d9 zdmYwfbx4Jp=laxA?yWgs9*e10i+NF|heA_x^cF}pvBLNcXUVmz$r5r!Je>a$Sa5ddq;qP-?%Wl#C4_)0%&@p^#M{ zboI482x{FPfo`8y@--d@m)n(<_W`WcuNe@DAZ!BE5Fg$U7t6~t=xcP4$~ns4pfO*k zkDDL-mL@K*G0fG~%BU=Mv~?4uOM3`=d*qBKNboXJ+#8@0j&>Zfs-yU}wvN(rwJ;M0 zOZd1a9Rah$*V3(;CK+958sd~hQg8mqsm!a^5ip$5=SHtJb4LW^yX|kF?-w%!)hpg1 zVJ@R72~{CHK?3qU?taH*!yZ1v*@J9E5vhFWkBT>U9~`H1oiySd|5k8p(V!?7;<9jJ z*qj3i(jThH2uOkjNeJzJzweMn=#VY<5AXLn#_T`xexf}38?xG}4N1NENwuOl^VU$* z`66<|F9MZ52`9A^`EOO#!<1KN@bN2QTto@c*t|CiA11XoUmp}9fM${IA64k!Q8E9> z$Uq|b(E18d$bvwPrP15zkg_L}%uWKnt{axKXF1A58y(`53x~~90$ma^JgpkD6eKjLUoJ(zQ}mPlydc59i7OJ1)r5Z;-zb2=U6xcv|0qYopoM! z+Q$&|Iya2oq5claf_13kx!^6JD%@mGLN{u}G;AO9EO&V=OT#4~rgRnwx|qDCT*5e? zeR1ej<8#yH%f|SEXvZT|{Tq9(lJh&(G06}26X)9L=mE190?X>Mcv#fNYi<>!5;4?9 zv+PXivNtnnrhinG3u!-j>{%(k;bi%OR&Vkt$&u0nPdfF?uFFr=7-)ifmg$CHVbJ09A}5F%nS7ihT|$(-4XDS$?YWN){5Ff%v_;1 zDz-IY$tCn`J68?;a2H0J%C4huL2+rg$hwP_d?B30Ms9y8aW5he6EtgJuJ!woK|{3b zJV>QDGp>Z|c(mGlXHr(Rc|uYL_3FA$yMdZ9@W9D#D$OT!aEK|Nqv6*}nUUqaXR0&L zr^I+W$u_YNXxmD|DUN0-`257qmKl;+GOOpI*n0HDFlwKKs)OHZ6uehVV~Rtq5>GOl z(!gl3@vU2RSY?ME=(2#VnfxbZqOQmKG~{m^t^J`vkBXSInFj84QlJW^*=yzX z)3t6I)3p*++8x^Gx$7YMz3}_ycPQGH)3fzfE?hL$8|ka>Wmtk^ooQyTdzUrGl56m@ zK#f|YO8!1#n-bfOxt-edCMyxmY0 zHK>**<;h+x4QrIC#_Zr8}DcJQeomfMqEJd07I!Vsyuy+q>~mJfX}PZ>I!^q#g6|VMS4#Kwry?3 zt}-4oN)Z%-)L`4Hx#ApA#T=KcEPfIKkp~7Hh}liT@pCJdZ)X0gvv5(R`Jgu3gWzUi0t(#U(%B&ca5f!1Gn2TPMP%7~9?dMcsMggBrc|fVE zjb*OL9UHqId*5O1&*5;$?yEE(aCk`+^6JcKfkE{ z2Y2pJ4SY4MTFP;pw;rKq|9hayR(1BNSo#vYR(?dmy+Bv zS%ty!6u^7Aa`l_pc!z7LeCZD|)ujhUyvpw8bCr%%>ct9Wt?7J+PV#`YFqiBU_G7$_ zB>jE^J3EoN4fo7ksPY^?~N=xrLztOdio{auktVsLf4|-m@rUPuuSLe1XRp1vEljlDzFI`Do ztElT2u&vaFsMnUQN&NM6lgM^JbBBpVWE+yn>6~0iQb}n$;LQQ95_%-y>WP;FH5Vqv z4AmZfn#hQ=LS&^zEBB3y6In-b`*@9b4tOJ_wJ`?Be0|XLBtc7Sg(lM+{tKDC1dvh) z^%iABLB!z32+n<&{xtbe!sPYSocO1E>De%ZT2o)fJMWVfD^uO3Ilui?>ykPNHTSq2XGjK?qiZ=Oj1mm;Re8)z3g)X9 zhr%yyN0d|q=5EJT8@ukLtEcp#UYOdR@~r%L#>t)^q9mU)mAxQ0@@^uxn**l{hf!bc zBY+M6AXz(|5TANHzs7#~BMj{)DuBd?(p%3JjU2N%=EZdUP9yHl&Sm_flK$}XC&P2v zY3?&&p!2(j56*Z^f;bWXAl1ktNKg$(vSSV@hfYH=T7fWl|Wvt%LyOagTq}LmC-fG=Y-lK-mH+B&UX#^1|9}c zDmEtZBtT0RY3%9P+W76#*WShSiD@ml$>nL0D@4~1ICJ<-V?th(ex;qtowZwUgC4(y zpB<6puEQ6|N82829_tYRPt?>1iOAcV!wb>F%)J+&Knd7dS5E)%}0ObQd6-4QzxsUZFlB!@VTDF*jK z6pT1g5Vjo7M|5 zY1~1X2)MH@akz)Lyrh)JxABHgYgba(WmW2B<+!ta00kXMkVg`d7q1vOY;Y4+`%IZ! zsr;C+Le1g0E3kC|i&oRPJi#HE>wP3c6scCQ^N`-Klrm(fyJcn(nTeqPz1MyOoV{0& zv$Iw3O)}GbF!qdwOm2PV z;!DE&j7ejuIw^aTI8Fp;4+?}1jw-bw2Fx}(u_r6|ma|GzR(5rtd-IXeeF}Aidx*&% z`mJvbyxCOJLuT8t^W1xrNh|@oEt_ex^R5ppWe;w4A=cGq+g1!rMD|m|t{eLiY7*mH zl6)Lpsm@i2>gBf^hY^|9R^>dY?me)M8DfRHN?eY@?|DGRUePi$(z=aE{ChL-Kh~;dZC8U{@l;EfxH5^~Q<>9p% zsi`2MJKE#9q|w;v&;2!7zg=7JR*y_2_&kunbJ;3sNqx}3o5!xkHkV6Q1G-iePCE1U z2HaVDh7D8&**8FFbzQB015Sn_t}UBeYk$b-Hzj3TDihMIxJ?l(_Ym-boNQ=&VOnn) z$5E8mt|UEw^{FUk&7eAynBrG{p@?4b!J&CDgK@f2uXL`Puov5gbnc|cKnvquI z)KLf7N952JRuo?77I3pCt?0_2Ys_ImED2z5RuNa^a0de?bG{wcm!HO55U74!N!13| zZ9_qZv3_1Kpa$7dMc;N|st#3uI}J9Q4WFaP^6gTjz!`KT1-R#AeM5yBqtVl8nm%f0 zY#iYzl&e^3_H=ARcfm@izwABF>^9ngP7K*)ilepo*pEjJlsd<1?%j(4>Y>c464Mmm zOdDF#pCUTG8}dd`ihUen=9%iLocK%8e*|Wb;Xwx&XE2GR(b4_o1@G^lulpH$M>hdL z8Ib;2xGJMQv=9mUv8BxC9U!ebrS;1gHSVAQ$7n}Wc|pTGRmG0bG8|sOafB(!bjNh)d23?*;mnIv$eHQQ|?sfmuDjB$#;86?_PS3V_ zpw>t&Hsra+8D=1!)@uIrD=kNU0wVlMiOhjjuZxQ#9Htv}wLK#(z~FtQT)9l9^*P~>&zj+Gl6V(0xJ4R&eJnuU}i$;}pro1rS#e8p?14J%W~_ia!<1E{E|>n+7uuE z*~~Jp2`!2sa)$wT#<5Oa-&bu8+Dls)XcjXqgcD}f-xOv+e-qT)9-u@M9e5bU7r7h< zt%%o6I$M^@ef@{Jnxxdw3bN@40(qJnk?B~jLr^Fqmv?@Zfb_lWeQ|+prcvl?h{BihL1ntBD0uZ^NbT%w z0)-fMr~S&;clSvicnfG?vF-$$pLXws!b^xFv~q{}G?uf>c;>6dY1n-z!ue;o!s+9c zmJOVYTxM*uZY4PhfXQKdd!*JnfG?X7OZq*=cEawQnzVMe6P}8J9c}lXn=Rq%uzAhQ zRK+~poZq=T6qSD-F0h-g3XaF2JS}R%+>Lb@Z^oZtS;b^*cJop&DwWe)>3NVu{I?SCZ0@R?GN~*Ct$F{@C<)auW3=H6PO=miqF?g$ZD)06*P%ZRWCiFK~ z_?u&u6r7e34r*K-BxJPCVh=L~#MZB|sC`QvI~h@*E0a{Q#H-T^4+#kPsMc@$f{W)< z>{4xeVx%r|=o1K%nq#SvQv-v8Phd7x%_$i?w|!u_HY8uBah zE=AjRo=)4M?MBOcBH|-3g5GKGP?bx~;!ve_iQZ>S>GE3Q2rW4`JypEa<9%Zx3b@IY zr}6KW!v$T2&N#F_G2My>`J<&xbI~e^dZMwQeyL}Xp*2nVkDg)|IJryp607fciz{^{URE7E>AHdaHlb{ zup$T@Y)0>dYTgZN5=ATa%0r}^-qdSzeJg8Y4sRNdYpHKUiK{3_i5g#?QKjBmL-O-%SP?HE`s4_ z;Y;r*^mkKg{8ZI{T}%11k(F{LvHJI(fF9q2{&)smqb%{M=Jg}S-f8gYmPXHRecGo~s(I0U8{W27)cGMePj+!m%Z@!^&cZk4)T zQ;o-35T3+ue;5vYbG7~%-#F$rN4t8*&#Ydb@eWw5)_DbzuSw`OmRept;&ZpHF;xRp zssLv=@Fd?JxR`;ZrqE+c>m!g-FvdO)DytN}$!`j`e z91ukyreQ{FA62FhL&%1tqz8#D`hnBNT(`+Hp4#lxkdUJ4uzT8v%vc!B`J9+NK01X& zm52+5&@T*n-V#Y2B*n4c+6pWhtuq3^)(NIlf%}yif-F8E@p8IM_V@#6P*MmBeB=8X z?(-3q9dk!ypLEGUwa>8O&8*Z-4a52_yC$iVRB~ov*q@DIkMTP{P z?JedV>jnfosj-dHS9vZmC(4tF`~hTC5REMf{2PruvUh2sLY-7dFa|cKid76EWkjYC$&mu`b^Tyvu<3 zwTWIc65{SZ9_DhC3GA+<+Sxk1U^e0N(29__6t&6k*BUnnQle`U`zfH1ohlpW{P(Z_ zqK6cD(b#*Wx@p3Y;^Gv$!?ChB+gq{kx*4b zjg$lf3aMRAc?J-LiZ)NlB9`FQy@z*H&)q(9QOHSsvnGq_cxv;1veDs*L=SAq1 z_mFAn>1gQ5(i2^5g_oMu6YB*E@|vTO0}TeTpH@U!R%&>+h({g)-Pz%fqyotht=#_R zg=u`6C03eG{J*hujU%?_ys*dQM;r5_jWDSb@CH%yO-NvpA3RgG7_t&F74>w6g+8=M z;Tvr4Ia<|us`|0|c-HdmMh3-}C1RwxECoEqlq(UKvk*8}F1TlVxaT&e`YB!QS;p+h z4R|c0l!DpSedAZD(wexpJuXYfd5aG9POc=9uKI+1(wn4JLzmS}OQc=jDKlOj1Qm^V z>Oe1v`!<%|5uGS4s=Cn?Q(0h-D>A>bPPAA)+he?Ahn)I_A6^p`)9-HUdi0E4)=cu) zMVNmq88*}&W5ph2yK09ER%mc;f5tmPW@{52)^Hfk+U{uKx~Qhecf{yS^U^n%uQbTy zso@1ZyT$AS&yJ}>tXTbIT`DqKl;6`oq8kj0yeKiPZrJ@`F_LB&7DPf@BLsb^Ph-Jr zv4|A2uRX=q@Ch^wb#HxpP7jI>QN2rIG*`0=$SUqMKzC-1o9c{}o#e@sy7l_}n9<_< z*+gT5`L^$xFT}g$WE^-W*=uEv@H{W&#rv)TFCi{2aut80Cc=yX0Ay8JFIe;J$N0sG2oDT5G5&>kS7A{Eu3D@kr{nCE)fo^ka^Ko zF|(?5B;pz75bWQ=_r?Qf=1NyHv8cuPQ#$uw`ZIZ z1v4miX}6YQe;GcnH1_R}dZlmWAF1POvy<%eHkDv^BX^n_?^YPbso`lH)0G{Uu3rV^ ztY6r9Z|KT!r|nuA#rf-`BLS2*l@7W(i$dcD=yqKT((|92?L9aSYm6N#2j2?x4(6WO zo2XYl^_H1FnBZ3!px*LZVB}p1fiu*rt?9c~{2Ea!5JJn@Pe};8CqtmhPiyK$-p7Fx zl-}1i_$@5|P5V>EgRKS9@f+eU2&iju?7@{<+KbAz9FjgrNpqrbLeRd)-RvU!EiPI2 zI8S)?QcpiEw<}7SEuh!|J5za6-jwxks(;(vc~#Eyms zQ^W^-u=pO}M2>mZq5AnR5UUA`WrdLaTVmCvdsgP9U{B?IzGyO-C&`1 zf~u0q7Y9=3#Tgc1fWM74sOHr2v)MQZOqWP>RQi&fnGx@%MfX@%Ic_T8D=AdRayKN!zgJR}v-VoHys2A1fPXI*vDek?`I6GW(qEd9 zvC(!zIZ|qm2fAZKq7I#>eG<&2VzEHjhw^ zca8HH{n`>@J=*@gD{H1B@U^QvrwZ1XP8y8GxGU{<&-GSZp_cp8KgUnyO^m5?ScXl zze{aWUyR_m!8C95ov0-1S?y=q5tUGzDSuQ_3LK%fbVSNIuA;bhW1}nzHO_3svSLbk z0{Q*)K)g8uN;R~m@C1L;)K$h`SNb%EoU=O&g9@V~1olk)V8492q9ThTjLXV4w!iCJ zBS7%|sI0No8ovRU7LsWh-}K50#X?nKTtR7+XfM~*XzP3p3=WzY&lh$4mXRi(F*e)z zeOu(>G}4Cc>gss3MqYkQp1((UcX^eT;rL^|ww;yinrY@4+g{G}u+khle0G6S>myfZ4i%E_$ogfgaq8^Ssxv@X?{}=Mw zAssL5uYI7_ESU#l$-?)Y+|RMgV8KNLQ@?3xbqat|mqhk3ew3-VH6!*v5=}4&HSFR~ z>Bx2sZP{whrE&;cOq_&%wZDlCyC)}#?7f0>G{@X#%J*0Of|`9qflDGxwp#UBi4bmE zih89%@|;7jc2gVFV$&u_qKdCg0`E; zd4JEFY$zi1_@E80J`6h1{gC*po%8RfF5!Qj=`Msdb12cb*!*3(qi3BN^Ro&%QI}lo z0@`I41wBN+|ns-k4e4x=MmR|-cXtuE`{ zN=?2%nBghKMGuUSZB0gpgC`f)3x?5}+T)Pl&b6aQ#B5N4P7ZKP8C?gNa~6j^Ygr9n z_x|Nx$`ceL>UT~&;aBF%QFcu;pt=6KICXWe+k&3%!%Vf5ynrDqHk;T+w@!`l8*RLQ z-x^ti3*kg%7-D&)Hon1R?{PfUl(UAh7?=IoRAKy%Zg1!nb(S>&>CAp`PEH)!8fgup z(2U}>cfWtDc$P12GhmpK>}-x~6tVeDMd?9lG{mpob=CH7ZLRDr<>{gYVN~GT733lJ zum^t}=zGI(5pAFq6@FQuF9vGMRpUWB3VQ4nsWF1j4g)} zW9wX*iIaE(#n={=NR?sD0Qdhm&Tb(2P-Fim>3KiK^Wk)!Z8u=I&DI0I7|1G#g;L0J< z;lOhVzE%pv`S03f+aM0?_5SeYK{2q#^p(>$0}YMd*4EJYySnx-f>Pw0p?sJai^$GF zL6I>0{1Sv3$7(_|+MrNWEF?~5?TrOV$TL!WGk3P}UuO#Ni9^;0{4ScazbGbO-9fip z`_>+l^N2Va(fa{*hBhmC$er*ZO^BsCQG#kr-n*q3y}&`fe~nVDzVz}9VBCs+aOnQh z%d}=8CIwFGUwH2fZb94;&cQh2EJPhf0Zh)6TJlU<`b_Wp3F8uL#Ir(!e1`aOs_Mv5 z7nfs_==P@G?DAlf7^txl84Z=RN;HxBa#$O9A=MYuG6(iZPn)5TsijoL9al zhUc^&>#T7YrgBg37J?|EOq(t*r@5<*YT-lNfrbvKPO;qv$WjPnMlSMb|2(Q)+{`Py zmUMqp8>zuoeVkvmd+2qe{|#9;eZ7e1s44eWTGurU8zus3WJJb`7x&#e zPV8$jge%K6293#hbm=a*ZHGXRVIH)oaE*lz5qRdF*&j;&`6fmnxOR0rT{frh^IYdQ zjKSPlv}$*_qM4pmAfmFucQdtrrZW;qHU>t+EVNlafbbBeIWpCf!$IRPhSyK;;Me^d zPC6=kN9&ErgFkM+vzjq#oW99tmb=G)Sla(0H$^peW|qM&K}_HA#0ca%*e=U}aVhjO zv)Dh8dNbW}T8avLG04!_Yg%cSI2J#UM*c02ywvADT`I2PbLTgv5q2+`Dx55h9{t@q zAqaq#W1h0$hcNFQY@yC6jIs2Nruwb7K`7MD-|)`XpFi?0B+Saw++QhLbtXo8QKpWw zP@20xbK);ZPB^7IUvSNKEvL*ft0+taOb|L4eh>3t)!bBHgVX0 zf~AZOGm2Pu5F3gyjXFG3F7t>!f3zsNMPisJ-m7flr!%c@%MU}oBv1Zdt_WM*+> zT2{&g`^AGPtzCWe5#)?BnicVpG0sR@&P|uC zm4TX@6fwSm1_=DuG;rY*wO;vsWzE4el#IQ@M*=if5RmD`7M{K0te%TJy53v-l$1-_ zm?zUcuWp;Q?iyAFTxD0zs8(RDt+3T5pEOLlCdEj!Skktj%^n08!x!cYNqcC2CwIE% z{F)HT*y9@ZPG*NPJ10A^JHt@CwuTX2=|#q02C>eYE<4{R*k)r$cyA|c!(tX=;q_w?{df30gVA>oUhQgIF#r=@+s1lE?g#T45>Su!a%@1|as-Pd{ zs)$W+wZ$i&ajBxSVNQUlV`Mh-Orj_timq zk}%b^jbfMU&3t^zWw`aKH-Fy;sY-ON8#uJ;Snb+J9V$Az-te;ONtyQO8zk`TYky3>{3lPes{;XhQ6XI z@0Kr32D*m@G&^wDyTMWeJIZ5&j&e5e zR)32AUY(6o*|hK6aoLwgy>-zPf6Z3qPPx44EhWOYT%mGC%u7N!un9Uql=V{2NY0Vb zAmuM_T$?~QdSbN^z{|Vl-%-xA5AFja_qJNJuAHxzSP#&gij^S#N#e}n^sK$`4hBEd0>SZu3PAa&FzoN#X|c~8Q;{Ir_+-c)Y!Eblyg!~%@3 z2Y7&}A|PFWvs-cV(x4j6zS34tFR>&`3E*K^o^C(oi6sc!S`hZ8q!4}$^yLw2-KN6` z77=;rg6doNVj$)0!ft#5(jX(&NzFGLW3ML1!M`Z&7Z7a&uu3Pa$F`t4u+>>Iu2?U$ zX(%VmbyLT)v02esi!WBF`GY0&lZzPJ9nsrQYEAlKZUKSCR8ys0<>46iN$0w2N%)I2 zzQ&Cc-lavCW35$JDcCBxE%GzwMpJL4Zl$&m-ekw@+fue< z{70Z5*HBelP*-F!^ZX_b3{c}~nE2`Hy>WksJ!U$@BO*WGZo zc|cJxEwbk&IR+W^qa3HqquegQ(cNizi1)nscglR#fzJ?NZ6I=7$2tD-@BDvuSpNHm zAMnrrZzuTuFA-FKvct?op!~&Jz9Ul3qY#vW!xz+YmxSMJ*y(*lGlAADf@6@I@Z4I7 zMoXz3j3jQ{GuPzb*EgE~Cf#H3PM)Av0Baf=Y4glWh=U^(G1N=!4>kIdyiy_T|CDw> zMpsd8K4y?>#ok0}I`30flO`fSBee`{LWn;Fd}PK{H}j44r|EB3m`peUVv)3Cf(>cX zV|@Bd{xXz8#kf<+3J7u|jfnAe7;fdI0gOa~LzuSiZVLRJ(gSi6nK*mp@LaeOG!9lXE)VB4*LCLq?gW-x#E#WXVl^}(YS1BRs?iC4um3l)4HKC9e zHv^cw)2NH`Cb+Xc*>tlP24cjF>|-y+BQZk&R5MZy78NXj@J^VW)an3pAS8KLoHo9b zl609j0BC0ncUF@kCO}pHA@z*0WYYd0P@piHmv~L>xaj**8iA{+LLN@ZMb9`Vk9|>E?To`4|p!#@tux+4ZFHx|EpmNLbqJz^>+o)@T6JuW_r~(KdITF1e zQk(8Dz#0XN)|;2^!f|V$+baMd`d>QyQcubM*xto%8p_d<^B2HzQPgl}O*K@((K0p9 zI0$i)Qq2We?Udpozw4moEtLbYRUZFh?g95WVa+EElX547Yyl6GGOLXe*BMx}T zit66K<9hxe<<(WXd5%_O?cu6dc8U)TGzdzt*&=B)PwhQ^DEWOB)>0Ugyd4~ZrA)J# zvq{vmiL+TRqN#MFG}dqa_guC24R5(o+e(f=O|Vz`cZ`M>8ipKVg*#_yVuv}=3t@AF z|7%GXBinBe=fI7X|<(0toPdZ}%kF6v!&^a&3@bGyE%waLjj1dNk zx1op!S1I(yPGYPKbK64*^Ps1iTU3i=m>GD~gcFYhnj&E0Qc|+JNQU}m8L;L^)){Ld z?I~)5j>+du?|n$8=T!SA)Kt3DFgWiaZC48**1`Q>Q)IshO!gA)hMHLzg|9H0RaW*r~p2|K*&hMOhQUT zMox?XB^JRZ#fPD#qesL4tF(f|#7G>p=zT5(2PKRY%0wLWZtLXaBp>l;aMW^l6;-em zZZtSL7!E0PiG`J!I2a00BS#7c4gAm%2zG_@CKO!yF&MnEIs6&{!9p4wi~yhmk^U!7 zC=;Jt!4UU3X6?!CDiIjzhy?pRrbkPSfL;Gl#6R}Bg~b}I%5Y{3n~gS`F=x5)aiL!mYNdFmEaW#r?0&LM~F!g$FVI0fBw<2licS`<8?do@kbsHfxe?v&a#4k% zl1LK9ClB(hZ;>>u2M8}c^S1V6C`LKX7h3^E$n}B`v@jfuk$6=`#S;@W2hrtlURx_i z_1*CFR5#@eT8ekn5-3ZWly)Zs)SEgY-R4a?7OLEFDVBRC#bJ+aq0z1-aWcF!MQnjAs0kB*_|2b;wWbPzX8)VNVfh75^VwycV`z9pCE@U?L%n6R zTbj3dxY#lY4Zn#PA*6=v0`r`+@%sxv*cQt>LP$xY=n`2XxG5fBX#@kX2k+#xC%IGv zR=c<{c0NMgGPJL5*lscA;uAC^=sm+gpEMbO!Qi9V32a?s#!v+q&*^6yiQzR9Luak7 zA1jUv7DuVQhTonyfuUKbI*wW-9&8>rx5HyA`Ws6;(|`wrpj@#sx$%b{Q<2D?mKydl zmQ0X_Dp5`3#5IGLUF4IvdmU^qFiEX())YOqAkCdOdoU_AnaRa~a9Gu1_t811G zt;2ue*t(Epp|dv#y+cwoU9w4%EjCP=Cp?10CBedTp5;78x+0ARz9x92AQXzpc2_rs zK-HD_?3iaK?N(b4zKYpp_?HxhCkVm8$qy`j5iUJPZ46p6Ukd=EYgN&yo^>@mGPojf zIuWnB;=@5pWhQn90L7VxUWO`!)o)&(_lqtjK-+$f`WfRCEgBoT6*R1J2S{uLAOH+C zG;7g0{g%=T;#JZ(seeF8+0~po4{&mNzqk=v9t>7r3Z(LrN(gn;i)^0Sdc8Npe{my< zKEo?s5CclG@-QV4Ng^W>i^@S?2s&g?9QznpB6)xlr#*GkC_!2AUI5-;0 zogi8V#(Hw3dja>lptAJsEB~7WeN*kDp;+=4c$prm2hTG+vEn2u|39E(wL{Y@Fk*z1 z!YksX!GEMM+LAo&X3+oL~FYfRnt+WPMa~8 z1wbj7%Cs55$+r|n6w6p1`Y|mqT*aDCustvWhXAp{;ZV#=*vV``z+bJ}X2P|@Lg=>Z zcG9*}{7#=SYyNEBoiXd3k#V6rO+QVa@dE5S&v=;tg4nwNzyEi7n(<2i133OPV>Y|E zIfW0UC_8EbH;U>3-GaQEZFGlGgRZ7dLl?8J+YZ;R6Zec0fTn^Jy?8}XwCKhuL3){8 z9w&Zx=UCQZc&o45n?5{)wLp`xsVgZA$AqN-Xs+uoE;D8uz5Eh|{9y~z3I_Qt3de!Y zCInds!T6AeC{rn~e;!NgFEl1l&FG&HEV@#wfn30v(g3{AT7GrQ^)6nvUAXw0glz^s zMmMM}&qR@%Z1nz;ov*ud_djh|^zY{*X%QEKy20z_dbw3^T^ycPKSLq&tuQ3Z@fEj| z9j_XnjptR~_$i_%qnuX5c!BU*1us)gG_0sM#nnN>&FQPmO|S5X=CD2sFZ;wxkIiyP z1w|0tBgMd^bgNrD%Pd&0vR?BbF>xGQi~$`puquQ?Gedir3eE%JskJ7B6RYySD;2gN z@|o{5EL!^vP;nuhVqu2fm~L@}YpD`MESZ#Sl0H3on)GdUUu9}B%k3f0H9Mu6b^QV4 z%$v&=-yO@N;V!PuLvgPhr>XG$fZOIJpjxE3;<1~8`V9Lsi5T!wHS3EVKh^mU2r05N z4^n7Rq%cD0dJXmy`*kU7+j-#%Y}U)>_SqUt9dwJ@bnQH749Nyl=sXDvO}K4*Hc zkyki^Q=3ztX8E$v9chVk!_OlnE$!RPyI4&?8s`C8qNk}xht>});(@7!DYFkZv6`>Z zKzlLTm!Mm-1E|`$SjK~q?J2WcgWYwSUKAqd{?X`yUvl7VXU@?8q61|*mq+EX^hJ+& zMsyvW((^})^)0HiErY?R!V!B&YLAwT!_>??fIV`7m0#aL*xIjUNRuc5-%5bjA4=9o z;dlSUSgq!t;$H)5KJ8m0I%C;yf8S$^CR@Rf7?u6kKOV}EtcH#h!>4tSL>{|Ud*><1 zuG%DpqsbN}Vll9gTvJDf`Li!jbB|=LM9qnEF1?*nBU1?P_5pORf{6Ja(CxkXMvsyp zC{Z*eckA=#jhLGHhT52Vc6&EPFQacfUr_5SQ?}?N`qCz>Ce)e0bo&_MhtDS$l&QIQ zpn0v_`^|f1$hU)Xl;nNQgoP2-Oo;kFAdg=SA6|{^nTY;~je}sEGY)$E*qtE?JkqR^ zY&w+exSx?T+gBVYS(#}giOznrmObwsw>PBeYBg+*Q{d^x9j*c-ScX;j0JhZf&H%*x zDg3ktfQSilpI2BMQrsqQ(5zOv1Khk8(_#}7;4NMGyahD6rwyIo=`hoCcMjwY*;jM5 zYp{*-h8r6&3hlgMu^O-fv~9rXs3W7W4JjK+ccaS_z_b5t=)k|`6S#daaC1Ru$Nf->i{}T$_4q0UoGv@M5*!*TnKIxIUtfj3{Wb}X9t8(A)r2b&i7Q>~(vefhfj{lAR%{;T z!miyGZIpIjD@BD#+~;9#CQ-mZGt8ib{Phf^w3;714I%`{t~jdX`+g_XW%DQiYaS4w#sh8!Q>@(m|sMLp32dC zKu52@B&p$APMMx~qx(OgaR7NMj+QFdF#YL1m(&)aYdgksP59xq(5@h>gg9GrP&KWB zlOy&D2}M7Af8;47fnvm0C?jNBZVR6#@KsNdjRB@7V%ZVLst?gKimgpvorq${4}pUG z?0F?lub6k=?#8w(kj7(`%~REq^w=980~l4%?fbyDN;cfM0&YopvlwzpxClr%=aNZ^gZ%qM_@^ycpYkC0 z_b$0FxYG1jU7E_{_?_S@_pu@C*3SCr4 z2{M268I0aj#JNe+`z+8MjZx(;a2NwfU{s=lLYx|$SM|8?gznmJOLiKFa|ieDGkGE^ zywMo800lz)Or9=5mOE+>Ddz$!Zi6Q`;pdb93F;Hv77Ffqe*Hlxoai;8&HP97s)K(( zWvg*v#@cOI+#8Jg-^JoJ0N4|^kVvP?rxv+_HWbM=qW=imp`4VMW1k9z(0CVp#y85y zM|gtv?;tI^oV!Yxi4;OwqW1SZotv!7Ci(Q`r5jFcM$*rDth55q7JB_7SfYw=Uh*ia z$tPd1tz3nx@9~4NX#{7bKf-)(_muj#e35~Lg@GUM+3gB{GtCFbWu1t>ClCmCU4HP! zkXv-FAXZmH@nz4bPqqg8;p0TgXFJzO%vsK%Fq&<{Tn|NRU#JM*HbP)!0!|b*lvq+; zBX+T6X+BuOr~FgdNLfw489ZE(6yuFebbX@?3;`&?lKuJ9v)?U<>rrOdJmkmrEV3^y z?M#%z(drIum9v`-{p}OCB7b~=Crm~bEV`Cdpw#S8cX#$;7aR^VXnF)>-s0}-MyxKa z4=7^ALYnN=knUl(x(^B89L_w6%9j=snmM?iiXRitPbIvX)wa!+Y$wy7^B+SL zx2On=&TvadgR^E0{bTb&Ov3>-C)BgY??Z*v;iH?{S^|2V)<| zoPR`4F|0c~zGiVfjNK!%;$a~wM+z&^+H8Vca=)5^n2`yI`g=%4FQj^`N}2_Ey$7as z#qp=$wegPM3op2-iH-?Q&Xbw!!$5;H3W-xuDAHIyI^Ls3U{c-G)_w?Ep`EjFjAeT~ zI6B6|mMy2n@UX7sV@s`KyYMB^dq0L-)FuE`K^W=XwDggA@B|Jo5N)096ct`?%zN!j zM!XejEGcQpNfurT{N~sqZ=rL6Em}8_VwKGilS9 zJJ<96uWwrgxr4D-_QFrjpNtAW1I;f~NvRY}Vf~60pq0j@=O8e>t~OL!>Vi4ZRarP+ zq!IsEXc1aYm%%Ljv(E3Jz)DF`RsjdW7kK@yh$~}ntBETY4XdZG&ViB#|Au-_B-L^= z3DWH6<~*lm|C zulliH%~&)YLMW*tY+ii4<(fUW>%v?@#)|mKi9Kl8fSH0-23%*id4=SPAyeG(Oy z{P1Oslt&eK%~nWu#@ZBwK+b0eqAH76EJPp6^J8bfIC~V;!DX(eyVUnyK9dc_(g3|) zXn4i@gsOPaa5la=rn3lV8M>5$lBWo1DI-;NPsxUYjzgje7GpAt!WkxQ$EQ6^oGHl$ z=CN-9-F!#cKx|W3It?la#^Cok=1a){1j6q`36!^elm9w_FmN`5=dwF z+G8a}Sd~K54~Juza0XW%hrtwT)8c9d9%9Pl%9axm?%hE_7gTaN5*<@BvGPg_w!GF> zH0nxT+0%Rc%?@@%~ae915GcTj}Vq%8f+Cd>`x6O%z@w%%RIpos5VuNRBHk){ff=hdr!vuhX&Ou50-@D`85C8X*QGntKQ8 zqkBvKKc2E0Gomagq?E^`l5@r@Cx9lEW&_Y0xzl2IzPej|Py^0d1mfNO<;K9O^{v@= zT_Ag5z*O@SrI<8tFbpKhY=t3me#AS_doStN??sevqgY%uWM0o(_9saQe@OTQ{EQ?9 z-M*-Ss%FEFPyTKHO*FR%d za%&>s-S+ljN)xQB;M-d8X<~SpOovm2)+5ZD-^=@fgUHo5h7Bw)p=A4cm=fE}cdtII zuFh*s6b9F!8(XwS7Rx`hs0n z`w34M8?R#vtuOed5<6%!_qqM&2-7EANoRRd2Vh`;LC=HIUjKG!v10%s3)rsa|+6yRqZKeu}_n|@h5a-j@21SG;#G-335 zL)tcyyvo?82X4mIJ-G;H?5qF6?V%FCY^SPf>M?JoQ6&d_8N6C8gb`5g{d zPnpGQ&P)rNPbU~cd7^16HCfV}t7fK-ONW29GExivM-GK9yS^8GX*!XkmfNll^MfH; zcT~a}k`nymMz&yS4(soHd1M`4v#zpK-b{FrKs)d7drxU_tvQ?2E4c4m55lzI|PeTBog+9&IbY)p~5$JDU24qS1~k9J`kbHWrX^C6l5 zgv9Y7&VwoRL8aKj%`}p2TPjLy41{}+F?JQXW_qVPo9sRwX@h-5jQUF{Y5LxLjZItO z{W9&{3OzM;F&%`SFP+);OTRu;i@3#p{b3*%*i`FMU&XoRC_JBq7L=kCrT2y za0O2~|F5b->0RT$xvZOTTM_4m)TePl=6tb7xNtu-%))>g|=rbnM@Nx1#hXkO-e zyu$sh^7Xlp$A#$UcI*%wW)MJ6{p)GBX~|a@Jt6uZjp$l`L}z2o?dtk%*mXYy-)uap zDyLz5lHbUWDMUL?zxpsTgMT^%>OC>~Y)2qAcf#*`G9uY*%$}lpWa@SFI zj(_v(f%aDcys?JdktGEhMG#Vq5YAvqy*C2?Cnl&|Tj4F`kR zg{9y9Q3VflH>dB&ZiJl>l%u5AMxLJ1IG6%i7A0z{(POay@-d$*iDFO<5FSC* z(#PK}-ItiMi1e5Lux0ZE2#RD=y#T`nM5C6Ou|AF5#&vS%h?xDmBR3%Bf z&6S-+TSNGZ9I`FC@V<;pZu%pGuc`#gG0?4+%uetkGz z8MM7@f9RuYIts&U@$Js>{a22(^pn&WYfou0evt4BhXpin-YmBgi~AFMjbOZf2Q*wU z0Nfuh@NQSNPjY|P5$y1yIn*`cejn9Z(%e@fBZq!`)b*!E)46IM22b>J;e4=yt@vqMLZPzqq2HuLAHqdzC zv6IAjsMJ_VH7-zEv)b~jP>lmaIr1IhC$o%9 z|I@II(2yJQxa-W*Q*p*{>ai}2#R#9U9(Y!z@`-S>?rd-+pv2bZ%byc-9HM2lg!nwrK=Y2#;Ag zIT$pa4*NvFMK7kSf1_4GO~d^QGp0e-T%ijx@LmbK`)Qs7%iEbiY$+d0O1e%uwIDuRPK=hG#UfGPn3fO3#z9>2eHt z#+C*^U>GQTip~)$EJvb|w-QZHcQX%h3H0cV!a$B74J7B|)j(hg2muvB`=;sj4!;LS zmDs@p**98oiZH?j80GK=eC)fhelTqlj_L+1o%tP82b`NJJW0l)=@t`{g#p!OMxAz?h?xcRaH@ zpNDJ79V#nAlsg~&`DmedZ5C3ACU?kJkMtgBy`>elW(BLQfO7#ET!*H$3nFnpMHM{h zIq%p47BN19nBa~=wH#bYL>A0=#HC%SQ{>6ZLC$)93m=$f#C>_K3lL)2VEiI%B0@!e ztssIr*n1Q~@T2KKT$GOTQ}G+2*J!lG?uMt$`f^ye?%5~J1*O#oH=R4*hgGf`2oBak zn5jhX#DZB0y|)_hGn%f})%pDOS>HZ?;t`7D+ubTWb}f3Yk~tm?!X3tdS93+_$}rkY zefS$lJu30WhI8nGINs>t`9$6c1HpKj6>INzu4~FHmv{&SReuteS?NNMUoF1F7O>)} z%rv?ri!!ja%~~e#p_UXKH)E}n)WR`H=e4SB1-p!rj`tniKOo0H0I2AVnft}kSx&Yy zQ>*To?}!MVyLfAY?8ND00O&`+JAP2o*k(-LA06-CP*k+$g zpax0D!>F{%g2Sx#T7+=n)5#@Zmr6lB3jGEGqo=0lI*5+euyuf5y1w^tniB{OBDLG3 z0HF6w#0D+%+Wii^+f?F2YFpC}FTzV%f46&J8GqU^aP2BngJnJavk2Zr*Ng9G9=e%8 ziV}?6@lI$h)uHw3k`*9X;pC&Ymc?Cb_9LI^j-llKgets2|yrI$Y zfA5RELPSM%7lSB|ptW#^0`CjXehQ3Je!CToHB?cgq9={ym@FX@n#c|!0+uQF33lDn z8SKgV(|rGCh`X~FV}Eyzt=aijHJ%o63 zqN&o=kcy??=>hF9>^zmT#L2t^4`EQX^*E?9o2ellwcxWftH=iVzm{0K%3LhiM)`@x@)~@ksHy9}#VHH$Vc-wZV5y~XhM{sb?Vykldi&xu_h<;E5=5{4xhYc2(zvaTSffx5nei}coB7+&q+=UN zQ54D*AIy#y_44p;+zB(;!*MoB#6~1!mDLY82zV9Wk6omMRspOA#*;KTKjB(7U#nq0 zeH(a(t9X_+QMHRLV9-UJ^va?KdoOwGd68~Q{_>fC>y0=aM^AMU1C2KH9Fcba;_w-2 zBUtbEA6LC1#IoZZuztb&m?9LlIeU^e>@ErU1~>%2U`^gb>PZb1uG7kXN`HifdM8>p z8SxO}P#eR&yAZ=f@0R)95@Q(p91`;ipKDJC%h^UNUZN*3Wol2U?i+-?tE4PB=ojN9 z;V&tCHz>ij7Ey-pmF$ZP7_`rH#83;V9txb!Ck$))({KvXz}BuHAU6vOo=`%zjW`_T zjwZaDD?n@#M9$Me^W!br{q|gA$znV0YC=_l=vj_1K30BPTKyZUGmOs8D0FivGIHd1 zjuK`1_CZEkm5c8m(0Jc#-^UjclR~7YO2Jj~7GClk=@B_3OC8>Z*3E@C}T6YB@4!Wggk#v^Sp;PONh}8pn4ytAJm?8N-^4F4)HRrPmH?Oa?@R#*~&nk z8L+H5q8&DzBOwbrV91>4w2w!w0{19c*%Zxwb=t(Ok$_poB(dT`#BPi5lUxBGd)ojS zuIODd9;dkP|Jv31PW^ z@gfib3g@@(Nd0{_t%wj#Z-l>e#Ymi86qX+A+m2}T+H}R+J`ni#pR5;hTa5L8W&8Es zc$|0;{b_h>W0s(eV`;1Y0mh48bDA(F?ysM7AX5C5B@nT5-+UVXk?rD;eD7I4`n}WX zFK~UG&EGKhaTtiiuXmvy$`aGW92>u%#g0-;GPQXz-`6CN?>#B#2E&XfZgvZ7QvD1E zZ>7unQ~(vdQZQ`$F}dtTUFL^>MJ{yeA3bI8!5}@zkgbHh?*0x~7=_~!n=Jmbpy*>~ zXD1E9VMpt`v%sa;H|CqXx!ihPg(3Tw9w`h$osHV-pw(}AGE@96QoO@hs zfCUp#sXd3y#-#qIx2tclDe<^@-eu@cuBhgl>e--|%XHQ+j{Mb;I+!(bii~+@pn8At zX8Iq{T0_5toVMY}tXRRdvs>vq2~Q5O+u2aBV-2IoWPVl2m0IphJX|dJj0TBb=i*Wd zJZOQW{478fzTofj$@1>`CdJhSi`+L?c+@cCY%d!;$|Zs`z6NNXB_nO#`2!7nF16}^ z&S?NjC!dU0`iKW*S4fdY)`33q+mD<8B9!_av$zX|J)s#2-gNuPEiW8}3YLu%lbMLGb#J<>xv4q0oqc~Ur1X~2|KcPJl$8>$ z!YVfYR^*0?_;JgN%6HZl?NR zY!ee7YE2jUl#4|;i1dZ}?A?I-jCq!Y2qbKVG0TRJGvjKtQWQN{;K1W;v0mXRC#W)d zyPTCQ?F2y#2M12B`Mdu;x`+zWbC$IUhdq?4fqg7%pXQ6yVUYMQ@rNUy%Y(`a2LAB? zZRKrlqo8`^$g0}~^PXdZey1IKRmvue5oov!+ z-u{V*E(l#iVF!p=-&f0O!bv2C@%92LBhaES^8rwH<+e=3Vd@I})q}4vca;jwjiI5j z(bE*ZS7SIv{JXL6rLf+aDX6&%@4-_B%8D6%Y*_TWk&0QITjKN4)wjS9t1!;u<9qfU zqhBrm=CRI!Fey#Jv1Mcg(fq9LQFykdMmqx5`8}53Wz{iK@S46R!21E&1z}Oz9#cJA z(e&7n=yWmZzhAW}sI=j@78p*Q}3eIb2etvtYES;F@z6on^5ooOBI#!Cq2gqYvS zE%%D)>9U%!BX*%Dp>~-4;Uw84A(dK6E5Tuo-wV)Z!b2yK{RED9~gyjL!;7 zdiz5ye&ZD(*)TmIx$&3ca6I9R`-!;tp4C^j0XU?5hcL=iB_ygyf>GvX#dH9x#~QOc zmQ7jcLaE=hAC|pnFM4}GWVYpMb}{=7dKP;h+P#(-{yQ_ag?O*E62V7;&0rMn?zTi4 zT^r-#3smDUzs!$3;fy-40{&B=D^JMrBOLPK1fuylX|HCSn-!a7c+6;oX~!!-o9n|t z2ndfQbYsxX??U0%H0niS=z6Poor+=P#y;eoR>#QCVQ@dTC`p!8Ft;z$;opE=MeT~$ zeT!B4gBOpyfxCO2fVF)9NTsFsHo(H`d*y54s^H8iK%|#s@~ru}VM73da0s4o4ujxi z_VEJ++E*2O7uim$?qc1%M}$kLS7PjUzQ@n0y-&qE|n#j zf_VNuD>o<BKbOH|d-eR6U_dO|-n`T`x-LX}an%yl$VDj-8FC*;ot~ zrF>0txR?yRk(&CGjZFquM%-}H5V1Zbr9W56U-E__jxP-|KFB;v(s~n-euYNEv~;di z1I=ya+l*PBUxj3FAn*BYSIkko3^wm;*%1~q z2`gpU14*ID%ri?clvEQ*D&mep-8}cCZ7fypd3HOGh*+ZBYFg=mHR~I}ipKb$b;Ky4 ztDpC{rQb3$t&Dr&Vh@wP6eGCgR-|a-%<9=Z(}+DH$qyNQDk*J+{nF8HnpSL8Su-qz z0r^Z*9Dki*2@y-H8N)A6p#>sj!#L&^_bg$8-wo7qiIS2%%R$dd4KUl9F^Xnb8Bnlz z@Cd-GGV9;zx8|8s0593X^uT_oaMrMM_gFayH|9G$wHH&pQ7G}yjKTQ_L{1XJ_(Wxc zd}5gOeo1trK-xIF&zaTB{^by&e^%Yq`KRd{6AAV^ZDi84f=cIz z5q}IBRnI{rRi&bsY|Qu(`@KD8?UM5dx9J_^%*E|R9*;n<<%h*X| zM$lwd@Gc(R`Q?*b;NYNkQ+=DEnzIN+of7;}HpkU;zeL@8<26QVytg>YTE8c)W%D<4 zEv)5DpSF)g;*RfyXj$|sd;PH}GK;oBvLbA`XajtJ#!b^D>;q}*J%)y5Y6YseUq^8% zDJ{2Hl@-S9Tpo*zgk9O`WN3xb3-6%ihHE*2+A;c+5Y4yJo78}i>-RG~W~ zVD1r&NOWHM54--4p~Ft*5=Ckn9|LEEdjO3#v&}dHY$_{|ccDUh@p(sEkp}p@)98^@i2i8q1d$fI3zurpGQ%GwG()>AMxm;;z@YMOC~E=8~=GUoxZk#!Lxz1fkB zGJ5`oImd^gg(n>;zBE%#z0F0$n9nprZIIjsp~tRUfY$qAyET)A^EbwxjHOw(Rn zG-GUtt1fKHj?&yD=Jk*+86h^SitSrbF=$9Y~QuCG9*q??ub zoFV2NH^9ubuR#DcWgR3m6-HcH?=KwH13NT+!5Zkc#^(P3cEajrmc1|04Hpa)iKCVs zvclDn***dg`|Kw4@%uX%e6lU3X&u)<;Y|kK9OdD7T)SqOHQ_#$bYy0uagS!15iQy+ zifi2-p*NO(;^eeGq8B%OX2+`_TR-Q@FDwB^yi&D&8Zv|MLxDSlLu-hdi5Sf?jUzra zVhgw;QER; z%a<pmz_d_M6ro8$T|7k~KJGtbXAghMD3fCr$wX4kYv^ZGk$tf^3+36afJznly!qOgEf)ByOww4y}|AptZcU=Y<=Q%C?B zS4B$0Xf|XsU`2xjsz+R~s6flnsZy}05}-=LrAn2BN6=L3qNPgeu9}rA3Y98UtOY_K z%t+>|n)Or#DWMwN>4i+lwBiDWsw%B3g-VrxO4b=OyTSxOKDkf{g+=9YvlCWStSSVl zT}1rzL&*oupMHP;!~io900II50|NpB1_cEG00000009sY0w5tV5ug6qZe!1pAnCf zH*8AW;ict8xmhtg(DE(H$WKGaw-zk4TQOtEvlwoRaG*DNwc7MLUlPfShjQz7Z|+>#WNK{bv-9Y)lM>|;)*I0hvNA{RHG%dVa9~4 zQ<5o|4l!4wBvUaPr5LNxl8j#35~*;NXlgN)4ieVkc(y9uoLxA4MjGnie=1nBcUA# z=tIY7!f|x;H%D2EvGEu^4bjvmW?V)uMouxDbUjWnl@1n%2ynbt9(6G~agv3RXzD|E z2~vit7GE(mJdoYOP}M@nv^=3J(T;|pIxQB5l;bN+#lnXR#Pe~2+g55ZcA?{^9BV_y zv?XFyEm+lPO2n&1I$G0IqZv}xl%>M)Iw^V-v{E`z(zViON;+1$M@lJarRa2`l-Hw0 zeu5YD)NX|D({8mg_OW`)Q+~2>em$1%jCOS?*xeR&sOm>jG0Kr~)F)$fbq|B`iPDDZ zE`^a|Wt~EHH%CzT7X_AFSlyv<9YS_lWr@v}F*&m0;`HB%ShsPJXhs7%k&h-nIBJl-M+i*~qp*>XxP<6c7Gh%zLmvW# z$=(s%ele5SSSlVWj2)=UXJ$L@J)NCMO|uU1i=!hXERdbEhq&0kl>DOLdoe0d-3_A? z7e+;+9!0R)I~8b5PPSuXLStft#mU1gLUjyI9|Df&cvF5aUWF|cG^H&Zy%e-knp$cr zMNN6_SC}uM3-Ne&^sw$?tJ1SB1*ZKcz_|6_urC0{?aWPCzQ;@Xn6lna zVwy1SV)+wqVWx}P#G5y|vA)GLC`)nhB-y%SDT{O^xTAJ%hRR}P*-Y#!XAsTYQFeql zp)+=HIiW=h#&G42gYpSFegfwm; zp%iXWx+_NIN}BRUtlUZR{Ly_#ZoNDD1!8v{QDu$LnQ_!6S$ZxLn-hzRE-o%^FTvNe zt&N!ZI*^QK9tJq!o#8^}5V?jTj`7^@2tzMI@P+Luo%SBi%R*w&5=dhgZb^ z0Au1}vs?V%_%p%bm3vB+x!7Ban6V!?ThDO|#=g&w@bc>HKfC)s@Xw3zR!gP+&;I}n z{vKF5%2uTd@oE-|l{GahLh*mIm2@}%04Vs_-W@sq&q~z3gkhV(+3>zM zQv19A0NGh&)v7)#S$)egt99=+<9&I2{{XPp|HJ?>5dZ=L0RjaA0|){G000000003H z5dt7F5+N`_U;}UjP>>TMKtfSrfPs;rBXPn1+5iXv0|5a)0Qy3&q-yacqMv}SqVQCi zM)xgi$(hxzKS^)?2VRRL46ske56P0GH%L*E<)vd{#VL5At3t%3FC8lqrQ@QI(2+xgikcSi_1BML zt;TLKe6-@O8>5U_!&8ce(l~{bHFJuKIE9ooqN2vdHeQF&vix%e51Wi1ILS&Itr)V= zkh#Njv|`Ic6gQ(63{u>!N*-)f z7_NgxJH~qOjJ4qpeG7PU^t~z`m8o=Bh+?@%8#vA;af>L;3L3ScD>w$IQIgP=oFi0Y zykxXuq07*`#sdN^l;r3hTThN>! zve|D<^en!m{aDwXC1qDlE7XeTU1hC#a^$Rh)xs)-tVrRCX9&=gaE(xvoFd%rRk%vw z93n$S93@bZLK#9uMF~w5Bve$E1edR$%z&;Qbnd@Gmx5Pb1fs42(_Va8;G&;(XqIZV>T@B8^W80 zD7^|1emcZ=EKl|lEM`%V#v$%Bp;|dHsO%`jMT~nw8!TMQ9>at^%wb1u4@wj*FYrrJ z@D}VtWv97ou$>%+>`(PAA-fwJ5xXrEn3$?ll*G>;o)4GkzuD<#y-Ze1*-whg?Qr4lh4;U;U)nt%Uo2k|=lZYxJwI0;RbD>H z{8ldic2JXz>7xtLr{u$_IvCoeW?RzKcdTTssp0A<8{`W86u$~wvPTM^NmLKw0 W{eJAi_*WMG%6*<2*VpNM@Bi8IqM1fIPs2WMo9Lv$8QCK(lk9IoUZmxp{>JxOs$lI5`Ex1%)tJQBhGY zehHj}2u@f;RAm1lFa`z&BqI{V#Do&z=HwRn{~mw3A$BCZ6wyEjV~61EFgkYFpC0JY z{#4;~u>A@DJ791+1Op>Ik_lYq0~hwkf3AVo{mb-!UO=pL;6@Z33Vijeu-)Cd9G7b1 z7Ln0!1LrcKIC4qVI4g{8GiVSJ2}6=~WyB1qX13M|W_BF>1E?M_;$x8nR0>xDL7IyP zt?z(}#&l8TBi0{7&S6uO$bjli_0jNC3RQPOA*rI%1y1S%@X3=+&nzo z_J?(?ZVCpXM95bWLa7(K3@Xhe()S?^!2;_dY@q<1N%On_9hn0!nFg@U5| zmlj_xdcrN}3OS>n4_#rbo9Z2rf#!nH7S(`KDi~(N2?^kc%k zSy$luTTPfPI4I2Y*0jMP*=A7@AV6?r^!Jxb0fWVGyo<>ia8IE+ps4a#ZaO*+eR+9# zY(xT44@I`8Vvz=@2nSRT6&uUy&P%}~@&9GWUrAsDTG@JnGYdCM%NUGGh$lp3h(i!- zRHR6SO{!S}82b#lSy*5RNK|dOtzumFNf8clB9-AVESy!Bst1wD4L!Umi$;Qg0GdE3 zYr^g;tyiQQ4zd2160CjPUx}`05?ziPl|1C^t{rw6c=U`>WW z*#Gjxwj3#{XgRu?5N82oXMi&W0;E;z5?P3`epL**3JS9B+|7#d4t$0-spJd`JRDWQ z!fl_0(#Il^22@;lERqOp0vq?31J%=*NQ@_;;+doefR*FPWa1Nj3VbT6&f*Ji(c;re#!EE8t8> zne_CKO*Nb*UVCH{?vP5(oi?-t8jB-QVA9emdA4HV+;BJwC(ja_T1UlFS&yID>^JD4 z%8NO61MVhJ93uVE7+^1=Tqw$aY;P$qoaiGD&7Xx91$JGmL?VFv%&Oc-5xJOhh()$4 z_PE)DQQd|os{AucbKSYQHj2Q#nBAEnCgR+E5fmz77LcUcyZK<5c&8GKfyV4zkYp<) zg(}hymk}eNC|$fg_DKA{Hb}3^52^6wraa=y9nQRw8lz^R-#gWEgzyL)bdhEgAoV}7TJKFCIDjqRw98tk7YG0 zJ*A7HI=Z4{F!C|`nmdA_LOtkzROjZWgzoR^pF0ca1MTSoCqwtQ>sTZ!l8BR6Ig&=7iWEWdrlj(qSn(LdKbo`(sfzB>Qz&q;TD{NU#N_1aCUcJpjjX&#rgd8UsGh7F-nDY(UXeDaOBWTDDp#+ERL=#l*-4;J<=$P9$%XNH>a_;mVu}tlRhu4EaCzWbp8KEE?^v zt|i~Ewj^a1XXyYC!6Y;`c(z#k>9*Z%NuN`mFO#?e(({iFH61b>bqbK_e||1|dAe4? zjOy4AMtNKdJf%$02?-D&g;C~BTv|6@MUxBwQ9TY*POjLv$DE=LF<22PDwau#DrUvW z!Dm{BLQznt{qVOxU=8S{3w>e}1Zsr={l54sUnbG#6n;9Ta?$vT`OV0q$D65c*SsIe zdh+%9gjcI&opIn&B$0BnfP+7=#N$}A>BSJ%z?P{k`q2Tm&NNKu0jNmj=FQ3Is%S!` z0=SK5f}{R2uaCh(7zE-U!Vnb5#(1nkQI(&WIitS?m#Uk3qE+y)y!O54?(5uv!v|oQ z?h-x?T~h%gD*CPZ(iJ`yR-$TyQ5=iRmPA8C?($d|vWY4t&4Exr0ymKoU6#QtgIAhARtYq7;6bs#{}oQU4q&V9{1IriAPHS1*3S=Fe2Sd z?nDZ*D;n!|nAaku`u|`(yhyJ?exIC_>^^XSK>4kFw810nzy0G4i0a}QZg z&tM2ECuYRp(=3@ROk*1WXn+7o>@91EmXI)P;*IGoGhh$U6-S{A5g7Ye7fdxrgg6QR zU&s$Voih=bq)xCPWi>(l<tzt)YQTOF6xi;wdkp0ZJy- zH2a$|bSCqe(WYE*UD9FK^#+6`z0>Bsnbx{!+QB-bj4#H!1pC}^bU)By* zPx2>O_R*U#J2rY}pa-NBaJnYUq+<81M|h~;pvt;D)5E~29N(mB8!6sp z9m|o5^gX4IqhMoUfSf%m(aHfyCQ?~IcoWm5O9?8C=rQE~h;cVmzHTuO zW|F~OZ59rI5NE?=SQK>!4Me5;FRWrKiek zNKc9h+z};FDHbN^@>o5o78%DzV5xCZzS$7G3yCLE%M4;0rW%0!U_9tFkOA~h>7iVC ztEiZ$wlqvQ@CtaBep5HIKBnw2@4x&UxNhd*tBeMj82}hkRvgBAMAoULKKPOUJ=ZkQc*~32>+( z2JUp0Lxw|EAM-C6fTU6j8RZRb%%q448v|g_2scNJ!CiGZE0GBtJ+nJ+R+LoSUw*>P z<0&wGCNv)8NP6TokZs_}NLLwq9R!_I!rw zDO)S=@}DqRni6g$Mx$kDDN@vMB)4##e8(97@B!S+;gJs;r#y9IpS$l}9}7L#iSb8) zcp)c`9nm=%^g4I;&6S)~V~x5)3qD))$d)z69pUlib;D}an#m_ z*UqDzNBji|s5rv!S1TO@M-$f73Nk;*&n~U0gb`PZs71EU)6Zu}^+OK^fm#yXuw%(H zKVD}$>}-=zJ?#1k`c@n$h#-)Np+mvqQp!x3h@`($J6Ji%$LF96GdV>sZ9&R~NSP3g zMnj-~$8I*SvJ?$?es`u-i*BkM0M34JA*=+BBNls_??&Ob<@Ay?+18 zXXnAo>|sQei15987j|#F(au5LxV_?B-FQ3Fb0I7zm78M##JNO*cqno&GjYefs4y5I zZ!abzA45V>*nubWQjogzBrHAV@2ox)%7@4K-lFtOA3jBn#8?2RV$Pt!Gcb^WQTamH zK;rK!>qB$uTT8-O_cyYv?t41dgam)NS@6r#zI9vt`<(xM)4EMR&tB!i;Qr)E`_<)b zEse3K_OBh98|!Z^Z--phTGurBV(wYyO{DC-9;%$uDLN&O#D|siKD?{(%WCq_fw2ku z8LN8kCSmLEb@NL{81?GW_ukAr&Hi4Yym%+<+|&)JZ|(E%+796~Z??I`T~U7@xN(B& z#aK8yNr6z5GK?jvQLN@~K?~cy;(CSbbIWkj&kE({>QNFADH1_N(~6m;VA4HlzY~^b z3c@_rhn_Wdy7vBoRKG1v-S)erx&BS{NC(ekQIfw_t?IYVBkk$5bsg)iYwKgmMg4{9 zA9PBbu7-3}hK>BZ5xuTmc+@?6z3}vi=fcN@L1je5@9mY&y3Wzx1#hqiEk^dT6MwtU z1EzJxf1lFq80&jE_EP6;&}+@jW9p1F(+S^S!R?#nd!30PY(=@>v}1Rh`9Clk-(vJm zx)bI)wbS^)sAXs(IrG%6`i*=cJ5O^60%qdl+cF5(bmy55aX+nJ<*~1tj|Jv?V(Y7T7C5e{z8Y^9Y67PR;vep`Bd=hQpld? zP`yWTe`v@xSN`t>{@)n2e!HwDW2dhd&t3VfqvJ0`SNH2m*Iv|E*mmJ{y!0V1tJ0FI8-kfjV@elc(u(7(76{a$O-Pq$dWwevuU)#&W^S$Q!>*nhs z{#xk0r-_$87Ui_+7<%kZb>=B+-Y$N>6Luqe=x~?Sx2cDkzm{A+ORhRx*Kk;&P#`?0 z*u(XScSLYfz&7dDf>#EV1k=5q+%_)D98j*9G)`@pU?x4o)61MhuvMu`EnLt4 z8MwZ!;`~%__oB@DM&~(=x=(yFYX?qU)2q~xo4$Vb>&J=UBJt@UMRmrtq)^RzhhNQ; zMO#Oc&)xETd%Jzd2r1kU@_AmdetG7!vkbdSwdB^(q7!x3RW-;c-oo$B^X=O^oesb5 z)%}5pJ6jw1Iz0Yzi$ak{U&fzma=tq9>tWpvTiD0m_H$!HyG3{9>odZX=1mh!b?g?L z&3=V=uJ2r1Yo}-Q(XkxbSsPP9;Ft*z42iTLi#v$Jh=5IDyLzqVbkQ`&g&7g~I?fay z71Z=OxjGa3J1{&Bj}Pb06|T7aZI@%~quarIM_*)jYXrSNHs(LFcWkIW_QJ||tIp}K z>s`$&`R2WoUgO_%G(HQDCuv;+Qx2a*(|!8`Y5nX7{`KnP(9#s2frI$(xS-e1b@H_2 zkq2*I6$<{z$PX;?hjP)x@2eX=I-LGm&-W_FuA8l91&q2qO!#%T?#Eu(MsGWLZ0LT` z!+`o@^#dBi8RW2=*$<2sShVK0Vz0%GWnC%jwEk7(Ifjq|g)fQ%r%#i@BWU|7{03Br z9=vB*$~>RLE0}JXn_6?HS2;$(qzocaqA+-%s?NamXRX@i;dBpIP91ajB2@76!p!Q; zQ<`=anw|E0nSFQj)^!SYHw|v3ZJIOL?XvXMXRqs|YLF>m4_7)bj&>Ft3b}S)A9iL> zaHX^9y8NySKJdhQVXbYZ>-mN9U+R7r2Cgn%3=^Ke?zeFI*3r$+A*<6KSEqw_gH$(q zn-}hcZTJ3i92?Rox*Je;u;=P1oaHB@N>P&UH@#QoJCc}n#v8NK5NM1+NIVRNsL^lS zw@u-odQCGH>&1+x?&^p8bO+Uz2ZtwonPMC>(2xZjNfP{*0N-^5i4 z`);Mc9$R6U!fLX7bGTy(O8Dvg)t@Vy&J`f&=w*L961>Xs+i3p!Q6IVArGcvp-@|z3 zuZOsvjy<~hxZ+C5MfL0c3(n_%-KzWXD(qKpJ85k2eo?Vv=(01SdGlk+wnJ!*Gpf0` zsC?v%4pzmDDVu1u%g#5v8y zUk^Mnd&)<>lu{NBA--HygMjj&ebbE7uN8ToL7+j5)9a&I)yEB7+PmIqC+=flJnZ7y3dOaUwl8^n{m9i^X$>Br|)-We6t-xzE_$y z<&T{l?cln3)g7{XJ+f6D60o>ibf7G76#J25Z}mJeAgTPckEbBY^mEg<^a0-+rKal> zA3#`;K5;gC>~NjXqeJ$agHc)%r@!%!QbV4&9_re&IhsBbFDduQ^i#mHB;`3IkSKxK zpFS4KNSlDuaL~c}nL;j8r*z9!Wjc2^mjk-AO3Xs}sp={;4T>s4`_M z5b)fo<`wtL*}c#l7SKwOrU0850>dJTBEV769wqwrDM!RG3ErlxE?0{`ar{Oomg<`T5=K$&DMeX(<&w$0}r7Jq<_TQpcXGrm7V3?XFWRyYTmY{bb z=0GNVHFHC)fO)V!sXJ}zBmxFwt4M?JZC$-7SR{cjZ$QS3(9Ij6Z?DIDqpYaP zeRH!<=1t*VsG}xfOc>g)zCjg5pvUOf%Frm#&%KcMvX1j)U*)oz1I9D~*2J@?b!h_W z7A&MPOFU4ko>b2hV{Q%%RE7@saXVutk;UQ?Rg#KB(VeNHQ0?(V6cvNbtr2e`a`UE8 zv*3nejEy%(j=oSTlG-??Y25`8D0o(z|k{s z84-h?v~Cn{50+Uk%TJ8~LxBhpklQ)6H$B78&q7pY-HZp8UUCVtz0mI7z0V5DQ>uur zR^5Ijbj7Bwm7=I+gDu=K&HzG{K}=+u9>B*wK|;Ry`P!p1+U6{1=2SdJg4v&7)CC>4J8$!b&UnL4b-x9Ru z$VfLIc5YA=Gs(z{kx>0T`;;JzYMyR*zuNL7CuHT78ym|i!u=xFMwIi@_Z8N}%=UK@ zBGvo+Rbs|DUOrT!yLMkH%2;WN0bY!&QwK!zAu88 z2j<~2;ja`K(l)`0XOAM6N#jnGzr0DlhnWbsE(a=&OgYPCzDfhlZ4;BSZ!-Q%m4tb( zk!(@LNXN;KreA!r1)Hb7){D+xGOF`DjjLO$Z$0!{HRU0EI63d;O5tUp%~|(#n{T4; zi=Y=*hC9icm#kKDiq7sBRW0uvshrDp=&dVl>KnOzC)Da)L9-&KZjZX5k+gH+=^NPv z4&UwSBcF<|<7RovD~IZD3Cm3c^27>8Dh6zxxN|M?OxxRhwm#GcBf*cJ&ot-W=0O;S z$DEKqb`VSJnsNaqQ38b~&8-e7PcP__#{Xre*y_rA-S%-bN3T)I!pZF6yGd7ex3zpU z<}2q$`qy3*c^nh+H=Fvn@%6yK(Shb}<(n%<1G8I(zFO7IcqR=$JU3Q+RJZw_(F^C2 zFxTmjzExJAw{}Id^GoYlq19o`mu9ake%9JJ{rX7yC!^E#ahKHAmB+L{oVP2R-7f7{ z)=F0WRDZKl?W5^5o2TjV*3+B$%Xh9IIyHavDt}*ia>7=#;G5#7Lf>&;?SU=Vq1rb> z8J9w?b&pO52R$_HYOX2F`T7Nkgw#D;#?N9w+r}`Mhc`pJ@AS1&1FMv?pg@0NwU0YU zr^M>ABU|I>ES9dtFSGus(BiGb_R_EI zx?SssgU-s$6XI(F&wO45UF+xw?>dIK>{0n(dB^5T0Z*UrR@G9ye)D?Ygl6>E*}P4} z$DyiEsZS5D_GQ{N7^U1C3|4F_u&Nd&E(=*4bkkT}O#`Kv)F;|TPt>Q|E_*iu;_?S$mqsFU?4#AQ`8W&5n>@?mMUKwRy<@i>*=s?>Vp1*PW!O<5wvg?|z z&&z*!7COw$eCT_sajJ0I`DpX|p{+4$=w{u`@0%{&2~6@0=KoCE6I~I?J!*YI zta-eAJ@d8e@cP1`o9eY+)9K$;(uSr+YeIt_9x_`hGJEe?T!z?t=wOmaxHNtPc$bI8t+~ z;$fm!zE1MF6G8olV+Pg&kd;m}gw_LxV7a+OABM+CToNw>6B%bdj-9V#wrI zM?9KQ1YI(@2?0f6%}n;oc{nG1% z@e302b}6Cf=6-xjHDPg(=*~f-7i}r$lv4yFL;3*L9vI0VP)}_P)aa;m9~pixFm|=K zMfk{A{Zaps%|y>nMjm!|2XYsrR;*b^^3h>(;q1I$Zlb zW|pn^MzVZp=UB+6ja}+PJ}st8eK*#wUh8z!9Bscic0If35UG7cOX2kUfHB5;k8Jb? z-E+HJm5oDhl;;-S%_LR^p58ko^PaI;81=J{cg-kBL;kfnW9H2ay_!RmiEEEP3thRi zKn;1Lh^s=+vmt=MRZ-)RYThs+&@Lf#bnct20WP=gj$6Q>g;l5*BZyl4F9Hv+w~Xrw zgJ$mx}cq(^tznFRyvF3@zDSTD|h5@N}o{yB#2=?Av^jLv;&-O5cV%y0>HA+&oyl z+o|#ERnP!vg6tL51l>+4WaxDA_{}%1Y`>|W~BiI(8zRUxOaIL(+FB{#LNFnt+oA_t7!zC5myNAy)G2!xPq z(H;^hc-F)U%DDtqp7o0>rVjb$eY|%9*$l;HtwPEw7Ie)Q)g{LiRP`A{ds{1u&Rsd6 zJQZA|+k5ZuljB9zFE)eJxK^%=g^af0UIpFigEq8QCO0p8j%jG!yjyrM-1BNSUES<- z()Ll~<_^#CcEEBBT+#vTZBgL?LCcC(>m=1kV?Zjr{D%10WvF6Mma zyY{=iQOhPQWW{(sn{xhm#}%|kn0tLd!dOYzC9U=itCvYk)5&dmZS_vaDVIt)Zy!Q_ z?rmN>KNWOSP znaKE3b`*q%JwHAl63dDQ&T>vpP6a(-p{MA@29`=;tRsEAbSdot!k+b7t!1qythK3p z4t+R-KsE`Gc6qO=8zf;65G4u=VNmicFcGj+g9j-hg&BWR45XZNq7agaITZ%u7PEJA z5doch;FO?C!;EK!7$z;vR|HZ0^FB%uz(-%W9Z+KwQQOM?^lhc2+6P0LJx*9BItq*! zZtAu# zL0DaYJt-9ia_WqNs5!TQei#ImGb|W4jKliX-Z|IKa1T0`GuW7bwGLALItO3gu=&$e zHjr0?E)o*Mjf%j6MW+Z$mlriS!F1?ST)ZJ+pNb1y3N_NX zeEDpK6SlY$v~~nt3famS7=VgljlKm)3I2*KL7#x8{F6?@FhE{lT`)?bM33w4J36_XAoZoomi$Y2qZ08|3jd*B=hkuD+#kVFzFRWVXA5Y*p8Vb`Z1 zsySe1Qe%)7V9ub!hJn!0z~3qb`$5sgmk*0IOEhd?O^jlgOz?q(jY00+lYzm-;`5Eqd_0M7x7&?2eCGE-wRRE7f7ki~G06fqb)E(HfkL3k7m z0zzf$W#~Gq&Ip=n7K#iEO?>%D^5#!4N9Er`?w z`$)jhWrC5=F-)k%daxOXJq9G(EMm1*C>#!A*JUR3@EyUDVGJfIEO2y03RH%J=>x?8 zQL+vX53@4+sOIG6jo^pp0{T6yEcR>D{^2Em%}Cv|po)|foAeZ$a`RUa;87HNaUbQP z$H~d{MBY(XqSPFLH=t0!QV-|^U?38^0S1C2poW2fR03ft0x3g?fKO2%bQEQu7z~0I z`FLl=$9+|J^B_ntG68I$0pev2I*uKkzsMhc7L)|)66qP<=2@GiY2;ixnT4F39NwI~ zeiyte^UEWWqeL_Tjs(WRL{EY8XXxyib>TJ?L>$x@Q-;JCV2&8)t>JO-6vzS+v-oT5 zf{}iOub(Zv4?E#6xWp@X{@^s@;up3RLBYr*V87UYMYS6f^ImndR`cFwuh{nKz)>$+ zb^kH`Z)=khDsLt3?@s*6SK`g-;S#AA8xO!^VHg66-7VDxDwDS9!I}UEf-@Ntn+%w^ z$bc-f2>}>+1RA-oOA6$Ftgmw5y>=&0!mrQO8}gGW0S6~<1nX!B*Cr2m1{@>(V7>}_ zBe8%7@@5pfA-H?8zY8spaQK4PZBTB@YZpIJ6&g8|b-LBlS>4FFzVy&LXV}!Y{&D}) z3PCfqm3h;T)!vL#Qh+@($zULenE;_FP&ibLPOFQf0Q&GaJb-)%>+)ChXwWmhH2V2; zz3|Y=hea1I6chcuf!uj_iAhIDc$YpVO z5`=>g5DdqKDP{(sfGVTM)G{Nj*0N=%%$gb?)wJJ|>4<+Hv*?`1#3sOyx@P z?15WCdd^kizm_~pi;~#D^s((z=ahg`_w=AEHh2Y){z}tE3SdbyJ;Uo(ZzLxt9h?{J zsDk${?PL)tC`!DT3`-1A+@2XKBcx!^DL7^^dJ>*O15__>ij|6vG9q}aq^|0p#$vuSf3V<*vqIB($#}r%sZgls}45q zH7w>7WX^k=*G9Os<>XX|$vctgc?+y56=EzXV;3w^Jj(Q>_+V3#GO?PT3#QAQ0*P2) z0A(aZk7m{flA!@x+4p$Zp!&-`VS%@imi=Jck0}hvZH)F+lvmDQPlli}I2wWf8xc{o3^uJL2P~Kg?uwsuzd~i4h)a^TL5rl?L3? zPbpG&KO9?P2oM?3Adu)qt(o&l@PZMUk^9X<@Csvw!&A7+EMPc@ghrKsCq@AxY&X;v z3ak|J5|^7HKNWsf^Jp4`0I7{&WM*|SS8hxHc;Er~ekG92xRC&W>9Qsn(*%P!Py+pJ z68Bs%EMV_|DFVboA160AF96Wsv0%U{a5M^X)Q3nA8f8!hzCkcNK0}06J@;4hV3<5p zH4Ph7tCZt6OOk_7i~PvI1Gbfdf-N%^&%s`=kzNWZW04<^QOQ3pU&e0PHsPTsY7AlF zW${Q5?&i)asPF?{!DE2*Xb=PvOgKCOo`R;pDWE+E!TAIp)lK7>eN_ZTj9B3XX?%GJ ziG~cDbOF6`2!;g*w&joJh8qH`R{Ya}L~jf|E;qLdPM6AWr?emyy^)s~nMS*>W z4Hbrw=and6^7{;dGld3GfTutJB~fS!389+H&x%)6RKUho^)Cws;Xy}BFg*bYJ8m3K zg9{M#&QA5u+f3(5lvfYjGNg%&(-!$*@KUc^A{)nXv8nxP@ek!s!Nm~-JY~)o8?zrc zAkf)f0*S)-pp*}q$^{pCItV^~U%9Dxih=2)WkYPi{HLkWz)y*K_EQ*d z!AFbH#dYA9;P?1&a2tR4)WNh5+7TKx%ePi&{+mbIbmnbs@(ezZb2#9WouV5ZGS4!MMpO>a`dXY?mV$9$Rqp(^QnNj1Yfs zW1Y9)Q@VaOy&OPL8OZ?XQIU)mQ|jAO8Z{d$b|ZEfnopnE6$=lil`b*8!7LlcCUkT4 z1VuMi1u4J97qBL+iPA|4@4d;b)W27slqR_x!O-_=1i+cS^#0w!C~Q0 z7bTc(q*E=J3y7~HV8d(e@Y93KV%O<{Uf2DqYyBOl9+{(Fnxk2@0t5zbpt!I@F$lZ} z426u3X#>Yw9zl@>s?+)SB=!qmC{_)D@s<0ZDygS5MOMF&SKsoSsO33=$+$m@nF7oYJm4B~%?>`b_@9ZsUb75mx8wPH9(1Samaj+d}Qd!E@dsDxdxP z$hT1ZVJZ6mKAR3adx+}ZeMAjBU*Rnedxkq-P_lBz@u4Nc-3xwwPi0+;Qa#l#8Q$S( zSiYKMc*sBfe9~w?3gZp5Sr>#jD5Xwu#^zE7hO15~d}iNb#Z?Ln^vDVm9f1k6{$n1L zYw_mQQ)(zCj1$DvNlRAg_>5IQZe3PZx)xj>o8gBjw!>{dnsV8+nsQ$5BC4V?vCE&z zUJ+FIj%68jsG~1xil!V&DtnaW?4H0qUAU?_;560pRq)ZqhwEDWJ;;5t^bxYwBPkwT zetB3lmRp#UsZuOtCu{Q8)P-SetV&GtagEEjY=vR4Z>7Tsr8GsPdFSaL2c@K!o)m>d zFHdohtZmbux4)g`uF$o9n15+%r)DpOWKog{NA^cn;a~kV+Hku5q2Xh)ckXaol*ka@ zzQ=9)eA-wcU8#wDFX^yFl_|C}!(pc~?{>EMAQF2ye#U<6>`*tHTd#EcimE{J7#VBi zs9e@7bzk!Fb8npsLL9g}tscRsmFrowDJvT`j#YY+uq@(BE-2Zqc__g4_Q{w5W9 zL8&vvy&}CwV(mvxz>@lkt*sI75!Qvwh(*5~lVQ2943aMnn%M(1p`iJuRZL1O1uYX=nxm!6B4snlOgK&N zw|IMMjmRkC>(bKl-6?=SrlAg*l;?X*1K7g8PH3sb6mh2R!l= zbP)O>jW>_LvkyME*S*O6ZpAuEqS#s*y{fvbaw%(^D748LXr;g@O%A@n8**&?ov#bh zx99GlxOw4_G5EGDK+&#onl z9Sc>L#;F=7mTAg##Qo@gK~ha<>lP$wRH(bvj;b7NF??*gCR6ssn%l|HRp%^4Z*|u8 zI9o3yokcq6z~6m*G2vI5FFmHz#a-dVX1?T-}&BZoC!&R)wKCp5px zAeEID-B!lg=UB({V4qd1)50HU&Grn_I4Ax3xrT@2S{)W?%akj9R7j@9rATF2O4 zmmGrzC+H`Z&e{w(lAarXy_5A>th_$4Hy}t~HACe3ttJY3* zus$fH@Df8FJ|fV?I5d~Khc!@>CdrqRo(BsYV6Qoa^_7)OP@-)bw0?_Tpcfa5Z1?#a z-V(|De#h@;y%aFDY(Twa>lYb8-}Y?nRyfh9b4T!e-79ZtJI)T45|#$1SjTH0w%Ug# z(nd9=YA((@G7tsLPII7J5nv-=K6LENn)2zC=t>W_3i0twdhD}1c*dqLe;}EWup2P} zhxEBC_z&E<=W*BfIhFlmyBB07<|8W`7dAE5ovKuw_Mq5n82#bX=u_LO=IDU3mQ6Ov z4J}6-!DgaCn&Eo*2X1)+O(ed|qz%7x*iu73 zAw4|qd4OL?$m^@rLP6>0;g??Bvd^`;A*!fa$eCF2qJoaN>%9o1K*B%KoiSC&?{q|w z(#jR-Cz}T`-hTVssNrHx!1Kd#+`Ls}wB};!8P+Czz)Zm`(;tY}6EzsH``Nz=Tg3&tTO3mCg{H3&Zv=64$=4!r90rQnQ`lQ&fv{- z8Jhnr<{GQZTFbKNxq5?Iv)eA_rZ{t+=2^SucLy(^^D1KEthG-kE5B|!@dx_+P5+gI z5AT~A&aLqqo??&Ays?Er?m^ejYkfVLmwof;N9^63zSgUw)lqUblgQz7pY$ynA7tdw zO&lEhF~Ee&3i5u^aO<4_DKFr#mjR1$8_7IIUe&tl#2xlyTFP?a_m12zKRE3o0msTd zNg29&E?+`8$NF)$x|myPmp4kI51vK8xE1kwWIbMkMCMey-O>9#WX9ZBj2#x{ozXGHI3I2oo8NgtToU7YBfE4 z7T4SsgDKY+8X+SNxU8s;r)S~k_!$=pq}HA`S6wV6l|5YcR;W2~`49BP2F=QM(mc@7 zeKiOtEB(ds^#$s4vAL830Y>$v!>d;=4Ax2826X5jZuB-Wy#7jF=I4~=1);q2)w2%W zLXLN9%iV>(a(oamG6^%(#>0CZ7q)|b=>R{`=lQ5mQ8aVt6OPHsmp9C zIVvE>32S5Hq^E^nLbHFC^KgGTSJGL6wV^8#dtvoay?gFt&HX9sU?h;GeK=f^p|9xE ztKk#*&a}$md#`R~jlT)<8&AxbbG|!}!$gXRF~+K=9aEO9>eLlTroDXn0y{nAJ+I2! zNZ6~^$hYl&^p+FX9Ikga2eY4=k;ZV6M;c!|7iKLcKndzPJBplE%L7gPm;H*C3o>_O z6R|ZR1$OdBwkFpz18@Nq8BHYEp~#D0$k}Q&!H%{857LjUIo%aF^fX4=+}3B)YU2Yp zYKPIKX6HGae6iJ{YFJ@waWHEg$#$oOl9ushP|;E>&6tvdq&%fOiF)=_@VG@~`OEp` zr*Fb9+vtjpD9<;u6og*aEIWVP!dNVQg#U(h*EB{zATlSSq?Jo5t>m2X@_{SYUhL7U zE38~fJL%&)+fywTKTDX~`o{BKG~m}b6T>?sC)-?bZ|jA{iK|v4C{P*G_ z)({vs^>N+~Km(!eZ--H;LN4J|K7w02t=m_auMIt%H#rxuIzw2tyVH;YcHTIBU7mRl zW#lKmJ7R39o}R0dXVDaOO0I?4R@}AbSuXR=CFq7+5UM1TUhH|oBh=KS6?rt{&UTkH z&yg~J6hBq2{2WTpFQm_+$o-ScXhTwob@Ru5Ld}6szT!e}gII$5G~Z3thir5+T6Y2M zS*#>0b$pgmuiiYe9THS6K&WJKz#dXTMT8a`eHt<+9VKjXo=EC$E3DG@Bn=Q{wlIi?LIK73W;6GFk`Y zeR))Ok#pCIRhN}E6Fvrdyd?BZRA_#Eb~xqy4va1bWciv6(;CsBxWI>x@=q??jhYe_ zYR=z>ci$TLUPwIMWj||jC*Aj_;=E>@=HrJirW;zF#HIRKB%jM(mp-HI7p4p0yy$f? z`CJ0ejl4#GE%M9Y`h9%sYL%gwOusp5Kywj>7j=6(2PlBjO6p)8{H>3QCk0#ig z=5+3MFXUT}+iP%r9)MBBXuo(MCiN7~QZox0?M zcr-o_3uNw8w{X>o>8-&>Dg#3v4}xGL>m6WlT&mjXMEW7Ck+ihjs!`R$n8(eWN1V@8 zq*#~RJ7Yw*+2XE$`FIlSOYWTIl9oLlcju{HN?!U&S_V^e9P^E)FZ-3b4+BDS7}Ob- z%R-+vtasTQYa;HuD_r!WyOMLpl&&3Fn37+qZC{ib=J596-Fzv#u1(atd@)*lM#eI% zKmL&M<~eVTl&Je_C%wdd9AG&3VtSj=CnMA!2s?FsYiKW3hfp)gd(hfzGVm7eB+Or- z3?8I%Z(GQ1#Q5mba@a{j{sEOX_8Y1)%akJNm2xJ|bL622V5URI&q*HVs_v^SE8Qi|tn&OP*QH~Jyk zz1&f9BIrvt()zOQihc2Q{K^aU$2mm+U1(=Fp)+we6W<4=Z>dWz7iBRAJiD8haJ|$F z1EV`I`3Ev!G`y_$2m1GI+kf63JX_DufjkAb4uzM6MoVijb_coq$SF+KDu`cleedr)*^?PR)U|QZm zB%gVC&u@u|-sI35{g!wRg~TD_rfByUL=yo&6%!*!RPe2XnR%2aZ;ls(dqu`Q&j%;% zc@Fj6Ehti1QHZ@MQnBaN*?}oh8kLPG``wZJIA|bo@Yhs1584=XIdVXWDu-V_c(`QE zXmMCDHmYK5tTs!>DDb*%=$A}zh@ z>%)ev^@|tX74PQazdl+|_#HmhuFU4rHH9H)4i(Pr9eh7wWz3~f%ek36*eWu}jqXCE zl`=lM82Rgg)eluJb?g3TEAFV0GSiMbkB;@5#H_r(#q;!t;4Uq$W#K}e%r}EAoS(7W z-RTT}ER!_mn&|J;wo5xAwO{;yjb9V_vb|wo_vZCZaWYayY^Qmm)kCZH3?i&Nr+Af- z`JEA+b29++ysspWh{&z>kpFUdb?YW0z!5+&2K?ppEsqtIjCT9zrfBy3o= zjX{Co*#Ip&i%U3-h9$o__1Nlf4i`S&wj6IteCah;wjH5wDc>tt-(S2VT~}j5kv_9R zy21VdC!v&BHmEFm@Hlb8)4s4~_>LpN-rmu%!@$I1NY>L4;P4sOs3(VLG2_SQJz{{s*E3 zeFR;fVFq>(ELVQu`d?(mRUSJO65ZLved2Y*99gi@#(6zyZ*%M^;b@7V@;6=HS?;IH zV!!Khe~AZO@T)NXa7^WiDo6X`5_%T)Yq){oF+4`AkSVos>b z?EJP}{k^=(pL8EQ6N6o`_Acmt-NU_b{Olb>+V4iAxAzt& z(`=D7Yix-TEOEt}zMvMdp!Sw6qCx6p@_MTvKde&GrYjS(I{`18@(A(F0 z>%A;>zfX2^aQdiTbxpstS}FSVHE%`Q;pfMTu`W&lC63=^Jc_2V__B;tceXcc>?NMNNsiC&hNC6$T!@;Ft=k7O^S&mRq?zl7FU!SK)%o6qKq!y%jz|`8i-0McJk)>`%XHr4N3;^CZXTm$mEh`-n9+Zu#A)SWQz*KF*YzwiEmYwpOk(^oqW%=*!8n6V}TWqu3()ZR=H7#>JdJ zP)VV{ABf#r2Ol$$#N-OOR-R1@IzG}8EpS2OJ+yW2zPQ1bR&-dEsG!IT^`6(yf^?|2 zFGY6XPFb;zj!Oz2zLG<`hBeAs=jI07MNwYH5D$so4v$`IyICey6?oiS=Vf!6g<7={MkMmh zmqN<2MD0=k)>z@qs_RtwVl#BN%zfATpQrLJgieTf658aKr?RR)4_$T4JHPkNmHo1g z>>bDe@wjvP@7%UEgXsz*C55+y|_Ch1Pks24-}Uo1q#KbSa2)F z-6_%n#kIIoDDJdSr1+oryZ8I&&SaiRGG|U6IeYJ{z1H3*S-(U}hf{NSzQjB&+wn5T z-Sn#m?Q~^mK6^=!OI;WJ7fL_ndfddT6j`jF-gn+V4gA}|Z$FvK32Epe7W>ZB8g|1U< zw-&e>JAvu4RtHF3P{|& z2%|fY$$O|1y%QR`BPa{uns`bONcAydJ@!y~lQ6O^*a3*gD3%j~Oqv%DGN7>;P; zjZhd(DL}}X_(kuSx|^36{oya>Xv*tngjpFIXomy2CfEzj7{IMspMn^u6Ye0qs6Uuk z;_kOjI={Z5(Cc{L5um8OAih14>^kN1D^_*q4IOjutV5m__wO0Q6EdRc%CLmUvi-9c zS&k=`Ee_02jr8ReO64h_cD7a_X~U)9;FPUKBZ8LSuQ&34!%}za;yv~$cMC{W%pvD(_rP507w6n`Vuv8>n zag$bYeL73_D`q#g=F}C4o-;Afm{eoFi~^CNc|&WYc7xB#+Wh;&t_8cD*I%1OYfHZ0 zhyfDFl0^NT@%hz=#J66b#-*nPIL#iV50`>L1UiGlCP)-bCH2yVBjGSEH(7zAu0Am( zu;G`C^)sNS@1&DU@Z{^5lgplg_7)j8^wS&p7%efG?BFGE5k!pFjRxunJjBhog?LW7 z;WnFEtc=5Qr8RmHSOFTO@5if9zd4;k6P@Cjhr*u$_aep50F$ymc`@cJRt?-!VvUiQ zavM?`r?ET%8J4ML^Y}#^vfL5rb0<~b`y&8YAg0!S1$}%t6T0BVN(h`*(PCB`}PfimTLeYsfMK$o|-FH=6_AFo)JA|qm5?O9HRhDup z`N=Lsnvbx8?kcR!tZ~bjT@K);1D&)e{@#?ts6eE9+H1_I)iRwWyw+r&wt%y$7O-?F z+uklm3h%_6y>WV)qq*zXdPLdR0aZ;uLFJ28z8{+BW=*f&8R!CC1b*GkZYE3Z#8nPs z?OngT4&|WP6G0(=^?_2chd6WPeKU}SRD~E)JcN2wu$P&c;R|&+^iLmiYix`nEX>x! zix!A#8W2L}d;beqTEne?ESZ~rAOfek;eWW!#1<>kP92S&LeFA-&eJ5RPU~#k4~B~09Hy-+E+&A zNv85g2;U7i8*$JVhv8dA5BNbc#V6ry95{-n6ADc9y5h^+bA%lFQQl2a`OxE<;^aF7MAykvKL$-xm(?^g-D3CCQ&G=R`C(+TW3=wGl>$tzBo&?8j+}|ZzMncFOuUF zIL;U0O2mtXGEWtoczQ5eTV4M&jdHJ#z4A51hb^yE-(;n^IXkpCctWL%msD5>gr$rP zJp-z5EgVv8eJHgmlTomF$a$vGmsU-| zDPO;E8DiJqi%u3>!$=x1(fwOE&p!3vk37a3#h-orcy>Q`6kPQcgpIT4L_Y%-Mu!$@ z?)M7{cPtf_OpmMuws?vlx{vz8|G@i>4ZZ6b{O#K^E5WQ&ris{SD3SH&n$~~?85eC=YoS&bfK)vYnkI-(Cxd1XW7VG+NXdwV*Im=z zfwAJnpIT;Q+9o#$i-*FI@UUu`;8-Y_q@(#&L^4xkew|F*_#8-43Ob|FZBhtI;5a0@ z`)OdG0P_0+N;g)FmH%O<81xLtd1;W)@Gya2KEPXg>zQ;HhOzt?TQXXvjtCn9|A1E- zNWfU%`!asmcAr#8>mQ9C2O9LxsSKzd01Id0oqaRupTNCnS!6Xeo;^xe@HUz26=584 zO#p9Qx7y%L<9CMvPHfx`WXOzK$FJcZ{;Wlpk;Jo|GVFK%SM_OQy!KrYmZjtHk=;gS z)0rMm{Q;BLhpS$2($`wd@Ct%BK9=){W>fKA5U6dqS1)QHBjp~~d&!cZen|fDk+e5S z`%SsaylB$6dqQ{z?>hJtVzM$M=C46CBQ7p}RP{kLk$$nwzHZUQXt3tifqZhj#1PK4 zlfBuP8#NSMIH%|M)NpHw0jjb8t^V#q{^i-z>E|-~d6Ps@dgN`%a%|PYx$YnDY)o}i zhp^c#7-%>2e6Xof5r^|8Ugkas_v%x6|EN;_FZ3y6sOfpI2!a$2v$9=^&Y(Tvgy`XL zN!kCB{J4x7U|P z&j4nyBOk@6=4-*ext?P#9Qob(uT+Uyn0oL#9TEvX0^d>1)`oRpX4)}9X+bNo4M_Ng ze?7h`jf}MM&S+;vimZ_k^M=%d%3|!$1BBC93#}Qw8G!nCB!EWJ?M%}Y-lLS>;NS)0 zv^zGIJ-YAzYQogB=DVtGxEkuZ-9LbnPo)FzlH%XRIgXKr=uHG;->y)lzFu+c!*kEx z4NOXD;~9!j+yur8@T_eAZhuS)t%!UEAQ%11Wm>}M*2w*?#-WWx7A9e6j#7(^@nJ88#ukqBZgLTfQp^zzLF7klzcc#^j_v4h<(Wcu_nu?)?~}-SR|_7a&pE37x^?;4V(;E4(4FhQ>dw;+Z&PDJ~tRCx43(Vz9U< z;W}aZOOL!&VdCYAU|uHsMmxm<&4TY&) zi_zH$e^jlA`M6>{V(+yo1tD36a_)$EYy|zMr6WS|?cvt{vP3Q{>c}h+s-X1;bq#U? zkQzNs{SppSi|Jf2b6+%mW9tY#4-0eg(hbEkKt;^$>FgPBI8S#lg!c^ix$!7b^hmMU zec$;EDAUuf|0#oJpzMYN6|rMMu9Mkqxk2aL7UsY^3r&9J}2s;jzGIS1xne zK$(iS!I~JU?jaWs2t2l5XKRWqs9*}wX8_@dN=CyvP0hi&y-K2CdUm$Qbt3b!XuaP5 zI(PV$tz)dMN47)GukNQYr;tVbU>yEbinvFosz;BFF8@1n+&-F{*1v{#qy7+Q68`p}?I1bzM{YDV?7S zAHheWM)&D+!#Uvl%o*BAe@X%R({Rz@OB`#?&$jF>>8$%?*dNrezePT70W<%kjge2w z6bIkNi$1kZKXv>cudiYvk@VE;_9t(SyR|oUo^eZPJjGkx=%(r4@ZLcJvtw7mscCm~ zITUuqHe?Q!gNlrx*2fLTc-ZK3^6kJ=h{V}~m1R23&>zj$539FQ-s0?@Z1YlgY!3h zJKC6l)P7`aR~Mq&;-vembzAhvJrxpN9vmaPmw4GA|HN2$$NUV)mYtb+`7bu=Aoa=7 zZbzoiO|YhPO$|!gPu&j}Nxs#>XuBhMyF)t0c5r5RG}&p85VE4ogv3IXNGufWVOQYGunN*O$JpTZ*s^u^19a z&kdSu?Q`~-_t6edfdng6hOquX8tURbqqU|Jq*zm3>bs#~+M7}&7oB#T_@C!5pWVM_ z4mpPWeMccU?W1+KIr-h5xV+zbskRnhNmKe}QdYtG4!74*hp1R6WZs_VT4(PWfc|d9 zN5-{AH$ACzto^m5;4hbSQ*!8>rkqNQke`r$(Xxrng1*Mzu}LvWbLkIDNiVER#CgCa z6O)r2uMh~hmekf47A&+>iasLgIFAyG_Sfzew-I6py3B-OEl)YhlGVq9$tgNpKUEw@ zaL6%t(4z%`9tK>3HYFEz_o%kkZu?9}LG?TT-k1xrIERICC;vO8!cZD@V644E8J)XY zi7jaKVbJtsEap@qc`x?{^N(ZSSa7#tT}NV4niaU&uf~?-&kG<^uMIa$=`QAekALy* zr^?;2>^p^2S|WePEycU(?%uZsxD}->?+;QBv~>b>s6rrkY0m&Zib0@mG{3n?X6Rd1 zyE&>m8=4BWQ-9ThqF(tkXtbq7FkgppD;aRqLb-HP@b@a-g8BM=Y0qsa zMaR@G3jzxw%fUq&rB@QNNb4vvi1CncHUbe7)ZLu&V}eT0_W$`Ur_}6MWId?G(IwmEq#r(PUfLoW`O0{)Lcy z(c3@kKV3VPlr?tV@DDj-N{&2vAOloW?COBg)eZCF7Y62J?%qyvYjE0n`Tza%R6e^N zz!Gx&X5cm-rv=D|ZFsmL9W&Rx){OEDxax(#ZIs$#(>_)4oT$M)!LaG+aAXkLz0~=?^I=NgZG@fhg)PHnF<=2|6uTy^UfHVv^@i+$~shk z#=rhGnRq-&KRF_xj5zi6pAOz(-4k*sl#(toK)v`hxv&r?4t?ivrVwluw&n*q^r+}P z!|HQ8ZH`DB_2Qw9p~`v&h_oX!wE_PVz=*9^2n+6J=|T*`99GL6W!~$23m5AhdXYhj z-<&~ZLWZIcisnJ|@sF|=%2rmkokm(@*hqTj7y0XZAbvH&hhvJFhFggUPIKcg?B^q` zy2m``Vf^;Qso#whzzGa9xU6U2oXK;g?J}V@z0TY=|7)NdDtCX81{&_CYFFeGa#a-) zepUmp!5AYEGw~Ud09vM8C+3IVAOQ;3LBd`5{rm~%B>Vl~Ur&Xe^6MJ9Erx5_w4gVT z=4~yJH?3iwo51=lJ{ZrBXyxqxfgM)I!TkPc`AJkN^(?$OGF#n+h1~_f!l70V za0RF&yFbzQ3l)z}l`gp&2A=R9=zhgmB(T>JE*jTmRr5{Y?{*gPI!h(c<|1A^kJY=k z)HH_QEAe2v*l~ftk$;qul#fb6`)m~8&3ZRt)u!%T|uBv|@ zIm}WfvI|ruAK>kj&FyN6q~hQrHPLM9oXV3G&9e)c6UXhx&oe>C55fu%c-7Nv5i&)+ zzap&7HQVOeIH8hVpOG_Md}WY7SG8n=xy|XzQyNvAP9zw5F;gWiGWqW#24Q(tuU|mW z*2yk$?r~YbJa3y|QK>w^o5RMgsZ`&qt>s6UHi)=v9Ey>Z@BMx8v2!Xr-*G2- zC7gC9P_x1e>7prvmyqzD?WtnpZdU3i&}MQCU(6}MmPc@V|s>I#j-E5DWGexl%$%kQc&=UCU)=l5$^6qw@C$*~%uFxP zk{9k~MP4hBzLlQ+ilwqSKaOJbHWJrp?#7pg=*YeseDw@4WAC^2p^Gz@`QU2bm*^P6&$!WoOJHiC`nf!i=uXg|_^7eKL!T zo}Si)QkQGTV*z$oq-|S2oLJefvX1a{DvtDemonX&UA2FyI>x_@WovLj`=Q6-oZ8T6 z@e)Z)5hr;?uwu@OYNeJ7k4%Ca_%|y5!fdVv8K+6fN@v!JQ8jW-mL^I9XP1AHIw|eq zS6)VdQs(+p1(sA{FPrJOgDAZ0-W`3Lnr_(;v}s#=W~dLxKkJW^m^mXOljbeWuTzTRkmWu^G|3=)}t}=Ffl*-4K=T zzk^JJ!noQo_D5K-(k15b`ZNIORFsvIdsIM#L^D(Ou+iK*;&q%Rr>X?6UMV$y zSz<1hzyh@~NfHfWWL7-`Zom!)xz7M8>v!hTadhdvpXZwF%~{xYhzq+DMq*T$sY^B6 zg@sy6SCM7ftefwAz!T$Y6O7gxH%38C-fc6ZdIcUtDzh5F@+cjtf>%I-{I0MkCcb%G z_!)SEPbmxZE2=kYiP25#0FzN69DEOanEZ!Akpudd3KZHVd97fg9Jp(z(yR4#Pisg! zQcpXA-3^5CGF*o98PKL9EBE_hX`sWhcAHzOLjwxUoNLTgkAO~a^KP#c(FiGHdv_ zP3(SEi^!6br+0A;@vm?^1X|n+&|1F4A|&4lF|WL=I?H%5N|njZfTO*Rawbv#B|M+E z*T^7Gv2?av6Rajg33sbMxAe!4R*k3?V@LL{LV{?yXTZb3Gt&Mfp1^&UcMi{hebqFUsn9tx=^8Ni<36r|&@-Sodj^}fe{!^?SGic=aR*SIo1a}i zQEOi)_%Jt?_tw(A#_1-tK*-N%+nb&qTEBll_YBCR*w<>yq1#j~*U!R!EttrI!-cS$ z>MY)9>8nG77m-^T5OFYw#f(~ug0x<25nXEZrV`VV;gY?tggIz;3C_Mx425X;8R#l9 zkHTVF>OM5Vjg26zJlIq7LlFshp($-yLM~pWCtcVCsRrvB!!r7QjW#!zm$QIQO*+YIaCDu) zt2*Er*ECdXT-d4bRwap=wcT}P%J4}ZT3x%1Y{HghFRymd%^qzK&go8RSl3*1&Yzex zOc^#gf9I23dxhZs%zg=n0n%okEW_+l^{Pf~@B0O>etqZ4PX1rKu`fKltAeK`ttM&;grQbRf&k1JSJo>2%Ws15cg)2bUdaLP z#6(&BJzGF|{xRU4voSCSwly5a`RG2F)S%bpvVeqc>6Ysl{Cint**WlBwK1*H z@?~AzB0IZ+zx zlym@>sCbS-igpW&F))WtgFfL`f}&ZsGCtS)aGzNc+Q3fjNd3lxsMN;uUtV(zlL#xm zX19g9bO+~sw%|`-HAWi1`>nTJrx&QB_!Sr1P^6+`WhgbEMJG6h4;0`sKU2|T0`Ww`$Ofp;*-B#*HgkPMkR3_$ z%Vwj${DB1ORsmePa#1?$OB@F0=?61`VPAi0bNV zrtF@Dg&2h~Q9J&~iq}eT$&&b_5#Ed5+p|WNzf|sMLpsu?R0VA+KjH2oxjDr>$e@tu zke7j-r~hgBq+g=JEA2D5V{o-sQya?x zj4rP_LchyheNK)4bLEdEG}>HLW%vweE;$i@j4$uj4Nd9CnRn&k%}wpBXiZV?LpfEhidE@9E-JC4hfee;(MPRdv5sjT z=sKx@2@xrtsc3<0>2A?6_bnjBKA?iWq}M-a)`OenoUv9kZNd{ZqLAMPY?Sq>x2JZ<>=J7=3N-P zA2E10(3MxO^Z{RvGSR71E<{GpegAR(-!I9X=M4u%vVWC#T2yuk_zD*xwXewBk%dQb2^pWc=7ZlIF8g~k@;p(&5>B5bdOI0z}p~1tRo%-H{r_dmquu`&+L15$7Xu4hr zNfztQpbo-rXC|ctEn$z=$VDnAXb+R=-J7b=xz&gkv$xUyNZS{sV@zM@ssx4dz}}J( zGwl!!=xmD|(PQ$p1zOdp2E^=PLV9P!?zaaG{1gx+SMiv3H7#aWN)lZ{|oWtQXX)k5>_T&qu zKZhx7?(I>Aq`XUBzt?-tVN6Iv%YuTEi{OTyOD132*VK#-R879U{D0>@_zh||nLA0M z9g^X1nq>kK+f+wFWROql!KU!L&h&N9p3sPe19RW;GIh2mz+s#+=LvKF^)n##_X49$ zb!xwJ2!F&m?S~}sxSJj zf3|@+>{X)j29%1wlr}nlD{ZtxeT7uf&IycEzng!@G|<}ww~9W0E2dzVu_6~_}T}~GB!3@GIjDyD{LXRC{)QCbQ6}8oB{m?hn(@ zNcW%s8!|q80S*7r8K8DMve%?sd;7P~Rp9)>d3*T0zXOXyNm}DEFAgxz1tzh&q#YN85!(PDf2un_iP*zx&Dg`5{d?qCup$)eLSUl&AJT0&A(& z+!0^uoNOlxb96VU`p6kcJ&pIVC9J-2GIpGA$XEf`?99}Oouhc87r;@oQ|lmN?-8GI zNbR+Pbg}^bWrU8WKIUI!$hSTpU!k zF&;SHa<&k^OMW8tNLQ095YE|_?LofEkgf^`?7Iad-q?VJ`UPULE5?jQ=U?HM*m!7@ z$yV7ODFC-)6yaPZ(O7Ea%)RU_qMR~}vG101TW;}{O?|hWEjWcZOPEo0%O=1jrij1F z^fv>N_zRpT6)SdvuJFQdE<)g70a2FM4E&{RCF>bMlB@pc*ffK$?3SZa#_(zu4c;K= z&C34(6#t15jgDq)6zU}nkC%^e@dX<@mCZUhC1wS@Df;V{dn-c4W!R&vg=@%O{j#z- z>re$$@399&Y;1H0jpUsQLAxC;kzoK2v- zRE31F4HyU{h4PaYZ`mfcdrtPLU-22xYkuc&IuGg<3;R0a8>d{5XY1w@2`ql+;y6vp zTBtO;K$})IJU#-6ke$qpo@*}74$ zCZRewEy*CNo@-VE*1rW6o5Hgq$DB0=2EGFZL=vIZUt24HwSzJgUj4g! zPTv^ht+W#-H-drXq(o{Q%XJ+pNU9K(Bjcb6roh*D2==!W$>}+{d5XXV%V79;(p(32 zdY${L{%iCBp^?|ZvQd@s>c!@oy!yUs*SiSa3R23>IAvA^S09(S8o}|3-sJx950u)| zP6wo%rec~2&@A|f={QIF!asb=Xu^B^(Q&cCfgc5TtjNijLmW~F3bxv_lbxQNynEAS@F$@_4A z>L)%qE8)sad{Pyl)3k=70An6*6xqz;*up%s%bJfTWtXl^37$y%;%xG>fr~>xS}4Y! zl_oRDpQTjWpd|4954JzSjHRGgl@a(weB8-C5<4AXy1zw6U)haBt=dH*I~%hz;1x1g zfLVJ0qM1OG&qtBD+1{JwI2L;Rwc5T{EGI?q)q-R3 zVC~AL8G;`px~1!>8Q}Ee14jfsikFY;u+o5F08}R{tAZolu7DiAPYKojwK2_Xw2Osp z5~hu%BLZ*}5LPcp&Q+>DiJv9m1!#|rSKw%~_V&c(ZeIxNUL++wRRn@rSnFH)X zXxTba#l`9c!|@$!6vJ)ej4EMe*&A=}!*3BxZ8R~#HXy*E{$=%`6hqooNuwmB{CjKk zo`)(1%_thI*AmWvw3T{*St_s^uze?5t>*ww?X4eM6`UYj0P0TN=5>ziROnuwSSmae zG6{21=Q}cG*r}~F&US1Gl`siQ7PpKI*PrwqWw=f>hZT|H${H3oQ&-H|?`BJ;A2YLU zS0Wt|r{Lx-CFFQKw=Wu|%(iB2uYW5Z(I7TfEfbGeE$4s+{W`KQ5Eo!S8UI?`*LZR} zlr#<1`7KgE*WtbGiJ9xEb2-xXCTeaOQ)iOC%IHi$t7LNCoJW^=k9AG0T^)~fb;Gej z`v?(KFp9X%(r&WN$mV#t<^$dYU+z={8zjnXGuzhpRtK%Y_Gr?B-i!;i=E*lEZGpjB z%@m#rE)`_^)M-J)AM+40rNau7fSMtXm=0mE0;r#y_)~G8dGv z7O~@4ECoVdJ#v}u#@O(9C+Gb2x*{i(1m@*~ z0)t_J(?21?N=U2#-Q$&Foctf0T&a@Dj~u!Cr+ck| znxeNIiv?DNONPVI=b-F*WxOab>m2XZ6o&n#V&MNut`ZVsw zLi|)x8p40jR(ytrQ)T35JBq&+A;cb)XW&Sn*@EZV#-`H^kmcJ!JY!6HFctwwdm*$T zfiu?5dmM60{VUUDHZX>j66W@5)LM573DidDm>0OvrB!^4T)2=!58_W@3hKaDnJ#PQpC`c*W;R3_y+5;Sr}<(~7dpNN zN}$qjh;Z`s3@xVAP_RxE=j~3fEeE>>Ca71Vr8xBLz>YRhYdQFH5G7ttn%WSKPyg{cD4sry6P&=H)L2^@Yqre?s6=_^ zh{k&;)lj{{U~||F+v8~3MZsr*G4qIf$P0j;0q+JL{gJX=GwY1jM>h!axdPzP?1Av!|Yv}`4+Zsnceq#o%Nv)_Jw@7 zb$2%K%mf18SMw`EUts6n>?nw75oI^?^TZSPvAjJL__Ztb7)WMgd^h%eac@bniFYS1 zrNnHPbVAsgWv(5UMUWlwN-HHLB@HfumoT6dmtsRQiO2F!Fjva}USwW49+`ee~lFOW}mFaJLO*~WL zzN_B96s+C$YlqVFXq9QdctD}>wSJ|B)@1wW#6(A#g_kWG94yM($3RIiso{-smDuCS zj?C#U;PfH4)e!xMuaYX1C>!OGpc(Ojj3Z@yoMZXRiX1OQrYmKHl^-v_4cvwb)D|#S zS{pAyS@!?@p45}n^)i_fbHFEU>yTQ=LG~X%d>`!#bHX*}v8j{5UaA1BKQlkvsr!Lj z6Ef;a%e|P51NC)l3Yr>kQB?`xC>D=^^Zu{~-^2FzR@3=BG3&Kop|-ID7-K#*XF;kM zj>01>idgk8W+sO@N3~p{HFe_Mtt+x1 z!ff2JU+<6`AN?I(rAI;T92b~MNmdX7G!)3)b%DA5;GYVzLbJ0g6~Irg)b*V84nQSt zevmLRtAa2j=^4|P3BDy}0+qK$)DyHIv*BpUd=ldsmk0N~LS1q2COmv@^6<$4%%E46T3t;D-Sz(>WxY=<(>(jdT0k1RXUBvD}K9)Ao$0|gwa!i6s z6!S?o?HkVnca-MciTPy7fx?XRzSFNWt!6IrASp~yBISXr z_QI6=>bOcixfRg-gSNkl6bsY}l=pe@D`AVD7B4+(Xueoa+cNnR&s*2UT{1ESd75uf z>zcTR%SUC`krBOOyU<(KSE(Xu*D1MM)+TSM9T&<3qASMIYo?RTob29^#gzlf6W>eJ z^LIrxB({e{RepIonD`+BLHs+TtPP23L`T~n-cqT&-yUZ?538*FfzA#tCj;tE?v z7Lk){4eO*A8&)ruzD^+5F4-%nSY+FcpA|}PUCF>&;T5-5F)v0rXJl&#PGVzQxaZch zGyOKzC+J{@`c3?1z}NV}=JQPzX$&fxd2!{a2PA!LA`y1d@GrVluef}Lph$1OL`SGy z6u48xwPwNP!NInMB@lhyVgqbm)ps!AZvNKH&>AKu`$oO19aLIiyS!jP_Mzv42c@Z9 z`=X;!n9;igFyx)8T5!oAxmgXyhe-GFCG^pqrXSt?E^LmdYrZxwVT<@_yA_FX|H5Yw zhu1=&M_bI2`EG7M<&4lyNW}ux4Fj@)gsXK`^ik$pkp2z(tOf6veX=M5*0ePw^C(6! zlP^#U8+|~0qYNBa_BC~Z?`U`CeU@<-_>9#u=j2!FRJymar=8aIZL!^0XOy!8Qr#{^ zBL8ldBOVarZ=jvS!t~L2kSU8|Op(WMKU=O+cO9>04#(w1ed?NT=mkCT8-~&`MTWNo zJqoX|B*BMV@ff2p7#A={xKjOfW1u-Z$2*tCns~J zT6_IEY>P0wRq{1IvcXd(l@_z&=!(BdWJSV(7D^4{5o$anr_&YHR0g(gHDBbUwM%U^ zd*&Ylyg0f!(>k8);LMgU)tIOASQBOtw?h=(M01*`$n9l*|2JA7Yx0mjj}OgVy8!Ez zsvRcbg$^sbW>!iE_{zVP;oVeYYmuA@9Qd5@3~-z6YTQ^SDrtJ>npnwns^{pqO8Fy5 zJ|ucgPxchleVB2bS@aC(Z1Fm_ykCj^+SFDje`$CK=aW@qAP|h7djW-d{^R;doKp8= zqJ@WjWtCEgmQ_Ec+@Xm(Q=LUhH$oz4h7NC;LLOA}BAcfbsbS(ZfA0Wg>sny>C z0SwT_eCR2iRY$uxZN)Oi7!YOpnzwge*m1_!pLVm)CCrg=a3Z8T3t(~0>#0QNdXWXa<=@hswxJ%jXu zAUAc%rDHp#FoIQDf;NkQw{%UGJUqZ7YwFwJfwpEK8vZT`uLS(cZPY#9ymrk-WWOsYSaUfi@q9QbO37k z30YS>IE(!K+tldR)=MX+ko039)g7=dFQEPm@EqN^N(p&Yo`(ojv%nJa*8UzGg5QO6 zQM1=c&m-#rHv^ACG=F^IVDC%Rkf@JbMy3y-Z7736$)K!aM_qv9FP^l;eS1@u#`+gn zoTqoLD*~N*cPhkv-cljq&J@eHAX7=bq%>{3d@4ADPP|YJo5uGhBXt0bWi!_MNjFi$ z|7LdO^*u=9fn&*iu0H;V5R#L^rC@nmyhicIf;?xiKwEX?n~~w;SVQurE?uw*kqn2M{>AAl z%s;6I@bE|Zf)<@krQTke%58E`Cp{tPI{ zd3ebL&VqXZgMvJMt@A$d-Nnkx558BUv@QGe+mrz1PS8eerVezd3KSKb$uX|z`h|T8 z&gBIC#t_hC>#v#`B^HByq{>L{0D7x$Dd%%tp@vw}Qj z5Pq=+u4llSI4xnOiMK&<11fIKGI38vkQzB2JF&bWK%oeK^Y%f@GQDIbn^)%UJtrV( zCD5Q2pi;oL`;on5dRJqD6Z#_%yHf^0F&l^@=7k1fK!^B!4)to>F3QBiwy!b9KMO!{S&!(&=E{^lOjFVI*%dlv27>p%) z1mW@GX5(Tv6%^aA!mMwf#2AOpHL52v!j%doPf%6(^Zm9yJX#HHu?nq;2xUJ5=Cc!U z;tmyb?_LI7k+OCu9Sg2Or1p9}K2j1xmpCVyzEgcKn6kG9EIPyXquL>PjvgO^~p&w!y>58_@UMeQlLY>|wmlt!_ z8th`Vp-aNGo2{fYL=*Sy586JfnE2`o?*1?nwoY}I?)k$wC}ef$d}AtK3JKTp&S3sc zoKwuzcA~V9toRjxehSxT|vUG}x4o6Smrlvbe#QzfKSeY*QAU8EAfqwcEk-^48%gSv=XWES2>&TVWHq6O`U zuH`M847H=?%=0u~7J+T`U&uEB6^RsED^^k;7s5T`^G*k6X+M9ycp#;M1jN2?{cS7k zg^7Iy4Lym;e2&oLLT*jT@N({_>axt?1^<@!`QySVgc zQh~Lrxq*KaYP8Ih8|mHOf3X$Yv+-li=W_Bp#B%zb8Y4eg3iOIs`C3s-xxh1|ZAoqv zQG^=SCZF;P6xqE+sG8`*mq_~a1tJ@`*U2i{CAD0lXgpG;3Br_PUe+fX(71oS|5%P! z2Go<=vNk2t0{>dBmRN-jg6#sGjX}F89hxfFw?&Qodz_Up_`EFW9W%G_U@p00{`pIY zJ`lYYoT#3W>u#(SIU=A_p)p^{r#WY7`d5ppW?&FFL*tB9-wsz6e$7}ELY9RNqW1Md zQ&v~M^3Qr%0vCh|CH-moILi0~S&pR`Un!QJteR9V%AH=p;|W0u&N!?`$n{6iE>(_| zsUX5J*st|giV+DG`borzRPvnl!)9mvPnjMsjxR_~gc`8oi+n7#t?P zb)&OHY$nNN3np$}sg9CC`yMf;BG&MZpAsI35%B1&8SiT?yT(_n{oW_8)|Pa7yUi&c zu@tGof=78fv82O4)!4;dEIqbKG6$<(f6N`=Zi@joZtV7!gkpiey-&@k|NbYkvTKH1 zr_cLU`*laEW+UuWa4TknTa{17jx{2v3v?wBVU7UEsT*ixy?Ff8M=4jS?l|gafUaL9xwV60vxCo~AslDL| zm$F0$tX1?BQ8Eo(@$gT~*VoFHf%KWtlKmi&FL3NPWqx@L$59l(+D&rTI`KVg1TT`J zq^*E*67}8TuEKpl3=WY}y?QqG zXO~BMPAU|u`1uvh%HZV6=AUG9f7FR*Sks^(89uv(cE4OdBxAR9L3~wEW>pw^^fEZO zh1sA*+^Ji-!_18L*J|Px0{Hn=^GpUAz{Tr54?1{rE0))0`aQNAM2_@sn|2W}W4)-No!t3mV`JY7I+ z|gJ{C+~ctPmeobgg+P zq9F6x-#Au$cohniQs4bNr(Ylac}=RUNTug-mdZ{l(McTNH9M{z${xWY$*@(ITjJn@ zDKawyT;aMU+7%Ne9yeESy?Z+!#Tz9wEH+-HO9%WvUwNWjBMk0H}L+&h=R&a zu`iyHD$PPYWNfTRVi|t#SuK-Sx}dV{97ceAx-%Mv#DGOW9tSi>sCYA6;)5)y5aD@uDs6ZpB@K z6nA%b5AN<(EVu`EcPBW-p}4yicZ!u3sND2_&bjM;xmmN4$;{r9$;e*syz=~(bAg*t z3*`tia2gh@9tcD^4XR?X^epn8V^xJY+H2c{s>3U#jY+=5*_J99AE!FvW=X(W!6jCr zkGk3xJN^dau6!>#Ym2Oty&m=GnM;1pX~Ac@tddRxA zKjNl?2)$uxi6uV?vzhl{RTA@R0_`kD$wbeH7IV&hIQ}%7RefIGcEG$9Fqme|tuLEHRzp{%U$|BI=G(P-L+k;}%WuG~gvilP$XKZ;{pIr}wf2BD!!!uoS zSZ4~?n{4*xG1;b%DbYb+#M35wr!Cx$!5J4>gPe3Dp3yxvdM7t80QlWm@L_7Lkv0#E zoBhxr5(@+_Wb_ZJE1Duy@uU}wy2n+> zY<;$HDd9?|yQo5~Ll4J67RjP#BGDV=$u-jC1?SnP;6(6jr5C?Da4W$}@YF0*TeGPk z0<+ZNm|{@rs!S!zP7z%B>}wJA_$T0UM=h=sXz2vf7}cSy8P_H-G`E9<4T> zMo?4F^XcTwXWCc#3UVVYH2y3S?{{M;@4hD60TM;z*X?IG%a!q!o0OaLkO!rd-YR!T zk?JmH(SSL%^vQ0cNkp0X1i{bpMs*Z-?^8&s&gg7%(v|De=+dVpFlv|wm>Y#xic)-g zCzlyMG3XpuNy10zJPBh5Iy+U9n)UERBpQF|s$U~dlTJOxnx~CM`C%rs;i+-DKIdbv zIt7_jgfsjcLtJQnmb5P^iPvWJMI!l(o!~J4>I0R9FPu5xu-^&U5hHgn9*}rkD{Rg% zTa2VNt@~ZdnGh3poZwtik@%bN_y#kn(j=fNEmU*jTet!r{V4+1+UiQ16_WdG8!%1w zV1{Hy8-1s>8_Pt3ZK*nnQcSY%wY=lAFO8x)c+)%W@6cP3eK;FUs&V zi71RMyv>Y>{FB5(*e1%GFU|&gNBl+X|POHO;9Q##MXMhD=4Tm zApQxv>lIZwSd}Aj@Hbp? z2HV-Pb*g|dL?FBxi*lUk73DX*J5*nkqC*d=;Z!2I_8EJ9(C@B#P4JGxX0Se19a~Sn zOz+ORGU;KG+Y_pjS(rZ!%N{2>(ciwcf%16ee~cVe;_)#$n1iTP3U?RE(-|Ys_A^vx z)XSS^X5Urcc-X6GV~0p1re@AbV=M=bb{2xXSHC@&0mej=#k3vlr-EAdrAr0m;+z*$ z{folRmKZ+@J~nmAr5i6N@J;cj8bdDR*ZE;6U>8ou$`K}6ph{z$mPbz$Bhlg`+{$Us>N!_Yr+Fe?Yu3e-(HU-FAq z=<-O_a_XiL6J5qE#z|AJfbqnx>|;|J3NWi%I7cI3TBr@Jqy=4hj)K^XSmg~o`xWJyQeS-pAD-qVT#M@+a)P& z67>VU?VYt8ZD=-&W@noJgk+{uDh%)VpWFi%CYwv)QChAfjk!*9P<;b58BNT6aJaWPhb_S5T%|*bUgvbn)vnmI9hwx?K zs!E=7FsXYiD|ZCTEwgO2PQFXXXondw``&P>U&TS|t)N4MHjC?t_BN=LBZU^*9haXZ zULq$u5LbuHLb*$eaoV8gQqI0v%<5xA^BPU2A5gGv*t;b}(y{Y9{C<9jO5R7;$sy3s zZ!D7@BE5R;B|B76ZbxiY_^YPIuVXWPmB)WdEqoD{9lUHW^|^|>A`c0;(&oCLF7ar=*1zoJKW- zms$U^^13C5&_>ayX+JeA46w#fJ}VYh>&a4>sk#Wx-D_IG46)z|jyQ4mRxM)@y&j=A zNQAs|Le{ylbOYA2g*V?xcW~9uve%1qD9OiH#6RPvWPfZ8%O+{qFk^I7tFf*eNu88wX#0{xN1z!uG7yq?Bo=MuuOlnpNC_>9*v<@O?N-;*TIe}D-bUooGdWC>6MkKiwI((7I|h+>-`;zcq( zhx1i3@YqHGte(zpUk7-h(e(kWlyq3vhLlpAqe$8?zlQTVw6#0oTe`(W>zDCk96fHj zkL%4d&$Qw1v0O=DD!)F)*mGai&HlOC=D5~+#-wX&E(yF2h+a#i*5EO8(B?=91KJ5YM~hLrBQTW(?xd*GUOGzxv;lqC-@ zK_u1@N5{4YXKo%NqV}8ko3u#T<`jFDeYP0(?f^*<5G49#-u?@m&w_m1ACOxJa+9ix zrLqDaT{L^TJXbusi_f-TiPmnf^5*|AC{Do-spO0g!0p3ng{{99w z1m%?P(r~k_O>!Z&WdVg?cY}&$GU+z7vW(n3H=In3+Z&B*tY@4BboNgVJbzdk2lz2Y zmalE52*06Y|0q)mVDOb)hP>nu-)k4^r0CHJi(dA|P(F|DM|>u(X902IZp2<%!?|QL z)M+hOprYYZl}7TUz|zS$#$hBr(Ind>Vj>;Z9^s!v9FBWg-NT28zhn8y+!EEY@{E&b znNioq73)QnKQiWXJQb3nPEA-MLSc89%=nz+Zgkkj8``2&@r-d?Be%bZ9p+^acBOw9 z7V9MyTU3M>5Ra+0R4X7F)$wTPq3oyp7p6~@`W0tCbgz~I)Z9Tl9VC!a z=~U0_(whNmN3z77lhn%%;4QvBpW*+7dGEv#;rRIYa|BOJX~@Zm-oTN`cxM~Lr>O}S zC>k4Q8vi6zPTgYzJC>m!_)~(FTVv;&yQJ%mB`#305ofF|JEVRHpIA>SkP#u8V^xzM zgo=%{aEd&rtsv0Ad8h}~^lOMVDqH3@eBVij`x?n1^^=2QfuYJvz$`bUiaN2g=A&?C z%k_E85cJ3=-h1+(aKHaTpy1Vmq=Zav%@K;NF=$9U|uKX%Eq$Es5P;^LN##rD6K)oX7ePp@&>Fx=9d!^(DGs4=&YGU0nmR z@|CTz5rrQiF{MP!#7HfQKo%Bpa;VMW5Hq)`DmAqM_n1p$YbLqdeuc+ zh;J{YrJO$Uj;~>G#VoR-7dWVX<%=D8sB|IzqKZptoCy7AUFo;lCAVF zw;X+i*iCavtMy9Gs%Tj|njAY8HPS_8dPU{)04?F^u+}ZlZlQ+p9|+=$b7aGiZuok0Qe~RUaj_V2V3TK8u=6!5Y5=_`K_wchy^bNr+K^KcilHE)R4ahskToX^z>cf1CZ@hFyb?B zv0qR={gAC?G_NiG!0I8@F8;f4;}eqk4rdEnT%DHM$~9_&0$SdH`){kj()KW>HlFua z+0>=oFHAwFbi0p<7|UHjNNdZ1w&Ajm7`@OU3VD)2+6@~ZD?)r%>Q=5o_SAMk}(6?I2YH1P+cR_lmVME=A!Z=uW< zqTjqZZ6>g`^Cj4gA%xqKkdK)Y70HtXrymxleUi46U&a(mu7!PYnj0onL%4<~f=cBZ zn<;qcr<^5H5&MJ}BYk^^%f#`gK(`lW0Qcn6(dOnG4W69bFM5;`TH1_-tRVoKL(lBzqY*10P(Wv4(8do5O0N_7Na+*5F`je+6+^7ALswfh zEnsEDQ-;CPa1WlqF1?~r=&HwH58+W!?&jnFHGRqKeUi~22LXE$X;?#t$v(~L?vl}5f=GL;<@0IB?rR8p3yDF2PoUz@R9D7ARdb#q3r#p_Ki>M>B&JMLCn4H! zh;J$h?6mUJz`5BkI@#2LtwQEMknCSFvb-rnb&EFeRfm_?zuCnVN<4;D(wZM4jBh%JSM61={Y5#Fu5o@FtJvCm9TTRYdycWAA_3DchK6=p?)TaR%z<0o}imHT5M>lcE< zLKMlEUuKhzCA!{Tv1ZDWil#qBWgydY;ecHp(mOojiFi$kF~W#4;~vvTeH zXvho^jsMs2DpOCqJUAqfAwDSn?E$-L^K7x09B-1!2lzr?V91}Y#Ebi5#_szMI!GCa z0%(mYW2hZ2R-vqOd_o^2Y}d~f>a2>1=@@IhO|lAB($W zp}e;J7Pxe)104J1=XkqHZ5!hfHWM3TMMu;l^(asEFq<96yCY`nkgRf&m%J)ra&hDE z>wYF)TX*d8AYazMFp*Y!fW^M0*?r)JT*}mf?pJ$ceMU;M*`c@!63v8JTLqqa;jO2% z9#ofJg%inXX3Fr-VL1X=YIY98fc#&pMoLe_uklwQ9xfdt^TMC642u+KzkpUHy>Hki zxUw)=VX4nH9P^gY3D$G(rbzrJZLaurHa7 z4&(>s1O)MVbS!krX``QBsrOpbia z4E_v|F(Cpyrfi##SSqdHT;-eThgcEBG#J`sH|%1kC(epb)gJ}}aG>}nur<7!h3|*+ zbtSJH?DAxalJygk~!RHDPIGNxbt2A{AFw?;lnah3N^WN4tLW=;s_OvoZvpLfFR&1&YvJ0!EV zR;Dj6Ic}(no*uooCn<=y-=j5)&FSOQ4>s>uXhFZJGY|cgTgnQoCoBun;*W^9cYMnE zp^K{77UA)RQ_uju(*OZ4Pf3?U$Wlk=8y##P5^8gEj4B4Nf|_B%E!CFQddnjL_rei4 z^2b`=Xu)`!lX1a@nl3Uj+?ux)<1zaX^ihjfud+hyY~cp#+h*MXdpSmROC?;6nG4PK zBidhSOIz;|BI9M_Z1yvjK$J=!MB9139)^Scij}y&M2@+4z#y@)1z#%SHLWRIY)0F( ze0Dxt(QXqwzeT0QI3NKSc*^PDWSlP1p@cqX=D!1oeyq}D^n5~-qwkKBeu!H!yrN8j zy&<-u)Rs8h!YJMTX$*Q^64yBvyF09! z2uEHxY17lQ+{ldo)9d%1F1=zF0%NdtwKkeZmMHF|nJaY=9`bca@IWGiSSP-&;z!dqn8RBl+GRjiTNf05K~o2<6N_&Jn8EMSZpmz|pj%-RP6 zD|g)Jl!SAm#Ph#f)JEoyCpPau`nEA6ZaE?nNP!%0=4mm~xujuH5Q2H73ENNNA@~qjvpuEG%XmoMg4nO39yksZJiWHmc!aM9kwrQH%$2lMy^m3qgQahS=eR+SgN(oz%7|Dcu5-9IJ_dhk9$)<` z`3qE>8CA6@z{proUXy4#-6xiySb@voRRd&altX)lGwa+u|5btwqZ(8*8B%cHcx;P^ z80Pw!K|NHAs>q6O>X}ab#BVHHFj1<)?}i3neifwv38w*%5KdkokM;J4!_Y=;wDDlw zkl*4X^OVYQRWFvS8}c~MF4pcLIW~9vV+D#LH;c8U^U$EJc?f6ny)%17Y`H3C znuVfl29!XTvQ?l@d6T-s=2y#sYM)fVz<*+B2#O=J{< z*f?hlGE1XbZX6t$F*%EeSR*e33SR@EhK=Zky3fMJ%+U`2Y_%ON=R~Dy5$dAcl&7p~ zJ@6+Id_R(&QzmA%Bt!TBZ^G4q1bx6kCY9s9-b&;?O5lhKq5vFs5aocaICT_>E25Xo ztfNxiTt;~UI$|I|CdJwb)ma631m!k1u4saxV-f=WcNYmHZ(Pva7mNX} zOJZPdde4scFitwHV44Dh8+N5}$Qbu9uN3|PN_vL0)s0j~^BBsp;uiC*5NA3vKdsWO%w>TNnpYJzBS?!1k) z!2^9TG}0_X5qRrQS&8&^kTs@cwpSZ6;OWwVuOE7wo4l$7r~GmfRqAql_OrdUjs0$H z4Q0SPy`QPKq@P=o81)Sjg$~H%LhE?Cw?qPJm|u@8Y*I|XUOj2Wm0?@roK+|`@Jj@| z{Fc~yq$AKY!YM);jhshZHi07gQh8!#z;hIUKB;!bLWJt*mGxJBEM|);m@;dHo4i8l z2P}#7($h2hG7HggHd-r_3olcmz-J_9Bk{UD7i9}WRrtGboQ7& zp~)EwRU|7H#vSO)2^5Mj9_n65Q8sPS#~FVn9T`QPfllq(LO(4TCX$7kU>#m9C=`;a zUu>z!&TMF_sSJi~H(FN%+JNcje>hzsh) zI-99o^n}INBC?;p+!Lf!@2BKlfLWW({DpDYJw>O-b6S!p#z@G9k1)lU*XabTmZi=) z083a98fTca0v*F$rVwO2W=&=f#oBhk(r(IE)Jd}5@e{&6V9)g&@#xd*Xkz?>+IvaW zlcbd*AR)Hdh@X&Bo8&;$J#7X_>#JPS^4WyS`e?|a8xYSJy>tri?YsZ0DeI_OnT4qW zl2QZrjnI)y#xK@bhtKe2N=nq4)uOVc`iRP`@e~=cYQXnyfN@X_&MKa&ShC%C90-(^ zaw$0__e(cHFQJ?>v*<4Oe6*W}Mud5o;@a!sYX;JiHy^4cneGWmm9|T)Sye}pq>IH4 z_=eHhi8M8vGwR7U>aN1Wup*X|2P84BOUOdQEWzHzO>BC~8NyeiYHy?QPS3>kUpMo)V`ka~-5MX-sTXee zV461kQjp^4VJi;euG?)|;x6LAV10jB&{IB*uj{5?;g^o3k*>7DMXn(rS#B#k4-QV? z+|N27nOH(qe%(Rsul(B6Soar&QXKWRzO5hFVr-yhUBZHNsRy0QUmKgK=%7-wExxUN z9E+T&YAAWer%6oHNJksrgY_m9nOyt&o6dn4_1yZr>5cQpPLKK&sxOVaq?2UF<#n{- z=1LnTs|YbChOV=fXR)BZ_U8>Z3W-pxtsT(=!v6m9qV}NCf0=BdnK@Ta>%(YM$-0x+ zQ}w^Ti~C!iWh+naG$SQO?S7#5QA5(ii?Eg+x0mcnavFbnYgfX^(X)_dGE!tns#Hg2 z(bEAOPOWJJlj2!~RZ2z{Ja^$I&FK&o+edFpNmx{S35V(NCkpWc5OR7Z7l(0IPzqH2- zGNZjZg$?xekBQ$>w(G1{x~5j5aa=OWHzKY9NAdovU6(7J1TGcTJK)hG`mM~b==gPM z3R|1~l4Pb^lh@ZP%f_XjBmARH1|*0nJ=K@8RjgUVuqfHQZRWHRP(M$PQ>vA}nasD4yJXurIn_ft19)C`V)0OQYm=*1B}|+T4M28hs#QW z*277`ifHBPTlpxRGh>i7W0Cz6s>8Kl13^4d%l}Rk*p|brP*mGLTAS=t7ty9360#6V zB6x6os_TEg!;>-}kTQQYr!xtKE+g8|tF;6gqx}!c4GM94$BREWKKxmW6y>`z9RpY& z;>5E0|1SR5{2xLMchvzTtEi~!zI4PTzX24YbV}%Qo%pu~y5;{iR|kc|MM70Y5;NTX z>#YV4OG_uY*!({`B}@aQty$eAf`@t4kXquQJ59g|o$OlT{dGN1&1ygL7PN<9HePL# zf>YMEB^Xc0Cw!2+4-dWyz2}E(7zF=sXkY%S`;(V*bHN0?usKGZxm+SgxaGKUGLdHj zYnHWS(w%206qb>?tZ*ohATZf|4R|y{>AEbC(sHhutDa7Hmn&!DD~#Z*^jZl6G>TPb zHW4>@Z|6y3Iy@Aef^#x{V%s1vS+`+fMA`0!m7?2=eoCPZ!<#q~GQh`jV~i1*b6AFH zHEsVKfPZ-J2?K?^L6L1x037ra%6p5?hLYNZEzT0)eDL#)p|CegyZg8scEwCuF^1LL z>Yt-MCcbGZ-S{7}Jvlw0%(u|8(f?_b_;2QjRQ5jTVm>Y$IdHcqTtl28Jro`Lk^h8t~qU2U~@+U8*)pea55 zeLLA&A*>LFO^o*Eu`MboEjH&}C0=pRKu8>y8Yc!5xfVHc60!##3Z6t*T@%ZVG7>gN zuCW?6()kqYJzzD@UiE|9T3APOcCenlqAxwgUep)!Snx7@=!zHs=|6BTG*!g=`~C#& zlg>qxRdbOxEZm3lNM2F9mFb|;$IHaK$6lL}YCcb_t$bc(C&;`}WtY*FS#zLp?gvA} z&SWQQbNo#OSb8V{-K?!GHv{i9O`12cJu=4Ma<;5C8|x#wcq*~z`d=6g!IQ-2#ZkvB zDDOL_jS9Y9{h_4vx%3Do6_WQX66eB_BNT)z6d`M!7RiG5xi>pzNh`ws^SV{g54 zn#m6KL%9wzKL3!PKp{0u8@I!be2q|KUaSjr9#YlEf$S(4)$Pj31Js~x>;AZ)UKcvM zA;pH9!Rp(0+UshHcX)F8&$fiL z;M#TG=pOWnmT!Q42~ZQ{m|EXJQ4bbjr-(8$#X9rAok~G~2J)(Z$ufoE@Q+d|q7wkk zeL6($27Hm})Ejijl804g@c=5aXlUH5AkZNLg;Z_&hfv*BnJ=FR_8AF8Xvas`zpD;` zlvv%g3D)?^$4dUhVTEl#QIz_tXLKUfVd$iSML}X`8St7}{+u_{M#wN3bsj+J2CMt% zR%EB3#eA9)=CZ9O#XhZLlNz;l<{@$S>5cjgE9B^0EzF*!i7jUt`tifB+l7?g_{T$M zP|Eo?i7NHVS{}m;2@n@iRRyybn~%o(=t?E;6+Z?&U8M_hH7^pS;4?!UB3I{N&o0Sy z-BN}5MVxj9kC-sP8J6XE))3&e@g=MVN%OTh8)v%Him9TkKAYsEZCeeyhTQAz&~pBR z7ZMh7dsAldf2X8_R4s^0P{!7Sa#xY|8~um#3MBG~rEkY_8m8)=TBqx~=S;LzAsUo5 z;^@a)NK6{<{dgZ9{=$fzU;5IMU=8~eQp_BzI`@>-L?V>;d>--QhYsJO3anjCdBB@w zf=&~2sJQ8Usjx9cUFcAjn~y1q{2tN+3sjNkw+au6;(D4iv-z!@%)eXO`8xxh7kF@_ zp~^PhwXRWr^Y+}#L~0xTW%LbNpCSxp?ovfYQuZ{S)7GZ}P2LT^d(4T#g`7XzV$?Yg zoBy#SmkB_+WLc2YF%3=6fH#+?&@>A8y;aGzL59G+037>)u2SYa2vvx<4yzlpZL;Ix8^iCnoe4{pq)R zA|6&#?h(Hko#la5SPO)lY6sMWE2b+%VyGG@-snQ`K;bQ3&oKsw(&Yvs5syfhbqmm= zAI`_JK$Poa(wVXd%fVo6;N!1g#^2;q(+Wii6A~7} zhZKjA#s6{tUtt`|_8 z=S)-xzpI*?!rHiAi&|Rp*}cU&s&%^~qhf6^0#mRyuF}fIQ;d}lXS<7J{cx0AiSWuc zl$tofUb`VlYp1D|599J>qtfUY?ktY!8-TOSQWV}c4^;jK15W%E{>oLYM)opi%eonc zQ{YVj$3I$q0UngORS1J45|>1czXdTiNN1}_>Ip6t zn46v}fYP%Fj1U=VH*feIl;DIT)6q{AX6auzLWCleU!|evS>q9 z#@)&iyxoc34kh9YYj&^lciE)_wFZ-pAC5|Oswd)_AD~2^;3&Ls5SBOB8GY}w&&c_P z*S7ooWC`M!zSL|*J4_+*U~IvK8tk0eo$T*Bs=|b}xTAC=+cMU}-eU16(rw*f4O9(p zXj(whAQhJ@`T6FnG-ije&e29*Fy|YPON~j~Kx`Z9sjdS_o|J~YD~$eL2>iR{oyslp zpN-$mR(q6r^!qAe9l{E4+tz%q_Q^P(!asEsZBsxc^ASEd|6B0~`8V|^CqM8n>{sbO z8}|%dG*7X=TMjW_tN&Xb|EE?=9@>G5b0hsvs#phkmVc_Rk)|A}^>Ne57w9F5DFeMs zKd59ABI(~?ow`CSoGaS7G!GROVB+r}ib=W2(BT-2#@EsJS{7srxVUx(9Bn#%Op_Ba zV_|E51UJm3*E!rih(n7$%cBO><^-~#bdTy`#2vMMddz7evh`ML$ROy+TD@=?4u2t2 z((eq8RCVi`s^fW{db3s5QJc0TMRQ)-{YLcXc2{T%8caG|!{siYbGY|CsG%_|*d0`z&w}MKAqJRKssK1+;&k*cyYLfc(~4X< zu^n4RHTEpLEV>66#k^My6+j?!v-*zb{;^!j`_d-HEuS4l#c;_OLZL{}1?%q}8PRYd z9=;UjJHdQS^nRtJ_O88*QE9qIaY#^k3jtUXg_6=-efPd;c4X_+->436it|C7VAUd< zhO{Wnt$?e2Ojau4G(PmRoMWDJ%HoJ#}=?nIjlk}a;EIkAgtyyo| zPw2yL9yBM2yL{=1H8gh&R^|(#B9V_*r{&J9P$ZkmZYL~ymA$z;C!nqu*bRW%6NUe1 z$4-3o2n@%he*#M2Q!^ApJbDzvtKSj1+U=TX5Q&ENnZ%#esLUW++!Dr&iuOc5?{w@=WE<7`4+ySkIWGStFGglWwk(s z)P^)yK~*S-#ftyz7t?B~>pOau4H=?N1s$0s#-z6C)?pO&*15Z@BoW`f zLwGbw0YFYJ%ujJ(4yK?L=p6Pat zq!AGs2Gb-c8&-JucUaKKAon&-CqUOIge4$(}BWwghJzYz&OKWNbRVDwDdzkZgA+~zG0KQ=O!MBaX3#&gyYJj zv_k1;>Z{vnzBH__b~jE*L-~3ama_WBg0ZrdSM3#Dpm{bb=gCk{NwFfL#HgsqW}}1h z_XQ!2&eL8QwmN-d7TKeFv2O5oTxm(OGyPkqg{?U32ZV-QNOk7)7hbVBi*a`|2Ufyx5SnPH2=WV|LuK%D zDkosV6HKbu4G%enq31MZ{S_@a3=QhYmA4x=D&eb$T8ELt@i8=4*Kw1WgVSYvVl~pz zZ8W?cjI`7s!%j&Wdfj2@qu+9}{=&p@9Dh50!z})_{L;-}Z9Wd4_IqA$QG>n0LrWQt z&7@jkQ{4>Wt?(5eN~C1Ek}wSYRiMtT_{S471H^G&-sAd2gY)%8F+Gh59FOycP?;mN z5q}H(WMNSieM#g|`qZEizFk(`aN*{zaDxqSZVlU@L0UGfKdMFK{0qY^reR?ca?+Ka zeK%DNy~@olTXjd_jS;p|&lVJy;< zuTDR~{v7;pBZczGXzCA_<%s7`48&K@B1^!OlpT9hmjsCCpRJB}ss_~wT+KhLX zgieKjHLgv^Fpw(zH-B+#=J-uQo zgNcQ4HoZA5JjIjJl+kZaCC=Ff6uXg=SE9xQ&36hl$@0nrRURW%Sj=#L4AUR+>rgH4 zu>aPuDUd@R+RK?;b#8T>=A`%mr<}t& z*DUgu1j`QbwHg~}Jl%99jKT1=c&guA)V)e%O;hg1yyWM&nG~yt^|GCjx-=xmQkQ21 zo>Soh(VO0{8s?>&4w16fCeK{I?A3_ z8J%=)x<%LuH4lgWb$d&{6gAMI*8H;)QCs z)v6YUId`h3`DkNmSJOVH0GEBU@LtX?G87apWDsVigrjry5NUTpT1X^|<$o%jbLD6b zJvvd>u(klED9kjb4c5pKSiY7e*LLN^8p}~LJ{1pS<#RyJq{88}6*v!U6YZCXcyr38 zXsS6t9oO6DFHuGx=p$}8!&~oQa-Y>5%z}R|sODX4JjC$0SBTFXEwg#xfVBdvv^JKb z*S}UTcZ65*XzAh`D$dN|6?_6lD_k_UBBQhOBuY?VQ(&xg-lZSCbL(1pV`Q1mRKx?d zHX=CJ0>mP0z_gHPsr>4NR3-*0(dloZ*H^R%C4(yA+RA>8qGQccbmskNTQ3}oQb}1D zfxYk3G6(~AsBCT--z>DBRzy-{Vxc$Ke2nK@)1H5f>e3)KmkiX@AMIg>mKkKb2fEPVuYRTA8I-?2D*VO6PUx(bq3Dt@sUxT9c#VG;Xv+Mbi&wZTX6K@kRI8 zDSinlMKeT-C!$6%FCR-VggPt+rP=H=WaeCct^9?F4KJ<0QJ_6lD|`MRaV?g2?wxh6 zW2Fj6=Vw)is-ae_%_&Mo!_lB0LbR>1nAkgo{P?)$A3Y}r`FA8-%5?KHxHV%^ z(=)w9mb}>3@i>51$El+`V4_kB&1zg3TxZIRip@^m!DAAI?(S=i_RwcAKF>6D6Mt^N z)bY~m2#U}YZ_?yXNvzDpjx)Ce&SXwrwCYArlr@`+#wNNb`~|Zmg&v(f>QBR3iW$VX zthdZY@@7tuGPI^{1##*RdD*%7J`VOjOmqBYT!b&DGTX_I!8x2NOD=3_Ls~?dnxA=B zQGlqD#}7?RBP;O55XO?`o;cE>Dn!BNlB+jA(CRG5EGR@j{kYu~JI@R||%t(0#I>12K z{fGj*;9Dk-Q^yg${^59ASh>5zvkGqRP64GD{&us){iXM#c#h#`(>IBHxqGv{WU~Kz z7u$>APd@xKK=J?c{ukyD;ZiL2&tI6oFfrN>H#TXJfwDFzv9w^1K-G3uMi?B#z8=jA zZwUb1`e$9NOrxz9kF!lwDKie5IyqzYGwhj1O*nDCekt|!>J@=@thlbuA_&A|apCr$H6QZ)8Es~g!y zQ>Kqu=uYJ@Cc4MBQ1jJV4yru&lbhYcaR9iF+}^s2Ou$j4g^J{b+e6`0V)bjerm{Z} z%G`=SAyDneVK)X_-6zR_)AaKBH=T6>umVV|{D{v%ksW96+YH;lYR$ufT#mx39Vy0c zu=|*e$5cXKbeL(GF;}5Tn+kV&WSr~kq@H4_FJODNWdF$UG9aR)3H0h8=^Oh>t`~(l zDJYWa*JsLsSa9 zsI++}?O=wOfVdpx#@>07LeUXTiqowRT5Z2W7>Enz{E&3681h~y)m;e z%TGL>1RZ|fTFlS=0{<`e^)Wfc^5rrLyQ5W2+Xi^%vwlrlQXZ8$1r<#GccKgKS@XKo zEIgy}I}QMON}Zm0(Qb*Qr3zs%mwk$LmF4%?U8%MAetm%3;p>1(GDF2F@>}(25-tC4 zY7~yVQVwp3z^VcT#j{%%bxts%pK~5k(u1{4^3UAm9!x! z+#3Y46p zCjNtV9t(074$g7Nl9@E@sR32@-I{)-T79=bBIh4P`tHh1U8*sQ@cNphZ>Or=Z3+&@ z#PpztptU`N!dx)VbR|(RiEZ+4jfXnI}>=PRvKO-cw;6P{sLJ%~-*_3q@9nIS*~kVmT|1S=1FcoJv1J#XvZqxI8iGZE`r1BH z2`RzHM&nKiV?U~MN9S+rfLB#-y}uHDU}JO5kAdD{gsbeIql2Rvy@fDpsIgkN8nZX0 zhh4ag*-#`}qbhgU8V_+QwLz%YARp*B3fmRc!*yHzd`#7t&>JVx?ddvaAKsYupv**S z5|qM6D}liz#6|<;oS>udw%nn~loVbPn)%O}2z70DEE%b8e;y|DSEWEcXkgNuP2+$K zP4(4A@R;p9UEDLD;rAt#Fr52N)E(N6J_X3y`5=feK|M!_O&0G0XUHo|C9h>$f!;?@ z5FG-zl4%bV!l)!{{x-nqAC9MpI1~wKCrbkk6I+6btG_)UT};(51o(OyOdh`-EWUl~ zllphS9GPsDVR2#!HKJbC66GyrL`Nd_+ewOH-&16miX7rIsB+f_SNE85!w6xa@(W;Q$8 z>RoAY?V?>rG4=LWD_~(Bx$uEGVl|Qz;uLw`;UYV1khozVQ1Zp}gN9-NJzqA&NT(PH zVn`P-eF_oPq4??f)BHz_TTtzv)JV)dr5|7Zj>2$Z!@xmbu<&pQi13Io|6Z_I*l?UU zlvDr-b8$C#Np%ebuF$YzT1?zgsX!uyC-jVlaQ&vYrC^hdHin)-U2$%t#;x z$E9(y{jou}^K=2IUpC@})AcI<3>%y>1;m*SD47xMmxD0BZJRW+{J1nv7o1ue_ZJ3U$jq|UE8Zd`;G}xwGSA0|EpytI zp0$TxZN-dqay#S1D)FTjp={|bsZq2V6c^dgy=6#NwiJw`IR(<&3_y3t<~Zj@l5 zsKRp8VGY|7u_~ODVESW-l;yZx-o@GKFqIiXQq(C>;V!GtUkJ^{DwO1@igMIoIK|P; z9MR1jlwrAB>z3hndlzS@##c*4oQC5WQoR+`d?CAW$()(Vjof16Ek*;>QN^5DOr}#Y zPUj8v%YKX4yJZ;4hU#gQ%8X@7_^w)oi)RLKX9jR_j^iyx6NjUY`sKex>|L9MIKzac z#xlM!oAU`$>z3geYA}qHEUt2Q7^+i-`sE4tdlcEp4aO^`FyVDmj5tni7sN4E`(>!X zk5Pm*V^tRC9C~B9P}fX#)$h8;t}xWxB^b(@_@h>ELdLuBLdJ%U2uiwfM+1PdOQ!@R zmufhpiWgod6mdrsaYZiNN}4#FM5Bn5P?t(5qQ9eM{{U)a<+|&CU~=;4!&7KY7L?kX zOvi2$MtYkbg`pQ8nk`MKOvt&oP9H|gi}cfr>b@t2+clIk1$#18yfLAqFDS()4@cz^ zG&CfQvXGyFD;SWWXh@-X5*grK7s6Mgo1`pVY|9pv*qx#1!$x|C{KD~+W97DCQUqOYfFFZM01m*BR<-?^2F#>-`vTU#B9;krDD z*=4x9(PGqDY`L=BU&B|emeH2fWBkl|7xEZrdOs+%XQM4B%W4z-hjeAAdoq={(lOBV zep4M84?@tc&mvw7bY-L1$578A*tX-?S{Stm@-0e*p&ovbd>bX@880d^URd;gLl)8- zc^rIDn_d~`BQ+TzI~C$PW7IvFAvkN@79=Z0wl&~dG8mgQS?pt>==K$;Xt8Wiv}oA1 z8irIU*T^!_j=xf05MO1N`Gr|!mRV(%S!I@4WtLyw^sN3P4nLu&P`;gfU-(=(ekc78 z#KmU3{_p+_@OWikq^Vv{VR;tf#D4L((e5F9YxsPB)618!{J+`%0K-2Q@K#HU{?Gpa zhJS~a4ziWmLi)7}rAnHbmC<}(_p=ABxsr?6WGjTK#6eH_tEM{0)Eq!~il800II60Rja91_lEG00000 z009sY0x=*GU;`mAL2yw76F^XakRn21fsvsjaiGEf+5iXv0|5a)0R5p??HapDaHrTS z)Ln{`DDo}Evww+Q=|_=e^V;|#k0Q(T*T9r{7GJN9{{SRizpqtt()^uv{Yr#;P4Y-@ z;3p`f-dCYVk$(a{5745KXt(e>^eL2DFW}AaqsYI6Y_k0Y`!lb~m+ESt!Pu)Q4;5T- z+-LZtibW!kNTa8M^`C9wscN4LcNzW#D>{7zsOmb4E-Q`qGEm({Ylb_F{{RDrqP595 zYr3$Eov}MZR=8uh&Qh^pbfcvY4jm&@C1S*BBcw{Br0E*2D_rr16jur;qPV^l+SsZ? zbfXAroa4C861nO^$wODcp+;IQO`_2E#dA>Q@v`|o*-G4H3C2os+-Df7P~FZhGF%>9 zG1sEGsB+;$l@E&Td=x#-I^l-nF+)~#v!$IZ>2a2&>yA3~R=KEh_)$d(LzmksI#JS& zlqFG;^Ne>Hi*snSS}hia)lORDkD|Hb4qPS8%k7L+xNeTLb)%^rN;1@AxX4kKy&Sd2 z9eOL8;^A>n<@UxJhVAjkP|SB3MlKqu#Y1zJxZ@3TTH``h4e)Texp2ExMoyP39Asm- z$`heGic;SUwZ%H%_r-I*E)$xIhucZf*2eFjQyZ}}sg2(Sk<@h-_;oVxk}aK2m6lm$ zmibw7i!8Xgzlc}p1%Aj?=vS*<>5r1LE-6h-4k*Wh+3yt}%M~>(81SfS7X*s#cl0)) z4bh9gCN^yvM^hQ0WMp5^SMFoDNNh~^JqeJ;uZ$%_76njgCF%~{6iu?-v?K+n38?icz@}W99p*lK?Eb1y$6p=~lDI>R#FuXiu#nEgsh~7ggS(eA)<;x zMHC^>kw~J75b6>9`J|F@lwACy9sHFP9B7Nj+l}~`PoUDF^ooyijcPpLK=k18M8v% zAwnqk7`P&b!*Jtpi{_%-$FGnB)E#;AXMUuMh6N6Y)GXo;1%F!0CU*<^6qx@WDEE+2Aa& b{{WJ|>-w_`>0Mj+Q||EHe*XYV^S}Suq6+no literal 0 HcmV?d00001 diff --git a/.github/assets/og/oku-scroll-area.jpg b/.github/assets/og/oku-scroll-area.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4b7c3bd2e2c2a34b9fa68a223764d37405c4670b GIT binary patch literal 50126 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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 CL&r`4 literal 0 HcmV?d00001 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a4648c2e..8758fd797 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,10 +66,13 @@ pnpm test testfilename # Run a specific test file name pnpm play # Run the playground pnpm play:vue # Run the playground with Vue pnpm play:nuxt # Run the playground with Nuxt +pnpm play:gencomponents # Run the playground with GenComponents pnpm clean: all # Clean all node_modules pnpm clean:dist # Clean dist folder in all packages pnpm clean:dts # Clean dts folder in all packages + +pnpm oku # Run Oku CLI (packages/cli) // pnpm oku publish (publish packages) ``` ## Testing diff --git a/README.md b/README.md index a0080c6a2..67f15c0d3 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Enter the component you want most in the components, leave the emojis and follow | Component | Description | Status | Docs | | --- | --- | --- | --- | -| [Accordion](https://github.com/oku-ui/primitives/issues/3) | A group of collapsible panels | 🚧 In Progress | - | +| [Accordion](https://oku-ui.com/primitives/components/accordion) | Version | Downloads | Website | | [Alert Dialog](https://oku-ui.com/primitives/components/alert-dialog) | Version | Downloads | Website | | [Aspect Ratio](https://oku-ui.com/primitives/components/aspect-ratio) | Version | Downloads | Website | | [Avatar](https://oku-ui.com/primitives/components/avatar) | Version | Downloads | Website | diff --git a/nx.json b/nx.json index bc2c739b9..944c92022 100644 --- a/nx.json +++ b/nx.json @@ -15,6 +15,9 @@ "build": { "dependsOn": [ "^build" + ], + "outputs": [ + "{projectRoot}/dist" ] }, "clean": { diff --git a/package.json b/package.json index 8c259a2f4..82302adc0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "primitives", "type": "module", - "version": "0.4.0", + "version": "0.5.0", "packageManager": "pnpm@8.7.4", "repository": "oku-ui/primitives", "engines": { @@ -18,12 +18,14 @@ "build": "pnpm nx run-many -t build", "build:skip": "pnpm nx run-many -t build --skip-nx-cache", "p:build": "tsup", + "oku": "pnpm jiti ./scripts/index.ts", "story": "pnpm storybook dev -p 6006 --no-open", "lint": "eslint . --cache ", "lint:fix": "eslint . --fix --cache", "play:vue": "pnpm clean:dts && pnpm --filter=vue3 p:dev", "play:nuxt": "pnpm clean:dts && pnpm --filter=nuxt3 p:dev", "play": "pnpm clean:dts && pnpm --filter='./playground/**' p:dev", + "play:gencomponents": "pnpm jiti ./scripts/playground-generator.ts", "postinstall": "simple-git-hooks", "test": "vitest run", "test:watch": "vitest --watch", @@ -37,8 +39,10 @@ "update:version": "esno scripts/update-version.ts" }, "devDependencies": { + "@clack/prompts": "^0.7.0", "@egoist/tailwindcss-icons": "^1.1.0", "@iconify-json/ph": "^1.1.6", + "@oku-ui/accordion": "workspace:^", "@oku-ui/alert-dialog": "workspace:^", "@oku-ui/arrow": "workspace:^", "@oku-ui/aspect-ratio": "workspace:^", @@ -123,6 +127,7 @@ }, "pnpm": { "overrides": { + "@oku-ui/accordion": "workspace:^", "@oku-ui/alert-dialog": "workspace:^", "@oku-ui/arrow": "workspace:^", "@oku-ui/aspect-ratio": "workspace:^", diff --git a/packages/components/accordion/README.md b/packages/components/accordion/README.md new file mode 100644 index 000000000..921db8c4b --- /dev/null +++ b/packages/components/accordion/README.md @@ -0,0 +1,14 @@ +# Accordion +A window overlaid on either the primary window or another dialog window, rendering the content underneath inert. + +![@oku-ui/accordion](./../../../.github/assets/og/oku-accordion.jpg) + +Version | Downloads | Website + +## Installation + +```sh +$ pnpm add @oku-ui/accordion +``` + +[Documentation](https://oku-ui.com/primitives/components/accordion) diff --git a/packages/components/arrow/build.config.ts b/packages/components/accordion/build.config.ts similarity index 100% rename from packages/components/arrow/build.config.ts rename to packages/components/accordion/build.config.ts diff --git a/packages/components/accordion/package.json b/packages/components/accordion/package.json new file mode 100644 index 000000000..03f0499f6 --- /dev/null +++ b/packages/components/accordion/package.json @@ -0,0 +1,55 @@ +{ + "name": "@oku-ui/accordion", + "type": "module", + "version": "0.5.0", + "license": "MIT", + "source": "src/index.ts", + "funding": "https://github.com/sponsors/productdevbook", + "homepage": "https://oku-ui.com/primitives", + "repository": { + "type": "git", + "url": "git+https://github.com/oku-ui/primitives.git", + "directory": "packages/components/label" + }, + "bugs": { + "url": "https://github.com/oku-ui/primitives/issues" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs" + } + }, + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "unbuild", + "dev": "unbuild --stub", + "clean": "rimraf ./dist && rimraf ./node_modules" + }, + "peerDependencies": { + "vue": "^3.3.0" + }, + "dependencies": { + "@oku-ui/collapsible": "latest", + "@oku-ui/collection": "latest", + "@oku-ui/direction": "latest", + "@oku-ui/primitive": "latest", + "@oku-ui/provide": "latest", + "@oku-ui/roving-focus": "latest", + "@oku-ui/use-composable": "latest", + "@oku-ui/utils": "latest" + }, + "devDependencies": { + "tsconfig": "workspace:^" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/components/accordion/src/accordion.ts b/packages/components/accordion/src/accordion.ts new file mode 100644 index 000000000..fe28c3f73 --- /dev/null +++ b/packages/components/accordion/src/accordion.ts @@ -0,0 +1,68 @@ +import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue' +import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable' +import { OkuAccordionImplSingle } from './accordionImplSingle' +import { OkuAccordionImplMultiple } from './accordionImplMultiple' +import { ACCORDION_NAME, CollectionProvider, accordionProps, scopeAccordionProps } from './props' +import type { AccordionImplSingleProps, AccordionNativeElement } from './props' + +const accordion = defineComponent({ + name: ACCORDION_NAME, + inheritAttrs: false, + props: { + ...accordionProps.props, + ...scopeAccordionProps, + }, + setup(props, { attrs, slots }) { + const { + type, ...accordionPropsRefs + } = toRefs(props) + const forwardRef = useForwardRef + + const _reactiveProps = reactive(accordionPropsRefs) as AccordionImplSingleProps + const reactiveProps = reactiveOmit(_reactiveProps, (key, _value) => key === undefined) + + return () => { + if (props.type) { + const value = props.value || props.defaultValue + if (props.type && !['single', 'multiple'].includes(props.type)) { + return new Error( + 'Invalid prop `type` supplied to `Accordion`. Expected one of `single | multiple`.', + ) + } + if (props.type === 'multiple' && typeof value === 'string') { + return new Error( + 'Invalid prop `type` supplied to `Accordion`. Expected `single` when `defaultValue` or `value` is type `string`.', + ) + } + if (props.type === 'single' && Array.isArray(value)) { + return new Error( + 'Invalid prop `type` supplied to `Accordion`. Expected `multiple` when `defaultValue` or `value` is type `string[]`.', + ) + } + } + + return h(CollectionProvider, { + scope: props.scopeOkuAccordion, + }, + { + default: () => type.value === 'multiple' + ? h(OkuAccordionImplMultiple, { + ...mergeProps(attrs, reactiveProps), + ref: forwardRef, + }, slots) + : h(OkuAccordionImplSingle, { + ...mergeProps(attrs, reactiveProps), + ref: forwardRef, + }, slots), + }, + ) + } + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuAccordion = accordion as typeof accordion +& +(new () => { + $props: AccordionNativeElement +}) diff --git a/packages/components/accordion/src/accordionContent.ts b/packages/components/accordion/src/accordionContent.ts new file mode 100644 index 000000000..eecababe5 --- /dev/null +++ b/packages/components/accordion/src/accordionContent.ts @@ -0,0 +1,54 @@ +import { OkuCollapsibleContent } from '@oku-ui/collapsible' +import { primitiveProps } from '@oku-ui/primitive' +import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue' +import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable' +import { ACCORDION_NAME, type AccordionContentNativeElement, CONTENT_NAME, accordionContentProps, scopeAccordionProps, useAccordionInject, useAccordionItemInject, useCollapsibleScope } from './props' + +/** + * `AccordionContent` contains the collapsible content for an `AccordionItem`. + */ +const accordionContent = defineComponent({ + name: CONTENT_NAME, + inheritAttrs: false, + props: { + ...primitiveProps, + ...accordionContentProps.props, + ...scopeAccordionProps, + }, + setup(props, { slots, attrs }) { + const { + scopeOkuAccordion, + ...contentProps + } = toRefs(props) + + const accordionInject = useAccordionInject(ACCORDION_NAME, scopeOkuAccordion.value) + const itemInject = useAccordionItemInject(CONTENT_NAME, scopeOkuAccordion.value) + const collapsibleScope = useCollapsibleScope(scopeOkuAccordion.value) + + const _reactive = reactive(contentProps) + const _contentProps = reactiveOmit(_reactive, (key, _value) => key === undefined) + + const forwardRef = useForwardRef() + + return () => h(OkuCollapsibleContent, { + 'role': 'region', + 'aria-labelledby': itemInject.triggerId.value, + 'data-orientation': accordionInject.orientation.value, + ...collapsibleScope, + ...mergeProps(attrs, _contentProps), + 'ref': forwardRef, + 'style': { + '--oku-accordion-content-height': 'var(--oku-collapsible-content-height)', + '--oku-accordion-content-width': 'var(--oku-collapsible-content-width)', + ...attrs.style as any, + }, + }, slots) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuAccordionContent = accordionContent as typeof accordionContent +& +(new () => { + $props: AccordionContentNativeElement +}) diff --git a/packages/components/accordion/src/accordionHeader.ts b/packages/components/accordion/src/accordionHeader.ts new file mode 100644 index 000000000..f9681b208 --- /dev/null +++ b/packages/components/accordion/src/accordionHeader.ts @@ -0,0 +1,50 @@ +import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue' +import { Primitive, primitiveProps } from '@oku-ui/primitive' +import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable' +import { ACCORDION_NAME, type AccordionHeaderNativeElement, HEADER_NAME, accordionHeaderProps, scopeAccordionProps, useAccordionInject, useAccordionItemInject } from './props' +import { getState } from './utils' + +/** + * `AccordionHeader` contains the content for the parts of an `AccordionItem` that will be visible + * whether or not its content is collapsed. + */ +const accordionHeader = defineComponent({ + name: HEADER_NAME, + inheritAttrs: false, + props: { + ...primitiveProps, + ...accordionHeaderProps.props, + ...scopeAccordionProps, + }, + emits: accordionHeaderProps.emits, + setup(props, { slots, attrs }) { + const { + scopeOkuAccordion, ...headerProps + } = toRefs(props) + + const accordionInject = useAccordionInject(ACCORDION_NAME, scopeOkuAccordion.value) + + const itemInject = useAccordionItemInject(HEADER_NAME, scopeOkuAccordion.value) + + const _reactive = reactive(headerProps) + const _headerProps = reactiveOmit(_reactive, (key, _value) => key === undefined) + + const forwardRef = useForwardRef() + + return () => h(Primitive.h3, { + 'data-orientation': accordionInject.orientation.value, + 'data-state': getState(itemInject.open?.value), + 'data-disabled': itemInject.disabled?.value ? '' : undefined, + ...mergeProps(attrs, _headerProps), + 'ref': forwardRef, + + }, { default: () => slots.default?.() }) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuAccordionHeader = accordionHeader as typeof accordionHeader +& +(new () => { + $props: AccordionHeaderNativeElement +}) diff --git a/packages/components/accordion/src/accordionImpl.ts b/packages/components/accordion/src/accordionImpl.ts new file mode 100644 index 000000000..63f4a78de --- /dev/null +++ b/packages/components/accordion/src/accordionImpl.ts @@ -0,0 +1,131 @@ +import { computed, defineComponent, h, mergeProps, reactive, ref, toRefs } from 'vue' +import { Primitive, primitiveProps } from '@oku-ui/primitive' +import { reactiveOmit, useComposedRefs, useForwardRef } from '@oku-ui/use-composable' +import { useDirection } from '@oku-ui/direction' +import { composeEventHandlers } from '@oku-ui/utils' +import { ACCORDION_IMPL_NAME, ACCORDION_KEYS, type AccordionImplNativeElement, AccordionImplProvider, CollectionSlot, accordionImplProps, scopeAccordionProps, useCollection } from './props' + +const accordionImpl = defineComponent({ + name: ACCORDION_IMPL_NAME, + inheritAttrs: false, + props: { + ...primitiveProps, + ...accordionImplProps.props, + ...scopeAccordionProps, + }, + emits: accordionImplProps.emits, + setup(props, { slots, emit, attrs }) { + const { + scopeOkuAccordion, disabled, dir, orientation, ...accordionProps + } = toRefs(props) + const accordionRef = ref(null) + + const forwardRef = useForwardRef() + const composedRefs = useComposedRefs(accordionRef, forwardRef) + + const getItems = useCollection(scopeOkuAccordion.value) + const direction = useDirection(dir) + const isDirectionLTR = computed(() => direction.value === 'ltr') + + const handleKeyDown = composeEventHandlers( + event => emit('keydown', event), + (event) => { + if (!ACCORDION_KEYS.includes(event.key)) + return + + const target = event.target as HTMLElement + const triggerCollection = getItems().filter(item => !item.ref.value?.disabled) + const triggerIndex = triggerCollection.findIndex(item => item.ref.value === target) + const triggerCount = triggerCollection.length + + if (triggerIndex === -1) + return + + // Prevents page scroll while user is navigating + event.preventDefault() + + let nextIndex = triggerIndex + const homeIndex = 0 + const endIndex = triggerCount - 1 + + const moveNext = () => { + nextIndex = triggerIndex + 1 + if (nextIndex > endIndex) + nextIndex = homeIndex + } + + const movePrev = () => { + nextIndex = triggerIndex - 1 + if (nextIndex < homeIndex) + nextIndex = endIndex + } + + switch (event.key) { + case 'Home': + nextIndex = homeIndex + break + case 'End': + nextIndex = endIndex + break + case 'ArrowRight': + if (orientation.value === 'horizontal') { + if (isDirectionLTR.value) + moveNext() + else + movePrev() + } + break + case 'ArrowDown': + if (orientation.value === 'vertical') + moveNext() + + break + case 'ArrowLeft': + if (orientation.value === 'horizontal') { + if (isDirectionLTR.value) + movePrev() + + else + moveNext() + } + break + case 'ArrowUp': + if (orientation.value === 'vertical') + movePrev() + + break + } + + const clampedIndex = nextIndex % triggerCount + triggerCollection[clampedIndex].ref.value?.focus() + }) + + const _reactive = reactive(accordionProps) + const _accordionProps = reactiveOmit(_reactive, (key, _value) => key === undefined) + + AccordionImplProvider({ + scope: scopeOkuAccordion.value, + disabled, + direction: dir, + orientation, + }) + + return () => h(CollectionSlot, { + scope: scopeOkuAccordion.value, + }, { + default: () => h(Primitive.div, { + ...mergeProps(attrs, _accordionProps), + 'data-orientation': orientation.value, + 'ref': composedRefs, + 'onKeydown': disabled.value ? undefined : handleKeyDown, + }, slots), + }) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuAccordionImpl = accordionImpl as typeof accordionImpl +& +(new () => { + $props: AccordionImplNativeElement +}) diff --git a/packages/components/accordion/src/accordionImplMultiple.ts b/packages/components/accordion/src/accordionImplMultiple.ts new file mode 100644 index 000000000..419360a19 --- /dev/null +++ b/packages/components/accordion/src/accordionImplMultiple.ts @@ -0,0 +1,75 @@ +import { computed, defineComponent, h, mergeProps, reactive, ref, toRefs, useModel } from 'vue' +import { reactiveOmit, useControllable, useForwardRef } from '@oku-ui/use-composable' +import { + ACCORDION_IMPL_MULTIPLE_NAME, AccordionCollapsibleProvider, type AccordionImplMultipleNativeElement, AccordionValueProvider, accordionImplMultipleProps, + scopeAccordionProps, +} from './props' +import { OkuAccordionImpl } from './accordionImpl' + +const accordionImplMultiple = defineComponent({ + name: ACCORDION_IMPL_MULTIPLE_NAME, + inheritAttrs: false, + props: { + ...accordionImplMultipleProps.props, + ...scopeAccordionProps, + }, + emits: accordionImplMultipleProps.emits, + setup(props, { slots, emit, attrs }) { + const { + value: valueProp, + defaultValue, + ...accordionMultipleProps + } = toRefs(props) + + const modelValue = useModel(props, 'modelValue') + const proxyChecked = computed(() => valueProp.value !== undefined ? valueProp.value : modelValue.value !== undefined ? modelValue.value : undefined) + + const { state, updateValue } = useControllable({ + prop: computed(() => proxyChecked.value), + defaultProp: computed(() => defaultValue.value), + onChange: (result) => { + emit('valueChange', result) + modelValue.value = result + }, + }) + + const forwardRef = useForwardRef() + + const _reactive = reactive(accordionMultipleProps) + const _accordionMultipleProps = reactiveOmit(_reactive, (key, _value) => key === undefined) + + AccordionValueProvider({ + scope: props.scopeOkuAccordion, + value: computed(() => state.value), + onItemOpen: (e) => { + if (!state.value) + updateValue([]) + + state.value.push(e) + updateValue(state.value) + }, + onItemClose: (e) => { + const index = state.value.indexOf(e) + state.value.splice(index, 1) + updateValue(state.value) + }, + }) + + AccordionCollapsibleProvider({ + scope: props.scopeOkuAccordion, + collapsible: ref(true), + }) + + return () => h(OkuAccordionImpl, { + ...mergeProps(attrs, _accordionMultipleProps), + ref: forwardRef, + }, slots) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuAccordionImplMultiple = accordionImplMultiple as typeof accordionImplMultiple +& +(new () => { + $props: AccordionImplMultipleNativeElement +}) diff --git a/packages/components/accordion/src/accordionImplSingle.ts b/packages/components/accordion/src/accordionImplSingle.ts new file mode 100644 index 000000000..ba3ab72ab --- /dev/null +++ b/packages/components/accordion/src/accordionImplSingle.ts @@ -0,0 +1,73 @@ +import { computed, defineComponent, h, mergeProps, reactive, ref, toRefs, useModel } from 'vue' +import { reactiveOmit, useControllable, useForwardRef } from '@oku-ui/use-composable' +import { OkuAccordionImpl } from './accordionImpl' +import { + ACCORDION_IMPL_SINGLE_NAME, AccordionCollapsibleProvider, + AccordionValueProvider, accordionImplSingleProps, + scopeAccordionProps, +} from './props' +import type { AccordionImplSingleNativeElement } from './props' + +const accordionImplSingle = defineComponent({ + name: ACCORDION_IMPL_SINGLE_NAME, + inheritAttrs: false, + props: { + ...accordionImplSingleProps.props, + ...scopeAccordionProps, + }, + emits: accordionImplSingleProps.emits, + setup(props, { slots, emit, attrs }) { + const { + value: valueProp, + defaultValue, + collapsible, + ...accordionSingleProps + } = toRefs(props) + + const modelValue = useModel(props, 'modelValue') + const proxyChecked = computed(() => valueProp.value !== undefined ? valueProp.value : modelValue.value !== undefined ? modelValue.value : undefined) + + const { state, updateValue } = useControllable({ + prop: computed(() => proxyChecked.value), + defaultProp: computed(() => defaultValue.value), + onChange: (result) => { + emit('valueChange', result) + modelValue.value = result + }, + }) + + const forwardRef = useForwardRef() + + const _reactive = reactive(accordionSingleProps) + const _accordionSingleProps = reactiveOmit(_reactive, (key, _value) => key === undefined) + + AccordionValueProvider({ + scope: props.scopeOkuAccordion, + value: computed(() => state.value ? [state.value] : []), + onItemOpen: (e) => { + updateValue(e) + }, + onItemClose: () => { + if (collapsible.value) + updateValue('') + }, + }) + + AccordionCollapsibleProvider({ + scope: props.scopeOkuAccordion, + collapsible: ref(collapsible.value || false), + }) + + return () => h(OkuAccordionImpl, { + ...mergeProps(attrs, _accordionSingleProps), + ref: forwardRef, + }, slots) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuAccordionImplSingle = accordionImplSingle as typeof accordionImplSingle +& +(new () => { + $props: AccordionImplSingleNativeElement +}) diff --git a/packages/components/accordion/src/accordionItem.ts b/packages/components/accordion/src/accordionItem.ts new file mode 100644 index 000000000..53d7a622c --- /dev/null +++ b/packages/components/accordion/src/accordionItem.ts @@ -0,0 +1,69 @@ +import { computed, defineComponent, h, mergeProps, reactive, toRefs } from 'vue' +import { reactiveOmit, useForwardRef, useId } from '@oku-ui/use-composable' +import { OkuCollapsible } from '@oku-ui/collapsible' +import type { AccordionItemNativeElement } from './props' +import { + AccordionItemProvider, ITEM_NAME, accordionItemProps, scopeAccordionProps, useAccordionInject, useAccordionValueInject, + useCollapsibleScope, +} from './props' +import { getState } from './utils' + +/** + * `AccordionItem` contains all of the parts of a collapsible section inside of an `Accordion`. + */ +const accordionItem = defineComponent({ + name: ITEM_NAME, + inheritAttrs: false, + props: { + ...accordionItemProps.props, + ...scopeAccordionProps, + }, + setup(props, { slots, attrs }) { + const { + scopeOkuAccordion, value: valueProp, ...accordionItemProps + } = toRefs(props) + + const forwardRef = useForwardRef() + const accordionInject = useAccordionInject(ITEM_NAME, scopeOkuAccordion.value) + const valueInject = useAccordionValueInject(ITEM_NAME, scopeOkuAccordion.value) + const collapsibleScope = useCollapsibleScope(scopeOkuAccordion.value) + const triggerId = useId() + + const open = computed(() => (valueProp.value && valueInject.value.value?.includes(valueProp.value)) || false) + const disabled = computed(() => accordionInject.disabled?.value || props.disabled) + + const _reactive = reactive(accordionItemProps) + const _accordionItemProps = reactiveOmit(_reactive, (key, _value) => key === undefined) + + AccordionItemProvider({ + scope: scopeOkuAccordion.value, + open, + triggerId: computed(() => triggerId), + disabled, + }) + + return () => h(OkuCollapsible, { + 'data-orientation': accordionInject.orientation.value, + 'data-state': getState(open.value), + ...collapsibleScope, + ...mergeProps(attrs, _accordionItemProps), + 'ref': forwardRef, + 'disabled': disabled.value, + 'open': open.value, + 'onOpenChange': (event) => { + if (event) + valueInject.onItemOpen(valueProp.value as string) + + else + valueInject.onItemClose(valueProp.value as string) + }, + }, slots) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuAccordionItem = accordionItem as typeof accordionItem +& +(new () => { + $props: AccordionItemNativeElement +}) diff --git a/packages/components/accordion/src/accordionTrigger.ts b/packages/components/accordion/src/accordionTrigger.ts new file mode 100644 index 000000000..b61bdad0e --- /dev/null +++ b/packages/components/accordion/src/accordionTrigger.ts @@ -0,0 +1,58 @@ +import { OkuCollapsibleTrigger } from '@oku-ui/collapsible' +import { computed, defineComponent, h, mergeProps, reactive, toRefs } from 'vue' +import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable' +import type { AccordionTriggerNativeElement } from './props' +import { + ACCORDION_NAME, CollectionItemSlot, TRIGGER_NAME, accordionTriggerProps, + scopeAccordionProps, useAccordionCollapsibleInject, + useAccordionInject, useAccordionItemInject, useCollapsibleScope, + +} from './props' + +const accordionTrigger = defineComponent({ + name: TRIGGER_NAME, + inheritAttrs: false, + props: { + ...accordionTriggerProps.props, + ...scopeAccordionProps, + }, + emits: accordionTriggerProps.emits, + setup(props, { attrs, slots }) { + const { + scopeOkuAccordion, + ...triggerProps + } = toRefs(props) + + const accordionInject = useAccordionInject(ACCORDION_NAME, scopeOkuAccordion.value) + const itemInject = useAccordionItemInject(TRIGGER_NAME, scopeOkuAccordion.value) + const collapsibleInject = useAccordionCollapsibleInject(TRIGGER_NAME, scopeOkuAccordion.value) + const collapsibleScope = useCollapsibleScope(scopeOkuAccordion.value) + + const _reactive = reactive(triggerProps) + const _triggerProps = reactiveOmit(_reactive, (key, _value) => key === undefined) + + const forwardRef = useForwardRef() + + const disabled = computed(() => (itemInject.open?.value && !collapsibleInject.collapsible.value) || undefined) + + return () => h(CollectionItemSlot, { + scope: scopeOkuAccordion.value, + }, { + default: () => h(OkuCollapsibleTrigger, { + 'aria-disabled': disabled.value, + 'data-orientation': accordionInject.orientation.value, + 'id': itemInject.triggerId.value, + ...collapsibleScope, + ...mergeProps(attrs, _triggerProps), + 'ref': forwardRef, + }, slots), + }) + }, +}) + +// TODO: https://github.com/vuejs/core/pull/7444 after delete +export const OkuAccordionTrigger = accordionTrigger as typeof accordionTrigger +& +(new () => { + $props: AccordionTriggerNativeElement +}) diff --git a/packages/components/accordion/src/index.ts b/packages/components/accordion/src/index.ts new file mode 100644 index 000000000..b0c986385 --- /dev/null +++ b/packages/components/accordion/src/index.ts @@ -0,0 +1,44 @@ +export { OkuAccordion } from './accordion' + +export { + type AccordionMultipleEmits, accordionProps, type AccordionMultipleProps, type AccordionSingleEmits, type AccordionSingleProps, + accordionContentProps, + type AccordionContentProps, accordionHeaderProps, + type AccordionHeaderProps, accordionImplProps, + type AccordionImplProps, type AccordionImplEmits, + accordionImplSingleProps, + type AccordionImplSingleProps, type AccordionImplSingleEmits, + accordionImplMultipleProps, + type AccordionImplMultipleProps, type AccordionImplMultipleEmits, + accordionItemProps, + type AccordionItemProps, type AccordionItemEmits, + accordionTriggerProps, + type AccordionTriggerProps, type AccordionTriggerEmits, +} from './props' +export { + OkuAccordionContent, +} from './accordionContent' + +export { + OkuAccordionHeader, +} from './accordionHeader' + +export { + OkuAccordionImpl, +} from './accordionImpl' + +export { + OkuAccordionImplSingle, +} from './accordionImplSingle' + +export { + OkuAccordionImplMultiple, +} from './accordionImplMultiple' + +export { + OkuAccordionItem, +} from './accordionItem' + +export { + OkuAccordionTrigger, +} from './accordionTrigger' diff --git a/packages/components/accordion/src/props.ts b/packages/components/accordion/src/props.ts new file mode 100644 index 000000000..abf278a4c --- /dev/null +++ b/packages/components/accordion/src/props.ts @@ -0,0 +1,383 @@ +import type { OkuElement, PrimitiveProps } from '@oku-ui/primitive' +import { ref } from 'vue' +import type { PropType, Ref } from 'vue' +import type { Scope } from '@oku-ui/provide' +import { ScopePropObject, createProvideScope } from '@oku-ui/provide' +import { createCollection } from '@oku-ui/collection' +import type { RovingFocusGroupProps } from '@oku-ui/roving-focus' +import { collapsibleContentProps, collapsibleProps, collapsibleTriggerProps, createCollapsibleScope } from '@oku-ui/collapsible' +import type { CollapsibleContentProps, CollapsibleProps, CollapsibleTriggerEmits, CollapsibleTriggerProps } from '@oku-ui/collapsible' + +import { propsOmit } from '@oku-ui/primitive' + +export const ACCORDION_NAME = 'OkuAccordion' +export const ITEM_NAME = 'OkuAccordionItem' +export const CONTENT_NAME = 'OkuAccordionContent' +export const HEADER_NAME = 'OkuAccordionHeader' +export const ACCORDION_IMPL_NAME = 'OkuAccordionImpl' +export const ACCORDION_IMPL_MULTIPLE_NAME = 'OkuAccordionImplMultiple' +export const ACCORDION_IMPL_SINGLE_NAME = 'OkuAccordionImplSingle' +export const TRIGGER_NAME = 'OkuAccordionTrigger' + +export const ACCORDION_KEYS = ['Home', 'End', 'ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'] + +export type Direction = 'ltr' | 'rtl' +export type SelectionType = 'single' | 'multiple' + +/* -------------------------------------------------------------------------- */ +/* AccordionTrigger - accordionTrigger.ts */ +/* -------------------------------------------------------------------------- */ + +export type AccordionTriggerNativeElement = OkuElement<'button'> + +export type AccordionTriggerElement = HTMLButtonElement + +export interface AccordionTriggerProps extends CollapsibleTriggerProps {} + +export interface AccordionTriggerEmits extends CollapsibleTriggerEmits {} + +export const accordionTriggerProps = { + props: { + ...collapsibleTriggerProps.props, + }, + emits: { + ...collapsibleTriggerProps.emits, + }, +} +/* -------------------------------------------------------------------------- */ +/* **************** */ +/* -------------------------------------------------------------------------- */ +export const { CollectionItemSlot, CollectionProvider, CollectionSlot, useCollection, createCollectionScope } = createCollection(ACCORDION_NAME) + +export type ScopeAccordion = T & { scopeOkuAccordion?: Scope } + +export const scopeAccordionProps = { + scopeOkuAccordion: { + ...ScopePropObject, + }, +} + +export const [createAccordionProvider, createAccordionScope] = createProvideScope(ACCORDION_NAME, [ + createCollectionScope, + createCollapsibleScope, +]) + +export const useCollapsibleScope = createCollapsibleScope() + +type AccordionValueProviderValue = { + value: Ref + onItemOpen(value: string): void + onItemClose(value: string): void +} + +export const [AccordionValueProvider, useAccordionValueInject] + = createAccordionProvider(ACCORDION_NAME) + +export const [AccordionCollapsibleProvider, useAccordionCollapsibleInject] = createAccordionProvider( + ACCORDION_NAME, + { collapsible: ref(false) }, +) +type AccordionImplContextValue = { + disabled?: Ref + direction: Ref + orientation: Ref +} +export const [AccordionImplProvider, useAccordionInject] = createAccordionProvider( + ACCORDION_NAME, +) +type AccordionItemContextValue = +{ open?: Ref + disabled?: Ref + triggerId: Ref +} +export const [AccordionItemProvider, useAccordionItemInject] + = createAccordionProvider(ITEM_NAME) + +/* -------------------------------------------------------------------------- */ +/* Accordion - accordion.ts */ +/* -------------------------------------------------------------------------- */ + +export type AccordionNativeElement = OkuElement<'div'> + +export type AccordionElement = HTMLDivElement + +export interface AccordionSingleProps extends AccordionImplSingleProps { + type: 'single' +} + +export interface AccordionSingleEmits extends AccordionImplSingleEmits { +} + +export interface AccordionMultipleProps extends AccordionImplMultipleProps { + type: 'multiple' +} + +export interface AccordionMultipleEmits extends AccordionImplMultipleEmits { +} +export const accordionProps = { + props: { + value: { + type: [String, Array, undefined] as PropType, + default: undefined, + }, + type: { + type: String as PropType, + default: 'single', + }, + defaultValue: { + type: [String, Array, undefined] as PropType, + default: undefined, + }, + /** + * Whether an accordion item can be collapsed after it has been opened. + * @default false + */ + collapsible: { + type: [Boolean, undefined] as PropType, + default: undefined, + }, + }, + emits: { + /** + * The callback that fires when the state of the accordion changes. + */ + // eslint-disable-next-line unused-imports/no-unused-vars + valueChange: (value: string | string[]) => true, + }, +} + +/* -------------------------------------------------------------------------- */ +/* AccordionContent - accordionContent.ts */ +/* -------------------------------------------------------------------------- */ + +export type AccordionContentNativeElement = OkuElement<'div'> + +export type AccordionContentElement = HTMLDivElement + +export interface AccordionContentProps extends CollapsibleContentProps {} + +export const accordionContentProps = { + props: { + ...collapsibleContentProps.props, + }, + emits: { + }, +} + +/* -------------------------------------------------------------------------- */ +/* AccordionContent - accordionContent.ts */ +/* -------------------------------------------------------------------------- */ +export type AccordionHeaderNativeElement = OkuElement<'h3'> + +export interface AccordionHeaderProps extends PrimitiveProps {} + +export const accordionHeaderProps = { + props: { + + }, + emits: { + }, +} + +/* -------------------------------------------------------------------------- */ +/* AccordionImpl - accordionImpl.ts */ +/* -------------------------------------------------------------------------- */ + +export type AccordionImplNativeElement = OkuElement<'div'> + +export interface AccordionImplProps extends PrimitiveProps { + /** + * Whether or not an accordion is disabled from user interaction. + * + * @defaultValue false + */ + disabled?: boolean + /** + * The layout in which the Accordion operates. + * @default vertical + */ + orientation?: RovingFocusGroupProps['orientation'] + /** + * The language read direction. + */ + dir?: RovingFocusGroupProps['dir'] + +} +export interface AccordionImplEmits { + valueChange: [value: string | string[]] + keydown: [event: KeyboardEvent] +} +export const accordionImplProps = { + props: { + disabled: { + type: [Boolean, undefined] as PropType, + default: undefined, + }, + orientation: { + type: String as PropType, + default: 'vertical', + }, + dir: { + type: String as PropType, + default: undefined, + }, + }, + emits: { + /** + * The callback that fires when the state of the accordion changes. + */ + // eslint-disable-next-line unused-imports/no-unused-vars + valueChange: (value: string | string[]) => true, + // eslint-disable-next-line unused-imports/no-unused-vars + keydown: (event: KeyboardEvent) => true, + }, +} +/* -------------------------------------------------------------------------- */ +/* AccordionImplMultiple - accordionImplMultiple.ts */ +/* -------------------------------------------------------------------------- */ +export type AccordionImplMultipleNativeElement = OkuElement<'div'> + +export type AccordionImplMultipleElement = HTMLDivElement + +export interface AccordionImplMultipleProps extends AccordionImplProps { + /** + * The controlled stateful value of the accordion item whose content is expanded. + */ + value?: string[] + /** + * The value of the item whose content is expanded when the accordion is initially rendered. Use + * `defaultValue` if you do not need to control the state of an accordion. + */ + defaultValue?: string[] +} +export interface AccordionImplMultipleEmits extends AccordionImplEmits { + /** + * The callback that fires when the state of the accordion changes. + */ + valueChange: [value: string[]] +} +export const accordionImplMultipleProps = { + props: { + ...accordionImplProps.props, + modelValue: { + type: [Array, undefined] as PropType, + default: undefined, + }, + value: { + type: [Array, undefined] as PropType, + default: undefined, + }, + defaultValue: { + type: [Array, undefined] as PropType, + default: undefined, + }, + }, + emits: { + ...accordionImplProps.emits, + /** + * The callback that fires when the state of the accordion changes. + */ + // eslint-disable-next-line unused-imports/no-unused-vars + 'valueChange': (value: string[]) => true, + // eslint-disable-next-line unused-imports/no-unused-vars + 'update:modelValue': (value: string[]) => true, + }, +} + +/* -------------------------------------------------------------------------- */ +/* AccordionImplSingle - accordionImplSingle.ts */ +/* -------------------------------------------------------------------------- */ +export type AccordionImplSingleNativeElement = OkuElement<'div'> + +export interface AccordionImplSingleProps extends AccordionImplProps { + /** + * The controlled stateful value of the accordion item whose content is expanded. + */ + value?: string + /** + * The value of the item whose content is expanded when the accordion is initially rendered. Use + * `defaultValue` if you do not need to control the state of an accordion. + */ + defaultValue?: string + + /** + * Whether an accordion item can be collapsed after it has been opened. + * @default false + */ + collapsible?: boolean +} +export interface AccordionImplSingleEmits extends AccordionImplEmits { + /** + * The callback that fires when the state of the accordion changes. + */ + + valueChange: [value: string] +} + +export const accordionImplSingleProps = { + props: { + ...accordionImplProps.props, + modelValue: { + type: [String, undefined] as PropType, + default: undefined, + }, + value: { + type: [String, undefined] as PropType, + default: undefined, + }, + defaultValue: { + type: [String, undefined] as PropType, + default: undefined, + }, + collapsible: { + type: [Boolean, undefined] as PropType, + default: false, + }, + }, + emits: { + ...accordionImplProps.emits, + /** + * The callback that fires when the state of the accordion changes. + */ + // eslint-disable-next-line unused-imports/no-unused-vars + 'valueChange': (value: string) => true, + // eslint-disable-next-line unused-imports/no-unused-vars + 'update:modelValue': (value: string) => true, + }, +} +/* -------------------------------------------------------------------------- */ +/* AccordionItem - accordionItem.ts */ +/* -------------------------------------------------------------------------- */ + +export type AccordionItemNativeElement = OkuElement<'div'> + +export interface AccordionItemProps extends Omit { + /** + * Whether or not an accordion is disabled from user interaction. + * + * @defaultValue false + */ + disabled?: boolean + /** + * A string value for the accordion item. All items within an accordion should use a unique value. + */ + value: string + +} +export interface AccordionItemEmits extends Omit { +} + +export const accordionItemProps = { + props: { + ...propsOmit(collapsibleProps.props, ['open', 'defaultOpen']), + disabled: { + type: [Boolean, undefined] as PropType, + }, + value: { + type: String as PropType, + }, + }, + emits: { + ...propsOmit(collapsibleProps.emits, ['openChange']), + }, +} diff --git a/packages/components/accordion/src/stories/AccordionDemo.vue b/packages/components/accordion/src/stories/AccordionDemo.vue new file mode 100644 index 000000000..67dfdef8b --- /dev/null +++ b/packages/components/accordion/src/stories/AccordionDemo.vue @@ -0,0 +1,336 @@ + + + + + diff --git a/packages/components/accordion/src/stories/Animated.vue b/packages/components/accordion/src/stories/Animated.vue new file mode 100644 index 000000000..ef1b62fa2 --- /dev/null +++ b/packages/components/accordion/src/stories/Animated.vue @@ -0,0 +1,72 @@ + + + diff --git a/packages/components/accordion/src/stories/Animated2D.vue b/packages/components/accordion/src/stories/Animated2D.vue new file mode 100644 index 000000000..3f41e4c66 --- /dev/null +++ b/packages/components/accordion/src/stories/Animated2D.vue @@ -0,0 +1,33 @@ + + + diff --git a/packages/components/accordion/src/stories/AnimatedControlled.vue b/packages/components/accordion/src/stories/AnimatedControlled.vue new file mode 100644 index 000000000..a62c3883c --- /dev/null +++ b/packages/components/accordion/src/stories/AnimatedControlled.vue @@ -0,0 +1,63 @@ + + + diff --git a/packages/components/accordion/src/stories/Chromatic.vue b/packages/components/accordion/src/stories/Chromatic.vue new file mode 100644 index 000000000..e41e6eeb3 --- /dev/null +++ b/packages/components/accordion/src/stories/Chromatic.vue @@ -0,0 +1,210 @@ + + + diff --git a/packages/components/accordion/src/stories/Horizontal.vue b/packages/components/accordion/src/stories/Horizontal.vue new file mode 100644 index 000000000..9a4e58f81 --- /dev/null +++ b/packages/components/accordion/src/stories/Horizontal.vue @@ -0,0 +1,57 @@ + + + diff --git a/packages/components/accordion/src/stories/Multiple.vue b/packages/components/accordion/src/stories/Multiple.vue new file mode 100644 index 000000000..f5a111299 --- /dev/null +++ b/packages/components/accordion/src/stories/Multiple.vue @@ -0,0 +1,112 @@ + + + diff --git a/packages/components/accordion/src/stories/OutsideViewport.vue b/packages/components/accordion/src/stories/OutsideViewport.vue new file mode 100644 index 000000000..98458817f --- /dev/null +++ b/packages/components/accordion/src/stories/OutsideViewport.vue @@ -0,0 +1,62 @@ + + + diff --git a/packages/components/accordion/src/stories/Single.vue b/packages/components/accordion/src/stories/Single.vue new file mode 100644 index 000000000..5a82fa21b --- /dev/null +++ b/packages/components/accordion/src/stories/Single.vue @@ -0,0 +1,162 @@ + + + diff --git a/packages/components/accordion/src/stories/accordion.stories.ts b/packages/components/accordion/src/stories/accordion.stories.ts new file mode 100644 index 000000000..62593d9cf --- /dev/null +++ b/packages/components/accordion/src/stories/accordion.stories.ts @@ -0,0 +1,141 @@ +import type { Meta, StoryObj } from '@storybook/vue3' + +import type { OkuAccordionProps } from './AccordionDemo.vue' +import OkuAccordionComponent from './AccordionDemo.vue' + +interface StoryProps extends OkuAccordionProps { +} + +const meta = { + title: 'Components/Accordion', + component: OkuAccordionComponent, + args: { + template: 'Single', + }, + argTypes: { + template: { + control: 'text', + }, + }, + tags: ['autodocs'], +} satisfies Meta & { + args: StoryProps +} + +export default meta +type Story = StoryObj & { + args: StoryProps +} + +export const Single: Story = { + args: { + template: 'Single', + }, + render: (args: any) => ({ + components: { OkuAccordionComponent }, + setup() { + return { args } + }, + template: ` + + `, + }), +} +export const Multiple: Story = { + args: { + template: 'Multiple', + }, + render: (args: any) => ({ + components: { OkuAccordionComponent }, + setup() { + return { args } + }, + template: ` + + `, + }), +} +export const Animated: Story = { + args: { + template: 'Animated', + }, + render: (args: any) => ({ + components: { OkuAccordionComponent }, + setup() { + return { args } + }, + template: ` + + `, + }), +} +export const Animated2D: Story = { + args: { + template: 'Animated2D', + }, + render: (args: any) => ({ + components: { OkuAccordionComponent }, + setup() { + return { args } + }, + template: ` + + `, + }), +} +export const AnimatedControlled: Story = { + args: { + template: 'AnimatedControlled', + }, + render: (args: any) => ({ + components: { OkuAccordionComponent }, + setup() { + return { args } + }, + template: ` + + `, + }), +} +export const OutsideViewport: Story = { + args: { + template: 'OutsideViewport', + }, + render: (args: any) => ({ + components: { OkuAccordionComponent }, + setup() { + return { args } + }, + template: ` + + `, + }), +} +export const Horizontal: Story = { + args: { + template: 'Horizontal', + }, + render: (args: any) => ({ + components: { OkuAccordionComponent }, + setup() { + return { args } + }, + template: ` + + `, + }), +} +export const Chromatic: Story = { + args: { + template: 'Chromatic', + }, + render: (args: any) => ({ + components: { OkuAccordionComponent }, + setup() { + return { args } + }, + template: ` + + `, + }), +} diff --git a/packages/components/accordion/src/utils.ts b/packages/components/accordion/src/utils.ts new file mode 100644 index 000000000..ff3c44642 --- /dev/null +++ b/packages/components/accordion/src/utils.ts @@ -0,0 +1,3 @@ +export function getState(open?: boolean) { + return open ? 'open' : 'closed' +} diff --git a/packages/components/accordion/tsconfig.json b/packages/components/accordion/tsconfig.json new file mode 100644 index 000000000..23fbd28d6 --- /dev/null +++ b/packages/components/accordion/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "tsconfig/node18.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "types": ["node"] + }, + "include": [ + "src" + ] +} diff --git a/packages/components/alert-dialog/package.json b/packages/components/alert-dialog/package.json index 540e77794..6355761e4 100644 --- a/packages/components/alert-dialog/package.json +++ b/packages/components/alert-dialog/package.json @@ -1,7 +1,7 @@ { "name": "@oku-ui/alert-dialog", "type": "module", - "version": "0.4.0-alpha.12", + "version": "0.5.0", "license": "MIT", "source": "src/index.ts", "funding": "https://github.com/sponsors/productdevbook", @@ -43,7 +43,7 @@ "@oku-ui/slot": "latest", "@oku-ui/use-composable": "latest", "@oku-ui/utils": "latest", - "@types/node": "^18.17.17" + "@types/node": "^18.18.1" }, "devDependencies": { "tsconfig": "workspace:^" diff --git a/packages/components/alert-dialog/src/alert-dialog-description-warning.ts b/packages/components/alert-dialog/src/alert-dialog-description-warning.ts index 3b3335237..407806be1 100644 --- a/packages/components/alert-dialog/src/alert-dialog-description-warning.ts +++ b/packages/components/alert-dialog/src/alert-dialog-description-warning.ts @@ -1,12 +1,12 @@ import { defineComponent, toRefs, watchEffect } from 'vue' -import { CONTENT_NAME, DESCRIPTION_NAME, DESCRIPTION_WARNING_NAME, dialogDescriptionWarningProps } from './props' +import { CONTENT_NAME, DESCRIPTION_NAME, DESCRIPTION_WARNING_NAME, alertDialogDescriptionWarningProps } from './props' const alertDialogDescriptionWarning = defineComponent({ name: DESCRIPTION_WARNING_NAME, inheritAttrs: false, props: { - ...dialogDescriptionWarningProps.props, + ...alertDialogDescriptionWarningProps.props, }, setup(props, { attrs: _attrs }) { const { contentRef } = toRefs(props) diff --git a/packages/components/alert-dialog/src/alert-dialog-description.ts b/packages/components/alert-dialog/src/alert-dialog-description.ts index a4842ca9a..19d69ddf8 100644 --- a/packages/components/alert-dialog/src/alert-dialog-description.ts +++ b/packages/components/alert-dialog/src/alert-dialog-description.ts @@ -2,13 +2,13 @@ import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue' import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable' import { OkuDialogDescription } from '@oku-ui/dialog' import type { AlertDialogDescriptionNaviteElement } from './props' -import { DESCRIPTION_NAME, alertDialogDescriptionProps, scopeAlertDialogProps, useAlertDialogScope } from './props' +import { DESCRIPTION_NAME, alertDialogDescriptionWarningProps, scopeAlertDialogProps, useAlertDialogScope } from './props' const alertDialogDescription = defineComponent({ name: DESCRIPTION_NAME, inheritAttrs: false, props: { - ...alertDialogDescriptionProps.props, + ...alertDialogDescriptionWarningProps.props, ...scopeAlertDialogProps, }, setup(props, { attrs, slots }) { diff --git a/packages/components/alert-dialog/src/index.ts b/packages/components/alert-dialog/src/index.ts index 08d47ed31..55db805b0 100644 --- a/packages/components/alert-dialog/src/index.ts +++ b/packages/components/alert-dialog/src/index.ts @@ -52,6 +52,6 @@ export { alertDialogPortalProps, alertDialogTitleProps, alertDialogTriggerProps, - dialogDescriptionWarningProps, + alertDialogDescriptionWarningProps, scopeAlertDialogProps, } from './props' diff --git a/packages/components/alert-dialog/src/props.ts b/packages/components/alert-dialog/src/props.ts index afec4be64..e88bba150 100644 --- a/packages/components/alert-dialog/src/props.ts +++ b/packages/components/alert-dialog/src/props.ts @@ -176,7 +176,7 @@ export const alertDialogDescriptionProps = { export type DescriptionWarningProps = { contentRef: HTMLDivElement } -export const dialogDescriptionWarningProps = { +export const alertDialogDescriptionWarningProps = { props: { contentRef: { type: [Object, null] as PropType, diff --git a/packages/components/alert-dialog/tests/__snapshots__/alert-dialog.test.ts.snap b/packages/components/alert-dialog/tests/__snapshots__/alert-dialog.test.ts.snap index 287b320e6..abc7d3174 100644 --- a/packages/components/alert-dialog/tests/__snapshots__/alert-dialog.test.ts.snap +++ b/packages/components/alert-dialog/tests/__snapshots__/alert-dialog.test.ts.snap @@ -86,7 +86,7 @@ exports[`OkuAlertDialog > Controlled > should be able to close 1`] = ` exports[`OkuAlertDialog > Controlled > should be able to open 1`] = ` StyledVue > should be able to close 1`] = ` exports[`OkuAlertDialog > StyledVue > should be able to open 1`] = ` { const wrapper = () => mount(OkuAlertDialogDescription) expect(() => wrapper()).toThrowErrorMatchingSnapshot() expect(spy).toHaveBeenCalled() - - expect(spy.mock.calls[0][0]).toContain('[Vue warn]: injection "Symbol(OkuDialog)" not found.') + expect(spy.mock.calls[0][0]).toContain('Missing required prop: "contentRef"') }) it('OkuAlertDialogDescriptionWarning renders correctly', () => { diff --git a/packages/components/aspect-ratio/package.json b/packages/components/aspect-ratio/package.json index f421cb9c6..40e1ca109 100644 --- a/packages/components/aspect-ratio/package.json +++ b/packages/components/aspect-ratio/package.json @@ -1,7 +1,7 @@ { "name": "@oku-ui/aspect-ratio", "type": "module", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "source": "src/index.ts", "funding": "https://github.com/sponsors/productdevbook", diff --git a/packages/components/avatar/package.json b/packages/components/avatar/package.json index 02e3f8542..07f5e9344 100644 --- a/packages/components/avatar/package.json +++ b/packages/components/avatar/package.json @@ -1,7 +1,7 @@ { "name": "@oku-ui/avatar", "type": "module", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "source": "src/index.ts", "funding": "https://github.com/sponsors/productdevbook", diff --git a/packages/components/checkbox/package.json b/packages/components/checkbox/package.json index ed375f29c..e5e5ce18f 100644 --- a/packages/components/checkbox/package.json +++ b/packages/components/checkbox/package.json @@ -1,7 +1,7 @@ { "name": "@oku-ui/checkbox", "type": "module", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "source": "src/index.ts", "funding": "https://github.com/sponsors/productdevbook", diff --git a/packages/components/collapsible/package.json b/packages/components/collapsible/package.json index 045587e9c..5bde35e89 100644 --- a/packages/components/collapsible/package.json +++ b/packages/components/collapsible/package.json @@ -1,7 +1,7 @@ { "name": "@oku-ui/collapsible", "type": "module", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "source": "src/index.ts", "funding": "https://github.com/sponsors/productdevbook", diff --git a/packages/components/collapsible/src/collapsible.ts b/packages/components/collapsible/src/collapsible.ts index 44c3a6d9e..6c77618ff 100644 --- a/packages/components/collapsible/src/collapsible.ts +++ b/packages/components/collapsible/src/collapsible.ts @@ -123,8 +123,8 @@ const collapsible = defineComponent({ { 'data-state': getState(state.value), 'data-disabled': disabled.value ? '' : undefined, - 'ref': forwardedRef, ...mergeProps(attrs, reactiveCollapsibleProps), + 'ref': forwardedRef, }, { default: () => slots.default?.(), diff --git a/packages/components/collapsible/src/collapsibleContent.ts b/packages/components/collapsible/src/collapsibleContent.ts index 952829a32..827051141 100644 --- a/packages/components/collapsible/src/collapsibleContent.ts +++ b/packages/components/collapsible/src/collapsibleContent.ts @@ -57,7 +57,6 @@ const collapsibleContent = defineComponent({ const forwardedRef = useForwardRef() - // TODO: Transition const originalReturn = () => h( OkuPresence, { @@ -70,10 +69,7 @@ const collapsibleContent = defineComponent({ ...mergeProps(attrs, reactiveContentProps), ref: forwardedRef, present: isPresent.value, - }, - { - default: () => slots.default && slots.default(), - }, + }, slots, ), }, ) diff --git a/packages/components/collapsible/src/collapsibleContentImpl.ts b/packages/components/collapsible/src/collapsibleContentImpl.ts index c8b23c820..14e4149a7 100644 --- a/packages/components/collapsible/src/collapsibleContentImpl.ts +++ b/packages/components/collapsible/src/collapsibleContentImpl.ts @@ -1,4 +1,4 @@ -import { computed, defineComponent, h, mergeProps, nextTick, onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue' +import { computed, defineComponent, h, mergeProps, nextTick, onBeforeUnmount, onMounted, reactive, ref, toRefs, watchEffect } from 'vue' import type { OkuElement, PrimitiveProps } from '@oku-ui/primitive' import { Primitive, primitiveProps } from '@oku-ui/primitive' @@ -61,8 +61,7 @@ const collapsibleContentImpl = defineComponent({ cancelAnimationFrame(rAf.value) }) - watch([isOpen, isPresent], async () => { - await nextTick() + watchEffect(async () => { const node = _ref.value if (node) { originalStylesRef.value = originalStylesRef.value || { @@ -77,6 +76,7 @@ const collapsibleContentImpl = defineComponent({ const rect = node.getBoundingClientRect() heightRef.value = rect.height widthRef.value = rect.width + await nextTick() // kick off any animations/transitions that were originally set up if it isn't the initial mount if (!isMountAnimationPreventedRef.value) { diff --git a/packages/components/dialog/package.json b/packages/components/dialog/package.json index 98a034d1f..d019f145f 100644 --- a/packages/components/dialog/package.json +++ b/packages/components/dialog/package.json @@ -1,7 +1,7 @@ { "name": "@oku-ui/dialog", "type": "module", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "source": "src/index.ts", "funding": "https://github.com/sponsors/productdevbook", @@ -47,7 +47,7 @@ "@oku-ui/slot": "latest", "@oku-ui/use-composable": "latest", "@oku-ui/utils": "latest", - "@types/node": "^18.17.17", + "@types/node": "^18.18.1", "aria-hidden": "^1.2.3" }, "devDependencies": { diff --git a/packages/components/hover-card/package.json b/packages/components/hover-card/package.json index 9028ec339..7a6c20660 100644 --- a/packages/components/hover-card/package.json +++ b/packages/components/hover-card/package.json @@ -1,7 +1,7 @@ { "name": "@oku-ui/hover-card", "type": "module", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "source": "src/index.ts", "funding": "https://github.com/sponsors/productdevbook", diff --git a/packages/components/label/package.json b/packages/components/label/package.json index 70a17db8d..d2ef59acf 100644 --- a/packages/components/label/package.json +++ b/packages/components/label/package.json @@ -1,7 +1,7 @@ { "name": "@oku-ui/label", "type": "module", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "source": "src/index.ts", "funding": "https://github.com/sponsors/productdevbook", diff --git a/packages/components/popover/package.json b/packages/components/popover/package.json index 6838eccc1..de9173d95 100644 --- a/packages/components/popover/package.json +++ b/packages/components/popover/package.json @@ -1,7 +1,7 @@ { "name": "@oku-ui/popover", "type": "module", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "source": "src/index.ts", "funding": "https://github.com/sponsors/productdevbook", diff --git a/packages/components/popover/src/index.ts b/packages/components/popover/src/index.ts index 1c8628605..1005b9245 100644 --- a/packages/components/popover/src/index.ts +++ b/packages/components/popover/src/index.ts @@ -2,72 +2,63 @@ import type { } from '@floating-ui/vue' export { OkuPopover, - createPopoverScope, - popoverProps, -} from './popover' - -export type { - PopoverEmits, - PopoverProps, } from './popover' export { OkuPopoverAnchor, - popoverAnchorProps, -} from './popoverAnchor' - -export type { - PopoverAnchorElement, - PopoverAnchorNaviteElement, - PopoverAnchorProps, } from './popoverAnchor' export { OkuPopoverTrigger, - popoverTriggerProps, -} from './popoverTrigger' - -export type { - PopoverTriggerNaviteElement, - PopoverTriggerEmits, - PopoverTriggerProps, } from './popoverTrigger' export { OkuPopoverPortal, - popoverPortalProps, -} from './popoverPortal' - -export type { - PopoverPortalProps, } from './popoverPortal' export { OkuPopoverContent, - popoverContentProps, -} from './popoverContent' - -export type { - PopoverContentProps, } from './popoverContent' export { OkuPopoverClose, } from './popoverClose' -export type { - PopoverCloseNaviteElement, - PopoverCloseElement, - PopoverCloseEmits, -} from './popoverClose' - export { OkuPopoverArrow, - popoverArrowProps, } from './popoverArrow' export type { PopoverArrowProps, PopoverArrowElement, PopoverArrowNaviteElement, -} from './popoverArrow' + + PopoverCloseNaviteElement, + PopoverCloseElement, + PopoverCloseEmits, + + PopoverPortalProps, + + PopoverTriggerNaviteElement, + PopoverTriggerEmits, + PopoverTriggerProps, + + PopoverContentProps, + + PopoverAnchorElement, + PopoverAnchorNaviteElement, + PopoverAnchorProps, + + PopoverEmits, + PopoverProps, +} from './props' + +export { + popoverArrowProps, + popoverTriggerProps, + popoverContentProps, + popoverAnchorProps, + createPopoverScope, + popoverProps, + popoverPortalProps, +} from './props' diff --git a/packages/components/popover/src/popover.ts b/packages/components/popover/src/popover.ts index 221fdaa13..713f31627 100644 --- a/packages/components/popover/src/popover.ts +++ b/packages/components/popover/src/popover.ts @@ -1,81 +1,13 @@ -import type { PropType, Ref } from 'vue' import { computed, defineComponent, h, ref, toRefs, useModel } from 'vue' -import { primitiveProps } from '@oku-ui/primitive' import { useControllable, useId } from '@oku-ui/use-composable' -import { createProvideScope } from '@oku-ui/provide' -import { OkuPopper, createPopperScope } from '@oku-ui/popper' -import { scopePopoverProps } from './utils' - -const POPOVER_NAME = 'OkuPopover' - -export const [createPopoverProvide, createPopoverScope] = createProvideScope(POPOVER_NAME, [ - createPopperScope, -]) - -export const usePopperScope = createPopperScope() - -type PopoverProvideValue = { - triggerRef: Ref - contentId: Ref - open: Ref - onOpenChange(open: boolean): void - onOpenToggle(): void - hasCustomAnchor: Ref - onCustomAnchorAdd(): void - onCustomAnchorRemove(): void - modal: Ref -} - -const [popoverProvide, usePopoverInject] - = createPopoverProvide(POPOVER_NAME) - -export { - usePopoverInject, -} - -export interface PopoverProps { - open?: boolean - defaultOpen?: boolean - modal?: boolean -} - -export interface PopoverEmits { - 'openChange': [open: boolean] -} - -export const popoverProps = { - props: { - modelValue: { - type: [Boolean, undefined] as PropType, - default: undefined, - }, - open: { - type: Boolean, - default: undefined, - }, - defaultOpen: { - type: Boolean, - default: undefined, - }, - modal: { - type: Boolean, - default: false, - }, - }, - emits: { - // eslint-disable-next-line unused-imports/no-unused-vars - 'openChange': (open: boolean) => true, - // eslint-disable-next-line unused-imports/no-unused-vars - 'update:modelValue': (open: boolean) => true, - }, -} +import { OkuPopper } from '@oku-ui/popper' +import { POPOVER_NAME, popoverProps, popoverProvide, scopePopoverProps, usePopperScope } from './props' const popover = defineComponent({ name: POPOVER_NAME, inheritAttrs: false, props: { ...popoverProps.props, - ...primitiveProps, ...scopePopoverProps, }, emits: popoverProps.emits, @@ -94,14 +26,16 @@ const popover = defineComponent({ const hasCustomAnchor = ref(false) const modelValue = useModel(props, 'modelValue') - const proxyChecked = computed({ - get: () => modelValue.value !== undefined - ? modelValue.value - : openProp.value !== undefined - ? openProp.value - : undefined, - set: () => { - }, + + const proxyChecked = computed(() => { + if (modelValue.value !== undefined) + return modelValue.value + + else if (openProp.value !== undefined) + return openProp.value + + else + return undefined }) const { state, updateValue } = useControllable({ diff --git a/packages/components/popover/src/popoverAnchor.ts b/packages/components/popover/src/popoverAnchor.ts index 1d87be930..f897d2ea3 100644 --- a/packages/components/popover/src/popoverAnchor.ts +++ b/packages/components/popover/src/popoverAnchor.ts @@ -1,33 +1,15 @@ import { defineComponent, h, mergeProps, reactive, toRefs, watchEffect } from 'vue' -import { primitiveProps } from '@oku-ui/primitive' import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable' -import { OkuPopperAnchor, popperAnchorProps } from '@oku-ui/popper' -import type { PopperAnchorElement, PopperAnchorNaviteElement, PopperAnchorProps } from '@oku-ui/popper' -import { scopePopoverProps } from './utils' -import { usePopoverInject, usePopperScope } from './popover' +import { OkuPopperAnchor } from '@oku-ui/popper' -export type PopoverAnchorNaviteElement = PopperAnchorNaviteElement -export type PopoverAnchorElement = PopperAnchorElement - -export interface PopoverAnchorProps extends PopperAnchorProps { } - -export const popoverAnchorProps = { - props: { - ...popperAnchorProps.props, - }, - emits: { - ...popperAnchorProps.emits, - }, -} - -const ANCHOR_NAME = 'OkuPopoverAnchor' +import type { PopoverAnchorNaviteElement } from './props' +import { ANCHOR_NAME, popoverAnchorProps, scopePopoverProps, usePopoverInject, usePopperScope } from './props' const popoverAnchor = defineComponent({ name: ANCHOR_NAME, inheritAttrs: false, props: { ...popoverAnchorProps.props, - ...primitiveProps, ...scopePopoverProps, }, emits: popoverAnchorProps.emits, diff --git a/packages/components/popover/src/popoverArrow.ts b/packages/components/popover/src/popoverArrow.ts index bd15f2b92..efdd9a868 100644 --- a/packages/components/popover/src/popoverArrow.ts +++ b/packages/components/popover/src/popoverArrow.ts @@ -1,31 +1,13 @@ import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue' -import { primitiveProps } from '@oku-ui/primitive' import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable' -import { OkuPopperArrow, type PopperArrowElement, type PopperArrowNaviteElement, type PopperArrowProps, popperAnchorProps } from '@oku-ui/popper' -import { scopePopoverProps } from './utils' -import { usePopperScope } from './popover' - -export type PopoverArrowNaviteElement = PopperArrowNaviteElement -export type PopoverArrowElement = PopperArrowElement - -export interface PopoverArrowProps extends PopperArrowProps { } - -export const popoverArrowProps = { - props: { - ...popperAnchorProps.props, - }, - emits: { - ...popperAnchorProps.emits, - }, -} - -const ARROW_NAME = 'PopoverArrow' +import { OkuPopperArrow } from '@oku-ui/popper' +import type { PopoverArrowNaviteElement } from './props' +import { ARROW_NAME, popoverArrowProps, scopePopoverProps, usePopperScope } from './props' const popoverArrow = defineComponent({ name: ARROW_NAME, inheritAttrs: false, props: { - ...primitiveProps, ...popoverArrowProps.props, ...scopePopoverProps, }, diff --git a/packages/components/popover/src/popoverClose.ts b/packages/components/popover/src/popoverClose.ts index 0e3b3c484..cf441d128 100644 --- a/packages/components/popover/src/popoverClose.ts +++ b/packages/components/popover/src/popoverClose.ts @@ -1,37 +1,16 @@ import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue' -import type { OkuElement, PrimitiveProps } from '@oku-ui/primitive' -import { Primitive, primitiveProps } from '@oku-ui/primitive' +import { Primitive } from '@oku-ui/primitive' import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable' import { composeEventHandlers } from '@oku-ui/utils' -import { scopePopoverProps } from './utils' -import { usePopoverInject } from './popover' - -export type PopoverCloseNaviteElement = OkuElement<'button'> -export type PopoverCloseElement = HTMLButtonElement - -export interface PopoverCloseProps extends PrimitiveProps { } - -export type PopoverCloseEmits = { - click: [event: MouseEvent] -} - -export const popoverArrowProps = { - props: { - ...primitiveProps, - }, - emits: { - // eslint-disable-next-line unused-imports/no-unused-vars - click: (event: MouseEvent) => true, - }, -} - -const CLOSE_NAME = 'OkuPopoverClose' +import type { PopoverCloseEmits, PopoverCloseNaviteElement } from './props' +import { CLOSE_NAME, popoverCloseProps, scopePopoverProps, usePopoverInject } from './props' const popoverClose = defineComponent({ name: CLOSE_NAME, inheritAttrs: false, props: { ...scopePopoverProps, + ...popoverCloseProps.props, }, setup(props, { attrs, slots, emit }) { const { scopeOkuPopover, ...closeProps } = toRefs(props) diff --git a/packages/components/popover/src/popoverContent.ts b/packages/components/popover/src/popoverContent.ts index 182bb091c..76c53c83d 100644 --- a/packages/components/popover/src/popoverContent.ts +++ b/packages/components/popover/src/popoverContent.ts @@ -1,46 +1,15 @@ -import type { PropType } from 'vue' import { computed, defineComponent, h, mergeProps, reactive, toRefs } from 'vue' -import { primitiveProps } from '@oku-ui/primitive' import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable' import { OkuPresence } from '@oku-ui/presence' -import { scopePopoverProps } from './utils' -import { usePortalInject } from './popoverPortal' -import { usePopoverInject } from './popover' -import type { PopoverContentTypeProps } from './popoverContentModal' -import { OkuPopoverContentModal, popoverContentTypeProps } from './popoverContentModal' +import { CONTENT_NAME, popoverContentProps, scopePopoverProps, usePopoverInject, usePortalInject } from './props' +import { OkuPopoverContentModal } from './popoverContentModal' import { OkuPopoverContentNonModal } from './popoverContentNonModal' -export const CONTENT_NAME = 'OkuPopoverContent' - -// TODO: PopoverContentTypeProps - -export interface PopoverContentProps extends PopoverContentTypeProps { - /** - * Used to force mounting when more control is needed. Useful when - * controlling animation with React animation libraries. - */ - forceMount?: true -} - -export const popoverContentProps = { - props: { - forceMount: { - type: Boolean as PropType, - default: undefined, - }, - ...popoverContentTypeProps.props, - }, - emits: { - ...popoverContentTypeProps.emits, - }, -} - const popoverContent = defineComponent({ name: CONTENT_NAME, inheritAttrs: false, props: { ...popoverContentProps.props, - ...primitiveProps, ...scopePopoverProps, }, emits: popoverContentProps.emits, @@ -55,7 +24,7 @@ const popoverContent = defineComponent({ const forwardedRef = useForwardRef() return () => h(OkuPresence, { - present: computed(() => forceMount.value || inject.open.value).value, + present: forceMount.value || inject.open.value, }, { default: () => inject.modal.value ? h(OkuPopoverContentModal, { diff --git a/packages/components/popover/src/popoverContentImpl.ts b/packages/components/popover/src/popoverContentImpl.ts index 439dd2631..291b689cc 100644 --- a/packages/components/popover/src/popoverContentImpl.ts +++ b/packages/components/popover/src/popoverContentImpl.ts @@ -1,69 +1,18 @@ -import type { PropType } from 'vue' import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue' -import type { OkuElement } from '@oku-ui/primitive' -import { primitiveProps } from '@oku-ui/primitive' import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable' -import { OkuPopperContent, type PopperContentEmits, type PopperContentProps, popperContentProps } from '@oku-ui/popper' -import { type FocusScopeEmits, type FocusScopeProps, OkuFocusScope } from '@oku-ui/focus-scope' -import { type DismissableLayerEmits, type DismissableLayerProps, OkuDismissableLayer, dismissableLayerProps } from '@oku-ui/dismissable-layer' +import { OkuPopperContent } from '@oku-ui/popper' +import { OkuFocusScope } from '@oku-ui/focus-scope' +import { OkuDismissableLayer } from '@oku-ui/dismissable-layer' import { useFocusGuards } from '@oku-ui/focus-guards' -import { getState, scopePopoverProps } from './utils' -import { usePopoverInject, usePopperScope } from './popover' - -export type PopoverContentImplNaviteElement = OkuElement<'label'> -export type PopoverContentImplElement = HTMLLabelElement - -export interface PopoverContentImplProps - extends PopperContentProps, - DismissableLayerProps { - /** - * Whether focus should be trapped within the `Popover` - * (default: false) - */ - trapFocus?: FocusScopeProps['trapped'] -} - -export type PopoverContentImplEmits = { - /** - * Event handler called when auto-focusing on open. - * Can be prevented. - */ - openAutoFocus: [event: FocusScopeEmits['mountAutoFocus'][0]] - /** - * Event handler called when auto-focusing on close. - * Can be prevented. - */ - closeAutoFocus: [event: FocusScopeEmits['unmountAutoFocus'][0]] -} & Omit -& Omit - -export const popoverContentImplProps = { - props: { - ...popperContentProps.props, - ...dismissableLayerProps.props, - trapFocus: { - type: Boolean as PropType, - default: false, - }, - }, - emits: { - ...popperContentProps.emits, - ...dismissableLayerProps.emits, - // eslint-disable-next-line unused-imports/no-unused-vars - openAutoFocus: (event: FocusScopeEmits['mountAutoFocus'][0]) => true, - // eslint-disable-next-line unused-imports/no-unused-vars - closeAutoFocus: (event: FocusScopeEmits['unmountAutoFocus'][0]) => true, - }, -} - -const NAME = 'OkuPopoverContentImpl' +import type { PopoverContentImplNaviteElement } from './props' +import { CONTENT_IMPL_NAME, popoverContentImplProps, scopePopoverProps, usePopoverInject, usePopperScope } from './props' +import { getState } from './utils' const popoverContentImpl = defineComponent({ - name: NAME, + name: CONTENT_IMPL_NAME, inheritAttrs: false, props: { ...popoverContentImplProps.props, - ...primitiveProps, ...scopePopoverProps, }, emits: popoverContentImplProps.emits, @@ -77,7 +26,7 @@ const popoverContentImpl = defineComponent({ const _reactive = reactive(contentProps) const reactiveContentProps = reactiveOmit(_reactive, (key, _value) => key === undefined) - const inject = usePopoverInject(NAME, scopeOkuPopover.value) + const inject = usePopoverInject(CONTENT_IMPL_NAME, scopeOkuPopover.value) const popperScope = usePopperScope(scopeOkuPopover.value) const forwardedRef = useForwardRef() diff --git a/packages/components/popover/src/popoverContentModal.ts b/packages/components/popover/src/popoverContentModal.ts index 8b73fb328..1d00ce4f8 100644 --- a/packages/components/popover/src/popoverContentModal.ts +++ b/packages/components/popover/src/popoverContentModal.ts @@ -1,40 +1,17 @@ import { defineComponent, h, mergeProps, onBeforeUnmount, ref } from 'vue' -import { primitiveProps, propsOmit } from '@oku-ui/primitive' import { useComposedRefs, useForwardRef, useScrollLock } from '@oku-ui/use-composable' import { hideOthers } from 'aria-hidden' import { OkuSlot } from '@oku-ui/slot' import { composeEventHandlers } from '@oku-ui/utils' -import { OkuPopoverContentImpl, popoverContentImplProps } from './popoverContentImpl' -import type { PopoverContentImplElement, PopoverContentImplEmits, PopoverContentImplNaviteElement, PopoverContentImplProps } from './popoverContentImpl' -import { scopePopoverProps } from './utils' -import { usePopoverInject } from './popover' -import { CONTENT_NAME } from './popoverContent' - -export type PopoverContentTypeNaviteElement = PopoverContentImplNaviteElement -export type PopoverContentTypeElement = PopoverContentImplElement - -export interface PopoverContentTypeProps - extends Omit { } - -export interface PopoverContentTypeEmits extends PopoverContentImplEmits { } - -export const popoverContentTypeProps = { - props: { - ...propsOmit(popoverContentImplProps.props, ['trapFocus', 'disableOutsidePointerEvents']), - }, - emits: { - ...popoverContentImplProps.emits, - }, -} - -const NAME = 'OkuPopoverContentModal' +import { OkuPopoverContentImpl } from './popoverContentImpl' +import type { PopoverContentTypeEmits, PopoverContentTypeNaviteElement } from './props' +import { CONTENT_MODAL_NAME, CONTENT_NAME, popoverContentTypeProps, scopePopoverProps, usePopoverInject } from './props' const popoverContentModal = defineComponent({ - name: NAME, + name: CONTENT_MODAL_NAME, inheritAttrs: false, props: { ...popoverContentTypeProps.props, - ...primitiveProps, ...scopePopoverProps, }, emits: popoverContentTypeProps.emits, @@ -54,12 +31,13 @@ const popoverContentModal = defineComponent({ }) useScrollLock(contentRef, true) + return () => h(OkuSlot, {}, { default: () => h(OkuPopoverContentImpl, { ...mergeProps(attrs, props), ref: composedRefs, trapFocus: inject.open.value, - disableOutsidePointerEvents: inject.open.value, + disableOutsidePointerEvents: true, onCloseAutoFocus: composeEventHandlers((el) => { emit('closeAutoFocus', el) }, (event) => { diff --git a/packages/components/popover/src/popoverContentNonModal.ts b/packages/components/popover/src/popoverContentNonModal.ts index 5d9d82f3a..3064a201e 100644 --- a/packages/components/popover/src/popoverContentNonModal.ts +++ b/packages/components/popover/src/popoverContentNonModal.ts @@ -1,23 +1,15 @@ import { defineComponent, h, mergeProps, ref } from 'vue' -import { primitiveProps } from '@oku-ui/primitive' import { useForwardRef } from '@oku-ui/use-composable' -import { OkuSlot } from '@oku-ui/slot' import { composeEventHandlers } from '@oku-ui/utils' import { OkuPopoverContentImpl } from './popoverContentImpl' -import { scopePopoverProps } from './utils' -import { usePopoverInject } from './popover' -import { CONTENT_NAME } from './popoverContent' -import type { PopoverContentTypeEmits, PopoverContentTypeNaviteElement } from './popoverContentModal' -import { popoverContentTypeProps } from './popoverContentModal' - -const NAME = 'OkuPopoverContentNonModal' +import { CONTENT_NAME, CONTENT_NON_MODAL_NAME, popoverContentTypeProps, scopePopoverProps, usePopoverInject } from './props' +import type { PopoverContentTypeEmits, PopoverContentTypeNaviteElement } from './props' const popoverContentNonModal = defineComponent({ - name: NAME, + name: CONTENT_NON_MODAL_NAME, inheritAttrs: false, props: { ...popoverContentTypeProps.props, - ...primitiveProps, ...scopePopoverProps, }, emits: popoverContentTypeProps.emits, @@ -29,52 +21,49 @@ const popoverContentNonModal = defineComponent({ const forwardedRef = useForwardRef() - return () => h(OkuSlot, {}, { - default: () => h(OkuPopoverContentImpl, { - ...mergeProps(attrs, props), - ref: forwardedRef, - trapFocus: false, - disableOutsidePointerEvents: false, - onCloseAutoFocus: composeEventHandlers((event) => { - emit('closeAutoFocus', event) + return () => h(OkuPopoverContentImpl, { + ...mergeProps(attrs, props), + ref: forwardedRef, + trapFocus: false, + disableOutsidePointerEvents: false, + onCloseAutoFocus: composeEventHandlers((event) => { + emit('closeAutoFocus', event) - if (!event.defaultPrevented) { - if (!hasInteractedOutsideRef.value) - inject.triggerRef.value?.focus() + if (!event.defaultPrevented) { + if (!hasInteractedOutsideRef.value) + inject.triggerRef.value?.focus() // Always prevent auto focus because we either focus manually or want user agent focus - event.preventDefault() - } + event.preventDefault() + } + + hasInteractedOutsideRef.value = false + hasPointerDownOutsideRef.value = false + }), + onInteractOutside: composeEventHandlers((event) => { + emit('interactOutside', event) - hasInteractedOutsideRef.value = false - hasPointerDownOutsideRef.value = false - }), - onFocusoutSide: composeEventHandlers((event) => { - emit('focusoutSide', event) - if (!event.defaultPrevented) { - hasInteractedOutsideRef.value = true - if (event.detail.originalEvent.type === 'pointerdown') - hasPointerDownOutsideRef.value = true - } + if (!event.defaultPrevented) { + hasInteractedOutsideRef.value = true + if (event.detail.originalEvent.type === 'pointerdown') + hasPointerDownOutsideRef.value = true + } - // Prevent dismissing when clicking the trigger. - // As the trigger is already setup to close, without doing so would - // cause it to close and immediately open. - const target = event.target as HTMLElement - const targetIsTrigger = inject.triggerRef.value?.contains(target) - if (targetIsTrigger) - event.preventDefault() + // Prevent dismissing when clicking the trigger. + // As the trigger is already setup to close, without doing so would + // cause it to close and immediately open. + const target = event.target as HTMLElement + const targetIsTrigger = inject.triggerRef.value?.contains(target) + if (targetIsTrigger) + event.preventDefault() - // On Safari if the trigger is inside a container with tabIndex={0}, when clicked - // we will get the pointer down outside event on the trigger, but then a subsequent - // focus outside event on the container, we ignore any focus outside event when we've - // already had a pointer down outside event. - if (event.detail.originalEvent.type === 'focusin' && hasPointerDownOutsideRef.value) - event.preventDefault() - }), - }, { - default: () => slots.default?.(), + // On Safari if the trigger is inside a container with tabIndex={0}, when clicked + // we will get the pointer down outside event on the trigger, but then a subsequent + // focus outside event on the container, we ignore any focus outside event when we've + // already had a pointer down outside event. + if (event.detail.originalEvent.type === 'focusin' && hasPointerDownOutsideRef.value) + event.preventDefault() }), - }) + }, slots) }, }) diff --git a/packages/components/popover/src/popoverPortal.ts b/packages/components/popover/src/popoverPortal.ts index 0191b9fdf..bd8a18636 100644 --- a/packages/components/popover/src/popoverPortal.ts +++ b/packages/components/popover/src/popoverPortal.ts @@ -1,63 +1,26 @@ -import type { PropType, Ref } from 'vue' -import { computed, defineComponent, h, toRefs } from 'vue' -import { primitiveProps } from '@oku-ui/primitive' -import { OkuPortal, type PortalProps } from '@oku-ui/portal' +import { defineComponent, h, toRefs } from 'vue' +import { OkuPortal } from '@oku-ui/portal' import { OkuPresence } from '@oku-ui/presence' -import { createPopoverProvide, usePopoverInject } from './popover' -import { scopePopoverProps } from './utils' - -const PORTAL_NAME = 'OkuPopoverPortal' - -export type PortalInjectValue = { - forceMount?: Ref -} - -export const [portalProvider, usePortalInject] = createPopoverProvide(PORTAL_NAME, { - forceMount: undefined, -}) - -export interface PopoverPortalProps { - /** - * Specify a container element to portal the content into. - */ - container?: PortalProps['container'] - /** - * Used to force mounting when more control is needed. Useful when - * controlling animation with React animation libraries. - */ - forceMount?: true -} - -export const popoverPortalProps = { - props: { - container: { - type: Object as () => PortalProps['container'], - }, - forceMount: { - type: Boolean as PropType, - default: undefined, - }, - }, - emits: {}, -} +import { PORTAL_NAME, popoverPortalProps, portalProvider, scopePopoverProps, usePopoverInject } from './props' const popoverPortal = defineComponent({ name: PORTAL_NAME, inheritAttrs: false, props: { ...popoverPortalProps.props, - ...primitiveProps, ...scopePopoverProps, }, setup(props, { slots }) { const { container, forceMount, scopeOkuPopover } = toRefs(props) const inject = usePopoverInject(PORTAL_NAME, scopeOkuPopover?.value) + portalProvider({ scope: scopeOkuPopover?.value, forceMount, }) + return () => h(OkuPresence, { - present: computed(() => forceMount?.value || inject.open.value).value, + present: forceMount?.value || inject.open.value, }, { default: () => h(OkuPortal, { asChild: true, diff --git a/packages/components/popover/src/popoverTrigger.ts b/packages/components/popover/src/popoverTrigger.ts index 917529419..00b58fbfe 100644 --- a/packages/components/popover/src/popoverTrigger.ts +++ b/packages/components/popover/src/popoverTrigger.ts @@ -1,33 +1,11 @@ import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue' -import type { OkuElement, PrimitiveProps } from '@oku-ui/primitive' -import { Primitive, primitiveProps } from '@oku-ui/primitive' +import { Primitive } from '@oku-ui/primitive' import { reactiveOmit, useComposedRefs, useForwardRef } from '@oku-ui/use-composable' import { composeEventHandlers } from '@oku-ui/utils' import { OkuPopperAnchor } from '@oku-ui/popper' -import { getState, scopePopoverProps } from './utils' -import { usePopoverInject, usePopperScope } from './popover' - -export type PopoverTriggerNaviteElement = OkuElement<'button'> -export type PopoverTriggerElement = HTMLButtonElement - -export interface PopoverTriggerProps extends PrimitiveProps {} - -export interface PopoverTriggerEmits { - click: [event: MouseEvent] -} - -export const popoverTriggerProps = { - props: { - ...primitiveProps, - ...scopePopoverProps, - }, - emits: { - // eslint-disable-next-line unused-imports/no-unused-vars - click: (event: MouseEvent) => true, - }, -} - -const TRIGGER_NAME = 'OkuPopoverTrigger' +import type { PopoverTriggerNaviteElement } from './props' +import { TRIGGER_NAME, popoverTriggerProps, usePopoverInject, usePopperScope } from './props' +import { getState } from './utils' const popoverTrigger = defineComponent({ name: TRIGGER_NAME, @@ -63,7 +41,7 @@ const popoverTrigger = defineComponent({ 'ref': composedTriggerRef, 'onClick': composeEventHandlers((el) => { emit('click', el) - }, () => inject.onOpenToggle()), + }, inject.onOpenToggle), }, { default: () => slots.default?.(), }) diff --git a/packages/components/popover/src/props.ts b/packages/components/popover/src/props.ts new file mode 100644 index 000000000..793772196 --- /dev/null +++ b/packages/components/popover/src/props.ts @@ -0,0 +1,308 @@ +import { dismissableLayerProps } from '@oku-ui/dismissable-layer' +import type { DismissableLayerEmits, DismissableLayerProps } from '@oku-ui/dismissable-layer' +import type { FocusScopeEmits, FocusScopeProps } from '@oku-ui/focus-scope' +import type { PopperAnchorElement, PopperAnchorNaviteElement, PopperAnchorProps, PopperArrowElement, PopperArrowNaviteElement, PopperArrowProps, PopperContentEmits, PopperContentProps } from '@oku-ui/popper' +import { createPopperScope, popperAnchorProps, popperContentProps } from '@oku-ui/popper' +import type { PortalProps } from '@oku-ui/portal' +import type { OkuElement, PrimitiveProps } from '@oku-ui/primitive' +import { primitiveProps, propsOmit } from '@oku-ui/primitive' +import type { Scope } from '@oku-ui/provide' +import { ScopePropObject, createProvideScope } from '@oku-ui/provide' +import { type PropType, type Ref, ref } from 'vue' + +export type ScopePopover = T & { scopeOkuPopover?: Scope } + +export const scopePopoverProps = { + scopeOkuPopover: { + ...ScopePropObject, + }, +} + +export const ANCHOR_NAME = 'OkuPopoverAnchor' +export const POPOVER_NAME = 'OkuPopover' +export const ARROW_NAME = 'PopoverArrow' +export const CLOSE_NAME = 'OkuPopoverClose' +export const CONTENT_NAME = 'OkuPopoverContent' +export const CONTENT_IMPL_NAME = 'OkuPopoverContentImpl' +export const CONTENT_MODAL_NAME = 'OkuPopoverContentModal' +export const CONTENT_NON_MODAL_NAME = 'OkuPopoverContentNonModal' +export const TRIGGER_NAME = 'OkuPopoverTrigger' +export const PORTAL_NAME = 'OkuPopoverPortal' + +/* -------------------------------------------------------------------------- */ +/* OkuPopover - popover.ts */ +/* -------------------------------------------------------------------------- */ + +export const [createPopoverProvide, createPopoverScope] = createProvideScope(POPOVER_NAME, [ + createPopperScope, +]) + +export const usePopperScope = createPopperScope() + +export type PopoverProvideValue = { + triggerRef: Ref + contentId: Ref + open: Ref + onOpenChange(open: boolean): void + onOpenToggle(): void + hasCustomAnchor: Ref + onCustomAnchorAdd(): void + onCustomAnchorRemove(): void + modal: Ref +} + +export const [popoverProvide, usePopoverInject] + = createPopoverProvide(POPOVER_NAME) + +export interface PopoverProps { + open?: boolean + defaultOpen?: boolean + modal?: boolean +} + +export interface PopoverEmits { + 'openChange': [open: boolean] +} + +export const popoverProps = { + props: { + modelValue: { + type: [Boolean, undefined] as PropType, + default: undefined, + }, + open: { + type: Boolean, + default: undefined, + }, + defaultOpen: { + type: Boolean, + default: undefined, + }, + modal: { + type: Boolean, + default: false, + }, + }, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + 'openChange': (open: boolean) => true, + // eslint-disable-next-line unused-imports/no-unused-vars + 'update:modelValue': (open: boolean) => true, + }, +} + +/* -------------------------------------------------------------------------- */ +/* OkuPopoverAnchor - popoverAnchor.ts */ +/* -------------------------------------------------------------------------- */ + +export type PopoverAnchorNaviteElement = PopperAnchorNaviteElement +export type PopoverAnchorElement = PopperAnchorElement + +export interface PopoverAnchorProps extends PopperAnchorProps { } + +export const popoverAnchorProps = { + props: { + ...popperAnchorProps.props, + }, + emits: { + ...popperAnchorProps.emits, + }, +} + +/* -------------------------------------------------------------------------- */ +/* OkuPopoverArrow - popoverArrow.ts */ +/* -------------------------------------------------------------------------- */ + +export type PopoverArrowNaviteElement = PopperArrowNaviteElement +export type PopoverArrowElement = PopperArrowElement + +export interface PopoverArrowProps extends PopperArrowProps { } + +export const popoverArrowProps = { + props: { + ...popperAnchorProps.props, + }, + emits: { + ...popperAnchorProps.emits, + }, +} + +/* -------------------------------------------------------------------------- */ +/* OkuPopoverClose - popoverClose.ts */ +/* -------------------------------------------------------------------------- */ + +export type PopoverCloseNaviteElement = OkuElement<'button'> +export type PopoverCloseElement = HTMLButtonElement + +export interface PopoverCloseProps extends PrimitiveProps { } + +export type PopoverCloseEmits = { + click: [event: MouseEvent] +} + +export const popoverCloseProps = { + props: { + ...primitiveProps, + }, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + click: (event: MouseEvent) => true, + }, +} + +/* -------------------------------------------------------------------------- */ +/* OkuPopoverContentImpl - popoverContentImpl.ts */ +/* -------------------------------------------------------------------------- */ + +export type PopoverContentImplNaviteElement = OkuElement<'label'> +export type PopoverContentImplElement = HTMLLabelElement + +export interface PopoverContentImplProps + extends PopperContentProps, + DismissableLayerProps { + /** + * Whether focus should be trapped within the `Popover` + * (default: false) + */ + trapFocus?: FocusScopeProps['trapped'] +} + +export type PopoverContentImplEmits = { + /** + * Event handler called when auto-focusing on open. + * Can be prevented. + */ + openAutoFocus: [event: FocusScopeEmits['mountAutoFocus'][0]] + /** + * Event handler called when auto-focusing on close. + * Can be prevented. + */ + closeAutoFocus: [event: FocusScopeEmits['unmountAutoFocus'][0]] +} & Omit +& Omit + +export const popoverContentImplProps = { + props: { + ...popperContentProps.props, + ...dismissableLayerProps.props, + trapFocus: { + type: Boolean as PropType, + default: false, + }, + }, + emits: { + ...propsOmit(popperContentProps.emits, ['placed']), + ...propsOmit(dismissableLayerProps.emits, ['dismiss']), + // eslint-disable-next-line unused-imports/no-unused-vars + openAutoFocus: (event: FocusScopeEmits['mountAutoFocus'][0]) => true, + // eslint-disable-next-line unused-imports/no-unused-vars + closeAutoFocus: (event: FocusScopeEmits['unmountAutoFocus'][0]) => true, + }, +} + +/* -------------------------------------------------------------------------- */ +/* OkuPopoverContentModal - popoverContentModal.ts */ +/* -------------------------------------------------------------------------- */ + +export type PopoverContentTypeNaviteElement = PopoverContentImplNaviteElement +export type PopoverContentTypeElement = PopoverContentImplElement + +export interface PopoverContentTypeProps + extends Omit { } + +export interface PopoverContentTypeEmits extends PopoverContentImplEmits { } + +export const popoverContentTypeProps = { + props: { + ...propsOmit(popoverContentImplProps.props, ['trapFocus', 'disableOutsidePointerEvents']), + }, + emits: { + ...popoverContentImplProps.emits, + }, +} + +/* -------------------------------------------------------------------------- */ +/* OkuPopoverContent - popoverContent.ts */ +/* -------------------------------------------------------------------------- */ + +export interface PopoverContentProps extends PopoverContentTypeProps { + /** + * Used to force mounting when more control is needed. Useful when + * controlling animation with React animation libraries. + */ + forceMount?: true +} + +export const popoverContentProps = { + props: { + forceMount: { + type: Boolean as PropType, + default: undefined, + }, + ...popoverContentTypeProps.props, + }, + emits: { + ...popoverContentTypeProps.emits, + }, +} + +/* -------------------------------------------------------------------------- */ +/* OkuPopoverContentNonModal - popoverContentNonModal.ts */ +/* -------------------------------------------------------------------------- */ + +export type PopoverTriggerNaviteElement = OkuElement<'button'> +export type PopoverTriggerElement = HTMLButtonElement + +export interface PopoverTriggerProps extends PrimitiveProps {} + +export interface PopoverTriggerEmits { + click: [event: MouseEvent] +} + +export const popoverTriggerProps = { + props: { + ...primitiveProps, + ...scopePopoverProps, + }, + emits: { + // eslint-disable-next-line unused-imports/no-unused-vars + click: (event: MouseEvent) => true, + }, +} + +/* -------------------------------------------------------------------------- */ +/* OkuPopoverPortal - popoverPortal.ts */ +/* -------------------------------------------------------------------------- */ + +export type PortalInjectValue = { + forceMount?: Ref +} + +export const [portalProvider, usePortalInject] = createPopoverProvide(PORTAL_NAME, { + forceMount: ref(undefined), +}) + +export interface PopoverPortalProps { + /** + * Specify a container element to portal the content into. + */ + container?: PortalProps['container'] + /** + * Used to force mounting when more control is needed. Useful when + * controlling animation with React animation libraries. + */ + forceMount?: true +} + +export const popoverPortalProps = { + props: { + container: { + type: Object as () => PortalProps['container'], + }, + forceMount: { + type: Boolean as PropType, + default: undefined, + }, + }, + emits: {}, +} diff --git a/packages/components/popover/src/stories/Animated.vue b/packages/components/popover/src/stories/Animated.vue new file mode 100644 index 000000000..1c95843f1 --- /dev/null +++ b/packages/components/popover/src/stories/Animated.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/components/popover/src/stories/Boundary.vue b/packages/components/popover/src/stories/Boundary.vue new file mode 100644 index 000000000..b24091b29 --- /dev/null +++ b/packages/components/popover/src/stories/Boundary.vue @@ -0,0 +1,46 @@ + + + diff --git a/packages/components/popover/src/stories/Chromatic.vue b/packages/components/popover/src/stories/Chromatic.vue new file mode 100644 index 000000000..6d78df3ee --- /dev/null +++ b/packages/components/popover/src/stories/Chromatic.vue @@ -0,0 +1,813 @@ + + + + + diff --git a/packages/components/popover/src/stories/Controlled.vue b/packages/components/popover/src/stories/Controlled.vue new file mode 100644 index 000000000..7003ffbac --- /dev/null +++ b/packages/components/popover/src/stories/Controlled.vue @@ -0,0 +1,42 @@ + + + diff --git a/packages/components/popover/src/stories/CustomAnchor.vue b/packages/components/popover/src/stories/CustomAnchor.vue new file mode 100644 index 000000000..614ee7f5a --- /dev/null +++ b/packages/components/popover/src/stories/CustomAnchor.vue @@ -0,0 +1,43 @@ + + + diff --git a/packages/components/popover/src/stories/ForcedMount.vue b/packages/components/popover/src/stories/ForcedMount.vue new file mode 100644 index 000000000..84a67066a --- /dev/null +++ b/packages/components/popover/src/stories/ForcedMount.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/components/popover/src/stories/Modality.vue b/packages/components/popover/src/stories/Modality.vue new file mode 100644 index 000000000..1084db896 --- /dev/null +++ b/packages/components/popover/src/stories/Modality.vue @@ -0,0 +1,82 @@ + + +