@rokkit/actions 1.0.0-next.99 → 1.0.1
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 +189 -1
- package/package.json +20 -31
- package/src/{delegate.js → delegate.svelte.js} +10 -10
- package/src/{dismissable.js → dismissable.svelte.js} +11 -11
- package/src/{fillable.js → fillable.svelte.js} +54 -45
- package/src/hover-lift.svelte.js +64 -0
- package/src/index.js +19 -12
- package/src/kbd.js +191 -0
- package/src/keyboard.svelte.js +59 -0
- package/src/keymap.js +89 -0
- package/src/lib/event-manager.js +35 -18
- package/src/lib/index.js +0 -2
- package/src/lib/internal.js +0 -152
- package/src/magnetic.svelte.js +63 -0
- package/src/nav-constants.js +61 -0
- package/src/navigable.svelte.js +40 -0
- package/src/navigator.js +241 -151
- package/src/navigator.svelte.js +235 -0
- package/src/{pannable.js → pannable.svelte.js} +38 -39
- package/src/reveal.svelte.js +147 -0
- package/src/ripple.svelte.js +92 -0
- package/src/skinnable.svelte.js +12 -0
- package/src/{swipeable.js → swipeable.svelte.js} +79 -89
- package/src/themable.svelte.js +46 -0
- package/src/tooltip.svelte.js +149 -0
- package/src/trigger.js +126 -0
- package/src/types.js +39 -108
- package/src/utils.js +137 -15
- package/LICENSE +0 -21
- package/src/hierarchy.js +0 -156
- package/src/lib/constants.js +0 -35
- package/src/lib/viewport.js +0 -123
- package/src/navigable.js +0 -46
- package/src/switchable.js +0 -52
- package/src/themeable.js +0 -42
- package/src/traversable.js +0 -385
package/README.md
CHANGED
|
@@ -1 +1,189 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @rokkit/actions
|
|
2
|
+
|
|
3
|
+
Svelte actions and DOM utilities for Rokkit components.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rokkit/actions
|
|
9
|
+
# or
|
|
10
|
+
bun add @rokkit/actions
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
`@rokkit/actions` provides the DOM event layer that `@rokkit/ui` components build on. The main export is `Navigator` — a class that wires keyboard, click, and focus events on a container element to a `Wrapper` or `ListController` instance. The package also includes standalone Svelte `use:` actions for effects like ripple, magnetic snap, reveal animations, dismissal on click-outside, and declarative keyboard shortcuts.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
### navigator — keyboard and click navigation
|
|
20
|
+
|
|
21
|
+
The `navigator` action connects a container element's DOM events to a Wrapper controller. It handles `ArrowUp`/`ArrowDown`/`ArrowLeft`/`ArrowRight`, `Home`, `End`, `Enter`, `Space`, typeahead, and click-to-select.
|
|
22
|
+
|
|
23
|
+
```svelte
|
|
24
|
+
<script>
|
|
25
|
+
import { navigator } from '@rokkit/actions'
|
|
26
|
+
import { ProxyTree, Wrapper } from '@rokkit/states'
|
|
27
|
+
|
|
28
|
+
const tree = new ProxyTree(items)
|
|
29
|
+
const wrapper = new Wrapper(tree, { onselect })
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<ul use:navigator={{ controller: wrapper, orientation: 'vertical' }}>
|
|
33
|
+
{#each wrapper.flatView as node (node.key)}
|
|
34
|
+
<li data-path={node.key}>{node.proxy.label}</li>
|
|
35
|
+
{/each}
|
|
36
|
+
</ul>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `data-path` attribute on each item is required — Navigator uses it to resolve which item was clicked or focused.
|
|
40
|
+
|
|
41
|
+
### Navigator class (imperative usage)
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import { Navigator } from '@rokkit/actions'
|
|
45
|
+
|
|
46
|
+
const nav = new Navigator(containerEl, wrapper, {
|
|
47
|
+
orientation: 'vertical', // 'vertical' | 'horizontal'
|
|
48
|
+
collapsible: true // enable expand/collapse key handling
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Clean up when done
|
|
52
|
+
nav.destroy()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### keyboard — declarative shortcut binding
|
|
56
|
+
|
|
57
|
+
```svelte
|
|
58
|
+
<script>
|
|
59
|
+
import { keyboard } from '@rokkit/actions'
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<div
|
|
63
|
+
use:keyboard={{ submit: 'enter', cancel: 'escape' }}
|
|
64
|
+
onsubmit={() => save()}
|
|
65
|
+
oncancel={() => close()}
|
|
66
|
+
>
|
|
67
|
+
...
|
|
68
|
+
</div>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Default mappings: alphabet keys dispatch `add`, Enter dispatches `submit`, Escape dispatches `cancel`, Backspace/Delete dispatch `delete`.
|
|
72
|
+
|
|
73
|
+
### ripple — click ripple effect
|
|
74
|
+
|
|
75
|
+
```svelte
|
|
76
|
+
<script>
|
|
77
|
+
import { ripple } from '@rokkit/actions'
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<button use:ripple>Click me</button>
|
|
81
|
+
|
|
82
|
+
<!-- With options -->
|
|
83
|
+
<button use:ripple={{ color: 'white', opacity: 0.2, duration: 400 }}>Click me</button>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### hoverLift — elevation shadow on hover
|
|
87
|
+
|
|
88
|
+
```svelte
|
|
89
|
+
<script>
|
|
90
|
+
import { hoverLift } from '@rokkit/actions'
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<div use:hoverLift>Card content</div>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### magnetic — snap-to-cursor effect
|
|
97
|
+
|
|
98
|
+
```svelte
|
|
99
|
+
<script>
|
|
100
|
+
import { magnetic } from '@rokkit/actions'
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<button use:magnetic>Hover me</button>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### reveal — intersection observer reveal animation
|
|
107
|
+
|
|
108
|
+
```svelte
|
|
109
|
+
<script>
|
|
110
|
+
import { reveal } from '@rokkit/actions'
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
<section use:reveal>Fades in when scrolled into view</section>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### dismissable — click-outside dismissal
|
|
117
|
+
|
|
118
|
+
```svelte
|
|
119
|
+
<script>
|
|
120
|
+
import { dismissable } from '@rokkit/actions'
|
|
121
|
+
|
|
122
|
+
let open = $state(false)
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<div use:dismissable={{ enabled: open, ondismiss: () => (open = false) }}>Dropdown content</div>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### swipeable — touch swipe detection
|
|
129
|
+
|
|
130
|
+
```svelte
|
|
131
|
+
<script>
|
|
132
|
+
import { swipeable } from '@rokkit/actions'
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<div use:swipeable onswipeleft={() => next()} onswiperight={() => prev()}>Swipeable content</div>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### themable — apply theme CSS variables
|
|
139
|
+
|
|
140
|
+
```svelte
|
|
141
|
+
<script>
|
|
142
|
+
import { themable } from '@rokkit/actions'
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<div use:themable={{ theme: 'ocean' }}>Themed content</div>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## API Reference
|
|
149
|
+
|
|
150
|
+
### Navigator options
|
|
151
|
+
|
|
152
|
+
| Option | Type | Default | Description |
|
|
153
|
+
| ------------- | ---------------------------- | ------------ | ------------------------------------- |
|
|
154
|
+
| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Arrow key axis for prev/next movement |
|
|
155
|
+
| `collapsible` | `boolean` | `false` | Enable expand/collapse via arrow keys |
|
|
156
|
+
|
|
157
|
+
### buildKeymap / resolveAction
|
|
158
|
+
|
|
159
|
+
Low-level utilities for constructing custom keymaps:
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
import { buildKeymap, resolveAction, ACTIONS } from '@rokkit/actions'
|
|
163
|
+
|
|
164
|
+
const keymap = buildKeymap({ orientation: 'vertical', collapsible: true })
|
|
165
|
+
const action = resolveAction(keymap, event) // returns action string or null
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Exports
|
|
169
|
+
|
|
170
|
+
| Export | Type | Description |
|
|
171
|
+
| --------------- | ------------- | ---------------------------------------------- |
|
|
172
|
+
| `Navigator` | Class | DOM event wiring for Wrapper/ListController |
|
|
173
|
+
| `navigator` | Svelte action | `use:navigator` wrapper around Navigator class |
|
|
174
|
+
| `keyboard` | Svelte action | Declarative keyboard shortcut binding |
|
|
175
|
+
| `ripple` | Svelte action | Material Design ink ripple on click |
|
|
176
|
+
| `hoverLift` | Svelte action | Elevation shadow on hover |
|
|
177
|
+
| `magnetic` | Svelte action | Snap-to-cursor magnetic effect |
|
|
178
|
+
| `reveal` | Svelte action | Intersection-observer reveal animation |
|
|
179
|
+
| `dismissable` | Svelte action | Click-outside dismissal |
|
|
180
|
+
| `pannable` | Svelte action | Pan / drag detection |
|
|
181
|
+
| `swipeable` | Svelte action | Touch swipe detection |
|
|
182
|
+
| `themable` | Svelte action | Apply theme CSS vars to element |
|
|
183
|
+
| `buildKeymap` | Function | Build a keymap for given orientation/options |
|
|
184
|
+
| `resolveAction` | Function | Resolve a keyboard event to an action string |
|
|
185
|
+
| `ACTIONS` | Object | Named action constants |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
Part of [Rokkit](https://github.com/jerrythomas/rokkit) — a Svelte 5 component library and design system.
|
package/package.json
CHANGED
|
@@ -1,33 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/actions",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Contains generic actions that can be used in various components.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/jerrythomas/rokkit.git"
|
|
8
|
+
},
|
|
5
9
|
"author": "Jerry Thomas <me@jerrythomas.name>",
|
|
6
10
|
"license": "MIT",
|
|
7
|
-
"main": "index.js",
|
|
8
|
-
"module": "src/index.js",
|
|
9
|
-
"types": "dist/index.d.ts",
|
|
10
11
|
"type": "module",
|
|
11
12
|
"publishConfig": {
|
|
12
13
|
"access": "public"
|
|
13
14
|
},
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"@vitest/ui": "~2.1.1",
|
|
20
|
-
"jsdom": "^25.0.0",
|
|
21
|
-
"svelte": "^4.2.19",
|
|
22
|
-
"typescript": "^5.6.2",
|
|
23
|
-
"vite": "^5.4.6",
|
|
24
|
-
"vitest": "~2.1.1",
|
|
25
|
-
"shared-config": "1.0.0-next.99",
|
|
26
|
-
"validators": "1.0.0-next.99"
|
|
15
|
+
"scripts": {
|
|
16
|
+
"prepublishOnly": "cp ../../LICENSE . && tsc --project tsconfig.build.json",
|
|
17
|
+
"postpublish": "rm -f LICENSE",
|
|
18
|
+
"clean": "rm -rf dist",
|
|
19
|
+
"build": "bun clean && bun prepublishOnly"
|
|
27
20
|
},
|
|
28
21
|
"files": [
|
|
29
22
|
"src/**/*.js",
|
|
30
|
-
"
|
|
23
|
+
"dist/**/*.d.ts",
|
|
24
|
+
"README.md",
|
|
25
|
+
"package.json",
|
|
26
|
+
"LICENSE"
|
|
31
27
|
],
|
|
32
28
|
"exports": {
|
|
33
29
|
"./src": "./src",
|
|
@@ -39,18 +35,11 @@
|
|
|
39
35
|
}
|
|
40
36
|
},
|
|
41
37
|
"dependencies": {
|
|
42
|
-
"ramda": "^0.
|
|
43
|
-
"@rokkit/core": "
|
|
44
|
-
"@rokkit/stores": "1.0.0-next.99"
|
|
38
|
+
"ramda": "^0.32.0",
|
|
39
|
+
"@rokkit/core": "latest"
|
|
45
40
|
},
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"test:ci": "vitest run",
|
|
50
|
-
"test:ui": "vitest --ui",
|
|
51
|
-
"test": "vitest",
|
|
52
|
-
"coverage": "vitest run --coverage",
|
|
53
|
-
"latest": "pnpm upgrade --latest && pnpm test:ci",
|
|
54
|
-
"release": "pnpm publish --access public"
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@rokkit/helpers": "latest",
|
|
43
|
+
"@rokkit/states": "latest"
|
|
55
44
|
}
|
|
56
|
-
}
|
|
45
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { EventManager } from './lib'
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* Svelte action function for forwarding keyboard events from a parent element to a child.
|
|
4
5
|
* The child is selected using a CSS selector passed in the options object.
|
|
5
6
|
* Optionally, you can specify which keyboard events you want to forward: "keydown", "keyup", and/or "keypress".
|
|
6
7
|
* By default, all three events are forwarded.
|
|
7
|
-
* The action returns an object with a destroy method.
|
|
8
|
-
* The destroy method removes all event listeners from the parent.
|
|
9
8
|
*
|
|
10
9
|
* @param {HTMLElement} element - The parent element from which keyboard events will be forwarded.
|
|
11
10
|
* @param {import('./types').PushDownOptions} options - The options object.
|
|
@@ -23,12 +22,13 @@ export function delegateKeyboardEvents(
|
|
|
23
22
|
child.dispatchEvent(new KeyboardEvent(event.type, event))
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
$effect(() => {
|
|
26
|
+
if (child) {
|
|
27
|
+
events.forEach((event) => {
|
|
28
|
+
handlers[event] = forwardEvent
|
|
29
|
+
})
|
|
30
|
+
manager.update(handlers)
|
|
31
|
+
}
|
|
32
|
+
return () => manager.reset()
|
|
33
|
+
})
|
|
34
34
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { on } from 'svelte/events'
|
|
1
2
|
const KEYCODE_ESC = 27
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -5,29 +6,28 @@ const KEYCODE_ESC = 27
|
|
|
5
6
|
* emits a `dismiss` event. This is useful for closing a modal or dropdown.
|
|
6
7
|
*
|
|
7
8
|
* @param {HTMLElement} node
|
|
8
|
-
* @returns {import('./types').SvelteActionReturn}
|
|
9
9
|
*/
|
|
10
10
|
export function dismissable(node) {
|
|
11
11
|
const handleClick = (event) => {
|
|
12
12
|
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
|
13
|
-
node.dispatchEvent(new CustomEvent('dismiss'
|
|
13
|
+
node.dispatchEvent(new CustomEvent('dismiss'))
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
+
|
|
16
17
|
const keyup = (event) => {
|
|
17
18
|
if (event.keyCode === KEYCODE_ESC || event.key === 'Escape') {
|
|
18
19
|
event.stopPropagation()
|
|
19
|
-
|
|
20
|
-
node.dispatchEvent(new CustomEvent('dismiss', node))
|
|
20
|
+
node.dispatchEvent(new CustomEvent('dismiss', { detail: node }))
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
$effect(() => {
|
|
25
|
+
const cleanupClickEvent = on(document, 'click', handleClick)
|
|
26
|
+
const cleanupKeyupEvent = on(document, 'keyup', keyup)
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
document.removeEventListener('keyup', keyup, true)
|
|
28
|
+
return () => {
|
|
29
|
+
cleanupClickEvent()
|
|
30
|
+
cleanupKeyupEvent()
|
|
31
31
|
}
|
|
32
|
-
}
|
|
32
|
+
})
|
|
33
33
|
}
|
|
@@ -1,39 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Action for filling a <del>?</del> element in html block.
|
|
3
|
-
*
|
|
4
|
-
* @param {HTMLElement} node
|
|
5
|
-
* @param {import('./types').FillOptions} options
|
|
6
|
-
* @returns
|
|
7
|
-
*/
|
|
8
|
-
export function fillable(node, { options, current, check }) {
|
|
9
|
-
const data = { options, current, check }
|
|
10
|
-
const blanks = node.getElementsByTagName('del')
|
|
11
|
-
|
|
12
|
-
function click(event) {
|
|
13
|
-
if (event.target.innerHTML !== '?') {
|
|
14
|
-
clear(event, node)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
initialize(blanks, click)
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
update(input) {
|
|
22
|
-
data.options = input.options
|
|
23
|
-
data.current = input.current
|
|
24
|
-
data.check = check
|
|
25
|
-
|
|
26
|
-
fill(blanks, data.options, data.current)
|
|
27
|
-
if (data.check) validate(blanks, data)
|
|
28
|
-
},
|
|
29
|
-
destroy() {
|
|
30
|
-
Object.keys(blanks).forEach((ref) => {
|
|
31
|
-
blanks[ref].removeEventListener('click', click)
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
1
|
+
import { on } from 'svelte/events'
|
|
37
2
|
/**
|
|
38
3
|
* Initialize empty fillable element style and add listener for click
|
|
39
4
|
*
|
|
@@ -41,12 +6,16 @@ export function fillable(node, { options, current, check }) {
|
|
|
41
6
|
* @param {EventListener} click
|
|
42
7
|
*/
|
|
43
8
|
function initialize(blanks, click) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
9
|
+
const registry = []
|
|
10
|
+
Array.from(blanks).forEach((blank, ref) => {
|
|
11
|
+
blank.innerHTML = '?'
|
|
12
|
+
blank.classList.add('empty')
|
|
13
|
+
blank.name = `fill-${ref}`
|
|
14
|
+
blank['data-index'] = ref
|
|
15
|
+
const cleanup = on(blank, 'click', click)
|
|
16
|
+
registry.push(cleanup)
|
|
49
17
|
})
|
|
18
|
+
return registry
|
|
50
19
|
}
|
|
51
20
|
|
|
52
21
|
/**
|
|
@@ -56,13 +25,21 @@ function initialize(blanks, click) {
|
|
|
56
25
|
* @param {Array<import('./types.js').FillableData>} options
|
|
57
26
|
* @param {*} current
|
|
58
27
|
*/
|
|
59
|
-
function fill(blanks, options, current) {
|
|
28
|
+
function fill(blanks, { options, current }, node) {
|
|
60
29
|
if (current > -1 && current < Object.keys(blanks).length) {
|
|
61
30
|
const index = options.findIndex(({ actualIndex }) => actualIndex === current)
|
|
62
31
|
if (index > -1) {
|
|
63
32
|
blanks[current].innerHTML = options[index].value
|
|
64
33
|
blanks[current].classList.remove('empty')
|
|
65
34
|
blanks[current].classList.add('filled')
|
|
35
|
+
node.dispatchEvent(
|
|
36
|
+
new CustomEvent('fill', {
|
|
37
|
+
detail: {
|
|
38
|
+
index: current,
|
|
39
|
+
value: options[index].value
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
)
|
|
66
43
|
}
|
|
67
44
|
}
|
|
68
45
|
}
|
|
@@ -73,17 +50,19 @@ function fill(blanks, options, current) {
|
|
|
73
50
|
* @param {EventListener} event
|
|
74
51
|
* @param {HTMLElement} node
|
|
75
52
|
*/
|
|
76
|
-
function clear(event, node) {
|
|
53
|
+
function clear(event, node, options) {
|
|
54
|
+
const item = options.find(({ value }) => value === event.target.innerHTML)
|
|
77
55
|
event.target.innerHTML = '?'
|
|
78
56
|
event.target.classList.remove('filled')
|
|
79
57
|
event.target.classList.remove('pass')
|
|
80
58
|
event.target.classList.remove('fail')
|
|
81
59
|
event.target.classList.add('empty')
|
|
60
|
+
|
|
82
61
|
node.dispatchEvent(
|
|
83
62
|
new CustomEvent('remove', {
|
|
84
63
|
detail: {
|
|
85
|
-
index: event.target
|
|
86
|
-
value:
|
|
64
|
+
index: event.target['data-index'],
|
|
65
|
+
value: item.value
|
|
87
66
|
}
|
|
88
67
|
})
|
|
89
68
|
)
|
|
@@ -104,3 +83,33 @@ function validate(blanks, data) {
|
|
|
104
83
|
)
|
|
105
84
|
})
|
|
106
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Action for filling a <del>?</del> element in html block.
|
|
89
|
+
*
|
|
90
|
+
* @param {HTMLElement} node
|
|
91
|
+
* @param {import('./types').FillOptions} options
|
|
92
|
+
* @returns
|
|
93
|
+
*/
|
|
94
|
+
export function fillable(node, data) {
|
|
95
|
+
const blanks = node.getElementsByTagName('del')
|
|
96
|
+
|
|
97
|
+
function click(event) {
|
|
98
|
+
if (event.target.innerHTML !== '?') {
|
|
99
|
+
clear(event, node, data.options)
|
|
100
|
+
} else {
|
|
101
|
+
data.current = event.target['data-index']
|
|
102
|
+
fill(blanks, data, node)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
$effect(() => {
|
|
107
|
+
const registry = initialize(blanks, click)
|
|
108
|
+
|
|
109
|
+
if (data.check) validate(blanks, data)
|
|
110
|
+
|
|
111
|
+
return () => {
|
|
112
|
+
registry.forEach((cleanup) => cleanup())
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hover lift action — adds translateY + elevated shadow on hover.
|
|
3
|
+
* Sets transition on mount, applies transform + box-shadow on mouseenter, resets on mouseleave.
|
|
4
|
+
*
|
|
5
|
+
* @param {HTMLElement} node
|
|
6
|
+
* @param {HoverLiftOptions} [options]
|
|
7
|
+
*
|
|
8
|
+
* @typedef {Object} HoverLiftOptions
|
|
9
|
+
* @property {string} [distance='-0.25rem'] Translate distance on hover (negative = up)
|
|
10
|
+
* @property {string} [shadow='0 10px 25px -5px rgba(0,0,0,0.1)'] Box shadow on hover
|
|
11
|
+
* @property {number} [duration=200] Transition duration (ms)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
function resolveHoverLiftOpts(options) {
|
|
15
|
+
return {
|
|
16
|
+
distance: '-0.25rem',
|
|
17
|
+
shadow: '0 10px 25px -5px rgba(0,0,0,0.1)',
|
|
18
|
+
duration: 200,
|
|
19
|
+
...options
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isReducedMotion() {
|
|
24
|
+
return (
|
|
25
|
+
typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function applyHoverLift(node, opts) {
|
|
30
|
+
const originalTransform = node.style.transform
|
|
31
|
+
const originalBoxShadow = node.style.boxShadow
|
|
32
|
+
const originalTransition = node.style.transition
|
|
33
|
+
|
|
34
|
+
node.style.transition = `transform ${opts.duration}ms ease, box-shadow ${opts.duration}ms ease`
|
|
35
|
+
|
|
36
|
+
function onEnter() {
|
|
37
|
+
node.style.transform = `translateY(${opts.distance})`
|
|
38
|
+
node.style.boxShadow = opts.shadow
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function onLeave() {
|
|
42
|
+
node.style.transform = originalTransform
|
|
43
|
+
node.style.boxShadow = originalBoxShadow
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
node.addEventListener('mouseenter', onEnter)
|
|
47
|
+
node.addEventListener('mouseleave', onLeave)
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
node.removeEventListener('mouseenter', onEnter)
|
|
51
|
+
node.removeEventListener('mouseleave', onLeave)
|
|
52
|
+
node.style.transform = originalTransform
|
|
53
|
+
node.style.boxShadow = originalBoxShadow
|
|
54
|
+
node.style.transition = originalTransition
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function hoverLift(node, options = {}) {
|
|
59
|
+
$effect(() => {
|
|
60
|
+
const opts = resolveHoverLiftOpts(options)
|
|
61
|
+
if (isReducedMotion()) return
|
|
62
|
+
return applyHoverLift(node, opts)
|
|
63
|
+
})
|
|
64
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
// skipcq: JS-E1004 - Needed for exposing all types
|
|
2
|
-
export * from './types'
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export { themable } from './
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
2
|
+
export * from './types.js'
|
|
3
|
+
export { Navigator } from './navigator.js'
|
|
4
|
+
export { Trigger } from './trigger.js'
|
|
5
|
+
export { buildKeymap, resolveAction, ACTIONS } from './keymap.js'
|
|
6
|
+
export { keyboard } from './keyboard.svelte.js'
|
|
7
|
+
export { pannable } from './pannable.svelte.js'
|
|
8
|
+
export { swipeable } from './swipeable.svelte.js'
|
|
9
|
+
export { navigator } from './navigator.svelte.js'
|
|
10
|
+
export { themable } from './themable.svelte.js'
|
|
11
|
+
export { skinnable } from './skinnable.svelte.js'
|
|
12
|
+
export { dismissable } from './dismissable.svelte.js'
|
|
13
|
+
export { navigable } from './navigable.svelte.js'
|
|
14
|
+
export { fillable } from './fillable.svelte.js'
|
|
15
|
+
export { delegateKeyboardEvents } from './delegate.svelte.js'
|
|
16
|
+
export { reveal } from './reveal.svelte.js'
|
|
17
|
+
export { hoverLift } from './hover-lift.svelte.js'
|
|
18
|
+
export { magnetic } from './magnetic.svelte.js'
|
|
19
|
+
export { ripple } from './ripple.svelte.js'
|
|
20
|
+
export { tooltip } from './tooltip.svelte.js'
|