Skip to content

Commit

Permalink
Drop actions in favor of onBrowserMount (#15)
Browse files Browse the repository at this point in the history
* fix: parent submenu closes because of rerender

* use menu in docs sidebar

* implement onBrowserMount on collapsible to get rid of the action

* rework tabs and implement missing ids

* cleanup tabs

* update readme

* udate accordion

* use data attrs to target the root element

* remove switch action in favor of onMount

* remove action on button

* remove link action

* remove actions in popover

* remove radiogroup action

* remove tooltip action

* remove tagGroup action

* remove toggle button action

* fix: remove action in switch example

* remove action from listbox and add multiple example

* avoid scrolling when setiing the 1st active desc

* update listbox

* remove action from calendar component

* remove actions from select

* remove actions from combobox

* remove action from label

* minor changes

* remove action from toolbar

* tooltip component now runs floating ui by itself

* chore: format code

* feat: focus trap

* update docs

* feat: update popover (trap focus and handles positioning)

* feat: update menu component to handle properly submenus

* chore: minor change

* feat: remove checkbox action

* chore: remove dangling actions in button tests

* feat: remove action from carousel component

* chore: remove already inferred types

* fix: toggle btn tabindex

* fix: tests

* add space between button and popover content
  • Loading branch information
ubermanu committed Jul 6, 2023
1 parent 28ecda5 commit d268292
Show file tree
Hide file tree
Showing 98 changed files with 1,522 additions and 1,230 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ npm install louisette
<script>
import { createCollapsible } from 'louisette'
const { trigger, triggerAttrs, contentAttrs } = createCollapsible()
const { triggerAttrs, contentAttrs } = createCollapsible()
</script>
<div>
<button use:trigger {...$triggerAttrs}>Toggle</button>
<button {...$triggerAttrs}>Toggle</button>
<div {...$contentAttrs}>
<p>Content</p>
</div>
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
},
"prettier": "@ubermanu/prettier-config",
"devDependencies": {
"@floating-ui/dom": "^1.2.9",
"@fontsource-variable/caveat": "^5.0.1",
"@sveltejs/adapter-auto": "^2.0.1",
"@sveltejs/adapter-static": "^2.0.2",
Expand Down Expand Up @@ -81,5 +80,9 @@
},
"peerDependencies": {
"svelte": "^3.54.0"
},
"dependencies": {
"@floating-ui/dom": "^1.4.3",
"focusable-selectors": "^0.8.0"
}
}
33 changes: 23 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions src/lib/actions/FocusTrap/focusTrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { getFocusableElements } from '$lib/helpers/dom.js'
import type { Action } from 'svelte/action'

export const focusTrap: Action = (node: HTMLElement) => {
const onKeyDown = (event: KeyboardEvent) => {
const focusableElements = getFocusableElements(node)

const firstFocusableElement = focusableElements[0]
const lastFocusableElement = focusableElements[focusableElements.length - 1]

if (event.key === 'Tab') {
if (event.shiftKey) {
if (document.activeElement === firstFocusableElement) {
event.preventDefault()
lastFocusableElement.focus()
}
} else {
if (document.activeElement === lastFocusableElement) {
event.preventDefault()
firstFocusableElement.focus()
}
}
}
}

node.addEventListener('keydown', onKeyDown, true)

return {
destroy() {
node.removeEventListener('keydown', onKeyDown, true)
},
}
}
58 changes: 30 additions & 28 deletions src/lib/components/Accordion/accordion.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { onBrowserMount } from '$lib/helpers/environment.js'
import type { DelegateEvent } from '$lib/helpers/events.js'
import { delegateEventListeners } from '$lib/helpers/events.js'
import { traveller } from '$lib/helpers/traveller.js'
import { generateId } from '$lib/helpers/uuid.js'
import type { Action } from 'svelte/action'
import { derived, get, readonly, writable } from 'svelte/store'
import { derived, get, readable, readonly, writable } from 'svelte/store'
import type { Accordion, AccordionConfig } from './accordion.types.js'

export const createAccordion = (config?: AccordionConfig): Accordion => {
Expand All @@ -14,16 +14,20 @@ export const createAccordion = (config?: AccordionConfig): Accordion => {
const disabled$ = writable(disabled || [])

const baseId = generateId()
const getTriggerId = (key: string) => `${baseId}-trigger-${key}`
const getContentId = (key: string) => `${baseId}-content-${key}`
const triggerId = (key: string) => `${baseId}-trigger-${key}`
const contentId = (key: string) => `${baseId}-content-${key}`

const rootAttrs = readable({
'data-accordion': baseId,
})

const triggerAttrs = derived(
[expanded$, disabled$],
([expanded, disabled]) => {
return (key: string) => ({
id: getTriggerId(key),
id: triggerId(key),
role: 'button',
'aria-controls': getContentId(key),
'aria-controls': contentId(key),
'aria-expanded': expanded.includes(key),
'aria-disabled': disabled.includes(key),
tabIndex: disabled.includes(key) ? -1 : 0,
Expand All @@ -35,9 +39,9 @@ export const createAccordion = (config?: AccordionConfig): Accordion => {

const contentAttrs = derived([expanded$], ([expanded]) => {
return (key: string) => ({
id: getContentId(key),
id: contentId(key),
role: 'region',
'aria-labelledby': getTriggerId(key),
'aria-labelledby': triggerId(key),
'aria-hidden': !expanded.includes(key),
inert: !expanded.includes(key) ? '' : undefined,
'data-accordion-content': key,
Expand Down Expand Up @@ -113,7 +117,7 @@ export const createAccordion = (config?: AccordionConfig): Accordion => {
/** Toggles the accordion when the trigger is clicked. */
const onTriggerClick = (event: DelegateEvent<MouseEvent>) => {
event.preventDefault()
toggle(event.delegateTarget.dataset.accordionTrigger as string)
toggle(event.delegateTarget.dataset.accordionTrigger!)
}

/**
Expand All @@ -128,7 +132,7 @@ export const createAccordion = (config?: AccordionConfig): Accordion => {
*/
const onTriggerKeyDown = (event: DelegateEvent<KeyboardEvent>) => {
const target = event.delegateTarget
const key = target.dataset.accordionTrigger as string
const key = target.dataset.accordionTrigger!

if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
Expand All @@ -140,15 +144,10 @@ export const createAccordion = (config?: AccordionConfig): Accordion => {
collapse(key)
}

if (!rootNode) {
console.warn('The accordion root node is not defined.')
return
}

const $disabled = get(disabled$)

const nodes = traveller(rootNode, '[data-accordion-trigger]', (el) => {
return $disabled.includes(el.dataset.accordionTrigger as string)
const nodes = traveller(rootNode!, '[data-accordion-trigger]', (el) => {
return $disabled.includes(el.dataset.accordionTrigger!)
})

if (event.key === 'ArrowUp') {
Expand All @@ -172,11 +171,16 @@ export const createAccordion = (config?: AccordionConfig): Accordion => {
}
}

/** Bind event listeners to the accordion */
const useAccordion: Action = (node) => {
rootNode = node
onBrowserMount(() => {
rootNode = document.querySelector<HTMLElement>(
`[data-accordion="${baseId}"]`
)

const removeListeners = delegateEventListeners(node, {
if (!rootNode) {
throw new Error('No root node found for the accordion')
}

const removeListeners = delegateEventListeners(rootNode, {
keydown: {
'[data-accordion-trigger]': onTriggerKeyDown,
},
Expand All @@ -192,21 +196,19 @@ export const createAccordion = (config?: AccordionConfig): Accordion => {
}
})

return {
destroy() {
removeListeners()
unsubscribe()
},
return () => {
removeListeners()
unsubscribe()
}
}
})

return {
multiple: multiple$,
expanded: readonly(expanded$),
disabled: readonly(disabled$),
rootAttrs,
triggerAttrs,
contentAttrs,
accordion: useAccordion,
expand,
collapse,
toggle,
Expand Down
3 changes: 1 addition & 2 deletions src/lib/components/Accordion/accordion.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { HTMLAttributes } from '$lib/helpers/types.js'
import type { Action } from 'svelte/action'
import type { Readable } from 'svelte/store'

export type AccordionConfig = {
Expand All @@ -12,9 +11,9 @@ export type Accordion = {
multiple: Readable<boolean>
expanded: Readable<string[]>
disabled: Readable<string[]>
rootAttrs: Readable<HTMLAttributes>
triggerAttrs: Readable<(key: string) => HTMLAttributes>
contentAttrs: Readable<(key: string) => HTMLAttributes>
accordion: Action
expand: (key: string) => void
collapse: (key: string) => void
toggle: (key: string) => void
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/Accordion/example/Accordion.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import { createAccordion } from '$lib'
import { setContext } from 'svelte'
const { accordion, ...accordionContext } = createAccordion()
const { rootAttrs, ...accordionContext } = createAccordion()
setContext('accordion', accordionContext)
</script>

<div use:accordion>
<div {...$rootAttrs}>
<slot />
</div>
4 changes: 2 additions & 2 deletions src/lib/components/Accordion/tests/Accordion.test.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
export let items: { id: number; label: string; content: string }[] = []
export let defaults = {}
const { accordion, triggerAttrs, contentAttrs } = createAccordion(defaults)
const { rootAttrs, triggerAttrs, contentAttrs } = createAccordion(defaults)
</script>

<div data-testid="accordion" use:accordion>
<div data-testid="accordion" {...$rootAttrs}>
{#each items as item}
<div data-testid="accordion-item-{item.id}">
<div
Expand Down
27 changes: 18 additions & 9 deletions src/lib/components/Button/button.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Action } from 'svelte/action'
import { onBrowserMount } from '$lib/helpers/environment.js'
import { generateId } from '$lib/helpers/uuid.js'
import { derived, get, writable } from 'svelte/store'
import type { Button, ButtonConfig } from './button.types.js'

Expand All @@ -7,10 +8,13 @@ export const createButton = (config?: ButtonConfig): Button => {

const disabled$ = writable(disabled || false)

const baseId = generateId()

const buttonAttrs = derived([disabled$], ([disabled]) => ({
role: 'button',
'aria-disabled': disabled,
tabIndex: disabled ? -1 : 0,
'data-button': baseId,
}))

const onButtonClick = (event: MouseEvent) => {
Expand All @@ -27,21 +31,26 @@ export const createButton = (config?: ButtonConfig): Button => {
}
}

const useButton: Action = (node) => {
onBrowserMount(() => {
const node = document.querySelector<HTMLElement>(
`[data-button="${baseId}"]`
)

if (!node) {
throw new Error('Could not find the button')
}

node.addEventListener('click', onButtonClick)
node.addEventListener('keydown', onButtonKeyDown)

return {
destroy() {
node.removeEventListener('click', onButtonClick)
node.removeEventListener('keydown', onButtonKeyDown)
},
return () => {
node.removeEventListener('click', onButtonClick)
node.removeEventListener('keydown', onButtonKeyDown)
}
}
})

return {
disabled: disabled$,
buttonAttrs,
button: useButton,
}
}
6 changes: 2 additions & 4 deletions src/lib/components/Button/button.types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import type { HTMLAttributes } from '$lib/helpers/types.js'
import type { Action } from 'svelte/action'
import type { Readable } from 'svelte/store'
import type { Readable, Writable } from 'svelte/store'

export type ButtonConfig = {
disabled?: boolean
}

export type Button = {
disabled: Readable<boolean>
disabled: Writable<boolean>
buttonAttrs: Readable<HTMLAttributes>
button: Action
}
3 changes: 1 addition & 2 deletions src/lib/components/Button/example/Button.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<script lang="ts">
import { createButton } from '$lib'
const { button, buttonAttrs } = createButton()
const { buttonAttrs } = createButton()
</script>

<div
use:button
{...$buttonAttrs}
class="inline-flex w-auto select-none items-center rounded-md border border-transparent bg-accent-500 px-4 py-2 text-base font-medium text-white shadow-sm transition duration-150 ease-in-out hover:bg-accent-400 focus:outline-none focus-visible:ring focus-visible:ring-accent-500 focus-visible:ring-opacity-50 active:bg-accent-700 active:shadow-inner"
on:click
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/Button/tests/Button.test.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
export let defaults = {}
const { button, buttonAttrs } = createButton(defaults)
const { buttonAttrs } = createButton(defaults)
</script>

<div data-testid="button" use:button {...$buttonAttrs}>Button</div>
<div data-testid="button" {...$buttonAttrs}>Button</div>
Loading

0 comments on commit d268292

Please sign in to comment.