@flyos/design-system 3.0.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
|
|
@@ -1323,6 +1323,15 @@ const DS_BASELINE_LOCALES = {
|
|
|
1323
1323
|
'app_unavailable.unreachable.title': 'App unavailable',
|
|
1324
1324
|
'app_unavailable.unreachable.message': "{{app}} can't be reached right now. Nothing was changed — please try again in a moment.",
|
|
1325
1325
|
'common.action.cancel': 'Cancel',
|
|
1326
|
+
'common.action.close': 'Close',
|
|
1327
|
+
'common.abort': 'Abort',
|
|
1328
|
+
'common.ignore': 'Ignore',
|
|
1329
|
+
'common.retry': 'Retry',
|
|
1330
|
+
'common.loading': 'Loading…',
|
|
1331
|
+
'common.remove': 'Remove',
|
|
1332
|
+
'common.yes': 'Yes',
|
|
1333
|
+
'common.no': 'No',
|
|
1334
|
+
'common.ok': 'OK',
|
|
1326
1335
|
'common.label.apply': 'Apply',
|
|
1327
1336
|
'common.label.formatting_toolbar': 'Formatting toolbar',
|
|
1328
1337
|
'common.label.https': 'https://…',
|
|
@@ -1423,7 +1432,7 @@ const DS_BASELINE_LOCALES = {
|
|
|
1423
1432
|
'search.placeholder': 'Search…',
|
|
1424
1433
|
'search.clear': 'Clear search',
|
|
1425
1434
|
'search.open': 'Search',
|
|
1426
|
-
'search.filters':
|
|
1435
|
+
'search.filters': 'Filters',
|
|
1427
1436
|
// skeleton (fly-skeleton)
|
|
1428
1437
|
'skeleton.loading': 'Loading…',
|
|
1429
1438
|
// typeahead (fly-typeahead)
|
|
@@ -1489,6 +1498,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
1489
1498
|
'gantt.aria.resize_start': 'Resize start date',
|
|
1490
1499
|
'gantt.aria.resize_end': 'Resize end date',
|
|
1491
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',
|
|
1492
1509
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
1493
1510
|
'form.required_error': 'This field is required.',
|
|
1494
1511
|
'form.yes': 'Yes',
|
|
@@ -1697,6 +1714,15 @@ const DS_BASELINE_LOCALES = {
|
|
|
1697
1714
|
'app_unavailable.unreachable.title': 'التطبيق غير متاح',
|
|
1698
1715
|
'app_unavailable.unreachable.message': 'تعذّر الوصول إلى {{app}} حاليًا. لم يتم تغيير أي شيء — يرجى المحاولة بعد قليل.',
|
|
1699
1716
|
'common.action.cancel': 'إلغاء',
|
|
1717
|
+
'common.action.close': 'إغلاق',
|
|
1718
|
+
'common.abort': 'إلغاء العملية',
|
|
1719
|
+
'common.ignore': 'تجاهل',
|
|
1720
|
+
'common.retry': 'إعادة المحاولة',
|
|
1721
|
+
'common.loading': 'جارٍ التحميل…',
|
|
1722
|
+
'common.remove': 'إزالة',
|
|
1723
|
+
'common.yes': 'نعم',
|
|
1724
|
+
'common.no': 'لا',
|
|
1725
|
+
'common.ok': 'موافق',
|
|
1700
1726
|
'common.label.apply': 'تطبيق',
|
|
1701
1727
|
'common.label.formatting_toolbar': 'شريط أدوات التنسيق',
|
|
1702
1728
|
'common.label.https': 'https://…',
|
|
@@ -1796,7 +1822,7 @@ const DS_BASELINE_LOCALES = {
|
|
|
1796
1822
|
'search.placeholder': 'بحث…',
|
|
1797
1823
|
'search.clear': 'مسح البحث',
|
|
1798
1824
|
'search.open': 'بحث',
|
|
1799
|
-
'search.filters':
|
|
1825
|
+
'search.filters': 'عوامل التصفية',
|
|
1800
1826
|
// skeleton (fly-skeleton)
|
|
1801
1827
|
'skeleton.loading': 'جارٍ التحميل…',
|
|
1802
1828
|
// typeahead (fly-typeahead)
|
|
@@ -1862,6 +1888,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
1862
1888
|
'gantt.aria.resize_start': 'تغيير تاريخ البدء',
|
|
1863
1889
|
'gantt.aria.resize_end': 'تغيير تاريخ الانتهاء',
|
|
1864
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': 'من البداية إلى النهاية',
|
|
1865
1899
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
1866
1900
|
'form.required_error': 'هذا الحقل مطلوب.',
|
|
1867
1901
|
'form.yes': 'نعم',
|
|
@@ -2070,6 +2104,15 @@ const DS_BASELINE_LOCALES = {
|
|
|
2070
2104
|
'app_unavailable.unreachable.title': 'Application indisponible',
|
|
2071
2105
|
'app_unavailable.unreachable.message': '{{app}} est injoignable pour le moment. Rien n’a été modifié — veuillez réessayer dans un instant.',
|
|
2072
2106
|
'common.action.cancel': 'Annuler',
|
|
2107
|
+
'common.action.close': 'Fermer',
|
|
2108
|
+
'common.abort': 'Abandonner',
|
|
2109
|
+
'common.ignore': 'Ignorer',
|
|
2110
|
+
'common.retry': 'Réessayer',
|
|
2111
|
+
'common.loading': 'Chargement…',
|
|
2112
|
+
'common.remove': 'Supprimer',
|
|
2113
|
+
'common.yes': 'Oui',
|
|
2114
|
+
'common.no': 'Non',
|
|
2115
|
+
'common.ok': 'OK',
|
|
2073
2116
|
'common.label.apply': 'Appliquer',
|
|
2074
2117
|
'common.label.formatting_toolbar': "Barre d'outils de mise en forme",
|
|
2075
2118
|
'common.label.https': 'https://…',
|
|
@@ -2169,7 +2212,7 @@ const DS_BASELINE_LOCALES = {
|
|
|
2169
2212
|
'search.placeholder': 'Rechercher…',
|
|
2170
2213
|
'search.clear': 'Effacer la recherche',
|
|
2171
2214
|
'search.open': 'Rechercher',
|
|
2172
|
-
'search.filters':
|
|
2215
|
+
'search.filters': 'Filtres',
|
|
2173
2216
|
// skeleton (fly-skeleton)
|
|
2174
2217
|
'skeleton.loading': 'Chargement…',
|
|
2175
2218
|
// typeahead (fly-typeahead)
|
|
@@ -2235,6 +2278,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
2235
2278
|
'gantt.aria.resize_start': 'Redimensionner la date de début',
|
|
2236
2279
|
'gantt.aria.resize_end': 'Redimensionner la date de fin',
|
|
2237
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',
|
|
2238
2289
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
2239
2290
|
'form.required_error': 'Ce champ est obligatoire.',
|
|
2240
2291
|
'form.yes': 'Oui',
|
|
@@ -2387,14 +2438,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
2387
2438
|
'chat_composer.label.mention_suggestions': 'Suggestions de mention',
|
|
2388
2439
|
// app-surface component library (fly-card / -data-table / -pagination /
|
|
2389
2440
|
// -filter-panel / forms / overlays). Absorbed with the kit from Circles.
|
|
2390
|
-
'ui.action.addMoreOptions':
|
|
2441
|
+
'ui.action.addMoreOptions': "Plus d'options",
|
|
2391
2442
|
'ui.action.cancel': 'Annuler',
|
|
2392
2443
|
'ui.action.clear': 'Effacer',
|
|
2393
2444
|
'ui.action.clearFilters': 'Effacer les filtres',
|
|
2394
2445
|
'ui.action.close': 'Fermer',
|
|
2395
2446
|
'ui.action.confirm': 'Confirmer',
|
|
2396
2447
|
'ui.action.dismiss': 'Ignorer',
|
|
2397
|
-
'ui.action.fewerOptions':
|
|
2448
|
+
'ui.action.fewerOptions': "Moins d'options",
|
|
2398
2449
|
'ui.action.open': 'Ouvrir',
|
|
2399
2450
|
'ui.action.retry': 'Réessayer',
|
|
2400
2451
|
'ui.action.save': 'Enregistrer',
|
|
@@ -2403,7 +2454,7 @@ const DS_BASELINE_LOCALES = {
|
|
|
2403
2454
|
'ui.confirm.typeToConfirm': 'Saisissez {{text}} pour confirmer',
|
|
2404
2455
|
'ui.filters.title': 'Filtres',
|
|
2405
2456
|
'ui.form.autoSaving': 'Enregistrement automatique',
|
|
2406
|
-
'ui.nav.breadcrumbs':
|
|
2457
|
+
'ui.nav.breadcrumbs': "Fil d'Ariane",
|
|
2407
2458
|
'ui.nav.home': 'Accueil',
|
|
2408
2459
|
'ui.nav.modules': 'Modules',
|
|
2409
2460
|
'ui.nav.selectModule': 'Choisir un module',
|
|
@@ -2443,6 +2494,15 @@ const DS_BASELINE_LOCALES = {
|
|
|
2443
2494
|
'app_unavailable.unreachable.title': 'ایپ دستیاب نہیں',
|
|
2444
2495
|
'app_unavailable.unreachable.message': '{{app}} تک ابھی رسائی ممکن نہیں۔ کچھ تبدیل نہیں ہوا — براہِ کرم تھوڑی دیر بعد کوشش کریں۔',
|
|
2445
2496
|
'common.action.cancel': 'منسوخ کریں',
|
|
2497
|
+
'common.action.close': 'بند کریں',
|
|
2498
|
+
'common.abort': 'منسوخ کریں',
|
|
2499
|
+
'common.ignore': 'نظرانداز کریں',
|
|
2500
|
+
'common.retry': 'دوبارہ کوشش کریں',
|
|
2501
|
+
'common.loading': 'لوڈ ہو رہا ہے…',
|
|
2502
|
+
'common.remove': 'ہٹائیں',
|
|
2503
|
+
'common.yes': 'ہاں',
|
|
2504
|
+
'common.no': 'نہیں',
|
|
2505
|
+
'common.ok': 'ٹھیک ہے',
|
|
2446
2506
|
'common.label.apply': 'لاگو کریں',
|
|
2447
2507
|
'common.label.formatting_toolbar': 'فارمیٹنگ ٹول بار',
|
|
2448
2508
|
'common.label.https': 'https://…',
|
|
@@ -2541,7 +2601,7 @@ const DS_BASELINE_LOCALES = {
|
|
|
2541
2601
|
'search.placeholder': 'تلاش کریں…',
|
|
2542
2602
|
'search.clear': 'تلاش صاف کریں',
|
|
2543
2603
|
'search.open': 'تلاش کریں',
|
|
2544
|
-
'search.filters':
|
|
2604
|
+
'search.filters': 'فلٹرز',
|
|
2545
2605
|
// skeleton (fly-skeleton)
|
|
2546
2606
|
'skeleton.loading': 'لوڈ ہو رہا ہے…',
|
|
2547
2607
|
// typeahead (fly-typeahead)
|
|
@@ -2607,6 +2667,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
2607
2667
|
'gantt.aria.resize_start': 'آغاز کی تاریخ تبدیل کریں',
|
|
2608
2668
|
'gantt.aria.resize_end': 'اختتام کی تاریخ تبدیل کریں',
|
|
2609
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': 'آغاز سے اختتام',
|
|
2610
2678
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
2611
2679
|
'form.required_error': 'یہ خانہ لازمی ہے۔',
|
|
2612
2680
|
'form.yes': 'ہاں',
|
|
@@ -12386,6 +12454,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
12386
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"] }]
|
|
12387
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 }] }] } });
|
|
12388
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
|
+
|
|
12389
12481
|
const MS_PER_DAY = 86_400_000;
|
|
12390
12482
|
/** Pixels allotted to one calendar day at each zoom level. Everything scales off this. */
|
|
12391
12483
|
const ZOOM_PX_PER_DAY = {
|
|
@@ -12641,11 +12733,16 @@ function resolveLinks(deps, renderableIds) {
|
|
|
12641
12733
|
continue;
|
|
12642
12734
|
if (!renderableIds.has(d.fromId) || !renderableIds.has(d.toId))
|
|
12643
12735
|
continue;
|
|
12644
|
-
|
|
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.
|
|
12645
12742
|
if (seen.has(key))
|
|
12646
12743
|
continue;
|
|
12647
12744
|
seen.add(key);
|
|
12648
|
-
out.push({ fromId: d.fromId, toId: d.toId });
|
|
12745
|
+
out.push({ fromId: d.fromId, toId: d.toId, type: d.type ?? 'FS' });
|
|
12649
12746
|
}
|
|
12650
12747
|
return out;
|
|
12651
12748
|
}
|
|
@@ -12759,15 +12856,37 @@ const HEADER_H = HEADER_UPPER_H + HEADER_LOWER_H;
|
|
|
12759
12856
|
const BAR_V_PAD = 6;
|
|
12760
12857
|
/** Edge-handle hit width (px) for resize + the link connector radius. */
|
|
12761
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
|
+
}
|
|
12762
12880
|
/**
|
|
12763
12881
|
* **`fly-gantt`** — the design-system SVG Gantt chart.
|
|
12764
12882
|
*
|
|
12765
12883
|
* A reusable, **data-contract-driven** timeline: feed it {@link GanttRow}[] + optional
|
|
12766
12884
|
* {@link GanttDependency}[] and it renders a two-pane chart — an indented, collapsible label
|
|
12767
12885
|
* tree on the inline-start side and a scrolling time grid (header ticks, weekend shading, a
|
|
12768
|
-
* today line, task bars with progress fill, milestone diamonds
|
|
12769
|
-
* arrows) on the other.
|
|
12770
|
-
*
|
|
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
|
|
12771
12890
|
* output event the consumer acts on.
|
|
12772
12891
|
*
|
|
12773
12892
|
* Rendered on plain inline SVG (no third-party charting lib, no CDN asset). All geometry is
|
|
@@ -12776,8 +12895,10 @@ const HANDLE_W = 8;
|
|
|
12776
12895
|
*
|
|
12777
12896
|
* Self-sufficient i18n: every user-visible string comes from the `gantt.*` keys in
|
|
12778
12897
|
* `DS_BASELINE_LOCALES` (en/ar/fr/ur), overridable by any consumer key of the same name.
|
|
12779
|
-
* Styling
|
|
12780
|
-
*
|
|
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.
|
|
12781
12902
|
*
|
|
12782
12903
|
* **Scale limit:** renders up to `maxRows` (default 500) visible rows; beyond that the list is
|
|
12783
12904
|
* capped and a footer notes the overflow. Heavy windowing/virtualization is intentionally
|
|
@@ -12794,8 +12915,14 @@ class FlyGanttComponent {
|
|
|
12794
12915
|
showToday = input(true, ...(ngDevMode ? [{ debugName: "showToday" }] : /* istanbul ignore next */ []));
|
|
12795
12916
|
/** Globally disable drag/resize/link (individual rows can also be `readonly`). */
|
|
12796
12917
|
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
12797
|
-
/**
|
|
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
|
+
*/
|
|
12798
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 */ []));
|
|
12799
12926
|
/** Row band height in px. */
|
|
12800
12927
|
rowHeight = input(34, ...(ngDevMode ? [{ debugName: "rowHeight" }] : /* istanbul ignore next */ []));
|
|
12801
12928
|
/** Hard cap on rendered rows; beyond it the list is truncated (see class doc). */
|
|
@@ -12803,10 +12930,17 @@ class FlyGanttComponent {
|
|
|
12803
12930
|
// ── Outputs ────────────────────────────────────────────────────────────────
|
|
12804
12931
|
/** Fires on a committed bar move / resize with the new ISO `start`/`end`. */
|
|
12805
12932
|
rowDatesChange = output();
|
|
12806
|
-
/**
|
|
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
|
+
*/
|
|
12807
12937
|
dependencyCreate = output();
|
|
12938
|
+
/** Fires when the user removes the selected dependency arrow (badge click or Delete). */
|
|
12939
|
+
dependencyDelete = output();
|
|
12808
12940
|
rowClick = output();
|
|
12809
12941
|
rowDblClick = output();
|
|
12942
|
+
/** Fires once per committed divider drag / keyboard resize with the new pane width in px. */
|
|
12943
|
+
labelWidthChange = output();
|
|
12810
12944
|
// ── Identity + geometry constants (template-visible) ─────────────────────────
|
|
12811
12945
|
_uid = ++_flyGanttUid;
|
|
12812
12946
|
markerId = `fly-gantt-${this._uid}-arrow`;
|
|
@@ -12814,20 +12948,30 @@ class FlyGanttComponent {
|
|
|
12814
12948
|
HEADER_UPPER_H = HEADER_UPPER_H;
|
|
12815
12949
|
HEADER_LOWER_H = HEADER_LOWER_H;
|
|
12816
12950
|
HANDLE_W = HANDLE_W;
|
|
12951
|
+
MIN_LABEL_W = MIN_LABEL_W;
|
|
12952
|
+
MAX_LABEL_W = MAX_LABEL_W;
|
|
12817
12953
|
// ── Interaction state ─────────────────────────────────────────────────────────
|
|
12818
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 */ []));
|
|
12819
12957
|
_collapsed = signal(new Set(), ...(ngDevMode ? [{ debugName: "_collapsed" }] : /* istanbul ignore next */ []));
|
|
12820
12958
|
/** Live drag preview `{id,start,end}` folded into geometry while a gesture runs. */
|
|
12821
12959
|
_dragPreview = signal(null, ...(ngDevMode ? [{ debugName: "_dragPreview" }] : /* istanbul ignore next */ []));
|
|
12822
|
-
/** 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. */
|
|
12823
12961
|
linkGesture = signal(null, ...(ngDevMode ? [{ debugName: "linkGesture" }] : /* istanbul ignore next */ []));
|
|
12824
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 */ []));
|
|
12825
12967
|
bodyRef = viewChild('bodySvg', ...(ngDevMode ? [{ debugName: "bodyRef" }] : /* istanbul ignore next */ []));
|
|
12826
12968
|
// ── Derived model ──────────────────────────────────────────────────────────────
|
|
12827
12969
|
rtl = computed(() => this.i18n.isRtl(), ...(ngDevMode ? [{ debugName: "rtl" }] : /* istanbul ignore next */ []));
|
|
12828
12970
|
pxPerDay = computed(() => ZOOM_PX_PER_DAY[this.zoom()], ...(ngDevMode ? [{ debugName: "pxPerDay" }] : /* istanbul ignore next */ []));
|
|
12829
12971
|
domain = computed(() => computeDomain(this.rows(), this.zoom()), ...(ngDevMode ? [{ debugName: "domain" }] : /* istanbul ignore next */ []));
|
|
12830
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 */ []));
|
|
12831
12975
|
/** Full visible list after tree flatten/collapse. */
|
|
12832
12976
|
_flat = computed(() => flattenRows(this.rows(), this._collapsed()), ...(ngDevMode ? [{ debugName: "_flat" }] : /* istanbul ignore next */ []));
|
|
12833
12977
|
/** Capped list actually rendered. */
|
|
@@ -12873,6 +13017,7 @@ class FlyGanttComponent {
|
|
|
12873
13017
|
}
|
|
12874
13018
|
const editable = !globalReadonly && !row.readonly && !isGroup;
|
|
12875
13019
|
const color = row.color ?? null;
|
|
13020
|
+
const tint = this._tintFor(row);
|
|
12876
13021
|
if (isMilestone) {
|
|
12877
13022
|
const at = start ?? end;
|
|
12878
13023
|
const logical = at ? dateToX(at, dom.start, ppd) : 0;
|
|
@@ -12880,7 +13025,7 @@ class FlyGanttComponent {
|
|
|
12880
13025
|
return {
|
|
12881
13026
|
flat, index, y, midY, shape: at ? 'milestone' : 'empty',
|
|
12882
13027
|
x: pointX, width: 0, progressWidth: 0, pointX,
|
|
12883
|
-
startX: pointX, endX: pointX, color,
|
|
13028
|
+
startX: pointX, endX: pointX, color, tint,
|
|
12884
13029
|
ariaLabel: this._ariaFor(row, start, end, 'milestone'),
|
|
12885
13030
|
editable: editable && !!at,
|
|
12886
13031
|
};
|
|
@@ -12888,7 +13033,7 @@ class FlyGanttComponent {
|
|
|
12888
13033
|
if (!start || !end) {
|
|
12889
13034
|
return {
|
|
12890
13035
|
flat, index, y, midY, shape: 'empty',
|
|
12891
|
-
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,
|
|
12892
13037
|
ariaLabel: this._ariaFor(row, start, end, isGroup ? 'group' : 'bar'),
|
|
12893
13038
|
editable: false,
|
|
12894
13039
|
};
|
|
@@ -12906,12 +13051,14 @@ class FlyGanttComponent {
|
|
|
12906
13051
|
pointX: 0,
|
|
12907
13052
|
startX: mapX(xs, w, rtl),
|
|
12908
13053
|
endX: mapX(xe, w, rtl),
|
|
12909
|
-
color,
|
|
13054
|
+
color, tint,
|
|
12910
13055
|
ariaLabel: this._ariaFor(row, start, end, isGroup ? 'group' : 'bar'),
|
|
12911
13056
|
editable: isGroup ? false : editable,
|
|
12912
13057
|
};
|
|
12913
13058
|
});
|
|
12914
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 */ []));
|
|
12915
13062
|
/** Dependency arrows between currently-rendered rows (missing/collapsed endpoints skipped). */
|
|
12916
13063
|
linkVms = computed(() => {
|
|
12917
13064
|
const vmById = new Map();
|
|
@@ -12926,7 +13073,21 @@ class FlyGanttComponent {
|
|
|
12926
13073
|
const to = vmById.get(link.toId);
|
|
12927
13074
|
if (!from || !to)
|
|
12928
13075
|
continue;
|
|
12929
|
-
|
|
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
|
+
});
|
|
12930
13091
|
}
|
|
12931
13092
|
return out;
|
|
12932
13093
|
}, ...(ngDevMode ? [{ debugName: "linkVms" }] : /* istanbul ignore next */ []));
|
|
@@ -12944,6 +13105,13 @@ class FlyGanttComponent {
|
|
|
12944
13105
|
const { pointX: cx, midY: cy } = vm;
|
|
12945
13106
|
return `${cx},${cy - r} ${cx + r},${cy} ${cx},${cy + r} ${cx - r},${cy}`;
|
|
12946
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
|
+
}
|
|
12947
13115
|
/** Summary-bracket path (thin bar + down-turned end caps) for a group row. */
|
|
12948
13116
|
groupPath(vm) {
|
|
12949
13117
|
const top = this.barTop(vm);
|
|
@@ -12959,6 +13127,88 @@ class FlyGanttComponent {
|
|
|
12959
13127
|
indentFor(depth) {
|
|
12960
13128
|
return 8 + depth * 16;
|
|
12961
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
|
+
}
|
|
12962
13212
|
// ── Group collapse ─────────────────────────────────────────────────────────────
|
|
12963
13213
|
isCollapsed(id) {
|
|
12964
13214
|
return this._collapsed().has(id);
|
|
@@ -12986,13 +13236,39 @@ class FlyGanttComponent {
|
|
|
12986
13236
|
// ── Row activation ─────────────────────────────────────────────────────────────
|
|
12987
13237
|
onRowClick(id) {
|
|
12988
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);
|
|
12989
13242
|
this.rowClick.emit(id);
|
|
12990
13243
|
}
|
|
12991
13244
|
onRowDblClick(id) {
|
|
12992
13245
|
this.rowDblClick.emit(id);
|
|
12993
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
|
+
}
|
|
12994
13260
|
// ── Keyboard ───────────────────────────────────────────────────────────────────
|
|
12995
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
|
+
}
|
|
12996
13272
|
const list = this.visibleRows();
|
|
12997
13273
|
if (list.length === 0)
|
|
12998
13274
|
return;
|
|
@@ -13059,6 +13335,7 @@ class FlyGanttComponent {
|
|
|
13059
13335
|
ev.preventDefault();
|
|
13060
13336
|
ev.stopPropagation();
|
|
13061
13337
|
this.selectedId.set(vm.flat.row.id);
|
|
13338
|
+
this.selectedLinkKey.set(null);
|
|
13062
13339
|
const dates = this._resolvedDates().get(vm.flat.row.id);
|
|
13063
13340
|
if (!dates?.start || !dates?.end)
|
|
13064
13341
|
return;
|
|
@@ -13113,13 +13390,19 @@ class FlyGanttComponent {
|
|
|
13113
13390
|
});
|
|
13114
13391
|
}
|
|
13115
13392
|
// ── Pointer drag: dependency create ──────────────────────────────────────────────
|
|
13116
|
-
|
|
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) {
|
|
13117
13399
|
if (this.readonly() || vm.flat.row.readonly || ev.button !== 0)
|
|
13118
13400
|
return;
|
|
13119
13401
|
ev.preventDefault();
|
|
13120
13402
|
ev.stopPropagation();
|
|
13121
13403
|
const local = this._toBodyPoint(ev);
|
|
13122
|
-
|
|
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 });
|
|
13123
13406
|
const onMove = (e) => {
|
|
13124
13407
|
const p = this._toBodyPoint(e);
|
|
13125
13408
|
const g = this.linkGesture();
|
|
@@ -13133,9 +13416,13 @@ class FlyGanttComponent {
|
|
|
13133
13416
|
this.linkGesture.set(null);
|
|
13134
13417
|
if (!g)
|
|
13135
13418
|
return;
|
|
13136
|
-
const
|
|
13137
|
-
if (
|
|
13138
|
-
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
|
+
});
|
|
13139
13426
|
}
|
|
13140
13427
|
};
|
|
13141
13428
|
document.addEventListener('pointermove', onMove);
|
|
@@ -13153,6 +13440,17 @@ class FlyGanttComponent {
|
|
|
13153
13440
|
return `M ${g.x0} ${g.y0} L ${g.x1} ${g.y1}`;
|
|
13154
13441
|
}, ...(ngDevMode ? [{ debugName: "linkGesturePath" }] : /* istanbul ignore next */ []));
|
|
13155
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
|
+
}
|
|
13156
13454
|
_mapTicks(ticks) {
|
|
13157
13455
|
const w = this.innerWidth();
|
|
13158
13456
|
const rtl = this.rtl();
|
|
@@ -13161,16 +13459,41 @@ class FlyGanttComponent {
|
|
|
13161
13459
|
// Reflect each cell: its inline-start edge becomes innerWidth - (x + width).
|
|
13162
13460
|
return ticks.map((t) => ({ ...t, x: w - t.x - t.width }));
|
|
13163
13461
|
}
|
|
13164
|
-
/**
|
|
13165
|
-
|
|
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) {
|
|
13166
13476
|
const fwd = rtl ? -1 : 1;
|
|
13167
|
-
const
|
|
13168
|
-
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;
|
|
13169
13481
|
const y1 = from.midY;
|
|
13170
|
-
const x2 = to.shape === 'milestone' ? to.pointX : to.startX;
|
|
13171
13482
|
const y2 = to.midY;
|
|
13172
|
-
|
|
13173
|
-
|
|
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
|
+
};
|
|
13174
13497
|
}
|
|
13175
13498
|
/** Convert a pointer event to a coordinate inside the body SVG (render space). */
|
|
13176
13499
|
_toBodyPoint(ev) {
|
|
@@ -13180,11 +13503,25 @@ class FlyGanttComponent {
|
|
|
13180
13503
|
const rect = svg.getBoundingClientRect();
|
|
13181
13504
|
return { x: ev.clientX - rect.left, y: ev.clientY - rect.top };
|
|
13182
13505
|
}
|
|
13183
|
-
/**
|
|
13184
|
-
|
|
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) {
|
|
13185
13512
|
const el = document.elementFromPoint(clientX, clientY);
|
|
13186
13513
|
const holder = el?.closest('[data-row-id]');
|
|
13187
|
-
|
|
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
|
+
};
|
|
13188
13525
|
}
|
|
13189
13526
|
_ariaFor(row, start, end, shape) {
|
|
13190
13527
|
const t = (k, p) => this.i18n.t(k, p);
|
|
@@ -13205,7 +13542,7 @@ class FlyGanttComponent {
|
|
|
13205
13542
|
: t('gantt.aria.bar', { label: row.label, start: toIsoDate(start), end: toIsoDate(end), progress });
|
|
13206
13543
|
}
|
|
13207
13544
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyGanttComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13208
|
-
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 });
|
|
13209
13546
|
}
|
|
13210
13547
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyGanttComponent, decorators: [{
|
|
13211
13548
|
type: Component,
|
|
@@ -13213,20 +13550,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
13213
13550
|
class: 'fly-gantt',
|
|
13214
13551
|
'[class.fly-gantt--rtl]': 'rtl()',
|
|
13215
13552
|
'[class.fly-gantt--readonly]': 'readonly()',
|
|
13553
|
+
'[class.fly-gantt--resizing]': 'resizingLabels()',
|
|
13216
13554
|
'[attr.dir]': 'i18n.direction()',
|
|
13217
|
-
}, 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"] }]
|
|
13218
|
-
}], 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 }] }] } });
|
|
13219
|
-
|
|
13220
|
-
/**
|
|
13221
|
-
* Public data contract for {@link FlyGanttComponent}.
|
|
13222
|
-
*
|
|
13223
|
-
* The types here are deliberately **app-agnostic** — no Projects/Tasks vocabulary leaks in.
|
|
13224
|
-
* A consumer maps its own domain rows to {@link GanttRow}[] + {@link GanttDependency}[],
|
|
13225
|
-
* feeds them in, and reacts to the emitted change events by persisting itself. The Gantt
|
|
13226
|
-
* never calls a backend and never mutates its inputs.
|
|
13227
|
-
*/
|
|
13228
|
-
/** Immutable list of the supported zoom levels, coarsest-last. */
|
|
13229
|
-
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 }] }] } });
|
|
13230
13557
|
|
|
13231
13558
|
// ── Bundled ISO 3166-1 country list ──────────────────────────────────────────
|
|
13232
13559
|
// Ships with <fly-survey-form> so a `Country` question renders a searchable
|
|
@@ -15855,7 +16182,9 @@ function actionFingerprint(action) {
|
|
|
15855
16182
|
paramsFingerprint(menu.titleParams),
|
|
15856
16183
|
menu.value,
|
|
15857
16184
|
menu.defaultValue,
|
|
15858
|
-
|
|
16185
|
+
// `label ?? null` so a RENAMED tenant row (campaign, area) repaints; null keeps a
|
|
16186
|
+
// key-only option's fingerprint byte-identical to what it was before the field existed.
|
|
16187
|
+
menu.options.map((o) => [o.value, o.labelKey, paramsFingerprint(o.labelParams), o.label ?? null]),
|
|
15859
16188
|
!!menu.onSelect,
|
|
15860
16189
|
]
|
|
15861
16190
|
: null,
|
|
@@ -15945,6 +16274,9 @@ function projectMenu(action, handlers) {
|
|
|
15945
16274
|
value: o.value,
|
|
15946
16275
|
labelKey: o.labelKey,
|
|
15947
16276
|
...(o.labelParams === undefined ? {} : { labelParams: o.labelParams }),
|
|
16277
|
+
// Conditional, like every other optional field here: an explicit `label: undefined` is
|
|
16278
|
+
// `in`-visible on the object and would make "absent" and "present but empty" compare equal.
|
|
16279
|
+
...(o.label === undefined ? {} : { label: o.label }),
|
|
15948
16280
|
})),
|
|
15949
16281
|
value: menu.value,
|
|
15950
16282
|
defaultValue: menu.defaultValue,
|
|
@@ -16845,7 +17177,13 @@ class FlyMagicActionsComponent {
|
|
|
16845
17177
|
if (!menu)
|
|
16846
17178
|
return this.resolvedLabel(action);
|
|
16847
17179
|
const current = menu.options.find((o) => o.value === menu.value);
|
|
16848
|
-
|
|
17180
|
+
// Same precedence as the rows — otherwise the trigger would announce a translated
|
|
17181
|
+
// placeholder while the open menu shows the tenant's actual name.
|
|
17182
|
+
const value = current
|
|
17183
|
+
? current.label?.trim()
|
|
17184
|
+
? current.label
|
|
17185
|
+
: this.i18n.t(current.labelKey, current.labelParams)
|
|
17186
|
+
: menu.value;
|
|
16849
17187
|
return this.i18n.t('magic_actions.radio_menu.trigger_label', {
|
|
16850
17188
|
label: this.resolvedLabel(action),
|
|
16851
17189
|
value,
|
|
@@ -16895,7 +17233,10 @@ class FlyMagicActionsComponent {
|
|
|
16895
17233
|
return [];
|
|
16896
17234
|
const items = menu.options.map((option) => ({
|
|
16897
17235
|
id: option.value,
|
|
16898
|
-
|
|
17236
|
+
// Tenant data wins over the key: `label` is a campaign/area name read from the publisher's
|
|
17237
|
+
// own rows, which no locale bundle can contain. Falls back to the key when absent OR when
|
|
17238
|
+
// the data-derived text is empty, so a blank row can never render as a nameless option.
|
|
17239
|
+
label: option.label?.trim() ? option.label : this.i18n.t(option.labelKey, option.labelParams),
|
|
16899
17240
|
// `selected` is what makes this a real radio menu rather than a list with
|
|
16900
17241
|
// a tick: `fly-context-menu` renders a selected row as
|
|
16901
17242
|
// `role="menuitemradio"` + `aria-checked`, with the check indicator and
|
|
@@ -16982,13 +17323,13 @@ class FlyMagicActionsComponent {
|
|
|
16982
17323
|
this.isOverflowing.set(!!el && el.scrollWidth - el.clientWidth > 1);
|
|
16983
17324
|
}
|
|
16984
17325
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyMagicActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
16985
|
-
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<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-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-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 });
|
|
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 });
|
|
16986
17327
|
}
|
|
16987
17328
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyMagicActionsComponent, decorators: [{
|
|
16988
17329
|
type: Component,
|
|
16989
17330
|
args: [{ selector: 'fly-magic-actions', standalone: true, imports: [ContextMenuComponent, FlyTooltipDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
16990
17331
|
'[attr.dir]': 'i18n.direction()',
|
|
16991
|
-
}, 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<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-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-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"] }]
|
|
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"] }]
|
|
16992
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 }] }] } });
|
|
16993
17334
|
|
|
16994
17335
|
/**
|
|
@@ -25452,5 +25793,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
25452
25793
|
* Generated bundle index. Do not edit.
|
|
25453
25794
|
*/
|
|
25454
25795
|
|
|
25455
|
-
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 };
|
|
25456
25797
|
//# sourceMappingURL=flyos-design-system.mjs.map
|