@akcelik/strct 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/fesm2022/akcelik-strct.mjs +822 -92
- package/fesm2022/akcelik-strct.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/_fonts.scss +49 -0
- package/styles/fonts/dm-sans-400.woff2 +0 -0
- package/styles/fonts/dm-sans-500.woff2 +0 -0
- package/styles/fonts/dm-sans-600.woff2 +0 -0
- package/styles/fonts/dm-sans-700.woff2 +0 -0
- package/styles/fonts/jetbrains-mono-400.woff2 +0 -0
- package/styles/fonts/jetbrains-mono-500.woff2 +0 -0
- package/styles/theme.scss +1 -0
- package/types/akcelik-strct.d.ts +229 -18
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, DOCUMENT, signal, computed, Injectable, input, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgZone, afterNextRender, Directive, booleanAttribute, output, HostListener, model, contentChildren, effect, forwardRef, TemplateRef, contentChild, Renderer2 } from '@angular/core';
|
|
2
|
+
import { inject, DOCUMENT, signal, computed, Injectable, input, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgZone, afterNextRender, Directive, booleanAttribute, output, HostListener, model, contentChildren, effect, DestroyRef, ApplicationRef, EnvironmentInjector, createComponent, forwardRef, TemplateRef, contentChild, Renderer2 } from '@angular/core';
|
|
3
3
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
4
|
+
import { DOCUMENT as DOCUMENT$1, NgTemplateOutlet } from '@angular/common';
|
|
4
5
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
5
|
-
import { NgTemplateOutlet } from '@angular/common';
|
|
6
6
|
|
|
7
7
|
/** All palettes the token system ships with, in display order. */
|
|
8
8
|
const STRCT_PALETTES = [
|
|
@@ -124,6 +124,9 @@ const STRCT_ICONS = {
|
|
|
124
124
|
code: '<path d="M5.5 5L2.5 8l3 3M10.5 5l3 3-3 3M9 3.5l-2 9"/>',
|
|
125
125
|
book: '<path d="M3 3.2h6a1.5 1.5 0 0 1 1.5 1.5v8.1H4.5A1.5 1.5 0 0 1 3 11.3z"/><path d="M13 3.2H9.5A1.5 1.5 0 0 0 8 4.7v8.1h5z"/>',
|
|
126
126
|
terminal: '<rect x="2" y="3" width="12" height="10" rx="1.5"/><path d="M4.8 6.5L7 8.2 4.8 9.9M8.2 10.2h3"/>',
|
|
127
|
+
folder: '<path d="M2 4.6a1 1 0 0 1 1-1h3.1l1.3 1.6H13a1 1 0 0 1 1 1V12a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1z"/>',
|
|
128
|
+
template: '<path d="M4.3 2.5h4.6l3.1 3.1V13a.5.5 0 0 1-.5.5H4.3a.5.5 0 0 1-.5-.5V3a.5.5 0 0 1 .5-.5z"/><path d="M8.7 2.6v3.1h3.1"/><path d="M5.8 9h4.2M5.8 11h2.6"/>',
|
|
129
|
+
tag: '<path d="M2.6 7.7V3.2a.6.6 0 0 1 .6-.6h4.5l5.6 5.6a1 1 0 0 1 0 1.4l-3.5 3.5a1 1 0 0 1-1.4 0z"/><circle cx="5.4" cy="5.4" r=".9" fill="currentColor" stroke="none"/>',
|
|
127
130
|
// ── Datacenter / infrastructure ─────────────────────────────
|
|
128
131
|
datacenter: '<rect x="2.5" y="2.5" width="11" height="11" rx="1"/><path d="M5 5h6M5 7.3h6M5 9.6h3.5"/><circle cx="11" cy="9.7" r=".5" fill="currentColor" stroke="none"/>',
|
|
129
132
|
rack: '<rect x="3.5" y="2" width="9" height="12" rx="1"/><path d="M3.5 5.2h9M3.5 8.4h9M3.5 11.6h9"/><circle cx="5.4" cy="3.6" r=".4" fill="currentColor" stroke="none"/><circle cx="5.4" cy="6.8" r=".4" fill="currentColor" stroke="none"/><circle cx="5.4" cy="10" r=".4" fill="currentColor" stroke="none"/>',
|
|
@@ -144,6 +147,10 @@ const STRCT_ICONS = {
|
|
|
144
147
|
hba: '<rect x="2.3" y="3.4" width="11.4" height="6.4" rx="1"/><circle cx="5.1" cy="6.6" r="1.2"/><circle cx="8.1" cy="6.6" r="1.2"/><path d="M10.6 5.6h1.6M10.6 7.6h1.6M5 9.8v2.6M11 9.8v2.6"/>',
|
|
145
148
|
// RJ45 ethernet port (switch / server) — keyed jack with contact pins.
|
|
146
149
|
ethernet: '<rect x="2.5" y="4" width="11" height="7.4" rx="1"/><path d="M6 4V2.6h4V4"/><path d="M5.3 7.6v2M7.1 7.6v2M8.9 7.6v2M10.7 7.6v2"/>',
|
|
150
|
+
// Resource pool — a proportional allocation (pie with two radii).
|
|
151
|
+
resourcePool: '<circle cx="8" cy="8" r="5.6"/><path d="M8 8V2.4M8 8l4.9 2.7"/>',
|
|
152
|
+
// Port group — a grouped set of switch ports under a shared rail.
|
|
153
|
+
portGroup: '<path d="M1.8 4.4h12.4"/><rect x="2.2" y="6.2" width="3.4" height="4" rx=".5"/><rect x="6.3" y="6.2" width="3.4" height="4" rx=".5"/><rect x="10.4" y="6.2" width="3.4" height="4" rx=".5"/><path d="M3.9 10.2v1.6M8 10.2v1.6M12.1 10.2v1.6"/>',
|
|
147
154
|
// ── Accessibility (original glyphs for generic concepts) ─────
|
|
148
155
|
universalAccess: '<circle cx="8" cy="8" r="6"/><circle cx="8" cy="4.9" r=".9" fill="currentColor" stroke="none"/><path d="M4.9 6.3c2 .8 4.2 .8 6.2 0M8 6.4v3.1M6.3 11.9 8 9.4l1.7 2.5"/>',
|
|
149
156
|
wheelchair: '<circle cx="6.5" cy="3.1" r="1.3"/><path d="M6.5 4.5v3.3h3.1l1.7 3.4"/><circle cx="6.7" cy="11.3" r="2.6"/><path d="M9.3 11.3h2.1l-.5 1.6"/>',
|
|
@@ -181,7 +188,7 @@ const STRCT_ICON_GROUPS = [
|
|
|
181
188
|
'hexagon', 'search', 'menu', 'ellipsis', 'dots', 'close', 'check', 'calendar',
|
|
182
189
|
'eye', 'eyeOff', 'upload', 'download', 'sun', 'moon', 'bell', 'heart', 'layers',
|
|
183
190
|
'grid', 'form', 'chart', 'bars', 'gauge', 'palette', 'sidebar', 'compass',
|
|
184
|
-
'copy', 'code', 'book', 'terminal',
|
|
191
|
+
'copy', 'code', 'book', 'terminal', 'folder', 'template', 'tag',
|
|
185
192
|
],
|
|
186
193
|
},
|
|
187
194
|
{
|
|
@@ -201,6 +208,7 @@ const STRCT_ICON_GROUPS = [
|
|
|
201
208
|
names: [
|
|
202
209
|
'datacenter', 'rack', 'cluster', 'host', 'vm', 'switch', 'storage',
|
|
203
210
|
'network', 'cpu', 'memory', 'disk', 'port', 'nic', 'hba', 'ethernet', 'power',
|
|
211
|
+
'resourcePool', 'portGroup',
|
|
204
212
|
],
|
|
205
213
|
},
|
|
206
214
|
{
|
|
@@ -436,10 +444,16 @@ class StrctOverlay {
|
|
|
436
444
|
let left;
|
|
437
445
|
if (p === 'right') {
|
|
438
446
|
left = a.right + gap;
|
|
447
|
+
// Flip to the left of the anchor if it would overflow the right edge.
|
|
448
|
+
if (left + w > vw - margin && a.left - gap - w > margin)
|
|
449
|
+
left = a.left - gap - w;
|
|
439
450
|
top = a.top;
|
|
440
451
|
}
|
|
441
452
|
else if (p === 'left') {
|
|
442
453
|
left = a.left - gap - w;
|
|
454
|
+
// Flip to the right of the anchor if it would overflow the left edge.
|
|
455
|
+
if (left < margin && a.right + gap + w < vw - margin)
|
|
456
|
+
left = a.right + gap;
|
|
443
457
|
top = a.top;
|
|
444
458
|
}
|
|
445
459
|
else {
|
|
@@ -1116,45 +1130,72 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
1116
1130
|
`, host: { class: 'strct-tabs' }, styles: [".strct-tabs{display:block}.strct-tabs__bar{display:flex;gap:2px;border-bottom:1px solid var(--b2)}.strct-tabs__btn{appearance:none;border:0;background:transparent;cursor:pointer;font-family:var(--font);font-size:13px;font-weight:500;color:var(--t2);padding:9px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .14s ease,border-color .14s ease}.strct-tabs__btn:hover{color:var(--t1)}.strct-tabs__btn--active{color:var(--acc);border-bottom-color:var(--acc)}.strct-tabs__btn:disabled{color:var(--t4);cursor:not-allowed}.strct-tabs__panels{padding-top:16px}\n"] }]
|
|
1117
1131
|
}], ctorParameters: () => [], propDecorators: { tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctTab), { isSignal: true }] }] } });
|
|
1118
1132
|
|
|
1119
|
-
/** Root container for a tree of `<strct-tree-node>` items. */
|
|
1120
|
-
class StrctTree {
|
|
1121
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTree, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1122
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.16", type: StrctTree, isStandalone: true, selector: "strct-tree", host: { attributes: { "role": "tree" }, classAttribute: "strct-tree" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [".strct-tree{display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1123
|
-
}
|
|
1124
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTree, decorators: [{
|
|
1125
|
-
type: Component,
|
|
1126
|
-
args: [{ selector: 'strct-tree', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `<ng-content />`, host: { class: 'strct-tree', role: 'tree' }, styles: [".strct-tree{display:block}\n"] }]
|
|
1127
|
-
}] });
|
|
1128
1133
|
/**
|
|
1129
|
-
* Tree node.
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1134
|
+
* Tree node. Two modes:
|
|
1135
|
+
* - **Content:** nest `<strct-tree-node>` children manually.
|
|
1136
|
+
* - **Data:** pass a `[node]` object that recurses over its `children` —
|
|
1137
|
+
* used internally by `<strct-tree [nodes]>`.
|
|
1138
|
+
*
|
|
1139
|
+
* <strct-tree-node label="Group" icon="layers" badge="ok" [(expanded)]="open">
|
|
1140
|
+
* <strct-tree-node label="Leaf" icon="vm" [active]="true" />
|
|
1132
1141
|
* </strct-tree-node>
|
|
1133
1142
|
*/
|
|
1134
1143
|
class StrctTreeNode {
|
|
1135
|
-
|
|
1144
|
+
/** Data-driven node; when set, label/icon/children come from it. */
|
|
1145
|
+
node = input(null, ...(ngDevMode ? [{ debugName: "node" }] : /* istanbul ignore next */ []));
|
|
1146
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1136
1147
|
icon = input(undefined, ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
1148
|
+
badge = input('none', ...(ngDevMode ? [{ debugName: "badge" }] : /* istanbul ignore next */ []));
|
|
1137
1149
|
active = input(false, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
1138
1150
|
expanded = model(false, ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
1151
|
+
/** Content-mode click. */
|
|
1139
1152
|
activated = output();
|
|
1153
|
+
/** Data-mode click — carries the activated node (bubbles to the tree). */
|
|
1154
|
+
nodeActivated = output();
|
|
1140
1155
|
childNodes = contentChildren(StrctTreeNode, ...(ngDevMode ? [{ debugName: "childNodes" }] : /* istanbul ignore next */ []));
|
|
1141
|
-
|
|
1156
|
+
/** Data-mode expansion (seeded from node.expanded on first toggle). */
|
|
1157
|
+
dataExpanded = signal(null, ...(ngDevMode ? [{ debugName: "dataExpanded" }] : /* istanbul ignore next */ []));
|
|
1158
|
+
displayLabel = computed(() => this.node()?.label ?? this.label(), ...(ngDevMode ? [{ debugName: "displayLabel" }] : /* istanbul ignore next */ []));
|
|
1159
|
+
displayIcon = computed(() => this.node()?.icon ?? this.icon(), ...(ngDevMode ? [{ debugName: "displayIcon" }] : /* istanbul ignore next */ []));
|
|
1160
|
+
displayBadge = computed(() => this.node()?.badge ?? this.badge(), ...(ngDevMode ? [{ debugName: "displayBadge" }] : /* istanbul ignore next */ []));
|
|
1161
|
+
displayActive = computed(() => this.node()?.active ?? this.active(), ...(ngDevMode ? [{ debugName: "displayActive" }] : /* istanbul ignore next */ []));
|
|
1162
|
+
hasChildren = computed(() => {
|
|
1163
|
+
const n = this.node();
|
|
1164
|
+
return n ? (n.children?.length ?? 0) > 0 : this.childNodes().length > 0;
|
|
1165
|
+
}, ...(ngDevMode ? [{ debugName: "hasChildren" }] : /* istanbul ignore next */ []));
|
|
1166
|
+
isOpen = computed(() => {
|
|
1167
|
+
if (this.node())
|
|
1168
|
+
return this.dataExpanded() ?? this.node().expanded ?? false;
|
|
1169
|
+
return this.expanded();
|
|
1170
|
+
}, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
1142
1171
|
toggle() {
|
|
1143
|
-
this.
|
|
1172
|
+
if (this.node()) {
|
|
1173
|
+
this.dataExpanded.set(!this.isOpen());
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
this.expanded.update((v) => !v);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
onActivate() {
|
|
1180
|
+
const n = this.node();
|
|
1181
|
+
if (n)
|
|
1182
|
+
this.nodeActivated.emit(n);
|
|
1183
|
+
else
|
|
1184
|
+
this.activated.emit();
|
|
1144
1185
|
}
|
|
1145
1186
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTreeNode, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1146
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctTreeNode, isStandalone: true, selector: "strct-tree-node", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired:
|
|
1187
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctTreeNode, isStandalone: true, selector: "strct-tree-node", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange", activated: "activated", nodeActivated: "nodeActivated" }, host: { classAttribute: "strct-tnode" }, queries: [{ propertyName: "childNodes", predicate: StrctTreeNode, isSignal: true }], ngImport: i0, template: `
|
|
1147
1188
|
<div
|
|
1148
1189
|
class="strct-tnode__row"
|
|
1149
|
-
[class.strct-tnode__row--active]="
|
|
1190
|
+
[class.strct-tnode__row--active]="displayActive()"
|
|
1150
1191
|
role="treeitem"
|
|
1151
|
-
[attr.aria-expanded]="hasChildren() ?
|
|
1152
|
-
(click)="
|
|
1192
|
+
[attr.aria-expanded]="hasChildren() ? isOpen() : null"
|
|
1193
|
+
(click)="onActivate()"
|
|
1153
1194
|
>
|
|
1154
1195
|
@if (hasChildren()) {
|
|
1155
1196
|
<span
|
|
1156
1197
|
class="strct-tnode__chevron"
|
|
1157
|
-
[class.strct-tnode__chevron--open]="
|
|
1198
|
+
[class.strct-tnode__chevron--open]="isOpen()"
|
|
1158
1199
|
(click)="$event.stopPropagation(); toggle()"
|
|
1159
1200
|
>
|
|
1160
1201
|
<strct-icon name="chevronRight" [size]="12" [strokeWidth]="1.7" />
|
|
@@ -1162,33 +1203,45 @@ class StrctTreeNode {
|
|
|
1162
1203
|
} @else {
|
|
1163
1204
|
<span class="strct-tnode__spacer"></span>
|
|
1164
1205
|
}
|
|
1165
|
-
@if (
|
|
1166
|
-
<strct-icon
|
|
1206
|
+
@if (displayIcon()) {
|
|
1207
|
+
<strct-icon
|
|
1208
|
+
class="strct-tnode__icon"
|
|
1209
|
+
[name]="displayIcon()!"
|
|
1210
|
+
[size]="14"
|
|
1211
|
+
[strokeWidth]="1.3"
|
|
1212
|
+
[badge]="displayBadge()"
|
|
1213
|
+
/>
|
|
1167
1214
|
}
|
|
1168
|
-
<span class="strct-tnode__label">{{
|
|
1215
|
+
<span class="strct-tnode__label">{{ displayLabel() }}</span>
|
|
1169
1216
|
<ng-content select="[strctTreeTrailing]" />
|
|
1170
1217
|
</div>
|
|
1171
|
-
@if (hasChildren() &&
|
|
1218
|
+
@if (hasChildren() && isOpen()) {
|
|
1172
1219
|
<div class="strct-tnode__children" role="group">
|
|
1173
|
-
|
|
1220
|
+
@if (node()) {
|
|
1221
|
+
@for (child of node()!.children ?? []; track $index) {
|
|
1222
|
+
<strct-tree-node [node]="child" (nodeActivated)="nodeActivated.emit($event)" />
|
|
1223
|
+
}
|
|
1224
|
+
} @else {
|
|
1225
|
+
<ng-content />
|
|
1226
|
+
}
|
|
1174
1227
|
</div>
|
|
1175
1228
|
}
|
|
1176
|
-
`, isInline: true, styles: [".strct-tnode{display:block}.strct-tnode__row{display:flex;align-items:center;gap:7px;padding:7px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1);-webkit-user-select:none;user-select:none}.strct-tnode__row:hover{background:var(--bg-3)}.strct-tnode__row--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-tnode__row--active .strct-tnode__icon,.strct-tnode__row--active .strct-tnode__chevron{color:var(--acc)}.strct-tnode__chevron{display:inline-flex;color:var(--t3);transition:transform .15s ease;width:14px;justify-content:center}.strct-tnode__chevron--open{transform:rotate(90deg)}.strct-tnode__spacer{width:14px;flex-shrink:0}.strct-tnode__icon{color:var(--t2);flex-shrink:0}.strct-tnode__label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-tnode__children{margin-left:16px}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1229
|
+
`, isInline: true, styles: [".strct-tnode{display:block}.strct-tnode__row{display:flex;align-items:center;gap:7px;padding:7px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1);-webkit-user-select:none;user-select:none}.strct-tnode__row:hover{background:var(--bg-3)}.strct-tnode__row--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-tnode__row--active .strct-tnode__icon,.strct-tnode__row--active .strct-tnode__chevron{color:var(--acc)}.strct-tnode__chevron{display:inline-flex;color:var(--t3);transition:transform .15s ease;width:14px;justify-content:center}.strct-tnode__chevron--open{transform:rotate(90deg)}.strct-tnode__spacer{width:14px;flex-shrink:0}.strct-tnode__icon{color:var(--t2);flex-shrink:0}.strct-tnode__label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-tnode__children{margin-left:16px}\n"], dependencies: [{ kind: "component", type: StrctTreeNode, selector: "strct-tree-node", inputs: ["node", "label", "icon", "badge", "active", "expanded"], outputs: ["expandedChange", "activated", "nodeActivated"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1177
1230
|
}
|
|
1178
1231
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTreeNode, decorators: [{
|
|
1179
1232
|
type: Component,
|
|
1180
1233
|
args: [{ selector: 'strct-tree-node', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
|
|
1181
1234
|
<div
|
|
1182
1235
|
class="strct-tnode__row"
|
|
1183
|
-
[class.strct-tnode__row--active]="
|
|
1236
|
+
[class.strct-tnode__row--active]="displayActive()"
|
|
1184
1237
|
role="treeitem"
|
|
1185
|
-
[attr.aria-expanded]="hasChildren() ?
|
|
1186
|
-
(click)="
|
|
1238
|
+
[attr.aria-expanded]="hasChildren() ? isOpen() : null"
|
|
1239
|
+
(click)="onActivate()"
|
|
1187
1240
|
>
|
|
1188
1241
|
@if (hasChildren()) {
|
|
1189
1242
|
<span
|
|
1190
1243
|
class="strct-tnode__chevron"
|
|
1191
|
-
[class.strct-tnode__chevron--open]="
|
|
1244
|
+
[class.strct-tnode__chevron--open]="isOpen()"
|
|
1192
1245
|
(click)="$event.stopPropagation(); toggle()"
|
|
1193
1246
|
>
|
|
1194
1247
|
<strct-icon name="chevronRight" [size]="12" [strokeWidth]="1.7" />
|
|
@@ -1196,21 +1249,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
1196
1249
|
} @else {
|
|
1197
1250
|
<span class="strct-tnode__spacer"></span>
|
|
1198
1251
|
}
|
|
1199
|
-
@if (
|
|
1200
|
-
<strct-icon
|
|
1252
|
+
@if (displayIcon()) {
|
|
1253
|
+
<strct-icon
|
|
1254
|
+
class="strct-tnode__icon"
|
|
1255
|
+
[name]="displayIcon()!"
|
|
1256
|
+
[size]="14"
|
|
1257
|
+
[strokeWidth]="1.3"
|
|
1258
|
+
[badge]="displayBadge()"
|
|
1259
|
+
/>
|
|
1201
1260
|
}
|
|
1202
|
-
<span class="strct-tnode__label">{{
|
|
1261
|
+
<span class="strct-tnode__label">{{ displayLabel() }}</span>
|
|
1203
1262
|
<ng-content select="[strctTreeTrailing]" />
|
|
1204
1263
|
</div>
|
|
1205
|
-
@if (hasChildren() &&
|
|
1264
|
+
@if (hasChildren() && isOpen()) {
|
|
1206
1265
|
<div class="strct-tnode__children" role="group">
|
|
1207
|
-
|
|
1266
|
+
@if (node()) {
|
|
1267
|
+
@for (child of node()!.children ?? []; track $index) {
|
|
1268
|
+
<strct-tree-node [node]="child" (nodeActivated)="nodeActivated.emit($event)" />
|
|
1269
|
+
}
|
|
1270
|
+
} @else {
|
|
1271
|
+
<ng-content />
|
|
1272
|
+
}
|
|
1208
1273
|
</div>
|
|
1209
1274
|
}
|
|
1210
1275
|
`, host: { class: 'strct-tnode' }, styles: [".strct-tnode{display:block}.strct-tnode__row{display:flex;align-items:center;gap:7px;padding:7px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1);-webkit-user-select:none;user-select:none}.strct-tnode__row:hover{background:var(--bg-3)}.strct-tnode__row--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-tnode__row--active .strct-tnode__icon,.strct-tnode__row--active .strct-tnode__chevron{color:var(--acc)}.strct-tnode__chevron{display:inline-flex;color:var(--t3);transition:transform .15s ease;width:14px;justify-content:center}.strct-tnode__chevron--open{transform:rotate(90deg)}.strct-tnode__spacer{width:14px;flex-shrink:0}.strct-tnode__icon{color:var(--t2);flex-shrink:0}.strct-tnode__label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-tnode__children{margin-left:16px}\n"] }]
|
|
1211
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required:
|
|
1276
|
+
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], activated: [{ type: i0.Output, args: ["activated"] }], nodeActivated: [{ type: i0.Output, args: ["nodeActivated"] }], childNodes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctTreeNode), { isSignal: true }] }] } });
|
|
1277
|
+
/**
|
|
1278
|
+
* Root container for a tree. Either project `<strct-tree-node>` children, or
|
|
1279
|
+
* pass `[nodes]` for a fully data-driven, self-recursing tree:
|
|
1280
|
+
* <strct-tree [nodes]="roots" (nodeActivated)="select($event)" />
|
|
1281
|
+
*/
|
|
1282
|
+
class StrctTree {
|
|
1283
|
+
/** Data-driven node list; when set, projected content is ignored. */
|
|
1284
|
+
nodes = input(null, ...(ngDevMode ? [{ debugName: "nodes" }] : /* istanbul ignore next */ []));
|
|
1285
|
+
/** Emitted when any data-driven node is clicked. */
|
|
1286
|
+
nodeActivated = output();
|
|
1287
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTree, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1288
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctTree, isStandalone: true, selector: "strct-tree", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { nodeActivated: "nodeActivated" }, host: { attributes: { "role": "tree" }, classAttribute: "strct-tree" }, ngImport: i0, template: `
|
|
1289
|
+
@if (nodes(); as ns) {
|
|
1290
|
+
@for (n of ns; track $index) {
|
|
1291
|
+
<strct-tree-node [node]="n" (nodeActivated)="nodeActivated.emit($event)" />
|
|
1292
|
+
}
|
|
1293
|
+
} @else {
|
|
1294
|
+
<ng-content />
|
|
1295
|
+
}
|
|
1296
|
+
`, isInline: true, styles: [".strct-tree{display:block}\n"], dependencies: [{ kind: "component", type: StrctTreeNode, selector: "strct-tree-node", inputs: ["node", "label", "icon", "badge", "active", "expanded"], outputs: ["expandedChange", "activated", "nodeActivated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1297
|
+
}
|
|
1298
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTree, decorators: [{
|
|
1299
|
+
type: Component,
|
|
1300
|
+
args: [{ selector: 'strct-tree', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctTreeNode], template: `
|
|
1301
|
+
@if (nodes(); as ns) {
|
|
1302
|
+
@for (n of ns; track $index) {
|
|
1303
|
+
<strct-tree-node [node]="n" (nodeActivated)="nodeActivated.emit($event)" />
|
|
1304
|
+
}
|
|
1305
|
+
} @else {
|
|
1306
|
+
<ng-content />
|
|
1307
|
+
}
|
|
1308
|
+
`, host: { class: 'strct-tree', role: 'tree' }, styles: [".strct-tree{display:block}\n"] }]
|
|
1309
|
+
}], propDecorators: { nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], nodeActivated: [{ type: i0.Output, args: ["nodeActivated"] }] } });
|
|
1212
1310
|
|
|
1213
1311
|
let modalCounter = 0;
|
|
1312
|
+
// Body scroll-lock shared across any number of simultaneously open modals.
|
|
1313
|
+
let scrollLockCount = 0;
|
|
1314
|
+
let savedBodyOverflow = '';
|
|
1315
|
+
function lockBodyScroll(doc) {
|
|
1316
|
+
if (scrollLockCount === 0) {
|
|
1317
|
+
savedBodyOverflow = doc.body.style.overflow;
|
|
1318
|
+
doc.body.style.overflow = 'hidden';
|
|
1319
|
+
}
|
|
1320
|
+
scrollLockCount++;
|
|
1321
|
+
}
|
|
1322
|
+
function unlockBodyScroll(doc) {
|
|
1323
|
+
scrollLockCount = Math.max(0, scrollLockCount - 1);
|
|
1324
|
+
if (scrollLockCount === 0)
|
|
1325
|
+
doc.body.style.overflow = savedBodyOverflow;
|
|
1326
|
+
}
|
|
1214
1327
|
/**
|
|
1215
1328
|
* Overlay dialog with two-way `open`:
|
|
1216
1329
|
* <strct-modal [(open)]="show" title="Confirm">
|
|
@@ -1233,16 +1346,32 @@ class StrctModal {
|
|
|
1233
1346
|
titleId = `strct-modal-${++modalCounter}`;
|
|
1234
1347
|
/** Element that had focus before the dialog opened, restored on close. */
|
|
1235
1348
|
previousActive = null;
|
|
1349
|
+
/** Whether this instance currently holds a scroll lock. */
|
|
1350
|
+
locked = false;
|
|
1236
1351
|
constructor() {
|
|
1237
1352
|
effect(() => {
|
|
1238
|
-
|
|
1353
|
+
const open = this.open();
|
|
1354
|
+
if (open && !this.locked) {
|
|
1355
|
+
this.locked = true;
|
|
1356
|
+
lockBodyScroll(this.doc);
|
|
1239
1357
|
this.previousActive = this.doc.activeElement;
|
|
1240
1358
|
// Move focus into the dialog once it has rendered.
|
|
1241
1359
|
setTimeout(() => this.focusInitial());
|
|
1242
1360
|
}
|
|
1243
|
-
else if (this.
|
|
1244
|
-
this.
|
|
1245
|
-
this.
|
|
1361
|
+
else if (!open && this.locked) {
|
|
1362
|
+
this.locked = false;
|
|
1363
|
+
unlockBodyScroll(this.doc);
|
|
1364
|
+
if (this.previousActive) {
|
|
1365
|
+
this.previousActive.focus?.();
|
|
1366
|
+
this.previousActive = null;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
// Release the lock if the modal is destroyed while still open.
|
|
1371
|
+
inject(DestroyRef).onDestroy(() => {
|
|
1372
|
+
if (this.locked) {
|
|
1373
|
+
this.locked = false;
|
|
1374
|
+
unlockBodyScroll(this.doc);
|
|
1246
1375
|
}
|
|
1247
1376
|
});
|
|
1248
1377
|
}
|
|
@@ -1543,28 +1672,45 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
1543
1672
|
}] } });
|
|
1544
1673
|
|
|
1545
1674
|
/**
|
|
1546
|
-
* A nested fly-out inside a `strct-context-menu` or `strct-dropdown`.
|
|
1547
|
-
*
|
|
1675
|
+
* A nested fly-out inside a `strct-context-menu` or `strct-dropdown`. Opens on
|
|
1676
|
+
* hover, click/tap, or the keyboard (Enter / Space / →), and flips to the left
|
|
1677
|
+
* near the right edge of the viewport. Reuse `strct-dropdown-item` for entries.
|
|
1548
1678
|
* <strct-submenu label="Power">
|
|
1549
1679
|
* <strct-dropdown-item>Power on</strct-dropdown-item>
|
|
1550
1680
|
* <strct-dropdown-item>Power off</strct-dropdown-item>
|
|
1551
1681
|
* </strct-submenu>
|
|
1552
1682
|
*/
|
|
1553
1683
|
class StrctSubmenu {
|
|
1684
|
+
host = inject(ElementRef);
|
|
1554
1685
|
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1555
1686
|
/** Optional leading icon; when omitted the icon column is still reserved so
|
|
1556
1687
|
* the label stays aligned with sibling items that do have icons. */
|
|
1557
1688
|
icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
1558
1689
|
open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
1690
|
+
/** Open to the left when the fly-out would overflow the right edge. */
|
|
1691
|
+
flip = signal(false, ...(ngDevMode ? [{ debugName: "flip" }] : /* istanbul ignore next */ []));
|
|
1692
|
+
setOpen(value) {
|
|
1693
|
+
if (value) {
|
|
1694
|
+
const rect = this.host.nativeElement.getBoundingClientRect();
|
|
1695
|
+
this.flip.set(rect.right + 190 > window.innerWidth);
|
|
1696
|
+
}
|
|
1697
|
+
this.open.set(value);
|
|
1698
|
+
}
|
|
1559
1699
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSubmenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1560
1700
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctSubmenu, isStandalone: true, selector: "strct-submenu", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "strct-submenu-host" }, ngImport: i0, template: `
|
|
1561
|
-
<div class="strct-submenu" (mouseenter)="
|
|
1701
|
+
<div class="strct-submenu" (mouseenter)="setOpen(true)" (mouseleave)="open.set(false)">
|
|
1562
1702
|
<div
|
|
1563
1703
|
class="strct-submenu__trigger"
|
|
1564
1704
|
role="menuitem"
|
|
1705
|
+
tabindex="0"
|
|
1565
1706
|
aria-haspopup="menu"
|
|
1566
1707
|
[attr.aria-expanded]="open()"
|
|
1567
|
-
(click)="$event.stopPropagation()"
|
|
1708
|
+
(click)="$event.stopPropagation(); setOpen(!open())"
|
|
1709
|
+
(keydown.enter)="$event.preventDefault(); $event.stopPropagation(); setOpen(true)"
|
|
1710
|
+
(keydown.space)="$event.preventDefault(); $event.stopPropagation(); setOpen(true)"
|
|
1711
|
+
(keydown.arrowright)="$event.preventDefault(); $event.stopPropagation(); setOpen(true)"
|
|
1712
|
+
(keydown.arrowleft)="$event.stopPropagation(); open.set(false)"
|
|
1713
|
+
(keydown.escape)="$event.stopPropagation(); open.set(false)"
|
|
1568
1714
|
>
|
|
1569
1715
|
@if (icon()) {
|
|
1570
1716
|
<strct-icon class="strct-submenu__icon" [name]="icon()" [size]="14" [strokeWidth]="1.3" />
|
|
@@ -1575,21 +1721,29 @@ class StrctSubmenu {
|
|
|
1575
1721
|
<strct-icon class="strct-submenu__arrow" name="chevronRight" [size]="12" [strokeWidth]="1.6" />
|
|
1576
1722
|
</div>
|
|
1577
1723
|
@if (open()) {
|
|
1578
|
-
<div class="strct-submenu__panel" role="menu"
|
|
1724
|
+
<div class="strct-submenu__panel" [class.strct-submenu__panel--flip]="flip()" role="menu">
|
|
1725
|
+
<ng-content />
|
|
1726
|
+
</div>
|
|
1579
1727
|
}
|
|
1580
1728
|
</div>
|
|
1581
|
-
`, isInline: true, styles: [".strct-submenu{position:relative}.strct-submenu__trigger{display:flex;align-items:center;gap:8px;padding:7px 8px 7px 10px;border-radius:5px;cursor:default;font-size:13px;color:var(--t1)}.strct-submenu__trigger:hover{background:var(--bg-3)}.strct-submenu__icon{color:var(--t2);flex-shrink:0}.strct-submenu__icon-spacer{width:14px;flex-shrink:0}.strct-submenu__label{flex:1;display:inline-flex;align-items:center;gap:8px}.strct-submenu__arrow{color:var(--t3)}.strct-submenu__panel{position:absolute;top:-5px;left:100%;z-index:1;min-width:170px;margin-left:2px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);animation:strct-submenu-in .1s ease}@keyframes strct-submenu-in{0%{opacity:0;transform:translate(-4px)}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1729
|
+
`, isInline: true, styles: [".strct-submenu{position:relative}.strct-submenu__trigger{display:flex;align-items:center;gap:8px;padding:7px 8px 7px 10px;border-radius:5px;cursor:default;font-size:13px;color:var(--t1)}.strct-submenu__trigger:hover{background:var(--bg-3)}.strct-submenu__trigger:focus-visible{outline:none;background:var(--bg-3)}.strct-submenu__icon{color:var(--t2);flex-shrink:0}.strct-submenu__icon-spacer{width:14px;flex-shrink:0}.strct-submenu__label{flex:1;display:inline-flex;align-items:center;gap:8px}.strct-submenu__arrow{color:var(--t3)}.strct-submenu__panel{position:absolute;top:-5px;left:100%;z-index:1;min-width:170px;margin-left:2px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);animation:strct-submenu-in .1s ease}.strct-submenu__panel--flip{left:auto;right:100%;margin-left:0;margin-right:2px}@keyframes strct-submenu-in{0%{opacity:0;transform:translate(-4px)}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1582
1730
|
}
|
|
1583
1731
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSubmenu, decorators: [{
|
|
1584
1732
|
type: Component,
|
|
1585
1733
|
args: [{ selector: 'strct-submenu', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
|
|
1586
|
-
<div class="strct-submenu" (mouseenter)="
|
|
1734
|
+
<div class="strct-submenu" (mouseenter)="setOpen(true)" (mouseleave)="open.set(false)">
|
|
1587
1735
|
<div
|
|
1588
1736
|
class="strct-submenu__trigger"
|
|
1589
1737
|
role="menuitem"
|
|
1738
|
+
tabindex="0"
|
|
1590
1739
|
aria-haspopup="menu"
|
|
1591
1740
|
[attr.aria-expanded]="open()"
|
|
1592
|
-
(click)="$event.stopPropagation()"
|
|
1741
|
+
(click)="$event.stopPropagation(); setOpen(!open())"
|
|
1742
|
+
(keydown.enter)="$event.preventDefault(); $event.stopPropagation(); setOpen(true)"
|
|
1743
|
+
(keydown.space)="$event.preventDefault(); $event.stopPropagation(); setOpen(true)"
|
|
1744
|
+
(keydown.arrowright)="$event.preventDefault(); $event.stopPropagation(); setOpen(true)"
|
|
1745
|
+
(keydown.arrowleft)="$event.stopPropagation(); open.set(false)"
|
|
1746
|
+
(keydown.escape)="$event.stopPropagation(); open.set(false)"
|
|
1593
1747
|
>
|
|
1594
1748
|
@if (icon()) {
|
|
1595
1749
|
<strct-icon class="strct-submenu__icon" [name]="icon()" [size]="14" [strokeWidth]="1.3" />
|
|
@@ -1600,15 +1754,349 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
1600
1754
|
<strct-icon class="strct-submenu__arrow" name="chevronRight" [size]="12" [strokeWidth]="1.6" />
|
|
1601
1755
|
</div>
|
|
1602
1756
|
@if (open()) {
|
|
1603
|
-
<div class="strct-submenu__panel" role="menu"
|
|
1757
|
+
<div class="strct-submenu__panel" [class.strct-submenu__panel--flip]="flip()" role="menu">
|
|
1758
|
+
<ng-content />
|
|
1759
|
+
</div>
|
|
1604
1760
|
}
|
|
1605
1761
|
</div>
|
|
1606
|
-
`, host: { class: 'strct-submenu-host' }, styles: [".strct-submenu{position:relative}.strct-submenu__trigger{display:flex;align-items:center;gap:8px;padding:7px 8px 7px 10px;border-radius:5px;cursor:default;font-size:13px;color:var(--t1)}.strct-submenu__trigger:hover{background:var(--bg-3)}.strct-submenu__icon{color:var(--t2);flex-shrink:0}.strct-submenu__icon-spacer{width:14px;flex-shrink:0}.strct-submenu__label{flex:1;display:inline-flex;align-items:center;gap:8px}.strct-submenu__arrow{color:var(--t3)}.strct-submenu__panel{position:absolute;top:-5px;left:100%;z-index:1;min-width:170px;margin-left:2px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);animation:strct-submenu-in .1s ease}@keyframes strct-submenu-in{0%{opacity:0;transform:translate(-4px)}}\n"] }]
|
|
1762
|
+
`, host: { class: 'strct-submenu-host' }, styles: [".strct-submenu{position:relative}.strct-submenu__trigger{display:flex;align-items:center;gap:8px;padding:7px 8px 7px 10px;border-radius:5px;cursor:default;font-size:13px;color:var(--t1)}.strct-submenu__trigger:hover{background:var(--bg-3)}.strct-submenu__trigger:focus-visible{outline:none;background:var(--bg-3)}.strct-submenu__icon{color:var(--t2);flex-shrink:0}.strct-submenu__icon-spacer{width:14px;flex-shrink:0}.strct-submenu__label{flex:1;display:inline-flex;align-items:center;gap:8px}.strct-submenu__arrow{color:var(--t3)}.strct-submenu__panel{position:absolute;top:-5px;left:100%;z-index:1;min-width:170px;margin-left:2px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);animation:strct-submenu-in .1s ease}.strct-submenu__panel--flip{left:auto;right:100%;margin-left:0;margin-right:2px}@keyframes strct-submenu-in{0%{opacity:0;transform:translate(-4px)}}\n"] }]
|
|
1607
1763
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }] } });
|
|
1608
1764
|
|
|
1765
|
+
/**
|
|
1766
|
+
* Floating menu panel — portaled into `<body>` (so it escapes overflow /
|
|
1767
|
+
* transform clipping), positioned by its real measured size, with full keyboard
|
|
1768
|
+
* navigation and recursive submenus. Usually created by `[strctContextMenu]`,
|
|
1769
|
+
* but can be embedded directly with `submenu`.
|
|
1770
|
+
*/
|
|
1771
|
+
class StrctMenuPanel {
|
|
1772
|
+
host = inject(ElementRef);
|
|
1773
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
1774
|
+
data = input(undefined, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
1775
|
+
x = input(0, ...(ngDevMode ? [{ debugName: "x" }] : /* istanbul ignore next */ []));
|
|
1776
|
+
y = input(0, ...(ngDevMode ? [{ debugName: "y" }] : /* istanbul ignore next */ []));
|
|
1777
|
+
submenu = input(false, { ...(ngDevMode ? { debugName: "submenu" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1778
|
+
select = output();
|
|
1779
|
+
close = output();
|
|
1780
|
+
/** ArrowLeft inside a submenu — asks the parent to close it. */
|
|
1781
|
+
back = output();
|
|
1782
|
+
posX = signal(0, ...(ngDevMode ? [{ debugName: "posX" }] : /* istanbul ignore next */ []));
|
|
1783
|
+
posY = signal(0, ...(ngDevMode ? [{ debugName: "posY" }] : /* istanbul ignore next */ []));
|
|
1784
|
+
flipLeft = signal(false, ...(ngDevMode ? [{ debugName: "flipLeft" }] : /* istanbul ignore next */ []));
|
|
1785
|
+
activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
1786
|
+
openSubIndex = signal(null, ...(ngDevMode ? [{ debugName: "openSubIndex" }] : /* istanbul ignore next */ []));
|
|
1787
|
+
navIndices = computed(() => this.items()
|
|
1788
|
+
.map((it, i) => (it.divider ? -1 : i))
|
|
1789
|
+
.filter((i) => i >= 0), ...(ngDevMode ? [{ debugName: "navIndices" }] : /* istanbul ignore next */ []));
|
|
1790
|
+
constructor() {
|
|
1791
|
+
this.posX.set(this.x());
|
|
1792
|
+
this.posY.set(this.y());
|
|
1793
|
+
afterNextRender(() => {
|
|
1794
|
+
this.activeIndex.set(this.navIndices()[0] ?? 0);
|
|
1795
|
+
if (!this.submenu())
|
|
1796
|
+
this.clampToViewport();
|
|
1797
|
+
this.focusItem(this.activeIndex());
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
clampToViewport() {
|
|
1801
|
+
const host = this.host.nativeElement;
|
|
1802
|
+
const w = host.offsetWidth;
|
|
1803
|
+
const h = host.offsetHeight;
|
|
1804
|
+
const vw = window.innerWidth;
|
|
1805
|
+
const vh = window.innerHeight;
|
|
1806
|
+
const m = 6;
|
|
1807
|
+
let nx = this.x();
|
|
1808
|
+
let ny = this.y();
|
|
1809
|
+
if (nx + w > vw - m)
|
|
1810
|
+
nx = Math.max(m, Math.min(this.x() - w, vw - w - m));
|
|
1811
|
+
if (ny + h > vh - m)
|
|
1812
|
+
ny = Math.max(m, vh - h - m);
|
|
1813
|
+
this.posX.set(nx);
|
|
1814
|
+
this.posY.set(ny);
|
|
1815
|
+
// Submenus of a panel near the right edge open to the left.
|
|
1816
|
+
this.flipLeft.set(nx + w > vw - 220);
|
|
1817
|
+
}
|
|
1818
|
+
focusItem(i) {
|
|
1819
|
+
this.activeIndex.set(i);
|
|
1820
|
+
this.host.nativeElement
|
|
1821
|
+
.querySelector(`.strct-menu__item[data-idx="${i}"]`)
|
|
1822
|
+
?.focus();
|
|
1823
|
+
}
|
|
1824
|
+
move(dir) {
|
|
1825
|
+
const nav = this.navIndices();
|
|
1826
|
+
if (!nav.length)
|
|
1827
|
+
return;
|
|
1828
|
+
const pos = nav.indexOf(this.activeIndex());
|
|
1829
|
+
const next = nav[(pos + dir + nav.length) % nav.length];
|
|
1830
|
+
this.openSubIndex.set(null);
|
|
1831
|
+
this.focusItem(next);
|
|
1832
|
+
}
|
|
1833
|
+
onHover(i) {
|
|
1834
|
+
this.activeIndex.set(i);
|
|
1835
|
+
const it = this.items()[i];
|
|
1836
|
+
this.openSubIndex.set(it?.children?.length ? i : null);
|
|
1837
|
+
}
|
|
1838
|
+
onLeave(i) {
|
|
1839
|
+
if (this.openSubIndex() === i)
|
|
1840
|
+
this.openSubIndex.set(null);
|
|
1841
|
+
}
|
|
1842
|
+
onItemClick(item, i, event) {
|
|
1843
|
+
event.stopPropagation();
|
|
1844
|
+
if (item.disabled)
|
|
1845
|
+
return;
|
|
1846
|
+
if (item.children?.length) {
|
|
1847
|
+
this.openSubIndex.set(this.openSubIndex() === i ? null : i);
|
|
1848
|
+
this.focusItem(i);
|
|
1849
|
+
}
|
|
1850
|
+
else {
|
|
1851
|
+
this.select.emit(item);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
closeSub() {
|
|
1855
|
+
this.openSubIndex.set(null);
|
|
1856
|
+
}
|
|
1857
|
+
onKeydown(event) {
|
|
1858
|
+
const key = event.key;
|
|
1859
|
+
const item = this.items()[this.activeIndex()];
|
|
1860
|
+
switch (key) {
|
|
1861
|
+
case 'ArrowDown':
|
|
1862
|
+
event.preventDefault();
|
|
1863
|
+
event.stopPropagation();
|
|
1864
|
+
this.move(1);
|
|
1865
|
+
break;
|
|
1866
|
+
case 'ArrowUp':
|
|
1867
|
+
event.preventDefault();
|
|
1868
|
+
event.stopPropagation();
|
|
1869
|
+
this.move(-1);
|
|
1870
|
+
break;
|
|
1871
|
+
case 'Home':
|
|
1872
|
+
event.preventDefault();
|
|
1873
|
+
event.stopPropagation();
|
|
1874
|
+
this.focusItem(this.navIndices()[0] ?? 0);
|
|
1875
|
+
break;
|
|
1876
|
+
case 'End':
|
|
1877
|
+
event.preventDefault();
|
|
1878
|
+
event.stopPropagation();
|
|
1879
|
+
this.focusItem(this.navIndices().at(-1) ?? 0);
|
|
1880
|
+
break;
|
|
1881
|
+
case 'ArrowRight':
|
|
1882
|
+
if (item?.children?.length) {
|
|
1883
|
+
event.preventDefault();
|
|
1884
|
+
event.stopPropagation();
|
|
1885
|
+
this.openSubIndex.set(this.activeIndex());
|
|
1886
|
+
}
|
|
1887
|
+
break;
|
|
1888
|
+
case 'ArrowLeft':
|
|
1889
|
+
event.preventDefault();
|
|
1890
|
+
event.stopPropagation();
|
|
1891
|
+
if (this.openSubIndex() != null)
|
|
1892
|
+
this.closeSub();
|
|
1893
|
+
else if (this.submenu())
|
|
1894
|
+
this.back.emit();
|
|
1895
|
+
break;
|
|
1896
|
+
case 'Enter':
|
|
1897
|
+
case ' ':
|
|
1898
|
+
event.preventDefault();
|
|
1899
|
+
event.stopPropagation();
|
|
1900
|
+
if (item && !item.disabled) {
|
|
1901
|
+
if (item.children?.length)
|
|
1902
|
+
this.openSubIndex.set(this.activeIndex());
|
|
1903
|
+
else
|
|
1904
|
+
this.select.emit(item);
|
|
1905
|
+
}
|
|
1906
|
+
break;
|
|
1907
|
+
case 'Escape':
|
|
1908
|
+
event.preventDefault();
|
|
1909
|
+
event.stopPropagation();
|
|
1910
|
+
this.close.emit();
|
|
1911
|
+
break;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctMenuPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1915
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctMenuPanel, isStandalone: true, selector: "strct-menu-panel", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: false, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: false, transformFunction: null }, submenu: { classPropertyName: "submenu", publicName: "submenu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { select: "select", close: "close", back: "back" }, host: { properties: { "style.position": "submenu() ? null : 'fixed'", "style.left.px": "submenu() ? null : posX()", "style.top.px": "submenu() ? null : posY()", "style.zIndex": "submenu() ? null : 1100" }, classAttribute: "strct-menu-host" }, ngImport: i0, template: `
|
|
1916
|
+
<div class="strct-menu" role="menu" tabindex="-1" (keydown)="onKeydown($event)">
|
|
1917
|
+
@for (item of items(); track $index; let i = $index) {
|
|
1918
|
+
@if (item.divider) {
|
|
1919
|
+
<div class="strct-menu__sep" role="separator"></div>
|
|
1920
|
+
} @else {
|
|
1921
|
+
<div class="strct-menu__wrap" (mouseenter)="onHover(i)" (mouseleave)="onLeave(i)">
|
|
1922
|
+
<button
|
|
1923
|
+
type="button"
|
|
1924
|
+
class="strct-menu__item"
|
|
1925
|
+
[attr.data-idx]="i"
|
|
1926
|
+
[class.strct-menu__item--danger]="item.danger"
|
|
1927
|
+
[class.strct-menu__item--active]="i === activeIndex()"
|
|
1928
|
+
[disabled]="item.disabled"
|
|
1929
|
+
role="menuitem"
|
|
1930
|
+
[attr.aria-haspopup]="item.children?.length ? 'menu' : null"
|
|
1931
|
+
[attr.aria-expanded]="item.children?.length ? openSubIndex() === i : null"
|
|
1932
|
+
[attr.tabindex]="i === activeIndex() ? 0 : -1"
|
|
1933
|
+
(click)="onItemClick(item, i, $event)"
|
|
1934
|
+
>
|
|
1935
|
+
@if (item.icon) {
|
|
1936
|
+
<strct-icon class="strct-menu__icon" [name]="item.icon" [size]="14" [strokeWidth]="1.3" />
|
|
1937
|
+
} @else {
|
|
1938
|
+
<span class="strct-menu__icon-spacer" aria-hidden="true"></span>
|
|
1939
|
+
}
|
|
1940
|
+
<span class="strct-menu__label">{{ item.label }}</span>
|
|
1941
|
+
@if (item.children?.length) {
|
|
1942
|
+
<strct-icon class="strct-menu__arrow" name="chevronRight" [size]="12" [strokeWidth]="1.6" />
|
|
1943
|
+
}
|
|
1944
|
+
</button>
|
|
1945
|
+
@if (openSubIndex() === i && item.children?.length) {
|
|
1946
|
+
<strct-menu-panel
|
|
1947
|
+
submenu
|
|
1948
|
+
class="strct-menu__subpanel"
|
|
1949
|
+
[class.strct-menu__subpanel--flip]="flipLeft()"
|
|
1950
|
+
[items]="item.children!"
|
|
1951
|
+
[data]="data()"
|
|
1952
|
+
(select)="select.emit($event)"
|
|
1953
|
+
(close)="close.emit()"
|
|
1954
|
+
(back)="closeSub(); focusItem(i)"
|
|
1955
|
+
/>
|
|
1956
|
+
}
|
|
1957
|
+
</div>
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
</div>
|
|
1961
|
+
`, isInline: true, styles: [".strct-menu-host{display:block}.strct-menu{min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);animation:strct-menu-in .1s ease}.strct-menu:focus{outline:none}.strct-menu__wrap{position:relative}.strct-menu__item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px 7px 10px;border:0;border-radius:5px;cursor:pointer;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:left}.strct-menu__item:hover:not(:disabled),.strct-menu__item--active:not(:disabled){background:var(--bg-3)}.strct-menu__item:focus-visible{outline:none;background:var(--bg-3)}.strct-menu__item--danger{color:var(--crt)}.strct-menu__item--danger:hover:not(:disabled),.strct-menu__item--danger.strct-menu__item--active:not(:disabled){background:var(--crt-bg)}.strct-menu__item:disabled{opacity:.45;cursor:not-allowed}.strct-menu__icon{color:var(--t2);flex-shrink:0}.strct-menu__item--danger .strct-menu__icon{color:var(--crt)}.strct-menu__icon-spacer{width:14px;flex-shrink:0}.strct-menu__label{flex:1;white-space:nowrap}.strct-menu__arrow{color:var(--t3);flex-shrink:0}.strct-menu__sep{height:1px;margin:4px 6px;background:var(--b1)}.strct-menu__subpanel{position:absolute;top:-5px;left:100%;margin-left:2px;z-index:1}.strct-menu__subpanel--flip{left:auto;right:100%;margin-left:0;margin-right:2px}@keyframes strct-menu-in{0%{opacity:0;transform:scale(.97)}}\n"], dependencies: [{ kind: "component", type: StrctMenuPanel, selector: "strct-menu-panel", inputs: ["items", "data", "x", "y", "submenu"], outputs: ["select", "close", "back"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1962
|
+
}
|
|
1963
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctMenuPanel, decorators: [{
|
|
1964
|
+
type: Component,
|
|
1965
|
+
args: [{ selector: 'strct-menu-panel', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
|
|
1966
|
+
<div class="strct-menu" role="menu" tabindex="-1" (keydown)="onKeydown($event)">
|
|
1967
|
+
@for (item of items(); track $index; let i = $index) {
|
|
1968
|
+
@if (item.divider) {
|
|
1969
|
+
<div class="strct-menu__sep" role="separator"></div>
|
|
1970
|
+
} @else {
|
|
1971
|
+
<div class="strct-menu__wrap" (mouseenter)="onHover(i)" (mouseleave)="onLeave(i)">
|
|
1972
|
+
<button
|
|
1973
|
+
type="button"
|
|
1974
|
+
class="strct-menu__item"
|
|
1975
|
+
[attr.data-idx]="i"
|
|
1976
|
+
[class.strct-menu__item--danger]="item.danger"
|
|
1977
|
+
[class.strct-menu__item--active]="i === activeIndex()"
|
|
1978
|
+
[disabled]="item.disabled"
|
|
1979
|
+
role="menuitem"
|
|
1980
|
+
[attr.aria-haspopup]="item.children?.length ? 'menu' : null"
|
|
1981
|
+
[attr.aria-expanded]="item.children?.length ? openSubIndex() === i : null"
|
|
1982
|
+
[attr.tabindex]="i === activeIndex() ? 0 : -1"
|
|
1983
|
+
(click)="onItemClick(item, i, $event)"
|
|
1984
|
+
>
|
|
1985
|
+
@if (item.icon) {
|
|
1986
|
+
<strct-icon class="strct-menu__icon" [name]="item.icon" [size]="14" [strokeWidth]="1.3" />
|
|
1987
|
+
} @else {
|
|
1988
|
+
<span class="strct-menu__icon-spacer" aria-hidden="true"></span>
|
|
1989
|
+
}
|
|
1990
|
+
<span class="strct-menu__label">{{ item.label }}</span>
|
|
1991
|
+
@if (item.children?.length) {
|
|
1992
|
+
<strct-icon class="strct-menu__arrow" name="chevronRight" [size]="12" [strokeWidth]="1.6" />
|
|
1993
|
+
}
|
|
1994
|
+
</button>
|
|
1995
|
+
@if (openSubIndex() === i && item.children?.length) {
|
|
1996
|
+
<strct-menu-panel
|
|
1997
|
+
submenu
|
|
1998
|
+
class="strct-menu__subpanel"
|
|
1999
|
+
[class.strct-menu__subpanel--flip]="flipLeft()"
|
|
2000
|
+
[items]="item.children!"
|
|
2001
|
+
[data]="data()"
|
|
2002
|
+
(select)="select.emit($event)"
|
|
2003
|
+
(close)="close.emit()"
|
|
2004
|
+
(back)="closeSub(); focusItem(i)"
|
|
2005
|
+
/>
|
|
2006
|
+
}
|
|
2007
|
+
</div>
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
</div>
|
|
2011
|
+
`, host: {
|
|
2012
|
+
class: 'strct-menu-host',
|
|
2013
|
+
'[style.position]': "submenu() ? null : 'fixed'",
|
|
2014
|
+
'[style.left.px]': 'submenu() ? null : posX()',
|
|
2015
|
+
'[style.top.px]': 'submenu() ? null : posY()',
|
|
2016
|
+
'[style.zIndex]': 'submenu() ? null : 1100',
|
|
2017
|
+
}, styles: [".strct-menu-host{display:block}.strct-menu{min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);animation:strct-menu-in .1s ease}.strct-menu:focus{outline:none}.strct-menu__wrap{position:relative}.strct-menu__item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px 7px 10px;border:0;border-radius:5px;cursor:pointer;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:left}.strct-menu__item:hover:not(:disabled),.strct-menu__item--active:not(:disabled){background:var(--bg-3)}.strct-menu__item:focus-visible{outline:none;background:var(--bg-3)}.strct-menu__item--danger{color:var(--crt)}.strct-menu__item--danger:hover:not(:disabled),.strct-menu__item--danger.strct-menu__item--active:not(:disabled){background:var(--crt-bg)}.strct-menu__item:disabled{opacity:.45;cursor:not-allowed}.strct-menu__icon{color:var(--t2);flex-shrink:0}.strct-menu__item--danger .strct-menu__icon{color:var(--crt)}.strct-menu__icon-spacer{width:14px;flex-shrink:0}.strct-menu__label{flex:1;white-space:nowrap}.strct-menu__arrow{color:var(--t3);flex-shrink:0}.strct-menu__sep{height:1px;margin:4px 6px;background:var(--b1)}.strct-menu__subpanel{position:absolute;top:-5px;left:100%;margin-left:2px;z-index:1}.strct-menu__subpanel--flip{left:auto;right:100%;margin-left:0;margin-right:2px}@keyframes strct-menu-in{0%{opacity:0;transform:scale(.97)}}\n"] }]
|
|
2018
|
+
}], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], x: [{ type: i0.Input, args: [{ isSignal: true, alias: "x", required: false }] }], y: [{ type: i0.Input, args: [{ isSignal: true, alias: "y", required: false }] }], submenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "submenu", required: false }] }], select: [{ type: i0.Output, args: ["select"] }], close: [{ type: i0.Output, args: ["close"] }], back: [{ type: i0.Output, args: ["back"] }] } });
|
|
2019
|
+
/**
|
|
2020
|
+
* Right-click (context) menu driven by a data array. Attach to any trigger; the
|
|
2021
|
+
* menu portals into `<body>` and runs each item's `action` on selection.
|
|
2022
|
+
* <div [strctContextMenu]="menuFor(host)" [strctContextMenuData]="host"
|
|
2023
|
+
* (menuSelect)="onPick($event)">…</div>
|
|
2024
|
+
*/
|
|
2025
|
+
class StrctContextMenuTrigger {
|
|
2026
|
+
appRef = inject(ApplicationRef);
|
|
2027
|
+
envInjector = inject(EnvironmentInjector);
|
|
2028
|
+
zone = inject(NgZone);
|
|
2029
|
+
doc = inject(DOCUMENT$1);
|
|
2030
|
+
items = input.required({ ...(ngDevMode ? { debugName: "items" } : /* istanbul ignore next */ {}), alias: 'strctContextMenu' });
|
|
2031
|
+
data = input(undefined, { ...(ngDevMode ? { debugName: "data" } : /* istanbul ignore next */ {}), alias: 'strctContextMenuData' });
|
|
2032
|
+
menuSelect = output();
|
|
2033
|
+
ref = null;
|
|
2034
|
+
onClose = () => this.zone.run(() => this.closeMenu());
|
|
2035
|
+
onContextMenu(event) {
|
|
2036
|
+
if (!this.items()?.length)
|
|
2037
|
+
return;
|
|
2038
|
+
event.preventDefault();
|
|
2039
|
+
this.openAt(event.clientX, event.clientY);
|
|
2040
|
+
}
|
|
2041
|
+
openAt(x, y) {
|
|
2042
|
+
this.closeMenu();
|
|
2043
|
+
const ref = createComponent(StrctMenuPanel, { environmentInjector: this.envInjector });
|
|
2044
|
+
ref.setInput('items', this.items());
|
|
2045
|
+
ref.setInput('data', this.data());
|
|
2046
|
+
ref.setInput('x', x);
|
|
2047
|
+
ref.setInput('y', y);
|
|
2048
|
+
ref.instance.select.subscribe((item) => {
|
|
2049
|
+
item.action?.(this.data());
|
|
2050
|
+
this.menuSelect.emit(item);
|
|
2051
|
+
this.closeMenu();
|
|
2052
|
+
});
|
|
2053
|
+
ref.instance.close.subscribe(() => this.closeMenu());
|
|
2054
|
+
this.appRef.attachView(ref.hostView);
|
|
2055
|
+
this.doc.body.appendChild(ref.location.nativeElement);
|
|
2056
|
+
this.ref = ref;
|
|
2057
|
+
// Defer global listeners so the opening right-click doesn't immediately close.
|
|
2058
|
+
setTimeout(() => {
|
|
2059
|
+
this.zone.runOutsideAngular(() => {
|
|
2060
|
+
this.doc.addEventListener('mousedown', this.onOutside, true);
|
|
2061
|
+
window.addEventListener('scroll', this.onClose, true);
|
|
2062
|
+
window.addEventListener('resize', this.onClose);
|
|
2063
|
+
});
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
onOutside = (event) => {
|
|
2067
|
+
if (this.ref && !this.ref.location.nativeElement.contains(event.target)) {
|
|
2068
|
+
this.onClose();
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
closeMenu() {
|
|
2072
|
+
if (!this.ref)
|
|
2073
|
+
return;
|
|
2074
|
+
this.doc.removeEventListener('mousedown', this.onOutside, true);
|
|
2075
|
+
window.removeEventListener('scroll', this.onClose, true);
|
|
2076
|
+
window.removeEventListener('resize', this.onClose);
|
|
2077
|
+
this.appRef.detachView(this.ref.hostView);
|
|
2078
|
+
this.ref.destroy();
|
|
2079
|
+
this.ref = null;
|
|
2080
|
+
}
|
|
2081
|
+
ngOnDestroy() {
|
|
2082
|
+
this.closeMenu();
|
|
2083
|
+
}
|
|
2084
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctContextMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
2085
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.16", type: StrctContextMenuTrigger, isStandalone: true, selector: "[strctContextMenu]", inputs: { items: { classPropertyName: "items", publicName: "strctContextMenu", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "strctContextMenuData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuSelect: "menuSelect" }, host: { listeners: { "contextmenu": "onContextMenu($event)" } }, ngImport: i0 });
|
|
2086
|
+
}
|
|
2087
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctContextMenuTrigger, decorators: [{
|
|
2088
|
+
type: Directive,
|
|
2089
|
+
args: [{ selector: '[strctContextMenu]' }]
|
|
2090
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "strctContextMenu", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "strctContextMenuData", required: false }] }], menuSelect: [{ type: i0.Output, args: ["menuSelect"] }], onContextMenu: [{
|
|
2091
|
+
type: HostListener,
|
|
2092
|
+
args: ['contextmenu', ['$event']]
|
|
2093
|
+
}] } });
|
|
2094
|
+
|
|
1609
2095
|
/** A single wizard step. `label` names it in the step header. */
|
|
1610
2096
|
class StrctStep {
|
|
1611
2097
|
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
2098
|
+
/** When false, the wizard's Next / Finish is disabled on this step. */
|
|
2099
|
+
canAdvance = input(true, { ...(ngDevMode ? { debugName: "canAdvance" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1612
2100
|
_active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : /* istanbul ignore next */ []));
|
|
1613
2101
|
active = this._active.asReadonly();
|
|
1614
2102
|
/** @internal */
|
|
@@ -1616,7 +2104,7 @@ class StrctStep {
|
|
|
1616
2104
|
this._active.set(value);
|
|
1617
2105
|
}
|
|
1618
2106
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctStep, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1619
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctStep, isStandalone: true, selector: "strct-step", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "hidden": "!active()" }, classAttribute: "strct-step" }, ngImport: i0, template: `@if (active()) {
|
|
2107
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctStep, isStandalone: true, selector: "strct-step", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, canAdvance: { classPropertyName: "canAdvance", publicName: "canAdvance", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!active()" }, classAttribute: "strct-step" }, ngImport: i0, template: `@if (active()) {
|
|
1620
2108
|
<ng-content />
|
|
1621
2109
|
}`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1622
2110
|
}
|
|
@@ -1630,13 +2118,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
1630
2118
|
}`,
|
|
1631
2119
|
host: { class: 'strct-step', '[hidden]': '!active()' },
|
|
1632
2120
|
}]
|
|
1633
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }] } });
|
|
2121
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], canAdvance: [{ type: i0.Input, args: [{ isSignal: true, alias: "canAdvance", required: false }] }] } });
|
|
1634
2122
|
/** Multi-step flow with a numbered header and Back / Next / Finish controls. */
|
|
1635
2123
|
class StrctWizard {
|
|
1636
2124
|
steps = contentChildren(StrctStep, ...(ngDevMode ? [{ debugName: "steps" }] : /* istanbul ignore next */ []));
|
|
1637
2125
|
current = signal(0, ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
|
|
2126
|
+
/** Label for the final-step button (default "Finish"). */
|
|
2127
|
+
finishLabel = input('Finish', ...(ngDevMode ? [{ debugName: "finishLabel" }] : /* istanbul ignore next */ []));
|
|
2128
|
+
/** Disable Finish and show a busy label while an async submit is in flight. */
|
|
2129
|
+
submitting = input(false, { ...(ngDevMode ? { debugName: "submitting" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2130
|
+
/** Show a Cancel button on the left. */
|
|
2131
|
+
cancelable = input(false, { ...(ngDevMode ? { debugName: "cancelable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1638
2132
|
finished = output();
|
|
2133
|
+
cancelled = output();
|
|
2134
|
+
/** Emits the new step index after a Back / Next move. */
|
|
2135
|
+
stepChange = output();
|
|
1639
2136
|
isLast = computed(() => this.current() >= this.steps().length - 1, ...(ngDevMode ? [{ debugName: "isLast" }] : /* istanbul ignore next */ []));
|
|
2137
|
+
/** Whether the current step permits advancing (its `canAdvance`). */
|
|
2138
|
+
canAdvance = computed(() => this.steps()[this.current()]?.canAdvance() ?? true, ...(ngDevMode ? [{ debugName: "canAdvance" }] : /* istanbul ignore next */ []));
|
|
1640
2139
|
constructor() {
|
|
1641
2140
|
effect(() => {
|
|
1642
2141
|
const idx = this.current();
|
|
@@ -1644,18 +2143,23 @@ class StrctWizard {
|
|
|
1644
2143
|
});
|
|
1645
2144
|
}
|
|
1646
2145
|
next() {
|
|
1647
|
-
if (!this.isLast())
|
|
2146
|
+
if (!this.isLast() && this.canAdvance()) {
|
|
1648
2147
|
this.current.update((i) => i + 1);
|
|
2148
|
+
this.stepChange.emit(this.current());
|
|
2149
|
+
}
|
|
1649
2150
|
}
|
|
1650
2151
|
back() {
|
|
1651
|
-
if (this.current() > 0)
|
|
2152
|
+
if (this.current() > 0) {
|
|
1652
2153
|
this.current.update((i) => i - 1);
|
|
2154
|
+
this.stepChange.emit(this.current());
|
|
2155
|
+
}
|
|
1653
2156
|
}
|
|
1654
2157
|
finish() {
|
|
1655
|
-
this.
|
|
2158
|
+
if (this.canAdvance() && !this.submitting())
|
|
2159
|
+
this.finished.emit();
|
|
1656
2160
|
}
|
|
1657
2161
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctWizard, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1658
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctWizard, isStandalone: true, selector: "strct-wizard", outputs: { finished: "finished" }, host: { classAttribute: "strct-wiz" }, queries: [{ propertyName: "steps", predicate: StrctStep, isSignal: true }], ngImport: i0, template: `
|
|
2162
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctWizard, isStandalone: true, selector: "strct-wizard", inputs: { finishLabel: { classPropertyName: "finishLabel", publicName: "finishLabel", isSignal: true, isRequired: false, transformFunction: null }, submitting: { classPropertyName: "submitting", publicName: "submitting", isSignal: true, isRequired: false, transformFunction: null }, cancelable: { classPropertyName: "cancelable", publicName: "cancelable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { finished: "finished", cancelled: "cancelled", stepChange: "stepChange" }, host: { classAttribute: "strct-wiz" }, queries: [{ propertyName: "steps", predicate: StrctStep, isSignal: true }], ngImport: i0, template: `
|
|
1659
2163
|
<div class="strct-wiz__steps">
|
|
1660
2164
|
@for (step of steps(); track step; let i = $index; let last = $last) {
|
|
1661
2165
|
<div
|
|
@@ -1675,14 +2179,26 @@ class StrctWizard {
|
|
|
1675
2179
|
<div class="strct-wiz__content"><ng-content /></div>
|
|
1676
2180
|
|
|
1677
2181
|
<div class="strct-wiz__foot">
|
|
2182
|
+
@if (cancelable()) {
|
|
2183
|
+
<button strct-button variant="flat" class="strct-wiz__cancel" (click)="cancelled.emit()">
|
|
2184
|
+
Cancel
|
|
2185
|
+
</button>
|
|
2186
|
+
}
|
|
1678
2187
|
<button strct-button variant="flat" [disabled]="current() === 0" (click)="back()">Back</button>
|
|
1679
2188
|
@if (isLast()) {
|
|
1680
|
-
<button
|
|
2189
|
+
<button
|
|
2190
|
+
strct-button
|
|
2191
|
+
variant="primary"
|
|
2192
|
+
[disabled]="submitting() || !canAdvance()"
|
|
2193
|
+
(click)="finish()"
|
|
2194
|
+
>
|
|
2195
|
+
{{ submitting() ? 'Submitting…' : finishLabel() }}
|
|
2196
|
+
</button>
|
|
1681
2197
|
} @else {
|
|
1682
|
-
<button strct-button variant="primary" (click)="next()">Next</button>
|
|
2198
|
+
<button strct-button variant="primary" [disabled]="!canAdvance()" (click)="next()">Next</button>
|
|
1683
2199
|
}
|
|
1684
2200
|
</div>
|
|
1685
|
-
`, isInline: true, styles: [".strct-wiz{display:block}.strct-wiz__steps{display:flex;align-items:center;gap:6px}.strct-wiz__step{display:flex;align-items:center;gap:8px}.strct-wiz__dot{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;font-size:11px;font-weight:600;color:var(--t2);background:var(--bg-3);border:1px solid var(--b2)}.strct-wiz__label{font-size:12px;color:var(--t2)}.strct-wiz__step--active .strct-wiz__dot{background:var(--acc-m);color:var(--acc);border-color:var(--acc)}.strct-wiz__step--active .strct-wiz__label{color:var(--t1);font-weight:600}.strct-wiz__step--done .strct-wiz__dot{background:var(--acc-m);color:var(--acc);border-color:var(--acc30)}.strct-wiz__sep{flex:1;height:1px;background:var(--b2);min-width:18px}.strct-wiz__content{margin:18px 0;padding:16px;min-height:80px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;color:var(--t2);font-size:13px}.strct-wiz__foot{display:flex;justify-content:flex-end;gap:8px}\n"], dependencies: [{ kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2201
|
+
`, isInline: true, styles: [".strct-wiz{display:block}.strct-wiz__steps{display:flex;align-items:center;gap:6px}.strct-wiz__step{display:flex;align-items:center;gap:8px}.strct-wiz__dot{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;font-size:11px;font-weight:600;color:var(--t2);background:var(--bg-3);border:1px solid var(--b2)}.strct-wiz__label{font-size:12px;color:var(--t2)}.strct-wiz__step--active .strct-wiz__dot{background:var(--acc-m);color:var(--acc);border-color:var(--acc)}.strct-wiz__step--active .strct-wiz__label{color:var(--t1);font-weight:600}.strct-wiz__step--done .strct-wiz__dot{background:var(--acc-m);color:var(--acc);border-color:var(--acc30)}.strct-wiz__sep{flex:1;height:1px;background:var(--b2);min-width:18px}.strct-wiz__content{margin:18px 0;padding:16px;min-height:80px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;color:var(--t2);font-size:13px}.strct-wiz__foot{display:flex;justify-content:flex-end;gap:8px}.strct-wiz__cancel{margin-right:auto}\n"], dependencies: [{ kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1686
2202
|
}
|
|
1687
2203
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctWizard, decorators: [{
|
|
1688
2204
|
type: Component,
|
|
@@ -1706,15 +2222,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
1706
2222
|
<div class="strct-wiz__content"><ng-content /></div>
|
|
1707
2223
|
|
|
1708
2224
|
<div class="strct-wiz__foot">
|
|
2225
|
+
@if (cancelable()) {
|
|
2226
|
+
<button strct-button variant="flat" class="strct-wiz__cancel" (click)="cancelled.emit()">
|
|
2227
|
+
Cancel
|
|
2228
|
+
</button>
|
|
2229
|
+
}
|
|
1709
2230
|
<button strct-button variant="flat" [disabled]="current() === 0" (click)="back()">Back</button>
|
|
1710
2231
|
@if (isLast()) {
|
|
1711
|
-
<button
|
|
2232
|
+
<button
|
|
2233
|
+
strct-button
|
|
2234
|
+
variant="primary"
|
|
2235
|
+
[disabled]="submitting() || !canAdvance()"
|
|
2236
|
+
(click)="finish()"
|
|
2237
|
+
>
|
|
2238
|
+
{{ submitting() ? 'Submitting…' : finishLabel() }}
|
|
2239
|
+
</button>
|
|
1712
2240
|
} @else {
|
|
1713
|
-
<button strct-button variant="primary" (click)="next()">Next</button>
|
|
2241
|
+
<button strct-button variant="primary" [disabled]="!canAdvance()" (click)="next()">Next</button>
|
|
1714
2242
|
}
|
|
1715
2243
|
</div>
|
|
1716
|
-
`, host: { class: 'strct-wiz' }, styles: [".strct-wiz{display:block}.strct-wiz__steps{display:flex;align-items:center;gap:6px}.strct-wiz__step{display:flex;align-items:center;gap:8px}.strct-wiz__dot{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;font-size:11px;font-weight:600;color:var(--t2);background:var(--bg-3);border:1px solid var(--b2)}.strct-wiz__label{font-size:12px;color:var(--t2)}.strct-wiz__step--active .strct-wiz__dot{background:var(--acc-m);color:var(--acc);border-color:var(--acc)}.strct-wiz__step--active .strct-wiz__label{color:var(--t1);font-weight:600}.strct-wiz__step--done .strct-wiz__dot{background:var(--acc-m);color:var(--acc);border-color:var(--acc30)}.strct-wiz__sep{flex:1;height:1px;background:var(--b2);min-width:18px}.strct-wiz__content{margin:18px 0;padding:16px;min-height:80px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;color:var(--t2);font-size:13px}.strct-wiz__foot{display:flex;justify-content:flex-end;gap:8px}\n"] }]
|
|
1717
|
-
}], ctorParameters: () => [], propDecorators: { steps: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctStep), { isSignal: true }] }], finished: [{ type: i0.Output, args: ["finished"] }] } });
|
|
2244
|
+
`, host: { class: 'strct-wiz' }, styles: [".strct-wiz{display:block}.strct-wiz__steps{display:flex;align-items:center;gap:6px}.strct-wiz__step{display:flex;align-items:center;gap:8px}.strct-wiz__dot{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;font-size:11px;font-weight:600;color:var(--t2);background:var(--bg-3);border:1px solid var(--b2)}.strct-wiz__label{font-size:12px;color:var(--t2)}.strct-wiz__step--active .strct-wiz__dot{background:var(--acc-m);color:var(--acc);border-color:var(--acc)}.strct-wiz__step--active .strct-wiz__label{color:var(--t1);font-weight:600}.strct-wiz__step--done .strct-wiz__dot{background:var(--acc-m);color:var(--acc);border-color:var(--acc30)}.strct-wiz__sep{flex:1;height:1px;background:var(--b2);min-width:18px}.strct-wiz__content{margin:18px 0;padding:16px;min-height:80px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;color:var(--t2);font-size:13px}.strct-wiz__foot{display:flex;justify-content:flex-end;gap:8px}.strct-wiz__cancel{margin-right:auto}\n"] }]
|
|
2245
|
+
}], ctorParameters: () => [], propDecorators: { steps: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctStep), { isSignal: true }] }], finishLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "finishLabel", required: false }] }], submitting: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitting", required: false }] }], cancelable: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelable", required: false }] }], finished: [{ type: i0.Output, args: ["finished"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }], stepChange: [{ type: i0.Output, args: ["stepChange"] }] } });
|
|
1718
2246
|
|
|
1719
2247
|
/**
|
|
1720
2248
|
* Separator rule. Horizontal by default; pass `vertical` for an inline rule.
|
|
@@ -1873,6 +2401,105 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
1873
2401
|
`, host: { class: 'strct-pg', role: 'navigation', 'aria-label': 'Pagination' }, styles: [".strct-pg{display:inline-flex;align-items:center;gap:4px}.strct-pg__btn{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:30px;padding:0 7px;border-radius:6px;font-family:var(--font);font-size:13px;cursor:pointer;color:var(--t1);background:transparent;border:1px solid transparent;transition:background .14s ease,border-color .14s ease,color .14s ease}.strct-pg__btn:hover{background:var(--bg-3)}.strct-pg__btn--active{color:var(--acc);border-color:var(--acc30);background:var(--acc-m)}.strct-pg__btn:disabled{color:var(--t4);cursor:not-allowed;background:transparent}.strct-pg__nav{color:var(--t2)}.strct-pg__dots{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:30px;color:var(--t3)}\n"] }]
|
|
1874
2402
|
}], propDecorators: { total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }, { type: i0.Output, args: ["pageChange"] }] } });
|
|
1875
2403
|
|
|
2404
|
+
let fieldCounter = 0;
|
|
2405
|
+
/**
|
|
2406
|
+
* Form-field wrapper: a label (with optional required marker), the projected
|
|
2407
|
+
* control, and a hint or error message. It auto-links the control via
|
|
2408
|
+
* `aria-describedby` and sets `aria-invalid` when an error is present.
|
|
2409
|
+
*
|
|
2410
|
+
* <strct-field label="Email" required hint="We never share it." [error]="emailError()">
|
|
2411
|
+
* <input strctInput type="email" [(ngModel)]="email" />
|
|
2412
|
+
* </strct-field>
|
|
2413
|
+
*/
|
|
2414
|
+
class StrctField {
|
|
2415
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
2416
|
+
required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2417
|
+
hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
2418
|
+
/** Error message (string or first-of array); falsy clears the error state. */
|
|
2419
|
+
error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
2420
|
+
host = inject(ElementRef);
|
|
2421
|
+
n = ++fieldCounter;
|
|
2422
|
+
hintId = `strct-field-hint-${this.n}`;
|
|
2423
|
+
errorId = `strct-field-err-${this.n}`;
|
|
2424
|
+
controlId = signal('', ...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
|
|
2425
|
+
errorText = computed(() => {
|
|
2426
|
+
const e = this.error();
|
|
2427
|
+
return (Array.isArray(e) ? e[0] : e) ?? '';
|
|
2428
|
+
}, ...(ngDevMode ? [{ debugName: "errorText" }] : /* istanbul ignore next */ []));
|
|
2429
|
+
constructor() {
|
|
2430
|
+
afterNextRender(() => this.link());
|
|
2431
|
+
// Keep aria in sync as the error / hint change.
|
|
2432
|
+
effect(() => {
|
|
2433
|
+
this.errorText();
|
|
2434
|
+
this.hint();
|
|
2435
|
+
this.applyAria();
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
control() {
|
|
2439
|
+
return this.host.nativeElement.querySelector('input, select, textarea, [strctInput], [strctField]');
|
|
2440
|
+
}
|
|
2441
|
+
link() {
|
|
2442
|
+
const el = this.control();
|
|
2443
|
+
if (!el)
|
|
2444
|
+
return;
|
|
2445
|
+
if (!el.id)
|
|
2446
|
+
el.id = `strct-field-ctrl-${this.n}`;
|
|
2447
|
+
this.controlId.set(el.id);
|
|
2448
|
+
this.applyAria();
|
|
2449
|
+
}
|
|
2450
|
+
applyAria() {
|
|
2451
|
+
const el = this.control();
|
|
2452
|
+
if (!el)
|
|
2453
|
+
return;
|
|
2454
|
+
const describedBy = this.errorText() ? this.errorId : this.hint() ? this.hintId : '';
|
|
2455
|
+
if (describedBy)
|
|
2456
|
+
el.setAttribute('aria-describedby', describedBy);
|
|
2457
|
+
else
|
|
2458
|
+
el.removeAttribute('aria-describedby');
|
|
2459
|
+
if (this.errorText())
|
|
2460
|
+
el.setAttribute('aria-invalid', 'true');
|
|
2461
|
+
else
|
|
2462
|
+
el.removeAttribute('aria-invalid');
|
|
2463
|
+
}
|
|
2464
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctField, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2465
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctField, isStandalone: true, selector: "strct-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-field--invalid": "!!errorText()" }, classAttribute: "strct-field" }, ngImport: i0, template: `
|
|
2466
|
+
@if (label()) {
|
|
2467
|
+
<label class="strct-field__label" [attr.for]="controlId() || null">
|
|
2468
|
+
{{ label() }}@if (required()) {<span class="strct-field__req" aria-hidden="true">*</span>}
|
|
2469
|
+
</label>
|
|
2470
|
+
}
|
|
2471
|
+
<div class="strct-field__control"><ng-content /></div>
|
|
2472
|
+
@if (errorText()) {
|
|
2473
|
+
<div class="strct-field__msg strct-field__msg--error" [id]="errorId" role="alert">
|
|
2474
|
+
{{ errorText() }}
|
|
2475
|
+
</div>
|
|
2476
|
+
} @else if (hint()) {
|
|
2477
|
+
<div class="strct-field__msg strct-field__msg--hint" [id]="hintId">{{ hint() }}</div>
|
|
2478
|
+
}
|
|
2479
|
+
`, isInline: true, styles: [".strct-field{display:flex;flex-direction:column;gap:6px}.strct-field__label{font-size:12px;font-weight:600;color:var(--t2)}.strct-field__req{color:var(--crt);margin-left:2px}.strct-field__control{display:flex;flex-direction:column}.strct-field__msg{font-size:12px;line-height:1.4}.strct-field__msg--hint{color:var(--t3)}.strct-field__msg--error{color:var(--crt)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2480
|
+
}
|
|
2481
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctField, decorators: [{
|
|
2482
|
+
type: Component,
|
|
2483
|
+
args: [{ selector: 'strct-field', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
2484
|
+
@if (label()) {
|
|
2485
|
+
<label class="strct-field__label" [attr.for]="controlId() || null">
|
|
2486
|
+
{{ label() }}@if (required()) {<span class="strct-field__req" aria-hidden="true">*</span>}
|
|
2487
|
+
</label>
|
|
2488
|
+
}
|
|
2489
|
+
<div class="strct-field__control"><ng-content /></div>
|
|
2490
|
+
@if (errorText()) {
|
|
2491
|
+
<div class="strct-field__msg strct-field__msg--error" [id]="errorId" role="alert">
|
|
2492
|
+
{{ errorText() }}
|
|
2493
|
+
</div>
|
|
2494
|
+
} @else if (hint()) {
|
|
2495
|
+
<div class="strct-field__msg strct-field__msg--hint" [id]="hintId">{{ hint() }}</div>
|
|
2496
|
+
}
|
|
2497
|
+
`, host: {
|
|
2498
|
+
class: 'strct-field',
|
|
2499
|
+
'[class.strct-field--invalid]': '!!errorText()',
|
|
2500
|
+
}, styles: [".strct-field{display:flex;flex-direction:column;gap:6px}.strct-field__label{font-size:12px;font-weight:600;color:var(--t2)}.strct-field__req{color:var(--crt);margin-left:2px}.strct-field__control{display:flex;flex-direction:column}.strct-field__msg{font-size:12px;line-height:1.4}.strct-field__msg--hint{color:var(--t3)}.strct-field__msg--error{color:var(--crt)}\n"] }]
|
|
2501
|
+
}], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }] } });
|
|
2502
|
+
|
|
1876
2503
|
/**
|
|
1877
2504
|
* Applies the shared `.strct-control` look to a native input / textarea / select.
|
|
1878
2505
|
* <input strctInput placeholder="Name" />
|
|
@@ -4014,7 +4641,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
4014
4641
|
}] } });
|
|
4015
4642
|
|
|
4016
4643
|
/**
|
|
4017
|
-
*
|
|
4644
|
+
* Per-column cell template for `strct-table` / `strct-datagrid`. The column key
|
|
4645
|
+
* is the directive value; the row, value and column are the template context:
|
|
4646
|
+
*
|
|
4647
|
+
* <ng-template strctCell="status" let-row let-value="value">
|
|
4648
|
+
* <strct-badge [status]="row['ok'] ? 'success' : 'danger'">{{ value }}</strct-badge>
|
|
4649
|
+
* </ng-template>
|
|
4650
|
+
*/
|
|
4651
|
+
class StrctCellDef {
|
|
4652
|
+
key = input.required({ ...(ngDevMode ? { debugName: "key" } : /* istanbul ignore next */ {}), alias: 'strctCell' });
|
|
4653
|
+
template = inject(TemplateRef);
|
|
4654
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctCellDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4655
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.16", type: StrctCellDef, isStandalone: true, selector: "[strctCell]", inputs: { key: { classPropertyName: "key", publicName: "strctCell", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
|
|
4656
|
+
}
|
|
4657
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctCellDef, decorators: [{
|
|
4658
|
+
type: Directive,
|
|
4659
|
+
args: [{ selector: '[strctCell]' }]
|
|
4660
|
+
}], propDecorators: { key: [{ type: i0.Input, args: [{ isSignal: true, alias: "strctCell", required: true }] }] } });
|
|
4661
|
+
/**
|
|
4662
|
+
* Declarative data table. Cells render `row[col.key]` as text by default; supply
|
|
4663
|
+
* a `*strctCell` template per column for custom content.
|
|
4018
4664
|
* <strct-table [columns]="cols" [rows]="data" hover />
|
|
4019
4665
|
*/
|
|
4020
4666
|
class StrctTable {
|
|
@@ -4023,8 +4669,18 @@ class StrctTable {
|
|
|
4023
4669
|
striped = input(false, { ...(ngDevMode ? { debugName: "striped" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4024
4670
|
hover = input(false, { ...(ngDevMode ? { debugName: "hover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4025
4671
|
emptyText = input('No data', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
4672
|
+
cellDefs = contentChildren(StrctCellDef, ...(ngDevMode ? [{ debugName: "cellDefs" }] : /* istanbul ignore next */ []));
|
|
4673
|
+
cellMap = computed(() => {
|
|
4674
|
+
const m = new Map();
|
|
4675
|
+
for (const d of this.cellDefs())
|
|
4676
|
+
m.set(d.key(), d.template);
|
|
4677
|
+
return m;
|
|
4678
|
+
}, ...(ngDevMode ? [{ debugName: "cellMap" }] : /* istanbul ignore next */ []));
|
|
4679
|
+
cellTemplate(key) {
|
|
4680
|
+
return this.cellMap().get(key) ?? null;
|
|
4681
|
+
}
|
|
4026
4682
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4027
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctTable, isStandalone: true, selector: "strct-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, striped: { classPropertyName: "striped", publicName: "striped", isSignal: true, isRequired: false, transformFunction: null }, hover: { classPropertyName: "hover", publicName: "hover", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-table-host--striped": "striped()", "class.strct-table-host--hover": "hover()" }, classAttribute: "strct-table-host" }, ngImport: i0, template: `
|
|
4683
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctTable, isStandalone: true, selector: "strct-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, striped: { classPropertyName: "striped", publicName: "striped", isSignal: true, isRequired: false, transformFunction: null }, hover: { classPropertyName: "hover", publicName: "hover", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-table-host--striped": "striped()", "class.strct-table-host--hover": "hover()" }, classAttribute: "strct-table-host" }, queries: [{ propertyName: "cellDefs", predicate: StrctCellDef, isSignal: true }], ngImport: i0, template: `
|
|
4028
4684
|
<table class="strct-table">
|
|
4029
4685
|
<thead>
|
|
4030
4686
|
<tr>
|
|
@@ -4037,7 +4693,16 @@ class StrctTable {
|
|
|
4037
4693
|
@for (row of rows(); track $index) {
|
|
4038
4694
|
<tr>
|
|
4039
4695
|
@for (col of columns(); track col.key) {
|
|
4040
|
-
<td [style.text-align]="col.align ?? 'start'">
|
|
4696
|
+
<td [style.text-align]="col.align ?? 'start'">
|
|
4697
|
+
@if (cellTemplate(col.key); as tpl) {
|
|
4698
|
+
<ng-container
|
|
4699
|
+
[ngTemplateOutlet]="tpl"
|
|
4700
|
+
[ngTemplateOutletContext]="{ $implicit: row, value: row[col.key], column: col }"
|
|
4701
|
+
/>
|
|
4702
|
+
} @else {
|
|
4703
|
+
{{ row[col.key] }}
|
|
4704
|
+
}
|
|
4705
|
+
</td>
|
|
4041
4706
|
}
|
|
4042
4707
|
</tr>
|
|
4043
4708
|
} @empty {
|
|
@@ -4047,11 +4712,11 @@ class StrctTable {
|
|
|
4047
4712
|
}
|
|
4048
4713
|
</tbody>
|
|
4049
4714
|
</table>
|
|
4050
|
-
`, isInline: true, styles: [".strct-table-host{display:block;overflow-x:auto}.strct-table{width:100%;border-collapse:collapse;font-size:13px;border:1px solid var(--b2);border-radius:8px;overflow:hidden}.strct-table th,.strct-table td{padding:9px 13px;text-align:left;border-bottom:1px solid var(--b1)}.strct-table th{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--t2);background:var(--bg-2)}.strct-table td{color:var(--t1)}.strct-table tbody tr:last-child td{border-bottom:0}.strct-table-host--striped tbody tr:nth-child(2n) td{background:var(--bg-2)}.strct-table-host--hover tbody tr:hover td{background:var(--acc-s)}.strct-table__empty{text-align:center;color:var(--t3);padding:22px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4715
|
+
`, isInline: true, styles: [".strct-table-host{display:block;overflow-x:auto}.strct-table{width:100%;border-collapse:collapse;font-size:13px;border:1px solid var(--b2);border-radius:8px;overflow:hidden}.strct-table th,.strct-table td{padding:9px 13px;text-align:left;border-bottom:1px solid var(--b1)}.strct-table th{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--t2);background:var(--bg-2)}.strct-table td{color:var(--t1)}.strct-table tbody tr:last-child td{border-bottom:0}.strct-table-host--striped tbody tr:nth-child(2n) td{background:var(--bg-2)}.strct-table-host--hover tbody tr:hover td{background:var(--acc-s)}.strct-table__empty{text-align:center;color:var(--t3);padding:22px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4051
4716
|
}
|
|
4052
4717
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTable, decorators: [{
|
|
4053
4718
|
type: Component,
|
|
4054
|
-
args: [{ selector: 'strct-table', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4719
|
+
args: [{ selector: 'strct-table', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], template: `
|
|
4055
4720
|
<table class="strct-table">
|
|
4056
4721
|
<thead>
|
|
4057
4722
|
<tr>
|
|
@@ -4064,7 +4729,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
4064
4729
|
@for (row of rows(); track $index) {
|
|
4065
4730
|
<tr>
|
|
4066
4731
|
@for (col of columns(); track col.key) {
|
|
4067
|
-
<td [style.text-align]="col.align ?? 'start'">
|
|
4732
|
+
<td [style.text-align]="col.align ?? 'start'">
|
|
4733
|
+
@if (cellTemplate(col.key); as tpl) {
|
|
4734
|
+
<ng-container
|
|
4735
|
+
[ngTemplateOutlet]="tpl"
|
|
4736
|
+
[ngTemplateOutletContext]="{ $implicit: row, value: row[col.key], column: col }"
|
|
4737
|
+
/>
|
|
4738
|
+
} @else {
|
|
4739
|
+
{{ row[col.key] }}
|
|
4740
|
+
}
|
|
4741
|
+
</td>
|
|
4068
4742
|
}
|
|
4069
4743
|
</tr>
|
|
4070
4744
|
} @empty {
|
|
@@ -4079,7 +4753,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
4079
4753
|
'[class.strct-table-host--striped]': 'striped()',
|
|
4080
4754
|
'[class.strct-table-host--hover]': 'hover()',
|
|
4081
4755
|
}, styles: [".strct-table-host{display:block;overflow-x:auto}.strct-table{width:100%;border-collapse:collapse;font-size:13px;border:1px solid var(--b2);border-radius:8px;overflow:hidden}.strct-table th,.strct-table td{padding:9px 13px;text-align:left;border-bottom:1px solid var(--b1)}.strct-table th{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--t2);background:var(--bg-2)}.strct-table td{color:var(--t1)}.strct-table tbody tr:last-child td{border-bottom:0}.strct-table-host--striped tbody tr:nth-child(2n) td{background:var(--bg-2)}.strct-table-host--hover tbody tr:hover td{background:var(--acc-s)}.strct-table__empty{text-align:center;color:var(--t3);padding:22px}\n"] }]
|
|
4082
|
-
}], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], striped: [{ type: i0.Input, args: [{ isSignal: true, alias: "striped", required: false }] }], hover: [{ type: i0.Input, args: [{ isSignal: true, alias: "hover", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }] } });
|
|
4756
|
+
}], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], striped: [{ type: i0.Input, args: [{ isSignal: true, alias: "striped", required: false }] }], hover: [{ type: i0.Input, args: [{ isSignal: true, alias: "hover", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], cellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctCellDef), { isSignal: true }] }] } });
|
|
4083
4757
|
|
|
4084
4758
|
/**
|
|
4085
4759
|
* Marks the expandable-row detail template. The row is the template's implicit
|
|
@@ -4123,14 +4797,34 @@ class StrctDatagrid {
|
|
|
4123
4797
|
detailPane = input(false, { ...(ngDevMode ? { debugName: "detailPane" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4124
4798
|
compact = input(false, { ...(ngDevMode ? { debugName: "compact" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4125
4799
|
emptyText = input('No data', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
4800
|
+
/**
|
|
4801
|
+
* Stable row identity (property key or function). Set this for live-refreshing
|
|
4802
|
+
* data so selection, expansion and the active detail row survive re-fetches
|
|
4803
|
+
* that replace the row objects. Defaults to object identity.
|
|
4804
|
+
*/
|
|
4805
|
+
rowId = input(null, ...(ngDevMode ? [{ debugName: "rowId" }] : /* istanbul ignore next */ []));
|
|
4126
4806
|
selectionChange = output();
|
|
4127
4807
|
detailDef = contentChild(StrctRowDetailDef, ...(ngDevMode ? [{ debugName: "detailDef" }] : /* istanbul ignore next */ []));
|
|
4128
4808
|
actionBarDef = contentChild(StrctDatagridActionBar, ...(ngDevMode ? [{ debugName: "actionBarDef" }] : /* istanbul ignore next */ []));
|
|
4809
|
+
cellDefs = contentChildren(StrctCellDef, ...(ngDevMode ? [{ debugName: "cellDefs" }] : /* istanbul ignore next */ []));
|
|
4810
|
+
cellMap = computed(() => {
|
|
4811
|
+
const m = new Map();
|
|
4812
|
+
for (const d of this.cellDefs())
|
|
4813
|
+
m.set(d.key(), d.template);
|
|
4814
|
+
return m;
|
|
4815
|
+
}, ...(ngDevMode ? [{ debugName: "cellMap" }] : /* istanbul ignore next */ []));
|
|
4129
4816
|
page = signal(1, ...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
|
|
4130
4817
|
sort = signal({ key: null, dir: 'asc' }, ...(ngDevMode ? [{ debugName: "sort" }] : /* istanbul ignore next */ []));
|
|
4818
|
+
/** Selection / expansion are tracked by row id (see {@link rowId}). */
|
|
4131
4819
|
selected = signal(new Set(), ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
4132
4820
|
expandedRows = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedRows" }] : /* istanbul ignore next */ []));
|
|
4133
|
-
|
|
4821
|
+
activeId = signal(null, ...(ngDevMode ? [{ debugName: "activeId" }] : /* istanbul ignore next */ []));
|
|
4822
|
+
activeRow = computed(() => {
|
|
4823
|
+
const id = this.activeId();
|
|
4824
|
+
if (id == null)
|
|
4825
|
+
return null;
|
|
4826
|
+
return this.rows().find((r) => this.idOf(r) === id) ?? null;
|
|
4827
|
+
}, ...(ngDevMode ? [{ debugName: "activeRow" }] : /* istanbul ignore next */ []));
|
|
4134
4828
|
canExpand = computed(() => this.expandable() && !this.detailPane() && !!this.detailDef(), ...(ngDevMode ? [{ debugName: "canExpand" }] : /* istanbul ignore next */ []));
|
|
4135
4829
|
canDetail = computed(() => this.detailPane() && !!this.detailDef(), ...(ngDevMode ? [{ debugName: "canDetail" }] : /* istanbul ignore next */ []));
|
|
4136
4830
|
paneOpen = computed(() => this.detailPane() && !!this.detailDef() && !!this.activeRow(), ...(ngDevMode ? [{ debugName: "paneOpen" }] : /* istanbul ignore next */ []));
|
|
@@ -4154,9 +4848,22 @@ class StrctDatagrid {
|
|
|
4154
4848
|
selectedCount = computed(() => this.selected().size, ...(ngDevMode ? [{ debugName: "selectedCount" }] : /* istanbul ignore next */ []));
|
|
4155
4849
|
allPageSelected = computed(() => {
|
|
4156
4850
|
const rows = this.paged();
|
|
4157
|
-
return rows.length > 0 && rows.every((r) => this.selected().has(r));
|
|
4851
|
+
return rows.length > 0 && rows.every((r) => this.selected().has(this.idOf(r)));
|
|
4158
4852
|
}, ...(ngDevMode ? [{ debugName: "allPageSelected" }] : /* istanbul ignore next */ []));
|
|
4159
|
-
somePageSelected = computed(() => !this.allPageSelected() && this.paged().some((r) => this.selected().has(r)), ...(ngDevMode ? [{ debugName: "somePageSelected" }] : /* istanbul ignore next */ []));
|
|
4853
|
+
somePageSelected = computed(() => !this.allPageSelected() && this.paged().some((r) => this.selected().has(this.idOf(r))), ...(ngDevMode ? [{ debugName: "somePageSelected" }] : /* istanbul ignore next */ []));
|
|
4854
|
+
/** Resolve a row's stable identity (defaults to the row object itself). */
|
|
4855
|
+
idOf(row) {
|
|
4856
|
+
const id = this.rowId();
|
|
4857
|
+
if (id == null)
|
|
4858
|
+
return row;
|
|
4859
|
+
return typeof id === 'function' ? id(row) : row[id];
|
|
4860
|
+
}
|
|
4861
|
+
rowKey(row) {
|
|
4862
|
+
return this.idOf(row);
|
|
4863
|
+
}
|
|
4864
|
+
cellTemplate(key) {
|
|
4865
|
+
return this.cellMap().get(key) ?? null;
|
|
4866
|
+
}
|
|
4160
4867
|
constructor() {
|
|
4161
4868
|
// Keep the page in range when the data set shrinks.
|
|
4162
4869
|
effect(() => {
|
|
@@ -4177,10 +4884,11 @@ class StrctDatagrid {
|
|
|
4177
4884
|
/** Toggle the detail pane for a row (triggered by its » button, not the row,
|
|
4178
4885
|
* so row cells remain selectable for copy). */
|
|
4179
4886
|
openDetail(row) {
|
|
4180
|
-
this.
|
|
4887
|
+
const id = this.idOf(row);
|
|
4888
|
+
this.activeId.set(this.activeId() === id ? null : id);
|
|
4181
4889
|
}
|
|
4182
4890
|
closePane() {
|
|
4183
|
-
this.
|
|
4891
|
+
this.activeId.set(null);
|
|
4184
4892
|
}
|
|
4185
4893
|
sortBy(key) {
|
|
4186
4894
|
const current = this.sort();
|
|
@@ -4211,38 +4919,42 @@ class StrctDatagrid {
|
|
|
4211
4919
|
this.sortBy(key);
|
|
4212
4920
|
}
|
|
4213
4921
|
isSelected(row) {
|
|
4214
|
-
return this.selected().has(row);
|
|
4922
|
+
return this.selected().has(this.idOf(row));
|
|
4215
4923
|
}
|
|
4216
4924
|
isExpanded(row) {
|
|
4217
|
-
return this.expandedRows().has(row);
|
|
4925
|
+
return this.expandedRows().has(this.idOf(row));
|
|
4218
4926
|
}
|
|
4219
4927
|
toggleExpand(row) {
|
|
4928
|
+
const id = this.idOf(row);
|
|
4220
4929
|
const next = new Set(this.expandedRows());
|
|
4221
|
-
next.has(
|
|
4930
|
+
next.has(id) ? next.delete(id) : next.add(id);
|
|
4222
4931
|
this.expandedRows.set(next);
|
|
4223
4932
|
}
|
|
4224
4933
|
toggleRow(row) {
|
|
4934
|
+
const id = this.idOf(row);
|
|
4225
4935
|
const next = new Set(this.selected());
|
|
4226
|
-
next.has(
|
|
4936
|
+
next.has(id) ? next.delete(id) : next.add(id);
|
|
4227
4937
|
this.commitSelection(next);
|
|
4228
4938
|
}
|
|
4229
4939
|
toggleAll() {
|
|
4230
4940
|
const next = new Set(this.selected());
|
|
4231
4941
|
const rows = this.paged();
|
|
4232
4942
|
if (this.allPageSelected()) {
|
|
4233
|
-
rows.forEach((r) => next.delete(r));
|
|
4943
|
+
rows.forEach((r) => next.delete(this.idOf(r)));
|
|
4234
4944
|
}
|
|
4235
4945
|
else {
|
|
4236
|
-
rows.forEach((r) => next.add(r));
|
|
4946
|
+
rows.forEach((r) => next.add(this.idOf(r)));
|
|
4237
4947
|
}
|
|
4238
4948
|
this.commitSelection(next);
|
|
4239
4949
|
}
|
|
4240
4950
|
clearSelection() {
|
|
4241
4951
|
this.commitSelection(new Set());
|
|
4242
4952
|
}
|
|
4953
|
+
/** Emit the current row objects whose id is selected (resolved against the
|
|
4954
|
+
* latest data, so consumers always get fresh references). */
|
|
4243
4955
|
commitSelection(next) {
|
|
4244
4956
|
this.selected.set(next);
|
|
4245
|
-
this.selectionChange.emit(
|
|
4957
|
+
this.selectionChange.emit(this.rows().filter((r) => next.has(this.idOf(r))));
|
|
4246
4958
|
}
|
|
4247
4959
|
compare(a, b) {
|
|
4248
4960
|
if (typeof a === 'number' && typeof b === 'number')
|
|
@@ -4250,7 +4962,7 @@ class StrctDatagrid {
|
|
|
4250
4962
|
return String(a ?? '').localeCompare(String(b ?? ''), undefined, { numeric: true });
|
|
4251
4963
|
}
|
|
4252
4964
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDatagrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4253
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctDatagrid, isStandalone: true, selector: "strct-datagrid", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, detailPane: { classPropertyName: "detailPane", publicName: "detailPane", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { properties: { "class.strct-dg-host--compact": "compact()" }, classAttribute: "strct-dg-host" }, queries: [{ propertyName: "detailDef", first: true, predicate: StrctRowDetailDef, descendants: true, isSignal: true }, { propertyName: "actionBarDef", first: true, predicate: StrctDatagridActionBar, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
4965
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctDatagrid, isStandalone: true, selector: "strct-datagrid", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, detailPane: { classPropertyName: "detailPane", publicName: "detailPane", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, rowId: { classPropertyName: "rowId", publicName: "rowId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { properties: { "class.strct-dg-host--compact": "compact()" }, classAttribute: "strct-dg-host" }, queries: [{ propertyName: "detailDef", first: true, predicate: StrctRowDetailDef, descendants: true, isSignal: true }, { propertyName: "actionBarDef", first: true, predicate: StrctDatagridActionBar, descendants: true, isSignal: true }, { propertyName: "cellDefs", predicate: StrctCellDef, isSignal: true }], ngImport: i0, template: `
|
|
4254
4966
|
@if (actionBarDef()) {
|
|
4255
4967
|
<div class="strct-dg__toolbar"><ng-content select="[strctDatagridActionBar]" /></div>
|
|
4256
4968
|
}
|
|
@@ -4306,7 +5018,7 @@ class StrctDatagrid {
|
|
|
4306
5018
|
</tr>
|
|
4307
5019
|
</thead>
|
|
4308
5020
|
<tbody>
|
|
4309
|
-
@for (row of paged(); track
|
|
5021
|
+
@for (row of paged(); track rowKey(row)) {
|
|
4310
5022
|
<tr
|
|
4311
5023
|
[class.strct-dg__row--selected]="isSelected(row)"
|
|
4312
5024
|
[class.strct-dg__row--active]="paneOpen() && row === activeRow()"
|
|
@@ -4350,7 +5062,16 @@ class StrctDatagrid {
|
|
|
4350
5062
|
</td>
|
|
4351
5063
|
}
|
|
4352
5064
|
@for (col of visibleColumns(); track col.key) {
|
|
4353
|
-
<td [style.text-align]="col.align ?? 'start'">
|
|
5065
|
+
<td [style.text-align]="col.align ?? 'start'">
|
|
5066
|
+
@if (cellTemplate(col.key); as tpl) {
|
|
5067
|
+
<ng-container
|
|
5068
|
+
[ngTemplateOutlet]="tpl"
|
|
5069
|
+
[ngTemplateOutletContext]="{ $implicit: row, value: row[col.key], column: col }"
|
|
5070
|
+
/>
|
|
5071
|
+
} @else {
|
|
5072
|
+
{{ row[col.key] }}
|
|
5073
|
+
}
|
|
5074
|
+
</td>
|
|
4354
5075
|
}
|
|
4355
5076
|
</tr>
|
|
4356
5077
|
@if (canExpand() && isExpanded(row)) {
|
|
@@ -4462,7 +5183,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
4462
5183
|
</tr>
|
|
4463
5184
|
</thead>
|
|
4464
5185
|
<tbody>
|
|
4465
|
-
@for (row of paged(); track
|
|
5186
|
+
@for (row of paged(); track rowKey(row)) {
|
|
4466
5187
|
<tr
|
|
4467
5188
|
[class.strct-dg__row--selected]="isSelected(row)"
|
|
4468
5189
|
[class.strct-dg__row--active]="paneOpen() && row === activeRow()"
|
|
@@ -4506,7 +5227,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
4506
5227
|
</td>
|
|
4507
5228
|
}
|
|
4508
5229
|
@for (col of visibleColumns(); track col.key) {
|
|
4509
|
-
<td [style.text-align]="col.align ?? 'start'">
|
|
5230
|
+
<td [style.text-align]="col.align ?? 'start'">
|
|
5231
|
+
@if (cellTemplate(col.key); as tpl) {
|
|
5232
|
+
<ng-container
|
|
5233
|
+
[ngTemplateOutlet]="tpl"
|
|
5234
|
+
[ngTemplateOutletContext]="{ $implicit: row, value: row[col.key], column: col }"
|
|
5235
|
+
/>
|
|
5236
|
+
} @else {
|
|
5237
|
+
{{ row[col.key] }}
|
|
5238
|
+
}
|
|
5239
|
+
</td>
|
|
4510
5240
|
}
|
|
4511
5241
|
</tr>
|
|
4512
5242
|
@if (canExpand() && isExpanded(row)) {
|
|
@@ -4562,7 +5292,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
4562
5292
|
class: 'strct-dg-host',
|
|
4563
5293
|
'[class.strct-dg-host--compact]': 'compact()',
|
|
4564
5294
|
}, styles: [".strct-dg-host{display:block}.strct-dg{width:100%;border-collapse:collapse;font-size:13px;border:1px solid var(--b2);border-radius:8px;overflow:hidden}.strct-dg th,.strct-dg td{padding:9px 13px;text-align:left;border-bottom:1px solid var(--b1)}.strct-dg-host--compact .strct-dg th,.strct-dg-host--compact .strct-dg td{padding:5px 11px}.strct-dg th{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--t2);background:var(--bg-2);white-space:nowrap;-webkit-user-select:none;user-select:none}.strct-dg__th--sortable{cursor:pointer}.strct-dg__th--sortable:hover{color:var(--t1)}.strct-dg__th--sortable:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__hd{display:inline-flex;align-items:center;gap:5px}.strct-dg__sorticon{color:var(--t3)}.strct-dg__th--sortable:hover .strct-dg__sorticon{color:var(--acc)}.strct-dg td{color:var(--t1)}.strct-dg tbody tr:last-child td{border-bottom:0}.strct-dg tbody tr:not(.strct-dg__detailrow):hover td{background:var(--acc-s)}.strct-dg__row--selected td{background:var(--acc-m)}.strct-dg__sel{width:1%;white-space:nowrap}.strct-dg__sel input{accent-color:var(--acc);width:15px;height:15px;cursor:pointer}.strct-dg__expandcol,.strct-dg__expandcell{width:1%;white-space:nowrap}.strct-dg__expandbtn{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;transition:transform .15s ease,color .15s ease}.strct-dg__expandbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__expandbtn--open{transform:rotate(90deg);color:var(--acc)}.strct-dg__detailbtn{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__detailbtn:hover{color:var(--acc);background:var(--bg-3)}.strct-dg__detailbtn--active{color:var(--acc);background:var(--acc-m)}.strct-dg__detailrow td{background:var(--bg-2);padding:0}.strct-dg__detail{padding:14px 16px;font-size:13px;color:var(--t2)}.strct-dg__layout{display:flex;gap:14px;align-items:flex-start}.strct-dg__layout--paned .strct-dg{width:auto;min-width:180px;max-width:260px;flex-shrink:0}.strct-dg__row--clickable{cursor:pointer}.strct-dg__row--active td{background:var(--acc-m)}.strct-dg__layout--paned .strct-dg__row--active td:last-child{position:relative;padding-right:26px}.strct-dg__layout--paned .strct-dg__row--active td:last-child:after{content:\"\";position:absolute;right:11px;top:50%;width:6px;height:6px;border-top:1.6px solid var(--acc);border-right:1.6px solid var(--acc);transform:translateY(-50%) rotate(45deg)}.strct-dg__pane{flex:1;min-width:0;align-self:stretch;background:var(--bg-1);border:1px solid var(--b2);border-left:2px solid var(--acc);border-radius:8px;overflow:hidden;animation:strct-dg-pane-in .14s ease}.strct-dg__pane-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:11px 14px;border-bottom:1px solid var(--b1);font-size:13px;font-weight:600;color:var(--t1)}.strct-dg__pane-close{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer}.strct-dg__pane-close:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__pane-body{padding:14px 16px;font-size:13px;color:var(--t2)}@keyframes strct-dg-pane-in{0%{opacity:0;transform:translate(8px)}}.strct-dg__empty{text-align:center;color:var(--t3);padding:22px}.strct-dg__toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 10px;margin-bottom:10px;background:var(--bg-2);border:1px solid var(--b2);border-radius:8px}.strct-dg__actionbar{display:flex;align-items:center;gap:14px;padding:8px 12px;margin-bottom:10px;background:var(--acc-m);border:1px solid var(--acc30);border-radius:7px;font-size:13px;animation:strct-dg-bar-in .12s ease}.strct-dg__actionbar-count{color:var(--acc);font-weight:600}.strct-dg__actionbar-clear{border:0;background:transparent;color:var(--t2);cursor:pointer;font-size:12px;padding:2px 4px}.strct-dg__actionbar-clear:hover{color:var(--t1)}.strct-dg__actionbar-actions{display:flex;gap:8px;margin-left:auto}@keyframes strct-dg-bar-in{0%{opacity:0;transform:translateY(-4px)}}.strct-dg__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:12px;flex-wrap:wrap}.strct-dg__count{font-size:12px;color:var(--t2)}\n"] }]
|
|
4565
|
-
}], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], detailPane: [{ type: i0.Input, args: [{ isSignal: true, alias: "detailPane", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], detailDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctRowDetailDef), { isSignal: true }] }], actionBarDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctDatagridActionBar), { isSignal: true }] }] } });
|
|
5295
|
+
}], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], detailPane: [{ type: i0.Input, args: [{ isSignal: true, alias: "detailPane", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], rowId: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowId", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], detailDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctRowDetailDef), { isSignal: true }] }], actionBarDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctDatagridActionBar), { isSignal: true }] }], cellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctCellDef), { isSignal: true }] }] } });
|
|
4566
5296
|
|
|
4567
5297
|
/** Vertical timeline container. Wraps `<strct-timeline-item>` children. */
|
|
4568
5298
|
class StrctTimeline {
|
|
@@ -5371,5 +6101,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
5371
6101
|
* Generated bundle index. Do not edit.
|
|
5372
6102
|
*/
|
|
5373
6103
|
|
|
5374
|
-
export { STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_MASKS, STRCT_PALETTES, STRCT_RAW_ICONS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCascadeHost, StrctCascadeNode, StrctCascadeSelect, StrctChart, StrctCheckbox, StrctChips, StrctColorPicker, StrctCombobox, StrctContextMenu, StrctDatagrid, StrctDatagridActionBar, StrctDatepicker, StrctDivider, StrctDonut, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctFile, StrctFooter, StrctGauge, StrctHeader, StrctIcon, StrctInput, StrctInputMask, StrctInputOtp, StrctKnob, StrctLogin, StrctModal, StrctNav, StrctNavItem, StrctOverlay, StrctPagination, StrctPassword, StrctProgress, StrctRadio, StrctRadioGroup, StrctRange, StrctRating, StrctRowDetailDef, StrctShell, StrctSignpost, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctStack, StrctStackItem, StrctStep, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctThemeService, StrctThemeSwitcher, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctTooltip, StrctTree, StrctTreeNode, StrctVerticalNav, StrctWizard, registerStrctIcon };
|
|
6104
|
+
export { STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_MASKS, STRCT_PALETTES, STRCT_RAW_ICONS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCascadeHost, StrctCascadeNode, StrctCascadeSelect, StrctCellDef, StrctChart, StrctCheckbox, StrctChips, StrctColorPicker, StrctCombobox, StrctContextMenu, StrctContextMenuTrigger, StrctDatagrid, StrctDatagridActionBar, StrctDatepicker, StrctDivider, StrctDonut, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctField, StrctFile, StrctFooter, StrctGauge, StrctHeader, StrctIcon, StrctInput, StrctInputMask, StrctInputOtp, StrctKnob, StrctLogin, StrctMenuPanel, StrctModal, StrctNav, StrctNavItem, StrctOverlay, StrctPagination, StrctPassword, StrctProgress, StrctRadio, StrctRadioGroup, StrctRange, StrctRating, StrctRowDetailDef, StrctShell, StrctSignpost, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctStack, StrctStackItem, StrctStep, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctThemeService, StrctThemeSwitcher, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctTooltip, StrctTree, StrctTreeNode, StrctVerticalNav, StrctWizard, registerStrctIcon };
|
|
5375
6105
|
//# sourceMappingURL=akcelik-strct.mjs.map
|