@joewinke/jatui 0.17.1 → 0.18.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.
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
* select, contenteditable) and most modifier combos are skipped so the
|
|
7
7
|
* shortcuts never interfere with typing or app-level keybindings.
|
|
8
8
|
*
|
|
9
|
+
* Focus honesty (mos-5lzns.59): Enter/Space only select the j/k cursor's
|
|
10
|
+
* item when real DOM focus is not on a different activatable control
|
|
11
|
+
* (a link/button/summary/[role=button]). After Tab moves focus elsewhere,
|
|
12
|
+
* Enter activates that control and Space keeps its native meaning instead
|
|
13
|
+
* of triggering the highlighted item.
|
|
14
|
+
*
|
|
9
15
|
* Vim-motion layer (always on):
|
|
10
16
|
*
|
|
11
17
|
* gg jump to first item
|
package/dist/actions/listNav.js
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
* select, contenteditable) and most modifier combos are skipped so the
|
|
7
7
|
* shortcuts never interfere with typing or app-level keybindings.
|
|
8
8
|
*
|
|
9
|
+
* Focus honesty (mos-5lzns.59): Enter/Space only select the j/k cursor's
|
|
10
|
+
* item when real DOM focus is not on a different activatable control
|
|
11
|
+
* (a link/button/summary/[role=button]). After Tab moves focus elsewhere,
|
|
12
|
+
* Enter activates that control and Space keeps its native meaning instead
|
|
13
|
+
* of triggering the highlighted item.
|
|
14
|
+
*
|
|
9
15
|
* Vim-motion layer (always on):
|
|
10
16
|
*
|
|
11
17
|
* gg jump to first item
|
|
@@ -75,6 +81,18 @@ function isTypingTarget(target) {
|
|
|
75
81
|
return true;
|
|
76
82
|
return target.isContentEditable;
|
|
77
83
|
}
|
|
84
|
+
/** Controls that own Enter/Space by real DOM focus. When one of these holds
|
|
85
|
+
* focus (and it is not the focused list item itself), listNav must not
|
|
86
|
+
* consume the key — the browser default (follow the link, press the
|
|
87
|
+
* button, toggle the disclosure) or the page's own handler wins. */
|
|
88
|
+
const ACTIVATABLE_SELECTOR = 'a[href], button, summary, [role="button"]';
|
|
89
|
+
function isActivatable(target) {
|
|
90
|
+
if (typeof HTMLElement === 'undefined')
|
|
91
|
+
return false;
|
|
92
|
+
if (!(target instanceof HTMLElement))
|
|
93
|
+
return false;
|
|
94
|
+
return target.matches(ACTIVATABLE_SELECTOR);
|
|
95
|
+
}
|
|
78
96
|
function hasModifier(e) {
|
|
79
97
|
return e.ctrlKey || e.metaKey || e.altKey;
|
|
80
98
|
}
|
|
@@ -497,6 +515,13 @@ function makeController(optsRef) {
|
|
|
497
515
|
const el = items[focused];
|
|
498
516
|
if (!el)
|
|
499
517
|
return false;
|
|
518
|
+
// Focus honesty (mos-5lzns.59): when real DOM focus sits on a
|
|
519
|
+
// different activatable control (the user Tabbed away from the
|
|
520
|
+
// list), Enter/Space belongs to that control — Enter must follow
|
|
521
|
+
// the link / press the button, and Space must keep scrolling.
|
|
522
|
+
// Bail without consuming the event.
|
|
523
|
+
if (e.target !== el && isActivatable(e.target))
|
|
524
|
+
return false;
|
|
500
525
|
e.preventDefault();
|
|
501
526
|
clearPending();
|
|
502
527
|
opts.onSelect?.(el, focused);
|
|
@@ -7,9 +7,20 @@
|
|
|
7
7
|
* success check animation, error shake → auto-idle.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
11
|
+
|
|
10
12
|
type SubmitState = 'idle' | 'loading' | 'success' | 'error';
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Any other native attribute (aria-*, data-*, name, value, form, title, id,
|
|
16
|
+
* event handlers…) passes straight through to the rendered <button> — or to
|
|
17
|
+
* the <a> when `href` is set. The props below are owned by the component.
|
|
18
|
+
*/
|
|
19
|
+
interface Props
|
|
20
|
+
extends Omit<
|
|
21
|
+
HTMLButtonAttributes,
|
|
22
|
+
'type' | 'disabled' | 'class' | 'onclick' | 'children'
|
|
23
|
+
> {
|
|
13
24
|
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'error' | 'warning' | 'success' | 'info';
|
|
14
25
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
15
26
|
href?: string;
|
|
@@ -48,7 +59,8 @@
|
|
|
48
59
|
pressed,
|
|
49
60
|
class: className = '',
|
|
50
61
|
onclick,
|
|
51
|
-
children
|
|
62
|
+
children,
|
|
63
|
+
...rest
|
|
52
64
|
}: Props = $props();
|
|
53
65
|
|
|
54
66
|
// Merge legacy boolean props into the state machine
|
|
@@ -63,7 +75,10 @@
|
|
|
63
75
|
const isError = $derived(effectiveState === 'error');
|
|
64
76
|
|
|
65
77
|
// Only a provided boolean becomes aria-pressed; undefined omits the attribute.
|
|
66
|
-
|
|
78
|
+
// A raw aria-pressed in the rest props is honoured when `pressed` is omitted.
|
|
79
|
+
const ariaPressed = $derived(
|
|
80
|
+
pressed === undefined ? rest['aria-pressed'] : pressed ? 'true' : 'false'
|
|
81
|
+
);
|
|
67
82
|
|
|
68
83
|
const variantClass = $derived(
|
|
69
84
|
{
|
|
@@ -128,15 +143,23 @@
|
|
|
128
143
|
</script>
|
|
129
144
|
|
|
130
145
|
{#if href}
|
|
131
|
-
<a
|
|
146
|
+
<a
|
|
147
|
+
{...rest as Record<string, unknown>}
|
|
148
|
+
{href}
|
|
149
|
+
class={buttonClass}
|
|
150
|
+
role={rest.role ?? 'button'}
|
|
151
|
+
aria-pressed={ariaPressed}
|
|
152
|
+
{onclick}
|
|
153
|
+
>
|
|
132
154
|
{#if children}{@render children()}{/if}
|
|
133
155
|
</a>
|
|
134
156
|
{:else}
|
|
135
157
|
<button
|
|
158
|
+
{...rest}
|
|
136
159
|
bind:this={btnEl}
|
|
137
160
|
{type}
|
|
138
161
|
disabled={disabled || isLoading}
|
|
139
|
-
aria-busy={isLoading ||
|
|
162
|
+
aria-busy={isLoading || rest['aria-busy']}
|
|
140
163
|
aria-pressed={ariaPressed}
|
|
141
164
|
class={buttonClass}
|
|
142
165
|
{onclick}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
* Button Component
|
|
3
|
+
*
|
|
4
|
+
* Generic DaisyUI button with variant, size, loading state, and optional href.
|
|
5
|
+
* Adds: press squish, submit state machine (idle/loading/success/error),
|
|
6
|
+
* success check animation, error shake → auto-idle.
|
|
7
|
+
*/
|
|
8
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
8
9
|
type SubmitState = 'idle' | 'loading' | 'success' | 'error';
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Any other native attribute (aria-*, data-*, name, value, form, title, id,
|
|
12
|
+
* event handlers…) passes straight through to the rendered <button> — or to
|
|
13
|
+
* the <a> when `href` is set. The props below are owned by the component.
|
|
14
|
+
*/
|
|
15
|
+
interface Props extends Omit<HTMLButtonAttributes, 'type' | 'disabled' | 'class' | 'onclick' | 'children'> {
|
|
10
16
|
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'error' | 'warning' | 'success' | 'info';
|
|
11
17
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
12
18
|
href?: string;
|