@neuravision/ng-construct 0.1.0 → 0.3.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.
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, output, signal, computed, ChangeDetectionStrategy, Component, inject, forwardRef, model, booleanAttribute, viewChild, contentChildren, effect,
|
|
2
|
+
import { input, output, signal, computed, ChangeDetectionStrategy, Component, inject, forwardRef, model, booleanAttribute, viewChild, contentChildren, effect, contentChild, ElementRef, TemplateRef, Directive, Injectable, viewChildren, Renderer2, InjectionToken, DOCUMENT, numberAttribute, Pipe } from '@angular/core';
|
|
3
3
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
4
4
|
import { NgTemplateOutlet } from '@angular/common';
|
|
5
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Alert component for displaying contextual feedback messages.
|
|
@@ -1200,36 +1201,65 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
1200
1201
|
`, styles: [":host{display:block}\n"] }]
|
|
1201
1202
|
}], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }, { type: i0.Output, args: ["indeterminateChange"] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }] } });
|
|
1202
1203
|
|
|
1204
|
+
// ── Radio Button ─────────────────────────────────────────────────────────────
|
|
1203
1205
|
/**
|
|
1204
|
-
*
|
|
1206
|
+
* Individual radio button. Use inside `af-radio-group` for full
|
|
1207
|
+
* accessibility, or standalone with its own `ControlValueAccessor`.
|
|
1205
1208
|
*
|
|
1206
1209
|
* @example
|
|
1207
|
-
* <af-radio
|
|
1208
|
-
* Standard
|
|
1209
|
-
* </af-radio>
|
|
1210
|
-
*
|
|
1211
|
-
* Premium
|
|
1212
|
-
* </af-radio>
|
|
1210
|
+
* <af-radio-group ariaLabel="Plan" name="plan" [(ngModel)]="plan">
|
|
1211
|
+
* <af-radio value="standard">Standard</af-radio>
|
|
1212
|
+
* <af-radio value="premium">Premium</af-radio>
|
|
1213
|
+
* </af-radio-group>
|
|
1213
1214
|
*/
|
|
1214
1215
|
class AfRadioComponent {
|
|
1215
|
-
|
|
1216
|
+
group = inject(forwardRef(() => AfRadioGroupComponent), { optional: true });
|
|
1217
|
+
/** Radio group name (only used without `af-radio-group`). */
|
|
1216
1218
|
name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
1217
|
-
/** Radio value */
|
|
1219
|
+
/** Radio value. */
|
|
1218
1220
|
value = input(undefined, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
1219
|
-
/** Whether radio is disabled */
|
|
1221
|
+
/** Whether this radio is disabled. */
|
|
1220
1222
|
disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
1223
|
+
inputRef = viewChild.required('inputEl');
|
|
1221
1224
|
modelValue = signal(undefined, ...(ngDevMode ? [{ debugName: "modelValue" }] : []));
|
|
1222
1225
|
onChangeCallback = () => { };
|
|
1223
1226
|
onTouched = () => { };
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1227
|
+
resolvedName = computed(() => this.group?.name() || this.name(), ...(ngDevMode ? [{ debugName: "resolvedName" }] : []));
|
|
1228
|
+
isChecked = computed(() => {
|
|
1229
|
+
const selected = this.group ? this.group.selectedValue() : this.modelValue();
|
|
1230
|
+
return selected === this.value();
|
|
1231
|
+
}, ...(ngDevMode ? [{ debugName: "isChecked" }] : []));
|
|
1232
|
+
isDisabled = computed(() => {
|
|
1233
|
+
if (this.group?.disabled())
|
|
1234
|
+
return true;
|
|
1235
|
+
return this.disabled();
|
|
1236
|
+
}, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
1237
|
+
resolvedTabindex = computed(() => {
|
|
1238
|
+
if (!this.group)
|
|
1239
|
+
return null;
|
|
1240
|
+
return this.group.tabindexFor(this);
|
|
1241
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedTabindex" }] : []));
|
|
1242
|
+
/** Focuses the native input element. */
|
|
1243
|
+
focus() {
|
|
1244
|
+
this.inputRef().nativeElement.focus();
|
|
1245
|
+
}
|
|
1246
|
+
onChangeEvent() {
|
|
1247
|
+
if (this.group) {
|
|
1248
|
+
this.group.selectRadio(this);
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1228
1251
|
this.modelValue.set(this.value());
|
|
1229
1252
|
this.onChangeCallback(this.value());
|
|
1230
1253
|
}
|
|
1231
1254
|
}
|
|
1232
|
-
|
|
1255
|
+
onFocus() {
|
|
1256
|
+
this.group?.onRadioFocus(this);
|
|
1257
|
+
}
|
|
1258
|
+
onKeydown(event) {
|
|
1259
|
+
if (this.group) {
|
|
1260
|
+
this.group.onRadioKeydown(event, this);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1233
1263
|
writeValue(value) {
|
|
1234
1264
|
this.modelValue.set(value);
|
|
1235
1265
|
}
|
|
@@ -1243,26 +1273,29 @@ class AfRadioComponent {
|
|
|
1243
1273
|
this.disabled.set(isDisabled);
|
|
1244
1274
|
}
|
|
1245
1275
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfRadioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1246
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
1276
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.2", type: AfRadioComponent, isStandalone: true, selector: "af-radio", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange" }, providers: [
|
|
1247
1277
|
{
|
|
1248
1278
|
provide: NG_VALUE_ACCESSOR,
|
|
1249
1279
|
useExisting: forwardRef(() => AfRadioComponent),
|
|
1250
|
-
multi: true
|
|
1251
|
-
}
|
|
1252
|
-
], ngImport: i0, template: `
|
|
1280
|
+
multi: true,
|
|
1281
|
+
},
|
|
1282
|
+
], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1253
1283
|
<label class="ct-radio">
|
|
1254
1284
|
<input
|
|
1285
|
+
#inputEl
|
|
1255
1286
|
class="ct-radio__input"
|
|
1256
1287
|
type="radio"
|
|
1257
|
-
[name]="
|
|
1288
|
+
[name]="resolvedName()"
|
|
1258
1289
|
[value]="value()"
|
|
1259
1290
|
[checked]="isChecked()"
|
|
1260
|
-
[disabled]="
|
|
1261
|
-
|
|
1291
|
+
[disabled]="isDisabled()"
|
|
1292
|
+
[attr.tabindex]="resolvedTabindex()"
|
|
1293
|
+
(change)="onChangeEvent()"
|
|
1262
1294
|
(blur)="onTouched()"
|
|
1263
|
-
|
|
1295
|
+
(focus)="onFocus()"
|
|
1296
|
+
(keydown)="onKeydown($event)" />
|
|
1264
1297
|
<span class="ct-radio__label">
|
|
1265
|
-
<ng-content
|
|
1298
|
+
<ng-content />
|
|
1266
1299
|
</span>
|
|
1267
1300
|
</label>
|
|
1268
1301
|
`, isInline: true, styles: [":host{display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
@@ -1273,26 +1306,172 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
1273
1306
|
{
|
|
1274
1307
|
provide: NG_VALUE_ACCESSOR,
|
|
1275
1308
|
useExisting: forwardRef(() => AfRadioComponent),
|
|
1276
|
-
multi: true
|
|
1277
|
-
}
|
|
1309
|
+
multi: true,
|
|
1310
|
+
},
|
|
1278
1311
|
], template: `
|
|
1279
1312
|
<label class="ct-radio">
|
|
1280
1313
|
<input
|
|
1314
|
+
#inputEl
|
|
1281
1315
|
class="ct-radio__input"
|
|
1282
1316
|
type="radio"
|
|
1283
|
-
[name]="
|
|
1317
|
+
[name]="resolvedName()"
|
|
1284
1318
|
[value]="value()"
|
|
1285
1319
|
[checked]="isChecked()"
|
|
1286
|
-
[disabled]="
|
|
1287
|
-
|
|
1320
|
+
[disabled]="isDisabled()"
|
|
1321
|
+
[attr.tabindex]="resolvedTabindex()"
|
|
1322
|
+
(change)="onChangeEvent()"
|
|
1288
1323
|
(blur)="onTouched()"
|
|
1289
|
-
|
|
1324
|
+
(focus)="onFocus()"
|
|
1325
|
+
(keydown)="onKeydown($event)" />
|
|
1290
1326
|
<span class="ct-radio__label">
|
|
1291
|
-
<ng-content
|
|
1327
|
+
<ng-content />
|
|
1292
1328
|
</span>
|
|
1293
1329
|
</label>
|
|
1294
1330
|
`, styles: [":host{display:block}\n"] }]
|
|
1295
|
-
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }] } });
|
|
1331
|
+
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], inputRef: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }] } });
|
|
1332
|
+
// ── Radio Group ──────────────────────────────────────────────────────────────
|
|
1333
|
+
/**
|
|
1334
|
+
* Groups `af-radio` components with `role="radiogroup"`, ARIA labeling,
|
|
1335
|
+
* roving tabindex, and arrow-key navigation per WAI-ARIA Radio Group Pattern.
|
|
1336
|
+
*
|
|
1337
|
+
* Implements `ControlValueAccessor` so the group value can be bound via
|
|
1338
|
+
* `[(ngModel)]` or reactive forms.
|
|
1339
|
+
*
|
|
1340
|
+
* @example
|
|
1341
|
+
* <af-radio-group ariaLabel="Select plan" name="plan" [(ngModel)]="plan">
|
|
1342
|
+
* <af-radio value="standard">Standard</af-radio>
|
|
1343
|
+
* <af-radio value="premium">Premium</af-radio>
|
|
1344
|
+
* </af-radio-group>
|
|
1345
|
+
*/
|
|
1346
|
+
class AfRadioGroupComponent {
|
|
1347
|
+
/** Shared `name` attribute for all child radios. */
|
|
1348
|
+
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
1349
|
+
/** Accessible label for the radio group. */
|
|
1350
|
+
ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
1351
|
+
/** ID of an external element labeling this group. */
|
|
1352
|
+
ariaLabelledBy = input('', ...(ngDevMode ? [{ debugName: "ariaLabelledBy" }] : []));
|
|
1353
|
+
/** Disables all radios in the group. */
|
|
1354
|
+
disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
1355
|
+
radios = contentChildren(AfRadioComponent, ...(ngDevMode ? [{ debugName: "radios" }] : []));
|
|
1356
|
+
selectedValue = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedValue" }] : []));
|
|
1357
|
+
focusedIndex = signal(0, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
|
|
1358
|
+
onChangeCallback = () => { };
|
|
1359
|
+
onTouchedCallback = () => { };
|
|
1360
|
+
syncEffect = effect(() => {
|
|
1361
|
+
const radios = this.radios();
|
|
1362
|
+
const value = this.selectedValue();
|
|
1363
|
+
const checkedIdx = radios.findIndex((r) => r.value() === value);
|
|
1364
|
+
this.focusedIndex.set(checkedIdx >= 0 ? checkedIdx : 0);
|
|
1365
|
+
}, ...(ngDevMode ? [{ debugName: "syncEffect" }] : []));
|
|
1366
|
+
/** Returns the tabindex a child radio should use for roving tabindex. */
|
|
1367
|
+
tabindexFor(radio) {
|
|
1368
|
+
const radios = this.enabledRadios();
|
|
1369
|
+
const idx = radios.indexOf(radio);
|
|
1370
|
+
if (idx === -1)
|
|
1371
|
+
return -1;
|
|
1372
|
+
return idx === this.focusedIndex() ? 0 : -1;
|
|
1373
|
+
}
|
|
1374
|
+
/** Selects a radio and propagates the value. */
|
|
1375
|
+
selectRadio(radio) {
|
|
1376
|
+
const value = radio.value();
|
|
1377
|
+
this.selectedValue.set(value);
|
|
1378
|
+
this.onChangeCallback(value);
|
|
1379
|
+
this.onTouchedCallback();
|
|
1380
|
+
}
|
|
1381
|
+
/** Called when a child radio receives focus. */
|
|
1382
|
+
onRadioFocus(radio) {
|
|
1383
|
+
const idx = this.enabledRadios().indexOf(radio);
|
|
1384
|
+
if (idx >= 0) {
|
|
1385
|
+
this.focusedIndex.set(idx);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
/** Handles keyboard navigation within the group. */
|
|
1389
|
+
onRadioKeydown(event, _current) {
|
|
1390
|
+
const enabled = this.enabledRadios();
|
|
1391
|
+
if (enabled.length === 0)
|
|
1392
|
+
return;
|
|
1393
|
+
let nextIndex = null;
|
|
1394
|
+
switch (event.key) {
|
|
1395
|
+
case 'ArrowDown':
|
|
1396
|
+
case 'ArrowRight':
|
|
1397
|
+
event.preventDefault();
|
|
1398
|
+
nextIndex = (this.focusedIndex() + 1) % enabled.length;
|
|
1399
|
+
break;
|
|
1400
|
+
case 'ArrowUp':
|
|
1401
|
+
case 'ArrowLeft':
|
|
1402
|
+
event.preventDefault();
|
|
1403
|
+
nextIndex = (this.focusedIndex() - 1 + enabled.length) % enabled.length;
|
|
1404
|
+
break;
|
|
1405
|
+
case 'Home':
|
|
1406
|
+
event.preventDefault();
|
|
1407
|
+
nextIndex = 0;
|
|
1408
|
+
break;
|
|
1409
|
+
case 'End':
|
|
1410
|
+
event.preventDefault();
|
|
1411
|
+
nextIndex = enabled.length - 1;
|
|
1412
|
+
break;
|
|
1413
|
+
case ' ':
|
|
1414
|
+
event.preventDefault();
|
|
1415
|
+
this.selectRadio(enabled[this.focusedIndex()]);
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
if (nextIndex !== null) {
|
|
1419
|
+
this.focusedIndex.set(nextIndex);
|
|
1420
|
+
const target = enabled[nextIndex];
|
|
1421
|
+
target.focus();
|
|
1422
|
+
this.selectRadio(target);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
writeValue(value) {
|
|
1426
|
+
this.selectedValue.set(value);
|
|
1427
|
+
}
|
|
1428
|
+
registerOnChange(fn) {
|
|
1429
|
+
this.onChangeCallback = fn;
|
|
1430
|
+
}
|
|
1431
|
+
registerOnTouched(fn) {
|
|
1432
|
+
this.onTouchedCallback = fn;
|
|
1433
|
+
}
|
|
1434
|
+
setDisabledState(isDisabled) {
|
|
1435
|
+
this.disabled.set(isDisabled);
|
|
1436
|
+
}
|
|
1437
|
+
enabledRadios() {
|
|
1438
|
+
return this.radios().filter((r) => !r.isDisabled());
|
|
1439
|
+
}
|
|
1440
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfRadioGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1441
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.2", type: AfRadioGroupComponent, isStandalone: true, selector: "af-radio-group", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "ariaLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange" }, providers: [
|
|
1442
|
+
{
|
|
1443
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1444
|
+
useExisting: forwardRef(() => AfRadioGroupComponent),
|
|
1445
|
+
multi: true,
|
|
1446
|
+
},
|
|
1447
|
+
], queries: [{ propertyName: "radios", predicate: AfRadioComponent, isSignal: true }], ngImport: i0, template: `
|
|
1448
|
+
<div
|
|
1449
|
+
role="radiogroup"
|
|
1450
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
1451
|
+
[attr.aria-labelledby]="ariaLabelledBy() || null"
|
|
1452
|
+
[attr.aria-disabled]="disabled() || null">
|
|
1453
|
+
<ng-content />
|
|
1454
|
+
</div>
|
|
1455
|
+
`, isInline: true, styles: [":host{display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1456
|
+
}
|
|
1457
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfRadioGroupComponent, decorators: [{
|
|
1458
|
+
type: Component,
|
|
1459
|
+
args: [{ selector: 'af-radio-group', changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
1460
|
+
{
|
|
1461
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1462
|
+
useExisting: forwardRef(() => AfRadioGroupComponent),
|
|
1463
|
+
multi: true,
|
|
1464
|
+
},
|
|
1465
|
+
], template: `
|
|
1466
|
+
<div
|
|
1467
|
+
role="radiogroup"
|
|
1468
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
1469
|
+
[attr.aria-labelledby]="ariaLabelledBy() || null"
|
|
1470
|
+
[attr.aria-disabled]="disabled() || null">
|
|
1471
|
+
<ng-content />
|
|
1472
|
+
</div>
|
|
1473
|
+
`, styles: [":host{display:block}\n"] }]
|
|
1474
|
+
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledBy", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], radios: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => AfRadioComponent), { isSignal: true }] }] } });
|
|
1296
1475
|
|
|
1297
1476
|
/**
|
|
1298
1477
|
* Switch/Toggle component with form control support
|
|
@@ -1303,7 +1482,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
1303
1482
|
* </af-switch>
|
|
1304
1483
|
*/
|
|
1305
1484
|
class AfSwitchComponent {
|
|
1306
|
-
/**
|
|
1485
|
+
/** Accessible label for icon-only or unlabeled switches. */
|
|
1486
|
+
ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
1487
|
+
/** Whether switch is disabled. */
|
|
1307
1488
|
disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
1308
1489
|
/** Checked state - supports two-way binding via [(checked)] */
|
|
1309
1490
|
checked = model(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
|
|
@@ -1328,7 +1509,7 @@ class AfSwitchComponent {
|
|
|
1328
1509
|
this.disabled.set(isDisabled);
|
|
1329
1510
|
}
|
|
1330
1511
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfSwitchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1331
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.2", type: AfSwitchComponent, isStandalone: true, selector: "af-switch", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", checked: "checkedChange" }, providers: [
|
|
1512
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.2", type: AfSwitchComponent, isStandalone: true, selector: "af-switch", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", checked: "checkedChange" }, providers: [
|
|
1332
1513
|
{
|
|
1333
1514
|
provide: NG_VALUE_ACCESSOR,
|
|
1334
1515
|
useExisting: forwardRef(() => AfSwitchComponent),
|
|
@@ -1342,9 +1523,9 @@ class AfSwitchComponent {
|
|
|
1342
1523
|
role="switch"
|
|
1343
1524
|
[checked]="checked()"
|
|
1344
1525
|
[disabled]="disabled()"
|
|
1526
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
1345
1527
|
(change)="onChange($event)"
|
|
1346
|
-
(blur)="onTouched()"
|
|
1347
|
-
/>
|
|
1528
|
+
(blur)="onTouched()" />
|
|
1348
1529
|
<span class="ct-switch__label">
|
|
1349
1530
|
<ng-content></ng-content>
|
|
1350
1531
|
</span>
|
|
@@ -1367,57 +1548,58 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
1367
1548
|
role="switch"
|
|
1368
1549
|
[checked]="checked()"
|
|
1369
1550
|
[disabled]="disabled()"
|
|
1551
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
1370
1552
|
(change)="onChange($event)"
|
|
1371
|
-
(blur)="onTouched()"
|
|
1372
|
-
/>
|
|
1553
|
+
(blur)="onTouched()" />
|
|
1373
1554
|
<span class="ct-switch__label">
|
|
1374
1555
|
<ng-content></ng-content>
|
|
1375
1556
|
</span>
|
|
1376
1557
|
</label>
|
|
1377
1558
|
`, styles: [":host{display:block}\n"] }]
|
|
1378
|
-
}], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }] } });
|
|
1559
|
+
}], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }] } });
|
|
1379
1560
|
|
|
1380
1561
|
/**
|
|
1381
|
-
* Card component for containing content
|
|
1562
|
+
* Card component for containing content.
|
|
1563
|
+
*
|
|
1564
|
+
* When `interactive` is set the card becomes keyboard-accessible with
|
|
1565
|
+
* `role="button"`, roving `tabindex`, and Enter/Space activation.
|
|
1382
1566
|
*
|
|
1383
1567
|
* @example
|
|
1384
1568
|
* <af-card elevation="md" padding="lg">
|
|
1385
|
-
* <div header>
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1390
|
-
* </
|
|
1569
|
+
* <div header><h3>Title</h3></div>
|
|
1570
|
+
* <div body><p>Card content</p></div>
|
|
1571
|
+
* </af-card>
|
|
1572
|
+
*
|
|
1573
|
+
* <af-card interactive ariaLabel="Open project" (cardClick)="open()">
|
|
1574
|
+
* <p body>Click me</p>
|
|
1391
1575
|
* </af-card>
|
|
1392
1576
|
*/
|
|
1393
1577
|
class AfCardComponent {
|
|
1394
|
-
/**
|
|
1578
|
+
/** Makes the card interactive (clickable, keyboard-accessible). */
|
|
1395
1579
|
interactive = input(false, ...(ngDevMode ? [{ debugName: "interactive" }] : []));
|
|
1396
|
-
/** Shadow elevation level */
|
|
1580
|
+
/** Shadow elevation level. */
|
|
1397
1581
|
elevation = input(null, ...(ngDevMode ? [{ debugName: "elevation" }] : []));
|
|
1398
|
-
/** Content padding level */
|
|
1582
|
+
/** Content padding level. */
|
|
1399
1583
|
padding = input(null, ...(ngDevMode ? [{ debugName: "padding" }] : []));
|
|
1400
|
-
/**
|
|
1584
|
+
/** Accessible label for interactive cards. */
|
|
1585
|
+
ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
1586
|
+
/** Emitted when an interactive card is activated (click, Enter, or Space). */
|
|
1401
1587
|
cardClick = output();
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
}
|
|
1407
|
-
set footerContent(value) {
|
|
1408
|
-
this.hasFooter.set(!!value);
|
|
1409
|
-
}
|
|
1588
|
+
headerRef = contentChild('[header]', { ...(ngDevMode ? { debugName: "headerRef" } : {}), read: ElementRef });
|
|
1589
|
+
footerRef = contentChild('[footer]', { ...(ngDevMode ? { debugName: "footerRef" } : {}), read: ElementRef });
|
|
1590
|
+
hasHeader = computed(() => !!this.headerRef(), ...(ngDevMode ? [{ debugName: "hasHeader" }] : []));
|
|
1591
|
+
hasFooter = computed(() => !!this.footerRef(), ...(ngDevMode ? [{ debugName: "hasFooter" }] : []));
|
|
1410
1592
|
static ELEVATION_MAP = {
|
|
1411
1593
|
none: 'none',
|
|
1412
1594
|
sm: '0 1px 3px rgba(0, 0, 0, 0.08)',
|
|
1413
1595
|
md: '0 4px 12px rgba(0, 0, 0, 0.08)',
|
|
1414
|
-
lg: '0 8px 24px rgba(0, 0, 0, 0.12)'
|
|
1596
|
+
lg: '0 8px 24px rgba(0, 0, 0, 0.12)',
|
|
1415
1597
|
};
|
|
1416
1598
|
static PADDING_MAP = {
|
|
1417
1599
|
none: '0',
|
|
1418
1600
|
sm: 'var(--space-3, 0.75rem)',
|
|
1419
1601
|
md: 'var(--space-5, 1.25rem)',
|
|
1420
|
-
lg: 'var(--space-7, 2rem)'
|
|
1602
|
+
lg: 'var(--space-7, 2rem)',
|
|
1421
1603
|
};
|
|
1422
1604
|
cardClasses = computed(() => {
|
|
1423
1605
|
const classes = ['ct-card'];
|
|
@@ -1441,24 +1623,36 @@ class AfCardComponent {
|
|
|
1441
1623
|
this.cardClick.emit();
|
|
1442
1624
|
}
|
|
1443
1625
|
}
|
|
1626
|
+
onCardKeydown(event) {
|
|
1627
|
+
if (!this.interactive())
|
|
1628
|
+
return;
|
|
1629
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
1630
|
+
event.preventDefault();
|
|
1631
|
+
this.cardClick.emit();
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1444
1634
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1445
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfCardComponent, isStandalone: true, selector: "af-card", inputs: { interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, elevation: { classPropertyName: "elevation", publicName: "elevation", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardClick: "cardClick" }, queries: [{ propertyName: "
|
|
1635
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfCardComponent, isStandalone: true, selector: "af-card", inputs: { interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, elevation: { classPropertyName: "elevation", publicName: "elevation", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardClick: "cardClick" }, queries: [{ propertyName: "headerRef", first: true, predicate: ["[header]"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "footerRef", first: true, predicate: ["[footer]"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: `
|
|
1446
1636
|
<section
|
|
1447
1637
|
[class]="cardClasses()"
|
|
1448
1638
|
[style]="cardStyles()"
|
|
1449
|
-
|
|
1639
|
+
[attr.role]="interactive() ? 'button' : null"
|
|
1640
|
+
[attr.tabindex]="interactive() ? 0 : null"
|
|
1641
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
1642
|
+
(click)="onCardClick()"
|
|
1643
|
+
(keydown)="onCardKeydown($event)">
|
|
1450
1644
|
@if (hasHeader()) {
|
|
1451
1645
|
<div class="ct-card__header">
|
|
1452
|
-
<ng-content select="[header]"
|
|
1646
|
+
<ng-content select="[header]" />
|
|
1453
1647
|
</div>
|
|
1454
1648
|
}
|
|
1455
1649
|
<div class="ct-card__body">
|
|
1456
|
-
<ng-content select="[body]"
|
|
1457
|
-
<ng-content
|
|
1650
|
+
<ng-content select="[body]" />
|
|
1651
|
+
<ng-content />
|
|
1458
1652
|
</div>
|
|
1459
1653
|
@if (hasFooter()) {
|
|
1460
1654
|
<div class="ct-card__footer">
|
|
1461
|
-
<ng-content select="[footer]"
|
|
1655
|
+
<ng-content select="[footer]" />
|
|
1462
1656
|
</div>
|
|
1463
1657
|
}
|
|
1464
1658
|
</section>
|
|
@@ -1470,30 +1664,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
1470
1664
|
<section
|
|
1471
1665
|
[class]="cardClasses()"
|
|
1472
1666
|
[style]="cardStyles()"
|
|
1473
|
-
|
|
1667
|
+
[attr.role]="interactive() ? 'button' : null"
|
|
1668
|
+
[attr.tabindex]="interactive() ? 0 : null"
|
|
1669
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
1670
|
+
(click)="onCardClick()"
|
|
1671
|
+
(keydown)="onCardKeydown($event)">
|
|
1474
1672
|
@if (hasHeader()) {
|
|
1475
1673
|
<div class="ct-card__header">
|
|
1476
|
-
<ng-content select="[header]"
|
|
1674
|
+
<ng-content select="[header]" />
|
|
1477
1675
|
</div>
|
|
1478
1676
|
}
|
|
1479
1677
|
<div class="ct-card__body">
|
|
1480
|
-
<ng-content select="[body]"
|
|
1481
|
-
<ng-content
|
|
1678
|
+
<ng-content select="[body]" />
|
|
1679
|
+
<ng-content />
|
|
1482
1680
|
</div>
|
|
1483
1681
|
@if (hasFooter()) {
|
|
1484
1682
|
<div class="ct-card__footer">
|
|
1485
|
-
<ng-content select="[footer]"
|
|
1683
|
+
<ng-content select="[footer]" />
|
|
1486
1684
|
</div>
|
|
1487
1685
|
}
|
|
1488
1686
|
</section>
|
|
1489
1687
|
`, styles: [":host{display:block}\n"] }]
|
|
1490
|
-
}], propDecorators: { interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], elevation: [{ type: i0.Input, args: [{ isSignal: true, alias: "elevation", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }],
|
|
1491
|
-
type: ContentChild,
|
|
1492
|
-
args: ['[header]', { read: ElementRef }]
|
|
1493
|
-
}], footerContent: [{
|
|
1494
|
-
type: ContentChild,
|
|
1495
|
-
args: ['[footer]', { read: ElementRef }]
|
|
1496
|
-
}] } });
|
|
1688
|
+
}], propDecorators: { interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], elevation: [{ type: i0.Input, args: [{ isSignal: true, alias: "elevation", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }], headerRef: [{ type: i0.ContentChild, args: ['[header]', { ...{ read: ElementRef }, isSignal: true }] }], footerRef: [{ type: i0.ContentChild, args: ['[footer]', { ...{ read: ElementRef }, isSignal: true }] }] } });
|
|
1497
1689
|
|
|
1498
1690
|
/**
|
|
1499
1691
|
* Directive for defining custom cell templates in AfDataTableComponent.
|
|
@@ -1721,6 +1913,85 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
1721
1913
|
args: [{ selector: 'af-data-table', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], template: "<div class=\"ct-data-table ct-data-table--simple\">\n <div class=\"ct-data-table__table\">\n <table [class]=\"tableClasses()\">\n <thead>\n <tr>\n @if (isSelectable()) {\n <th scope=\"col\" class=\"ct-table__cell--checkbox\">\n <label class=\"ct-check\">\n <input\n class=\"ct-check__input\"\n type=\"checkbox\"\n [checked]=\"allSelected\"\n [indeterminate]=\"someSelected\"\n (change)=\"toggleAll($any($event.target).checked)\"\n aria-label=\"Select all rows\" />\n </label>\n </th>\n }\n @for (col of columns(); track col.key) {\n <th scope=\"col\" [attr.aria-sort]=\"getAriaSort(col)\">\n @if (col.sortable) {\n <button class=\"ct-table__sort\" type=\"button\" (click)=\"toggleSort(col)\">\n {{ col.header }}\n <span class=\"ct-table__sort-indicator\" aria-hidden=\"true\"></span>\n </button>\n } @else {\n {{ col.header }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of sortedData(); track row) {\n <tr (click)=\"onRowClick(row)\">\n @if (isSelectable()) {\n <td class=\"ct-table__cell--checkbox\">\n <label class=\"ct-check\">\n <input\n class=\"ct-check__input\"\n type=\"checkbox\"\n [checked]=\"isSelected(row)\"\n (change)=\"toggleSelection(row, $event)\"\n aria-label=\"Select row\" />\n </label>\n </td>\n }\n @for (col of columns(); track col.key) {\n <td [class]=\"col.cellClass || ''\">\n @if (getCellTemplate(col.key); as tmpl) {\n <ng-container *ngTemplateOutlet=\"tmpl; context: { $implicit: row, column: col }\"></ng-container>\n } @else {\n {{ row[col.key] }}\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n</div>\n", styles: [":host{display:block}.ct-data-table__table tr{cursor:pointer}.ct-data-table__table tr:hover{background-color:var(--color-neutral-50)}\n"] }]
|
|
1722
1914
|
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], rowId: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowId", required: false }] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], cellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => AfCellDefDirective), { isSignal: true }] }] } });
|
|
1723
1915
|
|
|
1916
|
+
const FOCUSABLE_SELECTORS = [
|
|
1917
|
+
'a[href]',
|
|
1918
|
+
'area[href]',
|
|
1919
|
+
'button:not([disabled])',
|
|
1920
|
+
'input:not([disabled])',
|
|
1921
|
+
'select:not([disabled])',
|
|
1922
|
+
'textarea:not([disabled])',
|
|
1923
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
1924
|
+
].join(',');
|
|
1925
|
+
/**
|
|
1926
|
+
* Manages focus trapping within a container element.
|
|
1927
|
+
*
|
|
1928
|
+
* Handles Tab/Shift+Tab cycling, save/restore of the previously focused
|
|
1929
|
+
* element, and initial focus placement. Create one instance per overlay
|
|
1930
|
+
* (modal, drawer, popover, etc.).
|
|
1931
|
+
*/
|
|
1932
|
+
class FocusTrap {
|
|
1933
|
+
previousActiveElement = null;
|
|
1934
|
+
/** Saves the currently focused element for later restoration. */
|
|
1935
|
+
saveFocus() {
|
|
1936
|
+
this.previousActiveElement = document.activeElement;
|
|
1937
|
+
}
|
|
1938
|
+
/** Restores focus to the element saved via `saveFocus()`. */
|
|
1939
|
+
restoreFocus() {
|
|
1940
|
+
if (this.previousActiveElement) {
|
|
1941
|
+
this.previousActiveElement.focus();
|
|
1942
|
+
this.previousActiveElement = null;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
/** Overrides the saved focus target (e.g. to return focus to a specific trigger). */
|
|
1946
|
+
setReturnFocus(element) {
|
|
1947
|
+
this.previousActiveElement = element;
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Focuses the first focusable element inside the container,
|
|
1951
|
+
* or the fallback element if no focusable children exist.
|
|
1952
|
+
*/
|
|
1953
|
+
focusFirst(container, fallback) {
|
|
1954
|
+
const elements = queryFocusableElements(container);
|
|
1955
|
+
const first = elements[0];
|
|
1956
|
+
if (first) {
|
|
1957
|
+
first.focus();
|
|
1958
|
+
}
|
|
1959
|
+
else {
|
|
1960
|
+
fallback?.focus();
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Handles a Tab keydown event to trap focus within the container.
|
|
1965
|
+
* Call this from a `(keydown)` handler when the key is `Tab`.
|
|
1966
|
+
*/
|
|
1967
|
+
handleTab(event, container, fallback) {
|
|
1968
|
+
const elements = queryFocusableElements(container);
|
|
1969
|
+
if (elements.length === 0) {
|
|
1970
|
+
event.preventDefault();
|
|
1971
|
+
fallback?.focus();
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
const first = elements[0];
|
|
1975
|
+
const last = elements[elements.length - 1];
|
|
1976
|
+
const active = document.activeElement;
|
|
1977
|
+
if (event.shiftKey && active === first) {
|
|
1978
|
+
event.preventDefault();
|
|
1979
|
+
last.focus();
|
|
1980
|
+
}
|
|
1981
|
+
else if (!event.shiftKey && active === last) {
|
|
1982
|
+
event.preventDefault();
|
|
1983
|
+
first.focus();
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
/** Queries all visible, enabled, focusable elements within a container. */
|
|
1988
|
+
function queryFocusableElements(container) {
|
|
1989
|
+
if (!container)
|
|
1990
|
+
return [];
|
|
1991
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTORS)).filter((el) => !el.hasAttribute('disabled') &&
|
|
1992
|
+
el.getAttribute('aria-hidden') !== 'true');
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1724
1995
|
/**
|
|
1725
1996
|
* Modal/Dialog component with accessibility features
|
|
1726
1997
|
*
|
|
@@ -1740,6 +2011,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
1740
2011
|
*/
|
|
1741
2012
|
class AfModalComponent {
|
|
1742
2013
|
static nextId = 0;
|
|
2014
|
+
focusTrap = new FocusTrap();
|
|
1743
2015
|
/** Whether modal is open */
|
|
1744
2016
|
open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
1745
2017
|
/** Modal title */
|
|
@@ -1752,12 +2024,8 @@ class AfModalComponent {
|
|
|
1752
2024
|
closed = output();
|
|
1753
2025
|
/** Unique title ID for aria-labelledby */
|
|
1754
2026
|
titleId = `af-modal-title-${AfModalComponent.nextId++}`;
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
this.hasFooter.set(!!value);
|
|
1758
|
-
}
|
|
1759
|
-
previousActiveElement = null;
|
|
1760
|
-
focusableElements = [];
|
|
2027
|
+
footerRef = contentChild('[footer]', { ...(ngDevMode ? { debugName: "footerRef" } : {}), read: ElementRef });
|
|
2028
|
+
hasFooter = computed(() => !!this.footerRef(), ...(ngDevMode ? [{ debugName: "hasFooter" }] : []));
|
|
1761
2029
|
viewInitialized = signal(false, ...(ngDevMode ? [{ debugName: "viewInitialized" }] : []));
|
|
1762
2030
|
dialogRef = viewChild('dialog', ...(ngDevMode ? [{ debugName: "dialogRef" }] : []));
|
|
1763
2031
|
openEffect = effect(() => {
|
|
@@ -1767,14 +2035,14 @@ class AfModalComponent {
|
|
|
1767
2035
|
this.onOpen();
|
|
1768
2036
|
}
|
|
1769
2037
|
else if (!isOpen) {
|
|
1770
|
-
this.restoreFocus();
|
|
2038
|
+
this.focusTrap.restoreFocus();
|
|
1771
2039
|
}
|
|
1772
2040
|
}, ...(ngDevMode ? [{ debugName: "openEffect" }] : []));
|
|
1773
2041
|
ngAfterViewInit() {
|
|
1774
2042
|
this.viewInitialized.set(true);
|
|
1775
2043
|
}
|
|
1776
2044
|
ngOnDestroy() {
|
|
1777
|
-
this.restoreFocus();
|
|
2045
|
+
this.focusTrap.restoreFocus();
|
|
1778
2046
|
}
|
|
1779
2047
|
onEscapeKey() {
|
|
1780
2048
|
if (this.open()) {
|
|
@@ -1784,23 +2052,7 @@ class AfModalComponent {
|
|
|
1784
2052
|
onKeydown(event) {
|
|
1785
2053
|
if (!this.open() || event.key !== 'Tab')
|
|
1786
2054
|
return;
|
|
1787
|
-
this.
|
|
1788
|
-
if (this.focusableElements.length === 0) {
|
|
1789
|
-
event.preventDefault();
|
|
1790
|
-
this.dialogRef()?.nativeElement.focus();
|
|
1791
|
-
return;
|
|
1792
|
-
}
|
|
1793
|
-
const first = this.focusableElements[0];
|
|
1794
|
-
const last = this.focusableElements[this.focusableElements.length - 1];
|
|
1795
|
-
const active = document.activeElement;
|
|
1796
|
-
if (event.shiftKey && active === first) {
|
|
1797
|
-
event.preventDefault();
|
|
1798
|
-
last.focus();
|
|
1799
|
-
}
|
|
1800
|
-
else if (!event.shiftKey && active === last) {
|
|
1801
|
-
event.preventDefault();
|
|
1802
|
-
first.focus();
|
|
1803
|
-
}
|
|
2055
|
+
this.focusTrap.handleTab(event, this.dialogRef()?.nativeElement, this.dialogRef()?.nativeElement);
|
|
1804
2056
|
}
|
|
1805
2057
|
onBackdropClick(event) {
|
|
1806
2058
|
if (this.closeOnBackdropClick() && event.target === event.currentTarget) {
|
|
@@ -1811,46 +2063,15 @@ class AfModalComponent {
|
|
|
1811
2063
|
this.closed.emit();
|
|
1812
2064
|
}
|
|
1813
2065
|
onOpen() {
|
|
1814
|
-
this.
|
|
1815
|
-
this.refreshFocusableElements();
|
|
2066
|
+
this.focusTrap.saveFocus();
|
|
1816
2067
|
queueMicrotask(() => {
|
|
1817
2068
|
if (!this.open())
|
|
1818
2069
|
return;
|
|
1819
|
-
|
|
1820
|
-
if (first) {
|
|
1821
|
-
first.focus();
|
|
1822
|
-
}
|
|
1823
|
-
else {
|
|
1824
|
-
this.dialogRef()?.nativeElement.focus();
|
|
1825
|
-
}
|
|
2070
|
+
this.focusTrap.focusFirst(this.dialogRef()?.nativeElement, this.dialogRef()?.nativeElement);
|
|
1826
2071
|
});
|
|
1827
2072
|
}
|
|
1828
|
-
restoreFocus() {
|
|
1829
|
-
if (this.previousActiveElement) {
|
|
1830
|
-
this.previousActiveElement.focus();
|
|
1831
|
-
this.previousActiveElement = null;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
refreshFocusableElements() {
|
|
1835
|
-
const dialog = this.dialogRef()?.nativeElement;
|
|
1836
|
-
if (!dialog) {
|
|
1837
|
-
this.focusableElements = [];
|
|
1838
|
-
return;
|
|
1839
|
-
}
|
|
1840
|
-
const selectors = [
|
|
1841
|
-
'a[href]',
|
|
1842
|
-
'area[href]',
|
|
1843
|
-
'button:not([disabled])',
|
|
1844
|
-
'input:not([disabled])',
|
|
1845
|
-
'select:not([disabled])',
|
|
1846
|
-
'textarea:not([disabled])',
|
|
1847
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
1848
|
-
];
|
|
1849
|
-
this.focusableElements = Array.from(dialog.querySelectorAll(selectors.join(',')))
|
|
1850
|
-
.filter(el => !el.hasAttribute('disabled') && el.getAttribute('aria-hidden') !== 'true');
|
|
1851
|
-
}
|
|
1852
2073
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1853
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfModalComponent, isStandalone: true, selector: "af-modal", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscapeKey()", "document:keydown": "onKeydown($event)" } }, queries: [{ propertyName: "
|
|
2074
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfModalComponent, isStandalone: true, selector: "af-modal", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscapeKey()", "document:keydown": "onKeydown($event)" } }, queries: [{ propertyName: "footerRef", first: true, predicate: ["[footer]"], descendants: true, read: ElementRef, isSignal: true }], viewQueries: [{ propertyName: "dialogRef", first: true, predicate: ["dialog"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1854
2075
|
@if (open()) {
|
|
1855
2076
|
<div
|
|
1856
2077
|
class="ct-modal"
|
|
@@ -1932,10 +2153,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
1932
2153
|
</div>
|
|
1933
2154
|
}
|
|
1934
2155
|
`, styles: [":host{display:contents}\n"] }]
|
|
1935
|
-
}], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }],
|
|
1936
|
-
type: ContentChild,
|
|
1937
|
-
args: ['[footer]', { read: ElementRef }]
|
|
1938
|
-
}], dialogRef: [{ type: i0.ViewChild, args: ['dialog', { isSignal: true }] }] } });
|
|
2156
|
+
}], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], footerRef: [{ type: i0.ContentChild, args: ['[footer]', { ...{ read: ElementRef }, isSignal: true }] }], dialogRef: [{ type: i0.ViewChild, args: ['dialog', { isSignal: true }] }] } });
|
|
1939
2157
|
|
|
1940
2158
|
/**
|
|
1941
2159
|
* Toast notification service
|
|
@@ -2297,7 +2515,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
2297
2515
|
}], propDecorators: { activeTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTab", required: false }] }, { type: i0.Output, args: ["activeTabChange"] }], panels: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => AfTabPanelComponent), { isSignal: true }] }], tabButtons: [{ type: i0.ViewChildren, args: ['tabButton', { isSignal: true }] }] } });
|
|
2298
2516
|
|
|
2299
2517
|
/**
|
|
2300
|
-
* Dropdown menu component
|
|
2518
|
+
* Dropdown menu component implementing the WAI-ARIA Menu Pattern.
|
|
2519
|
+
*
|
|
2520
|
+
* Provides full keyboard navigation (Arrow keys, Home/End, type-ahead),
|
|
2521
|
+
* proper ARIA roles (`menu` / `menuitem`), and roving tabindex focus management.
|
|
2301
2522
|
*
|
|
2302
2523
|
* @example
|
|
2303
2524
|
* <af-dropdown
|
|
@@ -2308,19 +2529,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
2308
2529
|
*/
|
|
2309
2530
|
class AfDropdownComponent {
|
|
2310
2531
|
static nextId = 0;
|
|
2311
|
-
/** Dropdown button label */
|
|
2532
|
+
/** Dropdown button label. */
|
|
2312
2533
|
label = input('Actions', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
2313
|
-
/** Menu items */
|
|
2534
|
+
/** Menu items. */
|
|
2314
2535
|
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
2315
|
-
/**
|
|
2536
|
+
/** Emits the selected item's value. */
|
|
2316
2537
|
itemSelected = output();
|
|
2317
2538
|
triggerRef = viewChild('trigger', ...(ngDevMode ? [{ debugName: "triggerRef" }] : []));
|
|
2539
|
+
menuRef = viewChild('menu', ...(ngDevMode ? [{ debugName: "menuRef" }] : []));
|
|
2318
2540
|
itemButtons = viewChildren('itemButton', ...(ngDevMode ? [{ debugName: "itemButtons" }] : []));
|
|
2319
2541
|
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
2320
|
-
|
|
2542
|
+
focusedItemIndex = signal(0, ...(ngDevMode ? [{ debugName: "focusedItemIndex" }] : []));
|
|
2543
|
+
instanceId = AfDropdownComponent.nextId++;
|
|
2544
|
+
menuId = `af-dropdown-menu-${this.instanceId}`;
|
|
2545
|
+
triggerId = `af-dropdown-trigger-${this.instanceId}`;
|
|
2546
|
+
typeAheadBuffer = '';
|
|
2547
|
+
typeAheadTimer = null;
|
|
2321
2548
|
toggle() {
|
|
2322
2549
|
if (this.isOpen()) {
|
|
2323
|
-
this.close();
|
|
2550
|
+
this.close(true);
|
|
2324
2551
|
}
|
|
2325
2552
|
else {
|
|
2326
2553
|
this.open();
|
|
@@ -2332,46 +2559,180 @@ class AfDropdownComponent {
|
|
|
2332
2559
|
this.close(true);
|
|
2333
2560
|
}
|
|
2334
2561
|
}
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2562
|
+
/** Handles keyboard events on the trigger button. */
|
|
2563
|
+
onTriggerKeydown(event) {
|
|
2564
|
+
switch (event.key) {
|
|
2565
|
+
case 'ArrowDown':
|
|
2566
|
+
case 'Enter':
|
|
2567
|
+
case ' ':
|
|
2568
|
+
event.preventDefault();
|
|
2569
|
+
if (!this.isOpen()) {
|
|
2570
|
+
this.open();
|
|
2571
|
+
}
|
|
2572
|
+
break;
|
|
2573
|
+
case 'ArrowUp':
|
|
2574
|
+
event.preventDefault();
|
|
2575
|
+
if (!this.isOpen()) {
|
|
2576
|
+
this.open(true);
|
|
2577
|
+
}
|
|
2578
|
+
break;
|
|
2343
2579
|
}
|
|
2344
2580
|
}
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2581
|
+
/** Handles keyboard events within the open menu. */
|
|
2582
|
+
onMenuKeydown(event) {
|
|
2583
|
+
const actionableItems = this.getActionableItems();
|
|
2584
|
+
if (actionableItems.length === 0)
|
|
2585
|
+
return;
|
|
2586
|
+
switch (event.key) {
|
|
2587
|
+
case 'ArrowDown': {
|
|
2588
|
+
event.preventDefault();
|
|
2589
|
+
const next = this.nextEnabledIndex(this.focusedItemIndex(), 1);
|
|
2590
|
+
this.focusItem(next);
|
|
2591
|
+
break;
|
|
2592
|
+
}
|
|
2593
|
+
case 'ArrowUp': {
|
|
2594
|
+
event.preventDefault();
|
|
2595
|
+
const prev = this.nextEnabledIndex(this.focusedItemIndex(), -1);
|
|
2596
|
+
this.focusItem(prev);
|
|
2597
|
+
break;
|
|
2598
|
+
}
|
|
2599
|
+
case 'Home': {
|
|
2600
|
+
event.preventDefault();
|
|
2601
|
+
const first = this.nextEnabledIndex(-1, 1);
|
|
2602
|
+
this.focusItem(first);
|
|
2603
|
+
break;
|
|
2604
|
+
}
|
|
2605
|
+
case 'End': {
|
|
2606
|
+
event.preventDefault();
|
|
2607
|
+
const last = this.nextEnabledIndex(actionableItems.length, -1);
|
|
2608
|
+
this.focusItem(last);
|
|
2609
|
+
break;
|
|
2610
|
+
}
|
|
2611
|
+
case 'Escape':
|
|
2612
|
+
event.preventDefault();
|
|
2613
|
+
this.close(true);
|
|
2614
|
+
break;
|
|
2615
|
+
case 'Tab':
|
|
2616
|
+
this.close(false);
|
|
2617
|
+
break;
|
|
2618
|
+
case 'Enter':
|
|
2619
|
+
case ' ': {
|
|
2620
|
+
event.preventDefault();
|
|
2621
|
+
const item = actionableItems[this.focusedItemIndex()];
|
|
2622
|
+
if (item && !item.disabled) {
|
|
2623
|
+
this.selectItem(item);
|
|
2624
|
+
}
|
|
2625
|
+
break;
|
|
2626
|
+
}
|
|
2627
|
+
default:
|
|
2628
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
|
|
2629
|
+
this.handleTypeAhead(event.key);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2348
2632
|
}
|
|
2349
2633
|
onDocumentClick(event) {
|
|
2350
2634
|
const target = event.target;
|
|
2351
2635
|
if (!target.closest('.ct-dropdown')) {
|
|
2352
|
-
this.close();
|
|
2636
|
+
this.close(false);
|
|
2353
2637
|
}
|
|
2354
2638
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2639
|
+
/**
|
|
2640
|
+
* Returns the index of a non-separator item within the list of
|
|
2641
|
+
* actionable (non-separator) items.
|
|
2642
|
+
*/
|
|
2643
|
+
getActionableIndex(item) {
|
|
2644
|
+
return this.getActionableItems().indexOf(item);
|
|
2645
|
+
}
|
|
2646
|
+
open(focusLast = false) {
|
|
2647
|
+
this.isOpen.set(true);
|
|
2648
|
+
const actionableItems = this.getActionableItems();
|
|
2649
|
+
const startIndex = focusLast
|
|
2650
|
+
? this.nextEnabledIndex(actionableItems.length, -1)
|
|
2651
|
+
: this.nextEnabledIndex(-1, 1);
|
|
2652
|
+
this.focusedItemIndex.set(startIndex);
|
|
2653
|
+
queueMicrotask(() => this.focusCurrent());
|
|
2654
|
+
}
|
|
2655
|
+
close(returnFocus) {
|
|
2656
|
+
if (!this.isOpen())
|
|
2657
|
+
return;
|
|
2658
|
+
this.isOpen.set(false);
|
|
2659
|
+
this.typeAheadBuffer = '';
|
|
2660
|
+
if (returnFocus) {
|
|
2661
|
+
this.triggerRef()?.nativeElement.focus();
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
focusItem(index) {
|
|
2665
|
+
this.focusedItemIndex.set(index);
|
|
2666
|
+
this.focusCurrent();
|
|
2667
|
+
}
|
|
2668
|
+
focusCurrent() {
|
|
2669
|
+
const buttons = this.itemButtons();
|
|
2670
|
+
const idx = this.focusedItemIndex();
|
|
2671
|
+
buttons[idx]?.nativeElement.focus();
|
|
2672
|
+
}
|
|
2673
|
+
nextEnabledIndex(from, direction) {
|
|
2674
|
+
const actionableItems = this.getActionableItems();
|
|
2675
|
+
const len = actionableItems.length;
|
|
2676
|
+
if (len === 0)
|
|
2677
|
+
return 0;
|
|
2678
|
+
let idx = from + direction;
|
|
2679
|
+
for (let i = 0; i < len; i++) {
|
|
2680
|
+
if (idx < 0)
|
|
2681
|
+
idx = len - 1;
|
|
2682
|
+
if (idx >= len)
|
|
2683
|
+
idx = 0;
|
|
2684
|
+
if (!actionableItems[idx].disabled)
|
|
2685
|
+
return idx;
|
|
2686
|
+
idx += direction;
|
|
2687
|
+
}
|
|
2688
|
+
return from;
|
|
2689
|
+
}
|
|
2690
|
+
getActionableItems() {
|
|
2691
|
+
return this.items().filter((item) => !item.separator);
|
|
2692
|
+
}
|
|
2693
|
+
handleTypeAhead(char) {
|
|
2694
|
+
if (this.typeAheadTimer) {
|
|
2695
|
+
clearTimeout(this.typeAheadTimer);
|
|
2696
|
+
}
|
|
2697
|
+
this.typeAheadBuffer += char.toLowerCase();
|
|
2698
|
+
this.typeAheadTimer = setTimeout(() => {
|
|
2699
|
+
this.typeAheadBuffer = '';
|
|
2700
|
+
this.typeAheadTimer = null;
|
|
2701
|
+
}, 500);
|
|
2702
|
+
const actionableItems = this.getActionableItems();
|
|
2703
|
+
const startIndex = this.focusedItemIndex() + 1;
|
|
2704
|
+
for (let i = 0; i < actionableItems.length; i++) {
|
|
2705
|
+
const idx = (startIndex + i) % actionableItems.length;
|
|
2706
|
+
const item = actionableItems[idx];
|
|
2707
|
+
if (!item.disabled && item.label.toLowerCase().startsWith(this.typeAheadBuffer)) {
|
|
2708
|
+
this.focusItem(idx);
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2358
2711
|
}
|
|
2359
2712
|
}
|
|
2360
2713
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2361
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfDropdownComponent, isStandalone: true, selector: "af-dropdown", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelected: "itemSelected" }, host: { listeners: { "document:click": "onDocumentClick($event)",
|
|
2714
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfDropdownComponent, isStandalone: true, selector: "af-dropdown", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelected: "itemSelected" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "menuRef", first: true, predicate: ["menu"], descendants: true, isSignal: true }, { propertyName: "itemButtons", predicate: ["itemButton"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
2362
2715
|
<div class="ct-dropdown" [attr.data-state]="isOpen() ? 'open' : 'closed'">
|
|
2363
2716
|
<button
|
|
2364
2717
|
#trigger
|
|
2365
2718
|
class="ct-button ct-button--secondary ct-dropdown__trigger"
|
|
2366
2719
|
[attr.aria-expanded]="isOpen()"
|
|
2367
2720
|
[attr.aria-controls]="menuId"
|
|
2368
|
-
|
|
2721
|
+
aria-haspopup="menu"
|
|
2369
2722
|
type="button"
|
|
2370
|
-
(click)="toggle()"
|
|
2723
|
+
(click)="toggle()"
|
|
2724
|
+
(keydown)="onTriggerKeydown($event)">
|
|
2371
2725
|
{{ label() }}
|
|
2372
2726
|
</button>
|
|
2373
2727
|
@if (isOpen()) {
|
|
2374
|
-
<div
|
|
2728
|
+
<div
|
|
2729
|
+
#menu
|
|
2730
|
+
class="ct-dropdown__menu"
|
|
2731
|
+
[id]="menuId"
|
|
2732
|
+
role="menu"
|
|
2733
|
+
aria-orientation="vertical"
|
|
2734
|
+
[attr.aria-labelledby]="triggerId"
|
|
2735
|
+
(keydown)="onMenuKeydown($event)">
|
|
2375
2736
|
@for (item of items(); track $index) {
|
|
2376
2737
|
@if (item.separator) {
|
|
2377
2738
|
<div class="ct-dropdown__separator" role="separator"></div>
|
|
@@ -2379,8 +2740,9 @@ class AfDropdownComponent {
|
|
|
2379
2740
|
<button
|
|
2380
2741
|
#itemButton
|
|
2381
2742
|
class="ct-dropdown__item"
|
|
2382
|
-
|
|
2383
|
-
[attr.
|
|
2743
|
+
role="menuitem"
|
|
2744
|
+
[attr.tabindex]="focusedItemIndex() === getActionableIndex(item) ? 0 : -1"
|
|
2745
|
+
[attr.aria-disabled]="item.disabled ? 'true' : null"
|
|
2384
2746
|
type="button"
|
|
2385
2747
|
(click)="selectItem(item)">
|
|
2386
2748
|
{{ item.label }}
|
|
@@ -2396,7 +2758,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
2396
2758
|
type: Component,
|
|
2397
2759
|
args: [{ selector: 'af-dropdown', changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
2398
2760
|
'(document:click)': 'onDocumentClick($event)',
|
|
2399
|
-
'(document:keydown.escape)': 'onEscape()',
|
|
2400
2761
|
}, template: `
|
|
2401
2762
|
<div class="ct-dropdown" [attr.data-state]="isOpen() ? 'open' : 'closed'">
|
|
2402
2763
|
<button
|
|
@@ -2404,13 +2765,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
2404
2765
|
class="ct-button ct-button--secondary ct-dropdown__trigger"
|
|
2405
2766
|
[attr.aria-expanded]="isOpen()"
|
|
2406
2767
|
[attr.aria-controls]="menuId"
|
|
2407
|
-
|
|
2768
|
+
aria-haspopup="menu"
|
|
2408
2769
|
type="button"
|
|
2409
|
-
(click)="toggle()"
|
|
2770
|
+
(click)="toggle()"
|
|
2771
|
+
(keydown)="onTriggerKeydown($event)">
|
|
2410
2772
|
{{ label() }}
|
|
2411
2773
|
</button>
|
|
2412
2774
|
@if (isOpen()) {
|
|
2413
|
-
<div
|
|
2775
|
+
<div
|
|
2776
|
+
#menu
|
|
2777
|
+
class="ct-dropdown__menu"
|
|
2778
|
+
[id]="menuId"
|
|
2779
|
+
role="menu"
|
|
2780
|
+
aria-orientation="vertical"
|
|
2781
|
+
[attr.aria-labelledby]="triggerId"
|
|
2782
|
+
(keydown)="onMenuKeydown($event)">
|
|
2414
2783
|
@for (item of items(); track $index) {
|
|
2415
2784
|
@if (item.separator) {
|
|
2416
2785
|
<div class="ct-dropdown__separator" role="separator"></div>
|
|
@@ -2418,8 +2787,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
2418
2787
|
<button
|
|
2419
2788
|
#itemButton
|
|
2420
2789
|
class="ct-dropdown__item"
|
|
2421
|
-
|
|
2422
|
-
[attr.
|
|
2790
|
+
role="menuitem"
|
|
2791
|
+
[attr.tabindex]="focusedItemIndex() === getActionableIndex(item) ? 0 : -1"
|
|
2792
|
+
[attr.aria-disabled]="item.disabled ? 'true' : null"
|
|
2423
2793
|
type="button"
|
|
2424
2794
|
(click)="selectItem(item)">
|
|
2425
2795
|
{{ item.label }}
|
|
@@ -2430,7 +2800,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
2430
2800
|
}
|
|
2431
2801
|
</div>
|
|
2432
2802
|
`, styles: [":host{display:inline-block}\n"] }]
|
|
2433
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], itemButtons: [{ type: i0.ViewChildren, args: ['itemButton', { isSignal: true }] }] } });
|
|
2803
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], menuRef: [{ type: i0.ViewChild, args: ['menu', { isSignal: true }] }], itemButtons: [{ type: i0.ViewChildren, args: ['itemButton', { isSignal: true }] }] } });
|
|
2434
2804
|
|
|
2435
2805
|
/**
|
|
2436
2806
|
* Pagination component
|
|
@@ -3490,7 +3860,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
3490
3860
|
* <af-badge variant="danger">Blocked</af-badge>
|
|
3491
3861
|
*/
|
|
3492
3862
|
class AfBadgeComponent {
|
|
3493
|
-
/**
|
|
3863
|
+
/** Accessible label, useful when the badge has no visible text. */
|
|
3864
|
+
ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
3865
|
+
/** Color variant. */
|
|
3494
3866
|
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : []));
|
|
3495
3867
|
/** Icon character to display */
|
|
3496
3868
|
icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : []));
|
|
@@ -3507,32 +3879,32 @@ class AfBadgeComponent {
|
|
|
3507
3879
|
return classes.join(' ');
|
|
3508
3880
|
}, ...(ngDevMode ? [{ debugName: "badgeClasses" }] : []));
|
|
3509
3881
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3510
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfBadgeComponent, isStandalone: true, selector: "af-badge", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, dot: { classPropertyName: "dot", publicName: "dot", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
3511
|
-
<span [class]="badgeClasses()">
|
|
3882
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfBadgeComponent, isStandalone: true, selector: "af-badge", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, dot: { classPropertyName: "dot", publicName: "dot", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
3883
|
+
<span [class]="badgeClasses()" [attr.aria-label]="ariaLabel() || null">
|
|
3512
3884
|
@if (icon()) {
|
|
3513
3885
|
<span class="ct-badge__icon" aria-hidden="true">{{ icon() }}</span>
|
|
3514
3886
|
}
|
|
3515
3887
|
@if (dot()) {
|
|
3516
3888
|
<span class="ct-badge__dot" aria-hidden="true"></span>
|
|
3517
3889
|
}
|
|
3518
|
-
<ng-content
|
|
3890
|
+
<ng-content />
|
|
3519
3891
|
</span>
|
|
3520
3892
|
`, isInline: true, styles: [":host{display:inline-block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3521
3893
|
}
|
|
3522
3894
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfBadgeComponent, decorators: [{
|
|
3523
3895
|
type: Component,
|
|
3524
3896
|
args: [{ selector: 'af-badge', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3525
|
-
<span [class]="badgeClasses()">
|
|
3897
|
+
<span [class]="badgeClasses()" [attr.aria-label]="ariaLabel() || null">
|
|
3526
3898
|
@if (icon()) {
|
|
3527
3899
|
<span class="ct-badge__icon" aria-hidden="true">{{ icon() }}</span>
|
|
3528
3900
|
}
|
|
3529
3901
|
@if (dot()) {
|
|
3530
3902
|
<span class="ct-badge__dot" aria-hidden="true"></span>
|
|
3531
3903
|
}
|
|
3532
|
-
<ng-content
|
|
3904
|
+
<ng-content />
|
|
3533
3905
|
</span>
|
|
3534
3906
|
`, styles: [":host{display:inline-block}\n"] }]
|
|
3535
|
-
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }] } });
|
|
3907
|
+
}], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }] } });
|
|
3536
3908
|
|
|
3537
3909
|
/**
|
|
3538
3910
|
* Progress bar for showing completion state
|
|
@@ -4130,6 +4502,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
4130
4502
|
*/
|
|
4131
4503
|
class AfDrawerComponent {
|
|
4132
4504
|
static nextId = 0;
|
|
4505
|
+
focusTrap = new FocusTrap();
|
|
4133
4506
|
/** Two-way bindable open state */
|
|
4134
4507
|
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
4135
4508
|
/** Slide-in position */
|
|
@@ -4149,8 +4522,6 @@ class AfDrawerComponent {
|
|
|
4149
4522
|
/** Unique ID for aria-labelledby fallback */
|
|
4150
4523
|
titleId = `af-drawer-title-${AfDrawerComponent.nextId++}`;
|
|
4151
4524
|
panelRef = viewChild('panel', ...(ngDevMode ? [{ debugName: "panelRef" }] : []));
|
|
4152
|
-
previousActiveElement = null;
|
|
4153
|
-
focusableElements = [];
|
|
4154
4525
|
containerClasses = computed(() => {
|
|
4155
4526
|
const classes = ['ct-drawer'];
|
|
4156
4527
|
const pos = this.position();
|
|
@@ -4174,7 +4545,7 @@ class AfDrawerComponent {
|
|
|
4174
4545
|
}, ...(ngDevMode ? [{ debugName: "openEffect" }] : []));
|
|
4175
4546
|
ngOnDestroy() {
|
|
4176
4547
|
this.unlockBodyScroll();
|
|
4177
|
-
this.restoreFocus();
|
|
4548
|
+
this.focusTrap.restoreFocus();
|
|
4178
4549
|
}
|
|
4179
4550
|
/** Closes the drawer and emits the closed event */
|
|
4180
4551
|
close() {
|
|
@@ -4194,53 +4565,23 @@ class AfDrawerComponent {
|
|
|
4194
4565
|
return;
|
|
4195
4566
|
}
|
|
4196
4567
|
if (event.key === 'Tab') {
|
|
4197
|
-
this.
|
|
4568
|
+
const panel = this.panelRef()?.nativeElement;
|
|
4569
|
+
this.focusTrap.handleTab(event, panel, panel);
|
|
4198
4570
|
}
|
|
4199
4571
|
}
|
|
4200
4572
|
onOpen() {
|
|
4201
|
-
this.
|
|
4573
|
+
this.focusTrap.saveFocus();
|
|
4202
4574
|
this.lockBodyScroll();
|
|
4203
4575
|
queueMicrotask(() => {
|
|
4204
4576
|
if (!this.open())
|
|
4205
4577
|
return;
|
|
4206
|
-
this.
|
|
4207
|
-
|
|
4208
|
-
if (first) {
|
|
4209
|
-
first.focus();
|
|
4210
|
-
}
|
|
4211
|
-
else {
|
|
4212
|
-
this.panelRef()?.nativeElement.focus();
|
|
4213
|
-
}
|
|
4578
|
+
const panel = this.panelRef()?.nativeElement;
|
|
4579
|
+
this.focusTrap.focusFirst(panel, panel);
|
|
4214
4580
|
});
|
|
4215
4581
|
}
|
|
4216
4582
|
onClose() {
|
|
4217
4583
|
this.unlockBodyScroll();
|
|
4218
|
-
this.restoreFocus();
|
|
4219
|
-
}
|
|
4220
|
-
trapFocus(event) {
|
|
4221
|
-
this.refreshFocusableElements();
|
|
4222
|
-
if (this.focusableElements.length === 0) {
|
|
4223
|
-
event.preventDefault();
|
|
4224
|
-
this.panelRef()?.nativeElement.focus();
|
|
4225
|
-
return;
|
|
4226
|
-
}
|
|
4227
|
-
const first = this.focusableElements[0];
|
|
4228
|
-
const last = this.focusableElements[this.focusableElements.length - 1];
|
|
4229
|
-
const active = document.activeElement;
|
|
4230
|
-
if (event.shiftKey && active === first) {
|
|
4231
|
-
event.preventDefault();
|
|
4232
|
-
last.focus();
|
|
4233
|
-
}
|
|
4234
|
-
else if (!event.shiftKey && active === last) {
|
|
4235
|
-
event.preventDefault();
|
|
4236
|
-
first.focus();
|
|
4237
|
-
}
|
|
4238
|
-
}
|
|
4239
|
-
restoreFocus() {
|
|
4240
|
-
if (this.previousActiveElement) {
|
|
4241
|
-
this.previousActiveElement.focus();
|
|
4242
|
-
this.previousActiveElement = null;
|
|
4243
|
-
}
|
|
4584
|
+
this.focusTrap.restoreFocus();
|
|
4244
4585
|
}
|
|
4245
4586
|
lockBodyScroll() {
|
|
4246
4587
|
document.body.style.overflow = 'hidden';
|
|
@@ -4248,24 +4589,6 @@ class AfDrawerComponent {
|
|
|
4248
4589
|
unlockBodyScroll() {
|
|
4249
4590
|
document.body.style.overflow = '';
|
|
4250
4591
|
}
|
|
4251
|
-
refreshFocusableElements() {
|
|
4252
|
-
const panel = this.panelRef()?.nativeElement;
|
|
4253
|
-
if (!panel) {
|
|
4254
|
-
this.focusableElements = [];
|
|
4255
|
-
return;
|
|
4256
|
-
}
|
|
4257
|
-
const selectors = [
|
|
4258
|
-
'a[href]',
|
|
4259
|
-
'area[href]',
|
|
4260
|
-
'button:not([disabled])',
|
|
4261
|
-
'input:not([disabled])',
|
|
4262
|
-
'select:not([disabled])',
|
|
4263
|
-
'textarea:not([disabled])',
|
|
4264
|
-
'[tabindex]:not([tabindex="-1"])',
|
|
4265
|
-
];
|
|
4266
|
-
this.focusableElements = Array.from(panel.querySelectorAll(selectors.join(','))).filter((el) => !el.hasAttribute('disabled') &&
|
|
4267
|
-
el.getAttribute('aria-hidden') !== 'true');
|
|
4268
|
-
}
|
|
4269
4592
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4270
4593
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfDrawerComponent, isStandalone: true, selector: "af-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeButtonAriaLabel: { classPropertyName: "closeButtonAriaLabel", publicName: "closeButtonAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", closed: "closed" }, host: { listeners: { "document:keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
4271
4594
|
<div
|
|
@@ -5234,7 +5557,7 @@ class AfFileUploadComponent {
|
|
|
5234
5557
|
{{ liveAnnouncement() }}
|
|
5235
5558
|
</span>
|
|
5236
5559
|
</div>
|
|
5237
|
-
`, isInline: true, styles: [":host{display:block}.af-file-upload__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "component", type: AfButtonComponent, selector: "af-button", inputs: ["variant", "size", "type", "disabled", "iconOnly", "ariaLabel", "title"], outputs: ["clicked"] }, { kind: "component", type: AfBadgeComponent, selector: "af-badge", inputs: ["variant", "icon", "dot"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5560
|
+
`, isInline: true, styles: [":host{display:block}.af-file-upload__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "component", type: AfButtonComponent, selector: "af-button", inputs: ["variant", "size", "type", "disabled", "iconOnly", "ariaLabel", "title"], outputs: ["clicked"] }, { kind: "component", type: AfBadgeComponent, selector: "af-badge", inputs: ["ariaLabel", "variant", "icon", "dot"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5238
5561
|
}
|
|
5239
5562
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfFileUploadComponent, decorators: [{
|
|
5240
5563
|
type: Component,
|
|
@@ -5872,17 +6195,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
5872
6195
|
|
|
5873
6196
|
let nextId = 0;
|
|
5874
6197
|
/**
|
|
5875
|
-
* Individual navigation item used within af-navbar.
|
|
6198
|
+
* Individual navigation item used within af-navbar or af-toolbar.
|
|
6199
|
+
*
|
|
6200
|
+
* Supports Angular Router via `routerLink`, standard links via `href`,
|
|
6201
|
+
* and button mode when neither is provided. Content projection allows
|
|
6202
|
+
* icons and custom markup inside the link.
|
|
5876
6203
|
*
|
|
5877
6204
|
* @example
|
|
5878
|
-
* <af-nav-item label="Dashboard"
|
|
6205
|
+
* <af-nav-item label="Dashboard" routerLink="/dashboard">
|
|
6206
|
+
* <af-icon name="dashboard" /> Dashboard
|
|
6207
|
+
* </af-nav-item>
|
|
5879
6208
|
*/
|
|
5880
6209
|
class AfNavItemComponent {
|
|
5881
|
-
/** Text label
|
|
6210
|
+
/** Text label shown as fallback when no content is projected. Also used by the mobile menu. */
|
|
5882
6211
|
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
5883
6212
|
/** URL for the navigation link. Renders as `<a>` when provided, `<button>` otherwise. */
|
|
5884
6213
|
href = input('', ...(ngDevMode ? [{ debugName: "href" }] : []));
|
|
5885
|
-
/**
|
|
6214
|
+
/** Angular Router link. Renders as `<a>` with routerLink and auto-active detection. */
|
|
6215
|
+
routerLink = input(null, ...(ngDevMode ? [{ debugName: "routerLink" }] : []));
|
|
6216
|
+
/** Marks this item as the currently active page (used for href/button mode, routerLink auto-detects). */
|
|
5886
6217
|
active = input(false, { ...(ngDevMode ? { debugName: "active" } : {}), transform: booleanAttribute });
|
|
5887
6218
|
/** Disables interaction with this item. */
|
|
5888
6219
|
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}), transform: booleanAttribute });
|
|
@@ -5903,76 +6234,106 @@ class AfNavItemComponent {
|
|
|
5903
6234
|
this.clicked.emit(event);
|
|
5904
6235
|
}
|
|
5905
6236
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfNavItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5906
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfNavItemComponent, isStandalone: true, selector: "af-nav-item", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, viewQueries: [{ propertyName: "linkRef", first: true, predicate: ["linkEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
6237
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfNavItemComponent, isStandalone: true, selector: "af-nav-item", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, viewQueries: [{ propertyName: "linkRef", first: true, predicate: ["linkEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
5907
6238
|
<li class="ct-navbar__item" role="none">
|
|
5908
|
-
@if (
|
|
6239
|
+
@if (routerLink()) {
|
|
6240
|
+
<a
|
|
6241
|
+
#linkEl
|
|
6242
|
+
class="ct-navbar__link"
|
|
6243
|
+
[routerLink]="routerLink()!"
|
|
6244
|
+
routerLinkActive="ct-navbar__link--active"
|
|
6245
|
+
role="menuitem"
|
|
6246
|
+
[attr.aria-disabled]="disabled() || null"
|
|
6247
|
+
[attr.tabindex]="rovingTabindex()"
|
|
6248
|
+
(click)="onClick($event)">
|
|
6249
|
+
<ng-content>{{ label() }}</ng-content>
|
|
6250
|
+
</a>
|
|
6251
|
+
} @else if (href()) {
|
|
5909
6252
|
<a
|
|
5910
6253
|
#linkEl
|
|
5911
6254
|
class="ct-navbar__link"
|
|
6255
|
+
[class.ct-navbar__link--active]="active()"
|
|
5912
6256
|
[href]="href()"
|
|
5913
6257
|
role="menuitem"
|
|
5914
6258
|
[attr.aria-current]="active() ? 'page' : null"
|
|
5915
6259
|
[attr.aria-disabled]="disabled() || null"
|
|
5916
6260
|
[attr.tabindex]="rovingTabindex()"
|
|
5917
6261
|
(click)="onClick($event)">
|
|
5918
|
-
{{ label() }}
|
|
6262
|
+
<ng-content>{{ label() }}</ng-content>
|
|
5919
6263
|
</a>
|
|
5920
6264
|
} @else {
|
|
5921
6265
|
<button
|
|
5922
6266
|
#linkEl
|
|
5923
6267
|
class="ct-navbar__link"
|
|
6268
|
+
[class.ct-navbar__link--active]="active()"
|
|
5924
6269
|
type="button"
|
|
5925
6270
|
role="menuitem"
|
|
5926
6271
|
[attr.aria-current]="active() ? 'page' : null"
|
|
5927
6272
|
[attr.aria-disabled]="disabled() || null"
|
|
5928
6273
|
[attr.tabindex]="rovingTabindex()"
|
|
5929
6274
|
(click)="onClick($event)">
|
|
5930
|
-
{{ label() }}
|
|
6275
|
+
<ng-content>{{ label() }}</ng-content>
|
|
5931
6276
|
</button>
|
|
5932
6277
|
}
|
|
5933
6278
|
</li>
|
|
5934
|
-
`, isInline: true, styles: [":host{display:contents}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6279
|
+
`, isInline: true, styles: [":host{display:contents}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5935
6280
|
}
|
|
5936
6281
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfNavItemComponent, decorators: [{
|
|
5937
6282
|
type: Component,
|
|
5938
|
-
args: [{ selector: 'af-nav-item', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
6283
|
+
args: [{ selector: 'af-nav-item', changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterLink, RouterLinkActive], template: `
|
|
5939
6284
|
<li class="ct-navbar__item" role="none">
|
|
5940
|
-
@if (
|
|
6285
|
+
@if (routerLink()) {
|
|
6286
|
+
<a
|
|
6287
|
+
#linkEl
|
|
6288
|
+
class="ct-navbar__link"
|
|
6289
|
+
[routerLink]="routerLink()!"
|
|
6290
|
+
routerLinkActive="ct-navbar__link--active"
|
|
6291
|
+
role="menuitem"
|
|
6292
|
+
[attr.aria-disabled]="disabled() || null"
|
|
6293
|
+
[attr.tabindex]="rovingTabindex()"
|
|
6294
|
+
(click)="onClick($event)">
|
|
6295
|
+
<ng-content>{{ label() }}</ng-content>
|
|
6296
|
+
</a>
|
|
6297
|
+
} @else if (href()) {
|
|
5941
6298
|
<a
|
|
5942
6299
|
#linkEl
|
|
5943
6300
|
class="ct-navbar__link"
|
|
6301
|
+
[class.ct-navbar__link--active]="active()"
|
|
5944
6302
|
[href]="href()"
|
|
5945
6303
|
role="menuitem"
|
|
5946
6304
|
[attr.aria-current]="active() ? 'page' : null"
|
|
5947
6305
|
[attr.aria-disabled]="disabled() || null"
|
|
5948
6306
|
[attr.tabindex]="rovingTabindex()"
|
|
5949
6307
|
(click)="onClick($event)">
|
|
5950
|
-
{{ label() }}
|
|
6308
|
+
<ng-content>{{ label() }}</ng-content>
|
|
5951
6309
|
</a>
|
|
5952
6310
|
} @else {
|
|
5953
6311
|
<button
|
|
5954
6312
|
#linkEl
|
|
5955
6313
|
class="ct-navbar__link"
|
|
6314
|
+
[class.ct-navbar__link--active]="active()"
|
|
5956
6315
|
type="button"
|
|
5957
6316
|
role="menuitem"
|
|
5958
6317
|
[attr.aria-current]="active() ? 'page' : null"
|
|
5959
6318
|
[attr.aria-disabled]="disabled() || null"
|
|
5960
6319
|
[attr.tabindex]="rovingTabindex()"
|
|
5961
6320
|
(click)="onClick($event)">
|
|
5962
|
-
{{ label() }}
|
|
6321
|
+
<ng-content>{{ label() }}</ng-content>
|
|
5963
6322
|
</button>
|
|
5964
6323
|
}
|
|
5965
6324
|
</li>
|
|
5966
6325
|
`, styles: [":host{display:contents}\n"] }]
|
|
5967
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }], linkRef: [{ type: i0.ViewChild, args: ['linkEl', { isSignal: true }] }] } });
|
|
6326
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }], linkRef: [{ type: i0.ViewChild, args: ['linkEl', { isSignal: true }] }] } });
|
|
5968
6327
|
/**
|
|
5969
6328
|
* Responsive navbar with mobile menu, keyboard navigation, and ARIA landmarks.
|
|
5970
6329
|
*
|
|
5971
6330
|
* @example
|
|
5972
6331
|
* <af-navbar ariaLabel="Main navigation">
|
|
5973
6332
|
* <a brand class="ct-navbar__brand" href="/">My App</a>
|
|
5974
|
-
* <af-nav-item label="Dashboard"
|
|
5975
|
-
*
|
|
6333
|
+
* <af-nav-item label="Dashboard" routerLink="/dashboard">
|
|
6334
|
+
* <af-icon name="dashboard" /> Dashboard
|
|
6335
|
+
* </af-nav-item>
|
|
6336
|
+
* <af-nav-item label="Settings" routerLink="/settings" />
|
|
5976
6337
|
* <button actions class="ct-button">Profile</button>
|
|
5977
6338
|
* </af-navbar>
|
|
5978
6339
|
*/
|
|
@@ -6195,7 +6556,19 @@ class AfNavbarComponent {
|
|
|
6195
6556
|
role="menu"
|
|
6196
6557
|
aria-label="Mobile navigation">
|
|
6197
6558
|
@for (item of items(); track item) {
|
|
6198
|
-
@if (item.
|
|
6559
|
+
@if (item.routerLink(); as rl) {
|
|
6560
|
+
<a
|
|
6561
|
+
#mobileLink
|
|
6562
|
+
class="ct-navbar__link"
|
|
6563
|
+
[routerLink]="rl"
|
|
6564
|
+
routerLinkActive="ct-navbar__link--active"
|
|
6565
|
+
role="menuitem"
|
|
6566
|
+
[attr.aria-disabled]="item.disabled() || null"
|
|
6567
|
+
[attr.tabindex]="mobileMenuOpen() ? 0 : -1"
|
|
6568
|
+
(click)="onMobileItemClick($event, item)">
|
|
6569
|
+
{{ item.label() }}
|
|
6570
|
+
</a>
|
|
6571
|
+
} @else if (item.href()) {
|
|
6199
6572
|
<a
|
|
6200
6573
|
#mobileLink
|
|
6201
6574
|
class="ct-navbar__link"
|
|
@@ -6223,11 +6596,11 @@ class AfNavbarComponent {
|
|
|
6223
6596
|
}
|
|
6224
6597
|
</div>
|
|
6225
6598
|
</header>
|
|
6226
|
-
`, isInline: true, styles: [":host{display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6599
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6227
6600
|
}
|
|
6228
6601
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfNavbarComponent, decorators: [{
|
|
6229
6602
|
type: Component,
|
|
6230
|
-
args: [{ selector: 'af-navbar', changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
6603
|
+
args: [{ selector: 'af-navbar', changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterLink, RouterLinkActive], host: {
|
|
6231
6604
|
'(keydown)': 'handleKeydown($event)',
|
|
6232
6605
|
'(document:click)': 'onDocumentClick($event)',
|
|
6233
6606
|
}, template: `
|
|
@@ -6268,7 +6641,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
6268
6641
|
role="menu"
|
|
6269
6642
|
aria-label="Mobile navigation">
|
|
6270
6643
|
@for (item of items(); track item) {
|
|
6271
|
-
@if (item.
|
|
6644
|
+
@if (item.routerLink(); as rl) {
|
|
6645
|
+
<a
|
|
6646
|
+
#mobileLink
|
|
6647
|
+
class="ct-navbar__link"
|
|
6648
|
+
[routerLink]="rl"
|
|
6649
|
+
routerLinkActive="ct-navbar__link--active"
|
|
6650
|
+
role="menuitem"
|
|
6651
|
+
[attr.aria-disabled]="item.disabled() || null"
|
|
6652
|
+
[attr.tabindex]="mobileMenuOpen() ? 0 : -1"
|
|
6653
|
+
(click)="onMobileItemClick($event, item)">
|
|
6654
|
+
{{ item.label() }}
|
|
6655
|
+
</a>
|
|
6656
|
+
} @else if (item.href()) {
|
|
6272
6657
|
<a
|
|
6273
6658
|
#mobileLink
|
|
6274
6659
|
class="ct-navbar__link"
|
|
@@ -6347,6 +6732,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
6347
6732
|
*/
|
|
6348
6733
|
class AfPopoverComponent {
|
|
6349
6734
|
static nextId = 0;
|
|
6735
|
+
focusTrap = new FocusTrap();
|
|
6350
6736
|
/** Two-way bindable open state. */
|
|
6351
6737
|
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
6352
6738
|
/** Preferred position relative to the trigger. Flips automatically when space is insufficient. */
|
|
@@ -6370,8 +6756,6 @@ class AfPopoverComponent {
|
|
|
6370
6756
|
wrapperRef = viewChild('wrapper', ...(ngDevMode ? [{ debugName: "wrapperRef" }] : []));
|
|
6371
6757
|
contentRef = viewChild('popoverContent', ...(ngDevMode ? [{ debugName: "contentRef" }] : []));
|
|
6372
6758
|
triggerDirective = contentChild(AfPopoverTriggerDirective, ...(ngDevMode ? [{ debugName: "triggerDirective" }] : []));
|
|
6373
|
-
previousActiveElement = null;
|
|
6374
|
-
focusableElements = [];
|
|
6375
6759
|
flippedSide = signal(null, ...(ngDevMode ? [{ debugName: "flippedSide" }] : []));
|
|
6376
6760
|
/** Effective side after auto-flip evaluation. */
|
|
6377
6761
|
activeSide = computed(() => this.flippedSide() ?? this.position(), ...(ngDevMode ? [{ debugName: "activeSide" }] : []));
|
|
@@ -6392,7 +6776,7 @@ class AfPopoverComponent {
|
|
|
6392
6776
|
}
|
|
6393
6777
|
}, ...(ngDevMode ? [{ debugName: "openEffect" }] : []));
|
|
6394
6778
|
ngOnDestroy() {
|
|
6395
|
-
this.restoreFocus();
|
|
6779
|
+
this.focusTrap.restoreFocus();
|
|
6396
6780
|
}
|
|
6397
6781
|
/** Toggle the popover open state. */
|
|
6398
6782
|
toggle() {
|
|
@@ -6421,59 +6805,29 @@ class AfPopoverComponent {
|
|
|
6421
6805
|
return;
|
|
6422
6806
|
const trigger = this.triggerDirective()?.elementRef.nativeElement;
|
|
6423
6807
|
if (trigger) {
|
|
6424
|
-
this.
|
|
6808
|
+
this.focusTrap.setReturnFocus(trigger);
|
|
6425
6809
|
}
|
|
6426
6810
|
this.close();
|
|
6427
6811
|
}
|
|
6428
6812
|
onKeydown(event) {
|
|
6429
6813
|
if (!this.open() || event.key !== 'Tab')
|
|
6430
6814
|
return;
|
|
6431
|
-
this.
|
|
6815
|
+
const content = this.contentRef()?.nativeElement;
|
|
6816
|
+
this.focusTrap.handleTab(event, content, content);
|
|
6432
6817
|
}
|
|
6433
6818
|
onOpen() {
|
|
6434
|
-
this.
|
|
6819
|
+
this.focusTrap.saveFocus();
|
|
6435
6820
|
this.flippedSide.set(this.computeFlippedSide());
|
|
6436
6821
|
queueMicrotask(() => {
|
|
6437
6822
|
if (!this.open())
|
|
6438
6823
|
return;
|
|
6439
|
-
this.
|
|
6440
|
-
|
|
6441
|
-
if (first) {
|
|
6442
|
-
first.focus();
|
|
6443
|
-
}
|
|
6444
|
-
else {
|
|
6445
|
-
this.contentRef()?.nativeElement.focus();
|
|
6446
|
-
}
|
|
6824
|
+
const content = this.contentRef()?.nativeElement;
|
|
6825
|
+
this.focusTrap.focusFirst(content, content);
|
|
6447
6826
|
});
|
|
6448
6827
|
}
|
|
6449
6828
|
onClose() {
|
|
6450
6829
|
this.flippedSide.set(null);
|
|
6451
|
-
this.restoreFocus();
|
|
6452
|
-
}
|
|
6453
|
-
restoreFocus() {
|
|
6454
|
-
if (this.previousActiveElement) {
|
|
6455
|
-
this.previousActiveElement.focus();
|
|
6456
|
-
this.previousActiveElement = null;
|
|
6457
|
-
}
|
|
6458
|
-
}
|
|
6459
|
-
trapFocus(event) {
|
|
6460
|
-
this.refreshFocusableElements();
|
|
6461
|
-
if (this.focusableElements.length === 0) {
|
|
6462
|
-
event.preventDefault();
|
|
6463
|
-
this.contentRef()?.nativeElement.focus();
|
|
6464
|
-
return;
|
|
6465
|
-
}
|
|
6466
|
-
const first = this.focusableElements[0];
|
|
6467
|
-
const last = this.focusableElements[this.focusableElements.length - 1];
|
|
6468
|
-
const active = document.activeElement;
|
|
6469
|
-
if (event.shiftKey && active === first) {
|
|
6470
|
-
event.preventDefault();
|
|
6471
|
-
last.focus();
|
|
6472
|
-
}
|
|
6473
|
-
else if (!event.shiftKey && active === last) {
|
|
6474
|
-
event.preventDefault();
|
|
6475
|
-
first.focus();
|
|
6476
|
-
}
|
|
6830
|
+
this.focusTrap.restoreFocus();
|
|
6477
6831
|
}
|
|
6478
6832
|
computeFlippedSide() {
|
|
6479
6833
|
const trigger = this.triggerDirective()?.elementRef.nativeElement;
|
|
@@ -6508,24 +6862,6 @@ class AfPopoverComponent {
|
|
|
6508
6862
|
return opposite;
|
|
6509
6863
|
return preferred;
|
|
6510
6864
|
}
|
|
6511
|
-
refreshFocusableElements() {
|
|
6512
|
-
const content = this.contentRef()?.nativeElement;
|
|
6513
|
-
if (!content) {
|
|
6514
|
-
this.focusableElements = [];
|
|
6515
|
-
return;
|
|
6516
|
-
}
|
|
6517
|
-
const selectors = [
|
|
6518
|
-
'a[href]',
|
|
6519
|
-
'area[href]',
|
|
6520
|
-
'button:not([disabled])',
|
|
6521
|
-
'input:not([disabled])',
|
|
6522
|
-
'select:not([disabled])',
|
|
6523
|
-
'textarea:not([disabled])',
|
|
6524
|
-
'[tabindex]:not([tabindex="-1"])',
|
|
6525
|
-
];
|
|
6526
|
-
this.focusableElements = Array.from(content.querySelectorAll(selectors.join(','))).filter((el) => !el.hasAttribute('disabled') &&
|
|
6527
|
-
el.getAttribute('aria-hidden') !== 'true');
|
|
6528
|
-
}
|
|
6529
6865
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6530
6866
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfPopoverComponent, isStandalone: true, selector: "af-popover", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, showArrow: { classPropertyName: "showArrow", publicName: "showArrow", isSignal: true, isRequired: false, transformFunction: null }, closeOnClickOutside: { classPropertyName: "closeOnClickOutside", publicName: "closeOnClickOutside", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", closed: "closed" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscapeKey()", "document:keydown": "onKeydown($event)" } }, providers: [
|
|
6531
6867
|
{ provide: AF_POPOVER, useExisting: forwardRef(() => AfPopoverComponent) },
|
|
@@ -7385,5 +7721,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
7385
7721
|
* Generated bundle index. Do not edit.
|
|
7386
7722
|
*/
|
|
7387
7723
|
|
|
7388
|
-
export { AfAccordionComponent, AfAccordionItemComponent, AfAlertComponent, AfAvatarComponent, AfBadgeComponent, AfBannerComponent, AfBreadcrumbsComponent, AfButtonComponent, AfCardComponent, AfCellDefDirective, AfCheckboxComponent, AfChipInputComponent, AfComboboxComponent, AfDataTableComponent, AfDatepickerComponent, AfDividerComponent, AfDrawerComponent, AfDropdownComponent, AfEmptyStateComponent, AfFieldComponent, AfFileUploadComponent, AfFormatLabelPipe, AfIconComponent, AfInputComponent, AfModalComponent, AfNavItemComponent, AfNavbarComponent, AfPaginationComponent, AfPopoverComponent, AfPopoverTriggerDirective, AfProgressBarComponent, AfRadioComponent, AfSelectComponent, AfSelectMenuComponent, AfSidebarComponent, AfSkeletonComponent, AfSkipLinkComponent, AfSliderComponent, AfSpinnerComponent, AfSwitchComponent, AfTabPanelComponent, AfTableBodyComponent, AfTableCellComponent, AfTableComponent, AfTableHeaderCellComponent, AfTableHeaderComponent, AfTableRowComponent, AfTabsComponent, AfTextareaComponent, AfToastContainerComponent, AfToastService, AfToggleGroupComponent, AfToolbarComponent, AfTooltipDirective };
|
|
7724
|
+
export { AfAccordionComponent, AfAccordionItemComponent, AfAlertComponent, AfAvatarComponent, AfBadgeComponent, AfBannerComponent, AfBreadcrumbsComponent, AfButtonComponent, AfCardComponent, AfCellDefDirective, AfCheckboxComponent, AfChipInputComponent, AfComboboxComponent, AfDataTableComponent, AfDatepickerComponent, AfDividerComponent, AfDrawerComponent, AfDropdownComponent, AfEmptyStateComponent, AfFieldComponent, AfFileUploadComponent, AfFormatLabelPipe, AfIconComponent, AfInputComponent, AfModalComponent, AfNavItemComponent, AfNavbarComponent, AfPaginationComponent, AfPopoverComponent, AfPopoverTriggerDirective, AfProgressBarComponent, AfRadioComponent, AfRadioGroupComponent, AfSelectComponent, AfSelectMenuComponent, AfSidebarComponent, AfSkeletonComponent, AfSkipLinkComponent, AfSliderComponent, AfSpinnerComponent, AfSwitchComponent, AfTabPanelComponent, AfTableBodyComponent, AfTableCellComponent, AfTableComponent, AfTableHeaderCellComponent, AfTableHeaderComponent, AfTableRowComponent, AfTabsComponent, AfTextareaComponent, AfToastContainerComponent, AfToastService, AfToggleGroupComponent, AfToolbarComponent, AfTooltipDirective };
|
|
7389
7725
|
//# sourceMappingURL=neuravision-ng-construct.mjs.map
|