@akcelik/strct 5.12.0 → 5.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/akcelik-strct.mjs +1629 -986
- package/fesm2022/akcelik-strct.mjs.map +1 -1
- package/package.json +1 -1
- package/types/akcelik-strct.d.ts +240 -9
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, DOCUMENT, signal, computed, Injectable, input, isDevMode, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgZone, booleanAttribute, afterNextRender, Directive,
|
|
2
|
+
import { inject, DOCUMENT, signal, computed, Injectable, input, isDevMode, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgZone, booleanAttribute, afterNextRender, Directive, HostListener, TemplateRef, contentChildren, viewChild, effect, untracked, model, output, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID, forwardRef, DestroyRef, contentChild, InjectionToken, ViewContainerRef, Injector, viewChildren, Renderer2, Pipe, afterRenderEffect } from '@angular/core';
|
|
3
3
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
4
4
|
import { DOCUMENT as DOCUMENT$1, NgTemplateOutlet, isPlatformBrowser } from '@angular/common';
|
|
5
|
-
import {
|
|
5
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
|
+
import { Router, NavigationEnd, RouterLink, RouterLinkActive } from '@angular/router';
|
|
6
7
|
import * as i1 from '@angular/forms';
|
|
7
8
|
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
|
|
8
9
|
|
|
@@ -1026,122 +1027,788 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
1026
1027
|
args: [{ selector: '[strctOverlay]' }]
|
|
1027
1028
|
}], ctorParameters: () => [], propDecorators: { anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "strctOverlay", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "strctOverlayPlacement", required: false }] }], matchWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strctOverlayMatchWidth", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "strctOverlayGap", required: false }] }] } });
|
|
1028
1029
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1030
|
+
/**
|
|
1031
|
+
* Dev-mode diagnostics for combinations a component accepts but cannot honour.
|
|
1032
|
+
*
|
|
1033
|
+
* Every message names what was ignored and what to do instead. Keyed: a
|
|
1034
|
+
* condition warns once per page load, not once per change detection.
|
|
1035
|
+
*
|
|
1036
|
+
* Production cost: none, PROVIDED each call site is guarded INLINE with
|
|
1037
|
+
* if (typeof ngDevMode !== 'undefined' && ngDevMode) { … }
|
|
1038
|
+
* Production builds define `ngDevMode` as false, and the minifier then drops
|
|
1039
|
+
* the whole block — messages included. A helper function returning that same
|
|
1040
|
+
* expression would NOT be folded across the call, so the check and its
|
|
1041
|
+
* strings would ship; that is why no such helper exists here.
|
|
1042
|
+
*/
|
|
1043
|
+
const warned = new Set();
|
|
1044
|
+
function strctDevWarn(key, message) {
|
|
1045
|
+
if (typeof ngDevMode === 'undefined' || !ngDevMode || warned.has(key))
|
|
1046
|
+
return;
|
|
1047
|
+
warned.add(key);
|
|
1048
|
+
console.warn(message);
|
|
1049
|
+
}
|
|
1050
|
+
/** Test hook: forget which warnings have fired. Not part of the public API. */
|
|
1051
|
+
function resetStrctDevWarnings() {
|
|
1052
|
+
warned.clear();
|
|
1036
1053
|
}
|
|
1054
|
+
|
|
1037
1055
|
/**
|
|
1038
|
-
*
|
|
1039
|
-
* <strct-
|
|
1040
|
-
* <strct-
|
|
1041
|
-
* <
|
|
1042
|
-
* <strct-
|
|
1043
|
-
* </strct-
|
|
1056
|
+
* Click-to-open menu:
|
|
1057
|
+
* <strct-dropdown align="end">
|
|
1058
|
+
* <button strct-button strctDropdownTrigger>Actions</button>
|
|
1059
|
+
* <strct-dropdown-item>Rename</strct-dropdown-item>
|
|
1060
|
+
* <strct-dropdown-item critical>Delete</strct-dropdown-item>
|
|
1061
|
+
* </strct-dropdown>
|
|
1062
|
+
*
|
|
1063
|
+
* With `popover`, the panel holds *form controls* instead of menu items: an
|
|
1064
|
+
* inner click no longer closes it (only outside click / Escape do), and the
|
|
1065
|
+
* semantics switch from `role="menu"` to a labeled `role="dialog"` — a panel
|
|
1066
|
+
* of selects is not a menu to a screen reader either:
|
|
1067
|
+
*
|
|
1068
|
+
* <strct-dropdown popover popoverLabel="Filters">
|
|
1069
|
+
* <button strct-button strctDropdownTrigger>Filters</button>
|
|
1070
|
+
* <strct-field label="Severity">…</strct-field>
|
|
1071
|
+
* </strct-dropdown>
|
|
1044
1072
|
*/
|
|
1045
|
-
class
|
|
1073
|
+
class StrctDropdown {
|
|
1074
|
+
host = inject(ElementRef);
|
|
1075
|
+
/** Horizontal alignment of the menu. */
|
|
1076
|
+
align = input('start', /* @ts-ignore */
|
|
1077
|
+
...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
1046
1078
|
/**
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1079
|
+
* Popover mode for panels holding form controls (filter/settings panels):
|
|
1080
|
+
* inner clicks never close the panel — only outside click / Escape do —
|
|
1081
|
+
* and it renders as a labeled `role="dialog"` instead of a menu.
|
|
1050
1082
|
*/
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
...(ngDevMode ? [{ debugName: "skipLinkLabel" }] : /* istanbul ignore next */ []));
|
|
1056
|
-
/** The name of the backdrop that closes the navigation drawer on a phone. */
|
|
1057
|
-
closeNavLabel = input('Close navigation', /* @ts-ignore */
|
|
1058
|
-
...(ngDevMode ? [{ debugName: "closeNavLabel" }] : /* istanbul ignore next */ []));
|
|
1059
|
-
/** The drawer's open state lives on the shell, and so does its backdrop. */
|
|
1060
|
-
shell = inject(StrctShellService);
|
|
1061
|
-
doc = inject(DOCUMENT$1);
|
|
1083
|
+
popover = input(false, { ...(ngDevMode ? { debugName: "popover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1084
|
+
/** Accessible name of the popover dialog (localizable). */
|
|
1085
|
+
popoverLabel = input('Filters', /* @ts-ignore */
|
|
1086
|
+
...(ngDevMode ? [{ debugName: "popoverLabel" }] : /* istanbul ignore next */ []));
|
|
1062
1087
|
/**
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1088
|
+
* Only with `popover`: the panel's rows are controls in their own right — a
|
|
1089
|
+
* theme switcher, a filter — so they keep a real tab stop instead of the
|
|
1090
|
+
* `tabindex="-1"` a menu's items carry. Without it nothing inside a popover
|
|
1091
|
+
* can be reached from the keyboard at all.
|
|
1065
1092
|
*/
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1093
|
+
focusable = input(false, { ...(ngDevMode ? { debugName: "focusable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1094
|
+
open = signal(false, /* @ts-ignore */
|
|
1095
|
+
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
1096
|
+
constructor() {
|
|
1097
|
+
// The static `popover` attribute collides with the native HTML Popover
|
|
1098
|
+
// API: the UA styles [popover] hosts (Canvas background, medium border,
|
|
1099
|
+
// fixed positioning) — a white frame around the trigger in dark themes.
|
|
1100
|
+
// Angular has already read the input by now; the DOM attribute must go.
|
|
1101
|
+
this.host.nativeElement.removeAttribute('popover');
|
|
1102
|
+
}
|
|
1103
|
+
/** Where focus was when the menu opened — restored on Escape/selection. */
|
|
1104
|
+
lastFocused = null;
|
|
1105
|
+
toggle() {
|
|
1106
|
+
const willOpen = !this.open();
|
|
1107
|
+
if (willOpen)
|
|
1108
|
+
this.lastFocused = document.activeElement;
|
|
1109
|
+
this.open.set(willOpen);
|
|
1110
|
+
if (willOpen && !this.popover())
|
|
1111
|
+
this.focusInitialItem();
|
|
1112
|
+
}
|
|
1113
|
+
/** Open (if closed) and move focus into the menu — ArrowDown on the trigger. */
|
|
1114
|
+
openMenu() {
|
|
1115
|
+
if (this.open())
|
|
1070
1116
|
return;
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
// Optional: not every environment implements it (jsdom does not).
|
|
1076
|
-
target.scrollIntoView?.({ block: 'start' });
|
|
1117
|
+
this.lastFocused = document.activeElement;
|
|
1118
|
+
this.open.set(true);
|
|
1119
|
+
if (!this.popover())
|
|
1120
|
+
this.focusInitialItem();
|
|
1077
1121
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
<a
|
|
1083
|
-
class="strct-shell__skip"
|
|
1084
|
-
[attr.href]="'#' + skipLinkTarget()"
|
|
1085
|
-
(click)="skipToMain($event)"
|
|
1086
|
-
>{{ skipLinkLabel() }}</a
|
|
1087
|
-
>
|
|
1122
|
+
close(restoreFocus = false) {
|
|
1123
|
+
this.open.set(false);
|
|
1124
|
+
if (restoreFocus)
|
|
1125
|
+
this.lastFocused?.focus?.();
|
|
1088
1126
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
(
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1127
|
+
/**
|
|
1128
|
+
* Menu mode closes ONLY on a real item activation — a click that lands on
|
|
1129
|
+
* the menu's padding or a divider (a 2px miss) keeps it open instead of
|
|
1130
|
+
* throwing the whole interaction away. Popover form controls never close.
|
|
1131
|
+
*/
|
|
1132
|
+
onInnerActivate(event) {
|
|
1133
|
+
if (this.popover())
|
|
1134
|
+
return;
|
|
1135
|
+
const target = event.target;
|
|
1136
|
+
if (target?.closest('.strct-dd__itemaction'))
|
|
1137
|
+
return; // the second action is not the item
|
|
1138
|
+
const item = target?.closest('strct-dropdown-item');
|
|
1139
|
+
if (item && item.getAttribute('aria-disabled') !== 'true')
|
|
1140
|
+
this.close(true);
|
|
1102
1141
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1142
|
+
/** APG menu keyboarding: arrows rove, Home/End jump, Enter/Space activate. */
|
|
1143
|
+
onMenuKeydown(event) {
|
|
1144
|
+
if (this.popover())
|
|
1145
|
+
return;
|
|
1146
|
+
const items = this.navItems();
|
|
1147
|
+
if (!items.length)
|
|
1148
|
+
return;
|
|
1149
|
+
const idx = items.indexOf(event.target);
|
|
1150
|
+
const key = event.key;
|
|
1151
|
+
if (key === 'ArrowDown' || key === 'ArrowUp') {
|
|
1152
|
+
event.preventDefault();
|
|
1153
|
+
const next = idx === -1
|
|
1154
|
+
? key === 'ArrowDown'
|
|
1155
|
+
? 0
|
|
1156
|
+
: items.length - 1
|
|
1157
|
+
: (idx + (key === 'ArrowDown' ? 1 : -1) + items.length) % items.length;
|
|
1158
|
+
items[next].focus();
|
|
1159
|
+
}
|
|
1160
|
+
else if (key === 'Home' || key === 'End') {
|
|
1161
|
+
event.preventDefault();
|
|
1162
|
+
items[key === 'Home' ? 0 : items.length - 1].focus();
|
|
1163
|
+
}
|
|
1164
|
+
else if (key === 'Enter' || key === ' ') {
|
|
1165
|
+
event.preventDefault();
|
|
1166
|
+
const target = event.target;
|
|
1167
|
+
if (target.getAttribute('aria-disabled') !== 'true')
|
|
1168
|
+
target.click();
|
|
1169
|
+
}
|
|
1170
|
+
else if (key === 'ArrowRight' || key === 'ArrowLeft' || key === 'Delete') {
|
|
1171
|
+
this.onItemActionKey(event, key);
|
|
1172
|
+
}
|
|
1173
|
+
else if (key === 'Tab') {
|
|
1174
|
+
this.close();
|
|
1175
|
+
}
|
|
1118
1176
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1177
|
+
/**
|
|
1178
|
+
* An item with a trailing action has two targets: the right arrow reaches the
|
|
1179
|
+
* action from its item, the left arrow returns, and Delete on the item
|
|
1180
|
+
* triggers it (the WAI-ARIA pattern for a secondary action). Keys that do not
|
|
1181
|
+
* apply are left alone, so submenus keep their own arrow behaviour.
|
|
1182
|
+
*/
|
|
1183
|
+
onItemActionKey(event, key) {
|
|
1184
|
+
const target = event.target;
|
|
1185
|
+
if (key === 'ArrowLeft') {
|
|
1186
|
+
const item = target.closest('strct-dropdown-item');
|
|
1187
|
+
if (!target.classList.contains('strct-dd__itemaction') || !item)
|
|
1188
|
+
return;
|
|
1189
|
+
event.preventDefault();
|
|
1190
|
+
item.focus();
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
const item = target.closest('strct-dropdown-item');
|
|
1194
|
+
const action = item?.querySelector('.strct-dd__itemaction');
|
|
1195
|
+
if (!item || item !== target || !action)
|
|
1196
|
+
return;
|
|
1197
|
+
event.preventDefault();
|
|
1198
|
+
if (key === 'ArrowRight')
|
|
1199
|
+
action.focus();
|
|
1200
|
+
else
|
|
1201
|
+
action.click();
|
|
1132
1202
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1203
|
+
enabledItems() {
|
|
1204
|
+
return [
|
|
1205
|
+
...this.host.nativeElement.querySelectorAll('strct-dropdown-item:not([aria-disabled="true"]), strct-submenu .strct-submenu__trigger'),
|
|
1206
|
+
];
|
|
1207
|
+
}
|
|
1208
|
+
/** What the arrow keys visit: enabled items, plus disabled ones that carry a
|
|
1209
|
+
* hint (so the reason can be read). The menu still opens on an enabled one. */
|
|
1210
|
+
navItems() {
|
|
1211
|
+
const host = this.host.nativeElement;
|
|
1212
|
+
const rows = [
|
|
1213
|
+
...host.querySelectorAll('strct-dropdown-item'),
|
|
1214
|
+
...host.querySelectorAll('strct-submenu .strct-submenu__trigger'),
|
|
1215
|
+
].filter((el) => el.getAttribute('aria-disabled') !== 'true' ||
|
|
1216
|
+
el.classList.contains('strct-dd__item--hinted'));
|
|
1217
|
+
// Two queries, so restore DOM order explicitly. (A single comma-separated
|
|
1218
|
+
// selector is DOM-ordered in browsers, but not in every DOM implementation
|
|
1219
|
+
// — jsdom returns it grouped, which scrambles arrow-key order in tests.)
|
|
1220
|
+
return rows.sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
|
|
1221
|
+
}
|
|
1222
|
+
/** Focus the selected item if there is one, else the first — after render. */
|
|
1223
|
+
focusInitialItem() {
|
|
1224
|
+
setTimeout(() => {
|
|
1225
|
+
const items = this.enabledItems();
|
|
1226
|
+
(items.find((i) => i.getAttribute('aria-checked') === 'true') ?? items[0])?.focus();
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
onDocClick(event) {
|
|
1230
|
+
if (this.open() && !this.host.nativeElement.contains(event.target)) {
|
|
1231
|
+
this.close();
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
onEscape() {
|
|
1235
|
+
if (this.open())
|
|
1236
|
+
this.close(true);
|
|
1237
|
+
}
|
|
1238
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdown, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1239
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctDropdown, isStandalone: true, selector: "strct-dropdown", inputs: { align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, popover: { classPropertyName: "popover", publicName: "popover", isSignal: true, isRequired: false, transformFunction: null }, popoverLabel: { classPropertyName: "popoverLabel", publicName: "popoverLabel", isSignal: true, isRequired: false, transformFunction: null }, focusable: { classPropertyName: "focusable", publicName: "focusable", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocClick($event)", "document:keydown.escape": "onEscape()" }, classAttribute: "strct-dd" }, ngImport: i0, template: `
|
|
1240
|
+
<!--
|
|
1241
|
+
The wrapper is intentionally inert: the projected trigger should be a
|
|
1242
|
+
real button (nesting interactives fails axe). Its native click —
|
|
1243
|
+
pointer or Enter/Space — bubbles here; the strctDropdownTrigger
|
|
1244
|
+
directive puts aria-haspopup/aria-expanded on the button itself.
|
|
1245
|
+
-->
|
|
1246
|
+
<div #trigger class="strct-dd__trigger" (click)="toggle()">
|
|
1247
|
+
<ng-content select="[strctDropdownTrigger]" />
|
|
1248
|
+
</div>
|
|
1249
|
+
<!--
|
|
1250
|
+
One panel for both modes (a default ng-content per @if branch would strand
|
|
1251
|
+
the projected content in the inactive branch) — mode picks the semantics.
|
|
1252
|
+
-->
|
|
1253
|
+
@if (open()) {
|
|
1254
|
+
<div
|
|
1255
|
+
class="strct-dd__menu"
|
|
1256
|
+
[class.strct-dd__menu--popover]="popover()"
|
|
1257
|
+
[strctOverlay]="trigger"
|
|
1258
|
+
[strctOverlayPlacement]="align() === 'end' ? 'bottom-end' : 'bottom-start'"
|
|
1259
|
+
[attr.role]="popover() ? 'dialog' : 'menu'"
|
|
1260
|
+
[attr.aria-label]="popover() ? popoverLabel() : null"
|
|
1261
|
+
[attr.tabindex]="popover() ? -1 : 0"
|
|
1262
|
+
(click)="onInnerActivate($event)"
|
|
1263
|
+
(keydown)="onMenuKeydown($event)"
|
|
1264
|
+
>
|
|
1265
|
+
<ng-content />
|
|
1266
|
+
</div>
|
|
1267
|
+
}
|
|
1268
|
+
`, isInline: true, styles: [".strct-dd{position:relative;display:inline-block}.strct-dd__trigger{display:inline-flex}.strct-dd__menu{position:absolute;top:calc(100% + 4px);left:0;z-index:var(--z-dropdown);min-width:170px;max-width:calc(100vw - 24px);padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);animation:strct-dd-in .1s ease}.strct-dd__menu--end{left:auto;right:0}.strct-dd__menu--popover{min-width:240px;padding:12px 14px}@keyframes strct-dd-in{0%{opacity:0;transform:translateY(-4px)}}\n"], dependencies: [{ kind: "directive", type: StrctOverlay, selector: "[strctOverlay]", inputs: ["strctOverlay", "strctOverlayPlacement", "strctOverlayMatchWidth", "strctOverlayGap"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1269
|
+
}
|
|
1270
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdown, decorators: [{
|
|
1271
|
+
type: Component,
|
|
1272
|
+
args: [{ selector: 'strct-dropdown', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctOverlay], template: `
|
|
1273
|
+
<!--
|
|
1274
|
+
The wrapper is intentionally inert: the projected trigger should be a
|
|
1275
|
+
real button (nesting interactives fails axe). Its native click —
|
|
1276
|
+
pointer or Enter/Space — bubbles here; the strctDropdownTrigger
|
|
1277
|
+
directive puts aria-haspopup/aria-expanded on the button itself.
|
|
1278
|
+
-->
|
|
1279
|
+
<div #trigger class="strct-dd__trigger" (click)="toggle()">
|
|
1280
|
+
<ng-content select="[strctDropdownTrigger]" />
|
|
1281
|
+
</div>
|
|
1282
|
+
<!--
|
|
1283
|
+
One panel for both modes (a default ng-content per @if branch would strand
|
|
1284
|
+
the projected content in the inactive branch) — mode picks the semantics.
|
|
1285
|
+
-->
|
|
1286
|
+
@if (open()) {
|
|
1287
|
+
<div
|
|
1288
|
+
class="strct-dd__menu"
|
|
1289
|
+
[class.strct-dd__menu--popover]="popover()"
|
|
1290
|
+
[strctOverlay]="trigger"
|
|
1291
|
+
[strctOverlayPlacement]="align() === 'end' ? 'bottom-end' : 'bottom-start'"
|
|
1292
|
+
[attr.role]="popover() ? 'dialog' : 'menu'"
|
|
1293
|
+
[attr.aria-label]="popover() ? popoverLabel() : null"
|
|
1294
|
+
[attr.tabindex]="popover() ? -1 : 0"
|
|
1295
|
+
(click)="onInnerActivate($event)"
|
|
1296
|
+
(keydown)="onMenuKeydown($event)"
|
|
1297
|
+
>
|
|
1298
|
+
<ng-content />
|
|
1299
|
+
</div>
|
|
1300
|
+
}
|
|
1301
|
+
`, host: { class: 'strct-dd' }, styles: [".strct-dd{position:relative;display:inline-block}.strct-dd__trigger{display:inline-flex}.strct-dd__menu{position:absolute;top:calc(100% + 4px);left:0;z-index:var(--z-dropdown);min-width:170px;max-width:calc(100vw - 24px);padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);animation:strct-dd-in .1s ease}.strct-dd__menu--end{left:auto;right:0}.strct-dd__menu--popover{min-width:240px;padding:12px 14px}@keyframes strct-dd-in{0%{opacity:0;transform:translateY(-4px)}}\n"] }]
|
|
1302
|
+
}], ctorParameters: () => [], propDecorators: { align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], popover: [{ type: i0.Input, args: [{ isSignal: true, alias: "popover", required: false }] }], popoverLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverLabel", required: false }] }], focusable: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusable", required: false }] }], onDocClick: [{
|
|
1303
|
+
type: HostListener,
|
|
1304
|
+
args: ['document:click', ['$event']]
|
|
1305
|
+
}], onEscape: [{
|
|
1306
|
+
type: HostListener,
|
|
1307
|
+
args: ['document:keydown.escape']
|
|
1308
|
+
}] } });
|
|
1309
|
+
/**
|
|
1310
|
+
* Marks (and upgrades) the dropdown's trigger element. The attribute alone is
|
|
1311
|
+
* enough for content projection; importing the directive additionally wires
|
|
1312
|
+
* `aria-haspopup` / `aria-expanded` onto the real button.
|
|
1313
|
+
*/
|
|
1314
|
+
class StrctDropdownTrigger {
|
|
1315
|
+
dd = inject(StrctDropdown, { optional: true });
|
|
1316
|
+
onArrowDown(event) {
|
|
1317
|
+
if (!this.dd || this.dd.popover())
|
|
1318
|
+
return;
|
|
1319
|
+
event.preventDefault();
|
|
1320
|
+
this.dd.openMenu();
|
|
1321
|
+
}
|
|
1322
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1323
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDropdownTrigger, isStandalone: true, selector: "[strctDropdownTrigger]", host: { listeners: { "keydown.arrowdown": "onArrowDown($event)" }, properties: { "attr.aria-haspopup": "dd ? (dd.popover() ? 'dialog' : 'menu') : null", "attr.aria-expanded": "dd ? dd.open() : null" } }, ngImport: i0 });
|
|
1324
|
+
}
|
|
1325
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownTrigger, decorators: [{
|
|
1326
|
+
type: Directive,
|
|
1327
|
+
args: [{
|
|
1328
|
+
selector: '[strctDropdownTrigger]',
|
|
1329
|
+
host: {
|
|
1330
|
+
'[attr.aria-haspopup]': "dd ? (dd.popover() ? 'dialog' : 'menu') : null",
|
|
1331
|
+
'[attr.aria-expanded]': 'dd ? dd.open() : null',
|
|
1332
|
+
'(keydown.arrowdown)': 'onArrowDown($event)',
|
|
1333
|
+
},
|
|
1334
|
+
}]
|
|
1335
|
+
}] });
|
|
1336
|
+
let dropdownItemCounter = 0;
|
|
1337
|
+
/** A selectable row inside a `<strct-dropdown>`. */
|
|
1338
|
+
/**
|
|
1339
|
+
* A second action at the end of a menu item — "delete this saved view" beside
|
|
1340
|
+
* "open it". It stops its own click from reaching the item, so the item is not
|
|
1341
|
+
* activated and the menu stays open, and it stays out of the Tab order: the
|
|
1342
|
+
* right arrow reaches it from its item, the left arrow returns, and Delete on
|
|
1343
|
+
* the item triggers it.
|
|
1344
|
+
*/
|
|
1345
|
+
class StrctDropdownItemAction {
|
|
1346
|
+
onClick(event) {
|
|
1347
|
+
event.stopPropagation();
|
|
1348
|
+
}
|
|
1349
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemAction, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1350
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDropdownItemAction, isStandalone: true, selector: "[strctDropdownItemAction]", host: { attributes: { "tabindex": "-1" }, listeners: { "click": "onClick($event)" }, classAttribute: "strct-dd__itemaction" }, ngImport: i0 });
|
|
1351
|
+
}
|
|
1352
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemAction, decorators: [{
|
|
1353
|
+
type: Directive,
|
|
1354
|
+
args: [{
|
|
1355
|
+
selector: '[strctDropdownItemAction]',
|
|
1356
|
+
host: {
|
|
1357
|
+
class: 'strct-dd__itemaction',
|
|
1358
|
+
tabindex: '-1',
|
|
1359
|
+
'(click)': 'onClick($event)',
|
|
1360
|
+
},
|
|
1361
|
+
}]
|
|
1362
|
+
}] });
|
|
1363
|
+
/**
|
|
1364
|
+
* A leading icon for a menu row, in a colour of its own:
|
|
1365
|
+
* `<strct-icon strctDropdownItemIcon tone="var(--prod-acc)" name="shield" />`.
|
|
1366
|
+
* The row keeps its text aligned whether or not the icon is there.
|
|
1367
|
+
*/
|
|
1368
|
+
class StrctDropdownItemIcon {
|
|
1369
|
+
/** A colour token or value — a product's accent, say. */
|
|
1370
|
+
tone = input('', /* @ts-ignore */
|
|
1371
|
+
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
1372
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1373
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: StrctDropdownItemIcon, isStandalone: true, selector: "[strctDropdownItemIcon]", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.color": "tone() || null" }, classAttribute: "strct-dd__itemlead" }, ngImport: i0 });
|
|
1374
|
+
}
|
|
1375
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemIcon, decorators: [{
|
|
1376
|
+
type: Directive,
|
|
1377
|
+
args: [{
|
|
1378
|
+
selector: '[strctDropdownItemIcon]',
|
|
1379
|
+
host: { class: 'strct-dd__itemlead', '[style.color]': 'tone() || null' },
|
|
1380
|
+
}]
|
|
1381
|
+
}], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }] } });
|
|
1382
|
+
/**
|
|
1383
|
+
* The row's label, when the row also carries a description. A screen reader
|
|
1384
|
+
* reads a row in DOM order, and a projected slot can only be declared before
|
|
1385
|
+
* the catch-all one — so marking the label is what keeps "HyperStruct Backup"
|
|
1386
|
+
* ahead of "2 VMs past RPO" in what the row is called. A row without a
|
|
1387
|
+
* description does not need it.
|
|
1388
|
+
*/
|
|
1389
|
+
class StrctDropdownItemLabel {
|
|
1390
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1391
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDropdownItemLabel, isStandalone: true, selector: "[strctDropdownItemLabel]", host: { classAttribute: "strct-dd__itemlabel" }, ngImport: i0 });
|
|
1392
|
+
}
|
|
1393
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemLabel, decorators: [{
|
|
1394
|
+
type: Directive,
|
|
1395
|
+
args: [{
|
|
1396
|
+
selector: '[strctDropdownItemLabel]',
|
|
1397
|
+
host: { class: 'strct-dd__itemlabel' },
|
|
1398
|
+
}]
|
|
1399
|
+
}] });
|
|
1400
|
+
/**
|
|
1401
|
+
* A second, quieter line under a row's label: "● 2 VMs past RPO" under
|
|
1402
|
+
* "HyperStruct Backup". It is inside the row, so it is part of what the row
|
|
1403
|
+
* is called — put the label in `[strctDropdownItemLabel]` so it is read
|
|
1404
|
+
* first.
|
|
1405
|
+
*/
|
|
1406
|
+
class StrctDropdownItemDescription {
|
|
1407
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1408
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDropdownItemDescription, isStandalone: true, selector: "[strctDropdownItemDescription]", host: { classAttribute: "strct-dd__itemdesc" }, ngImport: i0 });
|
|
1409
|
+
}
|
|
1410
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemDescription, decorators: [{
|
|
1411
|
+
type: Directive,
|
|
1412
|
+
args: [{
|
|
1413
|
+
selector: '[strctDropdownItemDescription]',
|
|
1414
|
+
host: { class: 'strct-dd__itemdesc' },
|
|
1415
|
+
}]
|
|
1416
|
+
}] });
|
|
1417
|
+
class StrctDropdownItem {
|
|
1418
|
+
/**
|
|
1419
|
+
* Select-like usage: bind `selected` and the item becomes a
|
|
1420
|
+
* `menuitemradio` with `aria-checked`, a leading ✓ on the current choice
|
|
1421
|
+
* and an aligned lead slot — so reopening the menu shows what is chosen.
|
|
1422
|
+
* Leave unbound for plain action items.
|
|
1423
|
+
*/
|
|
1424
|
+
selected = input(null, /* @ts-ignore */
|
|
1425
|
+
...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
1426
|
+
/** Danger. */
|
|
1427
|
+
critical = input(false, { ...(ngDevMode ? { debugName: "critical" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1428
|
+
/** Static disable flag. */
|
|
1429
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1430
|
+
/**
|
|
1431
|
+
* A short explanation — typically why a disabled item is unavailable. Shown
|
|
1432
|
+
* as the tooltip and given to assistive technology as the description; never
|
|
1433
|
+
* rendered inline. A disabled item with a hint stays keyboard-reachable so
|
|
1434
|
+
* its reason can be read; activation stays blocked.
|
|
1435
|
+
*/
|
|
1436
|
+
hint = input(null, /* @ts-ignore */
|
|
1437
|
+
...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
1438
|
+
hintId = `strct-dd-item-${++dropdownItemCounter}-hint`;
|
|
1439
|
+
/** The panel this row belongs to, when it is inside one. */
|
|
1440
|
+
panel = inject(StrctDropdown, { optional: true });
|
|
1441
|
+
/**
|
|
1442
|
+
* A menu's rows are roved with the arrow keys, so they stay out of the tab
|
|
1443
|
+
* order. A `popover [focusable]` is a panel of real controls, and its rows
|
|
1444
|
+
* keep a tab stop of their own.
|
|
1445
|
+
*/
|
|
1446
|
+
tabindex = computed(() => {
|
|
1447
|
+
if (this.disabled() && !this.hint())
|
|
1448
|
+
return null;
|
|
1449
|
+
return this.panel?.popover() && this.panel.focusable() ? 0 : -1;
|
|
1450
|
+
}, /* @ts-ignore */
|
|
1451
|
+
...(ngDevMode ? [{ debugName: "tabindex" }] : /* istanbul ignore next */ []));
|
|
1452
|
+
constructor() {
|
|
1453
|
+
// Without pointer-events:none (see the hinted style), a disabled item
|
|
1454
|
+
// would pass clicks to the consumer's (click). Capture-phase at the target
|
|
1455
|
+
// runs before those listeners, and stopping here also keeps the menu's own
|
|
1456
|
+
// activate-and-close handler from seeing it.
|
|
1457
|
+
const host = inject((ElementRef)).nativeElement;
|
|
1458
|
+
// Dev-only: a description reads before an unmarked label, which is the
|
|
1459
|
+
// one thing this slot must not do.
|
|
1460
|
+
afterNextRender(() => {
|
|
1461
|
+
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
|
1462
|
+
if (host.querySelector('.strct-dd__itemdesc') &&
|
|
1463
|
+
!host.querySelector('.strct-dd__itemlabel')) {
|
|
1464
|
+
strctDevWarn('dd-item-description-without-label', '[strct] strct-dropdown-item: a [strctDropdownItemDescription] row reads its ' +
|
|
1465
|
+
'description before its label, because a slot can only be declared before the ' +
|
|
1466
|
+
'catch-all one. Wrap the label in [strctDropdownItemLabel].');
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
host.addEventListener('click', (event) => {
|
|
1471
|
+
if (!this.disabled())
|
|
1472
|
+
return;
|
|
1473
|
+
event.preventDefault();
|
|
1474
|
+
event.stopImmediatePropagation();
|
|
1475
|
+
}, { capture: true });
|
|
1476
|
+
}
|
|
1477
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1478
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctDropdownItem, isStandalone: true, selector: "strct-dropdown-item", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, critical: { classPropertyName: "critical", publicName: "critical", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "selected() === null ? 'menuitem' : 'menuitemradio'", "attr.aria-checked": "selected()", "attr.tabindex": "tabindex()", "attr.title": "hint() || null", "attr.aria-describedby": "hint() ? hintId : null", "class.strct-dd__item--hinted": "!!hint()", "class.strct-dd__item--critical": "critical()", "class.strct-dd__item--selected": "selected() === true", "attr.aria-disabled": "disabled() || null" }, classAttribute: "strct-dd__item" }, ngImport: i0, template: `@if (selected() !== null) {
|
|
1479
|
+
<span class="strct-dd__check" aria-hidden="true">
|
|
1480
|
+
@if (selected()) {
|
|
1481
|
+
<strct-icon strictName="check" [size]="12" [strokeWidth]="1.8" />
|
|
1482
|
+
}
|
|
1483
|
+
</span>
|
|
1484
|
+
}
|
|
1485
|
+
<!-- Every selective slot is declared before the catch-all, or the
|
|
1486
|
+
catch-all would swallow its content; the visual order is CSS's
|
|
1487
|
+
(the label is raw text, which only source order can place). -->
|
|
1488
|
+
<ng-content select="[strctDropdownItemIcon]" />
|
|
1489
|
+
<ng-content select="[strctDropdownItemAction]" />
|
|
1490
|
+
<span class="strct-dd__itemtext">
|
|
1491
|
+
<ng-content select="[strctDropdownItemLabel]" />
|
|
1492
|
+
<ng-content select="[strctDropdownItemDescription]" />
|
|
1493
|
+
</span>
|
|
1494
|
+
<ng-content />
|
|
1495
|
+
@if (hint()) {
|
|
1496
|
+
<!-- hidden: kept out of the item's accessible NAME, still read as its
|
|
1497
|
+
description through aria-describedby. -->
|
|
1498
|
+
<span [id]="hintId" hidden>{{ hint() }}</span>
|
|
1499
|
+
}`, isInline: true, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__itemaction{order:1;margin-inline-start:auto;flex:none}.strct-dd__itemlead{flex:none;display:inline-flex;align-items:center}.strct-dd__itemlabel{min-width:0;overflow:hidden;text-overflow:ellipsis}.strct-dd__itemtext{display:contents}.strct-dd__item:has(.strct-dd__itemdesc) .strct-dd__itemtext{display:flex;flex-direction:column;row-gap:2px;min-width:0}.strct-dd__itemdesc{display:flex;align-items:center;gap:6px;font-size:var(--text-sm);color:var(--t2)}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}.strct-dd__item--hinted[aria-disabled=true]{pointer-events:auto;cursor:not-allowed}.strct-dd__item--hinted[aria-disabled=true]:hover{background:transparent}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1500
|
+
}
|
|
1501
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItem, decorators: [{
|
|
1502
|
+
type: Component,
|
|
1503
|
+
args: [{ selector: 'strct-dropdown-item', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `@if (selected() !== null) {
|
|
1504
|
+
<span class="strct-dd__check" aria-hidden="true">
|
|
1505
|
+
@if (selected()) {
|
|
1506
|
+
<strct-icon strictName="check" [size]="12" [strokeWidth]="1.8" />
|
|
1507
|
+
}
|
|
1508
|
+
</span>
|
|
1509
|
+
}
|
|
1510
|
+
<!-- Every selective slot is declared before the catch-all, or the
|
|
1511
|
+
catch-all would swallow its content; the visual order is CSS's
|
|
1512
|
+
(the label is raw text, which only source order can place). -->
|
|
1513
|
+
<ng-content select="[strctDropdownItemIcon]" />
|
|
1514
|
+
<ng-content select="[strctDropdownItemAction]" />
|
|
1515
|
+
<span class="strct-dd__itemtext">
|
|
1516
|
+
<ng-content select="[strctDropdownItemLabel]" />
|
|
1517
|
+
<ng-content select="[strctDropdownItemDescription]" />
|
|
1518
|
+
</span>
|
|
1519
|
+
<ng-content />
|
|
1520
|
+
@if (hint()) {
|
|
1521
|
+
<!-- hidden: kept out of the item's accessible NAME, still read as its
|
|
1522
|
+
description through aria-describedby. -->
|
|
1523
|
+
<span [id]="hintId" hidden>{{ hint() }}</span>
|
|
1524
|
+
}`, host: {
|
|
1525
|
+
class: 'strct-dd__item',
|
|
1526
|
+
'[attr.role]': "selected() === null ? 'menuitem' : 'menuitemradio'",
|
|
1527
|
+
'[attr.aria-checked]': 'selected()',
|
|
1528
|
+
// A disabled item is skipped — unless it has a hint to be read.
|
|
1529
|
+
'[attr.tabindex]': 'tabindex()',
|
|
1530
|
+
'[attr.title]': 'hint() || null',
|
|
1531
|
+
'[attr.aria-describedby]': 'hint() ? hintId : null',
|
|
1532
|
+
'[class.strct-dd__item--hinted]': '!!hint()',
|
|
1533
|
+
'[class.strct-dd__item--critical]': 'critical()',
|
|
1534
|
+
'[class.strct-dd__item--selected]': 'selected() === true',
|
|
1535
|
+
'[attr.aria-disabled]': 'disabled() || null',
|
|
1536
|
+
}, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__itemaction{order:1;margin-inline-start:auto;flex:none}.strct-dd__itemlead{flex:none;display:inline-flex;align-items:center}.strct-dd__itemlabel{min-width:0;overflow:hidden;text-overflow:ellipsis}.strct-dd__itemtext{display:contents}.strct-dd__item:has(.strct-dd__itemdesc) .strct-dd__itemtext{display:flex;flex-direction:column;row-gap:2px;min-width:0}.strct-dd__itemdesc{display:flex;align-items:center;gap:6px;font-size:var(--text-sm);color:var(--t2)}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}.strct-dd__item--hinted[aria-disabled=true]{pointer-events:auto;cursor:not-allowed}.strct-dd__item--hinted[aria-disabled=true]:hover{background:transparent}\n"] }]
|
|
1537
|
+
}], ctorParameters: () => [], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], critical: [{ type: i0.Input, args: [{ isSignal: true, alias: "critical", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
|
|
1538
|
+
/**
|
|
1539
|
+
* A named group of rows — "Create" above the create actions, where a divider
|
|
1540
|
+
* alone leaves what the group is unsaid. The label is not an item: the arrows
|
|
1541
|
+
* pass over it and it is never activated.
|
|
1542
|
+
*/
|
|
1543
|
+
class StrctDropdownGroup {
|
|
1544
|
+
/** What the group is — read when a screen reader enters it. */
|
|
1545
|
+
label = input('', /* @ts-ignore */
|
|
1546
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1547
|
+
labelId = `strct-dd-group-${++dropdownItemCounter}-label`;
|
|
1548
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1549
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctDropdownGroup, isStandalone: true, selector: "strct-dropdown-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "group" }, properties: { "attr.aria-labelledby": "labelId" }, classAttribute: "strct-dd__group" }, ngImport: i0, template: `<div class="strct-dd__grouplabel" [id]="labelId" role="presentation">
|
|
1550
|
+
{{ label() }}
|
|
1551
|
+
</div>
|
|
1552
|
+
<ng-content />`, isInline: true, styles: [".strct-dd__group{display:block}.strct-dd__group+.strct-dd__group{margin-top:4px;border-top:1px solid var(--b2);padding-top:4px}.strct-dd__grouplabel{padding:6px 10px 2px;font-size:var(--text-xs, 11px);font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--t3)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1553
|
+
}
|
|
1554
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownGroup, decorators: [{
|
|
1555
|
+
type: Component,
|
|
1556
|
+
args: [{ selector: 'strct-dropdown-group', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `<div class="strct-dd__grouplabel" [id]="labelId" role="presentation">
|
|
1557
|
+
{{ label() }}
|
|
1558
|
+
</div>
|
|
1559
|
+
<ng-content />`, host: {
|
|
1560
|
+
class: 'strct-dd__group',
|
|
1561
|
+
role: 'group',
|
|
1562
|
+
'[attr.aria-labelledby]': 'labelId',
|
|
1563
|
+
}, styles: [".strct-dd__group{display:block}.strct-dd__group+.strct-dd__group{margin-top:4px;border-top:1px solid var(--b2);padding-top:4px}.strct-dd__grouplabel{padding:6px 10px 2px;font-size:var(--text-xs, 11px);font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--t3)}\n"] }]
|
|
1564
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
1565
|
+
/** Thin separator between groups of menu items. */
|
|
1566
|
+
class StrctDropdownDivider {
|
|
1567
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownDivider, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1568
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.4", type: StrctDropdownDivider, isStandalone: true, selector: "strct-dropdown-divider", host: { attributes: { "role": "separator" }, classAttribute: "strct-dd__divider" }, ngImport: i0, template: '', isInline: true, styles: [".strct-dd__divider{display:block;height:1px;margin:4px 6px;background:var(--b2)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1569
|
+
}
|
|
1570
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownDivider, decorators: [{
|
|
1571
|
+
type: Component,
|
|
1572
|
+
args: [{ selector: 'strct-dropdown-divider', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '', host: { class: 'strct-dd__divider', role: 'separator' }, styles: [".strct-dd__divider{display:block;height:1px;margin:4px 6px;background:var(--b2)}\n"] }]
|
|
1573
|
+
}] });
|
|
1574
|
+
|
|
1575
|
+
let shellCounter = 0;
|
|
1576
|
+
/** Shared layout state between shell parts. */
|
|
1577
|
+
class StrctShellService {
|
|
1578
|
+
mobileNavOpen = signal(false, /* @ts-ignore */
|
|
1579
|
+
...(ngDevMode ? [{ debugName: "mobileNavOpen" }] : /* istanbul ignore next */ []));
|
|
1580
|
+
/** Id of the vertical nav controlled by the header drawer toggle. */
|
|
1581
|
+
navId = `strct-vnav-${++shellCounter}`;
|
|
1582
|
+
/**
|
|
1583
|
+
* The header's drawer toggle, so closing the drawer can hand focus back to
|
|
1584
|
+
* what opened it. The header registers it; nothing else writes it.
|
|
1585
|
+
*/
|
|
1586
|
+
drawerToggle = null;
|
|
1587
|
+
/**
|
|
1588
|
+
* Closes the navigation drawer. On a phone the drawer holds the
|
|
1589
|
+
* navigation, so a choice made in it opens a page *behind* it; the shell
|
|
1590
|
+
* calls this on every completed navigation unless told not to.
|
|
1591
|
+
*/
|
|
1592
|
+
closeNav() {
|
|
1593
|
+
if (this.mobileNavOpen())
|
|
1594
|
+
this.mobileNavOpen.set(false);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Application frame: a full-viewport grid of header / body / footer rows.
|
|
1599
|
+
* <strct-shell>
|
|
1600
|
+
* <strct-header>…</strct-header>
|
|
1601
|
+
* <div strctShellMain>… sidebar + content …</div>
|
|
1602
|
+
* <strct-footer>…</strct-footer>
|
|
1603
|
+
* </strct-shell>
|
|
1604
|
+
*/
|
|
1605
|
+
class StrctShell {
|
|
1606
|
+
/**
|
|
1607
|
+
* Id of the main region. Given one, the shell renders "Skip to main content"
|
|
1608
|
+
* as its first focusable element — invisible until focused. Null (the
|
|
1609
|
+
* default) renders nothing, so existing shells are unchanged.
|
|
1610
|
+
*/
|
|
1611
|
+
skipLinkTarget = input(null, /* @ts-ignore */
|
|
1612
|
+
...(ngDevMode ? [{ debugName: "skipLinkTarget" }] : /* istanbul ignore next */ []));
|
|
1613
|
+
/** The link's text (localisable). */
|
|
1614
|
+
skipLinkLabel = input('Skip to main content', /* @ts-ignore */
|
|
1615
|
+
...(ngDevMode ? [{ debugName: "skipLinkLabel" }] : /* istanbul ignore next */ []));
|
|
1616
|
+
/** The name of the backdrop that closes the navigation drawer on a phone. */
|
|
1617
|
+
closeNavLabel = input('Close navigation', /* @ts-ignore */
|
|
1618
|
+
...(ngDevMode ? [{ debugName: "closeNavLabel" }] : /* istanbul ignore next */ []));
|
|
1619
|
+
/**
|
|
1620
|
+
* Close the drawer when a navigation completes — the subscription every app
|
|
1621
|
+
* with a router ends up writing. It needs a `Router`; without one nothing
|
|
1622
|
+
* happens. Set it to `false` to keep the drawer open across navigations,
|
|
1623
|
+
* and close it yourself with `StrctShellService.closeNav()`.
|
|
1624
|
+
*/
|
|
1625
|
+
closeNavOnNavigate = input(true, { ...(ngDevMode ? { debugName: "closeNavOnNavigate" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1626
|
+
/** The drawer's open state lives on the shell, and so does its backdrop. */
|
|
1627
|
+
shell = inject(StrctShellService);
|
|
1628
|
+
doc = inject(DOCUMENT$1);
|
|
1629
|
+
router = inject(Router, { optional: true });
|
|
1630
|
+
constructor() {
|
|
1631
|
+
// An app without the router package still works: the token is optional and
|
|
1632
|
+
// the events stream is simply never there.
|
|
1633
|
+
this.router?.events.pipe(takeUntilDestroyed()).subscribe((event) => {
|
|
1634
|
+
if (event instanceof NavigationEnd && this.closeNavOnNavigate())
|
|
1635
|
+
this.shell.closeNav();
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Moves focus rather than only the scroll position: an href alone scrolls
|
|
1640
|
+
* without focusing, so the next Tab would resume from the link.
|
|
1641
|
+
*/
|
|
1642
|
+
skipToMain(event) {
|
|
1643
|
+
const id = this.skipLinkTarget();
|
|
1644
|
+
const target = id ? this.doc.getElementById(id) : null;
|
|
1645
|
+
if (!target)
|
|
1646
|
+
return;
|
|
1647
|
+
event.preventDefault();
|
|
1648
|
+
if (!target.hasAttribute('tabindex'))
|
|
1649
|
+
target.setAttribute('tabindex', '-1');
|
|
1650
|
+
target.focus();
|
|
1651
|
+
// Optional: not every environment implements it (jsdom does not).
|
|
1652
|
+
target.scrollIntoView?.({ block: 'start' });
|
|
1653
|
+
}
|
|
1654
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1655
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctShell, isStandalone: true, selector: "strct-shell", inputs: { skipLinkTarget: { classPropertyName: "skipLinkTarget", publicName: "skipLinkTarget", isSignal: true, isRequired: false, transformFunction: null }, skipLinkLabel: { classPropertyName: "skipLinkLabel", publicName: "skipLinkLabel", isSignal: true, isRequired: false, transformFunction: null }, closeNavLabel: { classPropertyName: "closeNavLabel", publicName: "closeNavLabel", isSignal: true, isRequired: false, transformFunction: null }, closeNavOnNavigate: { classPropertyName: "closeNavOnNavigate", publicName: "closeNavOnNavigate", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "strct-shell" }, providers: [StrctShellService], ngImport: i0, template: `
|
|
1656
|
+
@if (skipLinkTarget()) {
|
|
1657
|
+
<!-- The first focusable element on the page, invisible until focused. -->
|
|
1658
|
+
<a
|
|
1659
|
+
class="strct-shell__skip"
|
|
1660
|
+
[attr.href]="'#' + skipLinkTarget()"
|
|
1661
|
+
(click)="skipToMain($event)"
|
|
1662
|
+
>{{ skipLinkLabel() }}</a
|
|
1663
|
+
>
|
|
1664
|
+
}
|
|
1665
|
+
<ng-content select="strct-header" />
|
|
1666
|
+
@if (shell.mobileNavOpen()) {
|
|
1667
|
+
<!-- Outside the drawer, which is transformed and would otherwise be this
|
|
1668
|
+
fixed element's containing block (BUG-53-01). -->
|
|
1669
|
+
<div
|
|
1670
|
+
class="strct-vnav__backdrop"
|
|
1671
|
+
tabindex="-1"
|
|
1672
|
+
role="button"
|
|
1673
|
+
[attr.aria-label]="closeNavLabel()"
|
|
1674
|
+
(click)="shell.mobileNavOpen.set(false)"
|
|
1675
|
+
(keydown.enter)="shell.mobileNavOpen.set(false)"
|
|
1676
|
+
(keydown.space)="shell.mobileNavOpen.set(false)"
|
|
1677
|
+
></div>
|
|
1678
|
+
}
|
|
1679
|
+
<div class="strct-shell__main"><ng-content /></div>
|
|
1680
|
+
<ng-content select="strct-footer" />
|
|
1681
|
+
`, isInline: true, styles: [".strct-shell{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden;background:var(--bg-0)}.strct-shell__main{display:flex;min-height:0;overflow:hidden}.strct-shell__skip{position:absolute;inset-inline-start:0;top:0;z-index:1;transform:translateY(-120%);padding:var(--space-2) var(--space-3);border-radius:0 0 var(--radius-md) 0;background:var(--acc);color:var(--inv);font-size:var(--text-sm);text-decoration:none}.strct-shell__skip:focus-visible{transform:none;outline:2px solid var(--acc50);outline-offset:2px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1682
|
+
}
|
|
1683
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctShell, decorators: [{
|
|
1684
|
+
type: Component,
|
|
1685
|
+
args: [{ selector: 'strct-shell', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [StrctShellService], template: `
|
|
1686
|
+
@if (skipLinkTarget()) {
|
|
1687
|
+
<!-- The first focusable element on the page, invisible until focused. -->
|
|
1688
|
+
<a
|
|
1689
|
+
class="strct-shell__skip"
|
|
1690
|
+
[attr.href]="'#' + skipLinkTarget()"
|
|
1691
|
+
(click)="skipToMain($event)"
|
|
1692
|
+
>{{ skipLinkLabel() }}</a
|
|
1693
|
+
>
|
|
1694
|
+
}
|
|
1695
|
+
<ng-content select="strct-header" />
|
|
1696
|
+
@if (shell.mobileNavOpen()) {
|
|
1697
|
+
<!-- Outside the drawer, which is transformed and would otherwise be this
|
|
1698
|
+
fixed element's containing block (BUG-53-01). -->
|
|
1699
|
+
<div
|
|
1700
|
+
class="strct-vnav__backdrop"
|
|
1701
|
+
tabindex="-1"
|
|
1702
|
+
role="button"
|
|
1703
|
+
[attr.aria-label]="closeNavLabel()"
|
|
1704
|
+
(click)="shell.mobileNavOpen.set(false)"
|
|
1705
|
+
(keydown.enter)="shell.mobileNavOpen.set(false)"
|
|
1706
|
+
(keydown.space)="shell.mobileNavOpen.set(false)"
|
|
1707
|
+
></div>
|
|
1708
|
+
}
|
|
1709
|
+
<div class="strct-shell__main"><ng-content /></div>
|
|
1710
|
+
<ng-content select="strct-footer" />
|
|
1711
|
+
`, host: { class: 'strct-shell' }, styles: [".strct-shell{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden;background:var(--bg-0)}.strct-shell__main{display:flex;min-height:0;overflow:hidden}.strct-shell__skip{position:absolute;inset-inline-start:0;top:0;z-index:1;transform:translateY(-120%);padding:var(--space-2) var(--space-3);border-radius:0 0 var(--radius-md) 0;background:var(--acc);color:var(--inv);font-size:var(--text-sm);text-decoration:none}.strct-shell__skip:focus-visible{transform:none;outline:2px solid var(--acc50);outline-offset:2px}\n"] }]
|
|
1712
|
+
}], ctorParameters: () => [], propDecorators: { skipLinkTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipLinkTarget", required: false }] }], skipLinkLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipLinkLabel", required: false }] }], closeNavLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeNavLabel", required: false }] }], closeNavOnNavigate: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeNavOnNavigate", required: false }] }] } });
|
|
1713
|
+
/**
|
|
1714
|
+
* An action in the header that knows whether it may move into the overflow
|
|
1715
|
+
* menu on a phone:
|
|
1716
|
+
*
|
|
1717
|
+
* <ng-container *strctHeaderAction="{ label: 'Create', icon: 'plus', run: create }">
|
|
1718
|
+
* <button strct-button variant="flat" iconOnly aria-label="Create">…</button>
|
|
1719
|
+
* </ng-container>
|
|
1720
|
+
*
|
|
1721
|
+
* Content projected into the header without this directive never moves.
|
|
1722
|
+
*/
|
|
1723
|
+
class StrctHeaderAction {
|
|
1724
|
+
config = input.required({ ...(ngDevMode ? { debugName: "config" } : /* istanbul ignore next */ {}), alias: 'strctHeaderAction' });
|
|
1725
|
+
template = inject(TemplateRef);
|
|
1726
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctHeaderAction, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1727
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: StrctHeaderAction, isStandalone: true, selector: "[strctHeaderAction]", inputs: { config: { classPropertyName: "config", publicName: "strctHeaderAction", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
|
|
1728
|
+
}
|
|
1729
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctHeaderAction, decorators: [{
|
|
1730
|
+
type: Directive,
|
|
1731
|
+
args: [{ selector: '[strctHeaderAction]' }]
|
|
1732
|
+
}], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "strctHeaderAction", required: true }] }] } });
|
|
1733
|
+
/** Top application bar. Holds brand on the left and actions on the right. */
|
|
1734
|
+
class StrctHeader {
|
|
1735
|
+
/** Accessible label of the mobile drawer toggle. */
|
|
1140
1736
|
drawerToggleAriaLabel = input('Toggle navigation', /* @ts-ignore */
|
|
1141
1737
|
...(ngDevMode ? [{ debugName: "drawerToggleAriaLabel" }] : /* istanbul ignore next */ []));
|
|
1738
|
+
/**
|
|
1739
|
+
* A thin line in a product's colour under the header — a colour token or
|
|
1740
|
+
* any CSS colour. It is drawn inset, so the header keeps its height and
|
|
1741
|
+
* nothing under it moves. Empty leaves today's border alone.
|
|
1742
|
+
*/
|
|
1743
|
+
accent = input('', /* @ts-ignore */
|
|
1744
|
+
...(ngDevMode ? [{ debugName: "accent" }] : /* istanbul ignore next */ []));
|
|
1745
|
+
/**
|
|
1746
|
+
* Below this width (px) the actions marked `overflow: 'phone'` move into a
|
|
1747
|
+
* "More" menu. A console header carries more icons than a phone has room
|
|
1748
|
+
* for, and an app should not have to draw two copies of each.
|
|
1749
|
+
*/
|
|
1750
|
+
overflowBreakpoint = input(600, /* @ts-ignore */
|
|
1751
|
+
...(ngDevMode ? [{ debugName: "overflowBreakpoint" }] : /* istanbul ignore next */ []));
|
|
1752
|
+
/** The "More" trigger's accessible name (localisable). */
|
|
1753
|
+
overflowLabel = input('More', /* @ts-ignore */
|
|
1754
|
+
...(ngDevMode ? [{ debugName: "overflowLabel" }] : /* istanbul ignore next */ []));
|
|
1755
|
+
actions = contentChildren(StrctHeaderAction, /* @ts-ignore */
|
|
1756
|
+
...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
|
|
1757
|
+
/** Whether the viewport is below `overflowBreakpoint`. */
|
|
1758
|
+
narrow = signal(false, /* @ts-ignore */
|
|
1759
|
+
...(ngDevMode ? [{ debugName: "narrow" }] : /* istanbul ignore next */ []));
|
|
1760
|
+
inlineActions = computed(() => this.actions().filter((a) => !this.narrow() || a.config().overflow === 'never'), /* @ts-ignore */
|
|
1761
|
+
...(ngDevMode ? [{ debugName: "inlineActions" }] : /* istanbul ignore next */ []));
|
|
1762
|
+
overflowActions = computed(() => this.narrow() ? this.actions().filter((a) => a.config().overflow !== 'never') : [], /* @ts-ignore */
|
|
1763
|
+
...(ngDevMode ? [{ debugName: "overflowActions" }] : /* istanbul ignore next */ []));
|
|
1764
|
+
/** An action that brings its own control makes the panel a dialog, not a menu. */
|
|
1765
|
+
overflowIsPanel = computed(() => this.overflowActions().some((a) => !a.config().run), /* @ts-ignore */
|
|
1766
|
+
...(ngDevMode ? [{ debugName: "overflowIsPanel" }] : /* istanbul ignore next */ []));
|
|
1767
|
+
overflowPanel = viewChild('overflow', /* @ts-ignore */
|
|
1768
|
+
...(ngDevMode ? [{ debugName: "overflowPanel" }] : /* istanbul ignore next */ []));
|
|
1769
|
+
/** In panel mode nothing closes on activation, so a command closes it. */
|
|
1770
|
+
runAndClose(run) {
|
|
1771
|
+
run();
|
|
1772
|
+
this.overflowPanel()?.close(true);
|
|
1773
|
+
}
|
|
1142
1774
|
shell = inject(StrctShellService);
|
|
1775
|
+
hostEl = inject(ElementRef);
|
|
1776
|
+
constructor() {
|
|
1777
|
+
// Dev-only: the shell clips what overflows the header, so a header that
|
|
1778
|
+
// does not fit a phone fails silently — the triggers are simply not
|
|
1779
|
+
// there. Say so, with the two numbers that make it obvious.
|
|
1780
|
+
afterNextRender(() => {
|
|
1781
|
+
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
|
1782
|
+
const el = this.hostEl.nativeElement;
|
|
1783
|
+
const over = el.scrollWidth - el.clientWidth;
|
|
1784
|
+
if (over > 1) {
|
|
1785
|
+
strctDevWarn('header-overflow', `[strct] strct-header: its content is ${el.scrollWidth}px wide in a ` +
|
|
1786
|
+
`${el.clientWidth}px header, so ${over}px of it is clipped and cannot be ` +
|
|
1787
|
+
'reached. Mark the triggers that may move with *strctHeaderAction so they ' +
|
|
1788
|
+
'collect into the header\u2019s "More" menu below overflowBreakpoint.');
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
1792
|
+
// The breakpoint has to be read in script, not only in CSS: moving an
|
|
1793
|
+
// action means rendering it somewhere else, which no media query can do.
|
|
1794
|
+
effect((onCleanup) => {
|
|
1795
|
+
const px = this.overflowBreakpoint();
|
|
1796
|
+
if (typeof matchMedia !== 'function')
|
|
1797
|
+
return;
|
|
1798
|
+
const query = matchMedia(`(max-width: ${px}px)`);
|
|
1799
|
+
const update = () => this.narrow.set(query.matches);
|
|
1800
|
+
update();
|
|
1801
|
+
query.addEventListener('change', update);
|
|
1802
|
+
onCleanup(() => query.removeEventListener('change', update));
|
|
1803
|
+
});
|
|
1804
|
+
// The drawer hands focus back here when it closes, so the shell has to
|
|
1805
|
+
// know which element opened it.
|
|
1806
|
+
afterNextRender(() => {
|
|
1807
|
+
this.shell.drawerToggle = this.hostEl.nativeElement.querySelector('.strct-header__drawer-toggle');
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1143
1810
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1144
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
1811
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctHeader, isStandalone: true, selector: "strct-header", inputs: { drawerToggleAriaLabel: { classPropertyName: "drawerToggleAriaLabel", publicName: "drawerToggleAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, overflowBreakpoint: { classPropertyName: "overflowBreakpoint", publicName: "overflowBreakpoint", isSignal: true, isRequired: false, transformFunction: null }, overflowLabel: { classPropertyName: "overflowLabel", publicName: "overflowLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-header--accent": "accent()", "style.--strct-header-accent": "accent() || null" }, classAttribute: "strct-header" }, queries: [{ propertyName: "actions", predicate: StrctHeaderAction, isSignal: true }], viewQueries: [{ propertyName: "overflowPanel", first: true, predicate: ["overflow"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1145
1812
|
<button
|
|
1146
1813
|
type="button"
|
|
1147
1814
|
class="strct-header__drawer-toggle"
|
|
@@ -1153,11 +1820,74 @@ class StrctHeader {
|
|
|
1153
1820
|
<strct-icon name="menu" [size]="18" />
|
|
1154
1821
|
</button>
|
|
1155
1822
|
<ng-content />
|
|
1156
|
-
|
|
1823
|
+
@if (actions().length) {
|
|
1824
|
+
<span class="strct-header__actions">
|
|
1825
|
+
@for (action of inlineActions(); track action) {
|
|
1826
|
+
<ng-container *ngTemplateOutlet="action.template" />
|
|
1827
|
+
}
|
|
1828
|
+
@if (overflowActions().length) {
|
|
1829
|
+
<!-- A menu may only hold menu items, so a panel that carries a
|
|
1830
|
+
control of its own (a theme switcher, a segmented choice) is a
|
|
1831
|
+
labelled dialog instead — the dropdown's popover mode. A panel
|
|
1832
|
+
of plain commands stays a menu, with its arrow keys. -->
|
|
1833
|
+
<strct-dropdown
|
|
1834
|
+
#overflow
|
|
1835
|
+
align="end"
|
|
1836
|
+
[popover]="overflowIsPanel()"
|
|
1837
|
+
[focusable]="overflowIsPanel()"
|
|
1838
|
+
[popoverLabel]="overflowLabel()"
|
|
1839
|
+
>
|
|
1840
|
+
<button
|
|
1841
|
+
type="button"
|
|
1842
|
+
class="strct-header__more"
|
|
1843
|
+
strctDropdownTrigger
|
|
1844
|
+
[attr.aria-label]="overflowLabel()"
|
|
1845
|
+
>
|
|
1846
|
+
<strct-icon name="dots" [size]="18" />
|
|
1847
|
+
</button>
|
|
1848
|
+
@for (action of overflowActions(); track action) {
|
|
1849
|
+
@if (action.config().run; as run) {
|
|
1850
|
+
@if (overflowIsPanel()) {
|
|
1851
|
+
<button type="button" class="strct-header__morecmd" (click)="runAndClose(run)">
|
|
1852
|
+
@if (action.config().icon; as icon) {
|
|
1853
|
+
<strct-icon [name]="icon" [size]="15" />
|
|
1854
|
+
}
|
|
1855
|
+
{{ action.config().label }}
|
|
1856
|
+
</button>
|
|
1857
|
+
} @else {
|
|
1858
|
+
<strct-dropdown-item (click)="run()">
|
|
1859
|
+
@if (action.config().icon; as icon) {
|
|
1860
|
+
<strct-icon strctDropdownItemIcon [name]="icon" [size]="15" />
|
|
1861
|
+
}
|
|
1862
|
+
{{ action.config().label }}
|
|
1863
|
+
</strct-dropdown-item>
|
|
1864
|
+
}
|
|
1865
|
+
} @else {
|
|
1866
|
+
<div
|
|
1867
|
+
class="strct-header__moreslot"
|
|
1868
|
+
role="group"
|
|
1869
|
+
[attr.aria-label]="action.config().label"
|
|
1870
|
+
>
|
|
1871
|
+
<ng-container *ngTemplateOutlet="action.template" />
|
|
1872
|
+
</div>
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
</strct-dropdown>
|
|
1876
|
+
}
|
|
1877
|
+
</span>
|
|
1878
|
+
}
|
|
1879
|
+
`, isInline: true, styles: [".strct-vnav__backdrop{position:fixed;inset:0;z-index:calc(var(--z-nav) - 1);background:var(--backdrop);animation:strct-vnav-backdrop-in .2s ease}@media(min-width:769px){.strct-vnav__backdrop{display:none}}@keyframes strct-vnav-backdrop-in{0%{opacity:0}to{opacity:1}}.strct-header{display:flex;align-items:center;gap:var(--strct-header-gap, 14px);height:56px;padding:0 var(--strct-header-pad, 18px);background:var(--hdr);border-bottom:1px solid var(--b2);color:var(--hdr-fg)}.strct-header--accent{box-shadow:inset 0 -3px 0 var(--strct-header-accent, var(--acc))}.strct-header__actions{display:flex;align-items:center;gap:inherit;min-width:0}.strct-header__more{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:0;border-radius:var(--radius-sm);background:transparent;color:inherit;cursor:pointer}.strct-header__more:hover{background:#ffffff1f}.strct-header__moreslot{display:flex;align-items:center;padding:6px 10px}.strct-header__morecmd{display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;padding:9px 10px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:start;cursor:pointer}.strct-header__morecmd:hover,.strct-header__morecmd:focus-visible{background:var(--bg-3);outline:none}@media(max-width:600px){.strct-header{gap:var(--strct-header-gap, 4px);padding:0 var(--strct-header-pad, 6px)}}.strct-header .strct-btn--flat:not(.strct-modal__dialog *,.strct-drawer *,.strct-popover__panel *,.strct-dd__menu *,.strct-tour__card *){color:color-mix(in srgb,var(--hdr-fg) 72%,transparent)}.strct-header .strct-btn--flat:not(.strct-modal__dialog *,.strct-drawer *,.strct-popover__panel *,.strct-dd__menu *,.strct-tour__card *):hover:not(:disabled){background:color-mix(in srgb,var(--hdr-fg) 12%,transparent);color:var(--hdr-fg)}.strct-header .strct-btn--flat:not(.strct-modal__dialog *,.strct-drawer *,.strct-popover__panel *,.strct-dd__menu *,.strct-tour__card *):focus-visible{outline-color:color-mix(in srgb,var(--hdr-fg) 60%,transparent)}.strct-header__drawer-toggle{display:none;align-items:center;justify-content:center;padding:4px;border:0;border-radius:5px;background:transparent;color:color-mix(in srgb,var(--hdr-fg) 85%,transparent);cursor:pointer}.strct-header__drawer-toggle:hover{background:#ffffff1f;color:var(--hdr-fg)}@media(max-width:768px){.strct-header__drawer-toggle{display:inline-flex}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }, { kind: "component", type: StrctDropdown, selector: "strct-dropdown", inputs: ["align", "popover", "popoverLabel", "focusable"] }, { kind: "directive", type: StrctDropdownTrigger, selector: "[strctDropdownTrigger]" }, { kind: "component", type: StrctDropdownItem, selector: "strct-dropdown-item", inputs: ["selected", "critical", "disabled", "hint"] }, { kind: "directive", type: StrctDropdownItemIcon, selector: "[strctDropdownItemIcon]", inputs: ["tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1157
1880
|
}
|
|
1158
1881
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctHeader, decorators: [{
|
|
1159
1882
|
type: Component,
|
|
1160
|
-
args: [{ selector: 'strct-header', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [
|
|
1883
|
+
args: [{ selector: 'strct-header', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [
|
|
1884
|
+
NgTemplateOutlet,
|
|
1885
|
+
StrctIcon,
|
|
1886
|
+
StrctDropdown,
|
|
1887
|
+
StrctDropdownTrigger,
|
|
1888
|
+
StrctDropdownItem,
|
|
1889
|
+
StrctDropdownItemIcon,
|
|
1890
|
+
], template: `
|
|
1161
1891
|
<button
|
|
1162
1892
|
type="button"
|
|
1163
1893
|
class="strct-header__drawer-toggle"
|
|
@@ -1169,8 +1899,68 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
1169
1899
|
<strct-icon name="menu" [size]="18" />
|
|
1170
1900
|
</button>
|
|
1171
1901
|
<ng-content />
|
|
1172
|
-
|
|
1173
|
-
|
|
1902
|
+
@if (actions().length) {
|
|
1903
|
+
<span class="strct-header__actions">
|
|
1904
|
+
@for (action of inlineActions(); track action) {
|
|
1905
|
+
<ng-container *ngTemplateOutlet="action.template" />
|
|
1906
|
+
}
|
|
1907
|
+
@if (overflowActions().length) {
|
|
1908
|
+
<!-- A menu may only hold menu items, so a panel that carries a
|
|
1909
|
+
control of its own (a theme switcher, a segmented choice) is a
|
|
1910
|
+
labelled dialog instead — the dropdown's popover mode. A panel
|
|
1911
|
+
of plain commands stays a menu, with its arrow keys. -->
|
|
1912
|
+
<strct-dropdown
|
|
1913
|
+
#overflow
|
|
1914
|
+
align="end"
|
|
1915
|
+
[popover]="overflowIsPanel()"
|
|
1916
|
+
[focusable]="overflowIsPanel()"
|
|
1917
|
+
[popoverLabel]="overflowLabel()"
|
|
1918
|
+
>
|
|
1919
|
+
<button
|
|
1920
|
+
type="button"
|
|
1921
|
+
class="strct-header__more"
|
|
1922
|
+
strctDropdownTrigger
|
|
1923
|
+
[attr.aria-label]="overflowLabel()"
|
|
1924
|
+
>
|
|
1925
|
+
<strct-icon name="dots" [size]="18" />
|
|
1926
|
+
</button>
|
|
1927
|
+
@for (action of overflowActions(); track action) {
|
|
1928
|
+
@if (action.config().run; as run) {
|
|
1929
|
+
@if (overflowIsPanel()) {
|
|
1930
|
+
<button type="button" class="strct-header__morecmd" (click)="runAndClose(run)">
|
|
1931
|
+
@if (action.config().icon; as icon) {
|
|
1932
|
+
<strct-icon [name]="icon" [size]="15" />
|
|
1933
|
+
}
|
|
1934
|
+
{{ action.config().label }}
|
|
1935
|
+
</button>
|
|
1936
|
+
} @else {
|
|
1937
|
+
<strct-dropdown-item (click)="run()">
|
|
1938
|
+
@if (action.config().icon; as icon) {
|
|
1939
|
+
<strct-icon strctDropdownItemIcon [name]="icon" [size]="15" />
|
|
1940
|
+
}
|
|
1941
|
+
{{ action.config().label }}
|
|
1942
|
+
</strct-dropdown-item>
|
|
1943
|
+
}
|
|
1944
|
+
} @else {
|
|
1945
|
+
<div
|
|
1946
|
+
class="strct-header__moreslot"
|
|
1947
|
+
role="group"
|
|
1948
|
+
[attr.aria-label]="action.config().label"
|
|
1949
|
+
>
|
|
1950
|
+
<ng-container *ngTemplateOutlet="action.template" />
|
|
1951
|
+
</div>
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
</strct-dropdown>
|
|
1955
|
+
}
|
|
1956
|
+
</span>
|
|
1957
|
+
}
|
|
1958
|
+
`, host: {
|
|
1959
|
+
class: 'strct-header',
|
|
1960
|
+
'[class.strct-header--accent]': 'accent()',
|
|
1961
|
+
'[style.--strct-header-accent]': 'accent() || null',
|
|
1962
|
+
}, styles: [".strct-vnav__backdrop{position:fixed;inset:0;z-index:calc(var(--z-nav) - 1);background:var(--backdrop);animation:strct-vnav-backdrop-in .2s ease}@media(min-width:769px){.strct-vnav__backdrop{display:none}}@keyframes strct-vnav-backdrop-in{0%{opacity:0}to{opacity:1}}.strct-header{display:flex;align-items:center;gap:var(--strct-header-gap, 14px);height:56px;padding:0 var(--strct-header-pad, 18px);background:var(--hdr);border-bottom:1px solid var(--b2);color:var(--hdr-fg)}.strct-header--accent{box-shadow:inset 0 -3px 0 var(--strct-header-accent, var(--acc))}.strct-header__actions{display:flex;align-items:center;gap:inherit;min-width:0}.strct-header__more{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:0;border-radius:var(--radius-sm);background:transparent;color:inherit;cursor:pointer}.strct-header__more:hover{background:#ffffff1f}.strct-header__moreslot{display:flex;align-items:center;padding:6px 10px}.strct-header__morecmd{display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;padding:9px 10px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:start;cursor:pointer}.strct-header__morecmd:hover,.strct-header__morecmd:focus-visible{background:var(--bg-3);outline:none}@media(max-width:600px){.strct-header{gap:var(--strct-header-gap, 4px);padding:0 var(--strct-header-pad, 6px)}}.strct-header .strct-btn--flat:not(.strct-modal__dialog *,.strct-drawer *,.strct-popover__panel *,.strct-dd__menu *,.strct-tour__card *){color:color-mix(in srgb,var(--hdr-fg) 72%,transparent)}.strct-header .strct-btn--flat:not(.strct-modal__dialog *,.strct-drawer *,.strct-popover__panel *,.strct-dd__menu *,.strct-tour__card *):hover:not(:disabled){background:color-mix(in srgb,var(--hdr-fg) 12%,transparent);color:var(--hdr-fg)}.strct-header .strct-btn--flat:not(.strct-modal__dialog *,.strct-drawer *,.strct-popover__panel *,.strct-dd__menu *,.strct-tour__card *):focus-visible{outline-color:color-mix(in srgb,var(--hdr-fg) 60%,transparent)}.strct-header__drawer-toggle{display:none;align-items:center;justify-content:center;padding:4px;border:0;border-radius:5px;background:transparent;color:color-mix(in srgb,var(--hdr-fg) 85%,transparent);cursor:pointer}.strct-header__drawer-toggle:hover{background:#ffffff1f;color:var(--hdr-fg)}@media(max-width:768px){.strct-header__drawer-toggle{display:inline-flex}}\n"] }]
|
|
1963
|
+
}], ctorParameters: () => [], propDecorators: { drawerToggleAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "drawerToggleAriaLabel", required: false }] }], accent: [{ type: i0.Input, args: [{ isSignal: true, alias: "accent", required: false }] }], overflowBreakpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "overflowBreakpoint", required: false }] }], overflowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "overflowLabel", required: false }] }], actions: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctHeaderAction), { isSignal: true }] }], overflowPanel: [{ type: i0.ViewChild, args: ['overflow', { isSignal: true }] }] } });
|
|
1174
1964
|
/** Bottom status bar. */
|
|
1175
1965
|
class StrctFooter {
|
|
1176
1966
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -1181,40 +1971,186 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
1181
1971
|
args: [{ selector: 'strct-footer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `<ng-content />`, host: { class: 'strct-footer' }, styles: [".strct-footer{display:flex;align-items:center;gap:12px;height:32px;padding:0 16px;background:var(--bg-1);border-top:1px solid var(--b2);font-size:12px;color:var(--t2)}\n"] }]
|
|
1182
1972
|
}] });
|
|
1183
1973
|
|
|
1974
|
+
/**
|
|
1975
|
+
* Shared focus-lifecycle helpers for transient overlay surfaces (tooltip,
|
|
1976
|
+
* drawer, menus, popovers …): remember the trigger before opening, move
|
|
1977
|
+
* focus in on open, cycle Tab inside, and hand focus back on close.
|
|
1978
|
+
* Modeled on the modal's local implementation (`modal/modal.ts`).
|
|
1979
|
+
*/
|
|
1980
|
+
/** Elements considered tabbable inside an overlay, queried in DOM order. */
|
|
1981
|
+
const FOCUSABLE_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
1982
|
+
/** Currently focused element, if it is an HTMLElement (remember the trigger before opening). */
|
|
1983
|
+
function saveFocusedElement() {
|
|
1984
|
+
const active = document.activeElement;
|
|
1985
|
+
return active instanceof HTMLElement ? active : null;
|
|
1986
|
+
}
|
|
1987
|
+
/** Focus `el` again if it is still in the document; null-safe. */
|
|
1988
|
+
function restoreFocus(el) {
|
|
1989
|
+
if (el?.isConnected)
|
|
1990
|
+
el.focus();
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Focus the first tabbable descendant of `container`.
|
|
1994
|
+
* Returns whether something was focused; with nothing tabbable the container
|
|
1995
|
+
* itself is NOT focused — the caller decides the fallback.
|
|
1996
|
+
*/
|
|
1997
|
+
function focusFirstIn(container) {
|
|
1998
|
+
const first = container.querySelector(FOCUSABLE_SELECTOR);
|
|
1999
|
+
if (!first)
|
|
2000
|
+
return false;
|
|
2001
|
+
first.focus();
|
|
2002
|
+
return true;
|
|
2003
|
+
}
|
|
2004
|
+
/**
|
|
2005
|
+
* Tab trap for modal-ish overlays: cycle Tab/Shift+Tab within the
|
|
2006
|
+
* container's tabbable descendants, wrapping at both ends (preventDefault
|
|
2007
|
+
* when it wraps). Wire it to the container's `keydown.tab` /
|
|
2008
|
+
* `keydown.shift.tab` events.
|
|
2009
|
+
*/
|
|
2010
|
+
function keepTabInside(event, container) {
|
|
2011
|
+
const items = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
|
|
2012
|
+
if (!items.length) {
|
|
2013
|
+
event.preventDefault();
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
const first = items[0];
|
|
2017
|
+
const last = items[items.length - 1];
|
|
2018
|
+
const active = container.ownerDocument.activeElement;
|
|
2019
|
+
if (event.shiftKey && (active === first || !container.contains(active))) {
|
|
2020
|
+
event.preventDefault();
|
|
2021
|
+
last.focus();
|
|
2022
|
+
}
|
|
2023
|
+
else if (!event.shiftKey && active === last) {
|
|
2024
|
+
event.preventDefault();
|
|
2025
|
+
first.focus();
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
1184
2029
|
/** Scrollable left sidebar surface. Holds the icon nav and/or a tree. */
|
|
1185
2030
|
class StrctVerticalNav {
|
|
1186
2031
|
shell = inject(StrctShellService);
|
|
2032
|
+
host = inject(ElementRef);
|
|
2033
|
+
/**
|
|
2034
|
+
* How wide the drawer may get on a phone — only in drawer mode, so the
|
|
2035
|
+
* width an app sets for its desktop sidebar is untouched. The default
|
|
2036
|
+
* leaves 48px of page to tap the drawer away.
|
|
2037
|
+
*/
|
|
2038
|
+
drawerMaxWidth = input('', /* @ts-ignore */
|
|
2039
|
+
...(ngDevMode ? [{ debugName: "drawerMaxWidth" }] : /* istanbul ignore next */ []));
|
|
2040
|
+
/**
|
|
2041
|
+
* Draw a close control at the drawer's top. Escape and the backdrop close
|
|
2042
|
+
* it too, but neither is visible, and a touch screen has no Escape.
|
|
2043
|
+
*/
|
|
2044
|
+
closeButton = input(false, { ...(ngDevMode ? { debugName: "closeButton" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2045
|
+
/** The close control's accessible name (localisable). */
|
|
2046
|
+
closeLabel = input('Close navigation', /* @ts-ignore */
|
|
2047
|
+
...(ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []));
|
|
2048
|
+
/**
|
|
2049
|
+
* Keep Tab inside the open drawer and move focus into it, the way a dialog
|
|
2050
|
+
* does. It is only ever on in drawer mode: a sidebar that is part of the
|
|
2051
|
+
* page must not trap anything.
|
|
2052
|
+
*/
|
|
2053
|
+
trapFocus = input(true, { ...(ngDevMode ? { debugName: "trapFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2054
|
+
/** True while the drawer is open AND the viewport is in drawer mode. */
|
|
2055
|
+
trapped() {
|
|
2056
|
+
return this.shell.mobileNavOpen() && this.trapFocus() && this.isDrawer();
|
|
2057
|
+
}
|
|
2058
|
+
constructor() {
|
|
2059
|
+
// Opening the drawer without moving focus leaves the keyboard behind the
|
|
2060
|
+
// page it covers; closing it without handing focus back leaves it nowhere.
|
|
2061
|
+
effect(() => {
|
|
2062
|
+
const open = this.shell.mobileNavOpen();
|
|
2063
|
+
untracked(() => {
|
|
2064
|
+
if (!this.isDrawer())
|
|
2065
|
+
return;
|
|
2066
|
+
if (open) {
|
|
2067
|
+
const el = this.host.nativeElement;
|
|
2068
|
+
// After the open class has painted, or the drawer is still
|
|
2069
|
+
// translated off-screen and focusing it scrolls the page.
|
|
2070
|
+
requestAnimationFrame(() => {
|
|
2071
|
+
if (!focusFirstIn(el)) {
|
|
2072
|
+
el.setAttribute('tabindex', '-1');
|
|
2073
|
+
el.focus();
|
|
2074
|
+
}
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
else {
|
|
2078
|
+
restoreFocus(this.shell.drawerToggle);
|
|
2079
|
+
}
|
|
2080
|
+
});
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
1187
2083
|
/** Closes the mobile nav on Escape. */
|
|
1188
2084
|
onEscape() {
|
|
1189
2085
|
if (this.shell.mobileNavOpen())
|
|
1190
|
-
this.shell.
|
|
2086
|
+
this.shell.closeNav();
|
|
2087
|
+
}
|
|
2088
|
+
onTab(event) {
|
|
2089
|
+
if (this.trapped())
|
|
2090
|
+
keepTabInside(event, this.host.nativeElement);
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* The drawer breakpoint is the media query the styles use; matchMedia is
|
|
2094
|
+
* the only way the component can read it, and a server render has none.
|
|
2095
|
+
*/
|
|
2096
|
+
isDrawer() {
|
|
2097
|
+
return typeof matchMedia === 'function' ? matchMedia('(max-width: 768px)').matches : false;
|
|
1191
2098
|
}
|
|
1192
2099
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctVerticalNav, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1193
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
2100
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctVerticalNav, isStandalone: true, selector: "strct-vertical-nav", inputs: { drawerMaxWidth: { classPropertyName: "drawerMaxWidth", publicName: "drawerMaxWidth", isSignal: true, isRequired: false, transformFunction: null }, closeButton: { classPropertyName: "closeButton", publicName: "closeButton", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, trapFocus: { classPropertyName: "trapFocus", publicName: "trapFocus", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:keydown.escape": "onEscape()", "keydown.tab": "onTab($event)", "keydown.shift.tab": "onTab($event)" }, properties: { "id": "shell.navId", "class.strct-vnav--open": "shell.mobileNavOpen()", "style.--strct-vnav-drawer-max": "drawerMaxWidth() || null", "attr.aria-modal": "trapped() ? \"true\" : null", "attr.role": "trapped() ? \"dialog\" : null" }, classAttribute: "strct-vnav" }, ngImport: i0, template: `
|
|
1194
2101
|
<!-- The backdrop is drawn by strct-shell. Here it was a fixed child of a
|
|
1195
2102
|
transformed element, so its inset: 0 was the drawer's own box and its
|
|
1196
2103
|
z-index put it above the drawer's content: the page beside the drawer
|
|
1197
2104
|
was never dimmed, and every tap on the rail or the menu landed on the
|
|
1198
2105
|
backdrop and closed the drawer. -->
|
|
2106
|
+
@if (closeButton()) {
|
|
2107
|
+
<!-- Only in drawer mode (CSS): on a wide screen the sidebar is just
|
|
2108
|
+
part of the page and has nothing to close. -->
|
|
2109
|
+
<button
|
|
2110
|
+
type="button"
|
|
2111
|
+
class="strct-vnav__close"
|
|
2112
|
+
[attr.aria-label]="closeLabel()"
|
|
2113
|
+
(click)="shell.closeNav()"
|
|
2114
|
+
>
|
|
2115
|
+
<strct-icon name="close" [size]="16" />
|
|
2116
|
+
</button>
|
|
2117
|
+
}
|
|
1199
2118
|
<ng-content />
|
|
1200
|
-
`, isInline: true, styles: [".strct-vnav{display:flex;flex-direction:column;width:264px;flex-shrink:0;background:var(--bg-1);border-inline-end:1px solid var(--b2);overflow:hidden}@media(max-width:768px){.strct-vnav{position:fixed;top:0;inset-inline-start:0;bottom:0;z-index:var(--z-nav);transform:translate(-100%);transition:transform .2s ease}[dir=rtl] .strct-vnav{transform:translate(100%)}.strct-vnav--open,[dir=rtl] .strct-vnav--open{transform:translate(0)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2119
|
+
`, isInline: true, styles: [".strct-vnav{display:flex;flex-direction:column;width:264px;flex-shrink:0;background:var(--bg-1);border-inline-end:1px solid var(--b2);overflow:hidden}.strct-vnav__close{display:none}@media(max-width:768px){.strct-vnav{position:fixed;top:0;inset-inline-start:0;bottom:0;z-index:var(--z-nav);transform:translate(-100%);transition:transform .2s ease;max-width:var(--strct-vnav-drawer-max, calc(100vw - 48px) )}.strct-vnav__close{display:inline-flex;align-items:center;justify-content:center;align-self:flex-end;flex:none;width:34px;height:34px;margin:8px 8px 0;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--t2);cursor:pointer}.strct-vnav__close:hover{background:var(--bg-3);color:var(--t1)}[dir=rtl] .strct-vnav{transform:translate(100%)}.strct-vnav--open,[dir=rtl] .strct-vnav--open{transform:translate(0)}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1201
2120
|
}
|
|
1202
2121
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctVerticalNav, decorators: [{
|
|
1203
2122
|
type: Component,
|
|
1204
|
-
args: [{ selector: 'strct-vertical-nav', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
2123
|
+
args: [{ selector: 'strct-vertical-nav', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
|
|
1205
2124
|
<!-- The backdrop is drawn by strct-shell. Here it was a fixed child of a
|
|
1206
2125
|
transformed element, so its inset: 0 was the drawer's own box and its
|
|
1207
2126
|
z-index put it above the drawer's content: the page beside the drawer
|
|
1208
2127
|
was never dimmed, and every tap on the rail or the menu landed on the
|
|
1209
2128
|
backdrop and closed the drawer. -->
|
|
2129
|
+
@if (closeButton()) {
|
|
2130
|
+
<!-- Only in drawer mode (CSS): on a wide screen the sidebar is just
|
|
2131
|
+
part of the page and has nothing to close. -->
|
|
2132
|
+
<button
|
|
2133
|
+
type="button"
|
|
2134
|
+
class="strct-vnav__close"
|
|
2135
|
+
[attr.aria-label]="closeLabel()"
|
|
2136
|
+
(click)="shell.closeNav()"
|
|
2137
|
+
>
|
|
2138
|
+
<strct-icon name="close" [size]="16" />
|
|
2139
|
+
</button>
|
|
2140
|
+
}
|
|
1210
2141
|
<ng-content />
|
|
1211
2142
|
`, host: {
|
|
1212
2143
|
class: 'strct-vnav',
|
|
1213
2144
|
'[id]': 'shell.navId',
|
|
1214
2145
|
'[class.strct-vnav--open]': 'shell.mobileNavOpen()',
|
|
2146
|
+
'[style.--strct-vnav-drawer-max]': 'drawerMaxWidth() || null',
|
|
2147
|
+
'[attr.aria-modal]': 'trapped() ? "true" : null',
|
|
2148
|
+
'[attr.role]': 'trapped() ? "dialog" : null',
|
|
1215
2149
|
'(document:keydown.escape)': 'onEscape()',
|
|
1216
|
-
|
|
1217
|
-
|
|
2150
|
+
'(keydown.tab)': 'onTab($event)',
|
|
2151
|
+
'(keydown.shift.tab)': 'onTab($event)',
|
|
2152
|
+
}, styles: [".strct-vnav{display:flex;flex-direction:column;width:264px;flex-shrink:0;background:var(--bg-1);border-inline-end:1px solid var(--b2);overflow:hidden}.strct-vnav__close{display:none}@media(max-width:768px){.strct-vnav{position:fixed;top:0;inset-inline-start:0;bottom:0;z-index:var(--z-nav);transform:translate(-100%);transition:transform .2s ease;max-width:var(--strct-vnav-drawer-max, calc(100vw - 48px) )}.strct-vnav__close{display:inline-flex;align-items:center;justify-content:center;align-self:flex-end;flex:none;width:34px;height:34px;margin:8px 8px 0;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--t2);cursor:pointer}.strct-vnav__close:hover{background:var(--bg-3);color:var(--t1)}[dir=rtl] .strct-vnav{transform:translate(100%)}.strct-vnav--open,[dir=rtl] .strct-vnav--open{transform:translate(0)}}\n"] }]
|
|
2153
|
+
}], ctorParameters: () => [], propDecorators: { drawerMaxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "drawerMaxWidth", required: false }] }], closeButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeButton", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], trapFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "trapFocus", required: false }] }] } });
|
|
1218
2154
|
/** Horizontal strip of icon tabs (e.g. section switcher at the top of a sidebar). */
|
|
1219
2155
|
class StrctNav {
|
|
1220
2156
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctNav, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -2199,31 +3135,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
2199
3135
|
args: [{ selector: 'strct-button-group', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `<ng-content />`, host: { class: 'strct-btn-group', role: 'group' }, styles: [".strct-btn-group{display:inline-flex}.strct-btn-group>.strct-btn{border-radius:0}.strct-btn-group>.strct-btn:not(:first-child){margin-inline-start:-1px}.strct-btn-group>.strct-btn:first-child{border-start-start-radius:var(--radius-md);border-end-start-radius:var(--radius-md)}.strct-btn-group>.strct-btn:last-child{border-start-end-radius:var(--radius-md);border-end-end-radius:var(--radius-md)}.strct-btn-group>.strct-btn:hover,.strct-btn-group>.strct-btn:focus-visible{position:relative;z-index:var(--z-base)}\n"] }]
|
|
2200
3136
|
}] });
|
|
2201
3137
|
|
|
2202
|
-
/**
|
|
2203
|
-
* Dev-mode diagnostics for combinations a component accepts but cannot honour.
|
|
2204
|
-
*
|
|
2205
|
-
* Every message names what was ignored and what to do instead. Keyed: a
|
|
2206
|
-
* condition warns once per page load, not once per change detection.
|
|
2207
|
-
*
|
|
2208
|
-
* Production cost: none, PROVIDED each call site is guarded INLINE with
|
|
2209
|
-
* if (typeof ngDevMode !== 'undefined' && ngDevMode) { … }
|
|
2210
|
-
* Production builds define `ngDevMode` as false, and the minifier then drops
|
|
2211
|
-
* the whole block — messages included. A helper function returning that same
|
|
2212
|
-
* expression would NOT be folded across the call, so the check and its
|
|
2213
|
-
* strings would ship; that is why no such helper exists here.
|
|
2214
|
-
*/
|
|
2215
|
-
const warned = new Set();
|
|
2216
|
-
function strctDevWarn(key, message) {
|
|
2217
|
-
if (typeof ngDevMode === 'undefined' || !ngDevMode || warned.has(key))
|
|
2218
|
-
return;
|
|
2219
|
-
warned.add(key);
|
|
2220
|
-
console.warn(message);
|
|
2221
|
-
}
|
|
2222
|
-
/** Test hook: forget which warnings have fired. Not part of the public API. */
|
|
2223
|
-
function resetStrctDevWarnings() {
|
|
2224
|
-
warned.clear();
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
3138
|
/**
|
|
2228
3139
|
* Dev-mode diagnostics for attributes a component does not have.
|
|
2229
3140
|
*
|
|
@@ -2323,6 +3234,9 @@ class StrctBadge {
|
|
|
2323
3234
|
const el = this.hostEl.nativeElement;
|
|
2324
3235
|
strctCheckHostInputs(el, 'strct-badge', {
|
|
2325
3236
|
status: 'neutral, accent, success, warning, critical',
|
|
3237
|
+
// A real input since 5.13.0 — a colour of its own, so it takes any
|
|
3238
|
+
// colour token or value rather than one of a list.
|
|
3239
|
+
tone: 'a colour token or value, e.g. var(--prod-acc)',
|
|
2326
3240
|
});
|
|
2327
3241
|
}
|
|
2328
3242
|
});
|
|
@@ -2330,10 +3244,25 @@ class StrctBadge {
|
|
|
2330
3244
|
/** Visual status color. */
|
|
2331
3245
|
status = input('neutral', /* @ts-ignore */
|
|
2332
3246
|
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
3247
|
+
/**
|
|
3248
|
+
* A colour of your own — `tone="var(--prod-acc)"` — for a distinction the
|
|
3249
|
+
* five statuses do not carry, such as which product a row belongs to. The
|
|
3250
|
+
* text takes it and the border a 30% wash of it; `solid` fills with it and
|
|
3251
|
+
* writes in `--strct-badge-on` (`--inv` by default). It overrides `status`.
|
|
3252
|
+
*
|
|
3253
|
+
* The same can be set from a stylesheet with `--strct-badge-fg`,
|
|
3254
|
+
* `--strct-badge-border` and `--strct-badge-on`, which is the better route
|
|
3255
|
+
* when a rule already selects the badges (`.product-badge { … }`).
|
|
3256
|
+
*
|
|
3257
|
+
* The colour is yours to pick, so its contrast is yours to check: the
|
|
3258
|
+
* library's own check covers `--acc` and the status tokens.
|
|
3259
|
+
*/
|
|
3260
|
+
tone = input('', /* @ts-ignore */
|
|
3261
|
+
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
2333
3262
|
/** Opt-in filled style instead of the default outlined style. */
|
|
2334
3263
|
solid = input(false, { ...(ngDevMode ? { debugName: "solid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2335
3264
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctBadge, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2336
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctBadge, isStandalone: true, selector: "strct-badge", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, solid: { classPropertyName: "solid", publicName: "solid", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-badge--accent": "status() === 'accent'", "class.strct-badge--success": "status() === 'success'", "class.strct-badge--warning": "status() === 'warning'", "class.strct-badge--warninging": "status() === 'warning'", "class.strct-badge--critical": "status() === 'critical'", "class.strct-badge--solid": "solid()" }, classAttribute: "strct-badge" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [".strct-badge{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:600;line-height:1;letter-spacing:.2px;padding-block:3px;padding-inline:9px 7px;border-radius:var(--radius-sm);white-space:nowrap;color:var(--t2);background:transparent;border:1px solid var(--b3);border-inline-start-width:3px}.strct-badge--accent{color:var(--acc);border-color:var(--acc30)}.strct-badge--success{color:var(--success);border-color:var(--success)}.strct-badge--warning{color:var(--warning);border-color:var(--warning)}.strct-badge--critical{color:var(--critical);border-color:var(--critical)}.strct-badge--solid{color:var(--t1);background:var(--bg-a);border-color:transparent;border-inline-start-width:1px;padding-inline-start:7px}.strct-badge--solid.strct-badge--accent{background:var(--acc);color:var(--inv)}.strct-badge--solid.strct-badge--success{background:var(--success);color:var(--inv)}.strct-badge--solid.strct-badge--warning{background:var(--warning);color:var(--inv)}.strct-badge--solid.strct-badge--critical{background:var(--critical);color:var(--inv)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
3265
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctBadge, isStandalone: true, selector: "strct-badge", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, solid: { classPropertyName: "solid", publicName: "solid", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-badge--accent": "status() === 'accent'", "class.strct-badge--success": "status() === 'success'", "class.strct-badge--warning": "status() === 'warning'", "class.strct-badge--warninging": "status() === 'warning'", "class.strct-badge--critical": "status() === 'critical'", "class.strct-badge--solid": "solid()", "class.strct-badge--tone": "tone()", "style.--strct-badge-fg": "tone() || null" }, classAttribute: "strct-badge" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [".strct-badge{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:600;line-height:1;letter-spacing:.2px;padding-block:3px;padding-inline:9px 7px;border-radius:var(--radius-sm);white-space:nowrap;color:var(--t2);background:transparent;border:1px solid var(--b3);border-inline-start-width:3px}.strct-badge--accent{color:var(--acc);border-color:var(--acc30)}.strct-badge--success{color:var(--success);border-color:var(--success)}.strct-badge--warning{color:var(--warning);border-color:var(--warning)}.strct-badge--critical{color:var(--critical);border-color:var(--critical)}.strct-badge--tone{color:var(--strct-badge-fg, var(--t2));border-color:var( --strct-badge-border, color-mix(in srgb, var(--strct-badge-fg, var(--b3)) 30%, transparent) )}.strct-badge--solid{color:var(--t1);background:var(--bg-a);border-color:transparent;border-inline-start-width:1px;padding-inline-start:7px}.strct-badge--solid.strct-badge--accent{background:var(--acc);color:var(--inv)}.strct-badge--solid.strct-badge--success{background:var(--success);color:var(--inv)}.strct-badge--solid.strct-badge--warning{background:var(--warning);color:var(--inv)}.strct-badge--solid.strct-badge--critical{background:var(--critical);color:var(--inv)}.strct-badge--solid.strct-badge--tone{background:var(--strct-badge-fg, var(--bg-a));color:var(--strct-badge-on, var(--inv));border-color:transparent}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2337
3266
|
}
|
|
2338
3267
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctBadge, decorators: [{
|
|
2339
3268
|
type: Component,
|
|
@@ -2346,8 +3275,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
2346
3275
|
'[class.strct-badge--warninging]': "status() === 'warning'",
|
|
2347
3276
|
'[class.strct-badge--critical]': "status() === 'critical'",
|
|
2348
3277
|
'[class.strct-badge--solid]': 'solid()',
|
|
2349
|
-
|
|
2350
|
-
|
|
3278
|
+
'[class.strct-badge--tone]': 'tone()',
|
|
3279
|
+
'[style.--strct-badge-fg]': 'tone() || null',
|
|
3280
|
+
}, styles: [".strct-badge{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:600;line-height:1;letter-spacing:.2px;padding-block:3px;padding-inline:9px 7px;border-radius:var(--radius-sm);white-space:nowrap;color:var(--t2);background:transparent;border:1px solid var(--b3);border-inline-start-width:3px}.strct-badge--accent{color:var(--acc);border-color:var(--acc30)}.strct-badge--success{color:var(--success);border-color:var(--success)}.strct-badge--warning{color:var(--warning);border-color:var(--warning)}.strct-badge--critical{color:var(--critical);border-color:var(--critical)}.strct-badge--tone{color:var(--strct-badge-fg, var(--t2));border-color:var( --strct-badge-border, color-mix(in srgb, var(--strct-badge-fg, var(--b3)) 30%, transparent) )}.strct-badge--solid{color:var(--t1);background:var(--bg-a);border-color:transparent;border-inline-start-width:1px;padding-inline-start:7px}.strct-badge--solid.strct-badge--accent{background:var(--acc);color:var(--inv)}.strct-badge--solid.strct-badge--success{background:var(--success);color:var(--inv)}.strct-badge--solid.strct-badge--warning{background:var(--warning);color:var(--inv)}.strct-badge--solid.strct-badge--critical{background:var(--critical);color:var(--inv)}.strct-badge--solid.strct-badge--tone{background:var(--strct-badge-fg, var(--bg-a));color:var(--strct-badge-on, var(--inv));border-color:transparent}\n"] }]
|
|
3281
|
+
}], ctorParameters: () => [], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], solid: [{ type: i0.Input, args: [{ isSignal: true, alias: "solid", required: false }] }] } });
|
|
2351
3282
|
|
|
2352
3283
|
/** Content rendered before the tag's text — a status dot, an icon. */
|
|
2353
3284
|
class StrctTagLeading {
|
|
@@ -2831,115 +3762,60 @@ class StrctSpinner {
|
|
|
2831
3762
|
* else on screen.
|
|
2832
3763
|
*/
|
|
2833
3764
|
caption = input('', /* @ts-ignore */
|
|
2834
|
-
...(ngDevMode ? [{ debugName: "caption" }] : /* istanbul ignore next */ []));
|
|
2835
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSpinner, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2836
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctSpinner, isStandalone: true, selector: "strct-spinner", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "progressbar" }, properties: { "class.strct-spinner--captioned": "caption()", "attr.aria-label": "caption() || label()", "class.strct-spinner--sm": "size() === 'sm'", "class.strct-spinner--lg": "size() === 'lg'" }, classAttribute: "strct-spinner" }, ngImport: i0, template: `
|
|
2837
|
-
@if (caption()) {
|
|
2838
|
-
<span class="strct-spinner__ring"></span>
|
|
2839
|
-
<span class="strct-spinner__caption">{{ caption() }}</span>
|
|
2840
|
-
}
|
|
2841
|
-
`, isInline: true, styles: [".strct-spinner{display:inline-block;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm{width:14px;height:14px;border-width:2px}.strct-spinner--lg{width:34px;height:34px;border-width:3px}@keyframes strct-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.strct-spinner{animation-duration:1.6s}}.strct-spinner--captioned{display:inline-flex;align-items:center;gap:var(--space-2);width:auto;height:auto;border:0;border-radius:0;animation:none}.strct-spinner__ring{flex:none;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm .strct-spinner__ring{width:14px;height:14px;border-width:2px}.strct-spinner--lg .strct-spinner__ring{width:34px;height:34px;border-width:3px}@media(prefers-reduced-motion:reduce){.strct-spinner__ring{animation-duration:1.6s}}.strct-spinner__caption{font-size:var(--text-sm);color:var(--t3);line-height:1.3}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2842
|
-
}
|
|
2843
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSpinner, decorators: [{
|
|
2844
|
-
type: Component,
|
|
2845
|
-
args: [{ selector: 'strct-spinner', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
2846
|
-
@if (caption()) {
|
|
2847
|
-
<span class="strct-spinner__ring"></span>
|
|
2848
|
-
<span class="strct-spinner__caption">{{ caption() }}</span>
|
|
2849
|
-
}
|
|
2850
|
-
`, host: {
|
|
2851
|
-
class: 'strct-spinner',
|
|
2852
|
-
role: 'progressbar',
|
|
2853
|
-
'[class.strct-spinner--captioned]': 'caption()',
|
|
2854
|
-
'[attr.aria-label]': 'caption() || label()',
|
|
2855
|
-
'[class.strct-spinner--sm]': "size() === 'sm'",
|
|
2856
|
-
'[class.strct-spinner--lg]': "size() === 'lg'",
|
|
2857
|
-
}, styles: [".strct-spinner{display:inline-block;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm{width:14px;height:14px;border-width:2px}.strct-spinner--lg{width:34px;height:34px;border-width:3px}@keyframes strct-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.strct-spinner{animation-duration:1.6s}}.strct-spinner--captioned{display:inline-flex;align-items:center;gap:var(--space-2);width:auto;height:auto;border:0;border-radius:0;animation:none}.strct-spinner__ring{flex:none;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm .strct-spinner__ring{width:14px;height:14px;border-width:2px}.strct-spinner--lg .strct-spinner__ring{width:34px;height:34px;border-width:3px}@media(prefers-reduced-motion:reduce){.strct-spinner__ring{animation-duration:1.6s}}.strct-spinner__caption{font-size:var(--text-sm);color:var(--t3);line-height:1.3}\n"] }]
|
|
2858
|
-
}], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }] } });
|
|
2859
|
-
|
|
2860
|
-
/**
|
|
2861
|
-
* Shimmering loading placeholder.
|
|
2862
|
-
* <strct-skeleton width="60%" height="14px" />
|
|
2863
|
-
* <strct-skeleton circle width="40px" height="40px" />
|
|
2864
|
-
*/
|
|
2865
|
-
class StrctSkeleton {
|
|
2866
|
-
/** Width (CSS length). */
|
|
2867
|
-
width = input('100%', /* @ts-ignore */
|
|
2868
|
-
...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
2869
|
-
/** Height in pixels. */
|
|
2870
|
-
height = input('14px', /* @ts-ignore */
|
|
2871
|
-
...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
2872
|
-
/** Circle. */
|
|
2873
|
-
circle = input(false, { ...(ngDevMode ? { debugName: "circle" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2874
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2875
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctSkeleton, isStandalone: true, selector: "strct-skeleton", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, circle: { classPropertyName: "circle", publicName: "circle", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "presentation", "aria-hidden": "true" }, properties: { "class.strct-skel--circle": "circle()", "style.width": "width()", "style.height": "height()" }, classAttribute: "strct-skel" }, ngImport: i0, template: '', isInline: true, styles: [".strct-skel{display:block;border-radius:5px;background:linear-gradient(90deg,var(--bg-3) 25%,var(--skeleton-hi, var(--acc18)) 50%,var(--bg-3) 75%);background-size:400% 100%;animation:strct-skel-shimmer 1.4s ease infinite}.strct-skel--circle{border-radius:50%}@keyframes strct-skel-shimmer{0%{background-position:100% 50%}to{background-position:0 50%}}@media(prefers-reduced-motion:reduce){.strct-skel{animation:none;background:var(--skeleton-hi, var(--acc18))}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2876
|
-
}
|
|
2877
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSkeleton, decorators: [{
|
|
2878
|
-
type: Component,
|
|
2879
|
-
args: [{ selector: 'strct-skeleton', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '', host: {
|
|
2880
|
-
class: 'strct-skel',
|
|
2881
|
-
role: 'presentation',
|
|
2882
|
-
'aria-hidden': 'true',
|
|
2883
|
-
'[class.strct-skel--circle]': 'circle()',
|
|
2884
|
-
'[style.width]': 'width()',
|
|
2885
|
-
'[style.height]': 'height()',
|
|
2886
|
-
}, styles: [".strct-skel{display:block;border-radius:5px;background:linear-gradient(90deg,var(--bg-3) 25%,var(--skeleton-hi, var(--acc18)) 50%,var(--bg-3) 75%);background-size:400% 100%;animation:strct-skel-shimmer 1.4s ease infinite}.strct-skel--circle{border-radius:50%}@keyframes strct-skel-shimmer{0%{background-position:100% 50%}to{background-position:0 50%}}@media(prefers-reduced-motion:reduce){.strct-skel{animation:none;background:var(--skeleton-hi, var(--acc18))}}\n"] }]
|
|
2887
|
-
}], propDecorators: { width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], circle: [{ type: i0.Input, args: [{ isSignal: true, alias: "circle", required: false }] }] } });
|
|
2888
|
-
|
|
2889
|
-
/**
|
|
2890
|
-
* Shared focus-lifecycle helpers for transient overlay surfaces (tooltip,
|
|
2891
|
-
* drawer, menus, popovers …): remember the trigger before opening, move
|
|
2892
|
-
* focus in on open, cycle Tab inside, and hand focus back on close.
|
|
2893
|
-
* Modeled on the modal's local implementation (`modal/modal.ts`).
|
|
2894
|
-
*/
|
|
2895
|
-
/** Elements considered tabbable inside an overlay, queried in DOM order. */
|
|
2896
|
-
const FOCUSABLE_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
2897
|
-
/** Currently focused element, if it is an HTMLElement (remember the trigger before opening). */
|
|
2898
|
-
function saveFocusedElement() {
|
|
2899
|
-
const active = document.activeElement;
|
|
2900
|
-
return active instanceof HTMLElement ? active : null;
|
|
2901
|
-
}
|
|
2902
|
-
/** Focus `el` again if it is still in the document; null-safe. */
|
|
2903
|
-
function restoreFocus(el) {
|
|
2904
|
-
if (el?.isConnected)
|
|
2905
|
-
el.focus();
|
|
2906
|
-
}
|
|
2907
|
-
/**
|
|
2908
|
-
* Focus the first tabbable descendant of `container`.
|
|
2909
|
-
* Returns whether something was focused; with nothing tabbable the container
|
|
2910
|
-
* itself is NOT focused — the caller decides the fallback.
|
|
2911
|
-
*/
|
|
2912
|
-
function focusFirstIn(container) {
|
|
2913
|
-
const first = container.querySelector(FOCUSABLE_SELECTOR);
|
|
2914
|
-
if (!first)
|
|
2915
|
-
return false;
|
|
2916
|
-
first.focus();
|
|
2917
|
-
return true;
|
|
2918
|
-
}
|
|
2919
|
-
/**
|
|
2920
|
-
* Tab trap for modal-ish overlays: cycle Tab/Shift+Tab within the
|
|
2921
|
-
* container's tabbable descendants, wrapping at both ends (preventDefault
|
|
2922
|
-
* when it wraps). Wire it to the container's `keydown.tab` /
|
|
2923
|
-
* `keydown.shift.tab` events.
|
|
2924
|
-
*/
|
|
2925
|
-
function keepTabInside(event, container) {
|
|
2926
|
-
const items = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
|
|
2927
|
-
if (!items.length) {
|
|
2928
|
-
event.preventDefault();
|
|
2929
|
-
return;
|
|
2930
|
-
}
|
|
2931
|
-
const first = items[0];
|
|
2932
|
-
const last = items[items.length - 1];
|
|
2933
|
-
const active = container.ownerDocument.activeElement;
|
|
2934
|
-
if (event.shiftKey && (active === first || !container.contains(active))) {
|
|
2935
|
-
event.preventDefault();
|
|
2936
|
-
last.focus();
|
|
3765
|
+
...(ngDevMode ? [{ debugName: "caption" }] : /* istanbul ignore next */ []));
|
|
3766
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSpinner, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3767
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctSpinner, isStandalone: true, selector: "strct-spinner", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "progressbar" }, properties: { "class.strct-spinner--captioned": "caption()", "attr.aria-label": "caption() || label()", "class.strct-spinner--sm": "size() === 'sm'", "class.strct-spinner--lg": "size() === 'lg'" }, classAttribute: "strct-spinner" }, ngImport: i0, template: `
|
|
3768
|
+
@if (caption()) {
|
|
3769
|
+
<span class="strct-spinner__ring"></span>
|
|
3770
|
+
<span class="strct-spinner__caption">{{ caption() }}</span>
|
|
2937
3771
|
}
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
3772
|
+
`, isInline: true, styles: [".strct-spinner{display:inline-block;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm{width:14px;height:14px;border-width:2px}.strct-spinner--lg{width:34px;height:34px;border-width:3px}@keyframes strct-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.strct-spinner{animation-duration:1.6s}}.strct-spinner--captioned{display:inline-flex;align-items:center;gap:var(--space-2);width:auto;height:auto;border:0;border-radius:0;animation:none}.strct-spinner__ring{flex:none;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm .strct-spinner__ring{width:14px;height:14px;border-width:2px}.strct-spinner--lg .strct-spinner__ring{width:34px;height:34px;border-width:3px}@media(prefers-reduced-motion:reduce){.strct-spinner__ring{animation-duration:1.6s}}.strct-spinner__caption{font-size:var(--text-sm);color:var(--t3);line-height:1.3}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
3773
|
+
}
|
|
3774
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSpinner, decorators: [{
|
|
3775
|
+
type: Component,
|
|
3776
|
+
args: [{ selector: 'strct-spinner', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
3777
|
+
@if (caption()) {
|
|
3778
|
+
<span class="strct-spinner__ring"></span>
|
|
3779
|
+
<span class="strct-spinner__caption">{{ caption() }}</span>
|
|
2941
3780
|
}
|
|
3781
|
+
`, host: {
|
|
3782
|
+
class: 'strct-spinner',
|
|
3783
|
+
role: 'progressbar',
|
|
3784
|
+
'[class.strct-spinner--captioned]': 'caption()',
|
|
3785
|
+
'[attr.aria-label]': 'caption() || label()',
|
|
3786
|
+
'[class.strct-spinner--sm]': "size() === 'sm'",
|
|
3787
|
+
'[class.strct-spinner--lg]': "size() === 'lg'",
|
|
3788
|
+
}, styles: [".strct-spinner{display:inline-block;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm{width:14px;height:14px;border-width:2px}.strct-spinner--lg{width:34px;height:34px;border-width:3px}@keyframes strct-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.strct-spinner{animation-duration:1.6s}}.strct-spinner--captioned{display:inline-flex;align-items:center;gap:var(--space-2);width:auto;height:auto;border:0;border-radius:0;animation:none}.strct-spinner__ring{flex:none;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm .strct-spinner__ring{width:14px;height:14px;border-width:2px}.strct-spinner--lg .strct-spinner__ring{width:34px;height:34px;border-width:3px}@media(prefers-reduced-motion:reduce){.strct-spinner__ring{animation-duration:1.6s}}.strct-spinner__caption{font-size:var(--text-sm);color:var(--t3);line-height:1.3}\n"] }]
|
|
3789
|
+
}], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }] } });
|
|
3790
|
+
|
|
3791
|
+
/**
|
|
3792
|
+
* Shimmering loading placeholder.
|
|
3793
|
+
* <strct-skeleton width="60%" height="14px" />
|
|
3794
|
+
* <strct-skeleton circle width="40px" height="40px" />
|
|
3795
|
+
*/
|
|
3796
|
+
class StrctSkeleton {
|
|
3797
|
+
/** Width (CSS length). */
|
|
3798
|
+
width = input('100%', /* @ts-ignore */
|
|
3799
|
+
...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
3800
|
+
/** Height in pixels. */
|
|
3801
|
+
height = input('14px', /* @ts-ignore */
|
|
3802
|
+
...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
3803
|
+
/** Circle. */
|
|
3804
|
+
circle = input(false, { ...(ngDevMode ? { debugName: "circle" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3805
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3806
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctSkeleton, isStandalone: true, selector: "strct-skeleton", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, circle: { classPropertyName: "circle", publicName: "circle", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "presentation", "aria-hidden": "true" }, properties: { "class.strct-skel--circle": "circle()", "style.width": "width()", "style.height": "height()" }, classAttribute: "strct-skel" }, ngImport: i0, template: '', isInline: true, styles: [".strct-skel{display:block;border-radius:5px;background:linear-gradient(90deg,var(--bg-3) 25%,var(--skeleton-hi, var(--acc18)) 50%,var(--bg-3) 75%);background-size:400% 100%;animation:strct-skel-shimmer 1.4s ease infinite}.strct-skel--circle{border-radius:50%}@keyframes strct-skel-shimmer{0%{background-position:100% 50%}to{background-position:0 50%}}@media(prefers-reduced-motion:reduce){.strct-skel{animation:none;background:var(--skeleton-hi, var(--acc18))}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2942
3807
|
}
|
|
3808
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSkeleton, decorators: [{
|
|
3809
|
+
type: Component,
|
|
3810
|
+
args: [{ selector: 'strct-skeleton', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '', host: {
|
|
3811
|
+
class: 'strct-skel',
|
|
3812
|
+
role: 'presentation',
|
|
3813
|
+
'aria-hidden': 'true',
|
|
3814
|
+
'[class.strct-skel--circle]': 'circle()',
|
|
3815
|
+
'[style.width]': 'width()',
|
|
3816
|
+
'[style.height]': 'height()',
|
|
3817
|
+
}, styles: [".strct-skel{display:block;border-radius:5px;background:linear-gradient(90deg,var(--bg-3) 25%,var(--skeleton-hi, var(--acc18)) 50%,var(--bg-3) 75%);background-size:400% 100%;animation:strct-skel-shimmer 1.4s ease infinite}.strct-skel--circle{border-radius:50%}@keyframes strct-skel-shimmer{0%{background-position:100% 50%}to{background-position:0 50%}}@media(prefers-reduced-motion:reduce){.strct-skel{animation:none;background:var(--skeleton-hi, var(--acc18))}}\n"] }]
|
|
3818
|
+
}], propDecorators: { width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], circle: [{ type: i0.Input, args: [{ isSignal: true, alias: "circle", required: false }] }] } });
|
|
2943
3819
|
|
|
2944
3820
|
/**
|
|
2945
3821
|
* Floating action button that fans out to reveal actions. Project icon buttons
|
|
@@ -5348,637 +6224,222 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
5348
6224
|
>
|
|
5349
6225
|
<strct-icon name="close" [size]="14" />
|
|
5350
6226
|
</button>
|
|
5351
|
-
</div>
|
|
5352
|
-
}
|
|
5353
|
-
<div class="strct-modal__body">
|
|
5354
|
-
<ng-content />
|
|
5355
|
-
@if (lazyContent(); as lazy) {
|
|
5356
|
-
<ng-container [ngTemplateOutlet]="lazy.template" />
|
|
5357
|
-
}
|
|
5358
|
-
</div>
|
|
5359
|
-
@if (!hideFooter() && !chromeless()) {
|
|
5360
|
-
<div class="strct-modal__foot"><ng-content select="[strctModalFooter]" /></div>
|
|
5361
|
-
}
|
|
5362
|
-
</div>
|
|
5363
|
-
</div>
|
|
5364
|
-
}
|
|
5365
|
-
`, host: {
|
|
5366
|
-
'(document:keydown.escape)': 'onEscape($event)',
|
|
5367
|
-
}, styles: [".strct-modal__overlay{position:fixed;inset:0;z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;padding:var(--space-5);background:var(--backdrop);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-modal-fade .12s ease}@media(max-width:768px){.strct-modal__overlay{padding:var(--space-3)}}.strct-modal__dialog{width:100%;max-height:calc(100vh - 48px);display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-elevated);overflow:hidden;animation:strct-modal-rise .14s ease}.strct-modal__dialog--sm{max-width:min(480px,calc(100vw - 32px))}.strct-modal__dialog--md{max-width:min(640px,calc(100vw - 32px))}.strct-modal__dialog--lg{max-width:min(860px,calc(100vw - 32px))}.strct-modal__dialog--xl{max-width:min(1080px,calc(100vw - 32px))}.strct-modal__head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--b1)}.strct-modal__head--drag{cursor:move;-webkit-user-select:none;user-select:none;touch-action:none}.strct-modal__overlay--glass{background:#00000061;-webkit-backdrop-filter:blur(10px) saturate(1.25);backdrop-filter:blur(10px) saturate(1.25)}.strct-modal__dialog--glass{background:color-mix(in srgb,var(--bg-1) 72%,transparent);-webkit-backdrop-filter:blur(24px) saturate(1.4);backdrop-filter:blur(24px) saturate(1.4)}.strct-modal__dialog--glass .strct-modal__foot{background:color-mix(in srgb,var(--bg-2) 55%,transparent)}.strct-modal__title{font-size:14px;font-weight:600;color:var(--t1)}.strct-modal__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-modal__close:hover{color:var(--t1);background:var(--bg-3)}.strct-modal__body{padding:var(--space-4);overflow-y:auto;color:var(--t2);font-size:13px}.strct-modal__dialog--chromeless{width:calc(232px + var(--strct-wiz-content-min, 864px) + 2px);max-width:calc(100vw - 32px)}.strct-modal__dialog--chromeless:has(.strct-wiz__layout--aside){width:calc(232px + var(--strct-wiz-content-min, 864px) + 280px + 2px)}.strct-modal__dialog--chromeless .strct-modal__body{padding:0;overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0}.strct-modal__dialog--chromeless .strct-modal__body>*{flex:1;min-height:0}.strct-modal__foot{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2);padding:var(--space-3) var(--space-4);border-top:1px solid var(--b1);background:var(--bg-2)}@keyframes strct-modal-fade{0%{opacity:0}}@keyframes strct-modal-rise{0%{opacity:0;transform:translateY(8px) scale(.98)}}@media(prefers-reduced-motion:reduce){.strct-modal__overlay,.strct-modal__dialog{animation:none}}\n"] }]
|
|
5368
|
-
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], hideFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideFooter", required: false }] }], chromeless: [{ type: i0.Input, args: [{ isSignal: true, alias: "chromeless", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], closeOnBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdrop", required: false }] }], draggable: [{ type: i0.Input, args: [{ isSignal: true, alias: "draggable", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], backdropClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdropClass", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], lazyContent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctModalContent), { isSignal: true }] }] } });
|
|
5369
|
-
|
|
5370
|
-
/** Marks the drawer's footer action area: `<ng-container strctDrawerFooter>…`. */
|
|
5371
|
-
class StrctDrawerFooter {
|
|
5372
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDrawerFooter, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
5373
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDrawerFooter, isStandalone: true, selector: "[strctDrawerFooter]", ngImport: i0 });
|
|
5374
|
-
}
|
|
5375
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDrawerFooter, decorators: [{
|
|
5376
|
-
type: Directive,
|
|
5377
|
-
args: [{ selector: '[strctDrawerFooter]' }]
|
|
5378
|
-
}] });
|
|
5379
|
-
/**
|
|
5380
|
-
* Slide-out overlay panel anchored to an edge of the viewport — for inspecting
|
|
5381
|
-
* or editing a record without losing the underlying table's scroll/selection.
|
|
5382
|
-
*
|
|
5383
|
-
* <strct-drawer [(open)]="editing" side="end" title="Virtual machine">
|
|
5384
|
-
* … body …
|
|
5385
|
-
* <ng-container strctDrawerFooter>
|
|
5386
|
-
* <button strct-button (click)="editing.set(false)">Cancel</button>
|
|
5387
|
-
* </ng-container>
|
|
5388
|
-
* </strct-drawer>
|
|
5389
|
-
*/
|
|
5390
|
-
class StrctDrawer {
|
|
5391
|
-
elementRef = inject((ElementRef));
|
|
5392
|
-
doc = inject(DOCUMENT);
|
|
5393
|
-
/** Open state (two-way). */
|
|
5394
|
-
open = model(false, /* @ts-ignore */
|
|
5395
|
-
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
5396
|
-
/** Edge to anchor to. Defaults to `end` (right in LTR). */
|
|
5397
|
-
side = input('end', /* @ts-ignore */
|
|
5398
|
-
...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
5399
|
-
/** Extent along the sliding axis. */
|
|
5400
|
-
size = input('md', /* @ts-ignore */
|
|
5401
|
-
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
5402
|
-
/** Header title. */
|
|
5403
|
-
title = input('', /* @ts-ignore */
|
|
5404
|
-
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
5405
|
-
/** Show the close button and allow backdrop/Esc dismissal. */
|
|
5406
|
-
dismissable = input(true, { ...(ngDevMode ? { debugName: "dismissable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5407
|
-
/** Accessible name of the dialog when no `title` is set (localizable). */
|
|
5408
|
-
ariaLabel = input('Panel', /* @ts-ignore */
|
|
5409
|
-
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
5410
|
-
/** Accessible label of the X close button (localizable). */
|
|
5411
|
-
closeLabel = input('Close', /* @ts-ignore */
|
|
5412
|
-
...(ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []));
|
|
5413
|
-
footerDef = contentChild(StrctDrawerFooter, /* @ts-ignore */
|
|
5414
|
-
...(ngDevMode ? [{ debugName: "footerDef" }] : /* istanbul ignore next */ []));
|
|
5415
|
-
hasFooter = computed(() => !!this.footerDef(), /* @ts-ignore */
|
|
5416
|
-
...(ngDevMode ? [{ debugName: "hasFooter" }] : /* istanbul ignore next */ []));
|
|
5417
|
-
/** Element that had focus before the drawer opened, restored on close. */
|
|
5418
|
-
previousActive = null;
|
|
5419
|
-
/** Whether this instance currently holds a scroll lock. */
|
|
5420
|
-
locked = false;
|
|
5421
|
-
constructor() {
|
|
5422
|
-
effect(() => {
|
|
5423
|
-
const open = this.open();
|
|
5424
|
-
if (open && !this.locked) {
|
|
5425
|
-
this.locked = true;
|
|
5426
|
-
lockBodyScroll(this.doc);
|
|
5427
|
-
this.previousActive = saveFocusedElement();
|
|
5428
|
-
// Move focus into the panel once it has rendered.
|
|
5429
|
-
setTimeout(() => this.focusInitial());
|
|
5430
|
-
}
|
|
5431
|
-
else if (!open && this.locked) {
|
|
5432
|
-
this.locked = false;
|
|
5433
|
-
unlockBodyScroll(this.doc);
|
|
5434
|
-
restoreFocus(this.previousActive);
|
|
5435
|
-
this.previousActive = null;
|
|
5436
|
-
}
|
|
5437
|
-
});
|
|
5438
|
-
// Release the lock if the drawer is destroyed while still open.
|
|
5439
|
-
inject(DestroyRef).onDestroy(() => {
|
|
5440
|
-
if (this.locked) {
|
|
5441
|
-
this.locked = false;
|
|
5442
|
-
unlockBodyScroll(this.doc);
|
|
5443
|
-
}
|
|
5444
|
-
});
|
|
5445
|
-
}
|
|
5446
|
-
onBackdrop() {
|
|
5447
|
-
if (this.dismissable())
|
|
5448
|
-
this.close();
|
|
5449
|
-
}
|
|
5450
|
-
onEscape(event) {
|
|
5451
|
-
if (!this.open() || !this.dismissable())
|
|
5452
|
-
return;
|
|
5453
|
-
// A host modal/drawer must not also close on the same keypress.
|
|
5454
|
-
event.stopPropagation();
|
|
5455
|
-
this.close();
|
|
5456
|
-
}
|
|
5457
|
-
/** Wrap Tab focus within the panel. */
|
|
5458
|
-
onTab(event) {
|
|
5459
|
-
const panel = this.panel();
|
|
5460
|
-
if (panel)
|
|
5461
|
-
keepTabInside(event, panel);
|
|
5462
|
-
}
|
|
5463
|
-
close() {
|
|
5464
|
-
this.open.set(false);
|
|
5465
|
-
}
|
|
5466
|
-
panel() {
|
|
5467
|
-
return this.elementRef.nativeElement.querySelector('.strct-drawer');
|
|
5468
|
-
}
|
|
5469
|
-
focusInitial() {
|
|
5470
|
-
const panel = this.panel();
|
|
5471
|
-
if (!panel)
|
|
5472
|
-
return;
|
|
5473
|
-
// Nothing tabbable (empty body, dismissable=false) → focus the panel itself.
|
|
5474
|
-
if (!focusFirstIn(panel))
|
|
5475
|
-
panel.focus();
|
|
5476
|
-
}
|
|
5477
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5478
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctDrawer, isStandalone: true, selector: "strct-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", 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 }, dismissable: { classPropertyName: "dismissable", publicName: "dismissable", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape($event)" }, classAttribute: "strct-drawer-host" }, queries: [{ propertyName: "footerDef", first: true, predicate: StrctDrawerFooter, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
5479
|
-
@if (open()) {
|
|
5480
|
-
<!-- Backdrop: pointer-only dismiss target (click-through when not
|
|
5481
|
-
dismissable). Keyboard users dismiss via Escape. -->
|
|
5482
|
-
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
5483
|
-
<div
|
|
5484
|
-
class="strct-drawer__backdrop"
|
|
5485
|
-
[class.strct-drawer__backdrop--bare]="!dismissable()"
|
|
5486
|
-
(click)="onBackdrop()"
|
|
5487
|
-
></div>
|
|
5488
|
-
<aside
|
|
5489
|
-
class="strct-drawer strct-drawer--{{ side() }} strct-drawer--{{ size() }}"
|
|
5490
|
-
role="dialog"
|
|
5491
|
-
aria-modal="true"
|
|
5492
|
-
tabindex="-1"
|
|
5493
|
-
[attr.aria-label]="title() || ariaLabel()"
|
|
5494
|
-
(keydown.tab)="onTab($event)"
|
|
5495
|
-
(keydown.shift.tab)="onTab($event)"
|
|
5496
|
-
>
|
|
5497
|
-
@if (title() || dismissable()) {
|
|
5498
|
-
<header class="strct-drawer__head">
|
|
5499
|
-
<span class="strct-drawer__title">{{ title() }}</span>
|
|
5500
|
-
@if (dismissable()) {
|
|
5501
|
-
<button
|
|
5502
|
-
type="button"
|
|
5503
|
-
class="strct-drawer__close"
|
|
5504
|
-
[attr.aria-label]="closeLabel()"
|
|
5505
|
-
(click)="close()"
|
|
5506
|
-
>
|
|
5507
|
-
<strct-icon name="close" [size]="15" />
|
|
5508
|
-
</button>
|
|
5509
|
-
}
|
|
5510
|
-
</header>
|
|
5511
|
-
}
|
|
5512
|
-
<div class="strct-drawer__body"><ng-content /></div>
|
|
5513
|
-
@if (hasFooter()) {
|
|
5514
|
-
<footer class="strct-drawer__foot"><ng-content select="[strctDrawerFooter]" /></footer>
|
|
5515
|
-
}
|
|
5516
|
-
</aside>
|
|
5517
|
-
}
|
|
5518
|
-
`, isInline: true, styles: [".strct-drawer__backdrop{position:fixed;inset:0;z-index:var(--z-overlay);background:var(--backdrop);-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);animation:strct-drawer-fade .16s ease}.strct-drawer__backdrop--bare{background:transparent;-webkit-backdrop-filter:none;backdrop-filter:none;pointer-events:none}.strct-drawer{position:fixed;z-index:calc(var(--z-overlay) + 1);display:flex;flex-direction:column;background:var(--bg-1);box-shadow:var(--shh)}.strct-drawer--start,.strct-drawer--end{top:0;bottom:0;border-radius:0}.strct-drawer--start{inset-inline-start:0;border-inline-end:1px solid var(--b2);animation:strct-drawer-in-start .18s ease}.strct-drawer--end{inset-inline-end:0;border-inline-start:1px solid var(--b2);animation:strct-drawer-in-end .18s ease}[dir=rtl] .strct-drawer--start{animation-name:strct-drawer-in-end}[dir=rtl] .strct-drawer--end{animation-name:strct-drawer-in-start}.strct-drawer--top,.strct-drawer--bottom{left:0;right:0}.strct-drawer--top{top:0;border-bottom:1px solid var(--b2);animation:strct-drawer-in-top .18s ease}.strct-drawer--bottom{bottom:0;border-top:1px solid var(--b2);animation:strct-drawer-in-bottom .18s ease}.strct-drawer--start.strct-drawer--sm,.strct-drawer--end.strct-drawer--sm{width:300px}.strct-drawer--start.strct-drawer--md,.strct-drawer--end.strct-drawer--md{width:420px}.strct-drawer--start.strct-drawer--lg,.strct-drawer--end.strct-drawer--lg{width:600px}.strct-drawer--top.strct-drawer--sm,.strct-drawer--bottom.strct-drawer--sm{height:220px}.strct-drawer--top.strct-drawer--md,.strct-drawer--bottom.strct-drawer--md{height:340px}.strct-drawer--top.strct-drawer--lg,.strct-drawer--bottom.strct-drawer--lg{height:480px}.strct-drawer{max-width:100vw;max-height:100vh}.strct-drawer__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-bottom:1px solid var(--b1);flex-shrink:0}.strct-drawer__title{font-size:15px;font-weight:600;color:var(--t1)}.strct-drawer__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-drawer__close:hover{color:var(--t1);background:var(--bg-3)}.strct-drawer__body{flex:1;min-height:0;overflow-y:auto;padding:16px 18px;font-size:13px;color:var(--t1)}.strct-drawer__foot{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-top:1px solid var(--b1);flex-shrink:0}@keyframes strct-drawer-fade{0%{opacity:0}}@keyframes strct-drawer-in-start{0%{transform:translate(-100%)}}@keyframes strct-drawer-in-end{0%{transform:translate(100%)}}@keyframes strct-drawer-in-top{0%{transform:translateY(-100%)}}@keyframes strct-drawer-in-bottom{0%{transform:translateY(100%)}}@media(prefers-reduced-motion:reduce){.strct-drawer__backdrop,.strct-drawer{animation:none}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
5519
|
-
}
|
|
5520
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDrawer, decorators: [{
|
|
5521
|
-
type: Component,
|
|
5522
|
-
args: [{ selector: 'strct-drawer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
|
|
5523
|
-
@if (open()) {
|
|
5524
|
-
<!-- Backdrop: pointer-only dismiss target (click-through when not
|
|
5525
|
-
dismissable). Keyboard users dismiss via Escape. -->
|
|
5526
|
-
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
5527
|
-
<div
|
|
5528
|
-
class="strct-drawer__backdrop"
|
|
5529
|
-
[class.strct-drawer__backdrop--bare]="!dismissable()"
|
|
5530
|
-
(click)="onBackdrop()"
|
|
5531
|
-
></div>
|
|
5532
|
-
<aside
|
|
5533
|
-
class="strct-drawer strct-drawer--{{ side() }} strct-drawer--{{ size() }}"
|
|
5534
|
-
role="dialog"
|
|
5535
|
-
aria-modal="true"
|
|
5536
|
-
tabindex="-1"
|
|
5537
|
-
[attr.aria-label]="title() || ariaLabel()"
|
|
5538
|
-
(keydown.tab)="onTab($event)"
|
|
5539
|
-
(keydown.shift.tab)="onTab($event)"
|
|
5540
|
-
>
|
|
5541
|
-
@if (title() || dismissable()) {
|
|
5542
|
-
<header class="strct-drawer__head">
|
|
5543
|
-
<span class="strct-drawer__title">{{ title() }}</span>
|
|
5544
|
-
@if (dismissable()) {
|
|
5545
|
-
<button
|
|
5546
|
-
type="button"
|
|
5547
|
-
class="strct-drawer__close"
|
|
5548
|
-
[attr.aria-label]="closeLabel()"
|
|
5549
|
-
(click)="close()"
|
|
5550
|
-
>
|
|
5551
|
-
<strct-icon name="close" [size]="15" />
|
|
5552
|
-
</button>
|
|
6227
|
+
</div>
|
|
6228
|
+
}
|
|
6229
|
+
<div class="strct-modal__body">
|
|
6230
|
+
<ng-content />
|
|
6231
|
+
@if (lazyContent(); as lazy) {
|
|
6232
|
+
<ng-container [ngTemplateOutlet]="lazy.template" />
|
|
5553
6233
|
}
|
|
5554
|
-
</
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
</aside>
|
|
6234
|
+
</div>
|
|
6235
|
+
@if (!hideFooter() && !chromeless()) {
|
|
6236
|
+
<div class="strct-modal__foot"><ng-content select="[strctModalFooter]" /></div>
|
|
6237
|
+
}
|
|
6238
|
+
</div>
|
|
6239
|
+
</div>
|
|
5561
6240
|
}
|
|
5562
6241
|
`, host: {
|
|
5563
|
-
class: 'strct-drawer-host',
|
|
5564
6242
|
'(document:keydown.escape)': 'onEscape($event)',
|
|
5565
|
-
}, styles: [".strct-
|
|
5566
|
-
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }],
|
|
6243
|
+
}, styles: [".strct-modal__overlay{position:fixed;inset:0;z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;padding:var(--space-5);background:var(--backdrop);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-modal-fade .12s ease}@media(max-width:768px){.strct-modal__overlay{padding:var(--space-3)}}.strct-modal__dialog{width:100%;max-height:calc(100vh - 48px);display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-elevated);overflow:hidden;animation:strct-modal-rise .14s ease}.strct-modal__dialog--sm{max-width:min(480px,calc(100vw - 32px))}.strct-modal__dialog--md{max-width:min(640px,calc(100vw - 32px))}.strct-modal__dialog--lg{max-width:min(860px,calc(100vw - 32px))}.strct-modal__dialog--xl{max-width:min(1080px,calc(100vw - 32px))}.strct-modal__head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--b1)}.strct-modal__head--drag{cursor:move;-webkit-user-select:none;user-select:none;touch-action:none}.strct-modal__overlay--glass{background:#00000061;-webkit-backdrop-filter:blur(10px) saturate(1.25);backdrop-filter:blur(10px) saturate(1.25)}.strct-modal__dialog--glass{background:color-mix(in srgb,var(--bg-1) 72%,transparent);-webkit-backdrop-filter:blur(24px) saturate(1.4);backdrop-filter:blur(24px) saturate(1.4)}.strct-modal__dialog--glass .strct-modal__foot{background:color-mix(in srgb,var(--bg-2) 55%,transparent)}.strct-modal__title{font-size:14px;font-weight:600;color:var(--t1)}.strct-modal__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-modal__close:hover{color:var(--t1);background:var(--bg-3)}.strct-modal__body{padding:var(--space-4);overflow-y:auto;color:var(--t2);font-size:13px}.strct-modal__dialog--chromeless{width:calc(232px + var(--strct-wiz-content-min, 864px) + 2px);max-width:calc(100vw - 32px)}.strct-modal__dialog--chromeless:has(.strct-wiz__layout--aside){width:calc(232px + var(--strct-wiz-content-min, 864px) + 280px + 2px)}.strct-modal__dialog--chromeless .strct-modal__body{padding:0;overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0}.strct-modal__dialog--chromeless .strct-modal__body>*{flex:1;min-height:0}.strct-modal__foot{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2);padding:var(--space-3) var(--space-4);border-top:1px solid var(--b1);background:var(--bg-2)}@keyframes strct-modal-fade{0%{opacity:0}}@keyframes strct-modal-rise{0%{opacity:0;transform:translateY(8px) scale(.98)}}@media(prefers-reduced-motion:reduce){.strct-modal__overlay,.strct-modal__dialog{animation:none}}\n"] }]
|
|
6244
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], hideFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideFooter", required: false }] }], chromeless: [{ type: i0.Input, args: [{ isSignal: true, alias: "chromeless", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], closeOnBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdrop", required: false }] }], draggable: [{ type: i0.Input, args: [{ isSignal: true, alias: "draggable", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], backdropClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdropClass", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], lazyContent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctModalContent), { isSignal: true }] }] } });
|
|
5567
6245
|
|
|
6246
|
+
/** Marks the drawer's footer action area: `<ng-container strctDrawerFooter>…`. */
|
|
6247
|
+
class StrctDrawerFooter {
|
|
6248
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDrawerFooter, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
6249
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDrawerFooter, isStandalone: true, selector: "[strctDrawerFooter]", ngImport: i0 });
|
|
6250
|
+
}
|
|
6251
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDrawerFooter, decorators: [{
|
|
6252
|
+
type: Directive,
|
|
6253
|
+
args: [{ selector: '[strctDrawerFooter]' }]
|
|
6254
|
+
}] });
|
|
5568
6255
|
/**
|
|
5569
|
-
*
|
|
5570
|
-
*
|
|
5571
|
-
* <button strct-button strctDropdownTrigger>Actions</button>
|
|
5572
|
-
* <strct-dropdown-item>Rename</strct-dropdown-item>
|
|
5573
|
-
* <strct-dropdown-item critical>Delete</strct-dropdown-item>
|
|
5574
|
-
* </strct-dropdown>
|
|
5575
|
-
*
|
|
5576
|
-
* With `popover`, the panel holds *form controls* instead of menu items: an
|
|
5577
|
-
* inner click no longer closes it (only outside click / Escape do), and the
|
|
5578
|
-
* semantics switch from `role="menu"` to a labeled `role="dialog"` — a panel
|
|
5579
|
-
* of selects is not a menu to a screen reader either:
|
|
6256
|
+
* Slide-out overlay panel anchored to an edge of the viewport — for inspecting
|
|
6257
|
+
* or editing a record without losing the underlying table's scroll/selection.
|
|
5580
6258
|
*
|
|
5581
|
-
* <strct-
|
|
5582
|
-
*
|
|
5583
|
-
* <
|
|
5584
|
-
*
|
|
6259
|
+
* <strct-drawer [(open)]="editing" side="end" title="Virtual machine">
|
|
6260
|
+
* … body …
|
|
6261
|
+
* <ng-container strctDrawerFooter>
|
|
6262
|
+
* <button strct-button (click)="editing.set(false)">Cancel</button>
|
|
6263
|
+
* </ng-container>
|
|
6264
|
+
* </strct-drawer>
|
|
5585
6265
|
*/
|
|
5586
|
-
class
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
/**
|
|
5592
|
-
* Popover mode for panels holding form controls (filter/settings panels):
|
|
5593
|
-
* inner clicks never close the panel — only outside click / Escape do —
|
|
5594
|
-
* and it renders as a labeled `role="dialog"` instead of a menu.
|
|
5595
|
-
*/
|
|
5596
|
-
popover = input(false, { ...(ngDevMode ? { debugName: "popover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5597
|
-
/** Accessible name of the popover dialog (localizable). */
|
|
5598
|
-
popoverLabel = input('Filters', /* @ts-ignore */
|
|
5599
|
-
...(ngDevMode ? [{ debugName: "popoverLabel" }] : /* istanbul ignore next */ []));
|
|
5600
|
-
/**
|
|
5601
|
-
* Only with `popover`: the panel's rows are controls in their own right — a
|
|
5602
|
-
* theme switcher, a filter — so they keep a real tab stop instead of the
|
|
5603
|
-
* `tabindex="-1"` a menu's items carry. Without it nothing inside a popover
|
|
5604
|
-
* can be reached from the keyboard at all.
|
|
5605
|
-
*/
|
|
5606
|
-
focusable = input(false, { ...(ngDevMode ? { debugName: "focusable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5607
|
-
open = signal(false, /* @ts-ignore */
|
|
6266
|
+
class StrctDrawer {
|
|
6267
|
+
elementRef = inject((ElementRef));
|
|
6268
|
+
doc = inject(DOCUMENT);
|
|
6269
|
+
/** Open state (two-way). */
|
|
6270
|
+
open = model(false, /* @ts-ignore */
|
|
5608
6271
|
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
6272
|
+
/** Edge to anchor to. Defaults to `end` (right in LTR). */
|
|
6273
|
+
side = input('end', /* @ts-ignore */
|
|
6274
|
+
...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
6275
|
+
/** Extent along the sliding axis. */
|
|
6276
|
+
size = input('md', /* @ts-ignore */
|
|
6277
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
6278
|
+
/** Header title. */
|
|
6279
|
+
title = input('', /* @ts-ignore */
|
|
6280
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
6281
|
+
/** Show the close button and allow backdrop/Esc dismissal. */
|
|
6282
|
+
dismissable = input(true, { ...(ngDevMode ? { debugName: "dismissable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
6283
|
+
/** Accessible name of the dialog when no `title` is set (localizable). */
|
|
6284
|
+
ariaLabel = input('Panel', /* @ts-ignore */
|
|
6285
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
6286
|
+
/** Accessible label of the X close button (localizable). */
|
|
6287
|
+
closeLabel = input('Close', /* @ts-ignore */
|
|
6288
|
+
...(ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []));
|
|
6289
|
+
footerDef = contentChild(StrctDrawerFooter, /* @ts-ignore */
|
|
6290
|
+
...(ngDevMode ? [{ debugName: "footerDef" }] : /* istanbul ignore next */ []));
|
|
6291
|
+
hasFooter = computed(() => !!this.footerDef(), /* @ts-ignore */
|
|
6292
|
+
...(ngDevMode ? [{ debugName: "hasFooter" }] : /* istanbul ignore next */ []));
|
|
6293
|
+
/** Element that had focus before the drawer opened, restored on close. */
|
|
6294
|
+
previousActive = null;
|
|
6295
|
+
/** Whether this instance currently holds a scroll lock. */
|
|
6296
|
+
locked = false;
|
|
5609
6297
|
constructor() {
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
this.focusInitialItem();
|
|
5634
|
-
}
|
|
5635
|
-
close(restoreFocus = false) {
|
|
5636
|
-
this.open.set(false);
|
|
5637
|
-
if (restoreFocus)
|
|
5638
|
-
this.lastFocused?.focus?.();
|
|
5639
|
-
}
|
|
5640
|
-
/**
|
|
5641
|
-
* Menu mode closes ONLY on a real item activation — a click that lands on
|
|
5642
|
-
* the menu's padding or a divider (a 2px miss) keeps it open instead of
|
|
5643
|
-
* throwing the whole interaction away. Popover form controls never close.
|
|
5644
|
-
*/
|
|
5645
|
-
onInnerActivate(event) {
|
|
5646
|
-
if (this.popover())
|
|
5647
|
-
return;
|
|
5648
|
-
const target = event.target;
|
|
5649
|
-
if (target?.closest('.strct-dd__itemaction'))
|
|
5650
|
-
return; // the second action is not the item
|
|
5651
|
-
const item = target?.closest('strct-dropdown-item');
|
|
5652
|
-
if (item && item.getAttribute('aria-disabled') !== 'true')
|
|
5653
|
-
this.close(true);
|
|
6298
|
+
effect(() => {
|
|
6299
|
+
const open = this.open();
|
|
6300
|
+
if (open && !this.locked) {
|
|
6301
|
+
this.locked = true;
|
|
6302
|
+
lockBodyScroll(this.doc);
|
|
6303
|
+
this.previousActive = saveFocusedElement();
|
|
6304
|
+
// Move focus into the panel once it has rendered.
|
|
6305
|
+
setTimeout(() => this.focusInitial());
|
|
6306
|
+
}
|
|
6307
|
+
else if (!open && this.locked) {
|
|
6308
|
+
this.locked = false;
|
|
6309
|
+
unlockBodyScroll(this.doc);
|
|
6310
|
+
restoreFocus(this.previousActive);
|
|
6311
|
+
this.previousActive = null;
|
|
6312
|
+
}
|
|
6313
|
+
});
|
|
6314
|
+
// Release the lock if the drawer is destroyed while still open.
|
|
6315
|
+
inject(DestroyRef).onDestroy(() => {
|
|
6316
|
+
if (this.locked) {
|
|
6317
|
+
this.locked = false;
|
|
6318
|
+
unlockBodyScroll(this.doc);
|
|
6319
|
+
}
|
|
6320
|
+
});
|
|
5654
6321
|
}
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
if (this.popover())
|
|
5658
|
-
return;
|
|
5659
|
-
const items = this.navItems();
|
|
5660
|
-
if (!items.length)
|
|
5661
|
-
return;
|
|
5662
|
-
const idx = items.indexOf(event.target);
|
|
5663
|
-
const key = event.key;
|
|
5664
|
-
if (key === 'ArrowDown' || key === 'ArrowUp') {
|
|
5665
|
-
event.preventDefault();
|
|
5666
|
-
const next = idx === -1
|
|
5667
|
-
? key === 'ArrowDown'
|
|
5668
|
-
? 0
|
|
5669
|
-
: items.length - 1
|
|
5670
|
-
: (idx + (key === 'ArrowDown' ? 1 : -1) + items.length) % items.length;
|
|
5671
|
-
items[next].focus();
|
|
5672
|
-
}
|
|
5673
|
-
else if (key === 'Home' || key === 'End') {
|
|
5674
|
-
event.preventDefault();
|
|
5675
|
-
items[key === 'Home' ? 0 : items.length - 1].focus();
|
|
5676
|
-
}
|
|
5677
|
-
else if (key === 'Enter' || key === ' ') {
|
|
5678
|
-
event.preventDefault();
|
|
5679
|
-
const target = event.target;
|
|
5680
|
-
if (target.getAttribute('aria-disabled') !== 'true')
|
|
5681
|
-
target.click();
|
|
5682
|
-
}
|
|
5683
|
-
else if (key === 'ArrowRight' || key === 'ArrowLeft' || key === 'Delete') {
|
|
5684
|
-
this.onItemActionKey(event, key);
|
|
5685
|
-
}
|
|
5686
|
-
else if (key === 'Tab') {
|
|
6322
|
+
onBackdrop() {
|
|
6323
|
+
if (this.dismissable())
|
|
5687
6324
|
this.close();
|
|
5688
|
-
}
|
|
5689
6325
|
}
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
* action from its item, the left arrow returns, and Delete on the item
|
|
5693
|
-
* triggers it (the WAI-ARIA pattern for a secondary action). Keys that do not
|
|
5694
|
-
* apply are left alone, so submenus keep their own arrow behaviour.
|
|
5695
|
-
*/
|
|
5696
|
-
onItemActionKey(event, key) {
|
|
5697
|
-
const target = event.target;
|
|
5698
|
-
if (key === 'ArrowLeft') {
|
|
5699
|
-
const item = target.closest('strct-dropdown-item');
|
|
5700
|
-
if (!target.classList.contains('strct-dd__itemaction') || !item)
|
|
5701
|
-
return;
|
|
5702
|
-
event.preventDefault();
|
|
5703
|
-
item.focus();
|
|
5704
|
-
return;
|
|
5705
|
-
}
|
|
5706
|
-
const item = target.closest('strct-dropdown-item');
|
|
5707
|
-
const action = item?.querySelector('.strct-dd__itemaction');
|
|
5708
|
-
if (!item || item !== target || !action)
|
|
6326
|
+
onEscape(event) {
|
|
6327
|
+
if (!this.open() || !this.dismissable())
|
|
5709
6328
|
return;
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
else
|
|
5714
|
-
action.click();
|
|
5715
|
-
}
|
|
5716
|
-
enabledItems() {
|
|
5717
|
-
return [
|
|
5718
|
-
...this.host.nativeElement.querySelectorAll('strct-dropdown-item:not([aria-disabled="true"]), strct-submenu .strct-submenu__trigger'),
|
|
5719
|
-
];
|
|
5720
|
-
}
|
|
5721
|
-
/** What the arrow keys visit: enabled items, plus disabled ones that carry a
|
|
5722
|
-
* hint (so the reason can be read). The menu still opens on an enabled one. */
|
|
5723
|
-
navItems() {
|
|
5724
|
-
const host = this.host.nativeElement;
|
|
5725
|
-
const rows = [
|
|
5726
|
-
...host.querySelectorAll('strct-dropdown-item'),
|
|
5727
|
-
...host.querySelectorAll('strct-submenu .strct-submenu__trigger'),
|
|
5728
|
-
].filter((el) => el.getAttribute('aria-disabled') !== 'true' ||
|
|
5729
|
-
el.classList.contains('strct-dd__item--hinted'));
|
|
5730
|
-
// Two queries, so restore DOM order explicitly. (A single comma-separated
|
|
5731
|
-
// selector is DOM-ordered in browsers, but not in every DOM implementation
|
|
5732
|
-
// — jsdom returns it grouped, which scrambles arrow-key order in tests.)
|
|
5733
|
-
return rows.sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
|
|
6329
|
+
// A host modal/drawer must not also close on the same keypress.
|
|
6330
|
+
event.stopPropagation();
|
|
6331
|
+
this.close();
|
|
5734
6332
|
}
|
|
5735
|
-
/**
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
(
|
|
5740
|
-
});
|
|
6333
|
+
/** Wrap Tab focus within the panel. */
|
|
6334
|
+
onTab(event) {
|
|
6335
|
+
const panel = this.panel();
|
|
6336
|
+
if (panel)
|
|
6337
|
+
keepTabInside(event, panel);
|
|
5741
6338
|
}
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
this.close();
|
|
5745
|
-
}
|
|
6339
|
+
close() {
|
|
6340
|
+
this.open.set(false);
|
|
5746
6341
|
}
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
this.close(true);
|
|
6342
|
+
panel() {
|
|
6343
|
+
return this.elementRef.nativeElement.querySelector('.strct-drawer');
|
|
5750
6344
|
}
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
</div>
|
|
5762
|
-
<!--
|
|
5763
|
-
One panel for both modes (a default ng-content per @if branch would strand
|
|
5764
|
-
the projected content in the inactive branch) — mode picks the semantics.
|
|
5765
|
-
-->
|
|
6345
|
+
focusInitial() {
|
|
6346
|
+
const panel = this.panel();
|
|
6347
|
+
if (!panel)
|
|
6348
|
+
return;
|
|
6349
|
+
// Nothing tabbable (empty body, dismissable=false) → focus the panel itself.
|
|
6350
|
+
if (!focusFirstIn(panel))
|
|
6351
|
+
panel.focus();
|
|
6352
|
+
}
|
|
6353
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6354
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctDrawer, isStandalone: true, selector: "strct-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", 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 }, dismissable: { classPropertyName: "dismissable", publicName: "dismissable", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape($event)" }, classAttribute: "strct-drawer-host" }, queries: [{ propertyName: "footerDef", first: true, predicate: StrctDrawerFooter, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
5766
6355
|
@if (open()) {
|
|
6356
|
+
<!-- Backdrop: pointer-only dismiss target (click-through when not
|
|
6357
|
+
dismissable). Keyboard users dismiss via Escape. -->
|
|
6358
|
+
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
5767
6359
|
<div
|
|
5768
|
-
class="strct-
|
|
5769
|
-
[class.strct-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
6360
|
+
class="strct-drawer__backdrop"
|
|
6361
|
+
[class.strct-drawer__backdrop--bare]="!dismissable()"
|
|
6362
|
+
(click)="onBackdrop()"
|
|
6363
|
+
></div>
|
|
6364
|
+
<aside
|
|
6365
|
+
class="strct-drawer strct-drawer--{{ side() }} strct-drawer--{{ size() }}"
|
|
6366
|
+
role="dialog"
|
|
6367
|
+
aria-modal="true"
|
|
6368
|
+
tabindex="-1"
|
|
6369
|
+
[attr.aria-label]="title() || ariaLabel()"
|
|
6370
|
+
(keydown.tab)="onTab($event)"
|
|
6371
|
+
(keydown.shift.tab)="onTab($event)"
|
|
5777
6372
|
>
|
|
5778
|
-
|
|
5779
|
-
|
|
6373
|
+
@if (title() || dismissable()) {
|
|
6374
|
+
<header class="strct-drawer__head">
|
|
6375
|
+
<span class="strct-drawer__title">{{ title() }}</span>
|
|
6376
|
+
@if (dismissable()) {
|
|
6377
|
+
<button
|
|
6378
|
+
type="button"
|
|
6379
|
+
class="strct-drawer__close"
|
|
6380
|
+
[attr.aria-label]="closeLabel()"
|
|
6381
|
+
(click)="close()"
|
|
6382
|
+
>
|
|
6383
|
+
<strct-icon name="close" [size]="15" />
|
|
6384
|
+
</button>
|
|
6385
|
+
}
|
|
6386
|
+
</header>
|
|
6387
|
+
}
|
|
6388
|
+
<div class="strct-drawer__body"><ng-content /></div>
|
|
6389
|
+
@if (hasFooter()) {
|
|
6390
|
+
<footer class="strct-drawer__foot"><ng-content select="[strctDrawerFooter]" /></footer>
|
|
6391
|
+
}
|
|
6392
|
+
</aside>
|
|
5780
6393
|
}
|
|
5781
|
-
`, isInline: true, styles: [".strct-
|
|
6394
|
+
`, isInline: true, styles: [".strct-drawer__backdrop{position:fixed;inset:0;z-index:var(--z-overlay);background:var(--backdrop);-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);animation:strct-drawer-fade .16s ease}.strct-drawer__backdrop--bare{background:transparent;-webkit-backdrop-filter:none;backdrop-filter:none;pointer-events:none}.strct-drawer{position:fixed;z-index:calc(var(--z-overlay) + 1);display:flex;flex-direction:column;background:var(--bg-1);box-shadow:var(--shh)}.strct-drawer--start,.strct-drawer--end{top:0;bottom:0;border-radius:0}.strct-drawer--start{inset-inline-start:0;border-inline-end:1px solid var(--b2);animation:strct-drawer-in-start .18s ease}.strct-drawer--end{inset-inline-end:0;border-inline-start:1px solid var(--b2);animation:strct-drawer-in-end .18s ease}[dir=rtl] .strct-drawer--start{animation-name:strct-drawer-in-end}[dir=rtl] .strct-drawer--end{animation-name:strct-drawer-in-start}.strct-drawer--top,.strct-drawer--bottom{left:0;right:0}.strct-drawer--top{top:0;border-bottom:1px solid var(--b2);animation:strct-drawer-in-top .18s ease}.strct-drawer--bottom{bottom:0;border-top:1px solid var(--b2);animation:strct-drawer-in-bottom .18s ease}.strct-drawer--start.strct-drawer--sm,.strct-drawer--end.strct-drawer--sm{width:300px}.strct-drawer--start.strct-drawer--md,.strct-drawer--end.strct-drawer--md{width:420px}.strct-drawer--start.strct-drawer--lg,.strct-drawer--end.strct-drawer--lg{width:600px}.strct-drawer--top.strct-drawer--sm,.strct-drawer--bottom.strct-drawer--sm{height:220px}.strct-drawer--top.strct-drawer--md,.strct-drawer--bottom.strct-drawer--md{height:340px}.strct-drawer--top.strct-drawer--lg,.strct-drawer--bottom.strct-drawer--lg{height:480px}.strct-drawer{max-width:100vw;max-height:100vh}.strct-drawer__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-bottom:1px solid var(--b1);flex-shrink:0}.strct-drawer__title{font-size:15px;font-weight:600;color:var(--t1)}.strct-drawer__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-drawer__close:hover{color:var(--t1);background:var(--bg-3)}.strct-drawer__body{flex:1;min-height:0;overflow-y:auto;padding:16px 18px;font-size:13px;color:var(--t1)}.strct-drawer__foot{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-top:1px solid var(--b1);flex-shrink:0}@keyframes strct-drawer-fade{0%{opacity:0}}@keyframes strct-drawer-in-start{0%{transform:translate(-100%)}}@keyframes strct-drawer-in-end{0%{transform:translate(100%)}}@keyframes strct-drawer-in-top{0%{transform:translateY(-100%)}}@keyframes strct-drawer-in-bottom{0%{transform:translateY(100%)}}@media(prefers-reduced-motion:reduce){.strct-drawer__backdrop,.strct-drawer{animation:none}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
5782
6395
|
}
|
|
5783
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type:
|
|
6396
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDrawer, decorators: [{
|
|
5784
6397
|
type: Component,
|
|
5785
|
-
args: [{ selector: 'strct-
|
|
5786
|
-
<!--
|
|
5787
|
-
The wrapper is intentionally inert: the projected trigger should be a
|
|
5788
|
-
real button (nesting interactives fails axe). Its native click —
|
|
5789
|
-
pointer or Enter/Space — bubbles here; the strctDropdownTrigger
|
|
5790
|
-
directive puts aria-haspopup/aria-expanded on the button itself.
|
|
5791
|
-
-->
|
|
5792
|
-
<div #trigger class="strct-dd__trigger" (click)="toggle()">
|
|
5793
|
-
<ng-content select="[strctDropdownTrigger]" />
|
|
5794
|
-
</div>
|
|
5795
|
-
<!--
|
|
5796
|
-
One panel for both modes (a default ng-content per @if branch would strand
|
|
5797
|
-
the projected content in the inactive branch) — mode picks the semantics.
|
|
5798
|
-
-->
|
|
6398
|
+
args: [{ selector: 'strct-drawer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
|
|
5799
6399
|
@if (open()) {
|
|
6400
|
+
<!-- Backdrop: pointer-only dismiss target (click-through when not
|
|
6401
|
+
dismissable). Keyboard users dismiss via Escape. -->
|
|
6402
|
+
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
5800
6403
|
<div
|
|
5801
|
-
class="strct-
|
|
5802
|
-
[class.strct-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
type
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
dd = inject(StrctDropdown, { optional: true });
|
|
5829
|
-
onArrowDown(event) {
|
|
5830
|
-
if (!this.dd || this.dd.popover())
|
|
5831
|
-
return;
|
|
5832
|
-
event.preventDefault();
|
|
5833
|
-
this.dd.openMenu();
|
|
5834
|
-
}
|
|
5835
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
5836
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDropdownTrigger, isStandalone: true, selector: "[strctDropdownTrigger]", host: { listeners: { "keydown.arrowdown": "onArrowDown($event)" }, properties: { "attr.aria-haspopup": "dd ? (dd.popover() ? 'dialog' : 'menu') : null", "attr.aria-expanded": "dd ? dd.open() : null" } }, ngImport: i0 });
|
|
5837
|
-
}
|
|
5838
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownTrigger, decorators: [{
|
|
5839
|
-
type: Directive,
|
|
5840
|
-
args: [{
|
|
5841
|
-
selector: '[strctDropdownTrigger]',
|
|
5842
|
-
host: {
|
|
5843
|
-
'[attr.aria-haspopup]': "dd ? (dd.popover() ? 'dialog' : 'menu') : null",
|
|
5844
|
-
'[attr.aria-expanded]': 'dd ? dd.open() : null',
|
|
5845
|
-
'(keydown.arrowdown)': 'onArrowDown($event)',
|
|
5846
|
-
},
|
|
5847
|
-
}]
|
|
5848
|
-
}] });
|
|
5849
|
-
let dropdownItemCounter = 0;
|
|
5850
|
-
/** A selectable row inside a `<strct-dropdown>`. */
|
|
5851
|
-
/**
|
|
5852
|
-
* A second action at the end of a menu item — "delete this saved view" beside
|
|
5853
|
-
* "open it". It stops its own click from reaching the item, so the item is not
|
|
5854
|
-
* activated and the menu stays open, and it stays out of the Tab order: the
|
|
5855
|
-
* right arrow reaches it from its item, the left arrow returns, and Delete on
|
|
5856
|
-
* the item triggers it.
|
|
5857
|
-
*/
|
|
5858
|
-
class StrctDropdownItemAction {
|
|
5859
|
-
onClick(event) {
|
|
5860
|
-
event.stopPropagation();
|
|
5861
|
-
}
|
|
5862
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemAction, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
5863
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDropdownItemAction, isStandalone: true, selector: "[strctDropdownItemAction]", host: { attributes: { "tabindex": "-1" }, listeners: { "click": "onClick($event)" }, classAttribute: "strct-dd__itemaction" }, ngImport: i0 });
|
|
5864
|
-
}
|
|
5865
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemAction, decorators: [{
|
|
5866
|
-
type: Directive,
|
|
5867
|
-
args: [{
|
|
5868
|
-
selector: '[strctDropdownItemAction]',
|
|
5869
|
-
host: {
|
|
5870
|
-
class: 'strct-dd__itemaction',
|
|
5871
|
-
tabindex: '-1',
|
|
5872
|
-
'(click)': 'onClick($event)',
|
|
5873
|
-
},
|
|
5874
|
-
}]
|
|
5875
|
-
}] });
|
|
5876
|
-
class StrctDropdownItem {
|
|
5877
|
-
/**
|
|
5878
|
-
* Select-like usage: bind `selected` and the item becomes a
|
|
5879
|
-
* `menuitemradio` with `aria-checked`, a leading ✓ on the current choice
|
|
5880
|
-
* and an aligned lead slot — so reopening the menu shows what is chosen.
|
|
5881
|
-
* Leave unbound for plain action items.
|
|
5882
|
-
*/
|
|
5883
|
-
selected = input(null, /* @ts-ignore */
|
|
5884
|
-
...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
5885
|
-
/** Danger. */
|
|
5886
|
-
critical = input(false, { ...(ngDevMode ? { debugName: "critical" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5887
|
-
/** Static disable flag. */
|
|
5888
|
-
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5889
|
-
/**
|
|
5890
|
-
* A short explanation — typically why a disabled item is unavailable. Shown
|
|
5891
|
-
* as the tooltip and given to assistive technology as the description; never
|
|
5892
|
-
* rendered inline. A disabled item with a hint stays keyboard-reachable so
|
|
5893
|
-
* its reason can be read; activation stays blocked.
|
|
5894
|
-
*/
|
|
5895
|
-
hint = input(null, /* @ts-ignore */
|
|
5896
|
-
...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
5897
|
-
hintId = `strct-dd-item-${++dropdownItemCounter}-hint`;
|
|
5898
|
-
/** The panel this row belongs to, when it is inside one. */
|
|
5899
|
-
panel = inject(StrctDropdown, { optional: true });
|
|
5900
|
-
/**
|
|
5901
|
-
* A menu's rows are roved with the arrow keys, so they stay out of the tab
|
|
5902
|
-
* order. A `popover [focusable]` is a panel of real controls, and its rows
|
|
5903
|
-
* keep a tab stop of their own.
|
|
5904
|
-
*/
|
|
5905
|
-
tabindex = computed(() => {
|
|
5906
|
-
if (this.disabled() && !this.hint())
|
|
5907
|
-
return null;
|
|
5908
|
-
return this.panel?.popover() && this.panel.focusable() ? 0 : -1;
|
|
5909
|
-
}, /* @ts-ignore */
|
|
5910
|
-
...(ngDevMode ? [{ debugName: "tabindex" }] : /* istanbul ignore next */ []));
|
|
5911
|
-
constructor() {
|
|
5912
|
-
// Without pointer-events:none (see the hinted style), a disabled item
|
|
5913
|
-
// would pass clicks to the consumer's (click). Capture-phase at the target
|
|
5914
|
-
// runs before those listeners, and stopping here also keeps the menu's own
|
|
5915
|
-
// activate-and-close handler from seeing it.
|
|
5916
|
-
const host = inject((ElementRef)).nativeElement;
|
|
5917
|
-
host.addEventListener('click', (event) => {
|
|
5918
|
-
if (!this.disabled())
|
|
5919
|
-
return;
|
|
5920
|
-
event.preventDefault();
|
|
5921
|
-
event.stopImmediatePropagation();
|
|
5922
|
-
}, { capture: true });
|
|
5923
|
-
}
|
|
5924
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5925
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctDropdownItem, isStandalone: true, selector: "strct-dropdown-item", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, critical: { classPropertyName: "critical", publicName: "critical", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "selected() === null ? 'menuitem' : 'menuitemradio'", "attr.aria-checked": "selected()", "attr.tabindex": "tabindex()", "attr.title": "hint() || null", "attr.aria-describedby": "hint() ? hintId : null", "class.strct-dd__item--hinted": "!!hint()", "class.strct-dd__item--critical": "critical()", "class.strct-dd__item--selected": "selected() === true", "attr.aria-disabled": "disabled() || null" }, classAttribute: "strct-dd__item" }, ngImport: i0, template: `@if (selected() !== null) {
|
|
5926
|
-
<span class="strct-dd__check" aria-hidden="true">
|
|
5927
|
-
@if (selected()) {
|
|
5928
|
-
<strct-icon strictName="check" [size]="12" [strokeWidth]="1.8" />
|
|
6404
|
+
class="strct-drawer__backdrop"
|
|
6405
|
+
[class.strct-drawer__backdrop--bare]="!dismissable()"
|
|
6406
|
+
(click)="onBackdrop()"
|
|
6407
|
+
></div>
|
|
6408
|
+
<aside
|
|
6409
|
+
class="strct-drawer strct-drawer--{{ side() }} strct-drawer--{{ size() }}"
|
|
6410
|
+
role="dialog"
|
|
6411
|
+
aria-modal="true"
|
|
6412
|
+
tabindex="-1"
|
|
6413
|
+
[attr.aria-label]="title() || ariaLabel()"
|
|
6414
|
+
(keydown.tab)="onTab($event)"
|
|
6415
|
+
(keydown.shift.tab)="onTab($event)"
|
|
6416
|
+
>
|
|
6417
|
+
@if (title() || dismissable()) {
|
|
6418
|
+
<header class="strct-drawer__head">
|
|
6419
|
+
<span class="strct-drawer__title">{{ title() }}</span>
|
|
6420
|
+
@if (dismissable()) {
|
|
6421
|
+
<button
|
|
6422
|
+
type="button"
|
|
6423
|
+
class="strct-drawer__close"
|
|
6424
|
+
[attr.aria-label]="closeLabel()"
|
|
6425
|
+
(click)="close()"
|
|
6426
|
+
>
|
|
6427
|
+
<strct-icon name="close" [size]="15" />
|
|
6428
|
+
</button>
|
|
6429
|
+
}
|
|
6430
|
+
</header>
|
|
5929
6431
|
}
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
auto margin put it at the item's end. -->
|
|
5934
|
-
<ng-content select="[strctDropdownItemAction]" />
|
|
5935
|
-
<ng-content />
|
|
5936
|
-
@if (hint()) {
|
|
5937
|
-
<!-- hidden: kept out of the item's accessible NAME, still read as its
|
|
5938
|
-
description through aria-describedby. -->
|
|
5939
|
-
<span [id]="hintId" hidden>{{ hint() }}</span>
|
|
5940
|
-
}`, isInline: true, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__itemaction{order:1;margin-inline-start:auto;flex:none}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}.strct-dd__item--hinted[aria-disabled=true]{pointer-events:auto;cursor:not-allowed}.strct-dd__item--hinted[aria-disabled=true]:hover{background:transparent}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
5941
|
-
}
|
|
5942
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItem, decorators: [{
|
|
5943
|
-
type: Component,
|
|
5944
|
-
args: [{ selector: 'strct-dropdown-item', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `@if (selected() !== null) {
|
|
5945
|
-
<span class="strct-dd__check" aria-hidden="true">
|
|
5946
|
-
@if (selected()) {
|
|
5947
|
-
<strct-icon strictName="check" [size]="12" [strokeWidth]="1.8" />
|
|
6432
|
+
<div class="strct-drawer__body"><ng-content /></div>
|
|
6433
|
+
@if (hasFooter()) {
|
|
6434
|
+
<footer class="strct-drawer__foot"><ng-content select="[strctDrawerFooter]" /></footer>
|
|
5948
6435
|
}
|
|
5949
|
-
</
|
|
6436
|
+
</aside>
|
|
5950
6437
|
}
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
<!-- hidden: kept out of the item's accessible NAME, still read as its
|
|
5957
|
-
description through aria-describedby. -->
|
|
5958
|
-
<span [id]="hintId" hidden>{{ hint() }}</span>
|
|
5959
|
-
}`, host: {
|
|
5960
|
-
class: 'strct-dd__item',
|
|
5961
|
-
'[attr.role]': "selected() === null ? 'menuitem' : 'menuitemradio'",
|
|
5962
|
-
'[attr.aria-checked]': 'selected()',
|
|
5963
|
-
// A disabled item is skipped — unless it has a hint to be read.
|
|
5964
|
-
'[attr.tabindex]': 'tabindex()',
|
|
5965
|
-
'[attr.title]': 'hint() || null',
|
|
5966
|
-
'[attr.aria-describedby]': 'hint() ? hintId : null',
|
|
5967
|
-
'[class.strct-dd__item--hinted]': '!!hint()',
|
|
5968
|
-
'[class.strct-dd__item--critical]': 'critical()',
|
|
5969
|
-
'[class.strct-dd__item--selected]': 'selected() === true',
|
|
5970
|
-
'[attr.aria-disabled]': 'disabled() || null',
|
|
5971
|
-
}, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__itemaction{order:1;margin-inline-start:auto;flex:none}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}.strct-dd__item--hinted[aria-disabled=true]{pointer-events:auto;cursor:not-allowed}.strct-dd__item--hinted[aria-disabled=true]:hover{background:transparent}\n"] }]
|
|
5972
|
-
}], ctorParameters: () => [], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], critical: [{ type: i0.Input, args: [{ isSignal: true, alias: "critical", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
|
|
5973
|
-
/** Thin separator between groups of menu items. */
|
|
5974
|
-
class StrctDropdownDivider {
|
|
5975
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownDivider, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5976
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.4", type: StrctDropdownDivider, isStandalone: true, selector: "strct-dropdown-divider", host: { attributes: { "role": "separator" }, classAttribute: "strct-dd__divider" }, ngImport: i0, template: '', isInline: true, styles: [".strct-dd__divider{display:block;height:1px;margin:4px 6px;background:var(--b2)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
5977
|
-
}
|
|
5978
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownDivider, decorators: [{
|
|
5979
|
-
type: Component,
|
|
5980
|
-
args: [{ selector: 'strct-dropdown-divider', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '', host: { class: 'strct-dd__divider', role: 'separator' }, styles: [".strct-dd__divider{display:block;height:1px;margin:4px 6px;background:var(--b2)}\n"] }]
|
|
5981
|
-
}] });
|
|
6438
|
+
`, host: {
|
|
6439
|
+
class: 'strct-drawer-host',
|
|
6440
|
+
'(document:keydown.escape)': 'onEscape($event)',
|
|
6441
|
+
}, styles: [".strct-drawer__backdrop{position:fixed;inset:0;z-index:var(--z-overlay);background:var(--backdrop);-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);animation:strct-drawer-fade .16s ease}.strct-drawer__backdrop--bare{background:transparent;-webkit-backdrop-filter:none;backdrop-filter:none;pointer-events:none}.strct-drawer{position:fixed;z-index:calc(var(--z-overlay) + 1);display:flex;flex-direction:column;background:var(--bg-1);box-shadow:var(--shh)}.strct-drawer--start,.strct-drawer--end{top:0;bottom:0;border-radius:0}.strct-drawer--start{inset-inline-start:0;border-inline-end:1px solid var(--b2);animation:strct-drawer-in-start .18s ease}.strct-drawer--end{inset-inline-end:0;border-inline-start:1px solid var(--b2);animation:strct-drawer-in-end .18s ease}[dir=rtl] .strct-drawer--start{animation-name:strct-drawer-in-end}[dir=rtl] .strct-drawer--end{animation-name:strct-drawer-in-start}.strct-drawer--top,.strct-drawer--bottom{left:0;right:0}.strct-drawer--top{top:0;border-bottom:1px solid var(--b2);animation:strct-drawer-in-top .18s ease}.strct-drawer--bottom{bottom:0;border-top:1px solid var(--b2);animation:strct-drawer-in-bottom .18s ease}.strct-drawer--start.strct-drawer--sm,.strct-drawer--end.strct-drawer--sm{width:300px}.strct-drawer--start.strct-drawer--md,.strct-drawer--end.strct-drawer--md{width:420px}.strct-drawer--start.strct-drawer--lg,.strct-drawer--end.strct-drawer--lg{width:600px}.strct-drawer--top.strct-drawer--sm,.strct-drawer--bottom.strct-drawer--sm{height:220px}.strct-drawer--top.strct-drawer--md,.strct-drawer--bottom.strct-drawer--md{height:340px}.strct-drawer--top.strct-drawer--lg,.strct-drawer--bottom.strct-drawer--lg{height:480px}.strct-drawer{max-width:100vw;max-height:100vh}.strct-drawer__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-bottom:1px solid var(--b1);flex-shrink:0}.strct-drawer__title{font-size:15px;font-weight:600;color:var(--t1)}.strct-drawer__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-drawer__close:hover{color:var(--t1);background:var(--bg-3)}.strct-drawer__body{flex:1;min-height:0;overflow-y:auto;padding:16px 18px;font-size:13px;color:var(--t1)}.strct-drawer__foot{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-top:1px solid var(--b1);flex-shrink:0}@keyframes strct-drawer-fade{0%{opacity:0}}@keyframes strct-drawer-in-start{0%{transform:translate(-100%)}}@keyframes strct-drawer-in-end{0%{transform:translate(100%)}}@keyframes strct-drawer-in-top{0%{transform:translateY(-100%)}}@keyframes strct-drawer-in-bottom{0%{transform:translateY(100%)}}@media(prefers-reduced-motion:reduce){.strct-drawer__backdrop,.strct-drawer{animation:none}}\n"] }]
|
|
6442
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], dismissable: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissable", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], footerDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctDrawerFooter), { isSignal: true }] }] } });
|
|
5982
6443
|
|
|
5983
6444
|
/**
|
|
5984
6445
|
* Right-click (context) menu. Wraps a trigger area and shows a menu at the
|
|
@@ -7108,14 +7569,102 @@ class StrctSectionMenu {
|
|
|
7108
7569
|
next.set(sec.label, !open);
|
|
7109
7570
|
this.overrides.set(next);
|
|
7110
7571
|
}
|
|
7111
|
-
|
|
7572
|
+
/** Enabled entry with a router destination → renders as `<a routerLink>`. */
|
|
7573
|
+
isRouted(item) {
|
|
7574
|
+
return item.routerLink != null && !item.disabled;
|
|
7575
|
+
}
|
|
7576
|
+
/**
|
|
7577
|
+
* Active state of a router-link entry: an explicit `activeId` is
|
|
7578
|
+
* authoritative; without one the router's own active state drives it — the
|
|
7579
|
+
* rail's contract.
|
|
7580
|
+
*/
|
|
7581
|
+
isActive(item, routerActive) {
|
|
7582
|
+
const id = this.activeId();
|
|
7583
|
+
return id !== null ? item.id === id : routerActive;
|
|
7584
|
+
}
|
|
7585
|
+
pick(item, event) {
|
|
7112
7586
|
if (item.disabled)
|
|
7113
7587
|
return;
|
|
7588
|
+
// A modified or non-primary click on a link belongs to the browser (a new
|
|
7589
|
+
// tab, a copied address) — never to us: no activeId change, no (select).
|
|
7590
|
+
if (event &&
|
|
7591
|
+
(item.routerLink != null || item.href) &&
|
|
7592
|
+
(event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) {
|
|
7593
|
+
return;
|
|
7594
|
+
}
|
|
7114
7595
|
this.activeId.set(item.id);
|
|
7115
7596
|
this.select.emit(item);
|
|
7116
7597
|
}
|
|
7117
7598
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSectionMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7118
7599
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctSectionMenu, isStandalone: true, selector: "strct-section-menu", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null }, activeId: { classPropertyName: "activeId", publicName: "activeId", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, showIcons: { classPropertyName: "showIcons", publicName: "showIcons", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeId: "activeIdChange", select: "select" }, host: { classAttribute: "strct-sm-host" }, ngImport: i0, template: `
|
|
7600
|
+
<!-- What every entry holds, whatever element carries it. -->
|
|
7601
|
+
<ng-template #rowContent let-item>
|
|
7602
|
+
@if (showIcons() && item.icon) {
|
|
7603
|
+
<strct-icon
|
|
7604
|
+
class="strct-sm__item-icon"
|
|
7605
|
+
[name]="item.icon"
|
|
7606
|
+
[size]="16"
|
|
7607
|
+
[strokeWidth]="1.4"
|
|
7608
|
+
/>
|
|
7609
|
+
}
|
|
7610
|
+
<span class="strct-sm__item-label">{{ item.label }}</span>
|
|
7611
|
+
@if (item.trailingIcon) {
|
|
7612
|
+
<strct-icon
|
|
7613
|
+
class="strct-sm__trailing"
|
|
7614
|
+
[name]="item.trailingIcon"
|
|
7615
|
+
[size]="14"
|
|
7616
|
+
[strokeWidth]="1.5"
|
|
7617
|
+
/>
|
|
7618
|
+
}
|
|
7619
|
+
@if (item.badge !== undefined && item.badge !== null && item.badge !== '') {
|
|
7620
|
+
<span class="strct-sm__badge strct-sm__badge--{{ item.badgeStatus ?? 'accent' }}">
|
|
7621
|
+
{{ item.badge }}
|
|
7622
|
+
</span>
|
|
7623
|
+
} @else if (item.dot) {
|
|
7624
|
+
<span class="strct-sm__dot strct-sm__dot--{{ item.dotStatus ?? 'accent' }}"></span>
|
|
7625
|
+
}
|
|
7626
|
+
</ng-template>
|
|
7627
|
+
|
|
7628
|
+
<!-- A router link, a plain link, or a button (the default). A page with an
|
|
7629
|
+
address of its own has to be openable in a new tab, as the rail
|
|
7630
|
+
beside this menu already is. -->
|
|
7631
|
+
<ng-template #row let-item>
|
|
7632
|
+
@if (isRouted(item)) {
|
|
7633
|
+
<a
|
|
7634
|
+
class="strct-sm__item"
|
|
7635
|
+
[routerLink]="item.routerLink"
|
|
7636
|
+
routerLinkActive
|
|
7637
|
+
#rla="routerLinkActive"
|
|
7638
|
+
[class.strct-sm__item--active]="isActive(item, rla.isActive)"
|
|
7639
|
+
[attr.aria-current]="isActive(item, rla.isActive) ? 'page' : null"
|
|
7640
|
+
(click)="pick(item, $event)"
|
|
7641
|
+
>
|
|
7642
|
+
<ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
|
|
7643
|
+
</a>
|
|
7644
|
+
} @else if (item.href && !item.disabled) {
|
|
7645
|
+
<a
|
|
7646
|
+
class="strct-sm__item"
|
|
7647
|
+
[href]="item.href"
|
|
7648
|
+
[class.strct-sm__item--active]="item.id === activeId()"
|
|
7649
|
+
[attr.aria-current]="item.id === activeId() ? 'page' : null"
|
|
7650
|
+
(click)="pick(item, $event)"
|
|
7651
|
+
>
|
|
7652
|
+
<ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
|
|
7653
|
+
</a>
|
|
7654
|
+
} @else {
|
|
7655
|
+
<button
|
|
7656
|
+
type="button"
|
|
7657
|
+
class="strct-sm__item"
|
|
7658
|
+
[class.strct-sm__item--active]="item.id === activeId()"
|
|
7659
|
+
[disabled]="item.disabled"
|
|
7660
|
+
[attr.aria-current]="item.id === activeId() ? 'page' : null"
|
|
7661
|
+
(click)="pick(item)"
|
|
7662
|
+
>
|
|
7663
|
+
<ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
|
|
7664
|
+
</button>
|
|
7665
|
+
}
|
|
7666
|
+
</ng-template>
|
|
7667
|
+
|
|
7119
7668
|
<nav class="strct-sm" [attr.aria-label]="ariaLabel()">
|
|
7120
7669
|
@for (sec of sections(); track sec.label) {
|
|
7121
7670
|
<div class="strct-sm__section" [class.strct-sm__section--collapsible]="collapsible()">
|
|
@@ -7146,54 +7695,86 @@ class StrctSectionMenu {
|
|
|
7146
7695
|
@if (isOpen(sec)) {
|
|
7147
7696
|
<div class="strct-sm__items" role="group">
|
|
7148
7697
|
@for (item of sec.items; track item.id) {
|
|
7149
|
-
<
|
|
7150
|
-
type="button"
|
|
7151
|
-
class="strct-sm__item"
|
|
7152
|
-
[class.strct-sm__item--active]="item.id === activeId()"
|
|
7153
|
-
[disabled]="item.disabled"
|
|
7154
|
-
[attr.aria-current]="item.id === activeId() ? 'page' : null"
|
|
7155
|
-
(click)="pick(item)"
|
|
7156
|
-
>
|
|
7157
|
-
@if (showIcons() && item.icon) {
|
|
7158
|
-
<strct-icon
|
|
7159
|
-
class="strct-sm__item-icon"
|
|
7160
|
-
[name]="item.icon"
|
|
7161
|
-
[size]="16"
|
|
7162
|
-
[strokeWidth]="1.4"
|
|
7163
|
-
/>
|
|
7164
|
-
}
|
|
7165
|
-
<span class="strct-sm__item-label">{{ item.label }}</span>
|
|
7166
|
-
@if (item.trailingIcon) {
|
|
7167
|
-
<strct-icon
|
|
7168
|
-
class="strct-sm__trailing"
|
|
7169
|
-
[name]="item.trailingIcon"
|
|
7170
|
-
[size]="14"
|
|
7171
|
-
[strokeWidth]="1.5"
|
|
7172
|
-
/>
|
|
7173
|
-
}
|
|
7174
|
-
@if (item.badge !== undefined && item.badge !== null && item.badge !== '') {
|
|
7175
|
-
<span
|
|
7176
|
-
class="strct-sm__badge strct-sm__badge--{{ item.badgeStatus ?? 'accent' }}"
|
|
7177
|
-
>
|
|
7178
|
-
{{ item.badge }}
|
|
7179
|
-
</span>
|
|
7180
|
-
} @else if (item.dot) {
|
|
7181
|
-
<span
|
|
7182
|
-
class="strct-sm__dot strct-sm__dot--{{ item.dotStatus ?? 'accent' }}"
|
|
7183
|
-
></span>
|
|
7184
|
-
}
|
|
7185
|
-
</button>
|
|
7698
|
+
<ng-container *ngTemplateOutlet="row; context: { $implicit: item }" />
|
|
7186
7699
|
}
|
|
7187
7700
|
</div>
|
|
7188
7701
|
}
|
|
7189
7702
|
</div>
|
|
7190
7703
|
}
|
|
7191
7704
|
</nav>
|
|
7192
|
-
`, isInline: true, styles: [".strct-sm{display:flex;flex-direction:column;gap:1px}.strct-sm__section--collapsible+.strct-sm__section--collapsible{margin-top:1px}.strct-sm__cat{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--t2);font-size:13px;font-weight:500;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease,color .13s ease}.strct-sm__cat:hover{background:var(--bg-3);color:var(--t1)}.strct-sm__cat:focus-visible,.strct-sm__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-sm__chevron{display:inline-flex;color:var(--t3);flex-shrink:0;transition:transform .15s ease}.strct-sm__chevron--open{transform:rotate(90deg)}.strct-sm__cat-icon{color:var(--t3);flex-shrink:0}.strct-sm__cat-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__cat-static{padding:11px 10px 5px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--t3)}.strct-sm__items{display:flex;flex-direction:column}.strct-sm__item{display:flex;align-items:center;gap:9px;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease}.strct-sm__section--collapsible .strct-sm__item{padding-inline-start:31px}.strct-sm__item:hover:not(:disabled){background:var(--bg-3)}.strct-sm__item--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-sm__item--active .strct-sm__item-icon{color:var(--acc)}.strct-sm__item:disabled{opacity:.5;cursor:not-allowed}.strct-sm__item-icon{color:var(--t2);flex-shrink:0}.strct-sm__item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__trailing{flex-shrink:0;color:var(--t3)}.strct-sm__badge{flex-shrink:0;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;line-height:1;border-radius:9px;font-variant-numeric:tabular-nums}.strct-sm__badge--accent{background:var(--acc-m);color:var(--acc)}.strct-sm__badge--success{background:var(--success-bg);color:var(--success)}.strct-sm__badge--warning{background:var(--warning-bg);color:var(--warning)}.strct-sm__badge--critical{background:var(--critical-bg);color:var(--critical)}.strct-sm__dot{flex-shrink:0;width:7px;height:7px;border-radius:50%}.strct-sm__dot--accent{background:var(--acc)}.strct-sm__dot--success{background:var(--success)}.strct-sm__dot--warning{background:var(--warning)}.strct-sm__dot--critical{background:var(--critical)}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
7705
|
+
`, isInline: true, styles: [".strct-sm{display:flex;flex-direction:column;gap:1px}.strct-sm__section--collapsible+.strct-sm__section--collapsible{margin-top:1px}.strct-sm__cat{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--t2);font-size:13px;font-weight:500;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease,color .13s ease}.strct-sm__cat:hover{background:var(--bg-3);color:var(--t1)}.strct-sm__cat:focus-visible,.strct-sm__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-sm__chevron{display:inline-flex;color:var(--t3);flex-shrink:0;transition:transform .15s ease}.strct-sm__chevron--open{transform:rotate(90deg)}.strct-sm__cat-icon{color:var(--t3);flex-shrink:0}.strct-sm__cat-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__cat-static{padding:11px 10px 5px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--t3)}.strct-sm__items{display:flex;flex-direction:column}.strct-sm__item{display:flex;align-items:center;gap:9px;width:100%;box-sizing:border-box;text-decoration:none;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease}.strct-sm__section--collapsible .strct-sm__item{padding-inline-start:31px}.strct-sm__item:hover:not(:disabled){background:var(--bg-3)}.strct-sm__item--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-sm__item--active .strct-sm__item-icon{color:var(--acc)}.strct-sm__item:disabled{opacity:.5;cursor:not-allowed}.strct-sm__item-icon{color:var(--t2);flex-shrink:0}.strct-sm__item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__trailing{flex-shrink:0;color:var(--t3)}.strct-sm__badge{flex-shrink:0;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;line-height:1;border-radius:9px;font-variant-numeric:tabular-nums}.strct-sm__badge--accent{background:var(--acc-m);color:var(--acc)}.strct-sm__badge--success{background:var(--success-bg);color:var(--success)}.strct-sm__badge--warning{background:var(--warning-bg);color:var(--warning)}.strct-sm__badge--critical{background:var(--critical-bg);color:var(--critical)}.strct-sm__dot{flex-shrink:0;width:7px;height:7px;border-radius:50%}.strct-sm__dot--accent{background:var(--acc)}.strct-sm__dot--success{background:var(--success)}.strct-sm__dot--warning{background:var(--warning)}.strct-sm__dot--critical{background:var(--critical)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
7193
7706
|
}
|
|
7194
7707
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSectionMenu, decorators: [{
|
|
7195
7708
|
type: Component,
|
|
7196
|
-
args: [{ selector: 'strct-section-menu', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
|
|
7709
|
+
args: [{ selector: 'strct-section-menu', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet, RouterLink, RouterLinkActive, StrctIcon], template: `
|
|
7710
|
+
<!-- What every entry holds, whatever element carries it. -->
|
|
7711
|
+
<ng-template #rowContent let-item>
|
|
7712
|
+
@if (showIcons() && item.icon) {
|
|
7713
|
+
<strct-icon
|
|
7714
|
+
class="strct-sm__item-icon"
|
|
7715
|
+
[name]="item.icon"
|
|
7716
|
+
[size]="16"
|
|
7717
|
+
[strokeWidth]="1.4"
|
|
7718
|
+
/>
|
|
7719
|
+
}
|
|
7720
|
+
<span class="strct-sm__item-label">{{ item.label }}</span>
|
|
7721
|
+
@if (item.trailingIcon) {
|
|
7722
|
+
<strct-icon
|
|
7723
|
+
class="strct-sm__trailing"
|
|
7724
|
+
[name]="item.trailingIcon"
|
|
7725
|
+
[size]="14"
|
|
7726
|
+
[strokeWidth]="1.5"
|
|
7727
|
+
/>
|
|
7728
|
+
}
|
|
7729
|
+
@if (item.badge !== undefined && item.badge !== null && item.badge !== '') {
|
|
7730
|
+
<span class="strct-sm__badge strct-sm__badge--{{ item.badgeStatus ?? 'accent' }}">
|
|
7731
|
+
{{ item.badge }}
|
|
7732
|
+
</span>
|
|
7733
|
+
} @else if (item.dot) {
|
|
7734
|
+
<span class="strct-sm__dot strct-sm__dot--{{ item.dotStatus ?? 'accent' }}"></span>
|
|
7735
|
+
}
|
|
7736
|
+
</ng-template>
|
|
7737
|
+
|
|
7738
|
+
<!-- A router link, a plain link, or a button (the default). A page with an
|
|
7739
|
+
address of its own has to be openable in a new tab, as the rail
|
|
7740
|
+
beside this menu already is. -->
|
|
7741
|
+
<ng-template #row let-item>
|
|
7742
|
+
@if (isRouted(item)) {
|
|
7743
|
+
<a
|
|
7744
|
+
class="strct-sm__item"
|
|
7745
|
+
[routerLink]="item.routerLink"
|
|
7746
|
+
routerLinkActive
|
|
7747
|
+
#rla="routerLinkActive"
|
|
7748
|
+
[class.strct-sm__item--active]="isActive(item, rla.isActive)"
|
|
7749
|
+
[attr.aria-current]="isActive(item, rla.isActive) ? 'page' : null"
|
|
7750
|
+
(click)="pick(item, $event)"
|
|
7751
|
+
>
|
|
7752
|
+
<ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
|
|
7753
|
+
</a>
|
|
7754
|
+
} @else if (item.href && !item.disabled) {
|
|
7755
|
+
<a
|
|
7756
|
+
class="strct-sm__item"
|
|
7757
|
+
[href]="item.href"
|
|
7758
|
+
[class.strct-sm__item--active]="item.id === activeId()"
|
|
7759
|
+
[attr.aria-current]="item.id === activeId() ? 'page' : null"
|
|
7760
|
+
(click)="pick(item, $event)"
|
|
7761
|
+
>
|
|
7762
|
+
<ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
|
|
7763
|
+
</a>
|
|
7764
|
+
} @else {
|
|
7765
|
+
<button
|
|
7766
|
+
type="button"
|
|
7767
|
+
class="strct-sm__item"
|
|
7768
|
+
[class.strct-sm__item--active]="item.id === activeId()"
|
|
7769
|
+
[disabled]="item.disabled"
|
|
7770
|
+
[attr.aria-current]="item.id === activeId() ? 'page' : null"
|
|
7771
|
+
(click)="pick(item)"
|
|
7772
|
+
>
|
|
7773
|
+
<ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
|
|
7774
|
+
</button>
|
|
7775
|
+
}
|
|
7776
|
+
</ng-template>
|
|
7777
|
+
|
|
7197
7778
|
<nav class="strct-sm" [attr.aria-label]="ariaLabel()">
|
|
7198
7779
|
@for (sec of sections(); track sec.label) {
|
|
7199
7780
|
<div class="strct-sm__section" [class.strct-sm__section--collapsible]="collapsible()">
|
|
@@ -7224,50 +7805,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
7224
7805
|
@if (isOpen(sec)) {
|
|
7225
7806
|
<div class="strct-sm__items" role="group">
|
|
7226
7807
|
@for (item of sec.items; track item.id) {
|
|
7227
|
-
<
|
|
7228
|
-
type="button"
|
|
7229
|
-
class="strct-sm__item"
|
|
7230
|
-
[class.strct-sm__item--active]="item.id === activeId()"
|
|
7231
|
-
[disabled]="item.disabled"
|
|
7232
|
-
[attr.aria-current]="item.id === activeId() ? 'page' : null"
|
|
7233
|
-
(click)="pick(item)"
|
|
7234
|
-
>
|
|
7235
|
-
@if (showIcons() && item.icon) {
|
|
7236
|
-
<strct-icon
|
|
7237
|
-
class="strct-sm__item-icon"
|
|
7238
|
-
[name]="item.icon"
|
|
7239
|
-
[size]="16"
|
|
7240
|
-
[strokeWidth]="1.4"
|
|
7241
|
-
/>
|
|
7242
|
-
}
|
|
7243
|
-
<span class="strct-sm__item-label">{{ item.label }}</span>
|
|
7244
|
-
@if (item.trailingIcon) {
|
|
7245
|
-
<strct-icon
|
|
7246
|
-
class="strct-sm__trailing"
|
|
7247
|
-
[name]="item.trailingIcon"
|
|
7248
|
-
[size]="14"
|
|
7249
|
-
[strokeWidth]="1.5"
|
|
7250
|
-
/>
|
|
7251
|
-
}
|
|
7252
|
-
@if (item.badge !== undefined && item.badge !== null && item.badge !== '') {
|
|
7253
|
-
<span
|
|
7254
|
-
class="strct-sm__badge strct-sm__badge--{{ item.badgeStatus ?? 'accent' }}"
|
|
7255
|
-
>
|
|
7256
|
-
{{ item.badge }}
|
|
7257
|
-
</span>
|
|
7258
|
-
} @else if (item.dot) {
|
|
7259
|
-
<span
|
|
7260
|
-
class="strct-sm__dot strct-sm__dot--{{ item.dotStatus ?? 'accent' }}"
|
|
7261
|
-
></span>
|
|
7262
|
-
}
|
|
7263
|
-
</button>
|
|
7808
|
+
<ng-container *ngTemplateOutlet="row; context: { $implicit: item }" />
|
|
7264
7809
|
}
|
|
7265
7810
|
</div>
|
|
7266
7811
|
}
|
|
7267
7812
|
</div>
|
|
7268
7813
|
}
|
|
7269
7814
|
</nav>
|
|
7270
|
-
`, host: { class: 'strct-sm-host' }, styles: [".strct-sm{display:flex;flex-direction:column;gap:1px}.strct-sm__section--collapsible+.strct-sm__section--collapsible{margin-top:1px}.strct-sm__cat{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--t2);font-size:13px;font-weight:500;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease,color .13s ease}.strct-sm__cat:hover{background:var(--bg-3);color:var(--t1)}.strct-sm__cat:focus-visible,.strct-sm__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-sm__chevron{display:inline-flex;color:var(--t3);flex-shrink:0;transition:transform .15s ease}.strct-sm__chevron--open{transform:rotate(90deg)}.strct-sm__cat-icon{color:var(--t3);flex-shrink:0}.strct-sm__cat-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__cat-static{padding:11px 10px 5px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--t3)}.strct-sm__items{display:flex;flex-direction:column}.strct-sm__item{display:flex;align-items:center;gap:9px;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease}.strct-sm__section--collapsible .strct-sm__item{padding-inline-start:31px}.strct-sm__item:hover:not(:disabled){background:var(--bg-3)}.strct-sm__item--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-sm__item--active .strct-sm__item-icon{color:var(--acc)}.strct-sm__item:disabled{opacity:.5;cursor:not-allowed}.strct-sm__item-icon{color:var(--t2);flex-shrink:0}.strct-sm__item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__trailing{flex-shrink:0;color:var(--t3)}.strct-sm__badge{flex-shrink:0;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;line-height:1;border-radius:9px;font-variant-numeric:tabular-nums}.strct-sm__badge--accent{background:var(--acc-m);color:var(--acc)}.strct-sm__badge--success{background:var(--success-bg);color:var(--success)}.strct-sm__badge--warning{background:var(--warning-bg);color:var(--warning)}.strct-sm__badge--critical{background:var(--critical-bg);color:var(--critical)}.strct-sm__dot{flex-shrink:0;width:7px;height:7px;border-radius:50%}.strct-sm__dot--accent{background:var(--acc)}.strct-sm__dot--success{background:var(--success)}.strct-sm__dot--warning{background:var(--warning)}.strct-sm__dot--critical{background:var(--critical)}\n"] }]
|
|
7815
|
+
`, host: { class: 'strct-sm-host' }, styles: [".strct-sm{display:flex;flex-direction:column;gap:1px}.strct-sm__section--collapsible+.strct-sm__section--collapsible{margin-top:1px}.strct-sm__cat{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--t2);font-size:13px;font-weight:500;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease,color .13s ease}.strct-sm__cat:hover{background:var(--bg-3);color:var(--t1)}.strct-sm__cat:focus-visible,.strct-sm__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-sm__chevron{display:inline-flex;color:var(--t3);flex-shrink:0;transition:transform .15s ease}.strct-sm__chevron--open{transform:rotate(90deg)}.strct-sm__cat-icon{color:var(--t3);flex-shrink:0}.strct-sm__cat-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__cat-static{padding:11px 10px 5px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--t3)}.strct-sm__items{display:flex;flex-direction:column}.strct-sm__item{display:flex;align-items:center;gap:9px;width:100%;box-sizing:border-box;text-decoration:none;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease}.strct-sm__section--collapsible .strct-sm__item{padding-inline-start:31px}.strct-sm__item:hover:not(:disabled){background:var(--bg-3)}.strct-sm__item--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-sm__item--active .strct-sm__item-icon{color:var(--acc)}.strct-sm__item:disabled{opacity:.5;cursor:not-allowed}.strct-sm__item-icon{color:var(--t2);flex-shrink:0}.strct-sm__item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__trailing{flex-shrink:0;color:var(--t3)}.strct-sm__badge{flex-shrink:0;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;line-height:1;border-radius:9px;font-variant-numeric:tabular-nums}.strct-sm__badge--accent{background:var(--acc-m);color:var(--acc)}.strct-sm__badge--success{background:var(--success-bg);color:var(--success)}.strct-sm__badge--warning{background:var(--warning-bg);color:var(--warning)}.strct-sm__badge--critical{background:var(--critical-bg);color:var(--critical)}.strct-sm__dot{flex-shrink:0;width:7px;height:7px;border-radius:50%}.strct-sm__dot--accent{background:var(--acc)}.strct-sm__dot--success{background:var(--success)}.strct-sm__dot--warning{background:var(--warning)}.strct-sm__dot--critical{background:var(--critical)}\n"] }]
|
|
7271
7816
|
}], propDecorators: { sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: true }] }], activeId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeId", required: false }] }, { type: i0.Output, args: ["activeIdChange"] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], showIcons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcons", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], select: [{ type: i0.Output, args: ["select"] }] } });
|
|
7272
7817
|
|
|
7273
7818
|
/**
|
|
@@ -7324,6 +7869,12 @@ class StrctCommandPalette {
|
|
|
7324
7869
|
* rendering.
|
|
7325
7870
|
*/
|
|
7326
7871
|
filter = input(true, { ...(ngDevMode ? { debugName: "filter" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
7872
|
+
/**
|
|
7873
|
+
* The order the sections are drawn in — the product you are in first, say.
|
|
7874
|
+
* Sections not named here follow, in the order their first result arrives.
|
|
7875
|
+
*/
|
|
7876
|
+
sectionOrder = input([], /* @ts-ignore */
|
|
7877
|
+
...(ngDevMode ? [{ debugName: "sectionOrder" }] : /* istanbul ignore next */ []));
|
|
7327
7878
|
/** Show a "searching" row while server results are in flight. */
|
|
7328
7879
|
loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
7329
7880
|
/** Localizable text of the loading row. */
|
|
@@ -7344,19 +7895,39 @@ class StrctCommandPalette {
|
|
|
7344
7895
|
optionId(i) {
|
|
7345
7896
|
return `${this.listId}-opt-${i}`;
|
|
7346
7897
|
}
|
|
7898
|
+
/**
|
|
7899
|
+
* The results in their drawn order, chunked into sections. The flat index
|
|
7900
|
+
* each row carries is the one the keyboard and `aria-activedescendant`
|
|
7901
|
+
* use, so what the arrows do and what the eye sees stay the same list.
|
|
7902
|
+
*/
|
|
7903
|
+
sections = computed(() => {
|
|
7904
|
+
const out = [];
|
|
7905
|
+
this.filtered().forEach((item, index) => {
|
|
7906
|
+
const label = item.section ?? '';
|
|
7907
|
+
const last = out[out.length - 1];
|
|
7908
|
+
if (last && last.label === label)
|
|
7909
|
+
last.rows.push({ item, index });
|
|
7910
|
+
else
|
|
7911
|
+
out.push({ label, id: `${this.listId}-sec-${out.length}`, rows: [{ item, index }] });
|
|
7912
|
+
});
|
|
7913
|
+
return out;
|
|
7914
|
+
}, /* @ts-ignore */
|
|
7915
|
+
...(ngDevMode ? [{ debugName: "sections" }] : /* istanbul ignore next */ []));
|
|
7347
7916
|
/** Rank: label prefix > word-start > substring; group/keyword matches last. */
|
|
7348
7917
|
filtered = computed(() => {
|
|
7349
7918
|
const items = this.items();
|
|
7350
7919
|
// Server-backed mode: the caller already filtered — render as given.
|
|
7351
7920
|
if (!this.filter())
|
|
7352
|
-
return items.slice(0, this.maxResults());
|
|
7921
|
+
return this.bySection(items.slice(0, this.maxResults()));
|
|
7353
7922
|
const q = this.query().trim().toLowerCase();
|
|
7354
7923
|
if (!q)
|
|
7355
|
-
return items.slice(0, this.maxResults());
|
|
7924
|
+
return this.bySection(items.slice(0, this.maxResults()));
|
|
7356
7925
|
const scored = [];
|
|
7357
7926
|
for (const item of items) {
|
|
7358
7927
|
const label = item.label.toLowerCase();
|
|
7359
|
-
const hay = [item.group ?? '', ...(item.keywords ?? [])]
|
|
7928
|
+
const hay = [item.group ?? '', item.section ?? '', ...(item.keywords ?? [])]
|
|
7929
|
+
.join(' ')
|
|
7930
|
+
.toLowerCase();
|
|
7360
7931
|
let score = -1;
|
|
7361
7932
|
if (label.startsWith(q))
|
|
7362
7933
|
score = 0;
|
|
@@ -7369,12 +7940,30 @@ class StrctCommandPalette {
|
|
|
7369
7940
|
if (score >= 0)
|
|
7370
7941
|
scored.push({ item, score });
|
|
7371
7942
|
}
|
|
7372
|
-
return scored
|
|
7943
|
+
return this.bySection(scored
|
|
7373
7944
|
.sort((a, b) => a.score - b.score)
|
|
7374
7945
|
.slice(0, this.maxResults())
|
|
7375
|
-
.map((s) => s.item);
|
|
7946
|
+
.map((s) => s.item));
|
|
7376
7947
|
}, /* @ts-ignore */
|
|
7377
7948
|
...(ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []));
|
|
7949
|
+
/**
|
|
7950
|
+
* Keeps each section's results together, in `sectionOrder` where it is
|
|
7951
|
+
* given and otherwise in the order the sections first appear. The sort is
|
|
7952
|
+
* stable, so how well a result matched still orders it inside its section.
|
|
7953
|
+
*/
|
|
7954
|
+
bySection(list) {
|
|
7955
|
+
const rank = new Map();
|
|
7956
|
+
this.sectionOrder().forEach((name, i) => rank.set(name, i));
|
|
7957
|
+
let next = rank.size;
|
|
7958
|
+
for (const item of list) {
|
|
7959
|
+
const key = item.section ?? '';
|
|
7960
|
+
if (!rank.has(key))
|
|
7961
|
+
rank.set(key, next++);
|
|
7962
|
+
}
|
|
7963
|
+
if (rank.size < 2)
|
|
7964
|
+
return list;
|
|
7965
|
+
return [...list].sort((a, b) => rank.get(a.section ?? '') - rank.get(b.section ?? ''));
|
|
7966
|
+
}
|
|
7378
7967
|
constructor() {
|
|
7379
7968
|
// Async results can shrink under the cursor — keep the active option in range.
|
|
7380
7969
|
effect(() => {
|
|
@@ -7453,7 +8042,7 @@ class StrctCommandPalette {
|
|
|
7453
8042
|
this.open.set(false);
|
|
7454
8043
|
}
|
|
7455
8044
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctCommandPalette, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7456
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctCommandPalette, isStandalone: true, selector: "strct-command-palette", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, hotkey: { classPropertyName: "hotkey", publicName: "hotkey", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, maxResults: { classPropertyName: "maxResults", publicName: "maxResults", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", picked: "picked", query: "queryChange" }, host: { listeners: { "document:keydown": "onGlobalKey($event)" } }, viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
8045
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctCommandPalette, isStandalone: true, selector: "strct-command-palette", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, hotkey: { classPropertyName: "hotkey", publicName: "hotkey", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, maxResults: { classPropertyName: "maxResults", publicName: "maxResults", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, sectionOrder: { classPropertyName: "sectionOrder", publicName: "sectionOrder", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", picked: "picked", query: "queryChange" }, host: { listeners: { "document:keydown": "onGlobalKey($event)" } }, viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
7457
8046
|
@if (open()) {
|
|
7458
8047
|
<!-- Backdrop: pointer-only dismiss (Escape covers keyboard). -->
|
|
7459
8048
|
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
@@ -7489,34 +8078,61 @@ class StrctCommandPalette {
|
|
|
7489
8078
|
<span class="strct-cmdp__spin" aria-hidden="true"></span>{{ loadingText() }}
|
|
7490
8079
|
</div>
|
|
7491
8080
|
}
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
[
|
|
7500
|
-
tabindex="-1"
|
|
7501
|
-
(click)="pick(item)"
|
|
7502
|
-
(mousemove)="activeIndex.set(i)"
|
|
8081
|
+
<!-- A section is a group with a heading, not an option: the
|
|
8082
|
+
heading is never active and never picked, and the arrows
|
|
8083
|
+
move through every option across every section. -->
|
|
8084
|
+
@for (section of sections(); track section.id) {
|
|
8085
|
+
<div
|
|
8086
|
+
class="strct-cmdp__sec"
|
|
8087
|
+
role="group"
|
|
8088
|
+
[attr.aria-labelledby]="section.label ? section.id : null"
|
|
7503
8089
|
>
|
|
7504
|
-
@if (
|
|
7505
|
-
<strct-
|
|
7506
|
-
class="strct-cmdp__icon"
|
|
7507
|
-
[name]="item.icon"
|
|
7508
|
-
[size]="16"
|
|
7509
|
-
[strokeWidth]="1.4"
|
|
7510
|
-
/>
|
|
8090
|
+
@if (section.label) {
|
|
8091
|
+
<div class="strct-cmdp__section" [id]="section.id">{{ section.label }}</div>
|
|
7511
8092
|
}
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
8093
|
+
@for (row of section.rows; track row.item.id) {
|
|
8094
|
+
<button
|
|
8095
|
+
type="button"
|
|
8096
|
+
class="strct-cmdp__item"
|
|
8097
|
+
role="option"
|
|
8098
|
+
[id]="optionId(row.index)"
|
|
8099
|
+
[attr.aria-selected]="row.index === activeIndex()"
|
|
8100
|
+
[class.is-active]="row.index === activeIndex()"
|
|
8101
|
+
tabindex="-1"
|
|
8102
|
+
(click)="pick(row.item)"
|
|
8103
|
+
(mousemove)="activeIndex.set(row.index)"
|
|
8104
|
+
>
|
|
8105
|
+
@if (row.item.icon) {
|
|
8106
|
+
<strct-icon
|
|
8107
|
+
class="strct-cmdp__icon"
|
|
8108
|
+
[name]="row.item.icon"
|
|
8109
|
+
[size]="16"
|
|
8110
|
+
[strokeWidth]="1.4"
|
|
8111
|
+
/>
|
|
8112
|
+
}
|
|
8113
|
+
<span class="strct-cmdp__text">
|
|
8114
|
+
<span class="strct-cmdp__label">{{ row.item.label }}</span>
|
|
8115
|
+
@if (row.item.description) {
|
|
8116
|
+
<span class="strct-cmdp__desc">{{ row.item.description }}</span>
|
|
8117
|
+
}
|
|
8118
|
+
</span>
|
|
8119
|
+
@if (row.item.hint) {
|
|
8120
|
+
<strct-kbd>{{ row.item.hint }}</strct-kbd>
|
|
8121
|
+
}
|
|
8122
|
+
@if (row.item.badge; as badge) {
|
|
8123
|
+
<strct-badge
|
|
8124
|
+
class="strct-cmdp__badge"
|
|
8125
|
+
[status]="badge.status ?? 'neutral'"
|
|
8126
|
+
[tone]="badge.tone ?? ''"
|
|
8127
|
+
>{{ badge.label }}</strct-badge
|
|
8128
|
+
>
|
|
8129
|
+
}
|
|
8130
|
+
@if (row.item.group) {
|
|
8131
|
+
<span class="strct-cmdp__group">{{ row.item.group }}</span>
|
|
8132
|
+
}
|
|
8133
|
+
</button>
|
|
7518
8134
|
}
|
|
7519
|
-
</
|
|
8135
|
+
</div>
|
|
7520
8136
|
} @empty {
|
|
7521
8137
|
@if (!loading()) {
|
|
7522
8138
|
<div class="strct-cmdp__empty">{{ emptyText() }} “{{ query() }}”</div>
|
|
@@ -7526,11 +8142,11 @@ class StrctCommandPalette {
|
|
|
7526
8142
|
</div>
|
|
7527
8143
|
</div>
|
|
7528
8144
|
}
|
|
7529
|
-
`, isInline: true, styles: [".strct-cmdp__backdrop{position:fixed;inset:0;z-index:var(--z-toast);display:flex;justify-content:center;align-items:flex-start;padding:14vh 16px 16px;background:var(--backdrop);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-cmdp-fade .12s ease}.strct-cmdp{width:100%;max-width:560px;max-height:60vh;display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-floating);overflow:hidden;animation:strct-cmdp-rise .14s ease}.strct-cmdp__search{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--b1);color:var(--t3)}.strct-cmdp__input{flex:1;min-width:0;border:0;background:transparent;color:var(--t1);font-size:15px;font-family:var(--font);outline:none}.strct-cmdp__list{overflow-y:auto;padding:6px}.strct-cmdp__item{display:flex;align-items:center;gap:11px;width:100%;padding:9px 11px;border:0;border-radius:var(--radius-md);background:transparent;cursor:pointer;text-align:start;font-family:var(--font)}.strct-cmdp__item.is-active{background:var(--acc-m)}.strct-cmdp__icon{color:var(--t2);flex-shrink:0}.strct-cmdp__item.is-active .strct-cmdp__icon{color:var(--acc)}.strct-cmdp__label{flex:1;min-width:0;font-size:var(--text-lg);color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-cmdp__group{font-size:var(--text-sm);color:var(--t3);flex-
|
|
8145
|
+
`, isInline: true, styles: [".strct-cmdp__backdrop{position:fixed;inset:0;z-index:var(--z-toast);display:flex;justify-content:center;align-items:flex-start;padding:14vh 16px 16px;background:var(--backdrop);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-cmdp-fade .12s ease}.strct-cmdp{width:100%;max-width:560px;max-height:60vh;display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-floating);overflow:hidden;animation:strct-cmdp-rise .14s ease}.strct-cmdp__search{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--b1);color:var(--t3)}.strct-cmdp__input{flex:1;min-width:0;border:0;background:transparent;color:var(--t1);font-size:15px;font-family:var(--font);outline:none}.strct-cmdp__list{overflow-y:auto;padding:6px}.strct-cmdp__item{display:flex;align-items:center;gap:11px;width:100%;padding:9px 11px;border:0;border-radius:var(--radius-md);background:transparent;cursor:pointer;text-align:start;font-family:var(--font)}.strct-cmdp__item.is-active{background:var(--acc-m)}.strct-cmdp__icon{color:var(--t2);flex-shrink:0}.strct-cmdp__item.is-active .strct-cmdp__icon{color:var(--acc)}.strct-cmdp__label{flex:1;min-width:0;font-size:var(--text-lg);color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-cmdp__group{font-size:var(--text-sm);color:var(--t3);flex:0 20 auto;min-width:0;max-width:55%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-cmdp__text{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}.strct-cmdp__desc{font-size:var(--text-sm);color:var(--t3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-cmdp__badge{flex:none}.strct-cmdp__section{padding:8px 12px 4px;font-size:var(--text-xs, 11px);font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--t3)}.strct-cmdp__loading{display:flex;align-items:center;gap:9px;padding:10px 12px;font-size:var(--text-md);color:var(--t3)}.strct-cmdp__spin{width:13px;height:13px;border:2px solid var(--acc30);border-top-color:var(--acc);border-radius:50%;flex-shrink:0}@media(prefers-reduced-motion:no-preference){.strct-cmdp__spin{animation:strct-cmdp-spin .8s linear infinite}}@keyframes strct-cmdp-spin{to{transform:rotate(360deg)}}.strct-cmdp__empty{padding:20px;text-align:center;font-size:var(--text-md);color:var(--t3)}@keyframes strct-cmdp-fade{0%{opacity:0}}@keyframes strct-cmdp-rise{0%{opacity:0;transform:translateY(-8px)}}@media(prefers-reduced-motion:reduce){.strct-cmdp__backdrop,.strct-cmdp{animation:none}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }, { kind: "component", type: StrctKbd, selector: "strct-kbd" }, { kind: "component", type: StrctBadge, selector: "strct-badge", inputs: ["status", "tone", "solid"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
7530
8146
|
}
|
|
7531
8147
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctCommandPalette, decorators: [{
|
|
7532
8148
|
type: Component,
|
|
7533
|
-
args: [{ selector: 'strct-command-palette', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon, StrctKbd], host: { '(document:keydown)': 'onGlobalKey($event)' }, template: `
|
|
8149
|
+
args: [{ selector: 'strct-command-palette', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon, StrctKbd, StrctBadge], host: { '(document:keydown)': 'onGlobalKey($event)' }, template: `
|
|
7534
8150
|
@if (open()) {
|
|
7535
8151
|
<!-- Backdrop: pointer-only dismiss (Escape covers keyboard). -->
|
|
7536
8152
|
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
@@ -7566,34 +8182,61 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
7566
8182
|
<span class="strct-cmdp__spin" aria-hidden="true"></span>{{ loadingText() }}
|
|
7567
8183
|
</div>
|
|
7568
8184
|
}
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
[
|
|
7577
|
-
tabindex="-1"
|
|
7578
|
-
(click)="pick(item)"
|
|
7579
|
-
(mousemove)="activeIndex.set(i)"
|
|
8185
|
+
<!-- A section is a group with a heading, not an option: the
|
|
8186
|
+
heading is never active and never picked, and the arrows
|
|
8187
|
+
move through every option across every section. -->
|
|
8188
|
+
@for (section of sections(); track section.id) {
|
|
8189
|
+
<div
|
|
8190
|
+
class="strct-cmdp__sec"
|
|
8191
|
+
role="group"
|
|
8192
|
+
[attr.aria-labelledby]="section.label ? section.id : null"
|
|
7580
8193
|
>
|
|
7581
|
-
@if (
|
|
7582
|
-
<strct-
|
|
7583
|
-
class="strct-cmdp__icon"
|
|
7584
|
-
[name]="item.icon"
|
|
7585
|
-
[size]="16"
|
|
7586
|
-
[strokeWidth]="1.4"
|
|
7587
|
-
/>
|
|
7588
|
-
}
|
|
7589
|
-
<span class="strct-cmdp__label">{{ item.label }}</span>
|
|
7590
|
-
@if (item.hint) {
|
|
7591
|
-
<strct-kbd>{{ item.hint }}</strct-kbd>
|
|
8194
|
+
@if (section.label) {
|
|
8195
|
+
<div class="strct-cmdp__section" [id]="section.id">{{ section.label }}</div>
|
|
7592
8196
|
}
|
|
7593
|
-
@
|
|
7594
|
-
<
|
|
8197
|
+
@for (row of section.rows; track row.item.id) {
|
|
8198
|
+
<button
|
|
8199
|
+
type="button"
|
|
8200
|
+
class="strct-cmdp__item"
|
|
8201
|
+
role="option"
|
|
8202
|
+
[id]="optionId(row.index)"
|
|
8203
|
+
[attr.aria-selected]="row.index === activeIndex()"
|
|
8204
|
+
[class.is-active]="row.index === activeIndex()"
|
|
8205
|
+
tabindex="-1"
|
|
8206
|
+
(click)="pick(row.item)"
|
|
8207
|
+
(mousemove)="activeIndex.set(row.index)"
|
|
8208
|
+
>
|
|
8209
|
+
@if (row.item.icon) {
|
|
8210
|
+
<strct-icon
|
|
8211
|
+
class="strct-cmdp__icon"
|
|
8212
|
+
[name]="row.item.icon"
|
|
8213
|
+
[size]="16"
|
|
8214
|
+
[strokeWidth]="1.4"
|
|
8215
|
+
/>
|
|
8216
|
+
}
|
|
8217
|
+
<span class="strct-cmdp__text">
|
|
8218
|
+
<span class="strct-cmdp__label">{{ row.item.label }}</span>
|
|
8219
|
+
@if (row.item.description) {
|
|
8220
|
+
<span class="strct-cmdp__desc">{{ row.item.description }}</span>
|
|
8221
|
+
}
|
|
8222
|
+
</span>
|
|
8223
|
+
@if (row.item.hint) {
|
|
8224
|
+
<strct-kbd>{{ row.item.hint }}</strct-kbd>
|
|
8225
|
+
}
|
|
8226
|
+
@if (row.item.badge; as badge) {
|
|
8227
|
+
<strct-badge
|
|
8228
|
+
class="strct-cmdp__badge"
|
|
8229
|
+
[status]="badge.status ?? 'neutral'"
|
|
8230
|
+
[tone]="badge.tone ?? ''"
|
|
8231
|
+
>{{ badge.label }}</strct-badge
|
|
8232
|
+
>
|
|
8233
|
+
}
|
|
8234
|
+
@if (row.item.group) {
|
|
8235
|
+
<span class="strct-cmdp__group">{{ row.item.group }}</span>
|
|
8236
|
+
}
|
|
8237
|
+
</button>
|
|
7595
8238
|
}
|
|
7596
|
-
</
|
|
8239
|
+
</div>
|
|
7597
8240
|
} @empty {
|
|
7598
8241
|
@if (!loading()) {
|
|
7599
8242
|
<div class="strct-cmdp__empty">{{ emptyText() }} “{{ query() }}”</div>
|
|
@@ -7603,8 +8246,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
7603
8246
|
</div>
|
|
7604
8247
|
</div>
|
|
7605
8248
|
}
|
|
7606
|
-
`, styles: [".strct-cmdp__backdrop{position:fixed;inset:0;z-index:var(--z-toast);display:flex;justify-content:center;align-items:flex-start;padding:14vh 16px 16px;background:var(--backdrop);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-cmdp-fade .12s ease}.strct-cmdp{width:100%;max-width:560px;max-height:60vh;display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-floating);overflow:hidden;animation:strct-cmdp-rise .14s ease}.strct-cmdp__search{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--b1);color:var(--t3)}.strct-cmdp__input{flex:1;min-width:0;border:0;background:transparent;color:var(--t1);font-size:15px;font-family:var(--font);outline:none}.strct-cmdp__list{overflow-y:auto;padding:6px}.strct-cmdp__item{display:flex;align-items:center;gap:11px;width:100%;padding:9px 11px;border:0;border-radius:var(--radius-md);background:transparent;cursor:pointer;text-align:start;font-family:var(--font)}.strct-cmdp__item.is-active{background:var(--acc-m)}.strct-cmdp__icon{color:var(--t2);flex-shrink:0}.strct-cmdp__item.is-active .strct-cmdp__icon{color:var(--acc)}.strct-cmdp__label{flex:1;min-width:0;font-size:var(--text-lg);color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-cmdp__group{font-size:var(--text-sm);color:var(--t3);flex-
|
|
7607
|
-
}], ctorParameters: () => [], propDecorators: { inputRef: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], hotkey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hotkey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], maxResults: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxResults", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], picked: [{ type: i0.Output, args: ["picked"] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }] } });
|
|
8249
|
+
`, styles: [".strct-cmdp__backdrop{position:fixed;inset:0;z-index:var(--z-toast);display:flex;justify-content:center;align-items:flex-start;padding:14vh 16px 16px;background:var(--backdrop);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-cmdp-fade .12s ease}.strct-cmdp{width:100%;max-width:560px;max-height:60vh;display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-floating);overflow:hidden;animation:strct-cmdp-rise .14s ease}.strct-cmdp__search{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--b1);color:var(--t3)}.strct-cmdp__input{flex:1;min-width:0;border:0;background:transparent;color:var(--t1);font-size:15px;font-family:var(--font);outline:none}.strct-cmdp__list{overflow-y:auto;padding:6px}.strct-cmdp__item{display:flex;align-items:center;gap:11px;width:100%;padding:9px 11px;border:0;border-radius:var(--radius-md);background:transparent;cursor:pointer;text-align:start;font-family:var(--font)}.strct-cmdp__item.is-active{background:var(--acc-m)}.strct-cmdp__icon{color:var(--t2);flex-shrink:0}.strct-cmdp__item.is-active .strct-cmdp__icon{color:var(--acc)}.strct-cmdp__label{flex:1;min-width:0;font-size:var(--text-lg);color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-cmdp__group{font-size:var(--text-sm);color:var(--t3);flex:0 20 auto;min-width:0;max-width:55%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-cmdp__text{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}.strct-cmdp__desc{font-size:var(--text-sm);color:var(--t3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-cmdp__badge{flex:none}.strct-cmdp__section{padding:8px 12px 4px;font-size:var(--text-xs, 11px);font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--t3)}.strct-cmdp__loading{display:flex;align-items:center;gap:9px;padding:10px 12px;font-size:var(--text-md);color:var(--t3)}.strct-cmdp__spin{width:13px;height:13px;border:2px solid var(--acc30);border-top-color:var(--acc);border-radius:50%;flex-shrink:0}@media(prefers-reduced-motion:no-preference){.strct-cmdp__spin{animation:strct-cmdp-spin .8s linear infinite}}@keyframes strct-cmdp-spin{to{transform:rotate(360deg)}}.strct-cmdp__empty{padding:20px;text-align:center;font-size:var(--text-md);color:var(--t3)}@keyframes strct-cmdp-fade{0%{opacity:0}}@keyframes strct-cmdp-rise{0%{opacity:0;transform:translateY(-8px)}}@media(prefers-reduced-motion:reduce){.strct-cmdp__backdrop,.strct-cmdp{animation:none}}\n"] }]
|
|
8250
|
+
}], ctorParameters: () => [], propDecorators: { inputRef: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], hotkey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hotkey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], maxResults: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxResults", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], sectionOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionOrder", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], picked: [{ type: i0.Output, args: ["picked"] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }] } });
|
|
7608
8251
|
|
|
7609
8252
|
/**
|
|
7610
8253
|
* Compact search pill — the pattern from the docs header: a leading search
|
|
@@ -29449,5 +30092,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
29449
30092
|
* Generated bundle index. Do not edit.
|
|
29450
30093
|
*/
|
|
29451
30094
|
|
|
29452
|
-
export { FOCUSABLE_SELECTOR, STRCT_DP_DOW, STRCT_DP_DOW_FULL, STRCT_DP_MONTHS, STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_ICON_NAMES, STRCT_MASKS, STRCT_MD_LIMITS, STRCT_NOTIFICATION_HISTORY_LIMIT, STRCT_PALETTES, STRCT_RAW_ICONS, STRCT_TIME_RANGE_PRESETS, STRCT_WIZARD_DEFAULTS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAlertActions, StrctAnnouncer, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctBytesPipe, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCardHeaderActions, StrctCardHeaderLeading, StrctCardHeaderMeta, StrctCardHeaderNote, StrctCascadeColumn, StrctCascadeHost, StrctCascadeSelect, StrctCellDef, StrctCellStatus, StrctChange, StrctChart, StrctChatAttachment, StrctChatComposer, StrctChatMessage, StrctChatMessageActions, StrctChatThread, StrctCheckbox, StrctChips, StrctCode, StrctCodeInline, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctConfirmOutlet, StrctConfirmService, StrctContextMenu, StrctContextMenuTrigger, StrctControlDescription, StrctCopy, StrctDatagrid, StrctDatagridActionBar, StrctDatagridActionBarCaption, StrctDatepicker, StrctDatetimePicker, StrctDesc, StrctDescriptionList, StrctDetailPaneActions, StrctDetailPaneMeta, StrctDetailPaneTitle, StrctDiff, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctDropdownItemAction, StrctDropdownTrigger, StrctDurationPipe, StrctEmptyState, StrctField, StrctFieldGroup, StrctFieldHint, StrctFieldPrefix, StrctFieldSuffix, StrctFieldValue, StrctFile, StrctFilterBar, StrctFlow, StrctFlowNodeTemplate, StrctFooter, StrctGauge, StrctGroupHeaderDef, StrctHeader, StrctHeatmap, StrctHero, StrctHotkeysHelp, StrctHotkeysService, StrctIcon, StrctInlineEdit, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctLegend, StrctList, StrctListItem, StrctListItemDescription, StrctListItemLeading, StrctListItemMeta, StrctListItemTrailing, StrctLiveIndicator, StrctLogViewer, StrctLogin, StrctMarkdown, StrctMarkdownToken, StrctMediaFrame, StrctMenuPanel, StrctMenuService, StrctMenubar, StrctMetricTile, StrctModal, StrctModalContent, StrctNav, StrctNavItem, StrctNotificationCenter, StrctNumber, StrctOverlay, StrctPageHeader, StrctPageHeaderActions, StrctPageHeaderCrumbs, StrctPageHeaderTitleMeta, StrctPagination, StrctPassword, StrctPopover, StrctPopoverTrigger, StrctProgress, StrctQr, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRatePipe, StrctRating, StrctReorder, StrctReorderGroup, StrctReorderHandle, StrctReorderItem, StrctResizeHandle, StrctRowDetailDef, StrctSearchbox, StrctSectionHeader, StrctSectionHeaderActions, StrctSectionHeaderMeta, StrctSectionMenu, StrctSegmented, StrctSelect, StrctShell, StrctShellService, StrctSiPipe, StrctSignpost, StrctSignpostTrigger, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctSplitButton, StrctSplitter, StrctStack, StrctStackItem, StrctStatusDot, StrctStep, StrctStepAction, StrctSteps, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctTagLeading, StrctThemeService, StrctThemeSwitcher, StrctTimeRangePicker, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctToolbar, StrctToolbarCaption, StrctToolbarSpacer, StrctTooltip, StrctTour, StrctTransfer, StrctTree, StrctTreeNode, StrctTreeSelect, StrctVerticalNav, StrctWatermark, StrctWindow, StrctWindowDock, StrctWindowDockItem, StrctWindowService, StrctWizard, StrctWizardAside, __qrInternals, focusFirstIn, keepTabInside, lockBodyScroll, parseAnsi, parseInlines, provideStrctWizardDefaults, registerStrctIcon, restoreFocus, saveFocusedElement, strctCheckHostDisplay, strctCheckHostInputs, strctClassifyHref, strctComputeDiff, strctDpPad, strctFormatBytes, strctFormatDuration, strctFormatRate, strctFormatSi, strctParseMarkdown, strctQrMatrix, strctValidationIcon, strctValidationTone, unlockBodyScroll };
|
|
30095
|
+
export { FOCUSABLE_SELECTOR, STRCT_DP_DOW, STRCT_DP_DOW_FULL, STRCT_DP_MONTHS, STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_ICON_NAMES, STRCT_MASKS, STRCT_MD_LIMITS, STRCT_NOTIFICATION_HISTORY_LIMIT, STRCT_PALETTES, STRCT_RAW_ICONS, STRCT_TIME_RANGE_PRESETS, STRCT_WIZARD_DEFAULTS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAlertActions, StrctAnnouncer, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctBytesPipe, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCardHeaderActions, StrctCardHeaderLeading, StrctCardHeaderMeta, StrctCardHeaderNote, StrctCascadeColumn, StrctCascadeHost, StrctCascadeSelect, StrctCellDef, StrctCellStatus, StrctChange, StrctChart, StrctChatAttachment, StrctChatComposer, StrctChatMessage, StrctChatMessageActions, StrctChatThread, StrctCheckbox, StrctChips, StrctCode, StrctCodeInline, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctConfirmOutlet, StrctConfirmService, StrctContextMenu, StrctContextMenuTrigger, StrctControlDescription, StrctCopy, StrctDatagrid, StrctDatagridActionBar, StrctDatagridActionBarCaption, StrctDatepicker, StrctDatetimePicker, StrctDesc, StrctDescriptionList, StrctDetailPaneActions, StrctDetailPaneMeta, StrctDetailPaneTitle, StrctDiff, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownGroup, StrctDropdownItem, StrctDropdownItemAction, StrctDropdownItemDescription, StrctDropdownItemIcon, StrctDropdownItemLabel, StrctDropdownTrigger, StrctDurationPipe, StrctEmptyState, StrctField, StrctFieldGroup, StrctFieldHint, StrctFieldPrefix, StrctFieldSuffix, StrctFieldValue, StrctFile, StrctFilterBar, StrctFlow, StrctFlowNodeTemplate, StrctFooter, StrctGauge, StrctGroupHeaderDef, StrctHeader, StrctHeaderAction, StrctHeatmap, StrctHero, StrctHotkeysHelp, StrctHotkeysService, StrctIcon, StrctInlineEdit, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctLegend, StrctList, StrctListItem, StrctListItemDescription, StrctListItemLeading, StrctListItemMeta, StrctListItemTrailing, StrctLiveIndicator, StrctLogViewer, StrctLogin, StrctMarkdown, StrctMarkdownToken, StrctMediaFrame, StrctMenuPanel, StrctMenuService, StrctMenubar, StrctMetricTile, StrctModal, StrctModalContent, StrctNav, StrctNavItem, StrctNotificationCenter, StrctNumber, StrctOverlay, StrctPageHeader, StrctPageHeaderActions, StrctPageHeaderCrumbs, StrctPageHeaderTitleMeta, StrctPagination, StrctPassword, StrctPopover, StrctPopoverTrigger, StrctProgress, StrctQr, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRatePipe, StrctRating, StrctReorder, StrctReorderGroup, StrctReorderHandle, StrctReorderItem, StrctResizeHandle, StrctRowDetailDef, StrctSearchbox, StrctSectionHeader, StrctSectionHeaderActions, StrctSectionHeaderMeta, StrctSectionMenu, StrctSegmented, StrctSelect, StrctShell, StrctShellService, StrctSiPipe, StrctSignpost, StrctSignpostTrigger, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctSplitButton, StrctSplitter, StrctStack, StrctStackItem, StrctStatusDot, StrctStep, StrctStepAction, StrctSteps, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctTagLeading, StrctThemeService, StrctThemeSwitcher, StrctTimeRangePicker, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctToolbar, StrctToolbarCaption, StrctToolbarSpacer, StrctTooltip, StrctTour, StrctTransfer, StrctTree, StrctTreeNode, StrctTreeSelect, StrctVerticalNav, StrctWatermark, StrctWindow, StrctWindowDock, StrctWindowDockItem, StrctWindowService, StrctWizard, StrctWizardAside, __qrInternals, focusFirstIn, keepTabInside, lockBodyScroll, parseAnsi, parseInlines, provideStrctWizardDefaults, registerStrctIcon, restoreFocus, saveFocusedElement, strctCheckHostDisplay, strctCheckHostInputs, strctClassifyHref, strctComputeDiff, strctDpPad, strctFormatBytes, strctFormatDuration, strctFormatRate, strctFormatSi, strctParseMarkdown, strctQrMatrix, strctValidationIcon, strctValidationTone, unlockBodyScroll };
|
|
29453
30096
|
//# sourceMappingURL=akcelik-strct.mjs.map
|