@akcelik/strct 4.9.0 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/akcelik-strct.mjs +1690 -1553
- package/fesm2022/akcelik-strct.mjs.map +1 -1
- package/package.json +1 -1
- package/types/akcelik-strct.d.ts +42 -2
|
@@ -11853,194 +11853,468 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
11853
11853
|
}], 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 }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], cellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctCellDef), { isSignal: true }] }] } });
|
|
11854
11854
|
|
|
11855
11855
|
/**
|
|
11856
|
-
*
|
|
11856
|
+
* Numeric stepper input: − / + buttons flanking a text field. The value is
|
|
11857
|
+
* `number | null` (null = empty); typing is free-form (intermediate states
|
|
11858
|
+
* like "-" are kept), committed values clamp to `min`/`max` on blur, and the
|
|
11859
|
+
* buttons + keyboard always clamp immediately.
|
|
11857
11860
|
*
|
|
11858
|
-
*
|
|
11859
|
-
*
|
|
11860
|
-
*
|
|
11861
|
-
*
|
|
11861
|
+
* <strct-number [min]="0" [max]="64" [step]="4" [(ngModel)]="vcpus" />
|
|
11862
|
+
*
|
|
11863
|
+
* Keyboard on the field: ArrowUp/Down ±step, PageUp/Down ±10×step,
|
|
11864
|
+
* Home/End jump to min/max. The field exposes `role="spinbutton"` with
|
|
11865
|
+
* aria-valuemin/max/now so assistive tech announces it as a number control.
|
|
11862
11866
|
*/
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
|
|
11869
|
-
|
|
11867
|
+
class StrctNumber {
|
|
11868
|
+
/** Minimum allowed value; null = unbounded. */
|
|
11869
|
+
min = input(null, /* @ts-ignore */
|
|
11870
|
+
...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
11871
|
+
/** Maximum allowed value; null = unbounded. */
|
|
11872
|
+
max = input(null, /* @ts-ignore */
|
|
11873
|
+
...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
11874
|
+
/** Step increment for buttons and Arrow keys (PageUp/Down use 10×). */
|
|
11875
|
+
step = input(1, /* @ts-ignore */
|
|
11876
|
+
...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
11877
|
+
/** Placeholder text when empty. */
|
|
11878
|
+
placeholder = input('', /* @ts-ignore */
|
|
11879
|
+
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
11880
|
+
/** Accessible label of the + button (localizable). */
|
|
11881
|
+
incrementLabel = input('Increment', /* @ts-ignore */
|
|
11882
|
+
...(ngDevMode ? [{ debugName: "incrementLabel" }] : /* istanbul ignore next */ []));
|
|
11883
|
+
/** Accessible label of the − button (localizable). */
|
|
11884
|
+
decrementLabel = input('Decrement', /* @ts-ignore */
|
|
11885
|
+
...(ngDevMode ? [{ debugName: "decrementLabel" }] : /* istanbul ignore next */ []));
|
|
11886
|
+
/** Static disable; forms' setDisabledState also drives the disabled state. */
|
|
11887
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
11888
|
+
value = signal(null, /* @ts-ignore */
|
|
11889
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
11890
|
+
/** Raw field text — may hold an unparseable intermediate state while typing. */
|
|
11891
|
+
text = signal('', /* @ts-ignore */
|
|
11892
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
11893
|
+
/** Disabled state pushed by the forms API (setDisabledState). */
|
|
11894
|
+
cvaDisabled = signal(false, /* @ts-ignore */
|
|
11895
|
+
...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
11896
|
+
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
|
|
11897
|
+
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
11898
|
+
/** At/below min (or min unset → never) — greys out the − button. */
|
|
11899
|
+
atMin = computed(() => {
|
|
11900
|
+
const v = this.value();
|
|
11901
|
+
const m = this.min();
|
|
11902
|
+
return v !== null && m !== null && v <= m;
|
|
11903
|
+
}, /* @ts-ignore */
|
|
11904
|
+
...(ngDevMode ? [{ debugName: "atMin" }] : /* istanbul ignore next */ []));
|
|
11905
|
+
/** At/above max (or max unset → never) — greys out the + button. */
|
|
11906
|
+
atMax = computed(() => {
|
|
11907
|
+
const v = this.value();
|
|
11908
|
+
const m = this.max();
|
|
11909
|
+
return v !== null && m !== null && v >= m;
|
|
11910
|
+
}, /* @ts-ignore */
|
|
11911
|
+
...(ngDevMode ? [{ debugName: "atMax" }] : /* istanbul ignore next */ []));
|
|
11912
|
+
onChange = () => { };
|
|
11913
|
+
onTouched = () => { };
|
|
11914
|
+
onInput(event) {
|
|
11915
|
+
const raw = event.target.value;
|
|
11916
|
+
this.text.set(raw);
|
|
11917
|
+
const trimmed = raw.trim();
|
|
11918
|
+
if (trimmed === '') {
|
|
11919
|
+
this.value.set(null);
|
|
11920
|
+
this.onChange(null);
|
|
11921
|
+
return;
|
|
11922
|
+
}
|
|
11923
|
+
const parsed = Number(trimmed);
|
|
11924
|
+
// Intermediate states ("-", "1.", "2e") don't parse yet — keep the text,
|
|
11925
|
+
// hold the last value; clamping happens on blur.
|
|
11926
|
+
if (Number.isFinite(parsed)) {
|
|
11927
|
+
this.value.set(parsed);
|
|
11928
|
+
this.onChange(parsed);
|
|
11929
|
+
}
|
|
11870
11930
|
}
|
|
11871
|
-
|
|
11872
|
-
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
11931
|
+
onKeydown(event) {
|
|
11932
|
+
if (this.isDisabled())
|
|
11933
|
+
return;
|
|
11934
|
+
switch (event.key) {
|
|
11935
|
+
case 'ArrowUp':
|
|
11936
|
+
event.preventDefault();
|
|
11937
|
+
this.stepBy(1);
|
|
11938
|
+
break;
|
|
11939
|
+
case 'ArrowDown':
|
|
11940
|
+
event.preventDefault();
|
|
11941
|
+
this.stepBy(-1);
|
|
11942
|
+
break;
|
|
11943
|
+
case 'PageUp':
|
|
11944
|
+
event.preventDefault();
|
|
11945
|
+
this.stepBy(1, 10);
|
|
11946
|
+
break;
|
|
11947
|
+
case 'PageDown':
|
|
11948
|
+
event.preventDefault();
|
|
11949
|
+
this.stepBy(-1, 10);
|
|
11950
|
+
break;
|
|
11951
|
+
case 'Home': {
|
|
11952
|
+
const m = this.min();
|
|
11953
|
+
if (m === null)
|
|
11954
|
+
return;
|
|
11955
|
+
event.preventDefault();
|
|
11956
|
+
this.commitTo(m);
|
|
11957
|
+
break;
|
|
11958
|
+
}
|
|
11959
|
+
case 'End': {
|
|
11960
|
+
const m = this.max();
|
|
11961
|
+
if (m === null)
|
|
11962
|
+
return;
|
|
11963
|
+
event.preventDefault();
|
|
11964
|
+
this.commitTo(m);
|
|
11965
|
+
break;
|
|
11966
|
+
}
|
|
11894
11967
|
}
|
|
11895
|
-
return out;
|
|
11896
|
-
};
|
|
11897
|
-
for (const { name, data } of entries) {
|
|
11898
|
-
const nameBytes = enc.encode(name);
|
|
11899
|
-
const crc = crc32(data);
|
|
11900
|
-
const local = cat(u32(0x04034b50), u16(20), // version needed
|
|
11901
|
-
u16(0), // flags
|
|
11902
|
-
u16(0), // method: stored
|
|
11903
|
-
u16(0), u16(0), // mod time/date
|
|
11904
|
-
u32(crc), u32(data.length), u32(data.length), u16(nameBytes.length), u16(0), nameBytes, data);
|
|
11905
|
-
central.push(cat(u32(0x02014b50), u16(20), u16(20), u16(0), u16(0), u16(0), u16(0), u32(crc), u32(data.length), u32(data.length), u16(nameBytes.length), u16(0), u16(0), u16(0), u16(0), u32(0), u32(offset), nameBytes));
|
|
11906
|
-
chunks.push(local);
|
|
11907
|
-
offset += local.length;
|
|
11908
11968
|
}
|
|
11909
|
-
|
|
11910
|
-
|
|
11911
|
-
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
.replace(/</g, '<')
|
|
11917
|
-
.replace(/>/g, '>')
|
|
11918
|
-
.replace(/"/g, '"');
|
|
11919
|
-
/** A1-style column ref: 0 → A, 26 → AA … */
|
|
11920
|
-
function colRef(i) {
|
|
11921
|
-
let s = '';
|
|
11922
|
-
for (let n = i; n >= 0; n = Math.floor(n / 26) - 1)
|
|
11923
|
-
s = String.fromCharCode(65 + (n % 26)) + s;
|
|
11924
|
-
return s;
|
|
11925
|
-
}
|
|
11926
|
-
/**
|
|
11927
|
-
* Build a single-sheet workbook: `header` as a bold-free first row, then
|
|
11928
|
-
* `rows`. Numbers stay numeric; everything else exports as inline strings.
|
|
11929
|
-
*/
|
|
11930
|
-
function buildXlsx(header, rows, sheetName = 'Data') {
|
|
11931
|
-
const rowXml = (cells, r) => `<row r="${r}">` +
|
|
11932
|
-
cells
|
|
11933
|
-
.map((v, c) => {
|
|
11934
|
-
const ref = `${colRef(c)}${r}`;
|
|
11935
|
-
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
11936
|
-
return `<c r="${ref}"><v>${v}</v></c>`;
|
|
11969
|
+
/** Commit the pending text on blur: parse, clamp, restore on garbage. */
|
|
11970
|
+
onBlur() {
|
|
11971
|
+
const trimmed = this.text().trim();
|
|
11972
|
+
if (trimmed === '') {
|
|
11973
|
+
if (this.value() !== null) {
|
|
11974
|
+
this.value.set(null);
|
|
11975
|
+
this.onChange(null);
|
|
11937
11976
|
}
|
|
11938
|
-
|
|
11939
|
-
}
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
}
|
|
11974
|
-
|
|
11975
|
-
const
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
}
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
11977
|
+
this.text.set('');
|
|
11978
|
+
}
|
|
11979
|
+
else {
|
|
11980
|
+
const parsed = Number(trimmed);
|
|
11981
|
+
if (Number.isFinite(parsed)) {
|
|
11982
|
+
const clamped = this.clamp(parsed);
|
|
11983
|
+
this.value.set(clamped);
|
|
11984
|
+
if (clamped !== parsed)
|
|
11985
|
+
this.onChange(clamped);
|
|
11986
|
+
this.text.set(String(clamped));
|
|
11987
|
+
}
|
|
11988
|
+
else {
|
|
11989
|
+
// Unparseable — restore the last committed value.
|
|
11990
|
+
this.text.set(this.value() === null ? '' : String(this.value()));
|
|
11991
|
+
}
|
|
11992
|
+
}
|
|
11993
|
+
this.onTouched();
|
|
11994
|
+
}
|
|
11995
|
+
/** Button / keyboard step: an empty field first lands on min (or 0). */
|
|
11996
|
+
stepBy(dir, factor = 1) {
|
|
11997
|
+
if (this.isDisabled())
|
|
11998
|
+
return;
|
|
11999
|
+
const v = this.value();
|
|
12000
|
+
if (v === null) {
|
|
12001
|
+
this.commitTo(this.min() ?? 0);
|
|
12002
|
+
return;
|
|
12003
|
+
}
|
|
12004
|
+
this.commitTo(v + dir * factor * this.step());
|
|
12005
|
+
}
|
|
12006
|
+
commitTo(n) {
|
|
12007
|
+
const clamped = this.clamp(this.snap(n));
|
|
12008
|
+
this.value.set(clamped);
|
|
12009
|
+
this.text.set(String(clamped));
|
|
12010
|
+
this.onChange(clamped);
|
|
12011
|
+
this.onTouched();
|
|
12012
|
+
}
|
|
12013
|
+
clamp(n) {
|
|
12014
|
+
const lo = this.min();
|
|
12015
|
+
const hi = this.max();
|
|
12016
|
+
if (lo !== null && n < lo)
|
|
12017
|
+
return lo;
|
|
12018
|
+
if (hi !== null && n > hi)
|
|
12019
|
+
return hi;
|
|
12020
|
+
return n;
|
|
12021
|
+
}
|
|
12022
|
+
/** Trim float dust from step arithmetic (0.1 + 0.2), not a grid snap. */
|
|
12023
|
+
snap(n) {
|
|
12024
|
+
const step = this.step();
|
|
12025
|
+
if (!Number.isFinite(step) || step <= 0)
|
|
12026
|
+
return n;
|
|
12027
|
+
const decimals = (String(step).split('.')[1] ?? '').length;
|
|
12028
|
+
return Number(n.toFixed(Math.min(decimals + 2, 12)));
|
|
12029
|
+
}
|
|
12030
|
+
writeValue(value) {
|
|
12031
|
+
const parsed = value === null || value === undefined ? null : Number(value);
|
|
12032
|
+
const v = parsed !== null && Number.isFinite(parsed) ? this.clamp(parsed) : null;
|
|
12033
|
+
this.value.set(v);
|
|
12034
|
+
this.text.set(v === null ? '' : String(v));
|
|
12035
|
+
}
|
|
12036
|
+
registerOnChange(fn) {
|
|
12037
|
+
this.onChange = fn;
|
|
12038
|
+
}
|
|
12039
|
+
registerOnTouched(fn) {
|
|
12040
|
+
this.onTouched = fn;
|
|
12041
|
+
}
|
|
12042
|
+
setDisabledState(isDisabled) {
|
|
12043
|
+
this.cvaDisabled.set(isDisabled);
|
|
12044
|
+
}
|
|
12045
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctNumber, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
12046
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctNumber, isStandalone: true, selector: "strct-number", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, incrementLabel: { classPropertyName: "incrementLabel", publicName: "incrementLabel", isSignal: true, isRequired: false, transformFunction: null }, decrementLabel: { classPropertyName: "decrementLabel", publicName: "decrementLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "strct-num" }, providers: [
|
|
12047
|
+
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StrctNumber), multi: true },
|
|
12048
|
+
], ngImport: i0, template: `
|
|
12049
|
+
<div class="strct-num__wrap">
|
|
12050
|
+
<button
|
|
12051
|
+
type="button"
|
|
12052
|
+
class="strct-num__btn"
|
|
12053
|
+
[attr.aria-label]="decrementLabel()"
|
|
12054
|
+
[disabled]="isDisabled() || atMin()"
|
|
12055
|
+
(click)="stepBy(-1)"
|
|
12056
|
+
>
|
|
12057
|
+
<strct-icon strictName="minus" [size]="12" [strokeWidth]="1.8" />
|
|
12058
|
+
</button>
|
|
12059
|
+
<input
|
|
12060
|
+
type="text"
|
|
12061
|
+
inputmode="decimal"
|
|
12062
|
+
class="strct-control strct-num__input"
|
|
12063
|
+
role="spinbutton"
|
|
12064
|
+
[attr.aria-valuemin]="min()"
|
|
12065
|
+
[attr.aria-valuemax]="max()"
|
|
12066
|
+
[attr.aria-valuenow]="value()"
|
|
12067
|
+
[placeholder]="placeholder()"
|
|
12068
|
+
[value]="text()"
|
|
12069
|
+
[disabled]="isDisabled()"
|
|
12070
|
+
(input)="onInput($event)"
|
|
12071
|
+
(keydown)="onKeydown($event)"
|
|
12072
|
+
(blur)="onBlur()"
|
|
12073
|
+
/>
|
|
12074
|
+
<button
|
|
12075
|
+
type="button"
|
|
12076
|
+
class="strct-num__btn"
|
|
12077
|
+
[attr.aria-label]="incrementLabel()"
|
|
12078
|
+
[disabled]="isDisabled() || atMax()"
|
|
12079
|
+
(click)="stepBy(1)"
|
|
12080
|
+
>
|
|
12081
|
+
<strct-icon strictName="plus" [size]="12" [strokeWidth]="1.8" />
|
|
12082
|
+
</button>
|
|
12083
|
+
</div>
|
|
12084
|
+
`, isInline: true, styles: [".strct-num{display:block;width:100%}.strct-num__wrap{display:flex;align-items:stretch;gap:var(--space-1)}.strct-num__input{flex:1;min-width:0;text-align:center;font-variant-numeric:tabular-nums}.strct-num__btn{display:inline-flex;align-items:center;justify-content:center;flex:none;padding-inline:var(--space-2);color:var(--t2);background:var(--bg-2);border:1px solid var(--b2);border-radius:var(--radius-md);cursor:pointer;transition:border-color .14s ease,color .14s ease,background .14s ease}.strct-num__btn:hover:not(:disabled){color:var(--t1);border-color:var(--b3);background:var(--bg-3)}.strct-num__btn:disabled{opacity:var(--disabled-opacity);cursor:not-allowed}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
12085
|
+
}
|
|
12086
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctNumber, decorators: [{
|
|
12087
|
+
type: Component,
|
|
12088
|
+
args: [{ selector: 'strct-number', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], providers: [
|
|
12089
|
+
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StrctNumber), multi: true },
|
|
12090
|
+
], template: `
|
|
12091
|
+
<div class="strct-num__wrap">
|
|
12092
|
+
<button
|
|
12093
|
+
type="button"
|
|
12094
|
+
class="strct-num__btn"
|
|
12095
|
+
[attr.aria-label]="decrementLabel()"
|
|
12096
|
+
[disabled]="isDisabled() || atMin()"
|
|
12097
|
+
(click)="stepBy(-1)"
|
|
12098
|
+
>
|
|
12099
|
+
<strct-icon strictName="minus" [size]="12" [strokeWidth]="1.8" />
|
|
12100
|
+
</button>
|
|
12101
|
+
<input
|
|
12102
|
+
type="text"
|
|
12103
|
+
inputmode="decimal"
|
|
12104
|
+
class="strct-control strct-num__input"
|
|
12105
|
+
role="spinbutton"
|
|
12106
|
+
[attr.aria-valuemin]="min()"
|
|
12107
|
+
[attr.aria-valuemax]="max()"
|
|
12108
|
+
[attr.aria-valuenow]="value()"
|
|
12109
|
+
[placeholder]="placeholder()"
|
|
12110
|
+
[value]="text()"
|
|
12111
|
+
[disabled]="isDisabled()"
|
|
12112
|
+
(input)="onInput($event)"
|
|
12113
|
+
(keydown)="onKeydown($event)"
|
|
12114
|
+
(blur)="onBlur()"
|
|
12115
|
+
/>
|
|
12116
|
+
<button
|
|
12117
|
+
type="button"
|
|
12118
|
+
class="strct-num__btn"
|
|
12119
|
+
[attr.aria-label]="incrementLabel()"
|
|
12120
|
+
[disabled]="isDisabled() || atMax()"
|
|
12121
|
+
(click)="stepBy(1)"
|
|
12122
|
+
>
|
|
12123
|
+
<strct-icon strictName="plus" [size]="12" [strokeWidth]="1.8" />
|
|
12124
|
+
</button>
|
|
12125
|
+
</div>
|
|
12126
|
+
`, host: { class: 'strct-num' }, styles: [".strct-num{display:block;width:100%}.strct-num__wrap{display:flex;align-items:stretch;gap:var(--space-1)}.strct-num__input{flex:1;min-width:0;text-align:center;font-variant-numeric:tabular-nums}.strct-num__btn{display:inline-flex;align-items:center;justify-content:center;flex:none;padding-inline:var(--space-2);color:var(--t2);background:var(--bg-2);border:1px solid var(--b2);border-radius:var(--radius-md);cursor:pointer;transition:border-color .14s ease,color .14s ease,background .14s ease}.strct-num__btn:hover:not(:disabled){color:var(--t1);border-color:var(--b3);background:var(--bg-3)}.strct-num__btn:disabled{opacity:var(--disabled-opacity);cursor:not-allowed}\n"] }]
|
|
12127
|
+
}], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], incrementLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "incrementLabel", required: false }] }], decrementLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "decrementLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
12128
|
+
|
|
12129
|
+
/**
|
|
12130
|
+
* Minimal, dependency-free XLSX writer for the datagrid's Excel export.
|
|
12131
|
+
*
|
|
12132
|
+
* An .xlsx file is a ZIP of SpreadsheetML parts. This builds the five
|
|
12133
|
+
* required parts with inline strings and zips them with STORED (uncompressed)
|
|
12134
|
+
* entries — no runtime dependency, byte-accurate CRC32, opens in Excel,
|
|
12135
|
+
* LibreOffice and Google Sheets.
|
|
12005
12136
|
*/
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12137
|
+
const CRC_TABLE = (() => {
|
|
12138
|
+
const t = new Uint32Array(256);
|
|
12139
|
+
for (let n = 0; n < 256; n++) {
|
|
12140
|
+
let c = n;
|
|
12141
|
+
for (let k = 0; k < 8; k++)
|
|
12142
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
12143
|
+
t[n] = c >>> 0;
|
|
12144
|
+
}
|
|
12145
|
+
return t;
|
|
12146
|
+
})();
|
|
12147
|
+
function crc32(data) {
|
|
12148
|
+
let c = 0xffffffff;
|
|
12149
|
+
for (const byte of data)
|
|
12150
|
+
c = CRC_TABLE[(c ^ byte) & 0xff] ^ (c >>> 8);
|
|
12151
|
+
return (c ^ 0xffffffff) >>> 0;
|
|
12010
12152
|
}
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12153
|
+
const enc = new TextEncoder();
|
|
12154
|
+
/** ZIP with STORED entries (no compression — spreadsheets are small). */
|
|
12155
|
+
function zip(entries) {
|
|
12156
|
+
const chunks = [];
|
|
12157
|
+
const central = [];
|
|
12158
|
+
let offset = 0;
|
|
12159
|
+
const u16 = (n) => new Uint8Array([n & 0xff, (n >> 8) & 0xff]);
|
|
12160
|
+
const u32 = (n) => new Uint8Array([n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]);
|
|
12161
|
+
const cat = (...parts) => {
|
|
12162
|
+
const total = parts.reduce((s, p) => s + p.length, 0);
|
|
12163
|
+
const out = new Uint8Array(total);
|
|
12164
|
+
let o = 0;
|
|
12165
|
+
for (const p of parts) {
|
|
12166
|
+
out.set(p, o);
|
|
12167
|
+
o += p.length;
|
|
12168
|
+
}
|
|
12169
|
+
return out;
|
|
12170
|
+
};
|
|
12171
|
+
for (const { name, data } of entries) {
|
|
12172
|
+
const nameBytes = enc.encode(name);
|
|
12173
|
+
const crc = crc32(data);
|
|
12174
|
+
const local = cat(u32(0x04034b50), u16(20), // version needed
|
|
12175
|
+
u16(0), // flags
|
|
12176
|
+
u16(0), // method: stored
|
|
12177
|
+
u16(0), u16(0), // mod time/date
|
|
12178
|
+
u32(crc), u32(data.length), u32(data.length), u16(nameBytes.length), u16(0), nameBytes, data);
|
|
12179
|
+
central.push(cat(u32(0x02014b50), u16(20), u16(20), u16(0), u16(0), u16(0), u16(0), u32(crc), u32(data.length), u32(data.length), u16(nameBytes.length), u16(0), u16(0), u16(0), u16(0), u32(0), u32(offset), nameBytes));
|
|
12180
|
+
chunks.push(local);
|
|
12181
|
+
offset += local.length;
|
|
12182
|
+
}
|
|
12183
|
+
const centralStart = offset;
|
|
12184
|
+
const centralBytes = cat(...central);
|
|
12185
|
+
const end = cat(u32(0x06054b50), u16(0), u16(0), u16(entries.length), u16(entries.length), u32(centralBytes.length), u32(centralStart), u16(0));
|
|
12186
|
+
return cat(...chunks, centralBytes, end);
|
|
12019
12187
|
}
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12188
|
+
const xml = (s) => String(s ?? '')
|
|
12189
|
+
.replace(/&/g, '&')
|
|
12190
|
+
.replace(/</g, '<')
|
|
12191
|
+
.replace(/>/g, '>')
|
|
12192
|
+
.replace(/"/g, '"');
|
|
12193
|
+
/** A1-style column ref: 0 → A, 26 → AA … */
|
|
12194
|
+
function colRef(i) {
|
|
12195
|
+
let s = '';
|
|
12196
|
+
for (let n = i; n >= 0; n = Math.floor(n / 26) - 1)
|
|
12197
|
+
s = String.fromCharCode(65 + (n % 26)) + s;
|
|
12198
|
+
return s;
|
|
12028
12199
|
}
|
|
12029
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDatagridActionBar, decorators: [{
|
|
12030
|
-
type: Directive,
|
|
12031
|
-
args: [{ selector: '[strctDatagridActionBar]' }]
|
|
12032
|
-
}] });
|
|
12033
12200
|
/**
|
|
12034
|
-
*
|
|
12035
|
-
*
|
|
12036
|
-
* rows, a batch action bar and built-in paging.
|
|
12037
|
-
* <strct-datagrid [columns]="cols" [rows]="data" selectable expandable [pageSize]="10">
|
|
12038
|
-
* <button strct-button strctDatagridActions size="sm">Migrate</button>
|
|
12039
|
-
* <ng-template strctRowDetail let-row> … </ng-template>
|
|
12040
|
-
* </strct-datagrid>
|
|
12201
|
+
* Build a single-sheet workbook: `header` as a bold-free first row, then
|
|
12202
|
+
* `rows`. Numbers stay numeric; everything else exports as inline strings.
|
|
12041
12203
|
*/
|
|
12042
|
-
|
|
12043
|
-
|
|
12204
|
+
function buildXlsx(header, rows, sheetName = 'Data') {
|
|
12205
|
+
const rowXml = (cells, r) => `<row r="${r}">` +
|
|
12206
|
+
cells
|
|
12207
|
+
.map((v, c) => {
|
|
12208
|
+
const ref = `${colRef(c)}${r}`;
|
|
12209
|
+
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
12210
|
+
return `<c r="${ref}"><v>${v}</v></c>`;
|
|
12211
|
+
}
|
|
12212
|
+
return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${xml(v)}</t></is></c>`;
|
|
12213
|
+
})
|
|
12214
|
+
.join('') +
|
|
12215
|
+
'</row>';
|
|
12216
|
+
const sheet = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
|
|
12217
|
+
`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>` +
|
|
12218
|
+
rowXml(header, 1) +
|
|
12219
|
+
rows.map((cells, i) => rowXml(cells, i + 2)).join('') +
|
|
12220
|
+
`</sheetData></worksheet>`;
|
|
12221
|
+
const workbook = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
|
|
12222
|
+
`<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ` +
|
|
12223
|
+
`xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">` +
|
|
12224
|
+
`<sheets><sheet name="${xml(sheetName)}" sheetId="1" r:id="rId1"/></sheets></workbook>`;
|
|
12225
|
+
const workbookRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
|
|
12226
|
+
`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` +
|
|
12227
|
+
`<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>` +
|
|
12228
|
+
`</Relationships>`;
|
|
12229
|
+
const rootRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
|
|
12230
|
+
`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` +
|
|
12231
|
+
`<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>` +
|
|
12232
|
+
`</Relationships>`;
|
|
12233
|
+
const contentTypes = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
|
|
12234
|
+
`<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">` +
|
|
12235
|
+
`<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>` +
|
|
12236
|
+
`<Default Extension="xml" ContentType="application/xml"/>` +
|
|
12237
|
+
`<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>` +
|
|
12238
|
+
`<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>` +
|
|
12239
|
+
`</Types>`;
|
|
12240
|
+
return zip([
|
|
12241
|
+
{ name: '[Content_Types].xml', data: enc.encode(contentTypes) },
|
|
12242
|
+
{ name: '_rels/.rels', data: enc.encode(rootRels) },
|
|
12243
|
+
{ name: 'xl/workbook.xml', data: enc.encode(workbook) },
|
|
12244
|
+
{ name: 'xl/_rels/workbook.xml.rels', data: enc.encode(workbookRels) },
|
|
12245
|
+
{ name: 'xl/worksheets/sheet1.xml', data: enc.encode(sheet) },
|
|
12246
|
+
]);
|
|
12247
|
+
}
|
|
12248
|
+
|
|
12249
|
+
const DG_LABELS = {
|
|
12250
|
+
row: 'row',
|
|
12251
|
+
rows: 'rows',
|
|
12252
|
+
selected: 'selected',
|
|
12253
|
+
selectAll: 'Select all rows on this page',
|
|
12254
|
+
selectRow: 'Select row',
|
|
12255
|
+
selectRowFor: (id) => `Select row ${id}`,
|
|
12256
|
+
selectAllInGroup: (group) => `Select all in ${group}`,
|
|
12257
|
+
openDetail: 'Open detail',
|
|
12258
|
+
toggleDetail: 'Toggle detail',
|
|
12259
|
+
closeDetail: 'Close detail',
|
|
12260
|
+
rowActions: 'Row actions',
|
|
12261
|
+
chooseColumns: 'Choose columns',
|
|
12262
|
+
refresh: 'Refresh data',
|
|
12263
|
+
actions: 'Actions',
|
|
12264
|
+
filterColumn: 'Filter',
|
|
12265
|
+
filterPlaceholder: 'Filter…',
|
|
12266
|
+
clearFilter: 'Clear filter',
|
|
12267
|
+
editCell: 'Edit',
|
|
12268
|
+
toggleChildren: 'Toggle children',
|
|
12269
|
+
quickFilter: 'Quick filter',
|
|
12270
|
+
};
|
|
12271
|
+
let datagridCounter = 0;
|
|
12272
|
+
/** Utility-column widths used when sticky columns are active (px). */
|
|
12273
|
+
const UTIL_W = { detail: 36, expand: 36, sel: 40 };
|
|
12274
|
+
/** Fallback width for a sticky column without an explicit px width. */
|
|
12275
|
+
const STICKY_FALLBACK_W = 120;
|
|
12276
|
+
/**
|
|
12277
|
+
* Marks the expandable-row detail template. The row is the template's implicit
|
|
12278
|
+
* context (`let-row`, or `let-row="row"`): `<ng-template strctRowDetail let-row> … {{ row['name'] }} … </ng-template>`.
|
|
12279
|
+
*/
|
|
12280
|
+
class StrctRowDetailDef {
|
|
12281
|
+
template = inject(TemplateRef);
|
|
12282
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctRowDetailDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
12283
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctRowDetailDef, isStandalone: true, selector: "[strctRowDetail]", ngImport: i0 });
|
|
12284
|
+
}
|
|
12285
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctRowDetailDef, decorators: [{
|
|
12286
|
+
type: Directive,
|
|
12287
|
+
args: [{ selector: '[strctRowDetail]' }]
|
|
12288
|
+
}] });
|
|
12289
|
+
/** Muted note inside the action bar, before the buttons. */
|
|
12290
|
+
class StrctDatagridActionBarCaption {
|
|
12291
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDatagridActionBarCaption, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
12292
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDatagridActionBarCaption, isStandalone: true, selector: "[strctDatagridActionBarCaption]", ngImport: i0 });
|
|
12293
|
+
}
|
|
12294
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDatagridActionBarCaption, decorators: [{
|
|
12295
|
+
type: Directive,
|
|
12296
|
+
args: [{ selector: '[strctDatagridActionBarCaption]' }]
|
|
12297
|
+
}] });
|
|
12298
|
+
/** Marks the persistent action-bar (toolbar) content shown above the grid. */
|
|
12299
|
+
class StrctDatagridActionBar {
|
|
12300
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDatagridActionBar, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
12301
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDatagridActionBar, isStandalone: true, selector: "[strctDatagridActionBar]", ngImport: i0 });
|
|
12302
|
+
}
|
|
12303
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDatagridActionBar, decorators: [{
|
|
12304
|
+
type: Directive,
|
|
12305
|
+
args: [{ selector: '[strctDatagridActionBar]' }]
|
|
12306
|
+
}] });
|
|
12307
|
+
/**
|
|
12308
|
+
* Interactive data table: sortable columns, row selection (Shift-click a row
|
|
12309
|
+
* checkbox to carry its new state across a whole block), expandable detail
|
|
12310
|
+
* rows, a batch action bar and built-in paging.
|
|
12311
|
+
* <strct-datagrid [columns]="cols" [rows]="data" selectable expandable [pageSize]="10">
|
|
12312
|
+
* <button strct-button strctDatagridActions size="sm">Migrate</button>
|
|
12313
|
+
* <ng-template strctRowDetail let-row> … </ng-template>
|
|
12314
|
+
* </strct-datagrid>
|
|
12315
|
+
*/
|
|
12316
|
+
class StrctDatagrid {
|
|
12317
|
+
/** Column definitions. */
|
|
12044
12318
|
columns = input.required(/* @ts-ignore */
|
|
12045
12319
|
...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
12046
12320
|
/** Data rows. */
|
|
@@ -12216,8 +12490,9 @@ class StrctDatagrid {
|
|
|
12216
12490
|
rowAction = output();
|
|
12217
12491
|
/** Server-side data request (lazy mode): load this page / sort and set `rows`. */
|
|
12218
12492
|
lazyLoad = output();
|
|
12219
|
-
/** An editable cell was committed (Enter / blur
|
|
12220
|
-
* rows — apply
|
|
12493
|
+
/** An editable cell was committed (Enter / blur, or choosing in a `select`
|
|
12494
|
+
* editor). The grid does not mutate rows — apply the value to your data and
|
|
12495
|
+
* pass the updated array back in. */
|
|
12221
12496
|
cellEdit = output();
|
|
12222
12497
|
menu = inject(StrctMenuService);
|
|
12223
12498
|
detailDef = contentChild(StrctRowDetailDef, /* @ts-ignore */
|
|
@@ -12592,9 +12867,80 @@ class StrctDatagrid {
|
|
|
12592
12867
|
const e = this.editing();
|
|
12593
12868
|
return !!e && e.rowKey === this.rowKey(row) && e.colKey === col.key;
|
|
12594
12869
|
}
|
|
12870
|
+
/** The value a typed editor holds while it is open (committed on Enter / blur). */
|
|
12871
|
+
editDraft = signal(null, /* @ts-ignore */
|
|
12872
|
+
...(ngDevMode ? [{ debugName: "editDraft" }] : /* istanbul ignore next */ []));
|
|
12595
12873
|
startEdit(row, col) {
|
|
12596
12874
|
this.editing.set({ rowKey: this.rowKey(row), colKey: col.key });
|
|
12597
|
-
|
|
12875
|
+
this.editDraft.set(row[col.key]);
|
|
12876
|
+
setTimeout(() => {
|
|
12877
|
+
const editor = this.hostRef.nativeElement.querySelector('.strct-dg__editinput, .strct-dg__editselect, .strct-dg__editnumber');
|
|
12878
|
+
if (!editor)
|
|
12879
|
+
return;
|
|
12880
|
+
// The stepper buttons come first in the DOM; the value is what the user
|
|
12881
|
+
// came to change, so focus lands on the field itself.
|
|
12882
|
+
(editor.matches('input')
|
|
12883
|
+
? editor
|
|
12884
|
+
: (editor.querySelector('input') ??
|
|
12885
|
+
editor.querySelector('button')))?.focus();
|
|
12886
|
+
});
|
|
12887
|
+
}
|
|
12888
|
+
/** A select column reads as its option's label, not as the stored value. */
|
|
12889
|
+
displayValue(row, col) {
|
|
12890
|
+
const raw = row[col.key];
|
|
12891
|
+
if (col.editor !== 'select')
|
|
12892
|
+
return raw;
|
|
12893
|
+
return col.editorOptions?.find((o) => o.value === raw)?.label ?? raw;
|
|
12894
|
+
}
|
|
12895
|
+
/** Close the editor and emit `cellEdit` when the value actually changed. */
|
|
12896
|
+
commitValue(row, col, raw) {
|
|
12897
|
+
if (!this.isEditingCell(row, col))
|
|
12898
|
+
return;
|
|
12899
|
+
this.editing.set(null);
|
|
12900
|
+
const value = this.clampToColumn(col, raw);
|
|
12901
|
+
const previous = row[col.key];
|
|
12902
|
+
if (previous === value)
|
|
12903
|
+
return;
|
|
12904
|
+
this.cellEdit.emit({
|
|
12905
|
+
row,
|
|
12906
|
+
column: col,
|
|
12907
|
+
value: value == null ? '' : String(value),
|
|
12908
|
+
typedValue: value,
|
|
12909
|
+
previous,
|
|
12910
|
+
});
|
|
12911
|
+
}
|
|
12912
|
+
/** Enter / blur on a typed editor commits what it currently holds. */
|
|
12913
|
+
commitDraft(row, col, event) {
|
|
12914
|
+
event?.stopPropagation();
|
|
12915
|
+
this.commitValue(row, col, this.editDraft());
|
|
12916
|
+
}
|
|
12917
|
+
/**
|
|
12918
|
+
* A column that declares bounds means them: Enter commits what the field
|
|
12919
|
+
* holds, and `strct-number` only clamps on blur, so the grid clamps here
|
|
12920
|
+
* rather than letting an out-of-range value reach `cellEdit`.
|
|
12921
|
+
*/
|
|
12922
|
+
clampToColumn(col, value) {
|
|
12923
|
+
if (col.editor !== 'number' || typeof value !== 'number' || !Number.isFinite(value))
|
|
12924
|
+
return value;
|
|
12925
|
+
const min = col.editorMin;
|
|
12926
|
+
const max = col.editorMax;
|
|
12927
|
+
let v = value;
|
|
12928
|
+
if (min != null && v < min)
|
|
12929
|
+
v = min;
|
|
12930
|
+
if (max != null && v > max)
|
|
12931
|
+
v = max;
|
|
12932
|
+
return v;
|
|
12933
|
+
}
|
|
12934
|
+
/**
|
|
12935
|
+
* A number editor is a field between two stepper buttons, so focus moves
|
|
12936
|
+
* inside it — only a move that actually leaves the editor is a blur.
|
|
12937
|
+
*/
|
|
12938
|
+
commitOnLeave(row, col, event) {
|
|
12939
|
+
const editor = event.currentTarget;
|
|
12940
|
+
const next = event.relatedTarget;
|
|
12941
|
+
if (next instanceof Node && editor.contains(next))
|
|
12942
|
+
return;
|
|
12943
|
+
this.commitDraft(row, col, event);
|
|
12598
12944
|
}
|
|
12599
12945
|
commitEdit(row, col, event) {
|
|
12600
12946
|
if (!this.isEditingCell(row, col))
|
|
@@ -12603,7 +12949,7 @@ class StrctDatagrid {
|
|
|
12603
12949
|
this.editing.set(null);
|
|
12604
12950
|
const previous = row[col.key];
|
|
12605
12951
|
if (String(previous ?? '') !== value)
|
|
12606
|
-
this.cellEdit.emit({ row, column: col, value, previous });
|
|
12952
|
+
this.cellEdit.emit({ row, column: col, value, typedValue: value, previous });
|
|
12607
12953
|
}
|
|
12608
12954
|
cancelEdit(event) {
|
|
12609
12955
|
event?.stopPropagation();
|
|
@@ -13716,16 +14062,47 @@ class StrctDatagrid {
|
|
|
13716
14062
|
}
|
|
13717
14063
|
}
|
|
13718
14064
|
@if (isEditingCell(row, col)) {
|
|
13719
|
-
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
13724
|
-
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
|
|
13728
|
-
|
|
14065
|
+
@switch (col.editor ?? 'text') {
|
|
14066
|
+
@case ('select') {
|
|
14067
|
+
<!-- Choosing is the commit: there is nothing to
|
|
14068
|
+
confirm with Enter once an option is picked. -->
|
|
14069
|
+
<strct-select
|
|
14070
|
+
class="strct-dg__editselect"
|
|
14071
|
+
[options]="col.editorOptions ?? []"
|
|
14072
|
+
[listLabel]="L().editCell + ': ' + col.label"
|
|
14073
|
+
[ngModel]="row[col.key]"
|
|
14074
|
+
(ngModelChange)="commitValue(row, col, $event)"
|
|
14075
|
+
(keydown.escape)="cancelEdit($event)"
|
|
14076
|
+
(click)="$event.stopPropagation()"
|
|
14077
|
+
/>
|
|
14078
|
+
}
|
|
14079
|
+
@case ('number') {
|
|
14080
|
+
<strct-number
|
|
14081
|
+
class="strct-dg__editnumber"
|
|
14082
|
+
[min]="col.editorMin ?? null"
|
|
14083
|
+
[max]="col.editorMax ?? null"
|
|
14084
|
+
[step]="col.editorStep ?? 1"
|
|
14085
|
+
[ngModel]="row[col.key]"
|
|
14086
|
+
(ngModelChange)="editDraft.set($event)"
|
|
14087
|
+
(keydown.enter)="commitDraft(row, col, $event)"
|
|
14088
|
+
(keydown.escape)="cancelEdit($event)"
|
|
14089
|
+
(focusout)="commitOnLeave(row, col, $event)"
|
|
14090
|
+
(click)="$event.stopPropagation()"
|
|
14091
|
+
/>
|
|
14092
|
+
}
|
|
14093
|
+
@default {
|
|
14094
|
+
<input
|
|
14095
|
+
class="strct-dg__editinput"
|
|
14096
|
+
type="text"
|
|
14097
|
+
[value]="row[col.key] ?? ''"
|
|
14098
|
+
[attr.aria-label]="L().editCell + ': ' + col.label"
|
|
14099
|
+
(keydown.enter)="commitEdit(row, col, $event)"
|
|
14100
|
+
(keydown.escape)="cancelEdit($event)"
|
|
14101
|
+
(blur)="commitEdit(row, col, $event)"
|
|
14102
|
+
(click)="$event.stopPropagation()"
|
|
14103
|
+
/>
|
|
14104
|
+
}
|
|
14105
|
+
}
|
|
13729
14106
|
} @else if (cellTemplate(col.key); as tpl) {
|
|
13730
14107
|
<ng-container
|
|
13731
14108
|
[ngTemplateOutlet]="tpl"
|
|
@@ -13737,7 +14114,7 @@ class StrctDatagrid {
|
|
|
13737
14114
|
}"
|
|
13738
14115
|
/>
|
|
13739
14116
|
} @else {
|
|
13740
|
-
{{ row
|
|
14117
|
+
{{ displayValue(row, col) }}
|
|
13741
14118
|
}
|
|
13742
14119
|
</td>
|
|
13743
14120
|
}
|
|
@@ -13872,7 +14249,7 @@ class StrctDatagrid {
|
|
|
13872
14249
|
</div>
|
|
13873
14250
|
</div>
|
|
13874
14251
|
}
|
|
13875
|
-
`, isInline: true, styles: [".strct-dg__radio{width:15px;height:15px;accent-color:var(--acc);cursor:pointer}.strct-dg__radio:disabled{cursor:not-allowed}.strct-dg__row--locked .strct-dg__sel{opacity:.55}.strct-dg__actioncaption:empty{display:none}.strct-dg__actioncaption{display:inline-flex;align-items:center;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);color:var(--t3)}.strct-dg-host{display:block;border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);box-shadow:var(--shadow-rest)}.strct-dg__scroll{flex:1 1 auto;min-width:0;overflow-x:auto;-webkit-overflow-scrolling:touch}.strct-dg-host--virtual .strct-dg__scroll{overflow-y:auto}.strct-dg-host--virtual .strct-dg thead th{position:sticky;top:0;z-index:var(--z-raised)}.strct-dg__vspacer td{padding:0;border:0;background:transparent}.strct-dg th[draggable]{cursor:grab}.strct-dg__th--drop{box-shadow:inset 3px 0 0 var(--acc)}.strct-dg__grouprow td{background:var(--bg-2)!important;padding:0}.strct-dg__groupbtn{display:flex;align-items:center;gap:8px;width:100%;padding:7px 13px;border:0;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;font-weight:600;cursor:pointer;text-align:start}.strct-dg__groupbtn:hover{background:var(--bg-3)}.strct-dg__groupbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__groupchev{display:inline-flex;color:var(--t3);transition:transform .15s ease}.strct-dg__groupchev--open{transform:rotate(90deg)}@media(prefers-reduced-motion:reduce){.strct-dg__groupchev{transition:none}}.strct-dg__groupcount{min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;border-radius:9px;background:var(--acc-m);color:var(--acc);font-variant-numeric:tabular-nums}.strct-dg-host--sticky .strct-dg,.strct-dg-host--virtual .strct-dg{border-collapse:separate;border-spacing:0;overflow:visible;border-radius:0}.strct-dg-host--sticky .strct-dg{width:max-content;min-width:100%}.strct-dg .strct-dg__cell--sticky{position:sticky;z-index:calc(var(--z-base) + 2)}.strct-dg thead th.strct-dg__cell--sticky{z-index:calc(var(--z-raised) + 1)}.strct-dg .strct-dg__cell--sticky-last:after{content:\"\";position:absolute;top:0;bottom:0;inset-inline-end:-1px;width:7px;pointer-events:none;background:linear-gradient(to right,rgba(0,0,0,.14),transparent)}[dir=rtl] .strct-dg .strct-dg__cell--sticky-last:after{background:linear-gradient(to left,rgba(0,0,0,.14),transparent)}.strct-dg-host--sticky .strct-dg__expandcol,.strct-dg-host--sticky .strct-dg__expandcell{width:36px;min-width:36px;max-width:36px;box-sizing:border-box}.strct-dg-host--sticky .strct-dg__sel{width:40px;min-width:40px;max-width:40px;box-sizing:border-box}.strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-3)}.strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-3)}.strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-3)}.strct-dg{width:100%;border-collapse:collapse;font-size:13px;overflow:hidden;background:var(--bg-2)}.strct-dg-host:not(:has(.strct-dg__toolbar)) .strct-dg{border-start-start-radius:9px;border-start-end-radius:9px}.strct-dg-host:not(:has(.strct-dg__foot)) .strct-dg{border-end-start-radius:9px;border-end-end-radius:9px}.strct-dg th,.strct-dg td{padding:9px 13px;text-align:start;border-bottom:1px solid var(--b1)}.strct-dg tbody td{background:var(--bg-1)}[data-theme=dark] .strct-dg tbody td{background:var(--bg-3)}.strct-dg-host--compact .strct-dg th,.strct-dg-host--compact .strct-dg td{padding:5px 11px}.strct-dg-host--singleline .strct-dg tbody tr:not(.strct-dg__detailrow)>td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}.strct-dg th{position:relative;font-size:12px;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__resize{position:absolute;inset-inline-end:0;top:0;bottom:0;width:4px;cursor:col-resize;background:transparent;z-index:calc(var(--z-base) + 1)}.strct-dg__resize:hover{background: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__actioncol,.strct-dg__actioncell{width:1%;white-space:nowrap;text-align:end}.strct-dg__kebab{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__kebab:hover{color:var(--t1);background:var(--bg-3)}.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;align-items:flex-start;min-width:0}.strct-dg__layout--paned{gap:0}.strct-dg__layout--paned .strct-dg__scroll{flex:0 0 auto;width:260px;min-width:260px;max-width:260px}.strct-dg__layout--paned .strct-dg{width:260px;min-width:260px;max-width:260px;flex-shrink:0;border-start-end-radius:0;border-end-end-radius: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-inline-end:26px}.strct-dg__layout--paned .strct-dg__row--active td:last-child:after{content:\"\";position:absolute;inset-inline-end:11px;top:50%;width:6px;height:6px;border-top:1.6px solid var(--acc);border-inline-end: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-inline-start:2px solid var(--acc);border-radius:8px;border-start-start-radius:0;border-end-start-radius:0;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)}}@keyframes strct-skeleton-pulse{0%,to{opacity:.4}50%{opacity:.7}}.strct-dg__skeleton-block{height:12px;border-radius:var(--radius-sm);background:linear-gradient(90deg,var(--bg-3) 25%,var(--skeleton-hi, var(--acc18)) 50%,var(--bg-3) 75%);background-size:200% 100%;animation:strct-dg-skeleton-sweep 1.1s linear infinite}@keyframes strct-dg-skeleton-sweep{0%{background-position:100% 0}to{background-position:-100% 0}}@media(prefers-reduced-motion:reduce){.strct-dg__skeleton-block{animation:none;background:var(--skeleton-hi, var(--acc18))}}.strct-dg__skeleton-row td{border-bottom:1px solid var(--b1)}.strct-dg__empty{text-align:center;color:var(--t3);padding:22px}.strct-dg__quickfilter{min-width:180px;max-width:260px}.strct-dg__quickfilter--end{order:2;margin-inline-start:auto}.strct-dg__filternote--end{order:3}.strct-dg__filternote{font-size:11.5px;color:var(--t3);font-variant-numeric:tabular-nums;white-space:nowrap}.strct-dg__toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid var(--b2)}.strct-dg__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--b2);flex-wrap:wrap}.strct-dg__foot-left,.strct-dg__foot-right{display:flex;align-items:center;gap:12px}.strct-dg__actions{position:relative}.strct-dg__chooser-menu{position:absolute;bottom:calc(100% + 6px);inset-inline-start:0;z-index:var(--z-sticky);min-width:180px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;box-shadow:var(--shadow-pop);padding:6px;display:flex;flex-direction:column;gap:2px}.strct-dg__chooser-menu--right{inset-inline-start:auto;inset-inline-end:0}.strct-dg__chooser-item{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--t1);transition:background .1s ease;-webkit-user-select:none;user-select:none}.strct-dg__chooser-item:hover{background:var(--bg-3)}.strct-dg__count{font-size:12px;color:var(--t2)}.strct-dg__count-sep{margin:0 8px;color:var(--b2)}.strct-dg__count-sel{color:var(--acc)}.strct-dg__filterbtn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-inline-start:2px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__filterbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__filterbtn--active{color:var(--acc)}.strct-dg__filterbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filterpanel{position:absolute;z-index:calc(var(--z-dropdown) + 10);display:flex;flex-direction:column;gap:6px;min-width:180px;padding:10px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);font-weight:400;text-transform:none;letter-spacing:normal;white-space:nowrap}.strct-dg__filterinput{padding:5px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:var(--bg-2);color:var(--t1);font-family:var(--font);font-size:12px}.strct-dg__filterinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filteropt{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--t1);cursor:pointer}.strct-dg__filterclear{align-self:flex-start;padding:3px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:transparent;color:var(--t2);font-family:var(--font);font-size:11.5px;cursor:pointer}.strct-dg__filterclear:hover:not(:disabled){color:var(--t1);background:var(--bg-3)}.strct-dg__filterclear:disabled{color:var(--t4);cursor:default}.strct-dg tbody tr[tabindex]:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__treepad{display:inline-block;height:1px;vertical-align:middle}.strct-dg__treebtn{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-inline-end:4px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__treebtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__treebtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__treebtn strct-icon{transition:transform .15s ease}.strct-dg__treebtn--open strct-icon{transform:rotate(90deg)}.strct-dg__treebtn--leaf{cursor:default;pointer-events:none}@media(prefers-reduced-motion:reduce){.strct-dg__treebtn strct-icon{transition:none}}.strct-dg__cell--editable{cursor:text}.strct-dg__cell--editable:hover{box-shadow:inset 0 0 0 1px var(--b2)}.strct-dg__editinput{width:100%;box-sizing:border-box;padding:3px 6px;border:1px solid var(--acc);border-radius:var(--radius-sm);background:var(--bg-1);color:var(--t1);font:inherit}.strct-dg__editinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "component", type: StrctPagination, selector: "strct-pagination", inputs: ["prevLabel", "nextLabel", "regionLabel", "pageLabel", "total", "pageSize", "page"], outputs: ["pageChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: StrctCheckbox, selector: "strct-checkbox", inputs: ["checked", "disabled", "indeterminate", "ariaLabel"], outputs: ["checkedChange"] }, { kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }, { kind: "component", type: StrctButtonGroup, selector: "strct-button-group" }, { kind: "directive", type: StrctOverlay, selector: "[strctOverlay]", inputs: ["strctOverlay", "strctOverlayPlacement", "strctOverlayMatchWidth", "strctOverlayGap"] }, { kind: "component", type: StrctSearchbox, selector: "strct-searchbox", inputs: ["placeholder", "hint", "trigger", "clearable", "ariaLabel", "clearLabel", "value"], outputs: ["valueChange", "search", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
14252
|
+
`, isInline: true, styles: [".strct-dg__radio{width:15px;height:15px;accent-color:var(--acc);cursor:pointer}.strct-dg__radio:disabled{cursor:not-allowed}.strct-dg__row--locked .strct-dg__sel{opacity:.55}.strct-dg__actioncaption:empty{display:none}.strct-dg__actioncaption{display:inline-flex;align-items:center;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);color:var(--t3)}.strct-dg-host{display:block;border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);box-shadow:var(--shadow-rest)}.strct-dg__scroll{flex:1 1 auto;min-width:0;overflow-x:auto;-webkit-overflow-scrolling:touch}.strct-dg-host--virtual .strct-dg__scroll{overflow-y:auto}.strct-dg-host--virtual .strct-dg thead th{position:sticky;top:0;z-index:var(--z-raised)}.strct-dg__vspacer td{padding:0;border:0;background:transparent}.strct-dg th[draggable]{cursor:grab}.strct-dg__th--drop{box-shadow:inset 3px 0 0 var(--acc)}.strct-dg__grouprow td{background:var(--bg-2)!important;padding:0}.strct-dg__groupbtn{display:flex;align-items:center;gap:8px;width:100%;padding:7px 13px;border:0;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;font-weight:600;cursor:pointer;text-align:start}.strct-dg__groupbtn:hover{background:var(--bg-3)}.strct-dg__groupbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__groupchev{display:inline-flex;color:var(--t3);transition:transform .15s ease}.strct-dg__groupchev--open{transform:rotate(90deg)}@media(prefers-reduced-motion:reduce){.strct-dg__groupchev{transition:none}}.strct-dg__groupcount{min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;border-radius:9px;background:var(--acc-m);color:var(--acc);font-variant-numeric:tabular-nums}.strct-dg-host--sticky .strct-dg,.strct-dg-host--virtual .strct-dg{border-collapse:separate;border-spacing:0;overflow:visible;border-radius:0}.strct-dg-host--sticky .strct-dg{width:max-content;min-width:100%}.strct-dg .strct-dg__cell--sticky{position:sticky;z-index:calc(var(--z-base) + 2)}.strct-dg thead th.strct-dg__cell--sticky{z-index:calc(var(--z-raised) + 1)}.strct-dg .strct-dg__cell--sticky-last:after{content:\"\";position:absolute;top:0;bottom:0;inset-inline-end:-1px;width:7px;pointer-events:none;background:linear-gradient(to right,rgba(0,0,0,.14),transparent)}[dir=rtl] .strct-dg .strct-dg__cell--sticky-last:after{background:linear-gradient(to left,rgba(0,0,0,.14),transparent)}.strct-dg-host--sticky .strct-dg__expandcol,.strct-dg-host--sticky .strct-dg__expandcell{width:36px;min-width:36px;max-width:36px;box-sizing:border-box}.strct-dg-host--sticky .strct-dg__sel{width:40px;min-width:40px;max-width:40px;box-sizing:border-box}.strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-3)}.strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-3)}.strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-3)}.strct-dg{width:100%;border-collapse:collapse;font-size:13px;overflow:hidden;background:var(--bg-2)}.strct-dg-host:not(:has(.strct-dg__toolbar)) .strct-dg{border-start-start-radius:9px;border-start-end-radius:9px}.strct-dg-host:not(:has(.strct-dg__foot)) .strct-dg{border-end-start-radius:9px;border-end-end-radius:9px}.strct-dg th,.strct-dg td{padding:9px 13px;text-align:start;border-bottom:1px solid var(--b1)}.strct-dg tbody td{background:var(--bg-1)}[data-theme=dark] .strct-dg tbody td{background:var(--bg-3)}.strct-dg-host--compact .strct-dg th,.strct-dg-host--compact .strct-dg td{padding:5px 11px}.strct-dg-host--singleline .strct-dg tbody tr:not(.strct-dg__detailrow)>td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}.strct-dg th{position:relative;font-size:12px;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__resize{position:absolute;inset-inline-end:0;top:0;bottom:0;width:4px;cursor:col-resize;background:transparent;z-index:calc(var(--z-base) + 1)}.strct-dg__resize:hover{background: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__actioncol,.strct-dg__actioncell{width:1%;white-space:nowrap;text-align:end}.strct-dg__kebab{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__kebab:hover{color:var(--t1);background:var(--bg-3)}.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;align-items:flex-start;min-width:0}.strct-dg__layout--paned{gap:0}.strct-dg__layout--paned .strct-dg__scroll{flex:0 0 auto;width:260px;min-width:260px;max-width:260px}.strct-dg__layout--paned .strct-dg{width:260px;min-width:260px;max-width:260px;flex-shrink:0;border-start-end-radius:0;border-end-end-radius: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-inline-end:26px}.strct-dg__layout--paned .strct-dg__row--active td:last-child:after{content:\"\";position:absolute;inset-inline-end:11px;top:50%;width:6px;height:6px;border-top:1.6px solid var(--acc);border-inline-end: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-inline-start:2px solid var(--acc);border-radius:8px;border-start-start-radius:0;border-end-start-radius:0;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)}}@keyframes strct-skeleton-pulse{0%,to{opacity:.4}50%{opacity:.7}}.strct-dg__skeleton-block{height:12px;border-radius:var(--radius-sm);background:linear-gradient(90deg,var(--bg-3) 25%,var(--skeleton-hi, var(--acc18)) 50%,var(--bg-3) 75%);background-size:200% 100%;animation:strct-dg-skeleton-sweep 1.1s linear infinite}@keyframes strct-dg-skeleton-sweep{0%{background-position:100% 0}to{background-position:-100% 0}}@media(prefers-reduced-motion:reduce){.strct-dg__skeleton-block{animation:none;background:var(--skeleton-hi, var(--acc18))}}.strct-dg__skeleton-row td{border-bottom:1px solid var(--b1)}.strct-dg__empty{text-align:center;color:var(--t3);padding:22px}.strct-dg__quickfilter{min-width:180px;max-width:260px}.strct-dg__quickfilter--end{order:2;margin-inline-start:auto}.strct-dg__filternote--end{order:3}.strct-dg__filternote{font-size:11.5px;color:var(--t3);font-variant-numeric:tabular-nums;white-space:nowrap}.strct-dg__toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid var(--b2)}.strct-dg__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--b2);flex-wrap:wrap}.strct-dg__foot-left,.strct-dg__foot-right{display:flex;align-items:center;gap:12px}.strct-dg__actions{position:relative}.strct-dg__chooser-menu{position:absolute;bottom:calc(100% + 6px);inset-inline-start:0;z-index:var(--z-sticky);min-width:180px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;box-shadow:var(--shadow-pop);padding:6px;display:flex;flex-direction:column;gap:2px}.strct-dg__chooser-menu--right{inset-inline-start:auto;inset-inline-end:0}.strct-dg__chooser-item{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--t1);transition:background .1s ease;-webkit-user-select:none;user-select:none}.strct-dg__chooser-item:hover{background:var(--bg-3)}.strct-dg__count{font-size:12px;color:var(--t2)}.strct-dg__count-sep{margin:0 8px;color:var(--b2)}.strct-dg__count-sel{color:var(--acc)}.strct-dg__filterbtn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-inline-start:2px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__filterbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__filterbtn--active{color:var(--acc)}.strct-dg__filterbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filterpanel{position:absolute;z-index:calc(var(--z-dropdown) + 10);display:flex;flex-direction:column;gap:6px;min-width:180px;padding:10px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);font-weight:400;text-transform:none;letter-spacing:normal;white-space:nowrap}.strct-dg__filterinput{padding:5px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:var(--bg-2);color:var(--t1);font-family:var(--font);font-size:12px}.strct-dg__filterinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filteropt{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--t1);cursor:pointer}.strct-dg__filterclear{align-self:flex-start;padding:3px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:transparent;color:var(--t2);font-family:var(--font);font-size:11.5px;cursor:pointer}.strct-dg__filterclear:hover:not(:disabled){color:var(--t1);background:var(--bg-3)}.strct-dg__filterclear:disabled{color:var(--t4);cursor:default}.strct-dg tbody tr[tabindex]:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__treepad{display:inline-block;height:1px;vertical-align:middle}.strct-dg__treebtn{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-inline-end:4px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__treebtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__treebtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__treebtn strct-icon{transition:transform .15s ease}.strct-dg__treebtn--open strct-icon{transform:rotate(90deg)}.strct-dg__treebtn--leaf{cursor:default;pointer-events:none}@media(prefers-reduced-motion:reduce){.strct-dg__treebtn strct-icon{transition:none}}.strct-dg__cell--editable{cursor:text}.strct-dg__cell--editable:hover{box-shadow:inset 0 0 0 1px var(--b2)}.strct-dg__editinput{width:100%;box-sizing:border-box;padding:3px 6px;border:1px solid var(--acc);border-radius:var(--radius-sm);background:var(--bg-1);color:var(--t1);font:inherit}.strct-dg__editselect,.strct-dg__editnumber{display:block;width:100%}.strct-dg__editinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "component", type: StrctPagination, selector: "strct-pagination", inputs: ["prevLabel", "nextLabel", "regionLabel", "pageLabel", "total", "pageSize", "page"], outputs: ["pageChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: StrctCheckbox, selector: "strct-checkbox", inputs: ["checked", "disabled", "indeterminate", "ariaLabel"], outputs: ["checkedChange"] }, { kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }, { kind: "component", type: StrctButtonGroup, selector: "strct-button-group" }, { kind: "directive", type: StrctOverlay, selector: "[strctOverlay]", inputs: ["strctOverlay", "strctOverlayPlacement", "strctOverlayMatchWidth", "strctOverlayGap"] }, { kind: "component", type: StrctSearchbox, selector: "strct-searchbox", inputs: ["placeholder", "hint", "trigger", "clearable", "ariaLabel", "clearLabel", "value"], outputs: ["valueChange", "search", "activated"] }, { kind: "component", type: StrctSelect, selector: "strct-select", inputs: ["options", "placeholder", "listLabel", "emptyText", "disabled", "compareWith"] }, { kind: "component", type: StrctNumber, selector: "strct-number", inputs: ["min", "max", "step", "placeholder", "incrementLabel", "decrementLabel", "disabled"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
13876
14253
|
}
|
|
13877
14254
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDatagrid, decorators: [{
|
|
13878
14255
|
type: Component,
|
|
@@ -13885,6 +14262,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
13885
14262
|
StrctButtonGroup,
|
|
13886
14263
|
StrctOverlay,
|
|
13887
14264
|
StrctSearchbox,
|
|
14265
|
+
StrctSelect,
|
|
14266
|
+
StrctNumber,
|
|
14267
|
+
FormsModule,
|
|
13888
14268
|
], template: `
|
|
13889
14269
|
@if (actionBarDef() || quickFilterable()) {
|
|
13890
14270
|
<div class="strct-dg__toolbar">
|
|
@@ -14249,16 +14629,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
14249
14629
|
}
|
|
14250
14630
|
}
|
|
14251
14631
|
@if (isEditingCell(row, col)) {
|
|
14252
|
-
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
|
|
14632
|
+
@switch (col.editor ?? 'text') {
|
|
14633
|
+
@case ('select') {
|
|
14634
|
+
<!-- Choosing is the commit: there is nothing to
|
|
14635
|
+
confirm with Enter once an option is picked. -->
|
|
14636
|
+
<strct-select
|
|
14637
|
+
class="strct-dg__editselect"
|
|
14638
|
+
[options]="col.editorOptions ?? []"
|
|
14639
|
+
[listLabel]="L().editCell + ': ' + col.label"
|
|
14640
|
+
[ngModel]="row[col.key]"
|
|
14641
|
+
(ngModelChange)="commitValue(row, col, $event)"
|
|
14642
|
+
(keydown.escape)="cancelEdit($event)"
|
|
14643
|
+
(click)="$event.stopPropagation()"
|
|
14644
|
+
/>
|
|
14645
|
+
}
|
|
14646
|
+
@case ('number') {
|
|
14647
|
+
<strct-number
|
|
14648
|
+
class="strct-dg__editnumber"
|
|
14649
|
+
[min]="col.editorMin ?? null"
|
|
14650
|
+
[max]="col.editorMax ?? null"
|
|
14651
|
+
[step]="col.editorStep ?? 1"
|
|
14652
|
+
[ngModel]="row[col.key]"
|
|
14653
|
+
(ngModelChange)="editDraft.set($event)"
|
|
14654
|
+
(keydown.enter)="commitDraft(row, col, $event)"
|
|
14655
|
+
(keydown.escape)="cancelEdit($event)"
|
|
14656
|
+
(focusout)="commitOnLeave(row, col, $event)"
|
|
14657
|
+
(click)="$event.stopPropagation()"
|
|
14658
|
+
/>
|
|
14659
|
+
}
|
|
14660
|
+
@default {
|
|
14661
|
+
<input
|
|
14662
|
+
class="strct-dg__editinput"
|
|
14663
|
+
type="text"
|
|
14664
|
+
[value]="row[col.key] ?? ''"
|
|
14665
|
+
[attr.aria-label]="L().editCell + ': ' + col.label"
|
|
14666
|
+
(keydown.enter)="commitEdit(row, col, $event)"
|
|
14667
|
+
(keydown.escape)="cancelEdit($event)"
|
|
14668
|
+
(blur)="commitEdit(row, col, $event)"
|
|
14669
|
+
(click)="$event.stopPropagation()"
|
|
14670
|
+
/>
|
|
14671
|
+
}
|
|
14672
|
+
}
|
|
14262
14673
|
} @else if (cellTemplate(col.key); as tpl) {
|
|
14263
14674
|
<ng-container
|
|
14264
14675
|
[ngTemplateOutlet]="tpl"
|
|
@@ -14270,7 +14681,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
14270
14681
|
}"
|
|
14271
14682
|
/>
|
|
14272
14683
|
} @else {
|
|
14273
|
-
{{ row
|
|
14684
|
+
{{ displayValue(row, col) }}
|
|
14274
14685
|
}
|
|
14275
14686
|
</td>
|
|
14276
14687
|
}
|
|
@@ -14411,7 +14822,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
14411
14822
|
'[class.strct-dg-host--singleline]': 'singleLine()',
|
|
14412
14823
|
'[class.strct-dg-host--virtual]': 'virtual()',
|
|
14413
14824
|
'[class.strct-dg-host--sticky]': 'stickyActive()',
|
|
14414
|
-
}, styles: [".strct-dg__radio{width:15px;height:15px;accent-color:var(--acc);cursor:pointer}.strct-dg__radio:disabled{cursor:not-allowed}.strct-dg__row--locked .strct-dg__sel{opacity:.55}.strct-dg__actioncaption:empty{display:none}.strct-dg__actioncaption{display:inline-flex;align-items:center;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);color:var(--t3)}.strct-dg-host{display:block;border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);box-shadow:var(--shadow-rest)}.strct-dg__scroll{flex:1 1 auto;min-width:0;overflow-x:auto;-webkit-overflow-scrolling:touch}.strct-dg-host--virtual .strct-dg__scroll{overflow-y:auto}.strct-dg-host--virtual .strct-dg thead th{position:sticky;top:0;z-index:var(--z-raised)}.strct-dg__vspacer td{padding:0;border:0;background:transparent}.strct-dg th[draggable]{cursor:grab}.strct-dg__th--drop{box-shadow:inset 3px 0 0 var(--acc)}.strct-dg__grouprow td{background:var(--bg-2)!important;padding:0}.strct-dg__groupbtn{display:flex;align-items:center;gap:8px;width:100%;padding:7px 13px;border:0;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;font-weight:600;cursor:pointer;text-align:start}.strct-dg__groupbtn:hover{background:var(--bg-3)}.strct-dg__groupbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__groupchev{display:inline-flex;color:var(--t3);transition:transform .15s ease}.strct-dg__groupchev--open{transform:rotate(90deg)}@media(prefers-reduced-motion:reduce){.strct-dg__groupchev{transition:none}}.strct-dg__groupcount{min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;border-radius:9px;background:var(--acc-m);color:var(--acc);font-variant-numeric:tabular-nums}.strct-dg-host--sticky .strct-dg,.strct-dg-host--virtual .strct-dg{border-collapse:separate;border-spacing:0;overflow:visible;border-radius:0}.strct-dg-host--sticky .strct-dg{width:max-content;min-width:100%}.strct-dg .strct-dg__cell--sticky{position:sticky;z-index:calc(var(--z-base) + 2)}.strct-dg thead th.strct-dg__cell--sticky{z-index:calc(var(--z-raised) + 1)}.strct-dg .strct-dg__cell--sticky-last:after{content:\"\";position:absolute;top:0;bottom:0;inset-inline-end:-1px;width:7px;pointer-events:none;background:linear-gradient(to right,rgba(0,0,0,.14),transparent)}[dir=rtl] .strct-dg .strct-dg__cell--sticky-last:after{background:linear-gradient(to left,rgba(0,0,0,.14),transparent)}.strct-dg-host--sticky .strct-dg__expandcol,.strct-dg-host--sticky .strct-dg__expandcell{width:36px;min-width:36px;max-width:36px;box-sizing:border-box}.strct-dg-host--sticky .strct-dg__sel{width:40px;min-width:40px;max-width:40px;box-sizing:border-box}.strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-3)}.strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-3)}.strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-3)}.strct-dg{width:100%;border-collapse:collapse;font-size:13px;overflow:hidden;background:var(--bg-2)}.strct-dg-host:not(:has(.strct-dg__toolbar)) .strct-dg{border-start-start-radius:9px;border-start-end-radius:9px}.strct-dg-host:not(:has(.strct-dg__foot)) .strct-dg{border-end-start-radius:9px;border-end-end-radius:9px}.strct-dg th,.strct-dg td{padding:9px 13px;text-align:start;border-bottom:1px solid var(--b1)}.strct-dg tbody td{background:var(--bg-1)}[data-theme=dark] .strct-dg tbody td{background:var(--bg-3)}.strct-dg-host--compact .strct-dg th,.strct-dg-host--compact .strct-dg td{padding:5px 11px}.strct-dg-host--singleline .strct-dg tbody tr:not(.strct-dg__detailrow)>td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}.strct-dg th{position:relative;font-size:12px;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__resize{position:absolute;inset-inline-end:0;top:0;bottom:0;width:4px;cursor:col-resize;background:transparent;z-index:calc(var(--z-base) + 1)}.strct-dg__resize:hover{background: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__actioncol,.strct-dg__actioncell{width:1%;white-space:nowrap;text-align:end}.strct-dg__kebab{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__kebab:hover{color:var(--t1);background:var(--bg-3)}.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;align-items:flex-start;min-width:0}.strct-dg__layout--paned{gap:0}.strct-dg__layout--paned .strct-dg__scroll{flex:0 0 auto;width:260px;min-width:260px;max-width:260px}.strct-dg__layout--paned .strct-dg{width:260px;min-width:260px;max-width:260px;flex-shrink:0;border-start-end-radius:0;border-end-end-radius: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-inline-end:26px}.strct-dg__layout--paned .strct-dg__row--active td:last-child:after{content:\"\";position:absolute;inset-inline-end:11px;top:50%;width:6px;height:6px;border-top:1.6px solid var(--acc);border-inline-end: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-inline-start:2px solid var(--acc);border-radius:8px;border-start-start-radius:0;border-end-start-radius:0;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)}}@keyframes strct-skeleton-pulse{0%,to{opacity:.4}50%{opacity:.7}}.strct-dg__skeleton-block{height:12px;border-radius:var(--radius-sm);background:linear-gradient(90deg,var(--bg-3) 25%,var(--skeleton-hi, var(--acc18)) 50%,var(--bg-3) 75%);background-size:200% 100%;animation:strct-dg-skeleton-sweep 1.1s linear infinite}@keyframes strct-dg-skeleton-sweep{0%{background-position:100% 0}to{background-position:-100% 0}}@media(prefers-reduced-motion:reduce){.strct-dg__skeleton-block{animation:none;background:var(--skeleton-hi, var(--acc18))}}.strct-dg__skeleton-row td{border-bottom:1px solid var(--b1)}.strct-dg__empty{text-align:center;color:var(--t3);padding:22px}.strct-dg__quickfilter{min-width:180px;max-width:260px}.strct-dg__quickfilter--end{order:2;margin-inline-start:auto}.strct-dg__filternote--end{order:3}.strct-dg__filternote{font-size:11.5px;color:var(--t3);font-variant-numeric:tabular-nums;white-space:nowrap}.strct-dg__toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid var(--b2)}.strct-dg__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--b2);flex-wrap:wrap}.strct-dg__foot-left,.strct-dg__foot-right{display:flex;align-items:center;gap:12px}.strct-dg__actions{position:relative}.strct-dg__chooser-menu{position:absolute;bottom:calc(100% + 6px);inset-inline-start:0;z-index:var(--z-sticky);min-width:180px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;box-shadow:var(--shadow-pop);padding:6px;display:flex;flex-direction:column;gap:2px}.strct-dg__chooser-menu--right{inset-inline-start:auto;inset-inline-end:0}.strct-dg__chooser-item{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--t1);transition:background .1s ease;-webkit-user-select:none;user-select:none}.strct-dg__chooser-item:hover{background:var(--bg-3)}.strct-dg__count{font-size:12px;color:var(--t2)}.strct-dg__count-sep{margin:0 8px;color:var(--b2)}.strct-dg__count-sel{color:var(--acc)}.strct-dg__filterbtn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-inline-start:2px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__filterbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__filterbtn--active{color:var(--acc)}.strct-dg__filterbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filterpanel{position:absolute;z-index:calc(var(--z-dropdown) + 10);display:flex;flex-direction:column;gap:6px;min-width:180px;padding:10px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);font-weight:400;text-transform:none;letter-spacing:normal;white-space:nowrap}.strct-dg__filterinput{padding:5px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:var(--bg-2);color:var(--t1);font-family:var(--font);font-size:12px}.strct-dg__filterinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filteropt{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--t1);cursor:pointer}.strct-dg__filterclear{align-self:flex-start;padding:3px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:transparent;color:var(--t2);font-family:var(--font);font-size:11.5px;cursor:pointer}.strct-dg__filterclear:hover:not(:disabled){color:var(--t1);background:var(--bg-3)}.strct-dg__filterclear:disabled{color:var(--t4);cursor:default}.strct-dg tbody tr[tabindex]:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__treepad{display:inline-block;height:1px;vertical-align:middle}.strct-dg__treebtn{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-inline-end:4px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__treebtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__treebtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__treebtn strct-icon{transition:transform .15s ease}.strct-dg__treebtn--open strct-icon{transform:rotate(90deg)}.strct-dg__treebtn--leaf{cursor:default;pointer-events:none}@media(prefers-reduced-motion:reduce){.strct-dg__treebtn strct-icon{transition:none}}.strct-dg__cell--editable{cursor:text}.strct-dg__cell--editable:hover{box-shadow:inset 0 0 0 1px var(--b2)}.strct-dg__editinput{width:100%;box-sizing:border-box;padding:3px 6px;border:1px solid var(--acc);border-radius:var(--radius-sm);background:var(--bg-1);color:var(--t1);font:inherit}.strct-dg__editinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}\n"] }]
|
|
14825
|
+
}, styles: [".strct-dg__radio{width:15px;height:15px;accent-color:var(--acc);cursor:pointer}.strct-dg__radio:disabled{cursor:not-allowed}.strct-dg__row--locked .strct-dg__sel{opacity:.55}.strct-dg__actioncaption:empty{display:none}.strct-dg__actioncaption{display:inline-flex;align-items:center;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);color:var(--t3)}.strct-dg-host{display:block;border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);box-shadow:var(--shadow-rest)}.strct-dg__scroll{flex:1 1 auto;min-width:0;overflow-x:auto;-webkit-overflow-scrolling:touch}.strct-dg-host--virtual .strct-dg__scroll{overflow-y:auto}.strct-dg-host--virtual .strct-dg thead th{position:sticky;top:0;z-index:var(--z-raised)}.strct-dg__vspacer td{padding:0;border:0;background:transparent}.strct-dg th[draggable]{cursor:grab}.strct-dg__th--drop{box-shadow:inset 3px 0 0 var(--acc)}.strct-dg__grouprow td{background:var(--bg-2)!important;padding:0}.strct-dg__groupbtn{display:flex;align-items:center;gap:8px;width:100%;padding:7px 13px;border:0;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;font-weight:600;cursor:pointer;text-align:start}.strct-dg__groupbtn:hover{background:var(--bg-3)}.strct-dg__groupbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__groupchev{display:inline-flex;color:var(--t3);transition:transform .15s ease}.strct-dg__groupchev--open{transform:rotate(90deg)}@media(prefers-reduced-motion:reduce){.strct-dg__groupchev{transition:none}}.strct-dg__groupcount{min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;border-radius:9px;background:var(--acc-m);color:var(--acc);font-variant-numeric:tabular-nums}.strct-dg-host--sticky .strct-dg,.strct-dg-host--virtual .strct-dg{border-collapse:separate;border-spacing:0;overflow:visible;border-radius:0}.strct-dg-host--sticky .strct-dg{width:max-content;min-width:100%}.strct-dg .strct-dg__cell--sticky{position:sticky;z-index:calc(var(--z-base) + 2)}.strct-dg thead th.strct-dg__cell--sticky{z-index:calc(var(--z-raised) + 1)}.strct-dg .strct-dg__cell--sticky-last:after{content:\"\";position:absolute;top:0;bottom:0;inset-inline-end:-1px;width:7px;pointer-events:none;background:linear-gradient(to right,rgba(0,0,0,.14),transparent)}[dir=rtl] .strct-dg .strct-dg__cell--sticky-last:after{background:linear-gradient(to left,rgba(0,0,0,.14),transparent)}.strct-dg-host--sticky .strct-dg__expandcol,.strct-dg-host--sticky .strct-dg__expandcell{width:36px;min-width:36px;max-width:36px;box-sizing:border-box}.strct-dg-host--sticky .strct-dg__sel{width:40px;min-width:40px;max-width:40px;box-sizing:border-box}.strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-3)}.strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-3)}.strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-3)}.strct-dg{width:100%;border-collapse:collapse;font-size:13px;overflow:hidden;background:var(--bg-2)}.strct-dg-host:not(:has(.strct-dg__toolbar)) .strct-dg{border-start-start-radius:9px;border-start-end-radius:9px}.strct-dg-host:not(:has(.strct-dg__foot)) .strct-dg{border-end-start-radius:9px;border-end-end-radius:9px}.strct-dg th,.strct-dg td{padding:9px 13px;text-align:start;border-bottom:1px solid var(--b1)}.strct-dg tbody td{background:var(--bg-1)}[data-theme=dark] .strct-dg tbody td{background:var(--bg-3)}.strct-dg-host--compact .strct-dg th,.strct-dg-host--compact .strct-dg td{padding:5px 11px}.strct-dg-host--singleline .strct-dg tbody tr:not(.strct-dg__detailrow)>td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}.strct-dg th{position:relative;font-size:12px;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__resize{position:absolute;inset-inline-end:0;top:0;bottom:0;width:4px;cursor:col-resize;background:transparent;z-index:calc(var(--z-base) + 1)}.strct-dg__resize:hover{background: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__actioncol,.strct-dg__actioncell{width:1%;white-space:nowrap;text-align:end}.strct-dg__kebab{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__kebab:hover{color:var(--t1);background:var(--bg-3)}.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;align-items:flex-start;min-width:0}.strct-dg__layout--paned{gap:0}.strct-dg__layout--paned .strct-dg__scroll{flex:0 0 auto;width:260px;min-width:260px;max-width:260px}.strct-dg__layout--paned .strct-dg{width:260px;min-width:260px;max-width:260px;flex-shrink:0;border-start-end-radius:0;border-end-end-radius: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-inline-end:26px}.strct-dg__layout--paned .strct-dg__row--active td:last-child:after{content:\"\";position:absolute;inset-inline-end:11px;top:50%;width:6px;height:6px;border-top:1.6px solid var(--acc);border-inline-end: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-inline-start:2px solid var(--acc);border-radius:8px;border-start-start-radius:0;border-end-start-radius:0;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)}}@keyframes strct-skeleton-pulse{0%,to{opacity:.4}50%{opacity:.7}}.strct-dg__skeleton-block{height:12px;border-radius:var(--radius-sm);background:linear-gradient(90deg,var(--bg-3) 25%,var(--skeleton-hi, var(--acc18)) 50%,var(--bg-3) 75%);background-size:200% 100%;animation:strct-dg-skeleton-sweep 1.1s linear infinite}@keyframes strct-dg-skeleton-sweep{0%{background-position:100% 0}to{background-position:-100% 0}}@media(prefers-reduced-motion:reduce){.strct-dg__skeleton-block{animation:none;background:var(--skeleton-hi, var(--acc18))}}.strct-dg__skeleton-row td{border-bottom:1px solid var(--b1)}.strct-dg__empty{text-align:center;color:var(--t3);padding:22px}.strct-dg__quickfilter{min-width:180px;max-width:260px}.strct-dg__quickfilter--end{order:2;margin-inline-start:auto}.strct-dg__filternote--end{order:3}.strct-dg__filternote{font-size:11.5px;color:var(--t3);font-variant-numeric:tabular-nums;white-space:nowrap}.strct-dg__toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid var(--b2)}.strct-dg__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--b2);flex-wrap:wrap}.strct-dg__foot-left,.strct-dg__foot-right{display:flex;align-items:center;gap:12px}.strct-dg__actions{position:relative}.strct-dg__chooser-menu{position:absolute;bottom:calc(100% + 6px);inset-inline-start:0;z-index:var(--z-sticky);min-width:180px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;box-shadow:var(--shadow-pop);padding:6px;display:flex;flex-direction:column;gap:2px}.strct-dg__chooser-menu--right{inset-inline-start:auto;inset-inline-end:0}.strct-dg__chooser-item{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--t1);transition:background .1s ease;-webkit-user-select:none;user-select:none}.strct-dg__chooser-item:hover{background:var(--bg-3)}.strct-dg__count{font-size:12px;color:var(--t2)}.strct-dg__count-sep{margin:0 8px;color:var(--b2)}.strct-dg__count-sel{color:var(--acc)}.strct-dg__filterbtn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-inline-start:2px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__filterbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__filterbtn--active{color:var(--acc)}.strct-dg__filterbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filterpanel{position:absolute;z-index:calc(var(--z-dropdown) + 10);display:flex;flex-direction:column;gap:6px;min-width:180px;padding:10px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);font-weight:400;text-transform:none;letter-spacing:normal;white-space:nowrap}.strct-dg__filterinput{padding:5px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:var(--bg-2);color:var(--t1);font-family:var(--font);font-size:12px}.strct-dg__filterinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filteropt{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--t1);cursor:pointer}.strct-dg__filterclear{align-self:flex-start;padding:3px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:transparent;color:var(--t2);font-family:var(--font);font-size:11.5px;cursor:pointer}.strct-dg__filterclear:hover:not(:disabled){color:var(--t1);background:var(--bg-3)}.strct-dg__filterclear:disabled{color:var(--t4);cursor:default}.strct-dg tbody tr[tabindex]:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__treepad{display:inline-block;height:1px;vertical-align:middle}.strct-dg__treebtn{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-inline-end:4px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__treebtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__treebtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__treebtn strct-icon{transition:transform .15s ease}.strct-dg__treebtn--open strct-icon{transform:rotate(90deg)}.strct-dg__treebtn--leaf{cursor:default;pointer-events:none}@media(prefers-reduced-motion:reduce){.strct-dg__treebtn strct-icon{transition:none}}.strct-dg__cell--editable{cursor:text}.strct-dg__cell--editable:hover{box-shadow:inset 0 0 0 1px var(--b2)}.strct-dg__editinput{width:100%;box-sizing:border-box;padding:3px 6px;border:1px solid var(--acc);border-radius:var(--radius-sm);background:var(--bg-1);color:var(--t1);font:inherit}.strct-dg__editselect,.strct-dg__editnumber{display:block;width:100%}.strct-dg__editinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}\n"] }]
|
|
14415
14826
|
}], 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 }] }], singleLine: [{ type: i0.Input, args: [{ isSignal: true, alias: "singleLine", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], columnChooser: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnChooser", required: false }] }], sync: [{ type: i0.Input, args: [{ isSignal: true, alias: "sync", required: false }] }], footerActionsDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerActionsDisabled", required: false }] }], rowId: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowId", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], selectedId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedId", required: false }] }, { type: i0.Output, args: ["selectedIdChange"] }], rowSelectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowSelectable", required: false }] }], groupSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupSelect", required: false }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], initialSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSelection", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], viewportHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportHeight", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: false }] }], stateKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateKey", required: false }] }], columnState: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnState", required: false }] }, { type: i0.Output, args: ["columnStateChange"] }], reorderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderable", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: false }] }, { type: i0.Output, args: ["filtersChange"] }], quickFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "quickFilter", required: false }] }, { type: i0.Output, args: ["quickFilterChange"] }], quickFilterFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "quickFilterFields", required: false }] }], quickFilterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "quickFilterable", required: false }] }], quickFilterAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "quickFilterAlign", required: false }] }], childrenKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "childrenKey", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], syncChange: [{ type: i0.Output, args: ["syncChange"] }], rowAction: [{ type: i0.Output, args: ["rowAction"] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], cellEdit: [{ type: i0.Output, args: ["cellEdit"] }], 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 }] }], onDocClickOverlays: [{
|
|
14416
14827
|
type: HostListener,
|
|
14417
14828
|
args: ['document:click', ['$event']]
|
|
@@ -19812,1441 +20223,1167 @@ class StrctTour {
|
|
|
19812
20223
|
const w = 300;
|
|
19813
20224
|
const h = 170;
|
|
19814
20225
|
if (!r) {
|
|
19815
|
-
return {
|
|
19816
|
-
top: Math.max(20, window.innerHeight / 2 - h / 2),
|
|
19817
|
-
left: Math.max(20, window.innerWidth / 2 - w / 2),
|
|
19818
|
-
};
|
|
19819
|
-
}
|
|
19820
|
-
// Below the target if it fits, else above; clamped into the viewport.
|
|
19821
|
-
let top = r.top + r.height + 14;
|
|
19822
|
-
if (top + h > window.innerHeight - 12)
|
|
19823
|
-
top = Math.max(12, r.top - h - 14);
|
|
19824
|
-
const left = Math.min(Math.max(12, r.left), window.innerWidth - w - 12);
|
|
19825
|
-
return { top, left };
|
|
19826
|
-
}, /* @ts-ignore */
|
|
19827
|
-
...(ngDevMode ? [{ debugName: "cardPos" }] : /* istanbul ignore next */ []));
|
|
19828
|
-
constructor() {
|
|
19829
|
-
// Restart from step 0 on open; scroll the first target into view.
|
|
19830
|
-
effect(() => {
|
|
19831
|
-
if (this.open()) {
|
|
19832
|
-
this.previousActive = saveFocusedElement();
|
|
19833
|
-
this.index.set(0);
|
|
19834
|
-
this.scrollToTarget();
|
|
19835
|
-
this.focusNextButton();
|
|
19836
|
-
}
|
|
19837
|
-
else {
|
|
19838
|
-
restoreFocus(this.previousActive);
|
|
19839
|
-
this.previousActive = null;
|
|
19840
|
-
}
|
|
19841
|
-
});
|
|
19842
|
-
}
|
|
19843
|
-
next() {
|
|
19844
|
-
if (this.index() >= this.steps().length - 1) {
|
|
19845
|
-
this.open.set(false);
|
|
19846
|
-
this.finished.emit();
|
|
19847
|
-
return;
|
|
19848
|
-
}
|
|
19849
|
-
this.index.update((i) => i + 1);
|
|
19850
|
-
this.scrollToTarget();
|
|
19851
|
-
this.focusNextButton();
|
|
19852
|
-
}
|
|
19853
|
-
prev() {
|
|
19854
|
-
this.index.update((i) => Math.max(0, i - 1));
|
|
19855
|
-
this.scrollToTarget();
|
|
19856
|
-
this.focusNextButton();
|
|
19857
|
-
}
|
|
19858
|
-
close() {
|
|
19859
|
-
this.open.set(false);
|
|
19860
|
-
this.dismissed.emit();
|
|
19861
|
-
}
|
|
19862
|
-
/** Wrap Tab focus within the card. */
|
|
19863
|
-
onTab(event) {
|
|
19864
|
-
const card = this.card();
|
|
19865
|
-
if (card)
|
|
19866
|
-
keepTabInside(event, card);
|
|
19867
|
-
}
|
|
19868
|
-
/** Escape closes; stopPropagation so a host modal/drawer does not also close. */
|
|
19869
|
-
onEscapeKey(event) {
|
|
19870
|
-
event.stopPropagation();
|
|
19871
|
-
this.close();
|
|
19872
|
-
}
|
|
19873
|
-
/**
|
|
19874
|
-
* Arrow keys step through the tour, mirrored under RTL. No shared direction
|
|
19875
|
-
* helper exists (other components handle RTL in CSS only), so read the
|
|
19876
|
-
* card's computed direction here.
|
|
19877
|
-
*/
|
|
19878
|
-
onArrow(event, dir) {
|
|
19879
|
-
event.preventDefault();
|
|
19880
|
-
const rtl = getComputedStyle(event.currentTarget).direction === 'rtl';
|
|
19881
|
-
if ((dir === 'right') !== rtl)
|
|
19882
|
-
this.next();
|
|
19883
|
-
else
|
|
19884
|
-
this.prev();
|
|
19885
|
-
}
|
|
19886
|
-
/** Home/End jump to the first/last step. */
|
|
19887
|
-
onHomeEnd(event, last) {
|
|
19888
|
-
event.preventDefault();
|
|
19889
|
-
this.index.set(last ? this.steps().length - 1 : 0);
|
|
19890
|
-
this.scrollToTarget();
|
|
19891
|
-
this.focusNextButton();
|
|
19892
|
-
}
|
|
19893
|
-
scrollToTarget() {
|
|
19894
|
-
setTimeout(() => {
|
|
19895
|
-
const sel = this.steps()[this.index()]?.target;
|
|
19896
|
-
if (sel)
|
|
19897
|
-
document.querySelector(sel)?.scrollIntoView?.({ block: 'center' });
|
|
19898
|
-
this.measureTick.update((n) => n + 1);
|
|
19899
|
-
});
|
|
19900
|
-
}
|
|
19901
|
-
card() {
|
|
19902
|
-
return this.elementRef.nativeElement.querySelector('.strct-tour__card');
|
|
19903
|
-
}
|
|
19904
|
-
/** Move focus to the step's primary (Next/Done) action once the card has rendered. */
|
|
19905
|
-
focusNextButton() {
|
|
19906
|
-
setTimeout(() => {
|
|
19907
|
-
if (!this.open())
|
|
19908
|
-
return;
|
|
19909
|
-
const card = this.card();
|
|
19910
|
-
if (!card)
|
|
19911
|
-
return;
|
|
19912
|
-
const nav = card.querySelectorAll('.strct-tour__nav button');
|
|
19913
|
-
const primary = nav[nav.length - 1];
|
|
19914
|
-
if (primary)
|
|
19915
|
-
primary.focus();
|
|
19916
|
-
else
|
|
19917
|
-
focusFirstIn(card);
|
|
19918
|
-
});
|
|
19919
|
-
}
|
|
19920
|
-
onReflow() {
|
|
19921
|
-
if (this.open())
|
|
19922
|
-
this.measureTick.update((n) => n + 1);
|
|
19923
|
-
}
|
|
19924
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctTour, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
19925
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctTour, isStandalone: true, selector: "strct-tour", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null }, skipLabel: { classPropertyName: "skipLabel", publicName: "skipLabel", isSignal: true, isRequired: false, transformFunction: null }, doneLabel: { classPropertyName: "doneLabel", publicName: "doneLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", finished: "finished", dismissed: "dismissed" }, host: { listeners: { "window:resize": "onReflow()", "window:scroll": "onReflow()" } }, ngImport: i0, template: `
|
|
19926
|
-
@if (open() && current(); as step) {
|
|
19927
|
-
<!-- Always the click-catcher; dims only when no ring (its shadow dims). -->
|
|
19928
|
-
<div
|
|
19929
|
-
class="strct-tour__backdrop"
|
|
19930
|
-
[class.strct-tour__backdrop--dim]="!spot()"
|
|
19931
|
-
(click)="close()"
|
|
19932
|
-
></div>
|
|
19933
|
-
@if (spot(); as r) {
|
|
19934
|
-
<div
|
|
19935
|
-
class="strct-tour__ring"
|
|
19936
|
-
[style.top.px]="r.top - 6"
|
|
19937
|
-
[style.left.px]="r.left - 6"
|
|
19938
|
-
[style.width.px]="r.width + 12"
|
|
19939
|
-
[style.height.px]="r.height + 12"
|
|
19940
|
-
></div>
|
|
19941
|
-
}
|
|
19942
|
-
<div
|
|
19943
|
-
class="strct-tour__card"
|
|
19944
|
-
role="dialog"
|
|
19945
|
-
[attr.aria-labelledby]="titleId"
|
|
19946
|
-
[style.top.px]="cardPos().top"
|
|
19947
|
-
[style.left.px]="cardPos().left"
|
|
19948
|
-
(keydown.tab)="onTab($event)"
|
|
19949
|
-
(keydown.shift.tab)="onTab($event)"
|
|
19950
|
-
(keydown.escape)="onEscapeKey($event)"
|
|
19951
|
-
(keydown.arrowleft)="onArrow($event, 'left')"
|
|
19952
|
-
(keydown.arrowright)="onArrow($event, 'right')"
|
|
19953
|
-
(keydown.home)="onHomeEnd($event, false)"
|
|
19954
|
-
(keydown.end)="onHomeEnd($event, true)"
|
|
19955
|
-
>
|
|
19956
|
-
<div class="strct-tour__step">{{ index() + 1 }} / {{ steps().length }}</div>
|
|
19957
|
-
<div class="strct-tour__title" [id]="titleId">{{ step.title }}</div>
|
|
19958
|
-
<div class="strct-tour__body">{{ step.body }}</div>
|
|
19959
|
-
<div class="strct-tour__nav">
|
|
19960
|
-
<button strct-button size="sm" variant="flat" (click)="close()">
|
|
19961
|
-
{{ skipLabel() }}
|
|
19962
|
-
</button>
|
|
19963
|
-
<span class="strct-tour__grow"></span>
|
|
19964
|
-
@if (index() > 0) {
|
|
19965
|
-
<button strct-button size="sm" variant="neutral" (click)="prev()">
|
|
19966
|
-
{{ backLabel() }}
|
|
19967
|
-
</button>
|
|
19968
|
-
}
|
|
19969
|
-
<button strct-button size="sm" variant="primary" solid (click)="next()">
|
|
19970
|
-
{{ index() === steps().length - 1 ? doneLabel() : nextLabel() }}
|
|
19971
|
-
</button>
|
|
19972
|
-
</div>
|
|
19973
|
-
</div>
|
|
19974
|
-
}
|
|
19975
|
-
`, isInline: true, styles: [".strct-tour__backdrop{position:fixed;inset:0;z-index:var(--z-tour);background:transparent}.strct-tour__backdrop--dim{background:var(--backdrop)}.strct-tour__ring{position:fixed;z-index:calc(var(--z-tour) + 1);border:2px solid var(--acc);border-radius:9px;box-shadow:0 0 0 4px var(--acc30),0 0 0 9999px var(--backdrop);pointer-events:none}.strct-tour__card{position:fixed;z-index:calc(var(--z-tour) + 2);width:300px;padding:14px 16px;background:var(--bg-1);border:1px solid var(--b2);border-radius:9px;box-shadow:var(--shh)}.strct-tour__step{font-size:10.5px;font-weight:600;letter-spacing:.6px;color:var(--t3);margin-bottom:4px;font-variant-numeric:tabular-nums}.strct-tour__title{font-size:13.5px;font-weight:650;color:var(--t1);margin-bottom:6px}.strct-tour__body{font-size:12.5px;line-height:1.5;color:var(--t2);margin-bottom:12px}.strct-tour__nav{display:flex;align-items:center;gap:8px}.strct-tour__grow{flex:1}\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 });
|
|
19976
|
-
}
|
|
19977
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctTour, decorators: [{
|
|
19978
|
-
type: Component,
|
|
19979
|
-
args: [{ selector: 'strct-tour', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctButton], template: `
|
|
19980
|
-
@if (open() && current(); as step) {
|
|
19981
|
-
<!-- Always the click-catcher; dims only when no ring (its shadow dims). -->
|
|
19982
|
-
<div
|
|
19983
|
-
class="strct-tour__backdrop"
|
|
19984
|
-
[class.strct-tour__backdrop--dim]="!spot()"
|
|
19985
|
-
(click)="close()"
|
|
19986
|
-
></div>
|
|
19987
|
-
@if (spot(); as r) {
|
|
19988
|
-
<div
|
|
19989
|
-
class="strct-tour__ring"
|
|
19990
|
-
[style.top.px]="r.top - 6"
|
|
19991
|
-
[style.left.px]="r.left - 6"
|
|
19992
|
-
[style.width.px]="r.width + 12"
|
|
19993
|
-
[style.height.px]="r.height + 12"
|
|
19994
|
-
></div>
|
|
19995
|
-
}
|
|
19996
|
-
<div
|
|
19997
|
-
class="strct-tour__card"
|
|
19998
|
-
role="dialog"
|
|
19999
|
-
[attr.aria-labelledby]="titleId"
|
|
20000
|
-
[style.top.px]="cardPos().top"
|
|
20001
|
-
[style.left.px]="cardPos().left"
|
|
20002
|
-
(keydown.tab)="onTab($event)"
|
|
20003
|
-
(keydown.shift.tab)="onTab($event)"
|
|
20004
|
-
(keydown.escape)="onEscapeKey($event)"
|
|
20005
|
-
(keydown.arrowleft)="onArrow($event, 'left')"
|
|
20006
|
-
(keydown.arrowright)="onArrow($event, 'right')"
|
|
20007
|
-
(keydown.home)="onHomeEnd($event, false)"
|
|
20008
|
-
(keydown.end)="onHomeEnd($event, true)"
|
|
20009
|
-
>
|
|
20010
|
-
<div class="strct-tour__step">{{ index() + 1 }} / {{ steps().length }}</div>
|
|
20011
|
-
<div class="strct-tour__title" [id]="titleId">{{ step.title }}</div>
|
|
20012
|
-
<div class="strct-tour__body">{{ step.body }}</div>
|
|
20013
|
-
<div class="strct-tour__nav">
|
|
20014
|
-
<button strct-button size="sm" variant="flat" (click)="close()">
|
|
20015
|
-
{{ skipLabel() }}
|
|
20016
|
-
</button>
|
|
20017
|
-
<span class="strct-tour__grow"></span>
|
|
20018
|
-
@if (index() > 0) {
|
|
20019
|
-
<button strct-button size="sm" variant="neutral" (click)="prev()">
|
|
20020
|
-
{{ backLabel() }}
|
|
20021
|
-
</button>
|
|
20022
|
-
}
|
|
20023
|
-
<button strct-button size="sm" variant="primary" solid (click)="next()">
|
|
20024
|
-
{{ index() === steps().length - 1 ? doneLabel() : nextLabel() }}
|
|
20025
|
-
</button>
|
|
20026
|
-
</div>
|
|
20027
|
-
</div>
|
|
20028
|
-
}
|
|
20029
|
-
`, styles: [".strct-tour__backdrop{position:fixed;inset:0;z-index:var(--z-tour);background:transparent}.strct-tour__backdrop--dim{background:var(--backdrop)}.strct-tour__ring{position:fixed;z-index:calc(var(--z-tour) + 1);border:2px solid var(--acc);border-radius:9px;box-shadow:0 0 0 4px var(--acc30),0 0 0 9999px var(--backdrop);pointer-events:none}.strct-tour__card{position:fixed;z-index:calc(var(--z-tour) + 2);width:300px;padding:14px 16px;background:var(--bg-1);border:1px solid var(--b2);border-radius:9px;box-shadow:var(--shh)}.strct-tour__step{font-size:10.5px;font-weight:600;letter-spacing:.6px;color:var(--t3);margin-bottom:4px;font-variant-numeric:tabular-nums}.strct-tour__title{font-size:13.5px;font-weight:650;color:var(--t1);margin-bottom:6px}.strct-tour__body{font-size:12.5px;line-height:1.5;color:var(--t2);margin-bottom:12px}.strct-tour__nav{display:flex;align-items:center;gap:8px}.strct-tour__grow{flex:1}\n"] }]
|
|
20030
|
-
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: true }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], backLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "backLabel", required: false }] }], skipLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipLabel", required: false }] }], doneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "doneLabel", required: false }] }], finished: [{ type: i0.Output, args: ["finished"] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], onReflow: [{
|
|
20031
|
-
type: HostListener,
|
|
20032
|
-
args: ['window:resize']
|
|
20033
|
-
}, {
|
|
20034
|
-
type: HostListener,
|
|
20035
|
-
args: ['window:scroll']
|
|
20036
|
-
}] } });
|
|
20037
|
-
|
|
20038
|
-
let menubarCounter = 0;
|
|
20039
|
-
/**
|
|
20040
|
-
* Horizontal menubar — the application-menu strip ("File · Edit · View") for
|
|
20041
|
-
* dense tool-style consoles:
|
|
20042
|
-
*
|
|
20043
|
-
* <strct-menubar [menus]="menus" (picked)="run($event)" />
|
|
20044
|
-
*
|
|
20045
|
-
* Click (or Enter/Down) opens a menu and focus moves into it; Left/Right move
|
|
20046
|
-
* across the bar and, while open, switch the open menu (the APG menubar
|
|
20047
|
-
* convention). Up/Down (+Home/End) rove the open menu, Right/Left enter and
|
|
20048
|
-
* leave an item's one-level `children` submenu. Escape and outside click
|
|
20049
|
-
* close, returning focus to the bar. `(picked)` carries `{ menu, item }`.
|
|
20050
|
-
*/
|
|
20051
|
-
class StrctMenubar {
|
|
20052
|
-
host = inject(ElementRef);
|
|
20053
|
-
/** Top-level menus. */
|
|
20054
|
-
menus = input.required(/* @ts-ignore */
|
|
20055
|
-
...(ngDevMode ? [{ debugName: "menus" }] : /* istanbul ignore next */ []));
|
|
20056
|
-
/** Accessible name of the bar (localizable). */
|
|
20057
|
-
ariaLabel = input('Application menu', /* @ts-ignore */
|
|
20058
|
-
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
20059
|
-
/** A menu entry was chosen. */
|
|
20060
|
-
picked = output();
|
|
20061
|
-
openId = signal(null, /* @ts-ignore */
|
|
20062
|
-
...(ngDevMode ? [{ debugName: "openId" }] : /* istanbul ignore next */ []));
|
|
20063
|
-
focusIdx = signal(0, /* @ts-ignore */
|
|
20064
|
-
...(ngDevMode ? [{ debugName: "focusIdx" }] : /* istanbul ignore next */ []));
|
|
20065
|
-
/** Index of the item whose one-level submenu is open. */
|
|
20066
|
-
subIdx = signal(null, /* @ts-ignore */
|
|
20067
|
-
...(ngDevMode ? [{ debugName: "subIdx" }] : /* istanbul ignore next */ []));
|
|
20068
|
-
/** Top-level button that opened the menu — focus returns here on close. */
|
|
20069
|
-
trigger = null;
|
|
20070
|
-
toggleMenu(id, i) {
|
|
20071
|
-
this.focusIdx.set(i);
|
|
20072
|
-
if (this.openId() === id) {
|
|
20073
|
-
this.closeMenu(false);
|
|
20074
|
-
}
|
|
20075
|
-
else {
|
|
20076
|
-
this.trigger = saveFocusedElement();
|
|
20077
|
-
this.openId.set(id);
|
|
20078
|
-
this.subIdx.set(null);
|
|
20079
|
-
}
|
|
20080
|
-
}
|
|
20081
|
-
onItemClick(menu, item, i) {
|
|
20082
|
-
if (item.disabled)
|
|
20083
|
-
return;
|
|
20084
|
-
if (item.children?.length) {
|
|
20085
|
-
this.subIdx.set(this.subIdx() === i ? null : i);
|
|
20086
|
-
return;
|
|
20087
|
-
}
|
|
20088
|
-
this.pick(menu, item);
|
|
20089
|
-
}
|
|
20090
|
-
onItemHover(item, i) {
|
|
20091
|
-
if (item.disabled)
|
|
20092
|
-
return;
|
|
20093
|
-
if (item.children?.length)
|
|
20094
|
-
this.subIdx.set(i);
|
|
20095
|
-
else if (this.subIdx() != null)
|
|
20096
|
-
this.subIdx.set(null);
|
|
20097
|
-
}
|
|
20098
|
-
pick(menu, item) {
|
|
20099
|
-
if (item.disabled)
|
|
20100
|
-
return;
|
|
20101
|
-
this.closeMenu(true);
|
|
20102
|
-
item.action?.(item.data);
|
|
20103
|
-
this.picked.emit({ menu, item });
|
|
20104
|
-
}
|
|
20105
|
-
onTopKey(event, i) {
|
|
20106
|
-
const menus = this.menus();
|
|
20107
|
-
if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {
|
|
20108
|
-
event.preventDefault();
|
|
20109
|
-
if (this.openId()) {
|
|
20110
|
-
// While a menu is open, moving across the bar switches the open menu.
|
|
20111
|
-
this.switchTop(event.key === 'ArrowRight' ? 1 : -1);
|
|
20112
|
-
}
|
|
20113
|
-
else {
|
|
20114
|
-
const dir = event.key === 'ArrowRight' ? 1 : -1;
|
|
20115
|
-
const next = (i + dir + menus.length) % menus.length;
|
|
20116
|
-
this.focusIdx.set(next);
|
|
20117
|
-
setTimeout(() => this.topButtons()[next]?.focus());
|
|
20118
|
-
}
|
|
20119
|
-
}
|
|
20120
|
-
else if (event.key === 'ArrowDown' || event.key === 'Enter') {
|
|
20121
|
-
event.preventDefault();
|
|
20122
|
-
this.trigger = saveFocusedElement();
|
|
20123
|
-
this.openId.set(menus[i].id);
|
|
20124
|
-
this.subIdx.set(null);
|
|
20125
|
-
setTimeout(() => {
|
|
20126
|
-
const menuEl = this.host.nativeElement.querySelector('.strct-mb__menu');
|
|
20127
|
-
if (menuEl)
|
|
20128
|
-
focusFirstIn(menuEl);
|
|
20129
|
-
});
|
|
20130
|
-
}
|
|
20131
|
-
else if (event.key === 'Escape' && this.openId()) {
|
|
20132
|
-
event.preventDefault();
|
|
20133
|
-
event.stopPropagation();
|
|
20134
|
-
this.closeMenu(true);
|
|
20135
|
-
}
|
|
20136
|
-
}
|
|
20137
|
-
onMenuKey(event, menu) {
|
|
20138
|
-
const active = document.activeElement;
|
|
20139
|
-
const inSub = !!active?.classList.contains('strct-mb__subitem');
|
|
20140
|
-
switch (event.key) {
|
|
20141
|
-
case 'ArrowDown':
|
|
20142
|
-
case 'ArrowUp': {
|
|
20143
|
-
event.preventDefault();
|
|
20144
|
-
event.stopPropagation();
|
|
20145
|
-
const list = this.navButtons(inSub);
|
|
20146
|
-
const dir = event.key === 'ArrowDown' ? 1 : -1;
|
|
20147
|
-
const cur = list.indexOf(active);
|
|
20148
|
-
list[(cur + dir + list.length) % list.length]?.focus();
|
|
20149
|
-
break;
|
|
20150
|
-
}
|
|
20151
|
-
case 'Home':
|
|
20152
|
-
case 'End': {
|
|
20153
|
-
event.preventDefault();
|
|
20154
|
-
event.stopPropagation();
|
|
20155
|
-
const list = this.navButtons(inSub);
|
|
20156
|
-
(event.key === 'Home' ? list[0] : list[list.length - 1])?.focus();
|
|
20157
|
-
break;
|
|
20158
|
-
}
|
|
20159
|
-
case 'ArrowRight': {
|
|
20160
|
-
if (inSub)
|
|
20161
|
-
break; // deepest level — nothing further to open
|
|
20162
|
-
event.preventDefault();
|
|
20163
|
-
event.stopPropagation();
|
|
20164
|
-
const idx = Number(active?.getAttribute('data-idx'));
|
|
20165
|
-
if (menu.items[idx]?.children?.length && !menu.items[idx].disabled) {
|
|
20166
|
-
this.subIdx.set(idx);
|
|
20167
|
-
setTimeout(() => this.navButtons(true)[0]?.focus());
|
|
20168
|
-
}
|
|
20169
|
-
else {
|
|
20170
|
-
this.switchTop(1);
|
|
20171
|
-
}
|
|
20172
|
-
break;
|
|
20173
|
-
}
|
|
20174
|
-
case 'ArrowLeft': {
|
|
20175
|
-
event.preventDefault();
|
|
20176
|
-
event.stopPropagation();
|
|
20177
|
-
if (inSub) {
|
|
20178
|
-
const parent = this.subIdx();
|
|
20179
|
-
this.subIdx.set(null);
|
|
20180
|
-
this.host.nativeElement
|
|
20181
|
-
.querySelector(`.strct-mb__item[data-idx="${parent}"]`)
|
|
20182
|
-
?.focus();
|
|
20183
|
-
}
|
|
20184
|
-
else if (this.subIdx() != null) {
|
|
20185
|
-
this.subIdx.set(null);
|
|
20186
|
-
}
|
|
20187
|
-
else {
|
|
20188
|
-
this.switchTop(-1);
|
|
20189
|
-
}
|
|
20190
|
-
break;
|
|
20226
|
+
return {
|
|
20227
|
+
top: Math.max(20, window.innerHeight / 2 - h / 2),
|
|
20228
|
+
left: Math.max(20, window.innerWidth / 2 - w / 2),
|
|
20229
|
+
};
|
|
20230
|
+
}
|
|
20231
|
+
// Below the target if it fits, else above; clamped into the viewport.
|
|
20232
|
+
let top = r.top + r.height + 14;
|
|
20233
|
+
if (top + h > window.innerHeight - 12)
|
|
20234
|
+
top = Math.max(12, r.top - h - 14);
|
|
20235
|
+
const left = Math.min(Math.max(12, r.left), window.innerWidth - w - 12);
|
|
20236
|
+
return { top, left };
|
|
20237
|
+
}, /* @ts-ignore */
|
|
20238
|
+
...(ngDevMode ? [{ debugName: "cardPos" }] : /* istanbul ignore next */ []));
|
|
20239
|
+
constructor() {
|
|
20240
|
+
// Restart from step 0 on open; scroll the first target into view.
|
|
20241
|
+
effect(() => {
|
|
20242
|
+
if (this.open()) {
|
|
20243
|
+
this.previousActive = saveFocusedElement();
|
|
20244
|
+
this.index.set(0);
|
|
20245
|
+
this.scrollToTarget();
|
|
20246
|
+
this.focusNextButton();
|
|
20191
20247
|
}
|
|
20192
|
-
|
|
20193
|
-
|
|
20194
|
-
|
|
20195
|
-
this.closeMenu(true);
|
|
20196
|
-
break;
|
|
20248
|
+
else {
|
|
20249
|
+
restoreFocus(this.previousActive);
|
|
20250
|
+
this.previousActive = null;
|
|
20197
20251
|
}
|
|
20252
|
+
});
|
|
20253
|
+
}
|
|
20254
|
+
next() {
|
|
20255
|
+
if (this.index() >= this.steps().length - 1) {
|
|
20256
|
+
this.open.set(false);
|
|
20257
|
+
this.finished.emit();
|
|
20258
|
+
return;
|
|
20198
20259
|
}
|
|
20260
|
+
this.index.update((i) => i + 1);
|
|
20261
|
+
this.scrollToTarget();
|
|
20262
|
+
this.focusNextButton();
|
|
20199
20263
|
}
|
|
20200
|
-
|
|
20201
|
-
|
|
20202
|
-
|
|
20203
|
-
|
|
20204
|
-
this.focusIdx.set(next);
|
|
20205
|
-
this.openId.set(menus[next].id);
|
|
20206
|
-
this.subIdx.set(null);
|
|
20207
|
-
setTimeout(() => this.topButtons()[next]?.focus());
|
|
20264
|
+
prev() {
|
|
20265
|
+
this.index.update((i) => Math.max(0, i - 1));
|
|
20266
|
+
this.scrollToTarget();
|
|
20267
|
+
this.focusNextButton();
|
|
20208
20268
|
}
|
|
20209
|
-
|
|
20210
|
-
this.
|
|
20211
|
-
this.
|
|
20212
|
-
if (returnFocus)
|
|
20213
|
-
restoreFocus(this.trigger);
|
|
20214
|
-
this.trigger = null;
|
|
20269
|
+
close() {
|
|
20270
|
+
this.open.set(false);
|
|
20271
|
+
this.dismissed.emit();
|
|
20215
20272
|
}
|
|
20216
|
-
|
|
20217
|
-
|
|
20218
|
-
|
|
20219
|
-
|
|
20220
|
-
|
|
20273
|
+
/** Wrap Tab focus within the card. */
|
|
20274
|
+
onTab(event) {
|
|
20275
|
+
const card = this.card();
|
|
20276
|
+
if (card)
|
|
20277
|
+
keepTabInside(event, card);
|
|
20221
20278
|
}
|
|
20222
|
-
|
|
20223
|
-
|
|
20279
|
+
/** Escape closes; stopPropagation so a host modal/drawer does not also close. */
|
|
20280
|
+
onEscapeKey(event) {
|
|
20281
|
+
event.stopPropagation();
|
|
20282
|
+
this.close();
|
|
20224
20283
|
}
|
|
20225
|
-
/**
|
|
20226
|
-
*
|
|
20227
|
-
*
|
|
20228
|
-
|
|
20229
|
-
|
|
20230
|
-
|
|
20231
|
-
|
|
20232
|
-
const
|
|
20233
|
-
|
|
20284
|
+
/**
|
|
20285
|
+
* Arrow keys step through the tour, mirrored under RTL. No shared direction
|
|
20286
|
+
* helper exists (other components handle RTL in CSS only), so read the
|
|
20287
|
+
* card's computed direction here.
|
|
20288
|
+
*/
|
|
20289
|
+
onArrow(event, dir) {
|
|
20290
|
+
event.preventDefault();
|
|
20291
|
+
const rtl = getComputedStyle(event.currentTarget).direction === 'rtl';
|
|
20292
|
+
if ((dir === 'right') !== rtl)
|
|
20293
|
+
this.next();
|
|
20294
|
+
else
|
|
20295
|
+
this.prev();
|
|
20234
20296
|
}
|
|
20235
|
-
|
|
20236
|
-
|
|
20237
|
-
|
|
20238
|
-
|
|
20297
|
+
/** Home/End jump to the first/last step. */
|
|
20298
|
+
onHomeEnd(event, last) {
|
|
20299
|
+
event.preventDefault();
|
|
20300
|
+
this.index.set(last ? this.steps().length - 1 : 0);
|
|
20301
|
+
this.scrollToTarget();
|
|
20302
|
+
this.focusNextButton();
|
|
20239
20303
|
}
|
|
20240
|
-
|
|
20241
|
-
|
|
20242
|
-
|
|
20243
|
-
|
|
20244
|
-
|
|
20304
|
+
scrollToTarget() {
|
|
20305
|
+
setTimeout(() => {
|
|
20306
|
+
const sel = this.steps()[this.index()]?.target;
|
|
20307
|
+
if (sel)
|
|
20308
|
+
document.querySelector(sel)?.scrollIntoView?.({ block: 'center' });
|
|
20309
|
+
this.measureTick.update((n) => n + 1);
|
|
20310
|
+
});
|
|
20245
20311
|
}
|
|
20246
|
-
|
|
20247
|
-
|
|
20248
|
-
|
|
20249
|
-
|
|
20250
|
-
|
|
20251
|
-
|
|
20252
|
-
|
|
20253
|
-
|
|
20254
|
-
|
|
20255
|
-
|
|
20256
|
-
|
|
20257
|
-
|
|
20258
|
-
|
|
20259
|
-
(
|
|
20260
|
-
|
|
20261
|
-
|
|
20262
|
-
|
|
20263
|
-
|
|
20264
|
-
|
|
20265
|
-
|
|
20266
|
-
|
|
20267
|
-
|
|
20268
|
-
|
|
20269
|
-
|
|
20270
|
-
|
|
20271
|
-
|
|
20272
|
-
|
|
20273
|
-
|
|
20274
|
-
|
|
20275
|
-
|
|
20276
|
-
|
|
20277
|
-
|
|
20278
|
-
|
|
20279
|
-
|
|
20280
|
-
|
|
20281
|
-
|
|
20282
|
-
|
|
20283
|
-
|
|
20284
|
-
|
|
20285
|
-
|
|
20286
|
-
|
|
20287
|
-
|
|
20288
|
-
|
|
20289
|
-
|
|
20290
|
-
|
|
20291
|
-
|
|
20292
|
-
|
|
20293
|
-
|
|
20294
|
-
|
|
20295
|
-
|
|
20296
|
-
|
|
20297
|
-
|
|
20298
|
-
|
|
20299
|
-
|
|
20300
|
-
|
|
20301
|
-
|
|
20302
|
-
|
|
20303
|
-
|
|
20304
|
-
|
|
20305
|
-
|
|
20306
|
-
|
|
20307
|
-
|
|
20308
|
-
|
|
20309
|
-
|
|
20310
|
-
|
|
20311
|
-
|
|
20312
|
-
|
|
20313
|
-
[attr.aria-disabled]="sub.disabled ? 'true' : null"
|
|
20314
|
-
[attr.aria-describedby]="sub.hint ? hintId(menu.id, i, j) : null"
|
|
20315
|
-
[attr.title]="sub.hint || null"
|
|
20316
|
-
(click)="pick(menu, sub)"
|
|
20317
|
-
>
|
|
20318
|
-
@if (sub.icon) {
|
|
20319
|
-
<strct-icon [name]="sub.icon" [size]="13" />
|
|
20320
|
-
}
|
|
20321
|
-
{{ sub.label }}
|
|
20322
|
-
</button>
|
|
20323
|
-
@if (sub.hint) {
|
|
20324
|
-
<span [id]="hintId(menu.id, i, j)" hidden>{{ sub.hint }}</span>
|
|
20325
|
-
}
|
|
20326
|
-
}
|
|
20327
|
-
}
|
|
20328
|
-
</div>
|
|
20329
|
-
}
|
|
20330
|
-
</div>
|
|
20331
|
-
}
|
|
20332
|
-
}
|
|
20333
|
-
</div>
|
|
20312
|
+
card() {
|
|
20313
|
+
return this.elementRef.nativeElement.querySelector('.strct-tour__card');
|
|
20314
|
+
}
|
|
20315
|
+
/** Move focus to the step's primary (Next/Done) action once the card has rendered. */
|
|
20316
|
+
focusNextButton() {
|
|
20317
|
+
setTimeout(() => {
|
|
20318
|
+
if (!this.open())
|
|
20319
|
+
return;
|
|
20320
|
+
const card = this.card();
|
|
20321
|
+
if (!card)
|
|
20322
|
+
return;
|
|
20323
|
+
const nav = card.querySelectorAll('.strct-tour__nav button');
|
|
20324
|
+
const primary = nav[nav.length - 1];
|
|
20325
|
+
if (primary)
|
|
20326
|
+
primary.focus();
|
|
20327
|
+
else
|
|
20328
|
+
focusFirstIn(card);
|
|
20329
|
+
});
|
|
20330
|
+
}
|
|
20331
|
+
onReflow() {
|
|
20332
|
+
if (this.open())
|
|
20333
|
+
this.measureTick.update((n) => n + 1);
|
|
20334
|
+
}
|
|
20335
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctTour, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
20336
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctTour, isStandalone: true, selector: "strct-tour", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null }, skipLabel: { classPropertyName: "skipLabel", publicName: "skipLabel", isSignal: true, isRequired: false, transformFunction: null }, doneLabel: { classPropertyName: "doneLabel", publicName: "doneLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", finished: "finished", dismissed: "dismissed" }, host: { listeners: { "window:resize": "onReflow()", "window:scroll": "onReflow()" } }, ngImport: i0, template: `
|
|
20337
|
+
@if (open() && current(); as step) {
|
|
20338
|
+
<!-- Always the click-catcher; dims only when no ring (its shadow dims). -->
|
|
20339
|
+
<div
|
|
20340
|
+
class="strct-tour__backdrop"
|
|
20341
|
+
[class.strct-tour__backdrop--dim]="!spot()"
|
|
20342
|
+
(click)="close()"
|
|
20343
|
+
></div>
|
|
20344
|
+
@if (spot(); as r) {
|
|
20345
|
+
<div
|
|
20346
|
+
class="strct-tour__ring"
|
|
20347
|
+
[style.top.px]="r.top - 6"
|
|
20348
|
+
[style.left.px]="r.left - 6"
|
|
20349
|
+
[style.width.px]="r.width + 12"
|
|
20350
|
+
[style.height.px]="r.height + 12"
|
|
20351
|
+
></div>
|
|
20352
|
+
}
|
|
20353
|
+
<div
|
|
20354
|
+
class="strct-tour__card"
|
|
20355
|
+
role="dialog"
|
|
20356
|
+
[attr.aria-labelledby]="titleId"
|
|
20357
|
+
[style.top.px]="cardPos().top"
|
|
20358
|
+
[style.left.px]="cardPos().left"
|
|
20359
|
+
(keydown.tab)="onTab($event)"
|
|
20360
|
+
(keydown.shift.tab)="onTab($event)"
|
|
20361
|
+
(keydown.escape)="onEscapeKey($event)"
|
|
20362
|
+
(keydown.arrowleft)="onArrow($event, 'left')"
|
|
20363
|
+
(keydown.arrowright)="onArrow($event, 'right')"
|
|
20364
|
+
(keydown.home)="onHomeEnd($event, false)"
|
|
20365
|
+
(keydown.end)="onHomeEnd($event, true)"
|
|
20366
|
+
>
|
|
20367
|
+
<div class="strct-tour__step">{{ index() + 1 }} / {{ steps().length }}</div>
|
|
20368
|
+
<div class="strct-tour__title" [id]="titleId">{{ step.title }}</div>
|
|
20369
|
+
<div class="strct-tour__body">{{ step.body }}</div>
|
|
20370
|
+
<div class="strct-tour__nav">
|
|
20371
|
+
<button strct-button size="sm" variant="flat" (click)="close()">
|
|
20372
|
+
{{ skipLabel() }}
|
|
20373
|
+
</button>
|
|
20374
|
+
<span class="strct-tour__grow"></span>
|
|
20375
|
+
@if (index() > 0) {
|
|
20376
|
+
<button strct-button size="sm" variant="neutral" (click)="prev()">
|
|
20377
|
+
{{ backLabel() }}
|
|
20378
|
+
</button>
|
|
20334
20379
|
}
|
|
20380
|
+
<button strct-button size="sm" variant="primary" solid (click)="next()">
|
|
20381
|
+
{{ index() === steps().length - 1 ? doneLabel() : nextLabel() }}
|
|
20382
|
+
</button>
|
|
20335
20383
|
</div>
|
|
20336
|
-
|
|
20337
|
-
|
|
20338
|
-
`, isInline: true, styles: [".strct-
|
|
20384
|
+
</div>
|
|
20385
|
+
}
|
|
20386
|
+
`, isInline: true, styles: [".strct-tour__backdrop{position:fixed;inset:0;z-index:var(--z-tour);background:transparent}.strct-tour__backdrop--dim{background:var(--backdrop)}.strct-tour__ring{position:fixed;z-index:calc(var(--z-tour) + 1);border:2px solid var(--acc);border-radius:9px;box-shadow:0 0 0 4px var(--acc30),0 0 0 9999px var(--backdrop);pointer-events:none}.strct-tour__card{position:fixed;z-index:calc(var(--z-tour) + 2);width:300px;padding:14px 16px;background:var(--bg-1);border:1px solid var(--b2);border-radius:9px;box-shadow:var(--shh)}.strct-tour__step{font-size:10.5px;font-weight:600;letter-spacing:.6px;color:var(--t3);margin-bottom:4px;font-variant-numeric:tabular-nums}.strct-tour__title{font-size:13.5px;font-weight:650;color:var(--t1);margin-bottom:6px}.strct-tour__body{font-size:12.5px;line-height:1.5;color:var(--t2);margin-bottom:12px}.strct-tour__nav{display:flex;align-items:center;gap:8px}.strct-tour__grow{flex:1}\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 });
|
|
20339
20387
|
}
|
|
20340
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type:
|
|
20388
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctTour, decorators: [{
|
|
20341
20389
|
type: Component,
|
|
20342
|
-
args: [{ selector: 'strct-
|
|
20343
|
-
|
|
20344
|
-
|
|
20345
|
-
|
|
20346
|
-
|
|
20347
|
-
|
|
20348
|
-
|
|
20349
|
-
|
|
20350
|
-
|
|
20351
|
-
|
|
20352
|
-
|
|
20353
|
-
|
|
20354
|
-
|
|
20355
|
-
|
|
20356
|
-
|
|
20357
|
-
|
|
20390
|
+
args: [{ selector: 'strct-tour', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctButton], template: `
|
|
20391
|
+
@if (open() && current(); as step) {
|
|
20392
|
+
<!-- Always the click-catcher; dims only when no ring (its shadow dims). -->
|
|
20393
|
+
<div
|
|
20394
|
+
class="strct-tour__backdrop"
|
|
20395
|
+
[class.strct-tour__backdrop--dim]="!spot()"
|
|
20396
|
+
(click)="close()"
|
|
20397
|
+
></div>
|
|
20398
|
+
@if (spot(); as r) {
|
|
20399
|
+
<div
|
|
20400
|
+
class="strct-tour__ring"
|
|
20401
|
+
[style.top.px]="r.top - 6"
|
|
20402
|
+
[style.left.px]="r.left - 6"
|
|
20403
|
+
[style.width.px]="r.width + 12"
|
|
20404
|
+
[style.height.px]="r.height + 12"
|
|
20405
|
+
></div>
|
|
20406
|
+
}
|
|
20407
|
+
<div
|
|
20408
|
+
class="strct-tour__card"
|
|
20409
|
+
role="dialog"
|
|
20410
|
+
[attr.aria-labelledby]="titleId"
|
|
20411
|
+
[style.top.px]="cardPos().top"
|
|
20412
|
+
[style.left.px]="cardPos().left"
|
|
20413
|
+
(keydown.tab)="onTab($event)"
|
|
20414
|
+
(keydown.shift.tab)="onTab($event)"
|
|
20415
|
+
(keydown.escape)="onEscapeKey($event)"
|
|
20416
|
+
(keydown.arrowleft)="onArrow($event, 'left')"
|
|
20417
|
+
(keydown.arrowright)="onArrow($event, 'right')"
|
|
20418
|
+
(keydown.home)="onHomeEnd($event, false)"
|
|
20419
|
+
(keydown.end)="onHomeEnd($event, true)"
|
|
20420
|
+
>
|
|
20421
|
+
<div class="strct-tour__step">{{ index() + 1 }} / {{ steps().length }}</div>
|
|
20422
|
+
<div class="strct-tour__title" [id]="titleId">{{ step.title }}</div>
|
|
20423
|
+
<div class="strct-tour__body">{{ step.body }}</div>
|
|
20424
|
+
<div class="strct-tour__nav">
|
|
20425
|
+
<button strct-button size="sm" variant="flat" (click)="close()">
|
|
20426
|
+
{{ skipLabel() }}
|
|
20358
20427
|
</button>
|
|
20359
|
-
|
|
20360
|
-
|
|
20361
|
-
|
|
20362
|
-
|
|
20363
|
-
|
|
20364
|
-
[attr.aria-label]="menu.label"
|
|
20365
|
-
(keydown)="onMenuKey($event, menu)"
|
|
20366
|
-
>
|
|
20367
|
-
@for (item of menu.items; track $index; let i = $index) {
|
|
20368
|
-
@if (item.divider) {
|
|
20369
|
-
<div class="strct-mb__divider" role="separator"></div>
|
|
20370
|
-
} @else {
|
|
20371
|
-
<div class="strct-mb__row">
|
|
20372
|
-
<button
|
|
20373
|
-
type="button"
|
|
20374
|
-
class="strct-mb__item"
|
|
20375
|
-
role="menuitem"
|
|
20376
|
-
[attr.data-idx]="i"
|
|
20377
|
-
[class.strct-mb__item--critical]="item.critical"
|
|
20378
|
-
[attr.aria-haspopup]="item.children?.length ? 'menu' : null"
|
|
20379
|
-
[attr.aria-expanded]="item.children?.length ? subIdx() === i : null"
|
|
20380
|
-
[attr.aria-disabled]="item.disabled ? 'true' : null"
|
|
20381
|
-
[attr.aria-describedby]="item.hint ? hintId(menu.id, i) : null"
|
|
20382
|
-
[attr.title]="item.hint || null"
|
|
20383
|
-
(click)="onItemClick(menu, item, i)"
|
|
20384
|
-
(mouseenter)="onItemHover(item, i)"
|
|
20385
|
-
>
|
|
20386
|
-
@if (item.icon) {
|
|
20387
|
-
<strct-icon [name]="item.icon" [size]="13" />
|
|
20388
|
-
}
|
|
20389
|
-
{{ item.label }}
|
|
20390
|
-
@if (item.children?.length) {
|
|
20391
|
-
<strct-icon class="strct-mb__caret" name="chevronRight" [size]="12" />
|
|
20392
|
-
}
|
|
20393
|
-
</button>
|
|
20394
|
-
@if (item.hint) {
|
|
20395
|
-
<span [id]="hintId(menu.id, i)" hidden>{{ item.hint }}</span>
|
|
20396
|
-
}
|
|
20397
|
-
@if (subIdx() === i && item.children?.length) {
|
|
20398
|
-
<div class="strct-mb__submenu" role="menu" [attr.aria-label]="item.label">
|
|
20399
|
-
@for (sub of item.children; track $index; let j = $index) {
|
|
20400
|
-
@if (sub.divider) {
|
|
20401
|
-
<div class="strct-mb__divider" role="separator"></div>
|
|
20402
|
-
} @else {
|
|
20403
|
-
<button
|
|
20404
|
-
type="button"
|
|
20405
|
-
class="strct-mb__item strct-mb__subitem"
|
|
20406
|
-
role="menuitem"
|
|
20407
|
-
[class.strct-mb__item--critical]="sub.critical"
|
|
20408
|
-
[attr.aria-disabled]="sub.disabled ? 'true' : null"
|
|
20409
|
-
[attr.aria-describedby]="sub.hint ? hintId(menu.id, i, j) : null"
|
|
20410
|
-
[attr.title]="sub.hint || null"
|
|
20411
|
-
(click)="pick(menu, sub)"
|
|
20412
|
-
>
|
|
20413
|
-
@if (sub.icon) {
|
|
20414
|
-
<strct-icon [name]="sub.icon" [size]="13" />
|
|
20415
|
-
}
|
|
20416
|
-
{{ sub.label }}
|
|
20417
|
-
</button>
|
|
20418
|
-
@if (sub.hint) {
|
|
20419
|
-
<span [id]="hintId(menu.id, i, j)" hidden>{{ sub.hint }}</span>
|
|
20420
|
-
}
|
|
20421
|
-
}
|
|
20422
|
-
}
|
|
20423
|
-
</div>
|
|
20424
|
-
}
|
|
20425
|
-
</div>
|
|
20426
|
-
}
|
|
20427
|
-
}
|
|
20428
|
-
</div>
|
|
20428
|
+
<span class="strct-tour__grow"></span>
|
|
20429
|
+
@if (index() > 0) {
|
|
20430
|
+
<button strct-button size="sm" variant="neutral" (click)="prev()">
|
|
20431
|
+
{{ backLabel() }}
|
|
20432
|
+
</button>
|
|
20429
20433
|
}
|
|
20434
|
+
<button strct-button size="sm" variant="primary" solid (click)="next()">
|
|
20435
|
+
{{ index() === steps().length - 1 ? doneLabel() : nextLabel() }}
|
|
20436
|
+
</button>
|
|
20430
20437
|
</div>
|
|
20431
|
-
|
|
20432
|
-
|
|
20433
|
-
`, styles: [".strct-
|
|
20434
|
-
}], propDecorators: {
|
|
20438
|
+
</div>
|
|
20439
|
+
}
|
|
20440
|
+
`, styles: [".strct-tour__backdrop{position:fixed;inset:0;z-index:var(--z-tour);background:transparent}.strct-tour__backdrop--dim{background:var(--backdrop)}.strct-tour__ring{position:fixed;z-index:calc(var(--z-tour) + 1);border:2px solid var(--acc);border-radius:9px;box-shadow:0 0 0 4px var(--acc30),0 0 0 9999px var(--backdrop);pointer-events:none}.strct-tour__card{position:fixed;z-index:calc(var(--z-tour) + 2);width:300px;padding:14px 16px;background:var(--bg-1);border:1px solid var(--b2);border-radius:9px;box-shadow:var(--shh)}.strct-tour__step{font-size:10.5px;font-weight:600;letter-spacing:.6px;color:var(--t3);margin-bottom:4px;font-variant-numeric:tabular-nums}.strct-tour__title{font-size:13.5px;font-weight:650;color:var(--t1);margin-bottom:6px}.strct-tour__body{font-size:12.5px;line-height:1.5;color:var(--t2);margin-bottom:12px}.strct-tour__nav{display:flex;align-items:center;gap:8px}.strct-tour__grow{flex:1}\n"] }]
|
|
20441
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: true }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], backLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "backLabel", required: false }] }], skipLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipLabel", required: false }] }], doneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "doneLabel", required: false }] }], finished: [{ type: i0.Output, args: ["finished"] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], onReflow: [{
|
|
20435
20442
|
type: HostListener,
|
|
20436
|
-
args: ['
|
|
20437
|
-
}
|
|
20443
|
+
args: ['window:resize']
|
|
20444
|
+
}, {
|
|
20438
20445
|
type: HostListener,
|
|
20439
|
-
args: ['
|
|
20446
|
+
args: ['window:scroll']
|
|
20440
20447
|
}] } });
|
|
20441
20448
|
|
|
20442
|
-
|
|
20443
|
-
return combo
|
|
20444
|
-
.toLowerCase()
|
|
20445
|
-
.split('+')
|
|
20446
|
-
.map((p) => p.trim())
|
|
20447
|
-
.sort()
|
|
20448
|
-
.join('+');
|
|
20449
|
-
}
|
|
20450
|
-
function comboOf(event) {
|
|
20451
|
-
const parts = [];
|
|
20452
|
-
if (event.ctrlKey)
|
|
20453
|
-
parts.push('ctrl');
|
|
20454
|
-
if (event.metaKey)
|
|
20455
|
-
parts.push('meta');
|
|
20456
|
-
if (event.altKey)
|
|
20457
|
-
parts.push('alt');
|
|
20458
|
-
if (event.shiftKey)
|
|
20459
|
-
parts.push('shift');
|
|
20460
|
-
parts.push(event.key.toLowerCase());
|
|
20461
|
-
return parts.sort().join('+');
|
|
20462
|
-
}
|
|
20463
|
-
/** `mod` matches Ctrl (and ⌘ on mac keyboards) — expand to both. */
|
|
20464
|
-
function expand(combo) {
|
|
20465
|
-
const n = combo.toLowerCase();
|
|
20466
|
-
if (!n.includes('mod'))
|
|
20467
|
-
return [normalize(n)];
|
|
20468
|
-
return [normalize(n.replace('mod', 'ctrl')), normalize(n.replace('mod', 'meta'))];
|
|
20469
|
-
}
|
|
20449
|
+
let menubarCounter = 0;
|
|
20470
20450
|
/**
|
|
20471
|
-
*
|
|
20472
|
-
*
|
|
20451
|
+
* Horizontal menubar — the application-menu strip ("File · Edit · View") for
|
|
20452
|
+
* dense tool-style consoles:
|
|
20473
20453
|
*
|
|
20474
|
-
*
|
|
20475
|
-
* ngOnInit() { this.dispose = this.hotkeys.register(
|
|
20476
|
-
* { combo: 'mod+k', description: 'Open command palette', group: 'Global',
|
|
20477
|
-
* handler: () => this.palette.open() }); }
|
|
20454
|
+
* <strct-menubar [menus]="menus" (picked)="run($event)" />
|
|
20478
20455
|
*
|
|
20479
|
-
*
|
|
20480
|
-
*
|
|
20481
|
-
*
|
|
20482
|
-
*
|
|
20456
|
+
* Click (or Enter/Down) opens a menu and focus moves into it; Left/Right move
|
|
20457
|
+
* across the bar and, while open, switch the open menu (the APG menubar
|
|
20458
|
+
* convention). Up/Down (+Home/End) rove the open menu, Right/Left enter and
|
|
20459
|
+
* leave an item's one-level `children` submenu. Escape and outside click
|
|
20460
|
+
* close, returning focus to the bar. `(picked)` carries `{ menu, item }`.
|
|
20483
20461
|
*/
|
|
20484
|
-
class
|
|
20485
|
-
|
|
20486
|
-
|
|
20487
|
-
|
|
20488
|
-
|
|
20489
|
-
|
|
20490
|
-
|
|
20491
|
-
|
|
20492
|
-
|
|
20493
|
-
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
20462
|
+
class StrctMenubar {
|
|
20463
|
+
host = inject(ElementRef);
|
|
20464
|
+
/** Top-level menus. */
|
|
20465
|
+
menus = input.required(/* @ts-ignore */
|
|
20466
|
+
...(ngDevMode ? [{ debugName: "menus" }] : /* istanbul ignore next */ []));
|
|
20467
|
+
/** Accessible name of the bar (localizable). */
|
|
20468
|
+
ariaLabel = input('Application menu', /* @ts-ignore */
|
|
20469
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
20470
|
+
/** A menu entry was chosen. */
|
|
20471
|
+
picked = output();
|
|
20472
|
+
openId = signal(null, /* @ts-ignore */
|
|
20473
|
+
...(ngDevMode ? [{ debugName: "openId" }] : /* istanbul ignore next */ []));
|
|
20474
|
+
focusIdx = signal(0, /* @ts-ignore */
|
|
20475
|
+
...(ngDevMode ? [{ debugName: "focusIdx" }] : /* istanbul ignore next */ []));
|
|
20476
|
+
/** Index of the item whose one-level submenu is open. */
|
|
20477
|
+
subIdx = signal(null, /* @ts-ignore */
|
|
20478
|
+
...(ngDevMode ? [{ debugName: "subIdx" }] : /* istanbul ignore next */ []));
|
|
20479
|
+
/** Top-level button that opened the menu — focus returns here on close. */
|
|
20480
|
+
trigger = null;
|
|
20481
|
+
toggleMenu(id, i) {
|
|
20482
|
+
this.focusIdx.set(i);
|
|
20483
|
+
if (this.openId() === id) {
|
|
20484
|
+
this.closeMenu(false);
|
|
20485
|
+
}
|
|
20486
|
+
else {
|
|
20487
|
+
this.trigger = saveFocusedElement();
|
|
20488
|
+
this.openId.set(id);
|
|
20489
|
+
this.subIdx.set(null);
|
|
20490
|
+
}
|
|
20497
20491
|
}
|
|
20498
|
-
|
|
20499
|
-
|
|
20500
|
-
|
|
20501
|
-
|
|
20502
|
-
|
|
20503
|
-
continue;
|
|
20504
|
-
// While typing, only modifier combos may fire.
|
|
20505
|
-
if (typing && !(event.ctrlKey || event.metaKey || event.altKey))
|
|
20506
|
-
continue;
|
|
20507
|
-
event.preventDefault();
|
|
20508
|
-
hk.handler(event);
|
|
20492
|
+
onItemClick(menu, item, i) {
|
|
20493
|
+
if (item.disabled)
|
|
20494
|
+
return;
|
|
20495
|
+
if (item.children?.length) {
|
|
20496
|
+
this.subIdx.set(this.subIdx() === i ? null : i);
|
|
20509
20497
|
return;
|
|
20510
20498
|
}
|
|
20499
|
+
this.pick(menu, item);
|
|
20511
20500
|
}
|
|
20512
|
-
|
|
20513
|
-
|
|
20501
|
+
onItemHover(item, i) {
|
|
20502
|
+
if (item.disabled)
|
|
20503
|
+
return;
|
|
20504
|
+
if (item.children?.length)
|
|
20505
|
+
this.subIdx.set(i);
|
|
20506
|
+
else if (this.subIdx() != null)
|
|
20507
|
+
this.subIdx.set(null);
|
|
20514
20508
|
}
|
|
20515
|
-
|
|
20516
|
-
|
|
20517
|
-
|
|
20518
|
-
|
|
20519
|
-
|
|
20520
|
-
|
|
20521
|
-
|
|
20522
|
-
|
|
20523
|
-
|
|
20524
|
-
|
|
20525
|
-
|
|
20526
|
-
|
|
20527
|
-
|
|
20528
|
-
|
|
20529
|
-
el.isContentEditable);
|
|
20530
|
-
}
|
|
20531
|
-
/**
|
|
20532
|
-
* The hotkey cheatsheet overlay. Mount once near the app root; it registers
|
|
20533
|
-
* `shift+?` (typed as `?`) itself and lists everything in the service, grouped:
|
|
20534
|
-
*
|
|
20535
|
-
* <strct-hotkeys-help />
|
|
20536
|
-
*/
|
|
20537
|
-
class StrctHotkeysHelp {
|
|
20538
|
-
service = inject(StrctHotkeysService);
|
|
20539
|
-
host = inject((ElementRef));
|
|
20540
|
-
/** Overlay visibility (two-way; `?` toggles it too). */
|
|
20541
|
-
open = model(false, /* @ts-ignore */
|
|
20542
|
-
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
20543
|
-
/** Localizable strings. */
|
|
20544
|
-
title = input('Keyboard shortcuts', /* @ts-ignore */
|
|
20545
|
-
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
20546
|
-
emptyText = input('No shortcuts registered.', /* @ts-ignore */
|
|
20547
|
-
...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
20548
|
-
/** Accessible label of the × close button (localizable). */
|
|
20549
|
-
closeLabel = input('Close', /* @ts-ignore */
|
|
20550
|
-
...(ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []));
|
|
20551
|
-
/** Element that had focus before the overlay opened, restored on close. */
|
|
20552
|
-
previousActive = null;
|
|
20553
|
-
grouped = computed(() => {
|
|
20554
|
-
const groups = new Map();
|
|
20555
|
-
for (const hk of this.service.hotkeys()) {
|
|
20556
|
-
const name = hk.group ?? 'General';
|
|
20557
|
-
const list = groups.get(name);
|
|
20558
|
-
if (list)
|
|
20559
|
-
list.push(hk);
|
|
20560
|
-
else
|
|
20561
|
-
groups.set(name, [hk]);
|
|
20562
|
-
}
|
|
20563
|
-
return [...groups.entries()].map(([name, keys]) => ({ name, keys }));
|
|
20564
|
-
}, /* @ts-ignore */
|
|
20565
|
-
...(ngDevMode ? [{ debugName: "grouped" }] : /* istanbul ignore next */ []));
|
|
20566
|
-
constructor() {
|
|
20567
|
-
const dispose = this.service.register({
|
|
20568
|
-
combo: 'shift+?',
|
|
20569
|
-
description: 'Show this help',
|
|
20570
|
-
group: 'General',
|
|
20571
|
-
handler: () => this.open.update((v) => !v),
|
|
20572
|
-
});
|
|
20573
|
-
inject(DestroyRef).onDestroy(dispose);
|
|
20574
|
-
// Focus lifecycle: remember the trigger, move focus into the dialog once
|
|
20575
|
-
// it has rendered, and hand focus back on close.
|
|
20576
|
-
effect(() => {
|
|
20577
|
-
if (this.open()) {
|
|
20578
|
-
this.previousActive = saveFocusedElement();
|
|
20579
|
-
setTimeout(() => {
|
|
20580
|
-
const dialog = this.host.nativeElement.querySelector('.strct-hkh');
|
|
20581
|
-
if (dialog)
|
|
20582
|
-
focusFirstIn(dialog);
|
|
20583
|
-
});
|
|
20509
|
+
pick(menu, item) {
|
|
20510
|
+
if (item.disabled)
|
|
20511
|
+
return;
|
|
20512
|
+
this.closeMenu(true);
|
|
20513
|
+
item.action?.(item.data);
|
|
20514
|
+
this.picked.emit({ menu, item });
|
|
20515
|
+
}
|
|
20516
|
+
onTopKey(event, i) {
|
|
20517
|
+
const menus = this.menus();
|
|
20518
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {
|
|
20519
|
+
event.preventDefault();
|
|
20520
|
+
if (this.openId()) {
|
|
20521
|
+
// While a menu is open, moving across the bar switches the open menu.
|
|
20522
|
+
this.switchTop(event.key === 'ArrowRight' ? 1 : -1);
|
|
20584
20523
|
}
|
|
20585
20524
|
else {
|
|
20586
|
-
|
|
20587
|
-
|
|
20525
|
+
const dir = event.key === 'ArrowRight' ? 1 : -1;
|
|
20526
|
+
const next = (i + dir + menus.length) % menus.length;
|
|
20527
|
+
this.focusIdx.set(next);
|
|
20528
|
+
setTimeout(() => this.topButtons()[next]?.focus());
|
|
20588
20529
|
}
|
|
20589
|
-
}
|
|
20530
|
+
}
|
|
20531
|
+
else if (event.key === 'ArrowDown' || event.key === 'Enter') {
|
|
20532
|
+
event.preventDefault();
|
|
20533
|
+
this.trigger = saveFocusedElement();
|
|
20534
|
+
this.openId.set(menus[i].id);
|
|
20535
|
+
this.subIdx.set(null);
|
|
20536
|
+
setTimeout(() => {
|
|
20537
|
+
const menuEl = this.host.nativeElement.querySelector('.strct-mb__menu');
|
|
20538
|
+
if (menuEl)
|
|
20539
|
+
focusFirstIn(menuEl);
|
|
20540
|
+
});
|
|
20541
|
+
}
|
|
20542
|
+
else if (event.key === 'Escape' && this.openId()) {
|
|
20543
|
+
event.preventDefault();
|
|
20544
|
+
event.stopPropagation();
|
|
20545
|
+
this.closeMenu(true);
|
|
20546
|
+
}
|
|
20590
20547
|
}
|
|
20591
|
-
|
|
20592
|
-
|
|
20593
|
-
|
|
20594
|
-
|
|
20595
|
-
|
|
20596
|
-
|
|
20597
|
-
|
|
20598
|
-
|
|
20599
|
-
|
|
20548
|
+
onMenuKey(event, menu) {
|
|
20549
|
+
const active = document.activeElement;
|
|
20550
|
+
const inSub = !!active?.classList.contains('strct-mb__subitem');
|
|
20551
|
+
switch (event.key) {
|
|
20552
|
+
case 'ArrowDown':
|
|
20553
|
+
case 'ArrowUp': {
|
|
20554
|
+
event.preventDefault();
|
|
20555
|
+
event.stopPropagation();
|
|
20556
|
+
const list = this.navButtons(inSub);
|
|
20557
|
+
const dir = event.key === 'ArrowDown' ? 1 : -1;
|
|
20558
|
+
const cur = list.indexOf(active);
|
|
20559
|
+
list[(cur + dir + list.length) % list.length]?.focus();
|
|
20560
|
+
break;
|
|
20561
|
+
}
|
|
20562
|
+
case 'Home':
|
|
20563
|
+
case 'End': {
|
|
20564
|
+
event.preventDefault();
|
|
20565
|
+
event.stopPropagation();
|
|
20566
|
+
const list = this.navButtons(inSub);
|
|
20567
|
+
(event.key === 'Home' ? list[0] : list[list.length - 1])?.focus();
|
|
20568
|
+
break;
|
|
20569
|
+
}
|
|
20570
|
+
case 'ArrowRight': {
|
|
20571
|
+
if (inSub)
|
|
20572
|
+
break; // deepest level — nothing further to open
|
|
20573
|
+
event.preventDefault();
|
|
20574
|
+
event.stopPropagation();
|
|
20575
|
+
const idx = Number(active?.getAttribute('data-idx'));
|
|
20576
|
+
if (menu.items[idx]?.children?.length && !menu.items[idx].disabled) {
|
|
20577
|
+
this.subIdx.set(idx);
|
|
20578
|
+
setTimeout(() => this.navButtons(true)[0]?.focus());
|
|
20579
|
+
}
|
|
20580
|
+
else {
|
|
20581
|
+
this.switchTop(1);
|
|
20582
|
+
}
|
|
20583
|
+
break;
|
|
20584
|
+
}
|
|
20585
|
+
case 'ArrowLeft': {
|
|
20586
|
+
event.preventDefault();
|
|
20587
|
+
event.stopPropagation();
|
|
20588
|
+
if (inSub) {
|
|
20589
|
+
const parent = this.subIdx();
|
|
20590
|
+
this.subIdx.set(null);
|
|
20591
|
+
this.host.nativeElement
|
|
20592
|
+
.querySelector(`.strct-mb__item[data-idx="${parent}"]`)
|
|
20593
|
+
?.focus();
|
|
20594
|
+
}
|
|
20595
|
+
else if (this.subIdx() != null) {
|
|
20596
|
+
this.subIdx.set(null);
|
|
20597
|
+
}
|
|
20598
|
+
else {
|
|
20599
|
+
this.switchTop(-1);
|
|
20600
|
+
}
|
|
20601
|
+
break;
|
|
20602
|
+
}
|
|
20603
|
+
case 'Escape': {
|
|
20604
|
+
event.preventDefault();
|
|
20605
|
+
event.stopPropagation();
|
|
20606
|
+
this.closeMenu(true);
|
|
20607
|
+
break;
|
|
20608
|
+
}
|
|
20609
|
+
}
|
|
20600
20610
|
}
|
|
20601
|
-
|
|
20602
|
-
|
|
20603
|
-
|
|
20604
|
-
|
|
20605
|
-
|
|
20606
|
-
|
|
20607
|
-
|
|
20608
|
-
|
|
20609
|
-
|
|
20610
|
-
|
|
20611
|
-
|
|
20612
|
-
|
|
20611
|
+
/** Switch the open menu to the next/previous top-level entry; focus follows. */
|
|
20612
|
+
switchTop(dir) {
|
|
20613
|
+
const menus = this.menus();
|
|
20614
|
+
const next = (this.focusIdx() + dir + menus.length) % menus.length;
|
|
20615
|
+
this.focusIdx.set(next);
|
|
20616
|
+
this.openId.set(menus[next].id);
|
|
20617
|
+
this.subIdx.set(null);
|
|
20618
|
+
setTimeout(() => this.topButtons()[next]?.focus());
|
|
20619
|
+
}
|
|
20620
|
+
closeMenu(returnFocus) {
|
|
20621
|
+
this.openId.set(null);
|
|
20622
|
+
this.subIdx.set(null);
|
|
20623
|
+
if (returnFocus)
|
|
20624
|
+
restoreFocus(this.trigger);
|
|
20625
|
+
this.trigger = null;
|
|
20626
|
+
}
|
|
20627
|
+
uid = ++menubarCounter;
|
|
20628
|
+
hintId(menuId, i, j) {
|
|
20629
|
+
// menu ids are consumer data; aria-describedby splits on whitespace.
|
|
20630
|
+
const menu = menuId.replace(/\s+/g, '_');
|
|
20631
|
+
return `strct-mb-${this.uid}-${menu}-${i}${j == null ? '' : '-' + j}-hint`;
|
|
20632
|
+
}
|
|
20633
|
+
topButtons() {
|
|
20634
|
+
return Array.from(this.host.nativeElement.querySelectorAll('.strct-mb__top'));
|
|
20635
|
+
}
|
|
20636
|
+
/** Keyboard-reachable item buttons of the open menu — either its items or the
|
|
20637
|
+
* open submenu's: enabled ones, plus disabled ones that carry a hint (so the
|
|
20638
|
+
* reason can be read). A disabled entry with nothing to say is skipped. */
|
|
20639
|
+
navButtons(sub) {
|
|
20640
|
+
const menuEl = this.host.nativeElement.querySelector('.strct-mb__menu');
|
|
20641
|
+
if (!menuEl)
|
|
20642
|
+
return [];
|
|
20643
|
+
const sel = sub ? '.strct-mb__subitem' : '.strct-mb__item:not(.strct-mb__subitem)';
|
|
20644
|
+
return Array.from(menuEl.querySelectorAll(sel)).filter((b) => b.getAttribute('aria-disabled') !== 'true' || b.hasAttribute('aria-describedby'));
|
|
20613
20645
|
}
|
|
20614
|
-
|
|
20615
|
-
|
|
20616
|
-
|
|
20617
|
-
<div class="strct-hkh__backdrop" (click)="open.set(false)"></div>
|
|
20618
|
-
<div class="strct-hkh" role="dialog" aria-modal="true" [attr.aria-label]="title()">
|
|
20619
|
-
<div class="strct-hkh__head">
|
|
20620
|
-
<span class="strct-hkh__title">{{ title() }}</span>
|
|
20621
|
-
<span class="strct-hkh__headside">
|
|
20622
|
-
<strct-kbd>?</strct-kbd>
|
|
20623
|
-
<button
|
|
20624
|
-
type="button"
|
|
20625
|
-
class="strct-hkh__close"
|
|
20626
|
-
[attr.aria-label]="closeLabel()"
|
|
20627
|
-
(click)="open.set(false)"
|
|
20628
|
-
>
|
|
20629
|
-
<strct-icon name="close" [size]="14" />
|
|
20630
|
-
</button>
|
|
20631
|
-
</span>
|
|
20632
|
-
</div>
|
|
20633
|
-
@for (group of grouped(); track group.name) {
|
|
20634
|
-
<div class="strct-hkh__group">
|
|
20635
|
-
<div class="strct-hkh__groupname">{{ group.name }}</div>
|
|
20636
|
-
@for (hk of group.keys; track hk.combo + hk.description) {
|
|
20637
|
-
<div class="strct-hkh__row">
|
|
20638
|
-
<span class="strct-hkh__desc">{{ hk.description }}</span>
|
|
20639
|
-
<span class="strct-hkh__keys">
|
|
20640
|
-
@for (part of parts(hk.combo); track $index) {
|
|
20641
|
-
<strct-kbd>{{ part }}</strct-kbd>
|
|
20642
|
-
}
|
|
20643
|
-
</span>
|
|
20644
|
-
</div>
|
|
20645
|
-
}
|
|
20646
|
-
</div>
|
|
20647
|
-
} @empty {
|
|
20648
|
-
<p class="strct-hkh__empty">{{ emptyText() }}</p>
|
|
20646
|
+
onDocClick(event) {
|
|
20647
|
+
if (this.openId() && !this.host.nativeElement.contains(event.target)) {
|
|
20648
|
+
this.closeMenu(false);
|
|
20649
20649
|
}
|
|
20650
|
-
</div>
|
|
20651
20650
|
}
|
|
20652
|
-
|
|
20651
|
+
onEscape(event) {
|
|
20652
|
+
if (!this.openId())
|
|
20653
|
+
return;
|
|
20654
|
+
event.stopPropagation();
|
|
20655
|
+
this.closeMenu(true);
|
|
20656
|
+
}
|
|
20657
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMenubar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
20658
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctMenubar, isStandalone: true, selector: "strct-menubar", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { picked: "picked" }, host: { listeners: { "document:click": "onDocClick($event)", "document:keydown.escape": "onEscape($event)" } }, ngImport: i0, template: `
|
|
20659
|
+
<div class="strct-mb" role="menubar" [attr.aria-label]="ariaLabel()">
|
|
20660
|
+
@for (menu of menus(); track menu.id; let i = $index) {
|
|
20661
|
+
<div class="strct-mb__wrap">
|
|
20662
|
+
<button
|
|
20663
|
+
type="button"
|
|
20664
|
+
class="strct-mb__top"
|
|
20665
|
+
role="menuitem"
|
|
20666
|
+
[class.strct-mb__top--open]="openId() === menu.id"
|
|
20667
|
+
[attr.aria-haspopup]="'menu'"
|
|
20668
|
+
[attr.aria-expanded]="openId() === menu.id"
|
|
20669
|
+
[attr.tabindex]="i === focusIdx() ? 0 : -1"
|
|
20670
|
+
(click)="toggleMenu(menu.id, i)"
|
|
20671
|
+
(keydown)="onTopKey($event, i)"
|
|
20672
|
+
>
|
|
20673
|
+
{{ menu.label }}
|
|
20674
|
+
</button>
|
|
20675
|
+
@if (openId() === menu.id) {
|
|
20676
|
+
<div
|
|
20677
|
+
class="strct-mb__menu"
|
|
20678
|
+
role="menu"
|
|
20679
|
+
tabindex="-1"
|
|
20680
|
+
[attr.aria-label]="menu.label"
|
|
20681
|
+
(keydown)="onMenuKey($event, menu)"
|
|
20682
|
+
>
|
|
20683
|
+
@for (item of menu.items; track $index; let i = $index) {
|
|
20684
|
+
@if (item.divider) {
|
|
20685
|
+
<div class="strct-mb__divider" role="separator"></div>
|
|
20686
|
+
} @else {
|
|
20687
|
+
<div class="strct-mb__row">
|
|
20688
|
+
<button
|
|
20689
|
+
type="button"
|
|
20690
|
+
class="strct-mb__item"
|
|
20691
|
+
role="menuitem"
|
|
20692
|
+
[attr.data-idx]="i"
|
|
20693
|
+
[class.strct-mb__item--critical]="item.critical"
|
|
20694
|
+
[attr.aria-haspopup]="item.children?.length ? 'menu' : null"
|
|
20695
|
+
[attr.aria-expanded]="item.children?.length ? subIdx() === i : null"
|
|
20696
|
+
[attr.aria-disabled]="item.disabled ? 'true' : null"
|
|
20697
|
+
[attr.aria-describedby]="item.hint ? hintId(menu.id, i) : null"
|
|
20698
|
+
[attr.title]="item.hint || null"
|
|
20699
|
+
(click)="onItemClick(menu, item, i)"
|
|
20700
|
+
(mouseenter)="onItemHover(item, i)"
|
|
20701
|
+
>
|
|
20702
|
+
@if (item.icon) {
|
|
20703
|
+
<strct-icon [name]="item.icon" [size]="13" />
|
|
20704
|
+
}
|
|
20705
|
+
{{ item.label }}
|
|
20706
|
+
@if (item.children?.length) {
|
|
20707
|
+
<strct-icon class="strct-mb__caret" name="chevronRight" [size]="12" />
|
|
20708
|
+
}
|
|
20709
|
+
</button>
|
|
20710
|
+
@if (item.hint) {
|
|
20711
|
+
<span [id]="hintId(menu.id, i)" hidden>{{ item.hint }}</span>
|
|
20712
|
+
}
|
|
20713
|
+
@if (subIdx() === i && item.children?.length) {
|
|
20714
|
+
<div class="strct-mb__submenu" role="menu" [attr.aria-label]="item.label">
|
|
20715
|
+
@for (sub of item.children; track $index; let j = $index) {
|
|
20716
|
+
@if (sub.divider) {
|
|
20717
|
+
<div class="strct-mb__divider" role="separator"></div>
|
|
20718
|
+
} @else {
|
|
20719
|
+
<button
|
|
20720
|
+
type="button"
|
|
20721
|
+
class="strct-mb__item strct-mb__subitem"
|
|
20722
|
+
role="menuitem"
|
|
20723
|
+
[class.strct-mb__item--critical]="sub.critical"
|
|
20724
|
+
[attr.aria-disabled]="sub.disabled ? 'true' : null"
|
|
20725
|
+
[attr.aria-describedby]="sub.hint ? hintId(menu.id, i, j) : null"
|
|
20726
|
+
[attr.title]="sub.hint || null"
|
|
20727
|
+
(click)="pick(menu, sub)"
|
|
20728
|
+
>
|
|
20729
|
+
@if (sub.icon) {
|
|
20730
|
+
<strct-icon [name]="sub.icon" [size]="13" />
|
|
20731
|
+
}
|
|
20732
|
+
{{ sub.label }}
|
|
20733
|
+
</button>
|
|
20734
|
+
@if (sub.hint) {
|
|
20735
|
+
<span [id]="hintId(menu.id, i, j)" hidden>{{ sub.hint }}</span>
|
|
20736
|
+
}
|
|
20737
|
+
}
|
|
20738
|
+
}
|
|
20739
|
+
</div>
|
|
20740
|
+
}
|
|
20741
|
+
</div>
|
|
20742
|
+
}
|
|
20743
|
+
}
|
|
20744
|
+
</div>
|
|
20745
|
+
}
|
|
20746
|
+
</div>
|
|
20747
|
+
}
|
|
20748
|
+
</div>
|
|
20749
|
+
`, isInline: true, styles: [".strct-mb{display:inline-flex;gap:2px;padding:2px;background:var(--bg-2);border:1px solid var(--b1);border-radius:var(--radius-md)}.strct-mb__wrap{position:relative}.strct-mb__top{padding:4px 11px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;cursor:pointer;white-space:nowrap}.strct-mb__top:hover,.strct-mb__top--open{background:var(--bg-3)}.strct-mb__top:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-mb__menu{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:var(--z-dropdown);min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}.strct-mb__menu:focus{outline:none}.strct-mb__item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;text-align:start;cursor:pointer;white-space:nowrap}.strct-mb__item:hover:not([aria-disabled=true]){background:var(--bg-3)}.strct-mb__item--critical{color:var(--critical)}.strct-mb__item--critical:hover:not([aria-disabled=true]){background:var(--critical-bg)}.strct-mb__item[aria-disabled=true]{color:var(--t4);cursor:default}.strct-mb__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-mb__divider{height:1px;margin:4px 6px;background:var(--b2)}.strct-mb__row{position:relative}.strct-mb__caret{margin-inline-start:auto;color:var(--t3)}[dir=rtl] .strct-mb__caret{transform:rotate(180deg)}.strct-mb__submenu{position:absolute;top:-5px;inset-inline-start:calc(100% + 2px);z-index:var(--z-base);min-width:160px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
20653
20750
|
}
|
|
20654
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type:
|
|
20751
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMenubar, decorators: [{
|
|
20655
20752
|
type: Component,
|
|
20656
|
-
args: [{ selector: 'strct-
|
|
20657
|
-
|
|
20658
|
-
|
|
20659
|
-
|
|
20660
|
-
|
|
20661
|
-
|
|
20662
|
-
|
|
20663
|
-
|
|
20664
|
-
|
|
20665
|
-
|
|
20666
|
-
|
|
20667
|
-
|
|
20668
|
-
|
|
20753
|
+
args: [{ selector: 'strct-menubar', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
|
|
20754
|
+
<div class="strct-mb" role="menubar" [attr.aria-label]="ariaLabel()">
|
|
20755
|
+
@for (menu of menus(); track menu.id; let i = $index) {
|
|
20756
|
+
<div class="strct-mb__wrap">
|
|
20757
|
+
<button
|
|
20758
|
+
type="button"
|
|
20759
|
+
class="strct-mb__top"
|
|
20760
|
+
role="menuitem"
|
|
20761
|
+
[class.strct-mb__top--open]="openId() === menu.id"
|
|
20762
|
+
[attr.aria-haspopup]="'menu'"
|
|
20763
|
+
[attr.aria-expanded]="openId() === menu.id"
|
|
20764
|
+
[attr.tabindex]="i === focusIdx() ? 0 : -1"
|
|
20765
|
+
(click)="toggleMenu(menu.id, i)"
|
|
20766
|
+
(keydown)="onTopKey($event, i)"
|
|
20767
|
+
>
|
|
20768
|
+
{{ menu.label }}
|
|
20769
|
+
</button>
|
|
20770
|
+
@if (openId() === menu.id) {
|
|
20771
|
+
<div
|
|
20772
|
+
class="strct-mb__menu"
|
|
20773
|
+
role="menu"
|
|
20774
|
+
tabindex="-1"
|
|
20775
|
+
[attr.aria-label]="menu.label"
|
|
20776
|
+
(keydown)="onMenuKey($event, menu)"
|
|
20669
20777
|
>
|
|
20670
|
-
|
|
20671
|
-
|
|
20672
|
-
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20677
|
-
|
|
20678
|
-
|
|
20679
|
-
|
|
20680
|
-
|
|
20681
|
-
|
|
20682
|
-
|
|
20683
|
-
|
|
20684
|
-
|
|
20685
|
-
|
|
20686
|
-
|
|
20687
|
-
|
|
20688
|
-
|
|
20689
|
-
|
|
20690
|
-
|
|
20691
|
-
|
|
20692
|
-
|
|
20693
|
-
|
|
20694
|
-
|
|
20778
|
+
@for (item of menu.items; track $index; let i = $index) {
|
|
20779
|
+
@if (item.divider) {
|
|
20780
|
+
<div class="strct-mb__divider" role="separator"></div>
|
|
20781
|
+
} @else {
|
|
20782
|
+
<div class="strct-mb__row">
|
|
20783
|
+
<button
|
|
20784
|
+
type="button"
|
|
20785
|
+
class="strct-mb__item"
|
|
20786
|
+
role="menuitem"
|
|
20787
|
+
[attr.data-idx]="i"
|
|
20788
|
+
[class.strct-mb__item--critical]="item.critical"
|
|
20789
|
+
[attr.aria-haspopup]="item.children?.length ? 'menu' : null"
|
|
20790
|
+
[attr.aria-expanded]="item.children?.length ? subIdx() === i : null"
|
|
20791
|
+
[attr.aria-disabled]="item.disabled ? 'true' : null"
|
|
20792
|
+
[attr.aria-describedby]="item.hint ? hintId(menu.id, i) : null"
|
|
20793
|
+
[attr.title]="item.hint || null"
|
|
20794
|
+
(click)="onItemClick(menu, item, i)"
|
|
20795
|
+
(mouseenter)="onItemHover(item, i)"
|
|
20796
|
+
>
|
|
20797
|
+
@if (item.icon) {
|
|
20798
|
+
<strct-icon [name]="item.icon" [size]="13" />
|
|
20799
|
+
}
|
|
20800
|
+
{{ item.label }}
|
|
20801
|
+
@if (item.children?.length) {
|
|
20802
|
+
<strct-icon class="strct-mb__caret" name="chevronRight" [size]="12" />
|
|
20803
|
+
}
|
|
20804
|
+
</button>
|
|
20805
|
+
@if (item.hint) {
|
|
20806
|
+
<span [id]="hintId(menu.id, i)" hidden>{{ item.hint }}</span>
|
|
20807
|
+
}
|
|
20808
|
+
@if (subIdx() === i && item.children?.length) {
|
|
20809
|
+
<div class="strct-mb__submenu" role="menu" [attr.aria-label]="item.label">
|
|
20810
|
+
@for (sub of item.children; track $index; let j = $index) {
|
|
20811
|
+
@if (sub.divider) {
|
|
20812
|
+
<div class="strct-mb__divider" role="separator"></div>
|
|
20813
|
+
} @else {
|
|
20814
|
+
<button
|
|
20815
|
+
type="button"
|
|
20816
|
+
class="strct-mb__item strct-mb__subitem"
|
|
20817
|
+
role="menuitem"
|
|
20818
|
+
[class.strct-mb__item--critical]="sub.critical"
|
|
20819
|
+
[attr.aria-disabled]="sub.disabled ? 'true' : null"
|
|
20820
|
+
[attr.aria-describedby]="sub.hint ? hintId(menu.id, i, j) : null"
|
|
20821
|
+
[attr.title]="sub.hint || null"
|
|
20822
|
+
(click)="pick(menu, sub)"
|
|
20823
|
+
>
|
|
20824
|
+
@if (sub.icon) {
|
|
20825
|
+
<strct-icon [name]="sub.icon" [size]="13" />
|
|
20826
|
+
}
|
|
20827
|
+
{{ sub.label }}
|
|
20828
|
+
</button>
|
|
20829
|
+
@if (sub.hint) {
|
|
20830
|
+
<span [id]="hintId(menu.id, i, j)" hidden>{{ sub.hint }}</span>
|
|
20831
|
+
}
|
|
20832
|
+
}
|
|
20833
|
+
}
|
|
20834
|
+
</div>
|
|
20835
|
+
}
|
|
20836
|
+
</div>
|
|
20837
|
+
}
|
|
20838
|
+
}
|
|
20839
|
+
</div>
|
|
20840
|
+
}
|
|
20841
|
+
</div>
|
|
20842
|
+
}
|
|
20843
|
+
</div>
|
|
20844
|
+
`, styles: [".strct-mb{display:inline-flex;gap:2px;padding:2px;background:var(--bg-2);border:1px solid var(--b1);border-radius:var(--radius-md)}.strct-mb__wrap{position:relative}.strct-mb__top{padding:4px 11px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;cursor:pointer;white-space:nowrap}.strct-mb__top:hover,.strct-mb__top--open{background:var(--bg-3)}.strct-mb__top:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-mb__menu{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:var(--z-dropdown);min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}.strct-mb__menu:focus{outline:none}.strct-mb__item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;text-align:start;cursor:pointer;white-space:nowrap}.strct-mb__item:hover:not([aria-disabled=true]){background:var(--bg-3)}.strct-mb__item--critical{color:var(--critical)}.strct-mb__item--critical:hover:not([aria-disabled=true]){background:var(--critical-bg)}.strct-mb__item[aria-disabled=true]{color:var(--t4);cursor:default}.strct-mb__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-mb__divider{height:1px;margin:4px 6px;background:var(--b2)}.strct-mb__row{position:relative}.strct-mb__caret{margin-inline-start:auto;color:var(--t3)}[dir=rtl] .strct-mb__caret{transform:rotate(180deg)}.strct-mb__submenu{position:absolute;top:-5px;inset-inline-start:calc(100% + 2px);z-index:var(--z-base);min-width:160px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}\n"] }]
|
|
20845
|
+
}], propDecorators: { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], picked: [{ type: i0.Output, args: ["picked"] }], onDocClick: [{
|
|
20846
|
+
type: HostListener,
|
|
20847
|
+
args: ['document:click', ['$event']]
|
|
20848
|
+
}], onEscape: [{
|
|
20695
20849
|
type: HostListener,
|
|
20696
20850
|
args: ['document:keydown.escape', ['$event']]
|
|
20697
20851
|
}] } });
|
|
20698
20852
|
|
|
20699
|
-
|
|
20853
|
+
function normalize(combo) {
|
|
20854
|
+
return combo
|
|
20855
|
+
.toLowerCase()
|
|
20856
|
+
.split('+')
|
|
20857
|
+
.map((p) => p.trim())
|
|
20858
|
+
.sort()
|
|
20859
|
+
.join('+');
|
|
20860
|
+
}
|
|
20861
|
+
function comboOf(event) {
|
|
20862
|
+
const parts = [];
|
|
20863
|
+
if (event.ctrlKey)
|
|
20864
|
+
parts.push('ctrl');
|
|
20865
|
+
if (event.metaKey)
|
|
20866
|
+
parts.push('meta');
|
|
20867
|
+
if (event.altKey)
|
|
20868
|
+
parts.push('alt');
|
|
20869
|
+
if (event.shiftKey)
|
|
20870
|
+
parts.push('shift');
|
|
20871
|
+
parts.push(event.key.toLowerCase());
|
|
20872
|
+
return parts.sort().join('+');
|
|
20873
|
+
}
|
|
20874
|
+
/** `mod` matches Ctrl (and ⌘ on mac keyboards) — expand to both. */
|
|
20875
|
+
function expand(combo) {
|
|
20876
|
+
const n = combo.toLowerCase();
|
|
20877
|
+
if (!n.includes('mod'))
|
|
20878
|
+
return [normalize(n)];
|
|
20879
|
+
return [normalize(n.replace('mod', 'ctrl')), normalize(n.replace('mod', 'meta'))];
|
|
20880
|
+
}
|
|
20700
20881
|
/**
|
|
20701
|
-
*
|
|
20702
|
-
*
|
|
20703
|
-
* <ul strctReorder (reordered)="move($event)">
|
|
20704
|
-
* @for (s of steps(); track s.id) {
|
|
20705
|
-
* <li strctReorderItem>{{ s.label }}</li>
|
|
20706
|
-
* }
|
|
20707
|
-
* </ul>
|
|
20882
|
+
* Application hotkeys, centrally registered so they are discoverable — the
|
|
20883
|
+
* Blueprint pattern:
|
|
20708
20884
|
*
|
|
20709
|
-
*
|
|
20710
|
-
*
|
|
20711
|
-
*
|
|
20885
|
+
* private hotkeys = inject(StrctHotkeysService);
|
|
20886
|
+
* ngOnInit() { this.dispose = this.hotkeys.register(
|
|
20887
|
+
* { combo: 'mod+k', description: 'Open command palette', group: 'Global',
|
|
20888
|
+
* handler: () => this.palette.open() }); }
|
|
20712
20889
|
*
|
|
20713
|
-
*
|
|
20714
|
-
*
|
|
20715
|
-
*
|
|
20716
|
-
*
|
|
20717
|
-
* items reference the sr-only `instructions` via `aria-describedby`.
|
|
20890
|
+
* Combos: `'a'`, `'shift+?'`, `'ctrl+k'`, `'meta+shift+p'`, `'mod+k'` (mod = Ctrl/⌘).
|
|
20891
|
+
* Plain-key combos are suppressed while typing in inputs / textareas /
|
|
20892
|
+
* contenteditable; modifier combos always fire. Pair with
|
|
20893
|
+
* `<strct-hotkeys-help/>` for the ⌘/ ("?") cheatsheet overlay.
|
|
20718
20894
|
*/
|
|
20719
|
-
class
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
|
|
20723
|
-
|
|
20724
|
-
|
|
20725
|
-
reordered = output();
|
|
20726
|
-
/** Disable all reordering (display-only mode). */
|
|
20727
|
-
reorderDisabled = input(false, { ...(ngDevMode ? { debugName: "reorderDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
20728
|
-
/**
|
|
20729
|
-
* Keyboard instructions (localizable), rendered sr-only and referenced by
|
|
20730
|
-
* every item's `aria-describedby`. Set to '' to opt out.
|
|
20731
|
-
*/
|
|
20732
|
-
instructions = input('Press Alt+ArrowUp or Alt+ArrowDown to move this item', /* @ts-ignore */
|
|
20733
|
-
...(ngDevMode ? [{ debugName: "instructions" }] : /* istanbul ignore next */ []));
|
|
20734
|
-
/**
|
|
20735
|
-
* Builds the live-region announcement after a move (localizable):
|
|
20736
|
-
* (moved item's text, new 1-based position, item count).
|
|
20737
|
-
*/
|
|
20738
|
-
announcement = input((label, position, total) => `Moved ${label} to position ${position} of ${total}`, /* @ts-ignore */
|
|
20739
|
-
...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
20740
|
-
dragIndex = signal(null, /* @ts-ignore */
|
|
20741
|
-
...(ngDevMode ? [{ debugName: "dragIndex" }] : /* istanbul ignore next */ []));
|
|
20742
|
-
overIndex = signal(null, /* @ts-ignore */
|
|
20743
|
-
...(ngDevMode ? [{ debugName: "overIndex" }] : /* istanbul ignore next */ []));
|
|
20744
|
-
/** Id of the sr-only instructions element the items point to. */
|
|
20745
|
-
instructionsId = `strct-reorder-hint-${++reorderCounter}`;
|
|
20746
|
-
hint = null;
|
|
20895
|
+
class StrctHotkeysService {
|
|
20896
|
+
registry = signal([], /* @ts-ignore */
|
|
20897
|
+
...(ngDevMode ? [{ debugName: "registry" }] : /* istanbul ignore next */ []));
|
|
20898
|
+
/** All registered hotkeys (for the help overlay). */
|
|
20899
|
+
hotkeys = this.registry.asReadonly();
|
|
20900
|
+
listener = (event) => this.onKeydown(event);
|
|
20747
20901
|
constructor() {
|
|
20748
|
-
|
|
20749
|
-
effect(() => {
|
|
20750
|
-
const text = this.instructions();
|
|
20751
|
-
if (text && !this.hint) {
|
|
20752
|
-
this.hint = this.renderer.createElement('span');
|
|
20753
|
-
this.renderer.setAttribute(this.hint, 'id', this.instructionsId);
|
|
20754
|
-
this.hint.style.cssText =
|
|
20755
|
-
'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;';
|
|
20756
|
-
this.renderer.appendChild(this.doc.body, this.hint);
|
|
20757
|
-
}
|
|
20758
|
-
if (this.hint)
|
|
20759
|
-
this.hint.textContent = text;
|
|
20760
|
-
});
|
|
20761
|
-
}
|
|
20762
|
-
items() {
|
|
20763
|
-
return [...this.host.nativeElement.querySelectorAll('[strctReorderItem]')];
|
|
20764
|
-
}
|
|
20765
|
-
indexOf(el) {
|
|
20766
|
-
return this.items().indexOf(el);
|
|
20767
|
-
}
|
|
20768
|
-
commit(from, to) {
|
|
20769
|
-
this.dragIndex.set(null);
|
|
20770
|
-
this.overIndex.set(null);
|
|
20771
|
-
if (from < 0 || to < 0 || from === to)
|
|
20772
|
-
return;
|
|
20773
|
-
const label = this.items()[from]?.textContent?.trim() ?? '';
|
|
20774
|
-
this.reordered.emit({ from, to });
|
|
20775
|
-
this.announcer.announce(this.announcement()(label, to + 1, this.items().length));
|
|
20776
|
-
}
|
|
20777
|
-
onContainerDragOver(event) {
|
|
20778
|
-
// Only the container's own empty space (below the last item); drags over
|
|
20779
|
-
// items are handled by the items themselves.
|
|
20780
|
-
if (this.dragIndex() == null)
|
|
20781
|
-
return;
|
|
20782
|
-
if (event.target.closest('[strctReorderItem]'))
|
|
20783
|
-
return;
|
|
20784
|
-
event.preventDefault();
|
|
20902
|
+
document.addEventListener('keydown', this.listener);
|
|
20785
20903
|
}
|
|
20786
|
-
|
|
20787
|
-
|
|
20788
|
-
|
|
20789
|
-
|
|
20790
|
-
if (event.target.closest('[strctReorderItem]'))
|
|
20791
|
-
return;
|
|
20792
|
-
event.preventDefault();
|
|
20793
|
-
// Drop into empty space moves the item to the end.
|
|
20794
|
-
this.commit(from, this.items().length - 1);
|
|
20904
|
+
/** Register a hotkey; the returned function unregisters it. */
|
|
20905
|
+
register(hotkey) {
|
|
20906
|
+
this.registry.update((list) => [...list, hotkey]);
|
|
20907
|
+
return () => this.registry.update((list) => list.filter((h) => h !== hotkey));
|
|
20795
20908
|
}
|
|
20796
|
-
|
|
20797
|
-
|
|
20798
|
-
|
|
20799
|
-
const
|
|
20800
|
-
|
|
20909
|
+
onKeydown(event) {
|
|
20910
|
+
const combo = comboOf(event);
|
|
20911
|
+
const typing = isTyping(event);
|
|
20912
|
+
for (const hk of this.registry()) {
|
|
20913
|
+
if (!expand(hk.combo).includes(combo))
|
|
20914
|
+
continue;
|
|
20915
|
+
// While typing, only modifier combos may fire.
|
|
20916
|
+
if (typing && !(event.ctrlKey || event.metaKey || event.altKey))
|
|
20917
|
+
continue;
|
|
20918
|
+
event.preventDefault();
|
|
20919
|
+
hk.handler(event);
|
|
20801
20920
|
return;
|
|
20802
|
-
|
|
20921
|
+
}
|
|
20803
20922
|
}
|
|
20804
20923
|
ngOnDestroy() {
|
|
20805
|
-
|
|
20806
|
-
this.renderer.removeChild(this.doc.body, this.hint);
|
|
20807
|
-
this.hint = null;
|
|
20808
|
-
}
|
|
20924
|
+
document.removeEventListener('keydown', this.listener);
|
|
20809
20925
|
}
|
|
20810
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type:
|
|
20811
|
-
static
|
|
20926
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctHotkeysService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
20927
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctHotkeysService, providedIn: 'root' });
|
|
20812
20928
|
}
|
|
20813
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type:
|
|
20814
|
-
type:
|
|
20815
|
-
args: [{
|
|
20816
|
-
}], ctorParameters: () => []
|
|
20817
|
-
|
|
20818
|
-
|
|
20819
|
-
|
|
20820
|
-
|
|
20821
|
-
|
|
20822
|
-
|
|
20823
|
-
|
|
20824
|
-
|
|
20825
|
-
|
|
20826
|
-
/**
|
|
20827
|
-
|
|
20828
|
-
|
|
20829
|
-
|
|
20830
|
-
|
|
20831
|
-
|
|
20832
|
-
|
|
20833
|
-
|
|
20834
|
-
|
|
20835
|
-
|
|
20836
|
-
|
|
20837
|
-
}
|
|
20838
|
-
|
|
20839
|
-
|
|
20840
|
-
}
|
|
20841
|
-
|
|
20842
|
-
|
|
20843
|
-
|
|
20844
|
-
|
|
20845
|
-
|
|
20846
|
-
|
|
20847
|
-
|
|
20848
|
-
|
|
20849
|
-
|
|
20850
|
-
|
|
20851
|
-
|
|
20852
|
-
|
|
20853
|
-
|
|
20854
|
-
|
|
20855
|
-
|
|
20856
|
-
|
|
20929
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctHotkeysService, decorators: [{
|
|
20930
|
+
type: Injectable,
|
|
20931
|
+
args: [{ providedIn: 'root' }]
|
|
20932
|
+
}], ctorParameters: () => [] });
|
|
20933
|
+
function isTyping(event) {
|
|
20934
|
+
const el = event.target;
|
|
20935
|
+
if (!el)
|
|
20936
|
+
return false;
|
|
20937
|
+
return (el.tagName === 'INPUT' ||
|
|
20938
|
+
el.tagName === 'TEXTAREA' ||
|
|
20939
|
+
el.tagName === 'SELECT' ||
|
|
20940
|
+
el.isContentEditable);
|
|
20941
|
+
}
|
|
20942
|
+
/**
|
|
20943
|
+
* The hotkey cheatsheet overlay. Mount once near the app root; it registers
|
|
20944
|
+
* `shift+?` (typed as `?`) itself and lists everything in the service, grouped:
|
|
20945
|
+
*
|
|
20946
|
+
* <strct-hotkeys-help />
|
|
20947
|
+
*/
|
|
20948
|
+
class StrctHotkeysHelp {
|
|
20949
|
+
service = inject(StrctHotkeysService);
|
|
20950
|
+
host = inject((ElementRef));
|
|
20951
|
+
/** Overlay visibility (two-way; `?` toggles it too). */
|
|
20952
|
+
open = model(false, /* @ts-ignore */
|
|
20953
|
+
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
20954
|
+
/** Localizable strings. */
|
|
20955
|
+
title = input('Keyboard shortcuts', /* @ts-ignore */
|
|
20956
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
20957
|
+
emptyText = input('No shortcuts registered.', /* @ts-ignore */
|
|
20958
|
+
...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
20959
|
+
/** Accessible label of the × close button (localizable). */
|
|
20960
|
+
closeLabel = input('Close', /* @ts-ignore */
|
|
20961
|
+
...(ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []));
|
|
20962
|
+
/** Element that had focus before the overlay opened, restored on close. */
|
|
20963
|
+
previousActive = null;
|
|
20964
|
+
grouped = computed(() => {
|
|
20965
|
+
const groups = new Map();
|
|
20966
|
+
for (const hk of this.service.hotkeys()) {
|
|
20967
|
+
const name = hk.group ?? 'General';
|
|
20968
|
+
const list = groups.get(name);
|
|
20969
|
+
if (list)
|
|
20970
|
+
list.push(hk);
|
|
20971
|
+
else
|
|
20972
|
+
groups.set(name, [hk]);
|
|
20973
|
+
}
|
|
20974
|
+
return [...groups.entries()].map(([name, keys]) => ({ name, keys }));
|
|
20975
|
+
}, /* @ts-ignore */
|
|
20976
|
+
...(ngDevMode ? [{ debugName: "grouped" }] : /* istanbul ignore next */ []));
|
|
20977
|
+
constructor() {
|
|
20978
|
+
const dispose = this.service.register({
|
|
20979
|
+
combo: 'shift+?',
|
|
20980
|
+
description: 'Show this help',
|
|
20981
|
+
group: 'General',
|
|
20982
|
+
handler: () => this.open.update((v) => !v),
|
|
20983
|
+
});
|
|
20984
|
+
inject(DestroyRef).onDestroy(dispose);
|
|
20985
|
+
// Focus lifecycle: remember the trigger, move focus into the dialog once
|
|
20986
|
+
// it has rendered, and hand focus back on close.
|
|
20987
|
+
effect(() => {
|
|
20988
|
+
if (this.open()) {
|
|
20989
|
+
this.previousActive = saveFocusedElement();
|
|
20990
|
+
setTimeout(() => {
|
|
20991
|
+
const dialog = this.host.nativeElement.querySelector('.strct-hkh');
|
|
20992
|
+
if (dialog)
|
|
20993
|
+
focusFirstIn(dialog);
|
|
20994
|
+
});
|
|
20995
|
+
}
|
|
20996
|
+
else {
|
|
20997
|
+
restoreFocus(this.previousActive);
|
|
20998
|
+
this.previousActive = null;
|
|
20999
|
+
}
|
|
21000
|
+
});
|
|
20857
21001
|
}
|
|
20858
|
-
|
|
20859
|
-
|
|
20860
|
-
|
|
20861
|
-
if (
|
|
21002
|
+
// Escape is handled locally — registering it in the service would swallow
|
|
21003
|
+
// every Escape in the app (the service preventDefaults on match).
|
|
21004
|
+
onEscape(event) {
|
|
21005
|
+
if (!this.open())
|
|
20862
21006
|
return;
|
|
20863
|
-
|
|
21007
|
+
// Document-level: stopImmediatePropagation so a host modal/drawer with its
|
|
21008
|
+
// own document listener doesn't also see the Escape the overlay consumed.
|
|
21009
|
+
event.stopImmediatePropagation();
|
|
21010
|
+
this.open.set(false);
|
|
20864
21011
|
}
|
|
20865
|
-
|
|
20866
|
-
|
|
20867
|
-
|
|
21012
|
+
parts(combo) {
|
|
21013
|
+
return combo.split('+').map((p) => {
|
|
21014
|
+
const map = {
|
|
21015
|
+
mod: '⌘/Ctrl',
|
|
21016
|
+
meta: '⌘',
|
|
21017
|
+
ctrl: 'Ctrl',
|
|
21018
|
+
alt: 'Alt',
|
|
21019
|
+
shift: 'Shift',
|
|
21020
|
+
escape: 'Esc',
|
|
21021
|
+
};
|
|
21022
|
+
return map[p.trim().toLowerCase()] ?? p.trim().toUpperCase();
|
|
21023
|
+
});
|
|
20868
21024
|
}
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
|
|
20872
|
-
|
|
20873
|
-
|
|
20874
|
-
|
|
20875
|
-
|
|
20876
|
-
|
|
20877
|
-
|
|
20878
|
-
|
|
20879
|
-
|
|
20880
|
-
|
|
20881
|
-
|
|
21025
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctHotkeysHelp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
21026
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctHotkeysHelp, isStandalone: true, selector: "strct-hotkeys-help", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape($event)" } }, ngImport: i0, template: `
|
|
21027
|
+
@if (open()) {
|
|
21028
|
+
<div class="strct-hkh__backdrop" (click)="open.set(false)"></div>
|
|
21029
|
+
<div class="strct-hkh" role="dialog" aria-modal="true" [attr.aria-label]="title()">
|
|
21030
|
+
<div class="strct-hkh__head">
|
|
21031
|
+
<span class="strct-hkh__title">{{ title() }}</span>
|
|
21032
|
+
<span class="strct-hkh__headside">
|
|
21033
|
+
<strct-kbd>?</strct-kbd>
|
|
21034
|
+
<button
|
|
21035
|
+
type="button"
|
|
21036
|
+
class="strct-hkh__close"
|
|
21037
|
+
[attr.aria-label]="closeLabel()"
|
|
21038
|
+
(click)="open.set(false)"
|
|
21039
|
+
>
|
|
21040
|
+
<strct-icon name="close" [size]="14" />
|
|
21041
|
+
</button>
|
|
21042
|
+
</span>
|
|
21043
|
+
</div>
|
|
21044
|
+
@for (group of grouped(); track group.name) {
|
|
21045
|
+
<div class="strct-hkh__group">
|
|
21046
|
+
<div class="strct-hkh__groupname">{{ group.name }}</div>
|
|
21047
|
+
@for (hk of group.keys; track hk.combo + hk.description) {
|
|
21048
|
+
<div class="strct-hkh__row">
|
|
21049
|
+
<span class="strct-hkh__desc">{{ hk.description }}</span>
|
|
21050
|
+
<span class="strct-hkh__keys">
|
|
21051
|
+
@for (part of parts(hk.combo); track $index) {
|
|
21052
|
+
<strct-kbd>{{ part }}</strct-kbd>
|
|
21053
|
+
}
|
|
21054
|
+
</span>
|
|
21055
|
+
</div>
|
|
21056
|
+
}
|
|
21057
|
+
</div>
|
|
21058
|
+
} @empty {
|
|
21059
|
+
<p class="strct-hkh__empty">{{ emptyText() }}</p>
|
|
21060
|
+
}
|
|
21061
|
+
</div>
|
|
20882
21062
|
}
|
|
20883
|
-
|
|
20884
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctReorderItem, isStandalone: true, selector: "[strctReorderItem]", host: { listeners: { "dragstart": "onDragStart($event)", "dragover": "onDragOver($event)", "drop": "onDrop($event)", "dragend": "onDragEnd()", "keydown": "onKeydown($event)" }, properties: { "attr.draggable": "!list.reorderDisabled()", "attr.tabindex": "hostTabindex()", "class.strct-reorder--dragging": "isDragging()", "class.strct-reorder--over": "isOver()", "attr.aria-roledescription": "'sortable'", "attr.aria-keyshortcuts": "'Alt+ArrowUp Alt+ArrowDown'", "attr.aria-posinset": "index() + 1", "attr.aria-setsize": "list.items().length", "attr.aria-describedby": "list.instructions() ? list.instructionsId : null" } }, ngImport: i0 });
|
|
20885
|
-
}
|
|
20886
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctReorderItem, decorators: [{
|
|
20887
|
-
type: Directive,
|
|
20888
|
-
args: [{
|
|
20889
|
-
selector: '[strctReorderItem]',
|
|
20890
|
-
host: {
|
|
20891
|
-
'[attr.draggable]': '!list.reorderDisabled()',
|
|
20892
|
-
'[attr.tabindex]': 'hostTabindex()',
|
|
20893
|
-
'[class.strct-reorder--dragging]': 'isDragging()',
|
|
20894
|
-
'[class.strct-reorder--over]': 'isOver()',
|
|
20895
|
-
'[attr.aria-roledescription]': "'sortable'",
|
|
20896
|
-
'[attr.aria-keyshortcuts]': "'Alt+ArrowUp Alt+ArrowDown'",
|
|
20897
|
-
'[attr.aria-posinset]': 'index() + 1',
|
|
20898
|
-
'[attr.aria-setsize]': 'list.items().length',
|
|
20899
|
-
'[attr.aria-describedby]': 'list.instructions() ? list.instructionsId : null',
|
|
20900
|
-
},
|
|
20901
|
-
}]
|
|
20902
|
-
}], propDecorators: { onDragStart: [{
|
|
20903
|
-
type: HostListener,
|
|
20904
|
-
args: ['dragstart', ['$event']]
|
|
20905
|
-
}], onDragOver: [{
|
|
20906
|
-
type: HostListener,
|
|
20907
|
-
args: ['dragover', ['$event']]
|
|
20908
|
-
}], onDrop: [{
|
|
20909
|
-
type: HostListener,
|
|
20910
|
-
args: ['drop', ['$event']]
|
|
20911
|
-
}], onDragEnd: [{
|
|
20912
|
-
type: HostListener,
|
|
20913
|
-
args: ['dragend']
|
|
20914
|
-
}], onKeydown: [{
|
|
20915
|
-
type: HostListener,
|
|
20916
|
-
args: ['keydown', ['$event']]
|
|
20917
|
-
}] } });
|
|
20918
|
-
|
|
20919
|
-
/**
|
|
20920
|
-
* Default accessible text per status, used when `label` is not given
|
|
20921
|
-
* (localizable — override via `label`). The canonical `StrctStatus` vocabulary
|
|
20922
|
-
* has no 'ok'/'info': OK maps to 'success', info to 'accent'.
|
|
20923
|
-
*/
|
|
20924
|
-
const DEFAULT_LABEL = {
|
|
20925
|
-
neutral: 'Neutral',
|
|
20926
|
-
accent: 'Info',
|
|
20927
|
-
success: 'OK',
|
|
20928
|
-
warning: 'Warning',
|
|
20929
|
-
critical: 'Critical',
|
|
20930
|
-
};
|
|
20931
|
-
/**
|
|
20932
|
-
* Presence/state dot that never relies on colour alone: the tone is painted by
|
|
20933
|
-
* a pure-CSS dot while the state is also rendered as visually-hidden text, so
|
|
20934
|
-
* screen readers (and colour-blind users copying text) get "Warning", not just
|
|
20935
|
-
* a yellow circle. Use it inside avatars, menu rows, metric tiles — anywhere a
|
|
20936
|
-
* bare coloured dot would otherwise carry meaning.
|
|
20937
|
-
*
|
|
20938
|
-
* <strct-status-dot status="success" />
|
|
20939
|
-
* <strct-status-dot status="critical" size="sm" label="Node unreachable" />
|
|
20940
|
-
*/
|
|
20941
|
-
class StrctStatusDot {
|
|
20942
|
-
/** Status tone (canonical vocabulary: OK = 'success', info = 'accent'). */
|
|
20943
|
-
status = input('neutral', /* @ts-ignore */
|
|
20944
|
-
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
20945
|
-
/**
|
|
20946
|
-
* Accessible text describing the state (localizable). Empty falls back to a
|
|
20947
|
-
* per-status default ("OK", "Warning", …) so the dot is never color-only.
|
|
20948
|
-
*/
|
|
20949
|
-
label = input('', /* @ts-ignore */
|
|
20950
|
-
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
20951
|
-
/** Dot size: `sm` for dense rows, `md` standalone. */
|
|
20952
|
-
size = input('md', /* @ts-ignore */
|
|
20953
|
-
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
20954
|
-
text = computed(() => this.label() || DEFAULT_LABEL[this.status()], /* @ts-ignore */
|
|
20955
|
-
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
20956
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctStatusDot, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
20957
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctStatusDot, isStandalone: true, selector: "strct-status-dot", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-dot--accent": "status() === 'accent'", "class.strct-dot--success": "status() === 'success'", "class.strct-dot--warning": "status() === 'warning'", "class.strct-dot--critical": "status() === 'critical'", "class.strct-dot--sm": "size() === 'sm'" }, classAttribute: "strct-dot" }, ngImport: i0, template: `
|
|
20958
|
-
<span class="strct-dot__dot" aria-hidden="true"></span>
|
|
20959
|
-
<span class="strct-dot__sr">{{ text() }}</span>
|
|
20960
|
-
`, isInline: true, styles: [".strct-dot{display:inline-flex;align-items:center;flex:none;line-height:0}.strct-dot__dot{width:10px;height:10px;border-radius:50%;background:var(--t3)}.strct-dot--sm .strct-dot__dot{width:8px;height:8px}.strct-dot--accent .strct-dot__dot{background:var(--acc)}.strct-dot--success .strct-dot__dot{background:var(--success)}.strct-dot--warning .strct-dot__dot{background:var(--warning)}.strct-dot--critical .strct-dot__dot{background:var(--critical)}.strct-dot__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
21063
|
+
`, isInline: true, styles: [".strct-hkh__backdrop{position:fixed;inset:0;z-index:var(--z-tour);background:var(--backdrop)}.strct-hkh{position:fixed;z-index:calc(var(--z-tour) + 1);top:50%;left:50%;transform:translate(-50%,-50%);width:min(420px,calc(100vw - 32px));max-height:min(70vh,520px);overflow:auto;padding:16px 18px;background:var(--bg-1);border:1px solid var(--b2);border-radius:10px;box-shadow:var(--shh)}.strct-hkh__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.strct-hkh__title{font-size:13.5px;font-weight:650;color:var(--t1)}.strct-hkh__headside{display:inline-flex;align-items:center;gap:8px}.strct-hkh__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-hkh__close:hover{color:var(--t1);background:var(--bg-3)}.strct-hkh__group{margin-bottom:12px}.strct-hkh__groupname{font-size:10.5px;font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--t3);margin-bottom:5px}.strct-hkh__row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:4px 0}.strct-hkh__desc{font-size:12.5px;color:var(--t1)}.strct-hkh__keys{display:inline-flex;gap:4px}.strct-hkh__empty{font-size:12.5px;color:var(--t3)}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "component", type: StrctKbd, selector: "strct-kbd" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
20961
21064
|
}
|
|
20962
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type:
|
|
21065
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctHotkeysHelp, decorators: [{
|
|
20963
21066
|
type: Component,
|
|
20964
|
-
args: [{ selector: 'strct-
|
|
20965
|
-
|
|
20966
|
-
|
|
20967
|
-
|
|
20968
|
-
|
|
20969
|
-
|
|
20970
|
-
|
|
20971
|
-
|
|
20972
|
-
|
|
20973
|
-
|
|
20974
|
-
|
|
20975
|
-
|
|
21067
|
+
args: [{ selector: 'strct-hotkeys-help', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon, StrctKbd], template: `
|
|
21068
|
+
@if (open()) {
|
|
21069
|
+
<div class="strct-hkh__backdrop" (click)="open.set(false)"></div>
|
|
21070
|
+
<div class="strct-hkh" role="dialog" aria-modal="true" [attr.aria-label]="title()">
|
|
21071
|
+
<div class="strct-hkh__head">
|
|
21072
|
+
<span class="strct-hkh__title">{{ title() }}</span>
|
|
21073
|
+
<span class="strct-hkh__headside">
|
|
21074
|
+
<strct-kbd>?</strct-kbd>
|
|
21075
|
+
<button
|
|
21076
|
+
type="button"
|
|
21077
|
+
class="strct-hkh__close"
|
|
21078
|
+
[attr.aria-label]="closeLabel()"
|
|
21079
|
+
(click)="open.set(false)"
|
|
21080
|
+
>
|
|
21081
|
+
<strct-icon name="close" [size]="14" />
|
|
21082
|
+
</button>
|
|
21083
|
+
</span>
|
|
21084
|
+
</div>
|
|
21085
|
+
@for (group of grouped(); track group.name) {
|
|
21086
|
+
<div class="strct-hkh__group">
|
|
21087
|
+
<div class="strct-hkh__groupname">{{ group.name }}</div>
|
|
21088
|
+
@for (hk of group.keys; track hk.combo + hk.description) {
|
|
21089
|
+
<div class="strct-hkh__row">
|
|
21090
|
+
<span class="strct-hkh__desc">{{ hk.description }}</span>
|
|
21091
|
+
<span class="strct-hkh__keys">
|
|
21092
|
+
@for (part of parts(hk.combo); track $index) {
|
|
21093
|
+
<strct-kbd>{{ part }}</strct-kbd>
|
|
21094
|
+
}
|
|
21095
|
+
</span>
|
|
21096
|
+
</div>
|
|
21097
|
+
}
|
|
21098
|
+
</div>
|
|
21099
|
+
} @empty {
|
|
21100
|
+
<p class="strct-hkh__empty">{{ emptyText() }}</p>
|
|
21101
|
+
}
|
|
21102
|
+
</div>
|
|
21103
|
+
}
|
|
21104
|
+
`, styles: [".strct-hkh__backdrop{position:fixed;inset:0;z-index:var(--z-tour);background:var(--backdrop)}.strct-hkh{position:fixed;z-index:calc(var(--z-tour) + 1);top:50%;left:50%;transform:translate(-50%,-50%);width:min(420px,calc(100vw - 32px));max-height:min(70vh,520px);overflow:auto;padding:16px 18px;background:var(--bg-1);border:1px solid var(--b2);border-radius:10px;box-shadow:var(--shh)}.strct-hkh__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.strct-hkh__title{font-size:13.5px;font-weight:650;color:var(--t1)}.strct-hkh__headside{display:inline-flex;align-items:center;gap:8px}.strct-hkh__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-hkh__close:hover{color:var(--t1);background:var(--bg-3)}.strct-hkh__group{margin-bottom:12px}.strct-hkh__groupname{font-size:10.5px;font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--t3);margin-bottom:5px}.strct-hkh__row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:4px 0}.strct-hkh__desc{font-size:12.5px;color:var(--t1)}.strct-hkh__keys{display:inline-flex;gap:4px}.strct-hkh__empty{font-size:12.5px;color:var(--t3)}\n"] }]
|
|
21105
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], onEscape: [{
|
|
21106
|
+
type: HostListener,
|
|
21107
|
+
args: ['document:keydown.escape', ['$event']]
|
|
21108
|
+
}] } });
|
|
20976
21109
|
|
|
21110
|
+
let reorderCounter = 0;
|
|
20977
21111
|
/**
|
|
20978
|
-
*
|
|
20979
|
-
* `number | null` (null = empty); typing is free-form (intermediate states
|
|
20980
|
-
* like "-" are kept), committed values clamp to `min`/`max` on blur, and the
|
|
20981
|
-
* buttons + keyboard always clamp immediately.
|
|
21112
|
+
* List drag-reorder primitive — the consumer owns the array:
|
|
20982
21113
|
*
|
|
20983
|
-
* <
|
|
21114
|
+
* <ul strctReorder (reordered)="move($event)">
|
|
21115
|
+
* @for (s of steps(); track s.id) {
|
|
21116
|
+
* <li strctReorderItem>{{ s.label }}</li>
|
|
21117
|
+
* }
|
|
21118
|
+
* </ul>
|
|
20984
21119
|
*
|
|
20985
|
-
*
|
|
20986
|
-
*
|
|
20987
|
-
*
|
|
21120
|
+
* move({ from, to }: StrctReorderEvent) {
|
|
21121
|
+
* this.steps.update((s) => { const c = [...s]; c.splice(to, 0, ...c.splice(from, 1)); return c; });
|
|
21122
|
+
* }
|
|
21123
|
+
*
|
|
21124
|
+
* Items are HTML5-draggable; keyboard reorder is Alt+ArrowUp / Alt+ArrowDown
|
|
21125
|
+
* on the focused item (items get `tabindex="0"` unless they already manage
|
|
21126
|
+
* focus). Indexes are positions among the `strctReorderItem` siblings.
|
|
21127
|
+
* Completed moves are announced in a live region (see `announcement`) and
|
|
21128
|
+
* items reference the sr-only `instructions` via `aria-describedby`.
|
|
20988
21129
|
*/
|
|
20989
|
-
class
|
|
20990
|
-
|
|
20991
|
-
|
|
20992
|
-
|
|
20993
|
-
|
|
20994
|
-
|
|
20995
|
-
|
|
20996
|
-
/**
|
|
20997
|
-
|
|
20998
|
-
|
|
20999
|
-
|
|
21000
|
-
|
|
21001
|
-
|
|
21002
|
-
|
|
21003
|
-
|
|
21004
|
-
|
|
21005
|
-
|
|
21006
|
-
|
|
21007
|
-
|
|
21008
|
-
|
|
21009
|
-
|
|
21010
|
-
|
|
21011
|
-
...(ngDevMode ? [{ debugName: "
|
|
21012
|
-
|
|
21013
|
-
|
|
21014
|
-
|
|
21015
|
-
|
|
21016
|
-
|
|
21017
|
-
|
|
21018
|
-
|
|
21019
|
-
|
|
21020
|
-
|
|
21021
|
-
|
|
21022
|
-
|
|
21023
|
-
|
|
21024
|
-
|
|
21025
|
-
|
|
21026
|
-
|
|
21027
|
-
|
|
21028
|
-
|
|
21029
|
-
|
|
21030
|
-
|
|
21031
|
-
|
|
21032
|
-
|
|
21033
|
-
|
|
21034
|
-
|
|
21035
|
-
|
|
21036
|
-
|
|
21037
|
-
|
|
21038
|
-
|
|
21039
|
-
|
|
21040
|
-
|
|
21041
|
-
|
|
21042
|
-
this.onChange(null);
|
|
21130
|
+
class StrctReorder {
|
|
21131
|
+
host = inject(ElementRef);
|
|
21132
|
+
renderer = inject(Renderer2);
|
|
21133
|
+
doc = inject(DOCUMENT);
|
|
21134
|
+
announcer = inject(StrctAnnouncer);
|
|
21135
|
+
/** Emits when a drag or keyboard move completes. */
|
|
21136
|
+
reordered = output();
|
|
21137
|
+
/** Disable all reordering (display-only mode). */
|
|
21138
|
+
reorderDisabled = input(false, { ...(ngDevMode ? { debugName: "reorderDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
21139
|
+
/**
|
|
21140
|
+
* Keyboard instructions (localizable), rendered sr-only and referenced by
|
|
21141
|
+
* every item's `aria-describedby`. Set to '' to opt out.
|
|
21142
|
+
*/
|
|
21143
|
+
instructions = input('Press Alt+ArrowUp or Alt+ArrowDown to move this item', /* @ts-ignore */
|
|
21144
|
+
...(ngDevMode ? [{ debugName: "instructions" }] : /* istanbul ignore next */ []));
|
|
21145
|
+
/**
|
|
21146
|
+
* Builds the live-region announcement after a move (localizable):
|
|
21147
|
+
* (moved item's text, new 1-based position, item count).
|
|
21148
|
+
*/
|
|
21149
|
+
announcement = input((label, position, total) => `Moved ${label} to position ${position} of ${total}`, /* @ts-ignore */
|
|
21150
|
+
...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
21151
|
+
dragIndex = signal(null, /* @ts-ignore */
|
|
21152
|
+
...(ngDevMode ? [{ debugName: "dragIndex" }] : /* istanbul ignore next */ []));
|
|
21153
|
+
overIndex = signal(null, /* @ts-ignore */
|
|
21154
|
+
...(ngDevMode ? [{ debugName: "overIndex" }] : /* istanbul ignore next */ []));
|
|
21155
|
+
/** Id of the sr-only instructions element the items point to. */
|
|
21156
|
+
instructionsId = `strct-reorder-hint-${++reorderCounter}`;
|
|
21157
|
+
hint = null;
|
|
21158
|
+
constructor() {
|
|
21159
|
+
// Keep the sr-only instructions element in sync with the input.
|
|
21160
|
+
effect(() => {
|
|
21161
|
+
const text = this.instructions();
|
|
21162
|
+
if (text && !this.hint) {
|
|
21163
|
+
this.hint = this.renderer.createElement('span');
|
|
21164
|
+
this.renderer.setAttribute(this.hint, 'id', this.instructionsId);
|
|
21165
|
+
this.hint.style.cssText =
|
|
21166
|
+
'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;';
|
|
21167
|
+
this.renderer.appendChild(this.doc.body, this.hint);
|
|
21168
|
+
}
|
|
21169
|
+
if (this.hint)
|
|
21170
|
+
this.hint.textContent = text;
|
|
21171
|
+
});
|
|
21172
|
+
}
|
|
21173
|
+
items() {
|
|
21174
|
+
return [...this.host.nativeElement.querySelectorAll('[strctReorderItem]')];
|
|
21175
|
+
}
|
|
21176
|
+
indexOf(el) {
|
|
21177
|
+
return this.items().indexOf(el);
|
|
21178
|
+
}
|
|
21179
|
+
commit(from, to) {
|
|
21180
|
+
this.dragIndex.set(null);
|
|
21181
|
+
this.overIndex.set(null);
|
|
21182
|
+
if (from < 0 || to < 0 || from === to)
|
|
21043
21183
|
return;
|
|
21044
|
-
|
|
21045
|
-
|
|
21046
|
-
|
|
21047
|
-
// hold the last value; clamping happens on blur.
|
|
21048
|
-
if (Number.isFinite(parsed)) {
|
|
21049
|
-
this.value.set(parsed);
|
|
21050
|
-
this.onChange(parsed);
|
|
21051
|
-
}
|
|
21184
|
+
const label = this.items()[from]?.textContent?.trim() ?? '';
|
|
21185
|
+
this.reordered.emit({ from, to });
|
|
21186
|
+
this.announcer.announce(this.announcement()(label, to + 1, this.items().length));
|
|
21052
21187
|
}
|
|
21053
|
-
|
|
21054
|
-
|
|
21188
|
+
onContainerDragOver(event) {
|
|
21189
|
+
// Only the container's own empty space (below the last item); drags over
|
|
21190
|
+
// items are handled by the items themselves.
|
|
21191
|
+
if (this.dragIndex() == null)
|
|
21055
21192
|
return;
|
|
21056
|
-
|
|
21057
|
-
|
|
21058
|
-
|
|
21059
|
-
this.stepBy(1);
|
|
21060
|
-
break;
|
|
21061
|
-
case 'ArrowDown':
|
|
21062
|
-
event.preventDefault();
|
|
21063
|
-
this.stepBy(-1);
|
|
21064
|
-
break;
|
|
21065
|
-
case 'PageUp':
|
|
21066
|
-
event.preventDefault();
|
|
21067
|
-
this.stepBy(1, 10);
|
|
21068
|
-
break;
|
|
21069
|
-
case 'PageDown':
|
|
21070
|
-
event.preventDefault();
|
|
21071
|
-
this.stepBy(-1, 10);
|
|
21072
|
-
break;
|
|
21073
|
-
case 'Home': {
|
|
21074
|
-
const m = this.min();
|
|
21075
|
-
if (m === null)
|
|
21076
|
-
return;
|
|
21077
|
-
event.preventDefault();
|
|
21078
|
-
this.commitTo(m);
|
|
21079
|
-
break;
|
|
21080
|
-
}
|
|
21081
|
-
case 'End': {
|
|
21082
|
-
const m = this.max();
|
|
21083
|
-
if (m === null)
|
|
21084
|
-
return;
|
|
21085
|
-
event.preventDefault();
|
|
21086
|
-
this.commitTo(m);
|
|
21087
|
-
break;
|
|
21088
|
-
}
|
|
21089
|
-
}
|
|
21193
|
+
if (event.target.closest('[strctReorderItem]'))
|
|
21194
|
+
return;
|
|
21195
|
+
event.preventDefault();
|
|
21090
21196
|
}
|
|
21091
|
-
|
|
21092
|
-
|
|
21093
|
-
|
|
21094
|
-
|
|
21095
|
-
|
|
21096
|
-
|
|
21097
|
-
|
|
21098
|
-
|
|
21099
|
-
|
|
21100
|
-
|
|
21101
|
-
|
|
21102
|
-
|
|
21103
|
-
|
|
21104
|
-
|
|
21105
|
-
|
|
21106
|
-
|
|
21107
|
-
|
|
21108
|
-
|
|
21109
|
-
|
|
21110
|
-
|
|
21111
|
-
|
|
21112
|
-
|
|
21113
|
-
}
|
|
21197
|
+
onContainerDrop(event) {
|
|
21198
|
+
const from = this.dragIndex();
|
|
21199
|
+
if (from == null)
|
|
21200
|
+
return; // an item already consumed this drop
|
|
21201
|
+
if (event.target.closest('[strctReorderItem]'))
|
|
21202
|
+
return;
|
|
21203
|
+
event.preventDefault();
|
|
21204
|
+
// Drop into empty space moves the item to the end.
|
|
21205
|
+
this.commit(from, this.items().length - 1);
|
|
21206
|
+
}
|
|
21207
|
+
onDragLeave(event) {
|
|
21208
|
+
// dragleave also fires when moving between children; only clear the
|
|
21209
|
+
// highlight when the drag truly leaves the container.
|
|
21210
|
+
const next = event.relatedTarget;
|
|
21211
|
+
if (next && this.host.nativeElement.contains(next))
|
|
21212
|
+
return;
|
|
21213
|
+
this.overIndex.set(null);
|
|
21214
|
+
}
|
|
21215
|
+
ngOnDestroy() {
|
|
21216
|
+
if (this.hint) {
|
|
21217
|
+
this.renderer.removeChild(this.doc.body, this.hint);
|
|
21218
|
+
this.hint = null;
|
|
21114
21219
|
}
|
|
21115
|
-
this.onTouched();
|
|
21116
21220
|
}
|
|
21117
|
-
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
|
|
21123
|
-
|
|
21124
|
-
|
|
21125
|
-
|
|
21126
|
-
|
|
21221
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctReorder, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
21222
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: StrctReorder, isStandalone: true, selector: "[strctReorder]", inputs: { reorderDisabled: { classPropertyName: "reorderDisabled", publicName: "reorderDisabled", isSignal: true, isRequired: false, transformFunction: null }, instructions: { classPropertyName: "instructions", publicName: "instructions", isSignal: true, isRequired: false, transformFunction: null }, announcement: { classPropertyName: "announcement", publicName: "announcement", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { reordered: "reordered" }, host: { listeners: { "dragover": "onContainerDragOver($event)", "drop": "onContainerDrop($event)", "dragleave": "onDragLeave($event)" } }, ngImport: i0 });
|
|
21223
|
+
}
|
|
21224
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctReorder, decorators: [{
|
|
21225
|
+
type: Directive,
|
|
21226
|
+
args: [{ selector: '[strctReorder]' }]
|
|
21227
|
+
}], ctorParameters: () => [], propDecorators: { reordered: [{ type: i0.Output, args: ["reordered"] }], reorderDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderDisabled", required: false }] }], instructions: [{ type: i0.Input, args: [{ isSignal: true, alias: "instructions", required: false }] }], announcement: [{ type: i0.Input, args: [{ isSignal: true, alias: "announcement", required: false }] }], onContainerDragOver: [{
|
|
21228
|
+
type: HostListener,
|
|
21229
|
+
args: ['dragover', ['$event']]
|
|
21230
|
+
}], onContainerDrop: [{
|
|
21231
|
+
type: HostListener,
|
|
21232
|
+
args: ['drop', ['$event']]
|
|
21233
|
+
}], onDragLeave: [{
|
|
21234
|
+
type: HostListener,
|
|
21235
|
+
args: ['dragleave', ['$event']]
|
|
21236
|
+
}] } });
|
|
21237
|
+
/** One draggable row inside a `[strctReorder]` container. */
|
|
21238
|
+
class StrctReorderItem {
|
|
21239
|
+
list = inject(StrctReorder);
|
|
21240
|
+
el = inject(ElementRef);
|
|
21241
|
+
// Captured before our own binding ever writes tabindex, so it never flips.
|
|
21242
|
+
hadOwnTabindex = this.el.nativeElement.hasAttribute('tabindex');
|
|
21243
|
+
hostTabindex() {
|
|
21244
|
+
return this.hadOwnTabindex ? null : 0;
|
|
21245
|
+
}
|
|
21246
|
+
isDragging() {
|
|
21247
|
+
return this.list.dragIndex() === this.index();
|
|
21127
21248
|
}
|
|
21128
|
-
|
|
21129
|
-
|
|
21130
|
-
this.value.set(clamped);
|
|
21131
|
-
this.text.set(String(clamped));
|
|
21132
|
-
this.onChange(clamped);
|
|
21133
|
-
this.onTouched();
|
|
21249
|
+
isOver() {
|
|
21250
|
+
return this.list.overIndex() === this.index() && this.list.dragIndex() !== this.index();
|
|
21134
21251
|
}
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
const hi = this.max();
|
|
21138
|
-
if (lo !== null && n < lo)
|
|
21139
|
-
return lo;
|
|
21140
|
-
if (hi !== null && n > hi)
|
|
21141
|
-
return hi;
|
|
21142
|
-
return n;
|
|
21252
|
+
index() {
|
|
21253
|
+
return this.list.indexOf(this.el.nativeElement);
|
|
21143
21254
|
}
|
|
21144
|
-
|
|
21145
|
-
|
|
21146
|
-
|
|
21147
|
-
|
|
21148
|
-
|
|
21149
|
-
|
|
21150
|
-
|
|
21255
|
+
onDragStart(event) {
|
|
21256
|
+
if (this.list.reorderDisabled())
|
|
21257
|
+
return;
|
|
21258
|
+
this.list.dragIndex.set(this.index());
|
|
21259
|
+
event.dataTransfer?.setData('text/plain', String(this.index()));
|
|
21260
|
+
if (event.dataTransfer)
|
|
21261
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
21151
21262
|
}
|
|
21152
|
-
|
|
21153
|
-
|
|
21154
|
-
|
|
21155
|
-
|
|
21156
|
-
this.
|
|
21263
|
+
onDragOver(event) {
|
|
21264
|
+
if (this.list.dragIndex() == null)
|
|
21265
|
+
return;
|
|
21266
|
+
event.preventDefault();
|
|
21267
|
+
this.list.overIndex.set(this.index());
|
|
21157
21268
|
}
|
|
21158
|
-
|
|
21159
|
-
|
|
21269
|
+
onDrop(event) {
|
|
21270
|
+
event.preventDefault();
|
|
21271
|
+
const from = this.list.dragIndex();
|
|
21272
|
+
if (from == null)
|
|
21273
|
+
return;
|
|
21274
|
+
this.list.commit(from, this.index());
|
|
21160
21275
|
}
|
|
21161
|
-
|
|
21162
|
-
this.
|
|
21276
|
+
onDragEnd() {
|
|
21277
|
+
this.list.dragIndex.set(null);
|
|
21278
|
+
this.list.overIndex.set(null);
|
|
21163
21279
|
}
|
|
21164
|
-
|
|
21165
|
-
this.
|
|
21280
|
+
onKeydown(event) {
|
|
21281
|
+
if (this.list.reorderDisabled() || !event.altKey)
|
|
21282
|
+
return;
|
|
21283
|
+
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')
|
|
21284
|
+
return;
|
|
21285
|
+
event.preventDefault();
|
|
21286
|
+
const from = this.index();
|
|
21287
|
+
const to = event.key === 'ArrowUp' ? from - 1 : from + 1;
|
|
21288
|
+
if (to < 0 || to >= this.list.items().length)
|
|
21289
|
+
return;
|
|
21290
|
+
this.list.commit(from, to);
|
|
21291
|
+
// Keep focus on the moved item after the consumer re-renders.
|
|
21292
|
+
setTimeout(() => this.list.items()[to]?.focus());
|
|
21166
21293
|
}
|
|
21167
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type:
|
|
21168
|
-
static
|
|
21169
|
-
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StrctNumber), multi: true },
|
|
21170
|
-
], ngImport: i0, template: `
|
|
21171
|
-
<div class="strct-num__wrap">
|
|
21172
|
-
<button
|
|
21173
|
-
type="button"
|
|
21174
|
-
class="strct-num__btn"
|
|
21175
|
-
[attr.aria-label]="decrementLabel()"
|
|
21176
|
-
[disabled]="isDisabled() || atMin()"
|
|
21177
|
-
(click)="stepBy(-1)"
|
|
21178
|
-
>
|
|
21179
|
-
<strct-icon strictName="minus" [size]="12" [strokeWidth]="1.8" />
|
|
21180
|
-
</button>
|
|
21181
|
-
<input
|
|
21182
|
-
type="text"
|
|
21183
|
-
inputmode="decimal"
|
|
21184
|
-
class="strct-control strct-num__input"
|
|
21185
|
-
role="spinbutton"
|
|
21186
|
-
[attr.aria-valuemin]="min()"
|
|
21187
|
-
[attr.aria-valuemax]="max()"
|
|
21188
|
-
[attr.aria-valuenow]="value()"
|
|
21189
|
-
[placeholder]="placeholder()"
|
|
21190
|
-
[value]="text()"
|
|
21191
|
-
[disabled]="isDisabled()"
|
|
21192
|
-
(input)="onInput($event)"
|
|
21193
|
-
(keydown)="onKeydown($event)"
|
|
21194
|
-
(blur)="onBlur()"
|
|
21195
|
-
/>
|
|
21196
|
-
<button
|
|
21197
|
-
type="button"
|
|
21198
|
-
class="strct-num__btn"
|
|
21199
|
-
[attr.aria-label]="incrementLabel()"
|
|
21200
|
-
[disabled]="isDisabled() || atMax()"
|
|
21201
|
-
(click)="stepBy(1)"
|
|
21202
|
-
>
|
|
21203
|
-
<strct-icon strictName="plus" [size]="12" [strokeWidth]="1.8" />
|
|
21204
|
-
</button>
|
|
21205
|
-
</div>
|
|
21206
|
-
`, isInline: true, styles: [".strct-num{display:block;width:100%}.strct-num__wrap{display:flex;align-items:stretch;gap:var(--space-1)}.strct-num__input{flex:1;min-width:0;text-align:center;font-variant-numeric:tabular-nums}.strct-num__btn{display:inline-flex;align-items:center;justify-content:center;flex:none;padding-inline:var(--space-2);color:var(--t2);background:var(--bg-2);border:1px solid var(--b2);border-radius:var(--radius-md);cursor:pointer;transition:border-color .14s ease,color .14s ease,background .14s ease}.strct-num__btn:hover:not(:disabled){color:var(--t1);border-color:var(--b3);background:var(--bg-3)}.strct-num__btn:disabled{opacity:var(--disabled-opacity);cursor:not-allowed}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
21294
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctReorderItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
21295
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctReorderItem, isStandalone: true, selector: "[strctReorderItem]", host: { listeners: { "dragstart": "onDragStart($event)", "dragover": "onDragOver($event)", "drop": "onDrop($event)", "dragend": "onDragEnd()", "keydown": "onKeydown($event)" }, properties: { "attr.draggable": "!list.reorderDisabled()", "attr.tabindex": "hostTabindex()", "class.strct-reorder--dragging": "isDragging()", "class.strct-reorder--over": "isOver()", "attr.aria-roledescription": "'sortable'", "attr.aria-keyshortcuts": "'Alt+ArrowUp Alt+ArrowDown'", "attr.aria-posinset": "index() + 1", "attr.aria-setsize": "list.items().length", "attr.aria-describedby": "list.instructions() ? list.instructionsId : null" } }, ngImport: i0 });
|
|
21207
21296
|
}
|
|
21208
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type:
|
|
21297
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctReorderItem, decorators: [{
|
|
21298
|
+
type: Directive,
|
|
21299
|
+
args: [{
|
|
21300
|
+
selector: '[strctReorderItem]',
|
|
21301
|
+
host: {
|
|
21302
|
+
'[attr.draggable]': '!list.reorderDisabled()',
|
|
21303
|
+
'[attr.tabindex]': 'hostTabindex()',
|
|
21304
|
+
'[class.strct-reorder--dragging]': 'isDragging()',
|
|
21305
|
+
'[class.strct-reorder--over]': 'isOver()',
|
|
21306
|
+
'[attr.aria-roledescription]': "'sortable'",
|
|
21307
|
+
'[attr.aria-keyshortcuts]': "'Alt+ArrowUp Alt+ArrowDown'",
|
|
21308
|
+
'[attr.aria-posinset]': 'index() + 1',
|
|
21309
|
+
'[attr.aria-setsize]': 'list.items().length',
|
|
21310
|
+
'[attr.aria-describedby]': 'list.instructions() ? list.instructionsId : null',
|
|
21311
|
+
},
|
|
21312
|
+
}]
|
|
21313
|
+
}], propDecorators: { onDragStart: [{
|
|
21314
|
+
type: HostListener,
|
|
21315
|
+
args: ['dragstart', ['$event']]
|
|
21316
|
+
}], onDragOver: [{
|
|
21317
|
+
type: HostListener,
|
|
21318
|
+
args: ['dragover', ['$event']]
|
|
21319
|
+
}], onDrop: [{
|
|
21320
|
+
type: HostListener,
|
|
21321
|
+
args: ['drop', ['$event']]
|
|
21322
|
+
}], onDragEnd: [{
|
|
21323
|
+
type: HostListener,
|
|
21324
|
+
args: ['dragend']
|
|
21325
|
+
}], onKeydown: [{
|
|
21326
|
+
type: HostListener,
|
|
21327
|
+
args: ['keydown', ['$event']]
|
|
21328
|
+
}] } });
|
|
21329
|
+
|
|
21330
|
+
/**
|
|
21331
|
+
* Default accessible text per status, used when `label` is not given
|
|
21332
|
+
* (localizable — override via `label`). The canonical `StrctStatus` vocabulary
|
|
21333
|
+
* has no 'ok'/'info': OK maps to 'success', info to 'accent'.
|
|
21334
|
+
*/
|
|
21335
|
+
const DEFAULT_LABEL = {
|
|
21336
|
+
neutral: 'Neutral',
|
|
21337
|
+
accent: 'Info',
|
|
21338
|
+
success: 'OK',
|
|
21339
|
+
warning: 'Warning',
|
|
21340
|
+
critical: 'Critical',
|
|
21341
|
+
};
|
|
21342
|
+
/**
|
|
21343
|
+
* Presence/state dot that never relies on colour alone: the tone is painted by
|
|
21344
|
+
* a pure-CSS dot while the state is also rendered as visually-hidden text, so
|
|
21345
|
+
* screen readers (and colour-blind users copying text) get "Warning", not just
|
|
21346
|
+
* a yellow circle. Use it inside avatars, menu rows, metric tiles — anywhere a
|
|
21347
|
+
* bare coloured dot would otherwise carry meaning.
|
|
21348
|
+
*
|
|
21349
|
+
* <strct-status-dot status="success" />
|
|
21350
|
+
* <strct-status-dot status="critical" size="sm" label="Node unreachable" />
|
|
21351
|
+
*/
|
|
21352
|
+
class StrctStatusDot {
|
|
21353
|
+
/** Status tone (canonical vocabulary: OK = 'success', info = 'accent'). */
|
|
21354
|
+
status = input('neutral', /* @ts-ignore */
|
|
21355
|
+
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
21356
|
+
/**
|
|
21357
|
+
* Accessible text describing the state (localizable). Empty falls back to a
|
|
21358
|
+
* per-status default ("OK", "Warning", …) so the dot is never color-only.
|
|
21359
|
+
*/
|
|
21360
|
+
label = input('', /* @ts-ignore */
|
|
21361
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
21362
|
+
/** Dot size: `sm` for dense rows, `md` standalone. */
|
|
21363
|
+
size = input('md', /* @ts-ignore */
|
|
21364
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
21365
|
+
text = computed(() => this.label() || DEFAULT_LABEL[this.status()], /* @ts-ignore */
|
|
21366
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
21367
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctStatusDot, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
21368
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctStatusDot, isStandalone: true, selector: "strct-status-dot", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-dot--accent": "status() === 'accent'", "class.strct-dot--success": "status() === 'success'", "class.strct-dot--warning": "status() === 'warning'", "class.strct-dot--critical": "status() === 'critical'", "class.strct-dot--sm": "size() === 'sm'" }, classAttribute: "strct-dot" }, ngImport: i0, template: `
|
|
21369
|
+
<span class="strct-dot__dot" aria-hidden="true"></span>
|
|
21370
|
+
<span class="strct-dot__sr">{{ text() }}</span>
|
|
21371
|
+
`, isInline: true, styles: [".strct-dot{display:inline-flex;align-items:center;flex:none;line-height:0}.strct-dot__dot{width:10px;height:10px;border-radius:50%;background:var(--t3)}.strct-dot--sm .strct-dot__dot{width:8px;height:8px}.strct-dot--accent .strct-dot__dot{background:var(--acc)}.strct-dot--success .strct-dot__dot{background:var(--success)}.strct-dot--warning .strct-dot__dot{background:var(--warning)}.strct-dot--critical .strct-dot__dot{background:var(--critical)}.strct-dot__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
21372
|
+
}
|
|
21373
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctStatusDot, decorators: [{
|
|
21209
21374
|
type: Component,
|
|
21210
|
-
args: [{ selector: 'strct-
|
|
21211
|
-
|
|
21212
|
-
|
|
21213
|
-
|
|
21214
|
-
|
|
21215
|
-
|
|
21216
|
-
|
|
21217
|
-
|
|
21218
|
-
|
|
21219
|
-
|
|
21220
|
-
|
|
21221
|
-
|
|
21222
|
-
</button>
|
|
21223
|
-
<input
|
|
21224
|
-
type="text"
|
|
21225
|
-
inputmode="decimal"
|
|
21226
|
-
class="strct-control strct-num__input"
|
|
21227
|
-
role="spinbutton"
|
|
21228
|
-
[attr.aria-valuemin]="min()"
|
|
21229
|
-
[attr.aria-valuemax]="max()"
|
|
21230
|
-
[attr.aria-valuenow]="value()"
|
|
21231
|
-
[placeholder]="placeholder()"
|
|
21232
|
-
[value]="text()"
|
|
21233
|
-
[disabled]="isDisabled()"
|
|
21234
|
-
(input)="onInput($event)"
|
|
21235
|
-
(keydown)="onKeydown($event)"
|
|
21236
|
-
(blur)="onBlur()"
|
|
21237
|
-
/>
|
|
21238
|
-
<button
|
|
21239
|
-
type="button"
|
|
21240
|
-
class="strct-num__btn"
|
|
21241
|
-
[attr.aria-label]="incrementLabel()"
|
|
21242
|
-
[disabled]="isDisabled() || atMax()"
|
|
21243
|
-
(click)="stepBy(1)"
|
|
21244
|
-
>
|
|
21245
|
-
<strct-icon strictName="plus" [size]="12" [strokeWidth]="1.8" />
|
|
21246
|
-
</button>
|
|
21247
|
-
</div>
|
|
21248
|
-
`, host: { class: 'strct-num' }, styles: [".strct-num{display:block;width:100%}.strct-num__wrap{display:flex;align-items:stretch;gap:var(--space-1)}.strct-num__input{flex:1;min-width:0;text-align:center;font-variant-numeric:tabular-nums}.strct-num__btn{display:inline-flex;align-items:center;justify-content:center;flex:none;padding-inline:var(--space-2);color:var(--t2);background:var(--bg-2);border:1px solid var(--b2);border-radius:var(--radius-md);cursor:pointer;transition:border-color .14s ease,color .14s ease,background .14s ease}.strct-num__btn:hover:not(:disabled){color:var(--t1);border-color:var(--b3);background:var(--bg-3)}.strct-num__btn:disabled{opacity:var(--disabled-opacity);cursor:not-allowed}\n"] }]
|
|
21249
|
-
}], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], incrementLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "incrementLabel", required: false }] }], decrementLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "decrementLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
21375
|
+
args: [{ selector: 'strct-status-dot', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
21376
|
+
<span class="strct-dot__dot" aria-hidden="true"></span>
|
|
21377
|
+
<span class="strct-dot__sr">{{ text() }}</span>
|
|
21378
|
+
`, host: {
|
|
21379
|
+
class: 'strct-dot',
|
|
21380
|
+
'[class.strct-dot--accent]': "status() === 'accent'",
|
|
21381
|
+
'[class.strct-dot--success]': "status() === 'success'",
|
|
21382
|
+
'[class.strct-dot--warning]': "status() === 'warning'",
|
|
21383
|
+
'[class.strct-dot--critical]': "status() === 'critical'",
|
|
21384
|
+
'[class.strct-dot--sm]': "size() === 'sm'",
|
|
21385
|
+
}, styles: [".strct-dot{display:inline-flex;align-items:center;flex:none;line-height:0}.strct-dot__dot{width:10px;height:10px;border-radius:50%;background:var(--t3)}.strct-dot--sm .strct-dot__dot{width:8px;height:8px}.strct-dot--accent .strct-dot__dot{background:var(--acc)}.strct-dot--success .strct-dot__dot{background:var(--success)}.strct-dot--warning .strct-dot__dot{background:var(--warning)}.strct-dot--critical .strct-dot__dot{background:var(--critical)}.strct-dot__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"] }]
|
|
21386
|
+
}], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
|
|
21250
21387
|
|
|
21251
21388
|
/**
|
|
21252
21389
|
* A generic anchored overlay panel for arbitrary projected content — the
|