@ctrliq/quantic-components 1.83.0 → 1.84.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/dist/action-group/action-group.component.d.ts +13 -0
- package/dist/action-group/action-group.component.js +63 -0
- package/dist/action-group/index.d.ts +1 -0
- package/dist/action-group/index.js +1 -0
- package/dist/action-group/index.stories.d.ts +25 -0
- package/dist/action-group/index.stories.js +59 -0
- package/dist/astro.d.ts +4 -1
- package/dist/astro.js +4 -1
- package/dist/astro.js.map +1 -1
- package/dist/badge/badge-group.component.d.ts +13 -0
- package/dist/badge/badge-group.component.js +62 -0
- package/dist/badge/badge-group.stories.d.ts +41 -0
- package/dist/badge/badge-group.stories.js +62 -0
- package/dist/badge/index.d.ts +1 -0
- package/dist/badge/index.js +1 -0
- package/dist/button-bar/button-bar-item.d.ts +3 -0
- package/dist/button-bar/button-bar-item.js +12 -1
- package/dist/button-bar/button-bar.d.ts +9 -0
- package/dist/button-bar/button-bar.js +53 -2
- package/dist/button-bar/index.stories.js +2 -2
- package/dist/button-bar/index.utils.d.ts +6 -0
- package/dist/button-bar/index.utils.js +31 -0
- package/dist/button-bar/index.utils.test.d.ts +1 -0
- package/dist/button-bar/index.utils.test.js +53 -0
- package/dist/button-group/button-group.js +1 -2
- package/dist/dialog/dialog-actions.js +11 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/meta/index.js +6 -0
- package/dist/meta/index.js.map +1 -1
- package/dist/meta.json +110 -5
- package/dist/quantic-components.js +266 -15
- package/dist/quantic-components.js.map +1 -1
- package/dist/quantic-components.min.js +163 -109
- package/dist/quantic-components.min.js.map +1 -1
- package/dist/register.js.map +1 -1
- package/dist/tag/index.d.ts +1 -0
- package/dist/tag/index.js +1 -0
- package/dist/tag/tag-group.component.d.ts +13 -0
- package/dist/tag/tag-group.component.js +62 -0
- package/dist/tag/tag-group.stories.d.ts +34 -0
- package/dist/tag/tag-group.stories.js +65 -0
- package/dist/types/action-group/action-group.component.d.ts +13 -0
- package/dist/types/action-group/index.d.ts +1 -0
- package/dist/types/action-group/index.stories.d.ts +25 -0
- package/dist/types/astro.d.ts +4 -1
- package/dist/types/badge/badge-group.component.d.ts +13 -0
- package/dist/types/badge/badge-group.stories.d.ts +41 -0
- package/dist/types/badge/index.d.ts +1 -0
- package/dist/types/button-bar/button-bar-item.d.ts +3 -0
- package/dist/types/button-bar/button-bar.d.ts +9 -0
- package/dist/types/button-bar/index.utils.d.ts +6 -0
- package/dist/types/button-bar/index.utils.test.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/tag/index.d.ts +1 -0
- package/dist/types/tag/tag-group.component.d.ts +13 -0
- package/dist/types/tag/tag-group.stories.d.ts +34 -0
- package/package.json +4 -3
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { QuanticElement } from '../shared/quantic-element';
|
|
2
|
+
import type { ComponentMeta } from '../shared/meta.types';
|
|
3
|
+
export declare class BadgeGroup extends QuanticElement {
|
|
4
|
+
static meta: ComponentMeta;
|
|
5
|
+
ariaLabel: string | null;
|
|
6
|
+
static styles: import("lit").CSSResult;
|
|
7
|
+
render(): import("lit").TemplateResult<1>;
|
|
8
|
+
}
|
|
9
|
+
declare global {
|
|
10
|
+
interface HTMLElementTagNameMap {
|
|
11
|
+
'quantic-badge-group': BadgeGroup;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { css, html } from 'lit';
|
|
8
|
+
import { QuanticElement, quanticElement } from '../shared/quantic-element';
|
|
9
|
+
import { prop } from '../shared/component-meta.decorators';
|
|
10
|
+
let BadgeGroup = class BadgeGroup extends QuanticElement {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.ariaLabel = null;
|
|
14
|
+
}
|
|
15
|
+
render() {
|
|
16
|
+
return html `
|
|
17
|
+
<div class="group" role="group" aria-label=${this.ariaLabel ?? 'Badges'}>
|
|
18
|
+
<slot></slot>
|
|
19
|
+
</div>
|
|
20
|
+
`;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
BadgeGroup.meta = {
|
|
24
|
+
tag: 'quantic-badge-group',
|
|
25
|
+
description: 'A run of quantic-badge or quantic-status-badge sharing one accessible name and a consistent gap. ' +
|
|
26
|
+
'Wraps across lines, and each badge keeps its own size. Gap: --quantic-component-badge-group-gap. ' +
|
|
27
|
+
'Use quantic-tag-group for tags.',
|
|
28
|
+
category: 'display',
|
|
29
|
+
slots: {
|
|
30
|
+
'': {
|
|
31
|
+
description: 'quantic-badge or quantic-status-badge elements.',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
subComponents: ['quantic-badge', 'quantic-status-badge'],
|
|
35
|
+
relatedTo: ['quantic-badge', 'quantic-tag-group'],
|
|
36
|
+
};
|
|
37
|
+
BadgeGroup.styles = css `
|
|
38
|
+
:host {
|
|
39
|
+
display: block;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.group {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-wrap: wrap;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: var(
|
|
47
|
+
--quantic-component-badge-group-gap,
|
|
48
|
+
var(--quantic-spacing-1)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
__decorate([
|
|
53
|
+
prop({
|
|
54
|
+
type: 'string',
|
|
55
|
+
attribute: 'aria-label',
|
|
56
|
+
description: 'Accessible name for the group.',
|
|
57
|
+
})
|
|
58
|
+
], BadgeGroup.prototype, "ariaLabel", void 0);
|
|
59
|
+
BadgeGroup = __decorate([
|
|
60
|
+
quanticElement('quantic-badge-group')
|
|
61
|
+
], BadgeGroup);
|
|
62
|
+
export { BadgeGroup };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BadgeColor, BadgeSize } from './index.constants';
|
|
2
|
+
interface Args {
|
|
3
|
+
label: string | undefined;
|
|
4
|
+
size: BadgeSize;
|
|
5
|
+
color: BadgeColor;
|
|
6
|
+
badges: string[];
|
|
7
|
+
}
|
|
8
|
+
declare const _default: {
|
|
9
|
+
title: string;
|
|
10
|
+
component: string;
|
|
11
|
+
tags: string[];
|
|
12
|
+
argTypes: {
|
|
13
|
+
label: {
|
|
14
|
+
control: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
size: {
|
|
18
|
+
control: {
|
|
19
|
+
type: string;
|
|
20
|
+
};
|
|
21
|
+
options: ("xs" | "sm" | "md" | "lg")[];
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
color: {
|
|
25
|
+
control: {
|
|
26
|
+
type: string;
|
|
27
|
+
};
|
|
28
|
+
options: ("success" | "error" | "warning" | "gray" | "brand" | "gray-blue" | "blue-light" | "blue" | "indigo" | "purple" | "pink" | "orange")[];
|
|
29
|
+
description: string;
|
|
30
|
+
};
|
|
31
|
+
badges: {
|
|
32
|
+
control: string;
|
|
33
|
+
description: string;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export default _default;
|
|
38
|
+
export declare const Default: import("../shared/story").StoryObject<Args>;
|
|
39
|
+
export declare const WithAccessibleName: import("../shared/story").StoryObject<Args>;
|
|
40
|
+
export declare const Wrapping: import("../shared/story").StoryObject<Args>;
|
|
41
|
+
export declare const StatusBadges: import("../shared/story").StoryObject<object>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { story } from '../shared/story';
|
|
2
|
+
import { BadgeColor, BadgeSize } from './index.constants';
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Components/Display/Badge/BadgeGroup',
|
|
5
|
+
component: 'quantic-badge-group',
|
|
6
|
+
tags: ['autodocs'],
|
|
7
|
+
argTypes: {
|
|
8
|
+
label: {
|
|
9
|
+
control: 'text',
|
|
10
|
+
description: 'Accessible name for the group. Defaults to "Badges".',
|
|
11
|
+
},
|
|
12
|
+
size: {
|
|
13
|
+
control: { type: 'select' },
|
|
14
|
+
options: Object.values(BadgeSize),
|
|
15
|
+
description: 'Size set on each badge. The group does not own sizing.',
|
|
16
|
+
},
|
|
17
|
+
color: {
|
|
18
|
+
control: { type: 'select' },
|
|
19
|
+
options: Object.values(BadgeColor),
|
|
20
|
+
description: 'Color set on each badge',
|
|
21
|
+
},
|
|
22
|
+
badges: {
|
|
23
|
+
control: 'object',
|
|
24
|
+
description: 'Badge labels',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const base = story({
|
|
29
|
+
args: {
|
|
30
|
+
label: undefined,
|
|
31
|
+
size: BadgeSize.Small,
|
|
32
|
+
color: BadgeColor.Gray,
|
|
33
|
+
badges: ['Backgrounds', 'Email signatures', 'Headers'],
|
|
34
|
+
},
|
|
35
|
+
}).render(({ label, size, color, badges }) => {
|
|
36
|
+
const labelAttr = label === undefined ? '' : `aria-label="${label}"`;
|
|
37
|
+
const items = badges
|
|
38
|
+
.map((badge) => `<quantic-badge color="${color}" size="${size}">${badge}</quantic-badge>`)
|
|
39
|
+
.join('');
|
|
40
|
+
return `<quantic-badge-group ${labelAttr}>${items}</quantic-badge-group>`;
|
|
41
|
+
});
|
|
42
|
+
export const Default = base;
|
|
43
|
+
export const WithAccessibleName = base.override({ label: 'Asset types' });
|
|
44
|
+
// No gap, axis or wrap props on purpose: the rhythm is a decision, not a knob.
|
|
45
|
+
export const Wrapping = base.override({
|
|
46
|
+
badges: [
|
|
47
|
+
'Backgrounds',
|
|
48
|
+
'Email signatures',
|
|
49
|
+
'Headers',
|
|
50
|
+
'Social graphics',
|
|
51
|
+
'Slide templates',
|
|
52
|
+
'Press kits',
|
|
53
|
+
'Advisory cards',
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
export const StatusBadges = story().render(() => `
|
|
57
|
+
<quantic-badge-group aria-label="Service status">
|
|
58
|
+
<quantic-status-badge color="success" size="sm">Running</quantic-status-badge>
|
|
59
|
+
<quantic-status-badge color="warning" size="sm">Degraded</quantic-status-badge>
|
|
60
|
+
<quantic-status-badge color="error" size="sm">Offline</quantic-status-badge>
|
|
61
|
+
</quantic-badge-group>
|
|
62
|
+
`);
|
package/dist/badge/index.d.ts
CHANGED
package/dist/badge/index.js
CHANGED
|
@@ -4,6 +4,9 @@ import type { ComponentMeta } from '../shared/meta.types';
|
|
|
4
4
|
export declare class ButtonBarItem extends QuanticElement {
|
|
5
5
|
static meta: ComponentMeta;
|
|
6
6
|
size?: ButtonBarSize;
|
|
7
|
+
tabbable: boolean;
|
|
8
|
+
private innerButton?;
|
|
9
|
+
focus(options?: FocusOptions): void;
|
|
7
10
|
disabled: boolean;
|
|
8
11
|
active: boolean;
|
|
9
12
|
static styles: import("lit").CSSResult;
|
|
@@ -5,6 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { css, html } from 'lit';
|
|
8
|
+
import { query, state } from 'lit/decorators.js';
|
|
8
9
|
import { consume } from '@lit/context';
|
|
9
10
|
import { classMap } from 'lit/directives/class-map.js';
|
|
10
11
|
import { sizeContext } from './index.context';
|
|
@@ -13,9 +14,13 @@ import { prop } from '../shared/component-meta.decorators';
|
|
|
13
14
|
let ButtonBarItem = class ButtonBarItem extends QuanticElement {
|
|
14
15
|
constructor() {
|
|
15
16
|
super(...arguments);
|
|
17
|
+
this.tabbable = true;
|
|
16
18
|
this.disabled = false;
|
|
17
19
|
this.active = false;
|
|
18
20
|
}
|
|
21
|
+
focus(options) {
|
|
22
|
+
this.innerButton?.focus(options);
|
|
23
|
+
}
|
|
19
24
|
render() {
|
|
20
25
|
const classes = classMap({
|
|
21
26
|
main: true,
|
|
@@ -26,7 +31,7 @@ let ButtonBarItem = class ButtonBarItem extends QuanticElement {
|
|
|
26
31
|
<button
|
|
27
32
|
type="button"
|
|
28
33
|
class=${classes}
|
|
29
|
-
tabindex=${this.disabled ? -1 : 0}
|
|
34
|
+
tabindex=${this.disabled || !this.tabbable ? -1 : 0}
|
|
30
35
|
?disabled=${this.disabled}
|
|
31
36
|
aria-pressed=${this.active ? 'true' : 'false'}
|
|
32
37
|
>
|
|
@@ -169,6 +174,12 @@ ButtonBarItem.styles = css `
|
|
|
169
174
|
__decorate([
|
|
170
175
|
consume({ context: sizeContext, subscribe: true })
|
|
171
176
|
], ButtonBarItem.prototype, "size", void 0);
|
|
177
|
+
__decorate([
|
|
178
|
+
state()
|
|
179
|
+
], ButtonBarItem.prototype, "tabbable", void 0);
|
|
180
|
+
__decorate([
|
|
181
|
+
query('button')
|
|
182
|
+
], ButtonBarItem.prototype, "innerButton", void 0);
|
|
172
183
|
__decorate([
|
|
173
184
|
prop({
|
|
174
185
|
type: 'boolean',
|
|
@@ -5,6 +5,15 @@ export declare class ButtonBar extends QuanticElement {
|
|
|
5
5
|
static meta: ComponentMeta;
|
|
6
6
|
size: ButtonBarSize;
|
|
7
7
|
ariaLabel: string | null;
|
|
8
|
+
private disabledObserver?;
|
|
9
|
+
private hasSynced;
|
|
10
|
+
connectedCallback(): void;
|
|
11
|
+
disconnectedCallback(): void;
|
|
12
|
+
private get items();
|
|
13
|
+
private setFocusStop;
|
|
14
|
+
private syncFocusStop;
|
|
15
|
+
private handleFocusIn;
|
|
16
|
+
private handleKeydown;
|
|
8
17
|
static styles: import("lit").CSSResult;
|
|
9
18
|
protected defaultLabel: string;
|
|
10
19
|
render(): import("lit").TemplateResult<1>;
|
|
@@ -10,21 +10,71 @@ import { ButtonBarSize } from './index.constants';
|
|
|
10
10
|
import { sizeContext } from './index.context';
|
|
11
11
|
import { quanticElement, QuanticElement } from '../shared/quantic-element';
|
|
12
12
|
import { prop } from '../shared/component-meta.decorators';
|
|
13
|
+
import { nextFocusIndex, resolveFocusStop } from './index.utils';
|
|
14
|
+
const ITEM_TAGS = 'quantic-button-bar-item, quantic-button-group-item';
|
|
13
15
|
let ButtonBar = class ButtonBar extends QuanticElement {
|
|
14
16
|
constructor() {
|
|
15
17
|
super(...arguments);
|
|
16
18
|
this.size = ButtonBarSize.Medium;
|
|
17
19
|
this.ariaLabel = null;
|
|
20
|
+
this.hasSynced = false;
|
|
18
21
|
this.defaultLabel = 'Button bar';
|
|
19
22
|
}
|
|
23
|
+
connectedCallback() {
|
|
24
|
+
super.connectedCallback();
|
|
25
|
+
this.disabledObserver = new MutationObserver(() => this.syncFocusStop());
|
|
26
|
+
this.disabledObserver.observe(this, {
|
|
27
|
+
attributeFilter: ['disabled'],
|
|
28
|
+
subtree: true,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
disconnectedCallback() {
|
|
32
|
+
super.disconnectedCallback();
|
|
33
|
+
this.disabledObserver?.disconnect();
|
|
34
|
+
this.disabledObserver = undefined;
|
|
35
|
+
}
|
|
36
|
+
get items() {
|
|
37
|
+
return [...this.querySelectorAll(ITEM_TAGS)].filter((item) => item.parentElement === this && !item.disabled);
|
|
38
|
+
}
|
|
39
|
+
setFocusStop(items, index) {
|
|
40
|
+
items.forEach((item, position) => {
|
|
41
|
+
item.tabbable = position === index;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
syncFocusStop() {
|
|
45
|
+
const items = this.items;
|
|
46
|
+
const stop = resolveFocusStop(items, this.hasSynced);
|
|
47
|
+
if (stop === null)
|
|
48
|
+
return;
|
|
49
|
+
this.hasSynced = true;
|
|
50
|
+
this.setFocusStop(items, stop);
|
|
51
|
+
}
|
|
52
|
+
handleFocusIn(event) {
|
|
53
|
+
const items = this.items;
|
|
54
|
+
const focused = items.findIndex((item) => item.contains(event.target));
|
|
55
|
+
if (focused !== -1)
|
|
56
|
+
this.setFocusStop(items, focused);
|
|
57
|
+
}
|
|
58
|
+
handleKeydown(event) {
|
|
59
|
+
const items = this.items;
|
|
60
|
+
const current = items.findIndex((item) => item.contains(event.target));
|
|
61
|
+
const next = nextFocusIndex(event.key, current, items.length);
|
|
62
|
+
if (next === null)
|
|
63
|
+
return;
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
this.setFocusStop(items, next);
|
|
66
|
+
items[next].focus();
|
|
67
|
+
}
|
|
20
68
|
render() {
|
|
21
69
|
return html `
|
|
22
70
|
<div
|
|
23
71
|
class="container"
|
|
24
72
|
role="toolbar"
|
|
25
|
-
aria-label=${this.ariaLabel
|
|
73
|
+
aria-label=${this.ariaLabel ?? this.defaultLabel}
|
|
74
|
+
@keydown=${this.handleKeydown}
|
|
75
|
+
@focusin=${this.handleFocusIn}
|
|
26
76
|
>
|
|
27
|
-
<slot></slot>
|
|
77
|
+
<slot @slotchange=${() => this.syncFocusStop()}></slot>
|
|
28
78
|
</div>
|
|
29
79
|
`;
|
|
30
80
|
}
|
|
@@ -34,6 +84,7 @@ ButtonBar.meta = {
|
|
|
34
84
|
description: 'Toolbar of related buttons joined into one cohesive control, with shared sizing. ' +
|
|
35
85
|
'Use for switching between views or modes, and for a compact run of icon actions. ' +
|
|
36
86
|
'Items carry aria-pressed via active, so a set can behave as exclusive options or as independent toggles. ' +
|
|
87
|
+
'Focus follows the toolbar pattern: one tab stop, arrow keys move between items, Home and End jump to the ends. ' +
|
|
37
88
|
'Prefer quantic-button for a standalone action, and quantic-tabs when each segment reveals a panel.',
|
|
38
89
|
category: 'action',
|
|
39
90
|
slots: { '': { description: 'One or more quantic-button-bar-item elements.' } },
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { story } from '../shared/story';
|
|
2
2
|
import { ButtonBarSize } from './index.constants';
|
|
3
3
|
export default {
|
|
4
|
-
title: 'Components/Actions/Button
|
|
4
|
+
title: 'Components/Actions/Button Bar',
|
|
5
5
|
component: 'quantic-button-bar',
|
|
6
6
|
tags: ['autodocs'],
|
|
7
7
|
argTypes: {
|
|
8
8
|
size: {
|
|
9
9
|
control: { type: 'select' },
|
|
10
10
|
options: Object.values(ButtonBarSize),
|
|
11
|
-
description: 'The size of the button
|
|
11
|
+
description: 'The size of the button bar',
|
|
12
12
|
},
|
|
13
13
|
items: {
|
|
14
14
|
control: 'object',
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface FocusStopCandidate {
|
|
2
|
+
tabbable: boolean;
|
|
3
|
+
active: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function resolveFocusStop(items: FocusStopCandidate[], hasSynced: boolean): number | null;
|
|
6
|
+
export declare function nextFocusIndex(key: string, current: number, count: number): number | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const KEY_STEPS = {
|
|
2
|
+
ArrowRight: 1,
|
|
3
|
+
ArrowDown: 1,
|
|
4
|
+
ArrowLeft: -1,
|
|
5
|
+
ArrowUp: -1,
|
|
6
|
+
Home: 'first',
|
|
7
|
+
End: 'last',
|
|
8
|
+
};
|
|
9
|
+
export function resolveFocusStop(items, hasSynced) {
|
|
10
|
+
if (!items.length)
|
|
11
|
+
return null;
|
|
12
|
+
if (hasSynced) {
|
|
13
|
+
const tabbable = items.findIndex((item) => item.tabbable);
|
|
14
|
+
if (tabbable !== -1)
|
|
15
|
+
return tabbable;
|
|
16
|
+
}
|
|
17
|
+
const active = items.findIndex((item) => item.active);
|
|
18
|
+
return active === -1 ? 0 : active;
|
|
19
|
+
}
|
|
20
|
+
export function nextFocusIndex(key, current, count) {
|
|
21
|
+
const step = KEY_STEPS[key];
|
|
22
|
+
if (step === undefined)
|
|
23
|
+
return null;
|
|
24
|
+
if (count < 2 || current < 0 || current >= count)
|
|
25
|
+
return null;
|
|
26
|
+
if (step === 'first')
|
|
27
|
+
return 0;
|
|
28
|
+
if (step === 'last')
|
|
29
|
+
return count - 1;
|
|
30
|
+
return (current + step + count) % count;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { nextFocusIndex, resolveFocusStop } from './index.utils';
|
|
3
|
+
const item = (tabbable, active = false) => ({ tabbable, active });
|
|
4
|
+
describe('resolveFocusStop', () => {
|
|
5
|
+
it('starts on the active item', () => {
|
|
6
|
+
expect(resolveFocusStop([item(true), item(true, true), item(true)], false)).toBe(1);
|
|
7
|
+
});
|
|
8
|
+
it('starts on the first item when none is active', () => {
|
|
9
|
+
expect(resolveFocusStop([item(true), item(true)], false)).toBe(0);
|
|
10
|
+
});
|
|
11
|
+
it('keeps the existing stop once synced, so a resync does not move focus', () => {
|
|
12
|
+
expect(resolveFocusStop([item(false), item(true), item(false, true)], true)).toBe(1);
|
|
13
|
+
});
|
|
14
|
+
it('rehomes the stop when the item holding it is gone', () => {
|
|
15
|
+
expect(resolveFocusStop([item(false), item(false, true)], true)).toBe(1);
|
|
16
|
+
expect(resolveFocusStop([item(false), item(false)], true)).toBe(0);
|
|
17
|
+
});
|
|
18
|
+
it('has nothing to do without items', () => {
|
|
19
|
+
expect(resolveFocusStop([], false)).toBeNull();
|
|
20
|
+
expect(resolveFocusStop([], true)).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe('nextFocusIndex', () => {
|
|
24
|
+
it('moves forward and back', () => {
|
|
25
|
+
expect(nextFocusIndex('ArrowRight', 0, 3)).toBe(1);
|
|
26
|
+
expect(nextFocusIndex('ArrowLeft', 2, 3)).toBe(1);
|
|
27
|
+
});
|
|
28
|
+
it('treats the vertical arrows the same, so a wrapped bar still navigates', () => {
|
|
29
|
+
expect(nextFocusIndex('ArrowDown', 0, 3)).toBe(1);
|
|
30
|
+
expect(nextFocusIndex('ArrowUp', 1, 3)).toBe(0);
|
|
31
|
+
});
|
|
32
|
+
it('wraps at both ends', () => {
|
|
33
|
+
expect(nextFocusIndex('ArrowRight', 2, 3)).toBe(0);
|
|
34
|
+
expect(nextFocusIndex('ArrowLeft', 0, 3)).toBe(2);
|
|
35
|
+
});
|
|
36
|
+
it('jumps to the ends', () => {
|
|
37
|
+
expect(nextFocusIndex('Home', 2, 3)).toBe(0);
|
|
38
|
+
expect(nextFocusIndex('End', 0, 3)).toBe(2);
|
|
39
|
+
});
|
|
40
|
+
it('ignores keys it does not own, so Tab and Enter still behave natively', () => {
|
|
41
|
+
expect(nextFocusIndex('Tab', 0, 3)).toBeNull();
|
|
42
|
+
expect(nextFocusIndex('Enter', 0, 3)).toBeNull();
|
|
43
|
+
expect(nextFocusIndex(' ', 0, 3)).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
it('does nothing without at least two items', () => {
|
|
46
|
+
expect(nextFocusIndex('ArrowRight', 0, 1)).toBeNull();
|
|
47
|
+
expect(nextFocusIndex('ArrowRight', 0, 0)).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
it('does nothing when the event came from outside the items', () => {
|
|
50
|
+
expect(nextFocusIndex('ArrowRight', -1, 3)).toBeNull();
|
|
51
|
+
expect(nextFocusIndex('ArrowRight', 3, 3)).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -21,8 +21,7 @@ let ButtonGroup = class ButtonGroup extends ButtonBar {
|
|
|
21
21
|
ButtonGroup.meta = {
|
|
22
22
|
...ButtonBar.meta,
|
|
23
23
|
tag: 'quantic-button-group',
|
|
24
|
-
description: 'Deprecated alias of quantic-button-bar.
|
|
25
|
-
'it joins borders and gives its items a pressed state. Use quantic-button-bar.',
|
|
24
|
+
description: 'Deprecated alias of quantic-button-bar. Use quantic-button-bar.',
|
|
26
25
|
deprecated: {
|
|
27
26
|
use: 'quantic-button-bar',
|
|
28
27
|
reason: 'The name describes a row of buttons, which this is not. Use quantic-action-group for that.',
|
|
@@ -6,34 +6,35 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { css } from 'lit';
|
|
8
8
|
import { html } from 'lit/static-html.js';
|
|
9
|
+
import { ActionGroup } from '../action-group/action-group.component';
|
|
9
10
|
import { quanticElement, QuanticElement } from '../shared/quantic-element';
|
|
11
|
+
ActionGroup.ensureDefined();
|
|
10
12
|
let DialogActions = class DialogActions extends QuanticElement {
|
|
11
13
|
render() {
|
|
12
14
|
return html `
|
|
13
|
-
<
|
|
15
|
+
<quantic-action-group aria-label="">
|
|
14
16
|
<slot></slot>
|
|
15
|
-
</
|
|
17
|
+
</quantic-action-group>
|
|
16
18
|
`;
|
|
17
19
|
}
|
|
18
20
|
};
|
|
19
21
|
DialogActions.meta = {
|
|
20
22
|
tag: 'quantic-dialog-actions',
|
|
21
|
-
description: 'Footer actions bar
|
|
22
|
-
'Lays
|
|
23
|
+
description: 'Footer actions bar for quantic-dialog. ' +
|
|
24
|
+
'Lays action buttons out as a quantic-action-group, flush to the right.',
|
|
23
25
|
category: 'feedback',
|
|
24
26
|
slots: { '': { description: 'Action buttons, typically quantic-button elements.' } },
|
|
25
|
-
|
|
27
|
+
subComponents: ['quantic-action-group'],
|
|
28
|
+
relatedTo: ['quantic-dialog', 'quantic-button', 'quantic-action-group'],
|
|
26
29
|
};
|
|
27
30
|
DialogActions.styles = css `
|
|
28
31
|
:host {
|
|
29
32
|
display: block;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
justify-content: flex-end;
|
|
36
|
-
gap: var(--quantic-spacing-3);
|
|
35
|
+
quantic-action-group {
|
|
36
|
+
width: fit-content;
|
|
37
|
+
margin-inline-start: auto;
|
|
37
38
|
}
|
|
38
39
|
`;
|
|
39
40
|
DialogActions = __decorate([
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED