@mk-kit/ui 0.40.0 → 0.42.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 +3 -3
- package/fesm2022/mk-kit-ui-core.mjs +87 -0
- package/fesm2022/mk-kit-ui-core.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-data.mjs +447 -26
- package/fesm2022/mk-kit-ui-data.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-datetime.mjs +18 -10
- package/fesm2022/mk-kit-ui-datetime.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-directives.mjs +395 -2
- package/fesm2022/mk-kit-ui-directives.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-dnd.mjs +183 -29
- package/fesm2022/mk-kit-ui-dnd.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-dynamic-form.mjs +1 -1
- package/fesm2022/mk-kit-ui-dynamic-form.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-feedback.mjs +7 -7
- package/fesm2022/mk-kit-ui-feedback.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-forms.mjs +38 -26
- package/fesm2022/mk-kit-ui-forms.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-query-builder.mjs +1 -1
- package/fesm2022/mk-kit-ui-query-builder.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-table.mjs +26 -6
- package/fesm2022/mk-kit-ui-table.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/mk-kit.css +608 -4
- package/types/mk-kit-ui-core.d.ts +48 -1
- package/types/mk-kit-ui-data.d.ts +199 -6
- package/types/mk-kit-ui-datetime.d.ts +8 -2
- package/types/mk-kit-ui-directives.d.ts +234 -3
- package/types/mk-kit-ui-dnd.d.ts +96 -14
- package/types/mk-kit-ui-forms.d.ts +21 -9
- package/types/mk-kit-ui-table.d.ts +38 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, inject, ElementRef, Directive, input, booleanAttribute, numberAttribute, contentChildren, computed, signal, ChangeDetectionStrategy, Component, output,
|
|
2
|
+
import { Injectable, inject, ElementRef, Directive, input, booleanAttribute, numberAttribute, contentChildren, computed, signal, effect, ChangeDetectionStrategy, Component, output, model, contentChild, TemplateRef } from '@angular/core';
|
|
3
3
|
import { MkLiveAnnouncer, MK_I18N, mkUniqueId } from '@mk-kit/ui/core';
|
|
4
4
|
import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
|
|
5
5
|
|
|
@@ -102,6 +102,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
102
102
|
args: [{ providedIn: 'root' }]
|
|
103
103
|
}] });
|
|
104
104
|
|
|
105
|
+
/** Elements that take keyboard focus natively (no `tabindex` needed). */
|
|
106
|
+
const NATIVELY_FOCUSABLE = /^(BUTTON|INPUT|SELECT|TEXTAREA)$/;
|
|
105
107
|
/**
|
|
106
108
|
* Optional grip that restricts where a pointer drag of the enclosing
|
|
107
109
|
* `[mkDrag]` may begin. Place it on the element the user should press to drag;
|
|
@@ -112,16 +114,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
112
114
|
* Its look (grab cursor, muted colour, `touch-action: none`) ships as the
|
|
113
115
|
* global `.mk-drag-handle` class in the theme stylesheet.
|
|
114
116
|
*
|
|
117
|
+
* **Decorative grip** — a non-focusable element (`<span>`, `<mk-icon>`): the
|
|
118
|
+
* item itself stays the keyboard target (`role="button"`, focusable), so the
|
|
119
|
+
* grip should be `aria-hidden`:
|
|
120
|
+
*
|
|
115
121
|
* ```html
|
|
116
122
|
* <div mkDrag [mkDragData]="row">
|
|
117
123
|
* <span mkDragHandle aria-hidden="true">⠿</span>
|
|
118
124
|
* {{ row.name }}
|
|
119
125
|
* </div>
|
|
120
126
|
* ```
|
|
127
|
+
*
|
|
128
|
+
* **Focusable grip** — a `<button>` (or any element with `tabindex`): the
|
|
129
|
+
* handle becomes the keyboard target instead. The item is then a plain
|
|
130
|
+
* container (no role, not focusable), so rows may hold inputs, links and
|
|
131
|
+
* other buttons without nesting interactive controls, and `<li>` items keep
|
|
132
|
+
* valid list semantics. Give it an accessible name:
|
|
133
|
+
*
|
|
134
|
+
* ```html
|
|
135
|
+
* <li mkDrag [mkDragData]="row">
|
|
136
|
+
* <button type="button" mkDragHandle [attr.aria-label]="'Reorder ' + row.name">⠿</button>
|
|
137
|
+
* <input mkInput [(ngModel)]="row.name" />
|
|
138
|
+
* </li>
|
|
139
|
+
* ```
|
|
121
140
|
*/
|
|
122
141
|
class MkDragHandle {
|
|
123
142
|
/** The handle's host element. */
|
|
124
143
|
element = inject(ElementRef).nativeElement;
|
|
144
|
+
/**
|
|
145
|
+
* Whether the handle can take keyboard focus itself — a native control
|
|
146
|
+
* (`<button>`, …), a link with `href`, or any element with a `tabindex`.
|
|
147
|
+
* A focusable handle carries the keyboard drag for its `[mkDrag]`.
|
|
148
|
+
*/
|
|
149
|
+
isFocusable() {
|
|
150
|
+
const el = this.element;
|
|
151
|
+
return (NATIVELY_FOCUSABLE.test(el.tagName) ||
|
|
152
|
+
(el.tagName === 'A' && el.hasAttribute('href')) ||
|
|
153
|
+
el.hasAttribute('tabindex'));
|
|
154
|
+
}
|
|
125
155
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDragHandle, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
126
156
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: MkDragHandle, isStandalone: true, selector: "[mkDragHandle]", host: { classAttribute: "mk-drag-handle" }, exportAs: ["mkDragHandle"], ngImport: i0 });
|
|
127
157
|
}
|
|
@@ -149,13 +179,25 @@ const TOUCH_SLOP = 10;
|
|
|
149
179
|
const SETTLE_MS = 180;
|
|
150
180
|
/**
|
|
151
181
|
* Makes an item inside a `[mkDropList]` draggable — by pointer (mouse / touch /
|
|
152
|
-
* pen) **and** by keyboard (WCAG 2.1.1).
|
|
153
|
-
*
|
|
154
|
-
*
|
|
182
|
+
* pen) **and** by keyboard (WCAG 2.1.1). Every move is announced via
|
|
183
|
+
* {@link MkLiveAnnouncer}. Which element carries the keyboard interaction
|
|
184
|
+
* depends on the handle:
|
|
185
|
+
*
|
|
186
|
+
* - **No handle, or a decorative one** (`<span mkDragHandle aria-hidden>`):
|
|
187
|
+
* the item itself is focusable and exposes `aria-roledescription="Draggable
|
|
188
|
+
* item"` with `role="button"` — or `role="option"` when it is an `<li>` of a
|
|
189
|
+
* `<ul mkDropList>`, which then becomes a labelled `listbox` (an `<li>` may
|
|
190
|
+
* not take the `button` role).
|
|
191
|
+
* - **A focusable handle** (`<button mkDragHandle aria-label="…">`, or any
|
|
192
|
+
* handle with `tabindex`): the handle is the keyboard target and receives
|
|
193
|
+
* the `aria-roledescription` / `aria-pressed` / `aria-grabbed` state; the
|
|
194
|
+
* item stays a plain container with no role and no `tabindex`, so it can hold
|
|
195
|
+
* inputs, links and buttons of its own (no nested interactive controls) and
|
|
196
|
+
* `<li>` items keep their list semantics.
|
|
155
197
|
*
|
|
156
|
-
* Keyboard: focus
|
|
157
|
-
* keys to move it (crossing into connected lists at the ends /
|
|
158
|
-
* perpendicular axis), **Space/Enter** to drop, **Escape** to cancel.
|
|
198
|
+
* Keyboard: focus the item (or its handle) and press **Space/Enter** to pick
|
|
199
|
+
* it up, **Arrow** keys to move it (crossing into connected lists at the ends /
|
|
200
|
+
* across the perpendicular axis), **Space/Enter** to drop, **Escape** to cancel.
|
|
159
201
|
*
|
|
160
202
|
* Touch: a swipe scrolls the page as usual — the drag only arms after a
|
|
161
203
|
* long-press ({@link mkDragTouchDelay}, default 300 ms). While armed the item
|
|
@@ -168,9 +210,9 @@ const SETTLE_MS = 180;
|
|
|
168
210
|
* synchronously on release so drops land exactly where the pointer ended.
|
|
169
211
|
*
|
|
170
212
|
* ```html
|
|
171
|
-
* <
|
|
213
|
+
* <div mkDrag [mkDragData]="row" [mkDragDisabled]="row.locked">
|
|
172
214
|
* <span mkDragHandle aria-hidden="true">⠿</span> {{ row.title }}
|
|
173
|
-
* </
|
|
215
|
+
* </div>
|
|
174
216
|
* ```
|
|
175
217
|
*
|
|
176
218
|
* @typeParam T item data type.
|
|
@@ -206,6 +248,28 @@ class MkDrag {
|
|
|
206
248
|
*/
|
|
207
249
|
ownHandles = computed(() => this.handles().filter((h) => h.element.closest('[mkDrag]') === this.element), /* @ts-ignore */
|
|
208
250
|
...(ngDevMode ? [{ debugName: "ownHandles" }] : /* istanbul ignore next */ []));
|
|
251
|
+
/**
|
|
252
|
+
* The handle that carries the keyboard drag — the first of this item's
|
|
253
|
+
* handles that is focusable on its own (a `<button mkDragHandle>`, say).
|
|
254
|
+
* `null` when the item itself is the keyboard target.
|
|
255
|
+
*/
|
|
256
|
+
keyboardHandle = computed(() => this.ownHandles().find((h) => h.isFocusable()) ?? null, /* @ts-ignore */
|
|
257
|
+
...(ngDevMode ? [{ debugName: "keyboardHandle" }] : /* istanbul ignore next */ []));
|
|
258
|
+
/**
|
|
259
|
+
* The role the item itself exposes: `null` when a focusable handle carries
|
|
260
|
+
* the interaction; `option` inside a list that resolved to a `listbox`
|
|
261
|
+
* (`<ul mkDropList>` / `<li mkDrag>`); `button` otherwise.
|
|
262
|
+
*/
|
|
263
|
+
itemRole = computed(() => {
|
|
264
|
+
if (this.keyboardHandle())
|
|
265
|
+
return null;
|
|
266
|
+
return this.home?.role() === 'listbox' ? 'option' : 'button';
|
|
267
|
+
}, /* @ts-ignore */
|
|
268
|
+
...(ngDevMode ? [{ debugName: "itemRole" }] : /* istanbul ignore next */ []));
|
|
269
|
+
/** The element keyboard events act on: the focusable handle, else the item. */
|
|
270
|
+
keyboardTarget() {
|
|
271
|
+
return this.keyboardHandle()?.element ?? this.element;
|
|
272
|
+
}
|
|
209
273
|
/** True while a pointer drag is in progress. */
|
|
210
274
|
dragging = signal(false, /* @ts-ignore */
|
|
211
275
|
...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
|
|
@@ -236,6 +300,29 @@ class MkDrag {
|
|
|
236
300
|
originLeft = 0;
|
|
237
301
|
originTop = 0;
|
|
238
302
|
preview = null;
|
|
303
|
+
constructor() {
|
|
304
|
+
// Mirror the button state onto a focusable handle. Host bindings cannot
|
|
305
|
+
// reach a projected element, so the attributes are written directly; the
|
|
306
|
+
// effect re-runs whenever the handle or the lift/drag state changes.
|
|
307
|
+
effect(() => {
|
|
308
|
+
const handle = this.keyboardHandle();
|
|
309
|
+
if (!handle)
|
|
310
|
+
return;
|
|
311
|
+
const el = handle.element;
|
|
312
|
+
if (el.tagName !== 'BUTTON')
|
|
313
|
+
el.setAttribute('role', 'button');
|
|
314
|
+
el.setAttribute('aria-roledescription', 'Draggable item');
|
|
315
|
+
el.setAttribute('aria-grabbed', String(this.dragging() || this.lifted()));
|
|
316
|
+
this.toggleAttr(el, 'aria-pressed', this.lifted() ? 'true' : null);
|
|
317
|
+
this.toggleAttr(el, 'aria-disabled', this.disabled() ? 'true' : null);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
toggleAttr(el, name, value) {
|
|
321
|
+
if (value === null)
|
|
322
|
+
el.removeAttribute(name);
|
|
323
|
+
else
|
|
324
|
+
el.setAttribute(name, value);
|
|
325
|
+
}
|
|
239
326
|
moveHandler = (e) => this.onPointerMove(e);
|
|
240
327
|
upHandler = (e) => this.onPointerUp(e);
|
|
241
328
|
cancelHandler = () => this.finishPointer(true);
|
|
@@ -564,9 +651,10 @@ class MkDrag {
|
|
|
564
651
|
onKeyDown(event) {
|
|
565
652
|
const e = event;
|
|
566
653
|
const key = e.key;
|
|
567
|
-
// Keys act on the focused item only — a nested
|
|
568
|
-
// through outer items, which must not pick
|
|
569
|
-
|
|
654
|
+
// Keys act on the focused item (or its focusable handle) only — a nested
|
|
655
|
+
// item's keydown bubbles up through outer items, which must not pick
|
|
656
|
+
// themselves up, and keys typed into a row's inputs are not drag keys.
|
|
657
|
+
if (e.target !== this.keyboardTarget())
|
|
570
658
|
return;
|
|
571
659
|
if (!this.lifted()) {
|
|
572
660
|
if ((key === ' ' || key === 'Enter') && !this.disabled() && this.home && !this.dragging()) {
|
|
@@ -607,8 +695,12 @@ class MkDrag {
|
|
|
607
695
|
break;
|
|
608
696
|
}
|
|
609
697
|
}
|
|
610
|
-
|
|
698
|
+
onFocusOut(event) {
|
|
611
699
|
// Losing focus mid-lift cancels the keyboard drag to avoid a stuck state.
|
|
700
|
+
// `focusout` bubbles, so only the keyboard target's own blur counts — a
|
|
701
|
+
// nested control losing focus must not cancel the outer item's lift.
|
|
702
|
+
if (event.target !== this.keyboardTarget())
|
|
703
|
+
return;
|
|
612
704
|
if (this.lifted())
|
|
613
705
|
this.cancelKeyboard();
|
|
614
706
|
}
|
|
@@ -903,19 +995,22 @@ class MkDrag {
|
|
|
903
995
|
.matches ?? false);
|
|
904
996
|
}
|
|
905
997
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDrag, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
906
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.7", type: MkDrag, isStandalone: true, selector: "[mkDrag]", inputs: { mkDragData: { classPropertyName: "mkDragData", publicName: "mkDragData", isSignal: true, isRequired: false, transformFunction: null }, mkDragDisabled: { classPropertyName: "mkDragDisabled", publicName: "mkDragDisabled", isSignal: true, isRequired: false, transformFunction: null }, mkDragTouchDelay: { classPropertyName: "mkDragTouchDelay", publicName: "mkDragTouchDelay", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "
|
|
998
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.7", type: MkDrag, isStandalone: true, selector: "[mkDrag]", inputs: { mkDragData: { classPropertyName: "mkDragData", publicName: "mkDragData", isSignal: true, isRequired: false, transformFunction: null }, mkDragDisabled: { classPropertyName: "mkDragDisabled", publicName: "mkDragDisabled", isSignal: true, isRequired: false, transformFunction: null }, mkDragTouchDelay: { classPropertyName: "mkDragTouchDelay", publicName: "mkDragTouchDelay", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "draggable": "false" }, listeners: { "pointerdown": "onPointerDown($event)", "keydown": "onKeyDown($event)", "focusout": "onFocusOut($event)" }, properties: { "attr.role": "itemRole()", "attr.aria-roledescription": "itemRole() ? 'Draggable item' : null", "attr.tabindex": "itemRole() ? (disabled() ? -1 : 0) : null", "attr.aria-disabled": "itemRole() ? disabled() || null : null", "attr.aria-pressed": "itemRole() === 'button' ? lifted() || null : null", "attr.aria-selected": "itemRole() === 'option' ? lifted() : null", "attr.aria-grabbed": "itemRole() ? dragging() || lifted() : null", "class.mk-drag--disabled": "disabled()", "class.mk-drag--dragging": "dragging()", "class.mk-drag--lifted": "lifted()", "class.mk-drag--armed": "armed()", "class.mk-drag--has-handle": "ownHandles().length > 0", "class.mk-drag--horizontal": "inHorizontalList()" }, classAttribute: "mk-drag" }, queries: [{ propertyName: "handles", predicate: MkDragHandle, descendants: true, isSignal: true }], exportAs: ["mkDrag"], ngImport: i0, template: "<ng-content />\n", styles: ["@charset \"UTF-8\";:host{position:relative;cursor:grab;touch-action:pan-y;user-select:none;-webkit-user-select:none}:host(.mk-drag--horizontal){touch-action:manipulation}:host(.mk-drag--has-handle){cursor:default;touch-action:auto}:host(.mk-drag--dragging){cursor:grabbing}:host(.mk-drag--armed){cursor:grabbing;border-radius:var(--mk-radius-md);box-shadow:var(--mk-shadow-md)}:host(.mk-drag--lifted){outline:var(--mk-border-width-strong) solid var(--mk-primary);outline-offset:var(--mk-focus-ring-offset);border-radius:var(--mk-radius-md);background-color:var(--mk-surface-2);box-shadow:var(--mk-shadow-lg);z-index:var(--mk-z-sticky)}:host(.mk-drag--disabled){cursor:not-allowed;opacity:.55;touch-action:auto}:host(:focus-visible){outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
907
999
|
}
|
|
908
1000
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDrag, decorators: [{
|
|
909
1001
|
type: Component,
|
|
910
1002
|
args: [{ selector: '[mkDrag]', exportAs: 'mkDrag', changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
911
1003
|
class: 'mk-drag',
|
|
912
|
-
role: 'button',
|
|
913
|
-
'aria-roledescription': 'Draggable item',
|
|
914
1004
|
draggable: 'false',
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
'[attr.
|
|
918
|
-
'[attr.aria-
|
|
1005
|
+
// Widget semantics live on the item only while no focusable handle takes
|
|
1006
|
+
// them over (see `keyboardHandle`); otherwise the item is a plain container.
|
|
1007
|
+
'[attr.role]': 'itemRole()',
|
|
1008
|
+
'[attr.aria-roledescription]': "itemRole() ? 'Draggable item' : null",
|
|
1009
|
+
'[attr.tabindex]': 'itemRole() ? (disabled() ? -1 : 0) : null',
|
|
1010
|
+
'[attr.aria-disabled]': 'itemRole() ? disabled() || null : null',
|
|
1011
|
+
'[attr.aria-pressed]': "itemRole() === 'button' ? lifted() || null : null",
|
|
1012
|
+
'[attr.aria-selected]': "itemRole() === 'option' ? lifted() : null",
|
|
1013
|
+
'[attr.aria-grabbed]': 'itemRole() ? dragging() || lifted() : null',
|
|
919
1014
|
'[class.mk-drag--disabled]': 'disabled()',
|
|
920
1015
|
'[class.mk-drag--dragging]': 'dragging()',
|
|
921
1016
|
'[class.mk-drag--lifted]': 'lifted()',
|
|
@@ -924,12 +1019,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
924
1019
|
'[class.mk-drag--horizontal]': 'inHorizontalList()',
|
|
925
1020
|
'(pointerdown)': 'onPointerDown($event)',
|
|
926
1021
|
'(keydown)': 'onKeyDown($event)',
|
|
927
|
-
'(
|
|
1022
|
+
'(focusout)': 'onFocusOut($event)',
|
|
928
1023
|
}, template: "<ng-content />\n", styles: ["@charset \"UTF-8\";:host{position:relative;cursor:grab;touch-action:pan-y;user-select:none;-webkit-user-select:none}:host(.mk-drag--horizontal){touch-action:manipulation}:host(.mk-drag--has-handle){cursor:default;touch-action:auto}:host(.mk-drag--dragging){cursor:grabbing}:host(.mk-drag--armed){cursor:grabbing;border-radius:var(--mk-radius-md);box-shadow:var(--mk-shadow-md)}:host(.mk-drag--lifted){outline:var(--mk-border-width-strong) solid var(--mk-primary);outline-offset:var(--mk-focus-ring-offset);border-radius:var(--mk-radius-md);background-color:var(--mk-surface-2);box-shadow:var(--mk-shadow-lg);z-index:var(--mk-z-sticky)}:host(.mk-drag--disabled){cursor:not-allowed;opacity:.55;touch-action:auto}:host(:focus-visible){outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}\n"] }]
|
|
929
|
-
}], propDecorators: { mkDragData: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDragData", required: false }] }], mkDragDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDragDisabled", required: false }] }], mkDragTouchDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDragTouchDelay", required: false }] }], handles: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkDragHandle), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
1024
|
+
}], ctorParameters: () => [], propDecorators: { mkDragData: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDragData", required: false }] }], mkDragDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDragDisabled", required: false }] }], mkDragTouchDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDragTouchDelay", required: false }] }], handles: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkDragHandle), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
930
1025
|
|
|
931
1026
|
/* eslint-disable @typescript-eslint/no-explicit-any -- item-type params use
|
|
932
1027
|
`any` to accept drags of any data type without generic-variance friction. */
|
|
1028
|
+
/**
|
|
1029
|
+
* Roles on which `aria-orientation` is permitted (WAI-ARIA 1.2). On any other
|
|
1030
|
+
* role the attribute is invalid, so the list only exposes it for these.
|
|
1031
|
+
*/
|
|
1032
|
+
const ORIENTATION_ROLES = new Set([
|
|
1033
|
+
'listbox',
|
|
1034
|
+
'menu',
|
|
1035
|
+
'radiogroup',
|
|
1036
|
+
'scrollbar',
|
|
1037
|
+
'select',
|
|
1038
|
+
'separator',
|
|
1039
|
+
'slider',
|
|
1040
|
+
'tablist',
|
|
1041
|
+
'toolbar',
|
|
1042
|
+
'tree',
|
|
1043
|
+
'treegrid',
|
|
1044
|
+
]);
|
|
933
1045
|
/**
|
|
934
1046
|
* A drop container for reorderable `[mkDrag]` items.
|
|
935
1047
|
*
|
|
@@ -941,14 +1053,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
941
1053
|
* The array bound to `mkDropListData` is **not** mutated for you — handle
|
|
942
1054
|
* `mkDropListDropped` and call {@link mkMoveItemInArray} / {@link mkTransferArrayItem}.
|
|
943
1055
|
*
|
|
1056
|
+
* Semantics follow the host element and its items, so the tree is always
|
|
1057
|
+
* valid ARIA:
|
|
1058
|
+
*
|
|
1059
|
+
* - any host other than `<ul>`/`<ol>` is a `role="group"` (named by
|
|
1060
|
+
* `mkDropListLabel`) of `role="button"` items;
|
|
1061
|
+
* - a `<ul>`/`<ol>` whose `<li mkDrag>` items all carry a *focusable*
|
|
1062
|
+
* `[mkDragHandle]` stays a plain list — the handles are the controls;
|
|
1063
|
+
* - a `<ul>`/`<ol>` whose items are themselves the keyboard targets becomes a
|
|
1064
|
+
* `listbox` of `option`s (an `<li>` may not be a `button`); give it a
|
|
1065
|
+
* `mkDropListLabel`, listboxes need a name.
|
|
1066
|
+
*
|
|
1067
|
+
* A `role` you set in the template is kept, and `aria-orientation` is only
|
|
1068
|
+
* exposed on roles that allow it (`listbox`, `toolbar`, `tree`, …) — the
|
|
1069
|
+
* keyboard model handles both axes regardless.
|
|
1070
|
+
*
|
|
944
1071
|
* ```html
|
|
945
|
-
* <
|
|
946
|
-
*
|
|
947
|
-
*
|
|
1072
|
+
* <div mkDropList [mkDropListData]="todo()" mkDropListLabel="To do"
|
|
1073
|
+
* mkDropListId="todo" [mkDropListConnectedTo]="['done']"
|
|
1074
|
+
* (mkDropListDropped)="drop($event)">
|
|
948
1075
|
* @for (t of todo(); track t.id) {
|
|
949
|
-
* <
|
|
1076
|
+
* <div mkDrag [mkDragData]="t">{{ t.title }}</div>
|
|
950
1077
|
* }
|
|
951
|
-
* </
|
|
1078
|
+
* </div>
|
|
952
1079
|
* ```
|
|
953
1080
|
*
|
|
954
1081
|
* @typeParam T item data type.
|
|
@@ -988,6 +1115,31 @@ class MkDropList {
|
|
|
988
1115
|
/** Announceable name: the label when set, otherwise the resolved id. */
|
|
989
1116
|
label = computed(() => this.mkDropListLabel() || this.id(), /* @ts-ignore */
|
|
990
1117
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1118
|
+
/** A `role` written in the template — always kept. */
|
|
1119
|
+
explicitRole = this.element.getAttribute('role');
|
|
1120
|
+
isNativeList = /^(UL|OL)$/.test(this.element.tagName);
|
|
1121
|
+
/**
|
|
1122
|
+
* The role the host exposes. One set in the template wins. A `<ul>`/`<ol>`
|
|
1123
|
+
* keeps its implicit `list` role (`null` — nothing is written) while every
|
|
1124
|
+
* item hands the keyboard drag to a focusable handle, and becomes a
|
|
1125
|
+
* `listbox` (its items `option`s) otherwise. Any other element is a `group`.
|
|
1126
|
+
*/
|
|
1127
|
+
role = computed(() => {
|
|
1128
|
+
if (this.explicitRole)
|
|
1129
|
+
return this.explicitRole;
|
|
1130
|
+
if (!this.isNativeList)
|
|
1131
|
+
return 'group';
|
|
1132
|
+
return this.drags().every((d) => d.keyboardHandle()) ? null : 'listbox';
|
|
1133
|
+
}, /* @ts-ignore */
|
|
1134
|
+
...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
|
|
1135
|
+
/** Whether `aria-orientation` is valid on the effective role. */
|
|
1136
|
+
orientationAllowed = computed(() => ORIENTATION_ROLES.has(this.role() ?? ''), /* @ts-ignore */
|
|
1137
|
+
...(ngDevMode ? [{ debugName: "orientationAllowed" }] : /* istanbul ignore next */ []));
|
|
1138
|
+
/** A static `aria-label` written in the template, kept when no label input is set. */
|
|
1139
|
+
staticAriaLabel = this.element.getAttribute('aria-label');
|
|
1140
|
+
/** Accessible name of the list: `mkDropListLabel`, else the template's own. */
|
|
1141
|
+
ariaLabel = computed(() => this.mkDropListLabel() || this.staticAriaLabel || null, /* @ts-ignore */
|
|
1142
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
991
1143
|
/** Connected-list ids, normalised to a plain array. */
|
|
992
1144
|
connectedTo = computed(() => this.mkDropListConnectedTo() ?? [], /* @ts-ignore */
|
|
993
1145
|
...(ngDevMode ? [{ debugName: "connectedTo" }] : /* istanbul ignore next */ []));
|
|
@@ -1027,13 +1179,15 @@ class MkDropList {
|
|
|
1027
1179
|
this.mkDropListDropped.emit(event);
|
|
1028
1180
|
}
|
|
1029
1181
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDropList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1030
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.7", type: MkDropList, isStandalone: true, selector: "[mkDropList]", inputs: { mkDropListData: { classPropertyName: "mkDropListData", publicName: "mkDropListData", isSignal: true, isRequired: false, transformFunction: null }, mkDropListId: { classPropertyName: "mkDropListId", publicName: "mkDropListId", isSignal: true, isRequired: false, transformFunction: null }, mkDropListConnectedTo: { classPropertyName: "mkDropListConnectedTo", publicName: "mkDropListConnectedTo", isSignal: true, isRequired: false, transformFunction: null }, mkDropListLabel: { classPropertyName: "mkDropListLabel", publicName: "mkDropListLabel", isSignal: true, isRequired: false, transformFunction: null }, mkDropListOrientation: { classPropertyName: "mkDropListOrientation", publicName: "mkDropListOrientation", isSignal: true, isRequired: false, transformFunction: null }, mkDropListDisabled: { classPropertyName: "mkDropListDisabled", publicName: "mkDropListDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mkDropListDropped: "mkDropListDropped" }, host: { properties: { "attr.aria-orientation": "mkDropListOrientation()", "attr.aria-disabled": "mkDropListDisabled() || null", "class.mk-drop-list--horizontal": "mkDropListOrientation() === 'horizontal'", "class.mk-drop-list--disabled": "mkDropListDisabled()", "class.mk-drop-list--receiving": "_receiving()" }, classAttribute: "mk-drop-list" }, queries: [{ propertyName: "drags", predicate: MkDrag, isSignal: true }], exportAs: ["mkDropList"], ngImport: i0, template: "<ng-content />\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative}:host(.mk-drop-list--receiving){outline:var(--mk-border-width-strong) solid var(--mk-primary-subtle-text);outline-offset:calc(-1 * var(--mk-border-width-strong));border-radius:var(--mk-radius-md);background-color:color-mix(in srgb,var(--mk-primary) 6%,transparent)}:host(.mk-drop-list--disabled){cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1182
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.7", type: MkDropList, isStandalone: true, selector: "[mkDropList]", inputs: { mkDropListData: { classPropertyName: "mkDropListData", publicName: "mkDropListData", isSignal: true, isRequired: false, transformFunction: null }, mkDropListId: { classPropertyName: "mkDropListId", publicName: "mkDropListId", isSignal: true, isRequired: false, transformFunction: null }, mkDropListConnectedTo: { classPropertyName: "mkDropListConnectedTo", publicName: "mkDropListConnectedTo", isSignal: true, isRequired: false, transformFunction: null }, mkDropListLabel: { classPropertyName: "mkDropListLabel", publicName: "mkDropListLabel", isSignal: true, isRequired: false, transformFunction: null }, mkDropListOrientation: { classPropertyName: "mkDropListOrientation", publicName: "mkDropListOrientation", isSignal: true, isRequired: false, transformFunction: null }, mkDropListDisabled: { classPropertyName: "mkDropListDisabled", publicName: "mkDropListDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mkDropListDropped: "mkDropListDropped" }, host: { properties: { "attr.role": "role()", "attr.aria-label": "ariaLabel()", "attr.aria-orientation": "orientationAllowed() ? mkDropListOrientation() : null", "attr.aria-disabled": "mkDropListDisabled() || null", "class.mk-drop-list--horizontal": "mkDropListOrientation() === 'horizontal'", "class.mk-drop-list--disabled": "mkDropListDisabled()", "class.mk-drop-list--receiving": "_receiving()" }, classAttribute: "mk-drop-list" }, queries: [{ propertyName: "drags", predicate: MkDrag, isSignal: true }], exportAs: ["mkDropList"], ngImport: i0, template: "<ng-content />\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative}:host(.mk-drop-list--receiving){outline:var(--mk-border-width-strong) solid var(--mk-primary-subtle-text);outline-offset:calc(-1 * var(--mk-border-width-strong));border-radius:var(--mk-radius-md);background-color:color-mix(in srgb,var(--mk-primary) 6%,transparent)}:host(.mk-drop-list--disabled){cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1031
1183
|
}
|
|
1032
1184
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDropList, decorators: [{
|
|
1033
1185
|
type: Component,
|
|
1034
1186
|
args: [{ selector: '[mkDropList]', exportAs: 'mkDropList', changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
1035
1187
|
class: 'mk-drop-list',
|
|
1036
|
-
'[attr.
|
|
1188
|
+
'[attr.role]': 'role()',
|
|
1189
|
+
'[attr.aria-label]': 'ariaLabel()',
|
|
1190
|
+
'[attr.aria-orientation]': 'orientationAllowed() ? mkDropListOrientation() : null',
|
|
1037
1191
|
'[attr.aria-disabled]': 'mkDropListDisabled() || null',
|
|
1038
1192
|
'[class.mk-drop-list--horizontal]': "mkDropListOrientation() === 'horizontal'",
|
|
1039
1193
|
'[class.mk-drop-list--disabled]': 'mkDropListDisabled()',
|