@flyos/design-system 3.1.0 → 3.2.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.
|
@@ -47,7 +47,7 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
|
|
47
47
|
// tools/publish-library.ps1 at bump time, and asserted by the spec beside this file.
|
|
48
48
|
// Used only for the diagnostic message; the duplicate-instance detection itself is
|
|
49
49
|
// version-agnostic, so a stale literal misnames a fork rather than hiding one.
|
|
50
|
-
const FLY_DS_VERSION = '3.
|
|
50
|
+
const FLY_DS_VERSION = '3.2.0';
|
|
51
51
|
const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
|
|
52
52
|
/**
|
|
53
53
|
* Records this design-system instance on the shared `scope` and returns the
|
|
@@ -1498,6 +1498,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
1498
1498
|
'gantt.aria.resize_start': 'Resize start date',
|
|
1499
1499
|
'gantt.aria.resize_end': 'Resize end date',
|
|
1500
1500
|
'gantt.aria.link_handle': 'Drag to link from {{label}}',
|
|
1501
|
+
'gantt.aria.link_handle_start': 'Drag to link from the start of {{label}}',
|
|
1502
|
+
'gantt.aria.label_resize': 'Resize the label column',
|
|
1503
|
+
'gantt.aria.link': '{{type}} dependency: {{from}} to {{to}}',
|
|
1504
|
+
'gantt.aria.delete_link': 'Delete dependency',
|
|
1505
|
+
'gantt.link_type.fs': 'Finish-to-start',
|
|
1506
|
+
'gantt.link_type.ss': 'Start-to-start',
|
|
1507
|
+
'gantt.link_type.ff': 'Finish-to-finish',
|
|
1508
|
+
'gantt.link_type.sf': 'Start-to-finish',
|
|
1501
1509
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
1502
1510
|
'form.required_error': 'This field is required.',
|
|
1503
1511
|
'form.yes': 'Yes',
|
|
@@ -1880,6 +1888,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
1880
1888
|
'gantt.aria.resize_start': 'تغيير تاريخ البدء',
|
|
1881
1889
|
'gantt.aria.resize_end': 'تغيير تاريخ الانتهاء',
|
|
1882
1890
|
'gantt.aria.link_handle': 'اسحب للربط من {{label}}',
|
|
1891
|
+
'gantt.aria.link_handle_start': 'اسحب للربط من بداية {{label}}',
|
|
1892
|
+
'gantt.aria.label_resize': 'تغيير عرض عمود التسميات',
|
|
1893
|
+
'gantt.aria.link': 'ارتباط {{type}}: من {{from}} إلى {{to}}',
|
|
1894
|
+
'gantt.aria.delete_link': 'حذف الارتباط',
|
|
1895
|
+
'gantt.link_type.fs': 'من النهاية إلى البداية',
|
|
1896
|
+
'gantt.link_type.ss': 'من البداية إلى البداية',
|
|
1897
|
+
'gantt.link_type.ff': 'من النهاية إلى النهاية',
|
|
1898
|
+
'gantt.link_type.sf': 'من البداية إلى النهاية',
|
|
1883
1899
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
1884
1900
|
'form.required_error': 'هذا الحقل مطلوب.',
|
|
1885
1901
|
'form.yes': 'نعم',
|
|
@@ -2262,6 +2278,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
2262
2278
|
'gantt.aria.resize_start': 'Redimensionner la date de début',
|
|
2263
2279
|
'gantt.aria.resize_end': 'Redimensionner la date de fin',
|
|
2264
2280
|
'gantt.aria.link_handle': 'Glisser pour lier depuis {{label}}',
|
|
2281
|
+
'gantt.aria.link_handle_start': 'Glisser pour lier depuis le début de {{label}}',
|
|
2282
|
+
'gantt.aria.label_resize': 'Redimensionner la colonne des libellés',
|
|
2283
|
+
'gantt.aria.link': 'Liaison {{type}} : de {{from}} à {{to}}',
|
|
2284
|
+
'gantt.aria.delete_link': 'Supprimer la liaison',
|
|
2285
|
+
'gantt.link_type.fs': 'Fin à début',
|
|
2286
|
+
'gantt.link_type.ss': 'Début à début',
|
|
2287
|
+
'gantt.link_type.ff': 'Fin à fin',
|
|
2288
|
+
'gantt.link_type.sf': 'Début à fin',
|
|
2265
2289
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
2266
2290
|
'form.required_error': 'Ce champ est obligatoire.',
|
|
2267
2291
|
'form.yes': 'Oui',
|
|
@@ -2643,6 +2667,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
2643
2667
|
'gantt.aria.resize_start': 'آغاز کی تاریخ تبدیل کریں',
|
|
2644
2668
|
'gantt.aria.resize_end': 'اختتام کی تاریخ تبدیل کریں',
|
|
2645
2669
|
'gantt.aria.link_handle': '{{label}} سے لنک کرنے کے لیے گھسیٹیں',
|
|
2670
|
+
'gantt.aria.link_handle_start': '{{label}} کے آغاز سے لنک کرنے کے لیے گھسیٹیں',
|
|
2671
|
+
'gantt.aria.label_resize': 'لیبل کالم کی چوڑائی تبدیل کریں',
|
|
2672
|
+
'gantt.aria.link': '{{type}} انحصار: {{from}} سے {{to}} تک',
|
|
2673
|
+
'gantt.aria.delete_link': 'انحصار حذف کریں',
|
|
2674
|
+
'gantt.link_type.fs': 'اختتام سے آغاز',
|
|
2675
|
+
'gantt.link_type.ss': 'آغاز سے آغاز',
|
|
2676
|
+
'gantt.link_type.ff': 'اختتام سے اختتام',
|
|
2677
|
+
'gantt.link_type.sf': 'آغاز سے اختتام',
|
|
2646
2678
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
2647
2679
|
'form.required_error': 'یہ خانہ لازمی ہے۔',
|
|
2648
2680
|
'form.yes': 'ہاں',
|
|
@@ -12422,6 +12454,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
12422
12454
|
}, template: "<!-- \u2500\u2500 Frequency selector \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n<div class=\"fly-cron__row\">\r\n <label class=\"fly-cron__label\" [attr.for]=\"modeSelectId\">{{ 'cron.frequency_label' | translate }}</label>\r\n <!-- Selection is synced imperatively by an effect() (see the component) \u2014 a [value] /\r\n [selected] binding is applied before the @for options exist on cold render and falls\r\n back to the first option. The #modeSelect ref is what the effect drives. -->\r\n <select\r\n #modeSelect\r\n class=\"fly-cron__select\"\r\n [id]=\"modeSelectId\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (change)=\"onModeChange($any($event.target).value)\"\r\n >\r\n @for (m of availableModes(); track m) {\r\n <option [value]=\"m\">{{ ('cron.mode.' + m) | translate }}</option>\r\n }\r\n </select>\r\n</div>\r\n\r\n<!-- \u2500\u2500 Mode-specific editor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n@switch (mode()) {\r\n @case ('minutes') {\r\n <div class=\"fly-cron__row\">\r\n <span class=\"fly-cron__label\">{{ 'cron.every_label' | translate }}</span>\r\n <input\r\n class=\"fly-cron__num\"\r\n type=\"number\"\r\n min=\"1\"\r\n max=\"59\"\r\n [value]=\"everyN()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onEveryN($any($event.target).value)\"\r\n />\r\n <span class=\"fly-cron__suffix\">{{ 'cron.unit_minutes' | translate }}</span>\r\n </div>\r\n }\r\n\r\n @case ('hourly') {\r\n <div class=\"fly-cron__row fly-cron__row--wrap\">\r\n <span class=\"fly-cron__label\">{{ 'cron.every_label' | translate }}</span>\r\n <input\r\n class=\"fly-cron__num\"\r\n type=\"number\"\r\n min=\"1\"\r\n max=\"23\"\r\n [value]=\"everyN()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onEveryN($any($event.target).value)\"\r\n />\r\n <span class=\"fly-cron__suffix\">{{ 'cron.unit_hours' | translate }}</span>\r\n <span class=\"fly-cron__suffix\">{{ 'cron.at_minute_label' | translate }}</span>\r\n <input\r\n class=\"fly-cron__num\"\r\n type=\"number\"\r\n min=\"0\"\r\n max=\"59\"\r\n [value]=\"minute()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onMinute($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @case ('daily') {\r\n <div class=\"fly-cron__row\">\r\n <label class=\"fly-cron__label\" [attr.for]=\"timeInputId\">{{ 'cron.time_label' | translate }}</label>\r\n <input\r\n class=\"fly-cron__time\"\r\n type=\"time\"\r\n [id]=\"timeInputId\"\r\n [value]=\"timeValue()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onTime($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @case ('weekly') {\r\n <div class=\"fly-cron__row fly-cron__row--wrap\">\r\n <span class=\"fly-cron__label\">{{ 'cron.on_days_label' | translate }}</span>\r\n <div class=\"fly-cron__weekdays\" role=\"group\" [attr.aria-label]=\"'cron.on_days_label' | translate\">\r\n @for (d of weekdayOrder(); track d) {\r\n <button\r\n type=\"button\"\r\n role=\"checkbox\"\r\n class=\"fly-cron__chip\"\r\n [class.fly-cron__chip--on]=\"isWeekday(d)\"\r\n [attr.aria-checked]=\"isWeekday(d)\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (click)=\"toggleWeekday(d)\"\r\n >{{ ('cron.weekday_short.' + d) | translate }}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"fly-cron__row\">\r\n <label class=\"fly-cron__label\" [attr.for]=\"timeInputId\">{{ 'cron.time_label' | translate }}</label>\r\n <input\r\n class=\"fly-cron__time\"\r\n type=\"time\"\r\n [id]=\"timeInputId\"\r\n [value]=\"timeValue()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onTime($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @case ('monthly') {\r\n <div class=\"fly-cron__row fly-cron__row--wrap\">\r\n <span class=\"fly-cron__label\">{{ 'cron.day_of_month_label' | translate }}</span>\r\n <input\r\n class=\"fly-cron__num\"\r\n type=\"number\"\r\n min=\"1\"\r\n max=\"31\"\r\n [value]=\"dayOfMonth()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onDayOfMonth($any($event.target).value)\"\r\n />\r\n <label class=\"fly-cron__label\" [attr.for]=\"timeInputId\">{{ 'cron.time_label' | translate }}</label>\r\n <input\r\n class=\"fly-cron__time\"\r\n type=\"time\"\r\n [id]=\"timeInputId\"\r\n [value]=\"timeValue()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onTime($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @case ('advanced') {\r\n <div class=\"fly-cron__advanced\">\r\n @for (f of advancedFieldDefs(); track f.index) {\r\n <div class=\"fly-cron__field\">\r\n <label class=\"fly-cron__field-label\" [attr.for]=\"fieldInputId(f.index)\">{{ f.labelKey | translate }}</label>\r\n <input\r\n [id]=\"fieldInputId(f.index)\"\r\n class=\"fly-cron__field-input fly-cron__mono\"\r\n type=\"text\"\r\n inputmode=\"text\"\r\n autocomplete=\"off\"\r\n spellcheck=\"false\"\r\n placeholder=\"*\"\r\n [value]=\"fields()[f.index]\"\r\n [disabled]=\"effectiveDisabled()\"\r\n [attr.aria-invalid]=\"!fieldValid(f.index)\"\r\n [attr.aria-label]=\"f.labelKey | translate\"\r\n (input)=\"onFieldInput(f.index, $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n <p class=\"fly-cron__hint\">{{ 'cron.advanced.hint' | translate }}</p>\r\n }\r\n}\r\n\r\n<!-- \u2500\u2500 Summary + resulting expression \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n@if (mode() !== 'none') {\r\n <div class=\"fly-cron__summary\" [class.fly-cron__summary--error]=\"!valid()\" aria-live=\"polite\">\r\n <span class=\"fly-cron__summary-text\">{{ summary() }}</span>\r\n @if (expression(); as expr) {\r\n <code class=\"fly-cron__expr fly-cron__mono\">{{ expr }}</code>\r\n }\r\n </div>\r\n}\r\n\r\n@if (errorKey(); as ek) {\r\n <p class=\"fly-cron__error\" role=\"alert\">{{ ek | translate }}</p>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_surface-active: var(--surface-active, #eef2ff);--_surface-muted: var(--surface-section, #f8fafc);--_border: var(--surface-border, #e2e8f0);--_text: var(--text-color, #0f172a);--_text-subtle: var(--text-color-secondary, #64748b);--_primary: var(--primary-color);--_primary-contrast: var(--primary-color-text, #fff);--_danger: var(--red-500, #ef4444);--_radius: 8px;display:block;inline-size:100%;font-size:13px;color:var(--_text)}:host(.fly-cron-builder--disabled){opacity:.6}.fly-cron__row{display:flex;align-items:center;gap:8px;margin-block-end:10px}.fly-cron__row--wrap{flex-wrap:wrap}.fly-cron__label{flex:0 0 auto;color:var(--_text-subtle);font-weight:600}.fly-cron__suffix{flex:0 0 auto;color:var(--_text-subtle)}.fly-cron__select,.fly-cron__num,.fly-cron__time{block-size:2.25rem;padding-inline:10px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit}.fly-cron__select{flex:1 1 auto;min-inline-size:10rem;cursor:pointer}.fly-cron__num{inline-size:4.5rem;text-align:end}.fly-cron__time{inline-size:auto}.fly-cron__select:focus-visible,.fly-cron__num:focus-visible,.fly-cron__time:focus-visible,.fly-cron__field-input:focus-visible{outline:2px solid var(--_primary);outline-offset:1px}.fly-cron__select:disabled,.fly-cron__num:disabled,.fly-cron__time:disabled,.fly-cron__field-input:disabled{opacity:.55;cursor:not-allowed}.fly-cron__weekdays{display:flex;flex-wrap:wrap;gap:6px}.fly-cron__chip{min-inline-size:2.5rem;block-size:2rem;padding-inline:10px;border:1px solid var(--_border);border-radius:999px;background:var(--_surface);color:var(--_text);font:inherit;cursor:pointer;transition:background .12s ease,border-color .12s ease,color .12s ease}.fly-cron__chip:hover:not(:disabled){background:var(--_surface-hover)}.fly-cron__chip--on{background:var(--_primary);border-color:var(--_primary);color:var(--_primary-contrast);font-weight:600}.fly-cron__chip:focus-visible{outline:2px solid var(--_primary);outline-offset:2px}.fly-cron__chip:disabled{cursor:not-allowed}.fly-cron__advanced{display:flex;flex-wrap:wrap;gap:8px;margin-block-end:6px}.fly-cron__field{display:flex;flex-direction:column;gap:4px}.fly-cron__field-label{color:var(--_text-subtle);font-size:11px;font-weight:600}.fly-cron__field-input{inline-size:5rem;block-size:2.25rem;padding-inline:8px;border:1px solid var(--_border);border-radius:6px;background:var(--_surface);color:var(--_text);font:inherit;text-align:center}.fly-cron__field-input[aria-invalid=true]{border-color:var(--_danger)}.fly-cron__hint{margin:0 0 8px;color:var(--_text-subtle);font-size:11px}.fly-cron__summary{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface-muted)}.fly-cron__summary--error{border-color:var(--_danger)}.fly-cron__summary-text{flex:1 1 auto}.fly-cron__expr{flex:0 0 auto;padding:2px 8px;border-radius:6px;background:var(--_surface);border:1px solid var(--_border);color:var(--_text-subtle);font-size:12px;direction:ltr;unicode-bidi:isolate}.fly-cron__mono{font-family:var(--font-family-mono, ui-monospace, \"SFMono-Regular\", \"Menlo\", \"Consolas\", monospace)}.fly-cron__error{margin:6px 0 0;color:var(--_danger);font-size:12px}\n"] }]
|
|
12423
12455
|
}], propDecorators: { seconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "seconds", required: false }] }], allowNone: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowNone", required: false }] }], modes: [{ type: i0.Input, args: [{ isSignal: true, alias: "modes", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], cronChange: [{ type: i0.Output, args: ["cronChange"] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], modeSelectRef: [{ type: i0.ViewChild, args: ['modeSelect', { isSignal: true }] }] } });
|
|
12424
12456
|
|
|
12457
|
+
/**
|
|
12458
|
+
* Public data contract for {@link FlyGanttComponent}.
|
|
12459
|
+
*
|
|
12460
|
+
* The types here are deliberately **app-agnostic** — no Projects/Tasks vocabulary leaks in.
|
|
12461
|
+
* A consumer maps its own domain rows to {@link GanttRow}[] + {@link GanttDependency}[],
|
|
12462
|
+
* feeds them in, and reacts to the emitted change events by persisting itself. The Gantt
|
|
12463
|
+
* never calls a backend and never mutates its inputs.
|
|
12464
|
+
*/
|
|
12465
|
+
/** Immutable list of the supported zoom levels, coarsest-last. */
|
|
12466
|
+
const GANTT_ZOOMS = ['day', 'week', 'month', 'quarter'];
|
|
12467
|
+
/** Immutable list of the supported relationship kinds, `FS` first. */
|
|
12468
|
+
const GANTT_DEPENDENCY_TYPES = [
|
|
12469
|
+
'FS',
|
|
12470
|
+
'SS',
|
|
12471
|
+
'FF',
|
|
12472
|
+
'SF',
|
|
12473
|
+
];
|
|
12474
|
+
/**
|
|
12475
|
+
* Alpha the chart paints {@link GanttRow.backgroundColor} at. Deliberately low and fixed: the
|
|
12476
|
+
* tint is a *band behind* the bars and the row label, so a consumer can pass a saturated brand
|
|
12477
|
+
* colour without pre-computing a pastel that survives both themes.
|
|
12478
|
+
*/
|
|
12479
|
+
const GANTT_ROW_TINT_ALPHA = 0.16;
|
|
12480
|
+
|
|
12425
12481
|
const MS_PER_DAY = 86_400_000;
|
|
12426
12482
|
/** Pixels allotted to one calendar day at each zoom level. Everything scales off this. */
|
|
12427
12483
|
const ZOOM_PX_PER_DAY = {
|
|
@@ -12677,11 +12733,16 @@ function resolveLinks(deps, renderableIds) {
|
|
|
12677
12733
|
continue;
|
|
12678
12734
|
if (!renderableIds.has(d.fromId) || !renderableIds.has(d.toId))
|
|
12679
12735
|
continue;
|
|
12680
|
-
|
|
12736
|
+
// `\u0000` (written as an escape, not as a literal NUL — a raw NUL byte in the source makes
|
|
12737
|
+
// the whole file read as binary to grep/ripgrep and silently excludes it from code search)
|
|
12738
|
+
// cannot occur in a row id, so it is a collision-proof pair separator.
|
|
12739
|
+
const key = `${d.fromId}\u0000${d.toId}`;
|
|
12740
|
+
// One relationship per ordered pair, first declaration wins: MS Project models at most one
|
|
12741
|
+
// link between two tasks, so a second edge for the same pair is a duplicate, not an SS+FF.
|
|
12681
12742
|
if (seen.has(key))
|
|
12682
12743
|
continue;
|
|
12683
12744
|
seen.add(key);
|
|
12684
|
-
out.push({ fromId: d.fromId, toId: d.toId });
|
|
12745
|
+
out.push({ fromId: d.fromId, toId: d.toId, type: d.type ?? 'FS' });
|
|
12685
12746
|
}
|
|
12686
12747
|
return out;
|
|
12687
12748
|
}
|
|
@@ -12795,15 +12856,37 @@ const HEADER_H = HEADER_UPPER_H + HEADER_LOWER_H;
|
|
|
12795
12856
|
const BAR_V_PAD = 6;
|
|
12796
12857
|
/** Edge-handle hit width (px) for resize + the link connector radius. */
|
|
12797
12858
|
const HANDLE_W = 8;
|
|
12859
|
+
/** Label-pane width bounds (px) the splitter clamps to. */
|
|
12860
|
+
const MIN_LABEL_W = 140;
|
|
12861
|
+
const MAX_LABEL_W = 720;
|
|
12862
|
+
/** Keyboard resize steps (plain arrow / with Shift). */
|
|
12863
|
+
const LABEL_STEP = 16;
|
|
12864
|
+
const LABEL_STEP_COARSE = 64;
|
|
12865
|
+
/** Straight run (px) a dependency elbow travels out of a bar edge before it turns. */
|
|
12866
|
+
const LINK_STUB = 12;
|
|
12867
|
+
/** Clamp any candidate label-pane width into the splitter's range. */
|
|
12868
|
+
function clampLabelWidth(px) {
|
|
12869
|
+
return Math.max(MIN_LABEL_W, Math.min(MAX_LABEL_W, Math.round(px)));
|
|
12870
|
+
}
|
|
12871
|
+
/**
|
|
12872
|
+
* Relationship kind implied by the edge a gesture left and the edge it landed on. The name is
|
|
12873
|
+
* literally `<predecessor endpoint><successor endpoint>`, so this is a lookup, not a rule.
|
|
12874
|
+
*/
|
|
12875
|
+
function linkTypeFor(from, to) {
|
|
12876
|
+
if (from === 'finish')
|
|
12877
|
+
return to === 'start' ? 'FS' : 'FF';
|
|
12878
|
+
return to === 'start' ? 'SS' : 'SF';
|
|
12879
|
+
}
|
|
12798
12880
|
/**
|
|
12799
12881
|
* **`fly-gantt`** — the design-system SVG Gantt chart.
|
|
12800
12882
|
*
|
|
12801
12883
|
* A reusable, **data-contract-driven** timeline: feed it {@link GanttRow}[] + optional
|
|
12802
12884
|
* {@link GanttDependency}[] and it renders a two-pane chart — an indented, collapsible label
|
|
12803
12885
|
* tree on the inline-start side and a scrolling time grid (header ticks, weekend shading, a
|
|
12804
|
-
* today line, task bars with progress fill, milestone diamonds
|
|
12805
|
-
* arrows) on the other.
|
|
12806
|
-
*
|
|
12886
|
+
* today line, task bars with progress fill, milestone diamonds, optional per-row colour bands
|
|
12887
|
+
* and MS-Project-style dependency arrows) on the other. The pane divider is draggable, pointer
|
|
12888
|
+
* drag moves/resizes bars and draws new dependencies, and the keyboard moves the selection,
|
|
12889
|
+
* nudges dates and removes the selected link. **It never persists** — every mutation is an
|
|
12807
12890
|
* output event the consumer acts on.
|
|
12808
12891
|
*
|
|
12809
12892
|
* Rendered on plain inline SVG (no third-party charting lib, no CDN asset). All geometry is
|
|
@@ -12812,8 +12895,10 @@ const HANDLE_W = 8;
|
|
|
12812
12895
|
*
|
|
12813
12896
|
* Self-sufficient i18n: every user-visible string comes from the `gantt.*` keys in
|
|
12814
12897
|
* `DS_BASELINE_LOCALES` (en/ar/fr/ur), overridable by any consumer key of the same name.
|
|
12815
|
-
* Styling
|
|
12816
|
-
*
|
|
12898
|
+
* Styling reads the **app-content** token family (`--bg-2`, `--bg-3`, `--line-3`, `--ink`,
|
|
12899
|
+
* `--accent`, …) that dresses `fly-data-table` and the rest of the app-surface kit, so a Gantt
|
|
12900
|
+
* placed among those controls matches them; the shell-chrome family (`--surface-card`, …) and
|
|
12901
|
+
* then a light-neutral literal are chained as fallbacks for consumers that map neither.
|
|
12817
12902
|
*
|
|
12818
12903
|
* **Scale limit:** renders up to `maxRows` (default 500) visible rows; beyond that the list is
|
|
12819
12904
|
* capped and a footer notes the overflow. Heavy windowing/virtualization is intentionally
|
|
@@ -12830,8 +12915,14 @@ class FlyGanttComponent {
|
|
|
12830
12915
|
showToday = input(true, ...(ngDevMode ? [{ debugName: "showToday" }] : /* istanbul ignore next */ []));
|
|
12831
12916
|
/** Globally disable drag/resize/link (individual rows can also be `readonly`). */
|
|
12832
12917
|
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
12833
|
-
/**
|
|
12918
|
+
/**
|
|
12919
|
+
* Inline-start label-pane width in px — the **initial** width. The user can drag the pane
|
|
12920
|
+
* divider from there; once they have, this input only re-seeds the pane if
|
|
12921
|
+
* {@link resetLabelWidth} is called (double-clicking the divider does exactly that).
|
|
12922
|
+
*/
|
|
12834
12923
|
labelWidth = input(240, ...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
|
|
12924
|
+
/** Let the user drag the divider between the label pane and the time grid. */
|
|
12925
|
+
resizableLabels = input(true, ...(ngDevMode ? [{ debugName: "resizableLabels" }] : /* istanbul ignore next */ []));
|
|
12835
12926
|
/** Row band height in px. */
|
|
12836
12927
|
rowHeight = input(34, ...(ngDevMode ? [{ debugName: "rowHeight" }] : /* istanbul ignore next */ []));
|
|
12837
12928
|
/** Hard cap on rendered rows; beyond it the list is truncated (see class doc). */
|
|
@@ -12839,10 +12930,17 @@ class FlyGanttComponent {
|
|
|
12839
12930
|
// ── Outputs ────────────────────────────────────────────────────────────────
|
|
12840
12931
|
/** Fires on a committed bar move / resize with the new ISO `start`/`end`. */
|
|
12841
12932
|
rowDatesChange = output();
|
|
12842
|
-
/**
|
|
12933
|
+
/**
|
|
12934
|
+
* Fires when the user drags a link between two rows. `type` is derived from the edge the drag
|
|
12935
|
+
* started on and the edge of the target row it was dropped nearest to.
|
|
12936
|
+
*/
|
|
12843
12937
|
dependencyCreate = output();
|
|
12938
|
+
/** Fires when the user removes the selected dependency arrow (badge click or Delete). */
|
|
12939
|
+
dependencyDelete = output();
|
|
12844
12940
|
rowClick = output();
|
|
12845
12941
|
rowDblClick = output();
|
|
12942
|
+
/** Fires once per committed divider drag / keyboard resize with the new pane width in px. */
|
|
12943
|
+
labelWidthChange = output();
|
|
12846
12944
|
// ── Identity + geometry constants (template-visible) ─────────────────────────
|
|
12847
12945
|
_uid = ++_flyGanttUid;
|
|
12848
12946
|
markerId = `fly-gantt-${this._uid}-arrow`;
|
|
@@ -12850,20 +12948,30 @@ class FlyGanttComponent {
|
|
|
12850
12948
|
HEADER_UPPER_H = HEADER_UPPER_H;
|
|
12851
12949
|
HEADER_LOWER_H = HEADER_LOWER_H;
|
|
12852
12950
|
HANDLE_W = HANDLE_W;
|
|
12951
|
+
MIN_LABEL_W = MIN_LABEL_W;
|
|
12952
|
+
MAX_LABEL_W = MAX_LABEL_W;
|
|
12853
12953
|
// ── Interaction state ─────────────────────────────────────────────────────────
|
|
12854
12954
|
selectedId = signal(null, ...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
|
|
12955
|
+
/** Key (`from~to~type`) of the dependency arrow the user has selected, if any. */
|
|
12956
|
+
selectedLinkKey = signal(null, ...(ngDevMode ? [{ debugName: "selectedLinkKey" }] : /* istanbul ignore next */ []));
|
|
12855
12957
|
_collapsed = signal(new Set(), ...(ngDevMode ? [{ debugName: "_collapsed" }] : /* istanbul ignore next */ []));
|
|
12856
12958
|
/** Live drag preview `{id,start,end}` folded into geometry while a gesture runs. */
|
|
12857
12959
|
_dragPreview = signal(null, ...(ngDevMode ? [{ debugName: "_dragPreview" }] : /* istanbul ignore next */ []));
|
|
12858
|
-
/** Rubber-band link gesture render-space anchor + cursor, and its origin row. */
|
|
12960
|
+
/** Rubber-band link gesture render-space anchor + cursor, and its origin row + edge. */
|
|
12859
12961
|
linkGesture = signal(null, ...(ngDevMode ? [{ debugName: "linkGesture" }] : /* istanbul ignore next */ []));
|
|
12860
12962
|
dragTooltip = signal(null, ...(ngDevMode ? [{ debugName: "dragTooltip" }] : /* istanbul ignore next */ []));
|
|
12963
|
+
/** True while a divider drag is in flight (suppresses text selection host-wide). */
|
|
12964
|
+
resizingLabels = signal(false, ...(ngDevMode ? [{ debugName: "resizingLabels" }] : /* istanbul ignore next */ []));
|
|
12965
|
+
/** User-chosen pane width; `null` = still following the {@link labelWidth} input. */
|
|
12966
|
+
_labelWidthOverride = signal(null, ...(ngDevMode ? [{ debugName: "_labelWidthOverride" }] : /* istanbul ignore next */ []));
|
|
12861
12967
|
bodyRef = viewChild('bodySvg', ...(ngDevMode ? [{ debugName: "bodyRef" }] : /* istanbul ignore next */ []));
|
|
12862
12968
|
// ── Derived model ──────────────────────────────────────────────────────────────
|
|
12863
12969
|
rtl = computed(() => this.i18n.isRtl(), ...(ngDevMode ? [{ debugName: "rtl" }] : /* istanbul ignore next */ []));
|
|
12864
12970
|
pxPerDay = computed(() => ZOOM_PX_PER_DAY[this.zoom()], ...(ngDevMode ? [{ debugName: "pxPerDay" }] : /* istanbul ignore next */ []));
|
|
12865
12971
|
domain = computed(() => computeDomain(this.rows(), this.zoom()), ...(ngDevMode ? [{ debugName: "domain" }] : /* istanbul ignore next */ []));
|
|
12866
12972
|
innerWidth = computed(() => Math.max(1, dateToX(this.domain().end, this.domain().start, this.pxPerDay())), ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
|
|
12973
|
+
/** Label-pane width actually rendered — the user's dragged width, else the input. */
|
|
12974
|
+
effectiveLabelWidth = computed(() => clampLabelWidth(this._labelWidthOverride() ?? this.labelWidth()), ...(ngDevMode ? [{ debugName: "effectiveLabelWidth" }] : /* istanbul ignore next */ []));
|
|
12867
12975
|
/** Full visible list after tree flatten/collapse. */
|
|
12868
12976
|
_flat = computed(() => flattenRows(this.rows(), this._collapsed()), ...(ngDevMode ? [{ debugName: "_flat" }] : /* istanbul ignore next */ []));
|
|
12869
12977
|
/** Capped list actually rendered. */
|
|
@@ -12909,6 +13017,7 @@ class FlyGanttComponent {
|
|
|
12909
13017
|
}
|
|
12910
13018
|
const editable = !globalReadonly && !row.readonly && !isGroup;
|
|
12911
13019
|
const color = row.color ?? null;
|
|
13020
|
+
const tint = this._tintFor(row);
|
|
12912
13021
|
if (isMilestone) {
|
|
12913
13022
|
const at = start ?? end;
|
|
12914
13023
|
const logical = at ? dateToX(at, dom.start, ppd) : 0;
|
|
@@ -12916,7 +13025,7 @@ class FlyGanttComponent {
|
|
|
12916
13025
|
return {
|
|
12917
13026
|
flat, index, y, midY, shape: at ? 'milestone' : 'empty',
|
|
12918
13027
|
x: pointX, width: 0, progressWidth: 0, pointX,
|
|
12919
|
-
startX: pointX, endX: pointX, color,
|
|
13028
|
+
startX: pointX, endX: pointX, color, tint,
|
|
12920
13029
|
ariaLabel: this._ariaFor(row, start, end, 'milestone'),
|
|
12921
13030
|
editable: editable && !!at,
|
|
12922
13031
|
};
|
|
@@ -12924,7 +13033,7 @@ class FlyGanttComponent {
|
|
|
12924
13033
|
if (!start || !end) {
|
|
12925
13034
|
return {
|
|
12926
13035
|
flat, index, y, midY, shape: 'empty',
|
|
12927
|
-
x: 0, width: 0, progressWidth: 0, pointX: 0, startX: 0, endX: 0, color,
|
|
13036
|
+
x: 0, width: 0, progressWidth: 0, pointX: 0, startX: 0, endX: 0, color, tint,
|
|
12928
13037
|
ariaLabel: this._ariaFor(row, start, end, isGroup ? 'group' : 'bar'),
|
|
12929
13038
|
editable: false,
|
|
12930
13039
|
};
|
|
@@ -12942,12 +13051,14 @@ class FlyGanttComponent {
|
|
|
12942
13051
|
pointX: 0,
|
|
12943
13052
|
startX: mapX(xs, w, rtl),
|
|
12944
13053
|
endX: mapX(xe, w, rtl),
|
|
12945
|
-
color,
|
|
13054
|
+
color, tint,
|
|
12946
13055
|
ariaLabel: this._ariaFor(row, start, end, isGroup ? 'group' : 'bar'),
|
|
12947
13056
|
editable: isGroup ? false : editable,
|
|
12948
13057
|
};
|
|
12949
13058
|
});
|
|
12950
13059
|
}, ...(ngDevMode ? [{ debugName: "rowVms" }] : /* istanbul ignore next */ []));
|
|
13060
|
+
/** Only the rows carrying a tint, so the band pass does not emit an empty rect per row. */
|
|
13061
|
+
tintedRows = computed(() => this.rowVms().filter((vm) => vm.tint !== null), ...(ngDevMode ? [{ debugName: "tintedRows" }] : /* istanbul ignore next */ []));
|
|
12951
13062
|
/** Dependency arrows between currently-rendered rows (missing/collapsed endpoints skipped). */
|
|
12952
13063
|
linkVms = computed(() => {
|
|
12953
13064
|
const vmById = new Map();
|
|
@@ -12962,7 +13073,21 @@ class FlyGanttComponent {
|
|
|
12962
13073
|
const to = vmById.get(link.toId);
|
|
12963
13074
|
if (!from || !to)
|
|
12964
13075
|
continue;
|
|
12965
|
-
|
|
13076
|
+
const routed = this._route(from, to, link.type, rtl);
|
|
13077
|
+
out.push({
|
|
13078
|
+
key: `${link.fromId}~${link.toId}~${link.type}`,
|
|
13079
|
+
fromId: link.fromId,
|
|
13080
|
+
toId: link.toId,
|
|
13081
|
+
type: link.type,
|
|
13082
|
+
path: routed.path,
|
|
13083
|
+
badgeX: routed.badgeX,
|
|
13084
|
+
badgeY: routed.badgeY,
|
|
13085
|
+
ariaLabel: this.i18n.t('gantt.aria.link', {
|
|
13086
|
+
type: this.i18n.t(`gantt.link_type.${link.type.toLowerCase()}`),
|
|
13087
|
+
from: from.flat.row.label,
|
|
13088
|
+
to: to.flat.row.label,
|
|
13089
|
+
}),
|
|
13090
|
+
});
|
|
12966
13091
|
}
|
|
12967
13092
|
return out;
|
|
12968
13093
|
}, ...(ngDevMode ? [{ debugName: "linkVms" }] : /* istanbul ignore next */ []));
|
|
@@ -12980,6 +13105,13 @@ class FlyGanttComponent {
|
|
|
12980
13105
|
const { pointX: cx, midY: cy } = vm;
|
|
12981
13106
|
return `${cx},${cy - r} ${cx + r},${cy} ${cx},${cy + r} ${cx - r},${cy}`;
|
|
12982
13107
|
}
|
|
13108
|
+
/**
|
|
13109
|
+
* Link connector x for a milestone. A diamond has no width, so the connector is nudged clear
|
|
13110
|
+
* of the marker in the forward-in-time direction (which flips under RTL).
|
|
13111
|
+
*/
|
|
13112
|
+
milestoneConnectorX(vm) {
|
|
13113
|
+
return vm.pointX + (this.milestoneR() + 5) * (this.rtl() ? -1 : 1);
|
|
13114
|
+
}
|
|
12983
13115
|
/** Summary-bracket path (thin bar + down-turned end caps) for a group row. */
|
|
12984
13116
|
groupPath(vm) {
|
|
12985
13117
|
const top = this.barTop(vm);
|
|
@@ -12995,6 +13127,88 @@ class FlyGanttComponent {
|
|
|
12995
13127
|
indentFor(depth) {
|
|
12996
13128
|
return 8 + depth * 16;
|
|
12997
13129
|
}
|
|
13130
|
+
// ── Label-pane divider ─────────────────────────────────────────────────────────
|
|
13131
|
+
/**
|
|
13132
|
+
* Pointer-drag the divider. Forward-in-time is `+x` LTR and `-x` RTL, and the label pane sits
|
|
13133
|
+
* on the inline-start side in both, so the same sign flip that mirrors the timeline also
|
|
13134
|
+
* mirrors "drag outward = wider".
|
|
13135
|
+
*/
|
|
13136
|
+
onLabelResizePointerDown(ev) {
|
|
13137
|
+
if (!this.resizableLabels() || ev.button !== 0)
|
|
13138
|
+
return;
|
|
13139
|
+
ev.preventDefault();
|
|
13140
|
+
ev.stopPropagation();
|
|
13141
|
+
const startClientX = ev.clientX;
|
|
13142
|
+
const startWidth = this.effectiveLabelWidth();
|
|
13143
|
+
const dirSign = this.rtl() ? -1 : 1;
|
|
13144
|
+
this.resizingLabels.set(true);
|
|
13145
|
+
const onMove = (e) => {
|
|
13146
|
+
this._labelWidthOverride.set(clampLabelWidth(startWidth + (e.clientX - startClientX) * dirSign));
|
|
13147
|
+
};
|
|
13148
|
+
const onUp = () => {
|
|
13149
|
+
document.removeEventListener('pointermove', onMove);
|
|
13150
|
+
document.removeEventListener('pointerup', onUp);
|
|
13151
|
+
this.resizingLabels.set(false);
|
|
13152
|
+
const final = this.effectiveLabelWidth();
|
|
13153
|
+
if (final !== startWidth)
|
|
13154
|
+
this.labelWidthChange.emit(final);
|
|
13155
|
+
};
|
|
13156
|
+
document.addEventListener('pointermove', onMove);
|
|
13157
|
+
document.addEventListener('pointerup', onUp);
|
|
13158
|
+
this.destroyRef.onDestroy(() => {
|
|
13159
|
+
document.removeEventListener('pointermove', onMove);
|
|
13160
|
+
document.removeEventListener('pointerup', onUp);
|
|
13161
|
+
});
|
|
13162
|
+
}
|
|
13163
|
+
/**
|
|
13164
|
+
* Keyboard resize on the focused divider. ArrowRight/Left grow/shrink by the *inline* meaning
|
|
13165
|
+
* of the key, so under RTL the arrow that points away from the pane is still the one that
|
|
13166
|
+
* widens it. `Home`/`End` jump to the bounds, Enter/Escape restore the input width.
|
|
13167
|
+
*
|
|
13168
|
+
* Typed `Event` for the same reason as {@link onLabelRowKeydown}: Angular's strict template
|
|
13169
|
+
* checker types `(keydown)` `$event` as `Event`, so the cast is done here once.
|
|
13170
|
+
*/
|
|
13171
|
+
onLabelResizeKeydown(ev) {
|
|
13172
|
+
if (!this.resizableLabels())
|
|
13173
|
+
return;
|
|
13174
|
+
const key = ev.key;
|
|
13175
|
+
const coarse = ev.shiftKey;
|
|
13176
|
+
const step = coarse ? LABEL_STEP_COARSE : LABEL_STEP;
|
|
13177
|
+
const grow = this.rtl() ? 'ArrowLeft' : 'ArrowRight';
|
|
13178
|
+
const shrink = this.rtl() ? 'ArrowRight' : 'ArrowLeft';
|
|
13179
|
+
let next;
|
|
13180
|
+
if (key === grow)
|
|
13181
|
+
next = this.effectiveLabelWidth() + step;
|
|
13182
|
+
else if (key === shrink)
|
|
13183
|
+
next = this.effectiveLabelWidth() - step;
|
|
13184
|
+
else if (key === 'Home')
|
|
13185
|
+
next = MIN_LABEL_W;
|
|
13186
|
+
else if (key === 'End')
|
|
13187
|
+
next = MAX_LABEL_W;
|
|
13188
|
+
else if (key === 'Enter' || key === 'Escape') {
|
|
13189
|
+
// Stop the grid-level Enter handler from also activating the selected row.
|
|
13190
|
+
ev.preventDefault();
|
|
13191
|
+
ev.stopPropagation();
|
|
13192
|
+
this.resetLabelWidth();
|
|
13193
|
+
return;
|
|
13194
|
+
}
|
|
13195
|
+
else
|
|
13196
|
+
return;
|
|
13197
|
+
ev.preventDefault();
|
|
13198
|
+
ev.stopPropagation();
|
|
13199
|
+
const clamped = clampLabelWidth(next);
|
|
13200
|
+
if (clamped === this.effectiveLabelWidth())
|
|
13201
|
+
return;
|
|
13202
|
+
this._labelWidthOverride.set(clamped);
|
|
13203
|
+
this.labelWidthChange.emit(clamped);
|
|
13204
|
+
}
|
|
13205
|
+
/** Drop the user's dragged width and fall back to the {@link labelWidth} input. */
|
|
13206
|
+
resetLabelWidth() {
|
|
13207
|
+
if (this._labelWidthOverride() === null)
|
|
13208
|
+
return;
|
|
13209
|
+
this._labelWidthOverride.set(null);
|
|
13210
|
+
this.labelWidthChange.emit(this.effectiveLabelWidth());
|
|
13211
|
+
}
|
|
12998
13212
|
// ── Group collapse ─────────────────────────────────────────────────────────────
|
|
12999
13213
|
isCollapsed(id) {
|
|
13000
13214
|
return this._collapsed().has(id);
|
|
@@ -13022,13 +13236,39 @@ class FlyGanttComponent {
|
|
|
13022
13236
|
// ── Row activation ─────────────────────────────────────────────────────────────
|
|
13023
13237
|
onRowClick(id) {
|
|
13024
13238
|
this.selectedId.set(id);
|
|
13239
|
+
// A row and a link are alternative selections — picking one drops the other, so Delete is
|
|
13240
|
+
// never ambiguous about which thing it would remove.
|
|
13241
|
+
this.selectedLinkKey.set(null);
|
|
13025
13242
|
this.rowClick.emit(id);
|
|
13026
13243
|
}
|
|
13027
13244
|
onRowDblClick(id) {
|
|
13028
13245
|
this.rowDblClick.emit(id);
|
|
13029
13246
|
}
|
|
13247
|
+
// ── Dependency arrow selection + removal ───────────────────────────────────────
|
|
13248
|
+
onLinkClick(ev, vm) {
|
|
13249
|
+
ev.stopPropagation();
|
|
13250
|
+
this.selectedLinkKey.set(this.selectedLinkKey() === vm.key ? null : vm.key);
|
|
13251
|
+
}
|
|
13252
|
+
deleteLink(ev, vm) {
|
|
13253
|
+
ev.stopPropagation();
|
|
13254
|
+
ev.preventDefault();
|
|
13255
|
+
if (this.readonly())
|
|
13256
|
+
return;
|
|
13257
|
+
this.selectedLinkKey.set(null);
|
|
13258
|
+
this.dependencyDelete.emit({ fromId: vm.fromId, toId: vm.toId, type: vm.type });
|
|
13259
|
+
}
|
|
13030
13260
|
// ── Keyboard ───────────────────────────────────────────────────────────────────
|
|
13031
13261
|
onGridKeydown(ev) {
|
|
13262
|
+
// Link removal is checked before the row branches: while an arrow is selected the grid's
|
|
13263
|
+
// Delete belongs to it, and selecting an arrow already cleared the row selection.
|
|
13264
|
+
if (ev.key === 'Delete' || ev.key === 'Backspace') {
|
|
13265
|
+
const selected = this.linkVms().find((l) => l.key === this.selectedLinkKey());
|
|
13266
|
+
if (selected) {
|
|
13267
|
+
ev.preventDefault();
|
|
13268
|
+
this.deleteLink(ev, selected);
|
|
13269
|
+
}
|
|
13270
|
+
return;
|
|
13271
|
+
}
|
|
13032
13272
|
const list = this.visibleRows();
|
|
13033
13273
|
if (list.length === 0)
|
|
13034
13274
|
return;
|
|
@@ -13095,6 +13335,7 @@ class FlyGanttComponent {
|
|
|
13095
13335
|
ev.preventDefault();
|
|
13096
13336
|
ev.stopPropagation();
|
|
13097
13337
|
this.selectedId.set(vm.flat.row.id);
|
|
13338
|
+
this.selectedLinkKey.set(null);
|
|
13098
13339
|
const dates = this._resolvedDates().get(vm.flat.row.id);
|
|
13099
13340
|
if (!dates?.start || !dates?.end)
|
|
13100
13341
|
return;
|
|
@@ -13149,13 +13390,19 @@ class FlyGanttComponent {
|
|
|
13149
13390
|
});
|
|
13150
13391
|
}
|
|
13151
13392
|
// ── Pointer drag: dependency create ──────────────────────────────────────────────
|
|
13152
|
-
|
|
13393
|
+
/**
|
|
13394
|
+
* Start a link gesture from one end of a bar. Which end it started on, plus which end of the
|
|
13395
|
+
* target row it is dropped nearest to, is what selects the {@link GanttDependencyType} — so all
|
|
13396
|
+
* four MS-Project relationships are reachable with the same single drag.
|
|
13397
|
+
*/
|
|
13398
|
+
onLinkPointerDown(ev, vm, anchor) {
|
|
13153
13399
|
if (this.readonly() || vm.flat.row.readonly || ev.button !== 0)
|
|
13154
13400
|
return;
|
|
13155
13401
|
ev.preventDefault();
|
|
13156
13402
|
ev.stopPropagation();
|
|
13157
13403
|
const local = this._toBodyPoint(ev);
|
|
13158
|
-
|
|
13404
|
+
const x0 = vm.shape === 'milestone' ? vm.pointX : anchor === 'finish' ? vm.endX : vm.startX;
|
|
13405
|
+
this.linkGesture.set({ fromId: vm.flat.row.id, anchor, x0, y0: vm.midY, x1: local.x, y1: local.y });
|
|
13159
13406
|
const onMove = (e) => {
|
|
13160
13407
|
const p = this._toBodyPoint(e);
|
|
13161
13408
|
const g = this.linkGesture();
|
|
@@ -13169,9 +13416,13 @@ class FlyGanttComponent {
|
|
|
13169
13416
|
this.linkGesture.set(null);
|
|
13170
13417
|
if (!g)
|
|
13171
13418
|
return;
|
|
13172
|
-
const
|
|
13173
|
-
if (
|
|
13174
|
-
this.dependencyCreate.emit({
|
|
13419
|
+
const target = this._dropTargetAt(e.clientX, e.clientY);
|
|
13420
|
+
if (target && target.id !== g.fromId) {
|
|
13421
|
+
this.dependencyCreate.emit({
|
|
13422
|
+
fromId: g.fromId,
|
|
13423
|
+
toId: target.id,
|
|
13424
|
+
type: linkTypeFor(g.anchor, target.anchor),
|
|
13425
|
+
});
|
|
13175
13426
|
}
|
|
13176
13427
|
};
|
|
13177
13428
|
document.addEventListener('pointermove', onMove);
|
|
@@ -13189,6 +13440,17 @@ class FlyGanttComponent {
|
|
|
13189
13440
|
return `M ${g.x0} ${g.y0} L ${g.x1} ${g.y1}`;
|
|
13190
13441
|
}, ...(ngDevMode ? [{ debugName: "linkGesturePath" }] : /* istanbul ignore next */ []));
|
|
13191
13442
|
// ── Internals ────────────────────────────────────────────────────────────────────
|
|
13443
|
+
/**
|
|
13444
|
+
* Resolve a row's `backgroundColor` into the low-alpha band colour actually painted.
|
|
13445
|
+
* `color-mix` does the fade in the consumer's own colour space, so a token, a hex, an
|
|
13446
|
+
* `rgb()` or an `oklch()` all work and none of them need pre-computing per theme.
|
|
13447
|
+
*/
|
|
13448
|
+
_tintFor(row) {
|
|
13449
|
+
const raw = row.backgroundColor?.trim();
|
|
13450
|
+
if (!raw)
|
|
13451
|
+
return null;
|
|
13452
|
+
return `color-mix(in srgb, ${raw} ${Math.round(GANTT_ROW_TINT_ALPHA * 100)}%, transparent)`;
|
|
13453
|
+
}
|
|
13192
13454
|
_mapTicks(ticks) {
|
|
13193
13455
|
const w = this.innerWidth();
|
|
13194
13456
|
const rtl = this.rtl();
|
|
@@ -13197,16 +13459,41 @@ class FlyGanttComponent {
|
|
|
13197
13459
|
// Reflect each cell: its inline-start edge becomes innerWidth - (x + width).
|
|
13198
13460
|
return ticks.map((t) => ({ ...t, x: w - t.x - t.width }));
|
|
13199
13461
|
}
|
|
13200
|
-
/**
|
|
13201
|
-
|
|
13462
|
+
/**
|
|
13463
|
+
* Route a dependency elbow for any of the four relationship kinds (render space).
|
|
13464
|
+
*
|
|
13465
|
+
* The kind names the two endpoints, so it decides both the anchor x's and the direction the
|
|
13466
|
+
* arrow travels: leaving a *finish* edge moves forward in time, leaving a *start* edge moves
|
|
13467
|
+
* backward; entering a *start* edge approaches from behind, entering a *finish* edge from
|
|
13468
|
+
* ahead. `fwd` folds RTL in, so none of that branches on direction.
|
|
13469
|
+
*
|
|
13470
|
+
* One turn suffices when the entry stub lies ahead of the exit stub in the approach
|
|
13471
|
+
* direction. When it does not — overlapping or reversed bars, which SS/FF/SF hit constantly —
|
|
13472
|
+
* a straight drop would cut back through the bars, so the elbow detours via the gap between
|
|
13473
|
+
* the two rows instead.
|
|
13474
|
+
*/
|
|
13475
|
+
_route(from, to, type, rtl) {
|
|
13202
13476
|
const fwd = rtl ? -1 : 1;
|
|
13203
|
-
const
|
|
13204
|
-
const
|
|
13477
|
+
const fromFinish = type === 'FS' || type === 'FF';
|
|
13478
|
+
const toStart = type === 'FS' || type === 'SS';
|
|
13479
|
+
const x1 = from.shape === 'milestone' ? from.pointX : fromFinish ? from.endX : from.startX;
|
|
13480
|
+
const x2 = to.shape === 'milestone' ? to.pointX : toStart ? to.startX : to.endX;
|
|
13205
13481
|
const y1 = from.midY;
|
|
13206
|
-
const x2 = to.shape === 'milestone' ? to.pointX : to.startX;
|
|
13207
13482
|
const y2 = to.midY;
|
|
13208
|
-
|
|
13209
|
-
|
|
13483
|
+
const d1 = (fromFinish ? 1 : -1) * fwd; // exit direction
|
|
13484
|
+
const d2 = (toStart ? 1 : -1) * fwd; // approach direction
|
|
13485
|
+
const exit = x1 + LINK_STUB * d1;
|
|
13486
|
+
const entry = x2 - LINK_STUB * d2;
|
|
13487
|
+
if ((entry - exit) * d2 >= 0) {
|
|
13488
|
+
return { path: `M ${x1} ${y1} H ${exit} V ${y2} H ${x2}`, badgeX: exit, badgeY: (y1 + y2) / 2 };
|
|
13489
|
+
}
|
|
13490
|
+
// Distinct rows always differ in y; the guard only keeps a degenerate input off the bar.
|
|
13491
|
+
const yMid = y1 === y2 ? y1 - this.rowHeight() / 2 : (y1 + y2) / 2;
|
|
13492
|
+
return {
|
|
13493
|
+
path: `M ${x1} ${y1} H ${exit} V ${yMid} H ${entry} V ${y2} H ${x2}`,
|
|
13494
|
+
badgeX: entry,
|
|
13495
|
+
badgeY: yMid,
|
|
13496
|
+
};
|
|
13210
13497
|
}
|
|
13211
13498
|
/** Convert a pointer event to a coordinate inside the body SVG (render space). */
|
|
13212
13499
|
_toBodyPoint(ev) {
|
|
@@ -13216,11 +13503,25 @@ class FlyGanttComponent {
|
|
|
13216
13503
|
const rect = svg.getBoundingClientRect();
|
|
13217
13504
|
return { x: ev.clientX - rect.left, y: ev.clientY - rect.top };
|
|
13218
13505
|
}
|
|
13219
|
-
/**
|
|
13220
|
-
|
|
13506
|
+
/**
|
|
13507
|
+
* The row a link gesture was dropped on **and which of its ends** the drop landed nearest —
|
|
13508
|
+
* the second half of the type derivation. A milestone has no width, so both of its ends are
|
|
13509
|
+
* the same point and it is reported as `start`, giving the FS/SS pair anyone actually wants.
|
|
13510
|
+
*/
|
|
13511
|
+
_dropTargetAt(clientX, clientY) {
|
|
13221
13512
|
const el = document.elementFromPoint(clientX, clientY);
|
|
13222
13513
|
const holder = el?.closest('[data-row-id]');
|
|
13223
|
-
|
|
13514
|
+
const id = holder?.getAttribute('data-row-id');
|
|
13515
|
+
if (!id)
|
|
13516
|
+
return null;
|
|
13517
|
+
const vm = this.rowVms().find((v) => v.flat.row.id === id);
|
|
13518
|
+
if (!vm || vm.shape === 'milestone' || vm.shape === 'empty')
|
|
13519
|
+
return { id, anchor: 'start' };
|
|
13520
|
+
const x = this._toBodyPoint({ clientX, clientY }).x;
|
|
13521
|
+
return {
|
|
13522
|
+
id,
|
|
13523
|
+
anchor: Math.abs(x - vm.endX) < Math.abs(x - vm.startX) ? 'finish' : 'start',
|
|
13524
|
+
};
|
|
13224
13525
|
}
|
|
13225
13526
|
_ariaFor(row, start, end, shape) {
|
|
13226
13527
|
const t = (k, p) => this.i18n.t(k, p);
|
|
@@ -13241,7 +13542,7 @@ class FlyGanttComponent {
|
|
|
13241
13542
|
: t('gantt.aria.bar', { label: row.label, start: toIsoDate(start), end: toIsoDate(end), progress });
|
|
13242
13543
|
}
|
|
13243
13544
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyGanttComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13244
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyGanttComponent, isStandalone: true, selector: "fly-gantt", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, dependencies: { classPropertyName: "dependencies", publicName: "dependencies", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, showToday: { classPropertyName: "showToday", publicName: "showToday", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, maxRows: { classPropertyName: "maxRows", publicName: "maxRows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowDatesChange: "rowDatesChange", dependencyCreate: "dependencyCreate", rowClick: "rowClick", rowDblClick: "rowDblClick" }, host: { properties: { "class.fly-gantt--rtl": "rtl()", "class.fly-gantt--readonly": "readonly()", "attr.dir": "i18n.direction()" }, classAttribute: "fly-gantt" }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodySvg"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\r\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\r\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\r\n<div\r\n class=\"fly-gantt__scroll\"\r\n role=\"grid\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\r\n [attr.aria-rowcount]=\"visibleRows().length\"\r\n (keydown)=\"onGridKeydown($event)\"\r\n>\r\n <div\r\n class=\"fly-gantt__canvas\"\r\n [style.grid-template-columns]=\"labelWidth() + 'px ' + innerWidth() + 'px'\"\r\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\r\n >\r\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\"></div>\r\n\r\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <svg\r\n class=\"fly-gantt__time-header\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"HEADER_H\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\r\n }\r\n @for (t of upperTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\r\n }\r\n </svg>\r\n\r\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"fly-gantt__labels\" role=\"rowgroup\">\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <div\r\n class=\"fly-gantt__label-row\"\r\n role=\"row\"\r\n tabindex=\"-1\"\r\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\r\n [style.height.px]=\"rowHeight()\"\r\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.aria-label]=\"vm.ariaLabel\"\r\n (click)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\r\n >\r\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\r\n @if (vm.flat.hasChildren) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-gantt__chevron\"\r\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\r\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\r\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\r\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\r\n >\u25B8</button>\r\n } @else {\r\n <span class=\"fly-gantt__chevron-spacer\"></span>\r\n }\r\n <span\r\n class=\"fly-gantt__label-text\"\r\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\r\n [title]=\"vm.flat.row.label\"\r\n >{{ vm.flat.row.label }}</span>\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <svg\r\n #bodySvg\r\n class=\"fly-gantt__body\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"bodyHeight()\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\r\n >\r\n <defs>\r\n <marker\r\n [attr.id]=\"markerId\"\r\n markerWidth=\"8\"\r\n markerHeight=\"8\"\r\n refX=\"6\"\r\n refY=\"4\"\r\n orient=\"auto\"\r\n markerUnits=\"userSpaceOnUse\"\r\n >\r\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\r\n </marker>\r\n </defs>\r\n\r\n <!-- Weekend shading + vertical gridlines (behind everything). -->\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n\r\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\r\n <rect\r\n class=\"fly-gantt__row-hit\"\r\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.x]=\"0\"\r\n [attr.y]=\"vm.y\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"rowHeight()\"\r\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n />\r\n\r\n @switch (vm.shape) {\r\n @case ('group') {\r\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\r\n }\r\n @case ('milestone') {\r\n <polygon\r\n class=\"fly-gantt__milestone\"\r\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\r\n [attr.points]=\"diamondPoints(vm)\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n }\r\n @case ('bar') {\r\n <g class=\"fly-gantt__bar-group\">\r\n <rect\r\n class=\"fly-gantt__bar\"\r\n [class.fly-gantt__bar--editable]=\"vm.editable\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.width\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n @if (vm.progressWidth > 0) {\r\n <rect\r\n class=\"fly-gantt__progress\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.progressWidth\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n />\r\n }\r\n @if (vm.editable) {\r\n <!-- Resize handles at both edges. -->\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\r\n />\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\r\n />\r\n }\r\n @if (!readonly() && !vm.flat.row.readonly) {\r\n <!-- Finish connector: drag to draw a dependency to another row. -->\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"vm.endX\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm)\"\r\n />\r\n }\r\n </g>\r\n }\r\n }\r\n </g>\r\n }\r\n\r\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). -->\r\n @for (link of linkVms(); track link.key) {\r\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n }\r\n\r\n <!-- In-flight link rubber band. -->\r\n @if (linkGesturePath(); as gp) {\r\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n }\r\n\r\n <!-- Today line (over the bars). -->\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n </svg>\r\n </div>\r\n\r\n @if (visibleRows().length === 0) {\r\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\r\n }\r\n @if (overflowCount() > 0) {\r\n <p class=\"fly-gantt__overflow\">\r\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\r\n </p>\r\n }\r\n</div>\r\n\r\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\r\n@if (dragTooltip(); as tip) {\r\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-alt: var(--glass-bg-elevated, #f8fafc);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--surface-border, #e2e8f0);--_grid-line: var(--surface-border, #eef2f7);--_text: var(--text-color, #0f172a);--_text-subtle: var(--text-color-secondary, #64748b);--_primary: var(--primary-color);--_primary-contrast: var(--primary-color-text, #fff);--_bar: var(--primary-color);--_bar-progress: var(--primary-600, #4f46e5);--_weekend: rgb(100 116 139 / 9%);--_today: var(--red-500, #ef4444);--_link: var(--text-color-secondary, #64748b);--_radius: 8px;display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_primary);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_primary)}.fly-gantt__label-inner{display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform .12s ease}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_primary);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_primary);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity .12s ease}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_primary);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-subtle);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13545
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyGanttComponent, isStandalone: true, selector: "fly-gantt", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, dependencies: { classPropertyName: "dependencies", publicName: "dependencies", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, showToday: { classPropertyName: "showToday", publicName: "showToday", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, resizableLabels: { classPropertyName: "resizableLabels", publicName: "resizableLabels", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, maxRows: { classPropertyName: "maxRows", publicName: "maxRows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowDatesChange: "rowDatesChange", dependencyCreate: "dependencyCreate", dependencyDelete: "dependencyDelete", rowClick: "rowClick", rowDblClick: "rowDblClick", labelWidthChange: "labelWidthChange" }, host: { properties: { "class.fly-gantt--rtl": "rtl()", "class.fly-gantt--readonly": "readonly()", "class.fly-gantt--resizing": "resizingLabels()", "attr.dir": "i18n.direction()" }, classAttribute: "fly-gantt" }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodySvg"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\n<div\n class=\"fly-gantt__scroll\"\n role=\"grid\"\n tabindex=\"0\"\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\n [attr.aria-rowcount]=\"visibleRows().length\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div\n class=\"fly-gantt__canvas\"\n [style.grid-template-columns]=\"effectiveLabelWidth() + 'px ' + innerWidth() + 'px'\"\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\n >\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\">\n @if (resizableLabels()) {\n <!-- The divider lives in the corner because the corner is the one cell pinned on BOTH\n axes: the grip stays reachable however far the user has scrolled. The full-height\n strip down the label pane below is the same gesture, presentational only. -->\n <div\n class=\"fly-gantt__splitter\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"'gantt.aria.label_resize' | translate\"\n [attr.aria-valuenow]=\"effectiveLabelWidth()\"\n [attr.aria-valuemin]=\"MIN_LABEL_W\"\n [attr.aria-valuemax]=\"MAX_LABEL_W\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (keydown)=\"onLabelResizeKeydown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n class=\"fly-gantt__time-header\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"HEADER_H\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\n aria-hidden=\"true\"\n >\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\n }\n @for (t of upperTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\n }\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\n }\n </svg>\n\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__labels\" role=\"rowgroup\">\n @for (vm of rowVms(); track vm.flat.row.id) {\n <div\n class=\"fly-gantt__label-row\"\n role=\"row\"\n tabindex=\"-1\"\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\n [class.fly-gantt__label-row--tinted]=\"vm.tint !== null\"\n [style.height.px]=\"rowHeight()\"\n [style.--fly-gantt-row-tint]=\"vm.tint\"\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\n [attr.aria-label]=\"vm.ariaLabel\"\n (click)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\n >\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\n @if (vm.flat.hasChildren) {\n <button\n type=\"button\"\n class=\"fly-gantt__chevron\"\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\n >\u25B8</button>\n } @else {\n <span class=\"fly-gantt__chevron-spacer\"></span>\n }\n @if (vm.color) {\n <!-- Colour chip: the row's own colour at full strength, so a milestone stays\n identifiable in the label pane where the band tint is deliberately faint. -->\n <span class=\"fly-gantt__label-swatch\" [style.background]=\"vm.color\" aria-hidden=\"true\"></span>\n }\n <span\n class=\"fly-gantt__label-text\"\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\n [title]=\"vm.flat.row.label\"\n >{{ vm.flat.row.label }}</span>\n </span>\n </div>\n }\n @if (resizableLabels()) {\n <!-- aria-hidden so this presentational twin of the corner separator is not a second\n (and structurally invalid) child of the rowgroup in the accessibility tree. -->\n <div\n class=\"fly-gantt__splitter fly-gantt__splitter--rail\"\n aria-hidden=\"true\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n #bodySvg\n class=\"fly-gantt__body\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"bodyHeight()\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\n >\n <defs>\n <marker\n [attr.id]=\"markerId\"\n markerWidth=\"8\"\n markerHeight=\"8\"\n refX=\"6\"\n refY=\"4\"\n orient=\"auto\"\n markerUnits=\"userSpaceOnUse\"\n >\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\n </marker>\n </defs>\n\n <!-- Row colour bands sit at the very bottom of the stack, so the weekend shading and the\n gridlines below still read through them. -->\n @for (vm of tintedRows(); track vm.flat.row.id) {\n <rect\n class=\"fly-gantt__row-band\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n [style.fill]=\"vm.tint\"\n />\n }\n\n <!-- Weekend shading + vertical gridlines (behind the bars). -->\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\n }\n\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\n @for (vm of rowVms(); track vm.flat.row.id) {\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\n <rect\n class=\"fly-gantt__row-hit\"\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n />\n\n @switch (vm.shape) {\n @case ('group') {\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\n }\n @case ('milestone') {\n <polygon\n class=\"fly-gantt__milestone\"\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\n [attr.points]=\"diamondPoints(vm)\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- A diamond has no width, so one connector serves it; it reads as the finish\n end, which is what makes a milestone\u2192task drag the FS everyone expects. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"milestoneConnectorX(vm)\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n }\n @case ('bar') {\n <g class=\"fly-gantt__bar-group\">\n <rect\n class=\"fly-gantt__bar\"\n [class.fly-gantt__bar--editable]=\"vm.editable\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.width\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (vm.progressWidth > 0) {\n <rect\n class=\"fly-gantt__progress\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.progressWidth\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n />\n }\n @if (vm.editable) {\n <!-- Resize handles at both edges. -->\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\n />\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\n />\n }\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- One connector per END. Dragging from the start edge yields an SS/SF link\n and from the finish edge an FS/FF one \u2014 the drop edge picks which. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.startX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle_start' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'start')\"\n />\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.endX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n </g>\n }\n }\n </g>\n }\n\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). Each carries a fat\n transparent twin so a 1.5px line is still clickable at pointer precision. -->\n @for (link of linkVms(); track link.key) {\n <g class=\"fly-gantt__link-group\" [class.fly-gantt__link-group--selected]=\"selectedLinkKey() === link.key\">\n <path\n class=\"fly-gantt__link-hit\"\n [attr.d]=\"link.path\"\n [attr.aria-label]=\"link.ariaLabel\"\n (click)=\"onLinkClick($event, link)\"\n />\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n @if (selectedLinkKey() === link.key && !readonly()) {\n <g\n class=\"fly-gantt__link-delete\"\n [attr.transform]=\"'translate(' + link.badgeX + ',' + link.badgeY + ')'\"\n [attr.aria-label]=\"'gantt.aria.delete_link' | translate\"\n (click)=\"deleteLink($event, link)\"\n >\n <circle class=\"fly-gantt__link-delete-bg\" r=\"8\" />\n <path class=\"fly-gantt__link-delete-x\" d=\"M-3.5,-3.5 L3.5,3.5 M3.5,-3.5 L-3.5,3.5\" />\n </g>\n }\n </g>\n }\n\n <!-- In-flight link rubber band. -->\n @if (linkGesturePath(); as gp) {\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n }\n\n <!-- Today line (over the bars). -->\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\n }\n </svg>\n </div>\n\n @if (visibleRows().length === 0) {\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\n }\n @if (overflowCount() > 0) {\n <p class=\"fly-gantt__overflow\">\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\n </p>\n }\n</div>\n\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\n@if (dragTooltip(); as tip) {\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\n}\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--bg-2, var(--surface-card, #fff));--_surface-alt: var(--bg-3, var(--glass-bg-elevated, #f8fafc));--_surface-hover: var(--bg-hover, var(--surface-hover, #f1f5f9));--_border: var(--w08, var(--surface-border, #e2e8f0));--_border-strong: var(--w1, var(--surface-border, #cbd5e1));--_grid-line: var(--line-3, var(--surface-border, #eef2f7));--_text: var(--ink, var(--text-color, #0f172a));--_text-subtle: var(--ink-3, var(--text-color-secondary, #64748b));--_text-faint: var(--ink-4, var(--text-color-secondary, #94a3b8));--_accent: var(--accent);--_bar: var(--_accent);--_bar-progress: color-mix(in srgb, var(--_accent) 72%, #000);--_weekend: color-mix(in srgb, var(--_text-subtle) 9%, transparent);--_today: var(--danger, #ef4444);--_link: var(--_text-subtle);--_radius: var(--r-lg, 8px);--_transition: var(--t-state, .12s ease);display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}:host(.fly-gantt--resizing){cursor:col-resize;-webkit-user-select:none;user-select:none}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);box-shadow:var(--shadow-card, none);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_accent);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border-strong)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border-strong)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__splitter{position:absolute;inset-block:0;inset-inline-end:-3px;inline-size:7px;z-index:5;cursor:col-resize;touch-action:none}.fly-gantt__splitter:after{content:\"\";position:absolute;inset-block:0;inset-inline-start:3px;inline-size:1px;background:transparent;transition:background var(--_transition)}.fly-gantt__splitter:hover:after,.fly-gantt__splitter:focus-visible:after{background:var(--_accent)}.fly-gantt__splitter:focus-visible{outline:2px solid var(--_accent);outline-offset:-1px}:host(.fly-gantt--resizing) .fly-gantt__splitter:after{background:var(--_accent)}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{position:relative;display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer;transition:background var(--_transition)}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_accent)}.fly-gantt__label-row--tinted:before{content:\"\";position:absolute;inset:0;background:var(--fly-gantt-row-tint);pointer-events:none}.fly-gantt__label-inner{position:relative;display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__label-swatch{flex:0 0 auto;inline-size:8px;block-size:8px;border-radius:2px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform var(--_transition)}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_accent);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__row-band{pointer-events:none}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_accent);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity var(--_transition)}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_accent);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link-hit{fill:none;stroke:transparent;stroke-width:11;pointer-events:stroke;cursor:pointer}.fly-gantt__link-group:hover .fly-gantt__link{stroke:var(--_text)}.fly-gantt__link-group--selected .fly-gantt__link{stroke:var(--_accent);stroke-width:2.5}.fly-gantt__link-delete-bg{fill:var(--_accent)}.fly-gantt__link-delete{cursor:pointer}.fly-gantt__link-delete-x{stroke:var(--_surface);stroke-width:1.75;stroke-linecap:round;fill:none;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-faint);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13245
13546
|
}
|
|
13246
13547
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyGanttComponent, decorators: [{
|
|
13247
13548
|
type: Component,
|
|
@@ -13249,20 +13550,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
13249
13550
|
class: 'fly-gantt',
|
|
13250
13551
|
'[class.fly-gantt--rtl]': 'rtl()',
|
|
13251
13552
|
'[class.fly-gantt--readonly]': 'readonly()',
|
|
13553
|
+
'[class.fly-gantt--resizing]': 'resizingLabels()',
|
|
13252
13554
|
'[attr.dir]': 'i18n.direction()',
|
|
13253
|
-
}, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\r\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\r\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\r\n<div\r\n class=\"fly-gantt__scroll\"\r\n role=\"grid\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\r\n [attr.aria-rowcount]=\"visibleRows().length\"\r\n (keydown)=\"onGridKeydown($event)\"\r\n>\r\n <div\r\n class=\"fly-gantt__canvas\"\r\n [style.grid-template-columns]=\"labelWidth() + 'px ' + innerWidth() + 'px'\"\r\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\r\n >\r\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\"></div>\r\n\r\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <svg\r\n class=\"fly-gantt__time-header\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"HEADER_H\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\r\n }\r\n @for (t of upperTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\r\n }\r\n </svg>\r\n\r\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"fly-gantt__labels\" role=\"rowgroup\">\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <div\r\n class=\"fly-gantt__label-row\"\r\n role=\"row\"\r\n tabindex=\"-1\"\r\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\r\n [style.height.px]=\"rowHeight()\"\r\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.aria-label]=\"vm.ariaLabel\"\r\n (click)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\r\n >\r\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\r\n @if (vm.flat.hasChildren) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-gantt__chevron\"\r\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\r\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\r\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\r\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\r\n >\u25B8</button>\r\n } @else {\r\n <span class=\"fly-gantt__chevron-spacer\"></span>\r\n }\r\n <span\r\n class=\"fly-gantt__label-text\"\r\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\r\n [title]=\"vm.flat.row.label\"\r\n >{{ vm.flat.row.label }}</span>\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <svg\r\n #bodySvg\r\n class=\"fly-gantt__body\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"bodyHeight()\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\r\n >\r\n <defs>\r\n <marker\r\n [attr.id]=\"markerId\"\r\n markerWidth=\"8\"\r\n markerHeight=\"8\"\r\n refX=\"6\"\r\n refY=\"4\"\r\n orient=\"auto\"\r\n markerUnits=\"userSpaceOnUse\"\r\n >\r\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\r\n </marker>\r\n </defs>\r\n\r\n <!-- Weekend shading + vertical gridlines (behind everything). -->\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n\r\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\r\n <rect\r\n class=\"fly-gantt__row-hit\"\r\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.x]=\"0\"\r\n [attr.y]=\"vm.y\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"rowHeight()\"\r\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n />\r\n\r\n @switch (vm.shape) {\r\n @case ('group') {\r\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\r\n }\r\n @case ('milestone') {\r\n <polygon\r\n class=\"fly-gantt__milestone\"\r\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\r\n [attr.points]=\"diamondPoints(vm)\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n }\r\n @case ('bar') {\r\n <g class=\"fly-gantt__bar-group\">\r\n <rect\r\n class=\"fly-gantt__bar\"\r\n [class.fly-gantt__bar--editable]=\"vm.editable\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.width\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n @if (vm.progressWidth > 0) {\r\n <rect\r\n class=\"fly-gantt__progress\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.progressWidth\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n />\r\n }\r\n @if (vm.editable) {\r\n <!-- Resize handles at both edges. -->\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\r\n />\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\r\n />\r\n }\r\n @if (!readonly() && !vm.flat.row.readonly) {\r\n <!-- Finish connector: drag to draw a dependency to another row. -->\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"vm.endX\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm)\"\r\n />\r\n }\r\n </g>\r\n }\r\n }\r\n </g>\r\n }\r\n\r\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). -->\r\n @for (link of linkVms(); track link.key) {\r\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n }\r\n\r\n <!-- In-flight link rubber band. -->\r\n @if (linkGesturePath(); as gp) {\r\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n }\r\n\r\n <!-- Today line (over the bars). -->\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n </svg>\r\n </div>\r\n\r\n @if (visibleRows().length === 0) {\r\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\r\n }\r\n @if (overflowCount() > 0) {\r\n <p class=\"fly-gantt__overflow\">\r\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\r\n </p>\r\n }\r\n</div>\r\n\r\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\r\n@if (dragTooltip(); as tip) {\r\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-alt: var(--glass-bg-elevated, #f8fafc);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--surface-border, #e2e8f0);--_grid-line: var(--surface-border, #eef2f7);--_text: var(--text-color, #0f172a);--_text-subtle: var(--text-color-secondary, #64748b);--_primary: var(--primary-color);--_primary-contrast: var(--primary-color-text, #fff);--_bar: var(--primary-color);--_bar-progress: var(--primary-600, #4f46e5);--_weekend: rgb(100 116 139 / 9%);--_today: var(--red-500, #ef4444);--_link: var(--text-color-secondary, #64748b);--_radius: 8px;display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_primary);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_primary)}.fly-gantt__label-inner{display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform .12s ease}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_primary);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_primary);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity .12s ease}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_primary);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-subtle);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"] }]
|
|
13254
|
-
}], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], dependencies: [{ type: i0.Input, args: [{ isSignal: true, alias: "dependencies", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], showToday: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToday", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], maxRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRows", required: false }] }], rowDatesChange: [{ type: i0.Output, args: ["rowDatesChange"] }], dependencyCreate: [{ type: i0.Output, args: ["dependencyCreate"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowDblClick: [{ type: i0.Output, args: ["rowDblClick"] }], bodyRef: [{ type: i0.ViewChild, args: ['bodySvg', { isSignal: true }] }] } });
|
|
13255
|
-
|
|
13256
|
-
/**
|
|
13257
|
-
* Public data contract for {@link FlyGanttComponent}.
|
|
13258
|
-
*
|
|
13259
|
-
* The types here are deliberately **app-agnostic** — no Projects/Tasks vocabulary leaks in.
|
|
13260
|
-
* A consumer maps its own domain rows to {@link GanttRow}[] + {@link GanttDependency}[],
|
|
13261
|
-
* feeds them in, and reacts to the emitted change events by persisting itself. The Gantt
|
|
13262
|
-
* never calls a backend and never mutates its inputs.
|
|
13263
|
-
*/
|
|
13264
|
-
/** Immutable list of the supported zoom levels, coarsest-last. */
|
|
13265
|
-
const GANTT_ZOOMS = ['day', 'week', 'month', 'quarter'];
|
|
13555
|
+
}, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\n<div\n class=\"fly-gantt__scroll\"\n role=\"grid\"\n tabindex=\"0\"\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\n [attr.aria-rowcount]=\"visibleRows().length\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div\n class=\"fly-gantt__canvas\"\n [style.grid-template-columns]=\"effectiveLabelWidth() + 'px ' + innerWidth() + 'px'\"\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\n >\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\">\n @if (resizableLabels()) {\n <!-- The divider lives in the corner because the corner is the one cell pinned on BOTH\n axes: the grip stays reachable however far the user has scrolled. The full-height\n strip down the label pane below is the same gesture, presentational only. -->\n <div\n class=\"fly-gantt__splitter\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"'gantt.aria.label_resize' | translate\"\n [attr.aria-valuenow]=\"effectiveLabelWidth()\"\n [attr.aria-valuemin]=\"MIN_LABEL_W\"\n [attr.aria-valuemax]=\"MAX_LABEL_W\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (keydown)=\"onLabelResizeKeydown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n class=\"fly-gantt__time-header\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"HEADER_H\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\n aria-hidden=\"true\"\n >\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\n }\n @for (t of upperTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\n }\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\n }\n </svg>\n\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__labels\" role=\"rowgroup\">\n @for (vm of rowVms(); track vm.flat.row.id) {\n <div\n class=\"fly-gantt__label-row\"\n role=\"row\"\n tabindex=\"-1\"\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\n [class.fly-gantt__label-row--tinted]=\"vm.tint !== null\"\n [style.height.px]=\"rowHeight()\"\n [style.--fly-gantt-row-tint]=\"vm.tint\"\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\n [attr.aria-label]=\"vm.ariaLabel\"\n (click)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\n >\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\n @if (vm.flat.hasChildren) {\n <button\n type=\"button\"\n class=\"fly-gantt__chevron\"\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\n >\u25B8</button>\n } @else {\n <span class=\"fly-gantt__chevron-spacer\"></span>\n }\n @if (vm.color) {\n <!-- Colour chip: the row's own colour at full strength, so a milestone stays\n identifiable in the label pane where the band tint is deliberately faint. -->\n <span class=\"fly-gantt__label-swatch\" [style.background]=\"vm.color\" aria-hidden=\"true\"></span>\n }\n <span\n class=\"fly-gantt__label-text\"\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\n [title]=\"vm.flat.row.label\"\n >{{ vm.flat.row.label }}</span>\n </span>\n </div>\n }\n @if (resizableLabels()) {\n <!-- aria-hidden so this presentational twin of the corner separator is not a second\n (and structurally invalid) child of the rowgroup in the accessibility tree. -->\n <div\n class=\"fly-gantt__splitter fly-gantt__splitter--rail\"\n aria-hidden=\"true\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n #bodySvg\n class=\"fly-gantt__body\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"bodyHeight()\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\n >\n <defs>\n <marker\n [attr.id]=\"markerId\"\n markerWidth=\"8\"\n markerHeight=\"8\"\n refX=\"6\"\n refY=\"4\"\n orient=\"auto\"\n markerUnits=\"userSpaceOnUse\"\n >\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\n </marker>\n </defs>\n\n <!-- Row colour bands sit at the very bottom of the stack, so the weekend shading and the\n gridlines below still read through them. -->\n @for (vm of tintedRows(); track vm.flat.row.id) {\n <rect\n class=\"fly-gantt__row-band\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n [style.fill]=\"vm.tint\"\n />\n }\n\n <!-- Weekend shading + vertical gridlines (behind the bars). -->\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\n }\n\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\n @for (vm of rowVms(); track vm.flat.row.id) {\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\n <rect\n class=\"fly-gantt__row-hit\"\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n />\n\n @switch (vm.shape) {\n @case ('group') {\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\n }\n @case ('milestone') {\n <polygon\n class=\"fly-gantt__milestone\"\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\n [attr.points]=\"diamondPoints(vm)\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- A diamond has no width, so one connector serves it; it reads as the finish\n end, which is what makes a milestone\u2192task drag the FS everyone expects. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"milestoneConnectorX(vm)\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n }\n @case ('bar') {\n <g class=\"fly-gantt__bar-group\">\n <rect\n class=\"fly-gantt__bar\"\n [class.fly-gantt__bar--editable]=\"vm.editable\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.width\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (vm.progressWidth > 0) {\n <rect\n class=\"fly-gantt__progress\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.progressWidth\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n />\n }\n @if (vm.editable) {\n <!-- Resize handles at both edges. -->\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\n />\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\n />\n }\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- One connector per END. Dragging from the start edge yields an SS/SF link\n and from the finish edge an FS/FF one \u2014 the drop edge picks which. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.startX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle_start' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'start')\"\n />\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.endX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n </g>\n }\n }\n </g>\n }\n\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). Each carries a fat\n transparent twin so a 1.5px line is still clickable at pointer precision. -->\n @for (link of linkVms(); track link.key) {\n <g class=\"fly-gantt__link-group\" [class.fly-gantt__link-group--selected]=\"selectedLinkKey() === link.key\">\n <path\n class=\"fly-gantt__link-hit\"\n [attr.d]=\"link.path\"\n [attr.aria-label]=\"link.ariaLabel\"\n (click)=\"onLinkClick($event, link)\"\n />\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n @if (selectedLinkKey() === link.key && !readonly()) {\n <g\n class=\"fly-gantt__link-delete\"\n [attr.transform]=\"'translate(' + link.badgeX + ',' + link.badgeY + ')'\"\n [attr.aria-label]=\"'gantt.aria.delete_link' | translate\"\n (click)=\"deleteLink($event, link)\"\n >\n <circle class=\"fly-gantt__link-delete-bg\" r=\"8\" />\n <path class=\"fly-gantt__link-delete-x\" d=\"M-3.5,-3.5 L3.5,3.5 M3.5,-3.5 L-3.5,3.5\" />\n </g>\n }\n </g>\n }\n\n <!-- In-flight link rubber band. -->\n @if (linkGesturePath(); as gp) {\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n }\n\n <!-- Today line (over the bars). -->\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\n }\n </svg>\n </div>\n\n @if (visibleRows().length === 0) {\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\n }\n @if (overflowCount() > 0) {\n <p class=\"fly-gantt__overflow\">\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\n </p>\n }\n</div>\n\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\n@if (dragTooltip(); as tip) {\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\n}\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--bg-2, var(--surface-card, #fff));--_surface-alt: var(--bg-3, var(--glass-bg-elevated, #f8fafc));--_surface-hover: var(--bg-hover, var(--surface-hover, #f1f5f9));--_border: var(--w08, var(--surface-border, #e2e8f0));--_border-strong: var(--w1, var(--surface-border, #cbd5e1));--_grid-line: var(--line-3, var(--surface-border, #eef2f7));--_text: var(--ink, var(--text-color, #0f172a));--_text-subtle: var(--ink-3, var(--text-color-secondary, #64748b));--_text-faint: var(--ink-4, var(--text-color-secondary, #94a3b8));--_accent: var(--accent);--_bar: var(--_accent);--_bar-progress: color-mix(in srgb, var(--_accent) 72%, #000);--_weekend: color-mix(in srgb, var(--_text-subtle) 9%, transparent);--_today: var(--danger, #ef4444);--_link: var(--_text-subtle);--_radius: var(--r-lg, 8px);--_transition: var(--t-state, .12s ease);display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}:host(.fly-gantt--resizing){cursor:col-resize;-webkit-user-select:none;user-select:none}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);box-shadow:var(--shadow-card, none);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_accent);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border-strong)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border-strong)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__splitter{position:absolute;inset-block:0;inset-inline-end:-3px;inline-size:7px;z-index:5;cursor:col-resize;touch-action:none}.fly-gantt__splitter:after{content:\"\";position:absolute;inset-block:0;inset-inline-start:3px;inline-size:1px;background:transparent;transition:background var(--_transition)}.fly-gantt__splitter:hover:after,.fly-gantt__splitter:focus-visible:after{background:var(--_accent)}.fly-gantt__splitter:focus-visible{outline:2px solid var(--_accent);outline-offset:-1px}:host(.fly-gantt--resizing) .fly-gantt__splitter:after{background:var(--_accent)}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{position:relative;display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer;transition:background var(--_transition)}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_accent)}.fly-gantt__label-row--tinted:before{content:\"\";position:absolute;inset:0;background:var(--fly-gantt-row-tint);pointer-events:none}.fly-gantt__label-inner{position:relative;display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__label-swatch{flex:0 0 auto;inline-size:8px;block-size:8px;border-radius:2px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform var(--_transition)}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_accent);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__row-band{pointer-events:none}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_accent);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity var(--_transition)}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_accent);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link-hit{fill:none;stroke:transparent;stroke-width:11;pointer-events:stroke;cursor:pointer}.fly-gantt__link-group:hover .fly-gantt__link{stroke:var(--_text)}.fly-gantt__link-group--selected .fly-gantt__link{stroke:var(--_accent);stroke-width:2.5}.fly-gantt__link-delete-bg{fill:var(--_accent)}.fly-gantt__link-delete{cursor:pointer}.fly-gantt__link-delete-x{stroke:var(--_surface);stroke-width:1.75;stroke-linecap:round;fill:none;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-faint);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"] }]
|
|
13556
|
+
}], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], dependencies: [{ type: i0.Input, args: [{ isSignal: true, alias: "dependencies", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], showToday: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToday", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], resizableLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableLabels", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], maxRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRows", required: false }] }], rowDatesChange: [{ type: i0.Output, args: ["rowDatesChange"] }], dependencyCreate: [{ type: i0.Output, args: ["dependencyCreate"] }], dependencyDelete: [{ type: i0.Output, args: ["dependencyDelete"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowDblClick: [{ type: i0.Output, args: ["rowDblClick"] }], labelWidthChange: [{ type: i0.Output, args: ["labelWidthChange"] }], bodyRef: [{ type: i0.ViewChild, args: ['bodySvg', { isSignal: true }] }] } });
|
|
13266
13557
|
|
|
13267
13558
|
// ── Bundled ISO 3166-1 country list ──────────────────────────────────────────
|
|
13268
13559
|
// Ships with <fly-survey-form> so a `Country` question renders a searchable
|
|
@@ -17032,13 +17323,13 @@ class FlyMagicActionsComponent {
|
|
|
17032
17323
|
this.isOverflowing.set(!!el && el.scrollWidth - el.clientWidth > 1);
|
|
17033
17324
|
}
|
|
17034
17325
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyMagicActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
17035
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyMagicActionsComponent, isStandalone: true, selector: "fly-magic-actions", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: true, transformFunction: null }, primary: { classPropertyName: "primary", publicName: "primary", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.dir": "i18n.direction()" } }, viewQueries: [{ propertyName: "rowRef", first: true, predicate: ["row"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\n 'text'` actions rely on their own visible text instead. Disabled rows stay\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\n tooltip, which native `disabled` would suppress.\n\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\n so there is nothing left to sanitize.\n\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\n contributed action is painted (group items and `primary`). It used to be\n inline in the group loop, which was fine while this component rendered groups\n only; with `primary` here too, a second copy would mean the next edit to the\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\n-->\n<!--\n `data-action` carries the publisher's action `id` verbatim \u2014 the one stable, LOCALE-INDEPENDENT\n handle on a rendered action. Every other identifying attribute here is translated\n (`aria-label`, the tooltip), so an e2e suite that targeted them had to assert in one language,\n and one that targeted a bespoke button lost its hook entirely when the action moved into the\n bar. That is not hypothetical: PPM's project-closure suite could not adopt the contract for\n exactly this reason. It is an ATTRIBUTE, not a class, so it never participates in styling, and\n `id` is already unique across the whole contribution (the projection enforces it), so it is\n unique in the DOM too.\n-->\n<ng-template #actionButton let-projected>\n <!--\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\n context, and there is no built-in way to declare one. `asAction` is an\n identity function whose only job is to give this block a TYPED local, so\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\n compiler instead of silently resolving to `undefined` on a typo. Without it\n the one place a `MagicBarActionView` is actually painted would be the one\n place its shape is not verified.\n -->\n @let action = asAction(projected);\n @if (action.kind === 'text') {\n <button\n type=\"button\"\n class=\"fma-btn fma-btn--text\"\n [attr.data-action]=\"action.id\"\n [attr.data-tone]=\"toneAttr(action)\"\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\n [attr.aria-pressed]=\"pressedAttr(action)\"\n [flyTooltip]=\"tooltipFor(action)\"\n [flyTooltipDelay]=\"450\"\n (click)=\"activate(action, $event)\"\n >{{ resolvedLabel(action) }}</button>\n } @else {\n <button\n type=\"button\"\n class=\"fma-btn\"\n [class.fma-btn--badge]=\"hasBadge(action)\"\n [attr.data-action]=\"action.id\"\n [attr.data-tone]=\"toneAttr(action)\"\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\n [attr.aria-pressed]=\"pressedAttr(action)\"\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\n [flyTooltip]=\"tooltipFor(action)\"\n [flyTooltipDelay]=\"450\"\n (click)=\"activate(action, $event)\"\n >\n <svg\n class=\"fma-btn__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @for (node of iconNodes(action); track $index) {\n @switch (node.tag) {\n @case ('path') {\n <svg:path\n [attr.d]=\"node.attrs['d']\"\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\n />\n }\n @case ('circle') {\n <svg:circle\n [attr.cx]=\"node.attrs['cx']\"\n [attr.cy]=\"node.attrs['cy']\"\n [attr.r]=\"node.attrs['r']\"\n />\n }\n @case ('rect') {\n <svg:rect\n [attr.x]=\"node.attrs['x']\"\n [attr.y]=\"node.attrs['y']\"\n [attr.width]=\"node.attrs['width']\"\n [attr.height]=\"node.attrs['height']\"\n [attr.rx]=\"node.attrs['rx']\"\n [attr.ry]=\"node.attrs['ry']\"\n />\n }\n @case ('ellipse') {\n <svg:ellipse\n [attr.cx]=\"node.attrs['cx']\"\n [attr.cy]=\"node.attrs['cy']\"\n [attr.rx]=\"node.attrs['rx']\"\n [attr.ry]=\"node.attrs['ry']\"\n />\n }\n @case ('line') {\n <svg:line\n [attr.x1]=\"node.attrs['x1']\"\n [attr.y1]=\"node.attrs['y1']\"\n [attr.x2]=\"node.attrs['x2']\"\n [attr.y2]=\"node.attrs['y2']\"\n />\n }\n @case ('polyline') {\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\n }\n @case ('polygon') {\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\n }\n }\n }\n </svg>\n @if (hasBadge(action)) {\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\n }\n </button>\n }\n</ng-template>\n\n<!--\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\n because it is the row's anchor \u2014 a call to action that dissolves into a\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\n It is also what makes `isOverflowing` measurable at all: see the component.\n-->\n@if (groups().length) {\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\n @for (group of groups(); track group.id) {\n <div\n class=\"fma-group\"\n [class.fma-group--framed]=\"group.framed\"\n role=\"group\"\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\n >\n @for (action of group.items; track action.id) {\n <ng-container\n [ngTemplateOutlet]=\"actionButton\"\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\n />\n }\n </div>\n @if (group.framed) {\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\n }\n }\n </div>\n}\n\n<!--\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\n button itself would simply never render. The wrapper owns the disc, the lift\n and the shadow; the button stays transparent and keeps the DS's own toolbar\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\n descendant selector so the glyph colour inherits the way custom properties do.\n-->\n@if (primary(); as p) {\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\n <ng-container\n [ngTemplateOutlet]=\"actionButton\"\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\n />\n </span>\n}\n\n<!--\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\n it beside its button would insert a non-`.fma-btn` element into the group,\n silently shifting every later button's `:nth-child` index and with it the\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\n staggered parent's children homogeneous is what makes that rule's comment true.\n It portals itself to <body>, so its position is unaffected by where it is\n declared.\n-->\n@if (openAction(); as open) {\n <fly-context-menu\n [anchor]=\"menuAnchorEl()\"\n [sections]=\"menuSections(open)\"\n (action)=\"onMenuAction(open, $event)\"\n (closed)=\"closeMenu()\"\n />\n}\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn{inline-size:44px;block-size:44px}.fma-btn--text{inline-size:auto;min-inline-size:44px}.fma-primary{inline-size:44px;block-size:44px}}\n"], dependencies: [{ kind: "component", type: ContextMenuComponent, selector: "fly-context-menu", inputs: ["x", "y", "anchor", "align", "placement", "sections", "boundary"], outputs: ["action", "closed"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
17326
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyMagicActionsComponent, isStandalone: true, selector: "fly-magic-actions", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: true, transformFunction: null }, primary: { classPropertyName: "primary", publicName: "primary", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.dir": "i18n.direction()" } }, viewQueries: [{ propertyName: "rowRef", first: true, predicate: ["row"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\r\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\r\n 'text'` actions rely on their own visible text instead. Disabled rows stay\r\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\r\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\r\n tooltip, which native `disabled` would suppress.\r\n\r\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\r\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\r\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\r\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\r\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\r\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\r\n so there is nothing left to sanitize.\r\n\r\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\r\n contributed action is painted (group items and `primary`). It used to be\r\n inline in the group loop, which was fine while this component rendered groups\r\n only; with `primary` here too, a second copy would mean the next edit to the\r\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\r\n-->\r\n<!--\r\n `data-action` carries the publisher's action `id` verbatim \u2014 the one stable, LOCALE-INDEPENDENT\r\n handle on a rendered action. Every other identifying attribute here is translated\r\n (`aria-label`, the tooltip), so an e2e suite that targeted them had to assert in one language,\r\n and one that targeted a bespoke button lost its hook entirely when the action moved into the\r\n bar. That is not hypothetical: PPM's project-closure suite could not adopt the contract for\r\n exactly this reason. It is an ATTRIBUTE, not a class, so it never participates in styling, and\r\n `id` is already unique across the whole contribution (the projection enforces it), so it is\r\n unique in the DOM too.\r\n-->\r\n<ng-template #actionButton let-projected>\r\n <!--\r\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\r\n context, and there is no built-in way to declare one. `asAction` is an\r\n identity function whose only job is to give this block a TYPED local, so\r\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\r\n compiler instead of silently resolving to `undefined` on a typo. Without it\r\n the one place a `MagicBarActionView` is actually painted would be the one\r\n place its shape is not verified.\r\n -->\r\n @let action = asAction(projected);\r\n @if (action.kind === 'text') {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn fma-btn--text\"\r\n [attr.data-action]=\"action.id\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >{{ resolvedLabel(action) }}</button>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn\"\r\n [class.fma-btn--badge]=\"hasBadge(action)\"\r\n [attr.data-action]=\"action.id\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\r\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\r\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >\r\n <svg\r\n class=\"fma-btn__icon\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (node of iconNodes(action); track $index) {\r\n @switch (node.tag) {\r\n @case ('path') {\r\n <svg:path\r\n [attr.d]=\"node.attrs['d']\"\r\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\r\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\r\n />\r\n }\r\n @case ('circle') {\r\n <svg:circle\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.r]=\"node.attrs['r']\"\r\n />\r\n }\r\n @case ('rect') {\r\n <svg:rect\r\n [attr.x]=\"node.attrs['x']\"\r\n [attr.y]=\"node.attrs['y']\"\r\n [attr.width]=\"node.attrs['width']\"\r\n [attr.height]=\"node.attrs['height']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('ellipse') {\r\n <svg:ellipse\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('line') {\r\n <svg:line\r\n [attr.x1]=\"node.attrs['x1']\"\r\n [attr.y1]=\"node.attrs['y1']\"\r\n [attr.x2]=\"node.attrs['x2']\"\r\n [attr.y2]=\"node.attrs['y2']\"\r\n />\r\n }\r\n @case ('polyline') {\r\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\r\n }\r\n @case ('polygon') {\r\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\r\n }\r\n }\r\n }\r\n </svg>\r\n @if (hasBadge(action)) {\r\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\r\n }\r\n </button>\r\n }\r\n</ng-template>\r\n\r\n<!--\r\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\r\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\r\n because it is the row's anchor \u2014 a call to action that dissolves into a\r\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\r\n It is also what makes `isOverflowing` measurable at all: see the component.\r\n-->\r\n@if (groups().length) {\r\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\r\n @for (group of groups(); track group.id) {\r\n <div\r\n class=\"fma-group\"\r\n [class.fma-group--framed]=\"group.framed\"\r\n role=\"group\"\r\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\r\n >\r\n @for (action of group.items; track action.id) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\r\n />\r\n }\r\n </div>\r\n @if (group.framed) {\r\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<!--\r\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\r\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\r\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\r\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\r\n button itself would simply never render. The wrapper owns the disc, the lift\r\n and the shadow; the button stays transparent and keeps the DS's own toolbar\r\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\r\n descendant selector so the glyph colour inherits the way custom properties do.\r\n-->\r\n@if (primary(); as p) {\r\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\r\n />\r\n </span>\r\n}\r\n\r\n<!--\r\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\r\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\r\n it beside its button would insert a non-`.fma-btn` element into the group,\r\n silently shifting every later button's `:nth-child` index and with it the\r\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\r\n staggered parent's children homogeneous is what makes that rule's comment true.\r\n It portals itself to <body>, so its position is unaffected by where it is\r\n declared.\r\n-->\r\n@if (openAction(); as open) {\r\n <fly-context-menu\r\n [anchor]=\"menuAnchorEl()\"\r\n [sections]=\"menuSections(open)\"\r\n (action)=\"onMenuAction(open, $event)\"\r\n (closed)=\"closeMenu()\"\r\n />\r\n}\r\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn{inline-size:44px;block-size:44px}.fma-btn--text{inline-size:auto;min-inline-size:44px}.fma-primary{inline-size:44px;block-size:44px}}\n"], dependencies: [{ kind: "component", type: ContextMenuComponent, selector: "fly-context-menu", inputs: ["x", "y", "anchor", "align", "placement", "sections", "boundary"], outputs: ["action", "closed"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
17036
17327
|
}
|
|
17037
17328
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyMagicActionsComponent, decorators: [{
|
|
17038
17329
|
type: Component,
|
|
17039
17330
|
args: [{ selector: 'fly-magic-actions', standalone: true, imports: [ContextMenuComponent, FlyTooltipDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
17040
17331
|
'[attr.dir]': 'i18n.direction()',
|
|
17041
|
-
}, template: "<!--\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\n 'text'` actions rely on their own visible text instead. Disabled rows stay\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\n tooltip, which native `disabled` would suppress.\n\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\n so there is nothing left to sanitize.\n\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\n contributed action is painted (group items and `primary`). It used to be\n inline in the group loop, which was fine while this component rendered groups\n only; with `primary` here too, a second copy would mean the next edit to the\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\n-->\n<!--\n `data-action` carries the publisher's action `id` verbatim \u2014 the one stable, LOCALE-INDEPENDENT\n handle on a rendered action. Every other identifying attribute here is translated\n (`aria-label`, the tooltip), so an e2e suite that targeted them had to assert in one language,\n and one that targeted a bespoke button lost its hook entirely when the action moved into the\n bar. That is not hypothetical: PPM's project-closure suite could not adopt the contract for\n exactly this reason. It is an ATTRIBUTE, not a class, so it never participates in styling, and\n `id` is already unique across the whole contribution (the projection enforces it), so it is\n unique in the DOM too.\n-->\n<ng-template #actionButton let-projected>\n <!--\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\n context, and there is no built-in way to declare one. `asAction` is an\n identity function whose only job is to give this block a TYPED local, so\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\n compiler instead of silently resolving to `undefined` on a typo. Without it\n the one place a `MagicBarActionView` is actually painted would be the one\n place its shape is not verified.\n -->\n @let action = asAction(projected);\n @if (action.kind === 'text') {\n <button\n type=\"button\"\n class=\"fma-btn fma-btn--text\"\n [attr.data-action]=\"action.id\"\n [attr.data-tone]=\"toneAttr(action)\"\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\n [attr.aria-pressed]=\"pressedAttr(action)\"\n [flyTooltip]=\"tooltipFor(action)\"\n [flyTooltipDelay]=\"450\"\n (click)=\"activate(action, $event)\"\n >{{ resolvedLabel(action) }}</button>\n } @else {\n <button\n type=\"button\"\n class=\"fma-btn\"\n [class.fma-btn--badge]=\"hasBadge(action)\"\n [attr.data-action]=\"action.id\"\n [attr.data-tone]=\"toneAttr(action)\"\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\n [attr.aria-pressed]=\"pressedAttr(action)\"\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\n [flyTooltip]=\"tooltipFor(action)\"\n [flyTooltipDelay]=\"450\"\n (click)=\"activate(action, $event)\"\n >\n <svg\n class=\"fma-btn__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @for (node of iconNodes(action); track $index) {\n @switch (node.tag) {\n @case ('path') {\n <svg:path\n [attr.d]=\"node.attrs['d']\"\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\n />\n }\n @case ('circle') {\n <svg:circle\n [attr.cx]=\"node.attrs['cx']\"\n [attr.cy]=\"node.attrs['cy']\"\n [attr.r]=\"node.attrs['r']\"\n />\n }\n @case ('rect') {\n <svg:rect\n [attr.x]=\"node.attrs['x']\"\n [attr.y]=\"node.attrs['y']\"\n [attr.width]=\"node.attrs['width']\"\n [attr.height]=\"node.attrs['height']\"\n [attr.rx]=\"node.attrs['rx']\"\n [attr.ry]=\"node.attrs['ry']\"\n />\n }\n @case ('ellipse') {\n <svg:ellipse\n [attr.cx]=\"node.attrs['cx']\"\n [attr.cy]=\"node.attrs['cy']\"\n [attr.rx]=\"node.attrs['rx']\"\n [attr.ry]=\"node.attrs['ry']\"\n />\n }\n @case ('line') {\n <svg:line\n [attr.x1]=\"node.attrs['x1']\"\n [attr.y1]=\"node.attrs['y1']\"\n [attr.x2]=\"node.attrs['x2']\"\n [attr.y2]=\"node.attrs['y2']\"\n />\n }\n @case ('polyline') {\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\n }\n @case ('polygon') {\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\n }\n }\n }\n </svg>\n @if (hasBadge(action)) {\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\n }\n </button>\n }\n</ng-template>\n\n<!--\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\n because it is the row's anchor \u2014 a call to action that dissolves into a\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\n It is also what makes `isOverflowing` measurable at all: see the component.\n-->\n@if (groups().length) {\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\n @for (group of groups(); track group.id) {\n <div\n class=\"fma-group\"\n [class.fma-group--framed]=\"group.framed\"\n role=\"group\"\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\n >\n @for (action of group.items; track action.id) {\n <ng-container\n [ngTemplateOutlet]=\"actionButton\"\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\n />\n }\n </div>\n @if (group.framed) {\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\n }\n }\n </div>\n}\n\n<!--\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\n button itself would simply never render. The wrapper owns the disc, the lift\n and the shadow; the button stays transparent and keeps the DS's own toolbar\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\n descendant selector so the glyph colour inherits the way custom properties do.\n-->\n@if (primary(); as p) {\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\n <ng-container\n [ngTemplateOutlet]=\"actionButton\"\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\n />\n </span>\n}\n\n<!--\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\n it beside its button would insert a non-`.fma-btn` element into the group,\n silently shifting every later button's `:nth-child` index and with it the\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\n staggered parent's children homogeneous is what makes that rule's comment true.\n It portals itself to <body>, so its position is unaffected by where it is\n declared.\n-->\n@if (openAction(); as open) {\n <fly-context-menu\n [anchor]=\"menuAnchorEl()\"\n [sections]=\"menuSections(open)\"\n (action)=\"onMenuAction(open, $event)\"\n (closed)=\"closeMenu()\"\n />\n}\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn{inline-size:44px;block-size:44px}.fma-btn--text{inline-size:auto;min-inline-size:44px}.fma-primary{inline-size:44px;block-size:44px}}\n"] }]
|
|
17332
|
+
}, template: "<!--\r\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\r\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\r\n 'text'` actions rely on their own visible text instead. Disabled rows stay\r\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\r\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\r\n tooltip, which native `disabled` would suppress.\r\n\r\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\r\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\r\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\r\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\r\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\r\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\r\n so there is nothing left to sanitize.\r\n\r\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\r\n contributed action is painted (group items and `primary`). It used to be\r\n inline in the group loop, which was fine while this component rendered groups\r\n only; with `primary` here too, a second copy would mean the next edit to the\r\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\r\n-->\r\n<!--\r\n `data-action` carries the publisher's action `id` verbatim \u2014 the one stable, LOCALE-INDEPENDENT\r\n handle on a rendered action. Every other identifying attribute here is translated\r\n (`aria-label`, the tooltip), so an e2e suite that targeted them had to assert in one language,\r\n and one that targeted a bespoke button lost its hook entirely when the action moved into the\r\n bar. That is not hypothetical: PPM's project-closure suite could not adopt the contract for\r\n exactly this reason. It is an ATTRIBUTE, not a class, so it never participates in styling, and\r\n `id` is already unique across the whole contribution (the projection enforces it), so it is\r\n unique in the DOM too.\r\n-->\r\n<ng-template #actionButton let-projected>\r\n <!--\r\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\r\n context, and there is no built-in way to declare one. `asAction` is an\r\n identity function whose only job is to give this block a TYPED local, so\r\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\r\n compiler instead of silently resolving to `undefined` on a typo. Without it\r\n the one place a `MagicBarActionView` is actually painted would be the one\r\n place its shape is not verified.\r\n -->\r\n @let action = asAction(projected);\r\n @if (action.kind === 'text') {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn fma-btn--text\"\r\n [attr.data-action]=\"action.id\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >{{ resolvedLabel(action) }}</button>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn\"\r\n [class.fma-btn--badge]=\"hasBadge(action)\"\r\n [attr.data-action]=\"action.id\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\r\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\r\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >\r\n <svg\r\n class=\"fma-btn__icon\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (node of iconNodes(action); track $index) {\r\n @switch (node.tag) {\r\n @case ('path') {\r\n <svg:path\r\n [attr.d]=\"node.attrs['d']\"\r\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\r\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\r\n />\r\n }\r\n @case ('circle') {\r\n <svg:circle\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.r]=\"node.attrs['r']\"\r\n />\r\n }\r\n @case ('rect') {\r\n <svg:rect\r\n [attr.x]=\"node.attrs['x']\"\r\n [attr.y]=\"node.attrs['y']\"\r\n [attr.width]=\"node.attrs['width']\"\r\n [attr.height]=\"node.attrs['height']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('ellipse') {\r\n <svg:ellipse\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('line') {\r\n <svg:line\r\n [attr.x1]=\"node.attrs['x1']\"\r\n [attr.y1]=\"node.attrs['y1']\"\r\n [attr.x2]=\"node.attrs['x2']\"\r\n [attr.y2]=\"node.attrs['y2']\"\r\n />\r\n }\r\n @case ('polyline') {\r\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\r\n }\r\n @case ('polygon') {\r\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\r\n }\r\n }\r\n }\r\n </svg>\r\n @if (hasBadge(action)) {\r\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\r\n }\r\n </button>\r\n }\r\n</ng-template>\r\n\r\n<!--\r\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\r\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\r\n because it is the row's anchor \u2014 a call to action that dissolves into a\r\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\r\n It is also what makes `isOverflowing` measurable at all: see the component.\r\n-->\r\n@if (groups().length) {\r\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\r\n @for (group of groups(); track group.id) {\r\n <div\r\n class=\"fma-group\"\r\n [class.fma-group--framed]=\"group.framed\"\r\n role=\"group\"\r\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\r\n >\r\n @for (action of group.items; track action.id) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\r\n />\r\n }\r\n </div>\r\n @if (group.framed) {\r\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<!--\r\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\r\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\r\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\r\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\r\n button itself would simply never render. The wrapper owns the disc, the lift\r\n and the shadow; the button stays transparent and keeps the DS's own toolbar\r\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\r\n descendant selector so the glyph colour inherits the way custom properties do.\r\n-->\r\n@if (primary(); as p) {\r\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\r\n />\r\n </span>\r\n}\r\n\r\n<!--\r\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\r\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\r\n it beside its button would insert a non-`.fma-btn` element into the group,\r\n silently shifting every later button's `:nth-child` index and with it the\r\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\r\n staggered parent's children homogeneous is what makes that rule's comment true.\r\n It portals itself to <body>, so its position is unaffected by where it is\r\n declared.\r\n-->\r\n@if (openAction(); as open) {\r\n <fly-context-menu\r\n [anchor]=\"menuAnchorEl()\"\r\n [sections]=\"menuSections(open)\"\r\n (action)=\"onMenuAction(open, $event)\"\r\n (closed)=\"closeMenu()\"\r\n />\r\n}\r\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn{inline-size:44px;block-size:44px}.fma-btn--text{inline-size:auto;min-inline-size:44px}.fma-primary{inline-size:44px;block-size:44px}}\n"] }]
|
|
17042
17333
|
}], ctorParameters: () => [], propDecorators: { groups: [{ type: i0.Input, args: [{ isSignal: true, alias: "groups", required: true }] }], primary: [{ type: i0.Input, args: [{ isSignal: true, alias: "primary", required: false }] }], rowRef: [{ type: i0.ViewChild, args: ['row', { isSignal: true }] }] } });
|
|
17043
17334
|
|
|
17044
17335
|
/**
|
|
@@ -25502,5 +25793,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
25502
25793
|
* Generated bundle index. Do not edit.
|
|
25503
25794
|
*/
|
|
25504
25795
|
|
|
25505
|
-
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapLenient, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
|
|
25796
|
+
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapLenient, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
|
|
25506
25797
|
//# sourceMappingURL=flyos-design-system.mjs.map
|