@santi020k/lumen-astro 1.0.0 → 1.2.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 +42 -0
- package/components/ContextNavigation.astro +37 -0
- package/components/Empty.astro +3 -0
- package/components/Input.astro +17 -4
- package/components/KanbanBoard.astro +32 -0
- package/components/KanbanColumn.astro +30 -0
- package/components/NativeSelect.astro +17 -4
- package/index.ts +3 -0
- package/package.json +14 -5
- package/runtime/UIPrimitives.astro +236 -0
package/README.md
CHANGED
|
@@ -51,6 +51,40 @@ import { Button, Card, Input } from '@santi020k/lumen-astro'
|
|
|
51
51
|
</Card>
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
`Input` and `NativeSelect` preserve the native numeric `size` attribute. Use `visualSize="sm"` or
|
|
55
|
+
`visualSize="lg"` for presentation. The pre-1.0 `size="sm|lg"` visual alias remains available for
|
|
56
|
+
incremental migrations, but new code should keep native behavior and visual styling separate.
|
|
57
|
+
|
|
58
|
+
```astro
|
|
59
|
+
<Input size={32} visualSize="sm" />
|
|
60
|
+
<NativeSelect size={8} visualSize="lg" />
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Context navigation
|
|
64
|
+
|
|
65
|
+
Use `ContextNavigation` below a primary header when the available links change with a selected
|
|
66
|
+
platform, product, or workspace. Put the stable selector or identity in the `context` slot and one
|
|
67
|
+
independently named `NavigationMenu` in the default slot. Long link sets scroll horizontally.
|
|
68
|
+
|
|
69
|
+
```astro
|
|
70
|
+
---
|
|
71
|
+
import { ContextNavigation, NativeSelect, NavigationMenu } from '@santi020k/lumen-astro'
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
<ContextNavigation>
|
|
75
|
+
<NativeSelect slot="context" aria-label="Documentation platform" visualSize="sm">
|
|
76
|
+
<option>Web</option>
|
|
77
|
+
<option>Apple</option>
|
|
78
|
+
<option>Android</option>
|
|
79
|
+
</NativeSelect>
|
|
80
|
+
<NavigationMenu aria-label="Web documentation" variant="unstyled">
|
|
81
|
+
<a href="/docs/web" aria-current="page">Overview</a>
|
|
82
|
+
<a href="/docs/components">Components</a>
|
|
83
|
+
<a href="/docs/web/playground">Playground</a>
|
|
84
|
+
</NavigationMenu>
|
|
85
|
+
</ContextNavigation>
|
|
86
|
+
```
|
|
87
|
+
|
|
54
88
|
## Forms and Astro Actions
|
|
55
89
|
|
|
56
90
|
`Form` renders a semantic form and preserves native `method`, `action`, `enctype`, autocomplete,
|
|
@@ -218,6 +252,14 @@ with `--ui-logo-delay`.
|
|
|
218
252
|
|
|
219
253
|
The package ships the complete Astro primitive catalog plus a small progressive-enhancement runtime for dialogs, popovers, tabs, menus, command filtering, carousels, and toasts.
|
|
220
254
|
|
|
255
|
+
## Kanban boards
|
|
256
|
+
|
|
257
|
+
Compose `KanbanBoard` and `KanbanColumn` with ordinary `Card` items. Give every item a stable
|
|
258
|
+
`data-ui-kanban-item` and add a dedicated button with `data-ui-kanban-handle`. `UIPrimitives` emits
|
|
259
|
+
the cancellable `ui:kanban-move-request` event for keyboard, mouse, and touch movement without
|
|
260
|
+
changing the DOM. Keep persistence, pending state, rollback, and status rules in the application.
|
|
261
|
+
Use `Empty variant="compact"` inside empty columns and `Skeleton` cards while loading.
|
|
262
|
+
|
|
221
263
|
## Motion
|
|
222
264
|
|
|
223
265
|
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>
|
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'
|
package/components/Input.astro
CHANGED
|
@@ -6,20 +6,33 @@ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
|
6
6
|
interface Props extends Omit<HTMLAttributes<'input'>, 'size'> {
|
|
7
7
|
class?: string | null
|
|
8
8
|
className?: string
|
|
9
|
-
size?: '
|
|
9
|
+
size?: HTMLAttributes<'input'>['size']
|
|
10
|
+
visualSize?: 'default' | 'lg' | 'sm'
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
class: classProp = '',
|
|
15
|
+
className = '',
|
|
16
|
+
size,
|
|
17
|
+
type = 'text',
|
|
18
|
+
visualSize,
|
|
19
|
+
...rest
|
|
20
|
+
} = Astro.props
|
|
21
|
+
|
|
22
|
+
const legacyVisualSize = size === 'default' || size === 'lg' || size === 'sm' ? size : undefined
|
|
23
|
+
const resolvedVisualSize = visualSize ?? legacyVisualSize ?? 'default'
|
|
24
|
+
const nativeSize = legacyVisualSize ? undefined : size
|
|
13
25
|
|
|
14
26
|
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
15
27
|
'ui-input',
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
resolvedVisualSize === 'sm' && 'ui-input--sm',
|
|
29
|
+
resolvedVisualSize === 'lg' && 'ui-input--lg'
|
|
18
30
|
], classProp, className)
|
|
19
31
|
---
|
|
20
32
|
|
|
21
33
|
<input
|
|
22
34
|
class:list={classList}
|
|
35
|
+
size={nativeSize}
|
|
23
36
|
type={type}
|
|
24
37
|
{...passthrough}
|
|
25
38
|
/>
|
|
@@ -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>
|
|
@@ -14,20 +14,33 @@ type SelectOption = Option | string
|
|
|
14
14
|
interface Props extends Omit<HTMLAttributes<'select'>, 'size'> {
|
|
15
15
|
class?: string | null
|
|
16
16
|
className?: string
|
|
17
|
-
size?: '
|
|
17
|
+
size?: HTMLAttributes<'select'>['size']
|
|
18
18
|
options?: SelectOption[]
|
|
19
19
|
placeholder?: string
|
|
20
|
+
visualSize?: 'default' | 'lg' | 'sm'
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const {
|
|
23
|
+
const {
|
|
24
|
+
class: classProp = '',
|
|
25
|
+
className = '',
|
|
26
|
+
options = [],
|
|
27
|
+
placeholder,
|
|
28
|
+
size,
|
|
29
|
+
visualSize,
|
|
30
|
+
...rest
|
|
31
|
+
} = Astro.props
|
|
32
|
+
|
|
23
33
|
const hasValue = Object.hasOwn(rest, 'value')
|
|
24
34
|
const normalizedOptions = options.map(option => typeof option === 'string' ? { label: option, value: option } : option)
|
|
35
|
+
const legacyVisualSize = size === 'default' || size === 'lg' || size === 'sm' ? size : undefined
|
|
36
|
+
const resolvedVisualSize = visualSize ?? legacyVisualSize ?? 'default'
|
|
37
|
+
const nativeSize = legacyVisualSize ? undefined : size
|
|
25
38
|
|
|
26
|
-
const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-select',
|
|
39
|
+
const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-select', resolvedVisualSize === 'sm' && 'ui-select--sm', resolvedVisualSize === 'lg' && 'ui-select--lg'
|
|
27
40
|
], classProp, className)
|
|
28
41
|
---
|
|
29
42
|
|
|
30
|
-
<select class:list={classList} {...passthrough}>
|
|
43
|
+
<select class:list={classList} size={nativeSize} {...passthrough}>
|
|
31
44
|
{placeholder && <option disabled selected={!hasValue} value="">{placeholder}</option>}
|
|
32
45
|
<slot />
|
|
33
46
|
{normalizedOptions.map(option => (
|
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'
|
|
@@ -68,6 +69,8 @@ export { default as Input } from './components/Input.astro'
|
|
|
68
69
|
export { default as InputGroup } from './components/InputGroup.astro'
|
|
69
70
|
export { default as InputOTP } from './components/InputOTP.astro'
|
|
70
71
|
export { default as Item } from './components/Item.astro'
|
|
72
|
+
export { default as KanbanBoard } from './components/KanbanBoard.astro'
|
|
73
|
+
export { default as KanbanColumn } from './components/KanbanColumn.astro'
|
|
71
74
|
export { default as Kbd } from './components/Kbd.astro'
|
|
72
75
|
export { default as Label } from './components/Label.astro'
|
|
73
76
|
export { default as LanguageToggle } from './components/LanguageToggle.astro'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@santi020k/lumen-astro",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"uqr": "^0.1.3",
|
|
40
|
-
"@santi020k/lumen": "^1.
|
|
41
|
-
"@santi020k/lumen-core": "^1.
|
|
40
|
+
"@santi020k/lumen": "^1.2.0",
|
|
41
|
+
"@santi020k/lumen-core": "^1.2.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@astrojs/ts-plugin": "^1.10.10",
|
|
45
|
-
"astro": "^7.
|
|
45
|
+
"astro": "^7.2.0",
|
|
46
46
|
"typescript": "^6.0.3",
|
|
47
47
|
"vitest": "^4.1.10"
|
|
48
48
|
},
|
|
@@ -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,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<script>
|
|
12
12
|
import {
|
|
13
|
+
createLumenKanbanMoveDetail,
|
|
13
14
|
createThemeFromHue,
|
|
14
15
|
exportThemeCss,
|
|
15
16
|
exportThemeDesignTokens,
|
|
@@ -3237,6 +3238,239 @@ const initSchedules = (scope: ParentNode): void => {
|
|
|
3237
3238
|
}
|
|
3238
3239
|
}
|
|
3239
3240
|
|
|
3241
|
+
const kanbanColumnSelector = '[data-ui-kanban-column]'
|
|
3242
|
+
|
|
3243
|
+
const initKanbanBoards = (scope: ParentNode): void => {
|
|
3244
|
+
for (const root of scope.querySelectorAll<HTMLElement>('[data-ui-kanban]')) {
|
|
3245
|
+
if (root.dataset.uiKanbanBound === 'true') continue
|
|
3246
|
+
|
|
3247
|
+
root.dataset.uiKanbanBound = 'true'
|
|
3248
|
+
|
|
3249
|
+
for (const handle of root.querySelectorAll<HTMLElement>('[data-ui-kanban-handle]'))
|
|
3250
|
+
handle.draggable = true
|
|
3251
|
+
|
|
3252
|
+
let pointer: {
|
|
3253
|
+
active: boolean
|
|
3254
|
+
handle: HTMLElement
|
|
3255
|
+
item: HTMLElement
|
|
3256
|
+
pointerId: number
|
|
3257
|
+
startX: number
|
|
3258
|
+
startY: number
|
|
3259
|
+
} | undefined
|
|
3260
|
+
|
|
3261
|
+
const itemFor = (target: EventTarget | null): HTMLElement | null => target instanceof Element ?
|
|
3262
|
+
target.closest<HTMLElement>('[data-ui-kanban-item]') :
|
|
3263
|
+
null
|
|
3264
|
+
|
|
3265
|
+
const columnFor = (target: EventTarget | null): HTMLElement | null => target instanceof Element ?
|
|
3266
|
+
target.closest<HTMLElement>(kanbanColumnSelector) :
|
|
3267
|
+
null
|
|
3268
|
+
|
|
3269
|
+
const clearState = (): void => {
|
|
3270
|
+
pointer = undefined
|
|
3271
|
+
|
|
3272
|
+
for (const item of root.querySelectorAll<HTMLElement>('[data-ui-kanban-item]'))
|
|
3273
|
+
if (item.dataset.state === 'dragging') delete item.dataset.state
|
|
3274
|
+
|
|
3275
|
+
for (const column of root.querySelectorAll<HTMLElement>(kanbanColumnSelector))
|
|
3276
|
+
if (column.dataset.state === 'drop-target') delete column.dataset.state
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
const announce = (column: HTMLElement): void => {
|
|
3280
|
+
let live = root.querySelector<HTMLElement>('[data-ui-kanban-live]')
|
|
3281
|
+
|
|
3282
|
+
if (!live) {
|
|
3283
|
+
live = document.createElement('span')
|
|
3284
|
+
|
|
3285
|
+
live.className = 'ui-visually-hidden'
|
|
3286
|
+
|
|
3287
|
+
live.dataset.uiKanbanLive = ''
|
|
3288
|
+
|
|
3289
|
+
live.setAttribute('aria-live', 'polite')
|
|
3290
|
+
|
|
3291
|
+
live.setAttribute('role', 'status')
|
|
3292
|
+
|
|
3293
|
+
root.append(live)
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
const label = column.getAttribute('aria-label') ||
|
|
3297
|
+
column.querySelector('h2, h3, [data-ui-kanban-column-label]')?.textContent?.trim() ||
|
|
3298
|
+
column.dataset.uiKanbanColumn || ''
|
|
3299
|
+
|
|
3300
|
+
live.textContent = `Move requested to ${label}.`
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
const requestMove = (
|
|
3304
|
+
item: HTMLElement,
|
|
3305
|
+
targetColumn: HTMLElement,
|
|
3306
|
+
input: 'keyboard' | 'pointer'
|
|
3307
|
+
): void => {
|
|
3308
|
+
const fromColumn = item.closest<HTMLElement>(kanbanColumnSelector)
|
|
3309
|
+
?.dataset.uiKanbanColumn ?? ''
|
|
3310
|
+
|
|
3311
|
+
const detail = createLumenKanbanMoveDetail({
|
|
3312
|
+
fromColumn,
|
|
3313
|
+
input,
|
|
3314
|
+
itemId: item.dataset.uiKanbanItem ?? item.id,
|
|
3315
|
+
toColumn: targetColumn.dataset.uiKanbanColumn ?? ''
|
|
3316
|
+
})
|
|
3317
|
+
|
|
3318
|
+
if (!detail) return
|
|
3319
|
+
|
|
3320
|
+
const accepted = root.dispatchEvent(new CustomEvent('ui:kanban-move-request', {
|
|
3321
|
+
bubbles: true,
|
|
3322
|
+
cancelable: true,
|
|
3323
|
+
detail
|
|
3324
|
+
}))
|
|
3325
|
+
|
|
3326
|
+
if (accepted) announce(targetColumn)
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
root.addEventListener('dragstart', event => {
|
|
3330
|
+
const handle = event.target instanceof Element ?
|
|
3331
|
+
event.target.closest<HTMLElement>('[data-ui-kanban-handle]') :
|
|
3332
|
+
null
|
|
3333
|
+
|
|
3334
|
+
const item = itemFor(handle)
|
|
3335
|
+
|
|
3336
|
+
if (!handle || !item || item.getAttribute('aria-busy') === 'true') {
|
|
3337
|
+
event.preventDefault()
|
|
3338
|
+
|
|
3339
|
+
return
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
event.dataTransfer?.setData('text/plain', item.dataset.uiKanbanItem ?? item.id)
|
|
3343
|
+
|
|
3344
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move'
|
|
3345
|
+
|
|
3346
|
+
item.dataset.state = 'dragging'
|
|
3347
|
+
})
|
|
3348
|
+
|
|
3349
|
+
root.addEventListener('dragend', clearState)
|
|
3350
|
+
|
|
3351
|
+
root.addEventListener('dragover', event => {
|
|
3352
|
+
const column = columnFor(event.target)
|
|
3353
|
+
|
|
3354
|
+
if (!column) return
|
|
3355
|
+
|
|
3356
|
+
event.preventDefault()
|
|
3357
|
+
|
|
3358
|
+
column.dataset.state = 'drop-target'
|
|
3359
|
+
|
|
3360
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'
|
|
3361
|
+
})
|
|
3362
|
+
|
|
3363
|
+
root.addEventListener('dragleave', event => {
|
|
3364
|
+
const column = columnFor(event.target)
|
|
3365
|
+
|
|
3366
|
+
if (
|
|
3367
|
+
!column ||
|
|
3368
|
+
(event.relatedTarget instanceof Node && column.contains(event.relatedTarget))
|
|
3369
|
+
) return
|
|
3370
|
+
|
|
3371
|
+
delete column.dataset.state
|
|
3372
|
+
})
|
|
3373
|
+
|
|
3374
|
+
root.addEventListener('drop', event => {
|
|
3375
|
+
const column = columnFor(event.target)
|
|
3376
|
+
const itemId = event.dataTransfer?.getData('text/plain') ?? ''
|
|
3377
|
+
|
|
3378
|
+
const item = [...root.querySelectorAll<HTMLElement>('[data-ui-kanban-item]')]
|
|
3379
|
+
.find(candidate => (candidate.dataset.uiKanbanItem ?? candidate.id) === itemId)
|
|
3380
|
+
|
|
3381
|
+
if (!column || !item) return
|
|
3382
|
+
|
|
3383
|
+
event.preventDefault()
|
|
3384
|
+
|
|
3385
|
+
requestMove(item, column, 'pointer')
|
|
3386
|
+
|
|
3387
|
+
clearState()
|
|
3388
|
+
})
|
|
3389
|
+
|
|
3390
|
+
root.addEventListener('keydown', event => {
|
|
3391
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
|
|
3392
|
+
|
|
3393
|
+
const handle = event.target instanceof Element ?
|
|
3394
|
+
event.target.closest<HTMLElement>('[data-ui-kanban-handle]') :
|
|
3395
|
+
null
|
|
3396
|
+
|
|
3397
|
+
const item = itemFor(handle)
|
|
3398
|
+
const currentColumn = columnFor(item)
|
|
3399
|
+
|
|
3400
|
+
if (!handle || !item || !currentColumn || item.getAttribute('aria-busy') === 'true') return
|
|
3401
|
+
|
|
3402
|
+
const columns = [...root.querySelectorAll<HTMLElement>(kanbanColumnSelector)]
|
|
3403
|
+
const currentIndex = columns.indexOf(currentColumn)
|
|
3404
|
+
const direction = event.key === 'ArrowRight' ? 1 : -1
|
|
3405
|
+
const rtl = getComputedStyle(root).direction === 'rtl'
|
|
3406
|
+
const target = columns[currentIndex + (rtl ? -direction : direction)]
|
|
3407
|
+
|
|
3408
|
+
if (!target) return
|
|
3409
|
+
|
|
3410
|
+
event.preventDefault()
|
|
3411
|
+
|
|
3412
|
+
requestMove(item, target, 'keyboard')
|
|
3413
|
+
})
|
|
3414
|
+
|
|
3415
|
+
root.addEventListener('pointerdown', event => {
|
|
3416
|
+
if (event.pointerType === 'mouse') return
|
|
3417
|
+
|
|
3418
|
+
const handle = event.target instanceof Element ?
|
|
3419
|
+
event.target.closest<HTMLElement>('[data-ui-kanban-handle]') :
|
|
3420
|
+
null
|
|
3421
|
+
|
|
3422
|
+
const item = itemFor(handle)
|
|
3423
|
+
|
|
3424
|
+
if (!handle || !item || item.getAttribute('aria-busy') === 'true') return
|
|
3425
|
+
|
|
3426
|
+
pointer = {
|
|
3427
|
+
active: false,
|
|
3428
|
+
handle,
|
|
3429
|
+
item,
|
|
3430
|
+
pointerId: event.pointerId,
|
|
3431
|
+
startX: event.clientX,
|
|
3432
|
+
startY: event.clientY
|
|
3433
|
+
}
|
|
3434
|
+
})
|
|
3435
|
+
|
|
3436
|
+
root.addEventListener('pointermove', event => {
|
|
3437
|
+
if (!pointer || pointer.pointerId !== event.pointerId) return
|
|
3438
|
+
|
|
3439
|
+
if (!pointer.active && Math.hypot(
|
|
3440
|
+
event.clientX - pointer.startX,
|
|
3441
|
+
event.clientY - pointer.startY
|
|
3442
|
+
) < 8) return
|
|
3443
|
+
|
|
3444
|
+
pointer.active = true
|
|
3445
|
+
|
|
3446
|
+
pointer.handle.setPointerCapture(event.pointerId)
|
|
3447
|
+
|
|
3448
|
+
pointer.item.dataset.state = 'dragging'
|
|
3449
|
+
|
|
3450
|
+
const target = document.elementFromPoint(event.clientX, event.clientY)
|
|
3451
|
+
?.closest<HTMLElement>(kanbanColumnSelector)
|
|
3452
|
+
|
|
3453
|
+
for (const column of root.querySelectorAll<HTMLElement>(kanbanColumnSelector)) {
|
|
3454
|
+
if (column === target) column.dataset.state = 'drop-target'
|
|
3455
|
+
else delete column.dataset.state
|
|
3456
|
+
}
|
|
3457
|
+
})
|
|
3458
|
+
|
|
3459
|
+
root.addEventListener('pointerup', event => {
|
|
3460
|
+
if (!pointer || pointer.pointerId !== event.pointerId) return
|
|
3461
|
+
|
|
3462
|
+
const target = document.elementFromPoint(event.clientX, event.clientY)
|
|
3463
|
+
?.closest<HTMLElement>(kanbanColumnSelector)
|
|
3464
|
+
|
|
3465
|
+
if (pointer.active && target) requestMove(pointer.item, target, 'pointer')
|
|
3466
|
+
|
|
3467
|
+
clearState()
|
|
3468
|
+
})
|
|
3469
|
+
|
|
3470
|
+
root.addEventListener('pointercancel', clearState)
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3240
3474
|
const initTreeCollections = (scope: ParentNode): void => {
|
|
3241
3475
|
for (const root of scope.querySelectorAll<HTMLElement>('[role="tree"], [role="treegrid"]')) {
|
|
3242
3476
|
if (root.dataset.uiBound === 'true') continue
|
|
@@ -5252,6 +5486,8 @@ const initUiPrimitives = (scope: ParentNode = document): void => {
|
|
|
5252
5486
|
|
|
5253
5487
|
initSchedules(scope)
|
|
5254
5488
|
|
|
5489
|
+
initKanbanBoards(scope)
|
|
5490
|
+
|
|
5255
5491
|
initTreeCollections(scope)
|
|
5256
5492
|
|
|
5257
5493
|
initVirtualLists(scope)
|