@santi020k/lumen-astro 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/components/ContextNavigation.astro +37 -0
- package/components/DropdownMenuContent.astro +28 -0
- package/components/DropdownMenuItem.astro +39 -0
- package/components/DropdownMenuSeparator.astro +25 -0
- package/components/DropdownMenuTrigger.astro +39 -0
- package/components/Empty.astro +3 -0
- package/components/KanbanBoard.astro +32 -0
- package/components/KanbanColumn.astro +30 -0
- package/components/LanguageToggle.astro +35 -2
- package/components/PopoverPanel.astro +27 -0
- package/components/PopoverTrigger.astro +34 -0
- package/components/TabsList.astro +34 -0
- package/components/TabsPanel.astro +35 -0
- package/components/TabsTrigger.astro +36 -0
- package/components/TooltipContent.astro +27 -0
- package/index.ts +13 -0
- package/package.json +13 -4
- package/runtime/UIPrimitives.astro +375 -4
package/README.md
CHANGED
|
@@ -60,6 +60,88 @@ incremental migrations, but new code should keep native behavior and visual styl
|
|
|
60
60
|
<NativeSelect size={8} visualSize="lg" />
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
## Compound interactive components
|
|
64
|
+
|
|
65
|
+
Popover, dropdown menu, tabs, and tooltip parts expose the DOM and ARIA contracts consumed by the
|
|
66
|
+
shared progressive-enhancement runtime. Prefer these parts over recreating roles and `data-ui-*`
|
|
67
|
+
attributes by hand.
|
|
68
|
+
|
|
69
|
+
```astro
|
|
70
|
+
---
|
|
71
|
+
import {
|
|
72
|
+
Button,
|
|
73
|
+
DropdownMenu,
|
|
74
|
+
DropdownMenuContent,
|
|
75
|
+
DropdownMenuItem,
|
|
76
|
+
DropdownMenuSeparator,
|
|
77
|
+
DropdownMenuTrigger,
|
|
78
|
+
Popover,
|
|
79
|
+
PopoverPanel,
|
|
80
|
+
PopoverTrigger,
|
|
81
|
+
Tabs,
|
|
82
|
+
TabsList,
|
|
83
|
+
TabsPanel,
|
|
84
|
+
TabsTrigger,
|
|
85
|
+
Tooltip,
|
|
86
|
+
TooltipContent
|
|
87
|
+
} from '@santi020k/lumen-astro'
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
<DropdownMenu>
|
|
91
|
+
<DropdownMenuTrigger>Download</DropdownMenuTrigger>
|
|
92
|
+
<DropdownMenuContent>
|
|
93
|
+
<DropdownMenuItem>macOS</DropdownMenuItem>
|
|
94
|
+
<DropdownMenuItem>Windows</DropdownMenuItem>
|
|
95
|
+
<DropdownMenuSeparator />
|
|
96
|
+
<DropdownMenuItem disabled status="Soon">Android</DropdownMenuItem>
|
|
97
|
+
</DropdownMenuContent>
|
|
98
|
+
</DropdownMenu>
|
|
99
|
+
|
|
100
|
+
<Popover>
|
|
101
|
+
<PopoverTrigger>Filters</PopoverTrigger>
|
|
102
|
+
<PopoverPanel>Filter controls</PopoverPanel>
|
|
103
|
+
</Popover>
|
|
104
|
+
|
|
105
|
+
<Tabs initialValue="preview">
|
|
106
|
+
<TabsList aria-label="Workspace view">
|
|
107
|
+
<TabsTrigger value="preview">Preview</TabsTrigger>
|
|
108
|
+
<TabsTrigger value="code">Code</TabsTrigger>
|
|
109
|
+
</TabsList>
|
|
110
|
+
<TabsPanel value="preview">Rendered result</TabsPanel>
|
|
111
|
+
<TabsPanel value="code">Source code</TabsPanel>
|
|
112
|
+
</Tabs>
|
|
113
|
+
|
|
114
|
+
<Tooltip>
|
|
115
|
+
<Button aria-label="Save changes">Save</Button>
|
|
116
|
+
<TooltipContent>Save changes</TooltipContent>
|
|
117
|
+
</Tooltip>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Context navigation
|
|
121
|
+
|
|
122
|
+
Use `ContextNavigation` below a primary header when the available links change with a selected
|
|
123
|
+
platform, product, or workspace. Put the stable selector or identity in the `context` slot and one
|
|
124
|
+
independently named `NavigationMenu` in the default slot. Long link sets scroll horizontally.
|
|
125
|
+
|
|
126
|
+
```astro
|
|
127
|
+
---
|
|
128
|
+
import { ContextNavigation, NativeSelect, NavigationMenu } from '@santi020k/lumen-astro'
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
<ContextNavigation>
|
|
132
|
+
<NativeSelect slot="context" aria-label="Documentation platform" visualSize="sm">
|
|
133
|
+
<option>Web</option>
|
|
134
|
+
<option>Apple</option>
|
|
135
|
+
<option>Android</option>
|
|
136
|
+
</NativeSelect>
|
|
137
|
+
<NavigationMenu aria-label="Web documentation" variant="unstyled">
|
|
138
|
+
<a href="/docs/web" aria-current="page">Overview</a>
|
|
139
|
+
<a href="/docs/components">Components</a>
|
|
140
|
+
<a href="/docs/web/playground">Playground</a>
|
|
141
|
+
</NavigationMenu>
|
|
142
|
+
</ContextNavigation>
|
|
143
|
+
```
|
|
144
|
+
|
|
63
145
|
## Forms and Astro Actions
|
|
64
146
|
|
|
65
147
|
`Form` renders a semantic form and preserves native `method`, `action`, `enctype`, autocomplete,
|
|
@@ -227,6 +309,14 @@ with `--ui-logo-delay`.
|
|
|
227
309
|
|
|
228
310
|
The package ships the complete Astro primitive catalog plus a small progressive-enhancement runtime for dialogs, popovers, tabs, menus, command filtering, carousels, and toasts.
|
|
229
311
|
|
|
312
|
+
## Kanban boards
|
|
313
|
+
|
|
314
|
+
Compose `KanbanBoard` and `KanbanColumn` with ordinary `Card` items. Give every item a stable
|
|
315
|
+
`data-ui-kanban-item` and add a dedicated button with `data-ui-kanban-handle`. `UIPrimitives` emits
|
|
316
|
+
the cancellable `ui:kanban-move-request` event for keyboard, mouse, and touch movement without
|
|
317
|
+
changing the DOM. Keep persistence, pending state, rollback, and status rules in the application.
|
|
318
|
+
Use `Empty variant="compact"` inside empty columns and `Skeleton` cards while loading.
|
|
319
|
+
|
|
230
320
|
## Motion
|
|
231
321
|
|
|
232
322
|
Use the shared `fast`, `standard`, and `slow` duration vocabulary across motion primitives.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'div'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
variant?: 'default' | 'unstyled'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
class: classProp = '',
|
|
14
|
+
className = '',
|
|
15
|
+
variant = 'default',
|
|
16
|
+
...rest
|
|
17
|
+
} = Astro.props
|
|
18
|
+
|
|
19
|
+
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
20
|
+
'ui-context-navigation',
|
|
21
|
+
variant === 'unstyled' && 'ui-context-navigation--unstyled'
|
|
22
|
+
], classProp, className)
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<div
|
|
26
|
+
class:list={classList}
|
|
27
|
+
data-slot="context-navigation"
|
|
28
|
+
data-variant={variant}
|
|
29
|
+
{...passthrough}
|
|
30
|
+
>
|
|
31
|
+
{Astro.slots.has('context') && (
|
|
32
|
+
<div class="ui-context-navigation__context" data-slot="context-navigation-context">
|
|
33
|
+
<slot name="context" />
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
<slot />
|
|
37
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'div'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
class: classProp = '',
|
|
13
|
+
className = '',
|
|
14
|
+
role = 'menu',
|
|
15
|
+
...rest
|
|
16
|
+
} = Astro.props
|
|
17
|
+
|
|
18
|
+
const { classList, passthrough } = resolveLumenAstroProps(
|
|
19
|
+
rest,
|
|
20
|
+
['ui-menu__content'],
|
|
21
|
+
classProp,
|
|
22
|
+
className
|
|
23
|
+
)
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<div role={role} class:list={classList} {...passthrough}>
|
|
27
|
+
<slot />
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'button'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
status?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
class: classProp = '',
|
|
14
|
+
className = '',
|
|
15
|
+
disabled = false,
|
|
16
|
+
status,
|
|
17
|
+
type = 'button',
|
|
18
|
+
...rest
|
|
19
|
+
} = Astro.props
|
|
20
|
+
|
|
21
|
+
const { classList, passthrough } = resolveLumenAstroProps(
|
|
22
|
+
rest,
|
|
23
|
+
['ui-menu__item'],
|
|
24
|
+
classProp,
|
|
25
|
+
className
|
|
26
|
+
)
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<button
|
|
30
|
+
type={type}
|
|
31
|
+
role="menuitem"
|
|
32
|
+
class:list={classList}
|
|
33
|
+
disabled={disabled}
|
|
34
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
35
|
+
{...passthrough}
|
|
36
|
+
>
|
|
37
|
+
<span class="ui-menu__item-label"><slot /></span>
|
|
38
|
+
{status && <span class="ui-menu__item-status">{status}</span>}
|
|
39
|
+
</button>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'div'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
class: classProp = '',
|
|
13
|
+
className = '',
|
|
14
|
+
...rest
|
|
15
|
+
} = Astro.props
|
|
16
|
+
|
|
17
|
+
const { classList, passthrough } = resolveLumenAstroProps(
|
|
18
|
+
rest,
|
|
19
|
+
['ui-menu__separator'],
|
|
20
|
+
classProp,
|
|
21
|
+
className
|
|
22
|
+
)
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<div role="separator" class:list={classList} {...passthrough} />
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'button'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
class: classProp = '',
|
|
13
|
+
className = '',
|
|
14
|
+
type = 'button',
|
|
15
|
+
...rest
|
|
16
|
+
} = Astro.props
|
|
17
|
+
|
|
18
|
+
const { classList, passthrough } = resolveLumenAstroProps(
|
|
19
|
+
rest,
|
|
20
|
+
[
|
|
21
|
+
'ui-button',
|
|
22
|
+
'ui-button--outline',
|
|
23
|
+
'ui-button--default-size',
|
|
24
|
+
'ui-menu__trigger'
|
|
25
|
+
],
|
|
26
|
+
classProp,
|
|
27
|
+
className
|
|
28
|
+
)
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<button
|
|
32
|
+
type={type}
|
|
33
|
+
class:list={classList}
|
|
34
|
+
data-ui-trigger
|
|
35
|
+
aria-haspopup="menu"
|
|
36
|
+
{...passthrough}
|
|
37
|
+
>
|
|
38
|
+
<slot />
|
|
39
|
+
</button>
|
package/components/Empty.astro
CHANGED
|
@@ -7,17 +7,20 @@ interface Props extends HTMLAttributes<'section'> {
|
|
|
7
7
|
class?: string | null
|
|
8
8
|
className?: string
|
|
9
9
|
glass?: boolean | 'subtle' | 'strong'
|
|
10
|
+
variant?: 'compact' | 'default'
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const {
|
|
13
14
|
class: classProp = '',
|
|
14
15
|
className = '',
|
|
15
16
|
glass = false,
|
|
17
|
+
variant = 'default',
|
|
16
18
|
...rest
|
|
17
19
|
} = Astro.props
|
|
18
20
|
|
|
19
21
|
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
20
22
|
'ui-empty',
|
|
23
|
+
variant === 'compact' && 'ui-empty--compact',
|
|
21
24
|
glass && 'ui-empty--glass',
|
|
22
25
|
glass === 'subtle' && 'ui-glass-subtle',
|
|
23
26
|
glass === 'strong' && 'ui-glass-strong'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'section'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
class: classProp = '',
|
|
13
|
+
className = '',
|
|
14
|
+
tabindex = '0',
|
|
15
|
+
...rest
|
|
16
|
+
} = Astro.props
|
|
17
|
+
|
|
18
|
+
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
19
|
+
'ui-kanban-board',
|
|
20
|
+
'ui-kanban'
|
|
21
|
+
], classProp, className)
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<section
|
|
25
|
+
aria-orientation="horizontal"
|
|
26
|
+
class:list={classList}
|
|
27
|
+
data-ui-kanban
|
|
28
|
+
tabindex={tabindex}
|
|
29
|
+
{...passthrough}
|
|
30
|
+
>
|
|
31
|
+
<slot />
|
|
32
|
+
</section>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'section'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
value: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
class: classProp = '',
|
|
14
|
+
className = '',
|
|
15
|
+
value,
|
|
16
|
+
...rest
|
|
17
|
+
} = Astro.props
|
|
18
|
+
|
|
19
|
+
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
20
|
+
'ui-kanban__column'
|
|
21
|
+
], classProp, className)
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<section
|
|
25
|
+
class:list={classList}
|
|
26
|
+
data-ui-kanban-column={value}
|
|
27
|
+
{...passthrough}
|
|
28
|
+
>
|
|
29
|
+
<slot />
|
|
30
|
+
</section>
|
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
formatLumenLanguageLabel,
|
|
6
|
+
getLumenLocalePair,
|
|
7
|
+
type LumenLocaleOption,
|
|
8
|
+
normalizeLumenLocales,
|
|
9
|
+
resolveLumenAstroProps
|
|
10
|
+
} from '@santi020k/lumen-core'
|
|
5
11
|
|
|
6
12
|
interface Props extends HTMLAttributes<'button'> {
|
|
7
13
|
class?: string | null
|
|
8
14
|
className?: string
|
|
15
|
+
defaultValue?: string
|
|
16
|
+
labelTemplate?: string
|
|
17
|
+
locales?: readonly LumenLocaleOption[]
|
|
18
|
+
storageKey?: string
|
|
19
|
+
value?: string
|
|
9
20
|
}
|
|
10
21
|
|
|
11
22
|
const {
|
|
12
23
|
class: classProp = '',
|
|
13
24
|
className = '',
|
|
25
|
+
defaultValue,
|
|
26
|
+
labelTemplate = 'Change language from {current} to {next}',
|
|
27
|
+
locales: localeOptions,
|
|
28
|
+
storageKey,
|
|
14
29
|
type = 'button',
|
|
30
|
+
value,
|
|
15
31
|
...rest
|
|
16
32
|
} = Astro.props
|
|
17
33
|
|
|
34
|
+
const locales = normalizeLumenLocales(localeOptions)
|
|
35
|
+
const initialValue = value ?? defaultValue ?? locales[0]?.value
|
|
36
|
+
const { current, next } = getLumenLocalePair(locales, initialValue)
|
|
37
|
+
const accessibleLabel = formatLumenLanguageLabel(labelTemplate, current, next)
|
|
38
|
+
|
|
18
39
|
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
19
40
|
'ui-language-toggle'
|
|
20
41
|
], classProp, className)
|
|
@@ -24,7 +45,19 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
24
45
|
type={type}
|
|
25
46
|
class:list={classList}
|
|
26
47
|
data-ui-language-toggle
|
|
48
|
+
data-ui-language-controlled={value === undefined ? undefined : ''}
|
|
49
|
+
data-ui-language-initial-value={initialValue}
|
|
50
|
+
data-ui-language-label-template={labelTemplate}
|
|
51
|
+
data-ui-language-locales={JSON.stringify(locales)}
|
|
52
|
+
data-ui-language-value={current.value}
|
|
53
|
+
data-ui-language-next-value={next.value}
|
|
54
|
+
data-ui-language-storage-key={storageKey}
|
|
55
|
+
aria-label={accessibleLabel}
|
|
27
56
|
{...passthrough}
|
|
28
57
|
>
|
|
29
|
-
|
|
58
|
+
{
|
|
59
|
+
Astro.slots.has('default') ?
|
|
60
|
+
<slot /> :
|
|
61
|
+
<span data-ui-language-label lang={next.value}>{next.label}</span>
|
|
62
|
+
}
|
|
30
63
|
</button>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'div'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
class: classProp = '',
|
|
13
|
+
className = '',
|
|
14
|
+
...rest
|
|
15
|
+
} = Astro.props
|
|
16
|
+
|
|
17
|
+
const { classList, passthrough } = resolveLumenAstroProps(
|
|
18
|
+
rest,
|
|
19
|
+
['ui-popover__panel'],
|
|
20
|
+
classProp,
|
|
21
|
+
className
|
|
22
|
+
)
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<div class:list={classList} {...passthrough}>
|
|
26
|
+
<slot />
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'button'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
class: classProp = '',
|
|
13
|
+
className = '',
|
|
14
|
+
type = 'button',
|
|
15
|
+
...rest
|
|
16
|
+
} = Astro.props
|
|
17
|
+
|
|
18
|
+
const { classList, passthrough } = resolveLumenAstroProps(
|
|
19
|
+
rest,
|
|
20
|
+
['ui-button', 'ui-button--outline', 'ui-button--default-size'],
|
|
21
|
+
classProp,
|
|
22
|
+
className
|
|
23
|
+
)
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<button
|
|
27
|
+
type={type}
|
|
28
|
+
class:list={classList}
|
|
29
|
+
data-ui-trigger
|
|
30
|
+
aria-haspopup="menu"
|
|
31
|
+
{...passthrough}
|
|
32
|
+
>
|
|
33
|
+
<slot />
|
|
34
|
+
</button>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'div'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
orientation?: 'horizontal' | 'vertical'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
class: classProp = '',
|
|
14
|
+
className = '',
|
|
15
|
+
orientation = 'horizontal',
|
|
16
|
+
...rest
|
|
17
|
+
} = Astro.props
|
|
18
|
+
|
|
19
|
+
const { classList, passthrough } = resolveLumenAstroProps(
|
|
20
|
+
rest,
|
|
21
|
+
[],
|
|
22
|
+
classProp,
|
|
23
|
+
className
|
|
24
|
+
)
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
role="tablist"
|
|
29
|
+
aria-orientation={orientation}
|
|
30
|
+
class:list={classList}
|
|
31
|
+
{...passthrough}
|
|
32
|
+
>
|
|
33
|
+
<slot />
|
|
34
|
+
</div>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'div'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
value: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
class: classProp = '',
|
|
14
|
+
className = '',
|
|
15
|
+
value,
|
|
16
|
+
...rest
|
|
17
|
+
} = Astro.props
|
|
18
|
+
|
|
19
|
+
const { classList, passthrough } = resolveLumenAstroProps(
|
|
20
|
+
rest,
|
|
21
|
+
[],
|
|
22
|
+
classProp,
|
|
23
|
+
className
|
|
24
|
+
)
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
role="tabpanel"
|
|
29
|
+
data-value={value}
|
|
30
|
+
tabindex="0"
|
|
31
|
+
class:list={classList}
|
|
32
|
+
{...passthrough}
|
|
33
|
+
>
|
|
34
|
+
<slot />
|
|
35
|
+
</div>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'button'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
value: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
class: classProp = '',
|
|
14
|
+
className = '',
|
|
15
|
+
type = 'button',
|
|
16
|
+
value,
|
|
17
|
+
...rest
|
|
18
|
+
} = Astro.props
|
|
19
|
+
|
|
20
|
+
const { classList, passthrough } = resolveLumenAstroProps(
|
|
21
|
+
rest,
|
|
22
|
+
[],
|
|
23
|
+
classProp,
|
|
24
|
+
className
|
|
25
|
+
)
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<button
|
|
29
|
+
type={type}
|
|
30
|
+
role="tab"
|
|
31
|
+
data-value={value}
|
|
32
|
+
class:list={classList}
|
|
33
|
+
{...passthrough}
|
|
34
|
+
>
|
|
35
|
+
<slot />
|
|
36
|
+
</button>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<'span'> {
|
|
7
|
+
class?: string | null
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
class: classProp = '',
|
|
13
|
+
className = '',
|
|
14
|
+
...rest
|
|
15
|
+
} = Astro.props
|
|
16
|
+
|
|
17
|
+
const { classList, passthrough } = resolveLumenAstroProps(
|
|
18
|
+
rest,
|
|
19
|
+
[],
|
|
20
|
+
classProp,
|
|
21
|
+
className
|
|
22
|
+
)
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<span role="tooltip" class:list={classList} {...passthrough}>
|
|
26
|
+
<slot />
|
|
27
|
+
</span>
|
package/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ export { default as Combobox } from './components/Combobox.astro'
|
|
|
40
40
|
export { default as Command } from './components/Command.astro'
|
|
41
41
|
export { default as Container } from './components/Container.astro'
|
|
42
42
|
export { default as ContextMenu } from './components/ContextMenu.astro'
|
|
43
|
+
export { default as ContextNavigation } from './components/ContextNavigation.astro'
|
|
43
44
|
export { default as CopyButton } from './components/CopyButton.astro'
|
|
44
45
|
export { default as CoverImage } from './components/CoverImage.astro'
|
|
45
46
|
export { default as DataTable } from './components/DataTable.astro'
|
|
@@ -50,6 +51,10 @@ export { default as Dialog } from './components/Dialog.astro'
|
|
|
50
51
|
export { default as Direction } from './components/Direction.astro'
|
|
51
52
|
export { default as Drawer } from './components/Drawer.astro'
|
|
52
53
|
export { default as DropdownMenu } from './components/DropdownMenu.astro'
|
|
54
|
+
export { default as DropdownMenuContent } from './components/DropdownMenuContent.astro'
|
|
55
|
+
export { default as DropdownMenuItem } from './components/DropdownMenuItem.astro'
|
|
56
|
+
export { default as DropdownMenuSeparator } from './components/DropdownMenuSeparator.astro'
|
|
57
|
+
export { default as DropdownMenuTrigger } from './components/DropdownMenuTrigger.astro'
|
|
53
58
|
export { default as Empty } from './components/Empty.astro'
|
|
54
59
|
export { default as ErrorSummary } from './components/ErrorSummary.astro'
|
|
55
60
|
export { default as Eyebrow } from './components/Eyebrow.astro'
|
|
@@ -68,6 +73,8 @@ export { default as Input } from './components/Input.astro'
|
|
|
68
73
|
export { default as InputGroup } from './components/InputGroup.astro'
|
|
69
74
|
export { default as InputOTP } from './components/InputOTP.astro'
|
|
70
75
|
export { default as Item } from './components/Item.astro'
|
|
76
|
+
export { default as KanbanBoard } from './components/KanbanBoard.astro'
|
|
77
|
+
export { default as KanbanColumn } from './components/KanbanColumn.astro'
|
|
71
78
|
export { default as Kbd } from './components/Kbd.astro'
|
|
72
79
|
export { default as Label } from './components/Label.astro'
|
|
73
80
|
export { default as LanguageToggle } from './components/LanguageToggle.astro'
|
|
@@ -92,6 +99,8 @@ export { default as PieChart } from './components/PieChart.astro'
|
|
|
92
99
|
export { default as Pill } from './components/Pill.astro'
|
|
93
100
|
export { default as Popconfirm } from './components/Popconfirm.astro'
|
|
94
101
|
export { default as Popover } from './components/Popover.astro'
|
|
102
|
+
export { default as PopoverPanel } from './components/PopoverPanel.astro'
|
|
103
|
+
export { default as PopoverTrigger } from './components/PopoverTrigger.astro'
|
|
95
104
|
export { default as Progress } from './components/Progress.astro'
|
|
96
105
|
export { default as Prose } from './components/Prose.astro'
|
|
97
106
|
export { default as QRCode } from './components/QRCode.astro'
|
|
@@ -128,6 +137,9 @@ export { default as Stepper } from './components/Stepper.astro'
|
|
|
128
137
|
export { default as Switch } from './components/Switch.astro'
|
|
129
138
|
export { default as Table } from './components/Table.astro'
|
|
130
139
|
export { default as Tabs } from './components/Tabs.astro'
|
|
140
|
+
export { default as TabsList } from './components/TabsList.astro'
|
|
141
|
+
export { default as TabsPanel } from './components/TabsPanel.astro'
|
|
142
|
+
export { default as TabsTrigger } from './components/TabsTrigger.astro'
|
|
131
143
|
export { default as TagGroup } from './components/TagGroup.astro'
|
|
132
144
|
export { default as Textarea } from './components/Textarea.astro'
|
|
133
145
|
export { default as ThemeBuilder } from './components/ThemeBuilder.astro'
|
|
@@ -140,6 +152,7 @@ export { default as Toggle } from './components/Toggle.astro'
|
|
|
140
152
|
export { default as ToggleGroup } from './components/ToggleGroup.astro'
|
|
141
153
|
export { default as Toolbar } from './components/Toolbar.astro'
|
|
142
154
|
export { default as Tooltip } from './components/Tooltip.astro'
|
|
155
|
+
export { default as TooltipContent } from './components/TooltipContent.astro'
|
|
143
156
|
export { default as Tour } from './components/Tour.astro'
|
|
144
157
|
export { default as Transfer } from './components/Transfer.astro'
|
|
145
158
|
export { default as Tree } from './components/Tree.astro'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@santi020k/lumen-astro",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"uqr": "^0.1.3",
|
|
40
|
-
"@santi020k/lumen": "^1.
|
|
41
|
-
"@santi020k/lumen-core": "^1.
|
|
40
|
+
"@santi020k/lumen": "^1.3.0",
|
|
41
|
+
"@santi020k/lumen-core": "^1.3.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@astrojs/ts-plugin": "^1.10.10",
|
|
@@ -57,10 +57,19 @@
|
|
|
57
57
|
"url": "https://github.com/santi020k/lumen.git",
|
|
58
58
|
"directory": "packages/astro"
|
|
59
59
|
},
|
|
60
|
-
"homepage": "https://lumen.santi020k.com",
|
|
60
|
+
"homepage": "https://lumen.santi020k.com/docs/frameworks/astro",
|
|
61
61
|
"bugs": {
|
|
62
62
|
"url": "https://github.com/santi020k/lumen/issues"
|
|
63
63
|
},
|
|
64
|
+
"keywords": [
|
|
65
|
+
"astro",
|
|
66
|
+
"astro-components",
|
|
67
|
+
"accessible-ui",
|
|
68
|
+
"component-library",
|
|
69
|
+
"design-system",
|
|
70
|
+
"lumen",
|
|
71
|
+
"ui"
|
|
72
|
+
],
|
|
64
73
|
"author": {
|
|
65
74
|
"name": "Santiago Molina",
|
|
66
75
|
"url": "https://santi020k.com"
|
|
@@ -10,17 +10,21 @@
|
|
|
10
10
|
|
|
11
11
|
<script>
|
|
12
12
|
import {
|
|
13
|
+
createLumenKanbanMoveDetail,
|
|
13
14
|
createThemeFromHue,
|
|
14
15
|
exportThemeCss,
|
|
15
16
|
exportThemeDesignTokens,
|
|
16
17
|
exportThemeFigmaVariables,
|
|
18
|
+
formatLumenLanguageLabel,
|
|
19
|
+
getLumenLocalePair,
|
|
17
20
|
getLumenRichTextShortcut,
|
|
18
21
|
isLumenRichTextToggleCommand,
|
|
22
|
+
type LumenLocaleOption,
|
|
19
23
|
type LumenRichTextChangeDetail,
|
|
20
24
|
type LumenRichTextCommandDetail,
|
|
21
25
|
type LumenThemeTokens,
|
|
22
|
-
lumenTokenNames
|
|
23
|
-
} from '@santi020k/lumen-core'
|
|
26
|
+
lumenTokenNames,
|
|
27
|
+
normalizeLumenLocales } from '@santi020k/lumen-core'
|
|
24
28
|
|
|
25
29
|
/* eslint-disable complexity */
|
|
26
30
|
/* Stateful DOM handlers stay local so Astro consumers do not need a framework runtime. */
|
|
@@ -236,6 +240,18 @@ const initDisclosures = (scope: ParentNode): void => {
|
|
|
236
240
|
nextItem?.focus()
|
|
237
241
|
})
|
|
238
242
|
|
|
243
|
+
if (root.hasAttribute('data-ui-dropdown-menu')) {
|
|
244
|
+
panel.addEventListener('click', event => {
|
|
245
|
+
const item = event.target instanceof Element ?
|
|
246
|
+
event.target.closest<HTMLElement>('[role="menuitem"]') :
|
|
247
|
+
null
|
|
248
|
+
|
|
249
|
+
if (!item || item.matches(':disabled, [aria-disabled="true"]')) return
|
|
250
|
+
|
|
251
|
+
setOpen(trigger, panel, false)
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
|
|
239
255
|
if (root.hasAttribute('data-ui-hover-card')) {
|
|
240
256
|
root.addEventListener('mouseenter', () => setOpen(trigger, panel, true))
|
|
241
257
|
|
|
@@ -932,6 +948,12 @@ const initTabs = (scope: ParentNode): void => {
|
|
|
932
948
|
const panels = [...root.querySelectorAll<HTMLElement>('[role="tabpanel"]')]
|
|
933
949
|
.filter(panel => panel.closest('[data-ui-tabs]') === root)
|
|
934
950
|
|
|
951
|
+
const tabList = tabs[0]?.closest<HTMLElement>('[role="tablist"]')
|
|
952
|
+
|
|
953
|
+
const orientation = tabList?.getAttribute('aria-orientation') === 'vertical' ?
|
|
954
|
+
'vertical' :
|
|
955
|
+
'horizontal'
|
|
956
|
+
|
|
935
957
|
if (!tabs.length || !panels.length) continue
|
|
936
958
|
|
|
937
959
|
const rootId = root.id || `ui-tabs-${++uiTabsCount}`
|
|
@@ -1033,13 +1055,22 @@ const initTabs = (scope: ParentNode): void => {
|
|
|
1033
1055
|
tab.addEventListener('click', () => activate(tab))
|
|
1034
1056
|
|
|
1035
1057
|
tab.addEventListener('keydown', event => {
|
|
1036
|
-
const keys =
|
|
1058
|
+
const keys = orientation === 'vertical' ?
|
|
1059
|
+
['ArrowUp', 'ArrowDown', 'Home', 'End'] :
|
|
1060
|
+
['ArrowLeft', 'ArrowRight', 'Home', 'End']
|
|
1037
1061
|
|
|
1038
1062
|
if (!keys.includes(event.key)) return
|
|
1039
1063
|
|
|
1040
1064
|
event.preventDefault()
|
|
1041
1065
|
|
|
1042
|
-
const nextTab = tabs[
|
|
1066
|
+
const nextTab = tabs[
|
|
1067
|
+
getLoopedIndex(
|
|
1068
|
+
event.key,
|
|
1069
|
+
index,
|
|
1070
|
+
tabs.length,
|
|
1071
|
+
orientation === 'vertical' ? ['ArrowDown'] : ['ArrowRight']
|
|
1072
|
+
)
|
|
1073
|
+
]
|
|
1043
1074
|
|
|
1044
1075
|
if (nextTab) {
|
|
1045
1076
|
activate(nextTab)
|
|
@@ -1281,6 +1312,109 @@ const initThemeToggles = (scope: ParentNode): void => {
|
|
|
1281
1312
|
}
|
|
1282
1313
|
}
|
|
1283
1314
|
|
|
1315
|
+
const isUnknownArray = (value: unknown): value is unknown[] => Array.isArray(value)
|
|
1316
|
+
|
|
1317
|
+
const parseLanguageToggleLocales = (value: string | undefined): readonly LumenLocaleOption[] => {
|
|
1318
|
+
if (!value) return normalizeLumenLocales(undefined)
|
|
1319
|
+
|
|
1320
|
+
try {
|
|
1321
|
+
const parsed: unknown = JSON.parse(value)
|
|
1322
|
+
|
|
1323
|
+
if (!isUnknownArray(parsed)) return normalizeLumenLocales(undefined)
|
|
1324
|
+
|
|
1325
|
+
const locales = parsed.flatMap(item => {
|
|
1326
|
+
if (!item || typeof item !== 'object' || !('label' in item) || !('value' in item)) return []
|
|
1327
|
+
|
|
1328
|
+
const { label, value: localeValue } = item
|
|
1329
|
+
|
|
1330
|
+
return typeof label === 'string' && typeof localeValue === 'string' ?
|
|
1331
|
+
[{ label, value: localeValue }] :
|
|
1332
|
+
[]
|
|
1333
|
+
})
|
|
1334
|
+
|
|
1335
|
+
return normalizeLumenLocales(locales)
|
|
1336
|
+
} catch {
|
|
1337
|
+
return normalizeLumenLocales(undefined)
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
const initLanguageToggles = (scope: ParentNode): void => {
|
|
1342
|
+
for (const control of scope.querySelectorAll<HTMLButtonElement>('[data-ui-language-toggle]')) {
|
|
1343
|
+
if (control.dataset.uiBound === 'true') continue
|
|
1344
|
+
|
|
1345
|
+
control.dataset.uiBound = 'true'
|
|
1346
|
+
|
|
1347
|
+
const locales = parseLanguageToggleLocales(control.dataset.uiLanguageLocales)
|
|
1348
|
+
const storageKey = control.dataset.uiLanguageStorageKey
|
|
1349
|
+
const controlled = control.hasAttribute('data-ui-language-controlled')
|
|
1350
|
+
|
|
1351
|
+
const labelTemplate = control.dataset.uiLanguageLabelTemplate ??
|
|
1352
|
+
'Change language from {current} to {next}'
|
|
1353
|
+
|
|
1354
|
+
const sync = (value: string): void => {
|
|
1355
|
+
const { current, next } = getLumenLocalePair(locales, value)
|
|
1356
|
+
const label = control.querySelector<HTMLElement>('[data-ui-language-label]')
|
|
1357
|
+
|
|
1358
|
+
control.dataset.uiLanguageValue = current.value
|
|
1359
|
+
|
|
1360
|
+
control.dataset.uiLanguageNextValue = next.value
|
|
1361
|
+
|
|
1362
|
+
control.setAttribute('aria-label', formatLumenLanguageLabel(labelTemplate, current, next))
|
|
1363
|
+
|
|
1364
|
+
if (label) {
|
|
1365
|
+
label.lang = next.value
|
|
1366
|
+
|
|
1367
|
+
label.textContent = next.label
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
let storedValue: string | null = null
|
|
1372
|
+
|
|
1373
|
+
if (!controlled && storageKey) {
|
|
1374
|
+
try {
|
|
1375
|
+
storedValue = localStorage.getItem(storageKey)
|
|
1376
|
+
} catch {
|
|
1377
|
+
// Storage can be unavailable in privacy modes.
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
const documentValue = document.documentElement.lang || undefined
|
|
1382
|
+
|
|
1383
|
+
const initialValue = controlled ?
|
|
1384
|
+
control.dataset.uiLanguageValue :
|
|
1385
|
+
storedValue ?? control.dataset.uiLanguageInitialValue ?? documentValue
|
|
1386
|
+
|
|
1387
|
+
const initialPair = getLumenLocalePair(locales, initialValue)
|
|
1388
|
+
|
|
1389
|
+
sync(initialPair.current.value)
|
|
1390
|
+
|
|
1391
|
+
if (!controlled) document.documentElement.lang = initialPair.current.value
|
|
1392
|
+
|
|
1393
|
+
control.addEventListener('click', () => {
|
|
1394
|
+
const { current, next } = getLumenLocalePair(locales, control.dataset.uiLanguageValue)
|
|
1395
|
+
|
|
1396
|
+
if (!controlled) {
|
|
1397
|
+
document.documentElement.lang = next.value
|
|
1398
|
+
|
|
1399
|
+
sync(next.value)
|
|
1400
|
+
|
|
1401
|
+
if (storageKey) {
|
|
1402
|
+
try {
|
|
1403
|
+
localStorage.setItem(storageKey, next.value)
|
|
1404
|
+
} catch {
|
|
1405
|
+
// Storage can be unavailable in privacy modes.
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
control.dispatchEvent(new CustomEvent('ui:language-change', {
|
|
1411
|
+
bubbles: true,
|
|
1412
|
+
detail: { previousValue: current.value, value: next.value }
|
|
1413
|
+
}))
|
|
1414
|
+
})
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1284
1418
|
const getVisibleItems = (items: HTMLElement[]): HTMLElement[] => items.filter(item => !item.hidden &&
|
|
1285
1419
|
item.offsetParent !== null &&
|
|
1286
1420
|
!item.hasAttribute('disabled') &&
|
|
@@ -3237,6 +3371,239 @@ const initSchedules = (scope: ParentNode): void => {
|
|
|
3237
3371
|
}
|
|
3238
3372
|
}
|
|
3239
3373
|
|
|
3374
|
+
const kanbanColumnSelector = '[data-ui-kanban-column]'
|
|
3375
|
+
|
|
3376
|
+
const initKanbanBoards = (scope: ParentNode): void => {
|
|
3377
|
+
for (const root of scope.querySelectorAll<HTMLElement>('[data-ui-kanban]')) {
|
|
3378
|
+
if (root.dataset.uiKanbanBound === 'true') continue
|
|
3379
|
+
|
|
3380
|
+
root.dataset.uiKanbanBound = 'true'
|
|
3381
|
+
|
|
3382
|
+
for (const handle of root.querySelectorAll<HTMLElement>('[data-ui-kanban-handle]'))
|
|
3383
|
+
handle.draggable = true
|
|
3384
|
+
|
|
3385
|
+
let pointer: {
|
|
3386
|
+
active: boolean
|
|
3387
|
+
handle: HTMLElement
|
|
3388
|
+
item: HTMLElement
|
|
3389
|
+
pointerId: number
|
|
3390
|
+
startX: number
|
|
3391
|
+
startY: number
|
|
3392
|
+
} | undefined
|
|
3393
|
+
|
|
3394
|
+
const itemFor = (target: EventTarget | null): HTMLElement | null => target instanceof Element ?
|
|
3395
|
+
target.closest<HTMLElement>('[data-ui-kanban-item]') :
|
|
3396
|
+
null
|
|
3397
|
+
|
|
3398
|
+
const columnFor = (target: EventTarget | null): HTMLElement | null => target instanceof Element ?
|
|
3399
|
+
target.closest<HTMLElement>(kanbanColumnSelector) :
|
|
3400
|
+
null
|
|
3401
|
+
|
|
3402
|
+
const clearState = (): void => {
|
|
3403
|
+
pointer = undefined
|
|
3404
|
+
|
|
3405
|
+
for (const item of root.querySelectorAll<HTMLElement>('[data-ui-kanban-item]'))
|
|
3406
|
+
if (item.dataset.state === 'dragging') delete item.dataset.state
|
|
3407
|
+
|
|
3408
|
+
for (const column of root.querySelectorAll<HTMLElement>(kanbanColumnSelector))
|
|
3409
|
+
if (column.dataset.state === 'drop-target') delete column.dataset.state
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
const announce = (column: HTMLElement): void => {
|
|
3413
|
+
let live = root.querySelector<HTMLElement>('[data-ui-kanban-live]')
|
|
3414
|
+
|
|
3415
|
+
if (!live) {
|
|
3416
|
+
live = document.createElement('span')
|
|
3417
|
+
|
|
3418
|
+
live.className = 'ui-visually-hidden'
|
|
3419
|
+
|
|
3420
|
+
live.dataset.uiKanbanLive = ''
|
|
3421
|
+
|
|
3422
|
+
live.setAttribute('aria-live', 'polite')
|
|
3423
|
+
|
|
3424
|
+
live.setAttribute('role', 'status')
|
|
3425
|
+
|
|
3426
|
+
root.append(live)
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
const label = column.getAttribute('aria-label') ||
|
|
3430
|
+
column.querySelector('h2, h3, [data-ui-kanban-column-label]')?.textContent?.trim() ||
|
|
3431
|
+
column.dataset.uiKanbanColumn || ''
|
|
3432
|
+
|
|
3433
|
+
live.textContent = `Move requested to ${label}.`
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
const requestMove = (
|
|
3437
|
+
item: HTMLElement,
|
|
3438
|
+
targetColumn: HTMLElement,
|
|
3439
|
+
input: 'keyboard' | 'pointer'
|
|
3440
|
+
): void => {
|
|
3441
|
+
const fromColumn = item.closest<HTMLElement>(kanbanColumnSelector)
|
|
3442
|
+
?.dataset.uiKanbanColumn ?? ''
|
|
3443
|
+
|
|
3444
|
+
const detail = createLumenKanbanMoveDetail({
|
|
3445
|
+
fromColumn,
|
|
3446
|
+
input,
|
|
3447
|
+
itemId: item.dataset.uiKanbanItem ?? item.id,
|
|
3448
|
+
toColumn: targetColumn.dataset.uiKanbanColumn ?? ''
|
|
3449
|
+
})
|
|
3450
|
+
|
|
3451
|
+
if (!detail) return
|
|
3452
|
+
|
|
3453
|
+
const accepted = root.dispatchEvent(new CustomEvent('ui:kanban-move-request', {
|
|
3454
|
+
bubbles: true,
|
|
3455
|
+
cancelable: true,
|
|
3456
|
+
detail
|
|
3457
|
+
}))
|
|
3458
|
+
|
|
3459
|
+
if (accepted) announce(targetColumn)
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
root.addEventListener('dragstart', event => {
|
|
3463
|
+
const handle = event.target instanceof Element ?
|
|
3464
|
+
event.target.closest<HTMLElement>('[data-ui-kanban-handle]') :
|
|
3465
|
+
null
|
|
3466
|
+
|
|
3467
|
+
const item = itemFor(handle)
|
|
3468
|
+
|
|
3469
|
+
if (!handle || !item || item.getAttribute('aria-busy') === 'true') {
|
|
3470
|
+
event.preventDefault()
|
|
3471
|
+
|
|
3472
|
+
return
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
event.dataTransfer?.setData('text/plain', item.dataset.uiKanbanItem ?? item.id)
|
|
3476
|
+
|
|
3477
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move'
|
|
3478
|
+
|
|
3479
|
+
item.dataset.state = 'dragging'
|
|
3480
|
+
})
|
|
3481
|
+
|
|
3482
|
+
root.addEventListener('dragend', clearState)
|
|
3483
|
+
|
|
3484
|
+
root.addEventListener('dragover', event => {
|
|
3485
|
+
const column = columnFor(event.target)
|
|
3486
|
+
|
|
3487
|
+
if (!column) return
|
|
3488
|
+
|
|
3489
|
+
event.preventDefault()
|
|
3490
|
+
|
|
3491
|
+
column.dataset.state = 'drop-target'
|
|
3492
|
+
|
|
3493
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'
|
|
3494
|
+
})
|
|
3495
|
+
|
|
3496
|
+
root.addEventListener('dragleave', event => {
|
|
3497
|
+
const column = columnFor(event.target)
|
|
3498
|
+
|
|
3499
|
+
if (
|
|
3500
|
+
!column ||
|
|
3501
|
+
(event.relatedTarget instanceof Node && column.contains(event.relatedTarget))
|
|
3502
|
+
) return
|
|
3503
|
+
|
|
3504
|
+
delete column.dataset.state
|
|
3505
|
+
})
|
|
3506
|
+
|
|
3507
|
+
root.addEventListener('drop', event => {
|
|
3508
|
+
const column = columnFor(event.target)
|
|
3509
|
+
const itemId = event.dataTransfer?.getData('text/plain') ?? ''
|
|
3510
|
+
|
|
3511
|
+
const item = [...root.querySelectorAll<HTMLElement>('[data-ui-kanban-item]')]
|
|
3512
|
+
.find(candidate => (candidate.dataset.uiKanbanItem ?? candidate.id) === itemId)
|
|
3513
|
+
|
|
3514
|
+
if (!column || !item) return
|
|
3515
|
+
|
|
3516
|
+
event.preventDefault()
|
|
3517
|
+
|
|
3518
|
+
requestMove(item, column, 'pointer')
|
|
3519
|
+
|
|
3520
|
+
clearState()
|
|
3521
|
+
})
|
|
3522
|
+
|
|
3523
|
+
root.addEventListener('keydown', event => {
|
|
3524
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
|
|
3525
|
+
|
|
3526
|
+
const handle = event.target instanceof Element ?
|
|
3527
|
+
event.target.closest<HTMLElement>('[data-ui-kanban-handle]') :
|
|
3528
|
+
null
|
|
3529
|
+
|
|
3530
|
+
const item = itemFor(handle)
|
|
3531
|
+
const currentColumn = columnFor(item)
|
|
3532
|
+
|
|
3533
|
+
if (!handle || !item || !currentColumn || item.getAttribute('aria-busy') === 'true') return
|
|
3534
|
+
|
|
3535
|
+
const columns = [...root.querySelectorAll<HTMLElement>(kanbanColumnSelector)]
|
|
3536
|
+
const currentIndex = columns.indexOf(currentColumn)
|
|
3537
|
+
const direction = event.key === 'ArrowRight' ? 1 : -1
|
|
3538
|
+
const rtl = getComputedStyle(root).direction === 'rtl'
|
|
3539
|
+
const target = columns[currentIndex + (rtl ? -direction : direction)]
|
|
3540
|
+
|
|
3541
|
+
if (!target) return
|
|
3542
|
+
|
|
3543
|
+
event.preventDefault()
|
|
3544
|
+
|
|
3545
|
+
requestMove(item, target, 'keyboard')
|
|
3546
|
+
})
|
|
3547
|
+
|
|
3548
|
+
root.addEventListener('pointerdown', event => {
|
|
3549
|
+
if (event.pointerType === 'mouse') return
|
|
3550
|
+
|
|
3551
|
+
const handle = event.target instanceof Element ?
|
|
3552
|
+
event.target.closest<HTMLElement>('[data-ui-kanban-handle]') :
|
|
3553
|
+
null
|
|
3554
|
+
|
|
3555
|
+
const item = itemFor(handle)
|
|
3556
|
+
|
|
3557
|
+
if (!handle || !item || item.getAttribute('aria-busy') === 'true') return
|
|
3558
|
+
|
|
3559
|
+
pointer = {
|
|
3560
|
+
active: false,
|
|
3561
|
+
handle,
|
|
3562
|
+
item,
|
|
3563
|
+
pointerId: event.pointerId,
|
|
3564
|
+
startX: event.clientX,
|
|
3565
|
+
startY: event.clientY
|
|
3566
|
+
}
|
|
3567
|
+
})
|
|
3568
|
+
|
|
3569
|
+
root.addEventListener('pointermove', event => {
|
|
3570
|
+
if (!pointer || pointer.pointerId !== event.pointerId) return
|
|
3571
|
+
|
|
3572
|
+
if (!pointer.active && Math.hypot(
|
|
3573
|
+
event.clientX - pointer.startX,
|
|
3574
|
+
event.clientY - pointer.startY
|
|
3575
|
+
) < 8) return
|
|
3576
|
+
|
|
3577
|
+
pointer.active = true
|
|
3578
|
+
|
|
3579
|
+
pointer.handle.setPointerCapture(event.pointerId)
|
|
3580
|
+
|
|
3581
|
+
pointer.item.dataset.state = 'dragging'
|
|
3582
|
+
|
|
3583
|
+
const target = document.elementFromPoint(event.clientX, event.clientY)
|
|
3584
|
+
?.closest<HTMLElement>(kanbanColumnSelector)
|
|
3585
|
+
|
|
3586
|
+
for (const column of root.querySelectorAll<HTMLElement>(kanbanColumnSelector)) {
|
|
3587
|
+
if (column === target) column.dataset.state = 'drop-target'
|
|
3588
|
+
else delete column.dataset.state
|
|
3589
|
+
}
|
|
3590
|
+
})
|
|
3591
|
+
|
|
3592
|
+
root.addEventListener('pointerup', event => {
|
|
3593
|
+
if (!pointer || pointer.pointerId !== event.pointerId) return
|
|
3594
|
+
|
|
3595
|
+
const target = document.elementFromPoint(event.clientX, event.clientY)
|
|
3596
|
+
?.closest<HTMLElement>(kanbanColumnSelector)
|
|
3597
|
+
|
|
3598
|
+
if (pointer.active && target) requestMove(pointer.item, target, 'pointer')
|
|
3599
|
+
|
|
3600
|
+
clearState()
|
|
3601
|
+
})
|
|
3602
|
+
|
|
3603
|
+
root.addEventListener('pointercancel', clearState)
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3240
3607
|
const initTreeCollections = (scope: ParentNode): void => {
|
|
3241
3608
|
for (const root of scope.querySelectorAll<HTMLElement>('[role="tree"], [role="treegrid"]')) {
|
|
3242
3609
|
if (root.dataset.uiBound === 'true') continue
|
|
@@ -5226,6 +5593,8 @@ const initUiPrimitives = (scope: ParentNode = document): void => {
|
|
|
5226
5593
|
|
|
5227
5594
|
initThemeToggles(scope)
|
|
5228
5595
|
|
|
5596
|
+
initLanguageToggles(scope)
|
|
5597
|
+
|
|
5229
5598
|
initCommand(scope)
|
|
5230
5599
|
|
|
5231
5600
|
initComboboxes(scope)
|
|
@@ -5252,6 +5621,8 @@ const initUiPrimitives = (scope: ParentNode = document): void => {
|
|
|
5252
5621
|
|
|
5253
5622
|
initSchedules(scope)
|
|
5254
5623
|
|
|
5624
|
+
initKanbanBoards(scope)
|
|
5625
|
+
|
|
5255
5626
|
initTreeCollections(scope)
|
|
5256
5627
|
|
|
5257
5628
|
initVirtualLists(scope)
|