@flyos/design-system 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/flyos-design-system.mjs +1131 -310
- package/fesm2022/flyos-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/scss/_fly-theme.scss +5 -0
- package/scss/_nova-tokens.scss +22 -0
- package/scss/_nova-type.scss +51 -12
- package/scss/_sf-arabic-font.scss +32 -0
- package/scss/_tokens.scss +56 -20
- package/types/flyos-design-system.d.ts +413 -63
- package/types/flyos-design-system.d.ts.map +1 -1
|
@@ -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.5.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
|
|
@@ -1218,12 +1218,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
1218
1218
|
* Standalone-dev route guard: if there is no authenticated session, start the PKCE login (redirects
|
|
1219
1219
|
* to the STS) and block activation. Not reached in federated mode — the shell mounts the app's root
|
|
1220
1220
|
* component directly rather than routing through the app shell.
|
|
1221
|
+
*
|
|
1222
|
+
* Before redirecting it stashes the attempted URL under the same appId-scoped sessionStorage key the
|
|
1223
|
+
* step-up flow uses, so the auth callback lands back on the deep link instead of `/` — an
|
|
1224
|
+
* unauthenticated deep link previously lost its target on the STS round-trip.
|
|
1221
1225
|
*/
|
|
1222
|
-
const flyStandaloneAuthGuard = () => {
|
|
1226
|
+
const flyStandaloneAuthGuard = (_route, state) => {
|
|
1223
1227
|
const auth = inject(FlyStandaloneAuthService);
|
|
1224
1228
|
if (auth.isAuthenticated())
|
|
1225
1229
|
return true;
|
|
1226
|
-
|
|
1230
|
+
const appId = inject(FLY_STANDALONE_AUTH_CONFIG).appId;
|
|
1231
|
+
try {
|
|
1232
|
+
sessionStorage.setItem(stepUpReturnKey(appId), state.url);
|
|
1233
|
+
}
|
|
1234
|
+
catch {
|
|
1235
|
+
/* sessionStorage unavailable — fall back to the callback's default landing */
|
|
1236
|
+
}
|
|
1237
|
+
auth.startLogin().catch((err) => {
|
|
1238
|
+
// Surface the failure instead of leaving a silent white screen: the guard already returned
|
|
1239
|
+
// false, so if the authorize redirect never happens the router renders nothing at all.
|
|
1240
|
+
console.error('[FlyStandaloneAuth] startLogin failed — the app cannot authenticate', err);
|
|
1241
|
+
});
|
|
1227
1242
|
return false;
|
|
1228
1243
|
};
|
|
1229
1244
|
|
|
@@ -1303,7 +1318,7 @@ function flyStandaloneAuthInterceptor(req, next) {
|
|
|
1303
1318
|
* is a safe default that an integrator can override without coordination.
|
|
1304
1319
|
*
|
|
1305
1320
|
* Scope: only the keys the **shippable** DS components reference today
|
|
1306
|
-
* (`common.*` toolbar/link/emoji-picker labels + `agent.lookup.*` + `select.*` + `typeahead.*` + `cron.*` + `gantt.*` + `form.*` + `captcha.*` + `comment.*` + `canvas_board.*` + `moderation.*` + `people_picker.*` + `currency_selector.*` + `pagination.*` + `tree_nav.*` + `magic_actions.*`). When a new DS component
|
|
1321
|
+
* (`common.*` toolbar/link/emoji-picker labels + `agent.lookup.*` + `select.*` + `typeahead.*` + `cron.*` + `gantt.*` + `form.*` + `captcha.*` + `comment.*` + `canvas_board.*` + `moderation.*` + `people_picker.*` + `strategy_selector.*` + `currency_selector.*` + `pagination.*` + `tree_nav.*` + `magic_actions.*`). When a new DS component
|
|
1307
1322
|
* starts using `| translate` **or `I18nService.t()`**, add its keys here so it stays
|
|
1308
1323
|
* self-sufficient — `fly-magic-actions` shipped resolving three keys that existed only in the
|
|
1309
1324
|
* shell bundle, so an External App rendering it got the raw key string as every disabled
|
|
@@ -1575,6 +1590,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
1575
1590
|
'captcha.verified': 'Verified',
|
|
1576
1591
|
'captcha.expired': 'Verification expired — please retry.',
|
|
1577
1592
|
'captcha.error': 'Verification failed.',
|
|
1593
|
+
// file upload (fly-file-upload)
|
|
1594
|
+
'files.fileUpload.dropOrClick': 'Drop files or click to browse',
|
|
1595
|
+
'files.fileUpload.limitHint': 'Max {{n}} files, {{mb}}MB each',
|
|
1578
1596
|
// standalone auth callback (provideFlyStandaloneAuth)
|
|
1579
1597
|
'auth.callback.signing_in': 'Signing you in…',
|
|
1580
1598
|
// comment-thread (fly-comment-thread)
|
|
@@ -1641,6 +1659,13 @@ const DS_BASELINE_LOCALES = {
|
|
|
1641
1659
|
'people_picker.search_placeholder': 'Search people…',
|
|
1642
1660
|
'people_picker.remove': 'Remove {{name}}',
|
|
1643
1661
|
'people_picker.change': 'Change',
|
|
1662
|
+
// strategy selector (fly-strategy-selector)
|
|
1663
|
+
'strategy_selector.search_placeholder': 'Search strategic objectives…',
|
|
1664
|
+
'strategy_selector.remove': 'Remove {{name}}',
|
|
1665
|
+
'strategy_selector.no_results': 'No matching objectives.',
|
|
1666
|
+
'strategy_selector.loading': 'Loading objectives…',
|
|
1667
|
+
'strategy_selector.error': 'Objectives could not be loaded.',
|
|
1668
|
+
'strategy_selector.retry': 'Retry',
|
|
1644
1669
|
// currency selector (fly-currency-selector)
|
|
1645
1670
|
'currency_selector.placeholder': 'Select a currency',
|
|
1646
1671
|
'currency_selector.placeholder_multi': 'Select currencies',
|
|
@@ -1680,6 +1705,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
1680
1705
|
'ui.nav.selectModule': 'Select a module',
|
|
1681
1706
|
'ui.nav.sections': 'Sections',
|
|
1682
1707
|
'ui.nav.switchModule': 'Switch module',
|
|
1708
|
+
'ui.pager.next': 'Next page',
|
|
1709
|
+
'ui.pager.page': 'Page {{page}}',
|
|
1710
|
+
'ui.pager.previous': 'Previous page',
|
|
1683
1711
|
'ui.pagination.first': 'First page',
|
|
1684
1712
|
'ui.pagination.label': 'Pagination',
|
|
1685
1713
|
'ui.pagination.last': 'Last page',
|
|
@@ -1965,6 +1993,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
1965
1993
|
'captcha.verified': 'تم التحقق',
|
|
1966
1994
|
'captcha.expired': 'انتهت صلاحية التحقق — يرجى إعادة المحاولة.',
|
|
1967
1995
|
'captcha.error': 'فشل التحقق.',
|
|
1996
|
+
// file upload (fly-file-upload)
|
|
1997
|
+
'files.fileUpload.dropOrClick': 'أسقط الملفات أو انقر للتصفح',
|
|
1998
|
+
'files.fileUpload.limitHint': 'بحد أقصى {{n}} ملفات، {{mb}} ميغابايت لكل ملف',
|
|
1968
1999
|
// standalone auth callback (provideFlyStandaloneAuth)
|
|
1969
2000
|
'auth.callback.signing_in': 'جارٍ تسجيل الدخول…',
|
|
1970
2001
|
// comment-thread (fly-comment-thread)
|
|
@@ -2031,6 +2062,13 @@ const DS_BASELINE_LOCALES = {
|
|
|
2031
2062
|
'people_picker.search_placeholder': 'ابحث عن أشخاص…',
|
|
2032
2063
|
'people_picker.remove': 'إزالة {{name}}',
|
|
2033
2064
|
'people_picker.change': 'تغيير',
|
|
2065
|
+
// strategy selector (fly-strategy-selector)
|
|
2066
|
+
'strategy_selector.search_placeholder': 'ابحث عن الأهداف الاستراتيجية…',
|
|
2067
|
+
'strategy_selector.remove': 'إزالة {{name}}',
|
|
2068
|
+
'strategy_selector.no_results': 'لا توجد أهداف مطابقة.',
|
|
2069
|
+
'strategy_selector.loading': 'جارٍ تحميل الأهداف…',
|
|
2070
|
+
'strategy_selector.error': 'تعذّر تحميل الأهداف.',
|
|
2071
|
+
'strategy_selector.retry': 'إعادة المحاولة',
|
|
2034
2072
|
// currency selector (fly-currency-selector)
|
|
2035
2073
|
'currency_selector.placeholder': 'اختر عملة',
|
|
2036
2074
|
'currency_selector.placeholder_multi': 'اختر العملات',
|
|
@@ -2070,6 +2108,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
2070
2108
|
'ui.nav.selectModule': 'اختر وحدة',
|
|
2071
2109
|
'ui.nav.sections': 'الأقسام',
|
|
2072
2110
|
'ui.nav.switchModule': 'تبديل الوحدة',
|
|
2111
|
+
'ui.pager.next': 'الصفحة التالية',
|
|
2112
|
+
'ui.pager.page': 'الصفحة {{page}}',
|
|
2113
|
+
'ui.pager.previous': 'الصفحة السابقة',
|
|
2073
2114
|
'ui.pagination.first': 'الصفحة الأولى',
|
|
2074
2115
|
'ui.pagination.label': 'تقسيم الصفحات',
|
|
2075
2116
|
'ui.pagination.last': 'الصفحة الأخيرة',
|
|
@@ -2355,6 +2396,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
2355
2396
|
'captcha.verified': 'Vérifié',
|
|
2356
2397
|
'captcha.expired': 'Vérification expirée — veuillez réessayer.',
|
|
2357
2398
|
'captcha.error': 'Échec de la vérification.',
|
|
2399
|
+
// file upload (fly-file-upload)
|
|
2400
|
+
'files.fileUpload.dropOrClick': 'Déposez des fichiers ou cliquez pour parcourir',
|
|
2401
|
+
'files.fileUpload.limitHint': '{{n}} fichiers max., {{mb}} Mo chacun',
|
|
2358
2402
|
// standalone auth callback (provideFlyStandaloneAuth)
|
|
2359
2403
|
'auth.callback.signing_in': 'Connexion en cours…',
|
|
2360
2404
|
// comment-thread (fly-comment-thread)
|
|
@@ -2421,6 +2465,13 @@ const DS_BASELINE_LOCALES = {
|
|
|
2421
2465
|
'people_picker.search_placeholder': 'Rechercher des personnes…',
|
|
2422
2466
|
'people_picker.remove': 'Retirer {{name}}',
|
|
2423
2467
|
'people_picker.change': 'Changer',
|
|
2468
|
+
// strategy selector (fly-strategy-selector)
|
|
2469
|
+
'strategy_selector.search_placeholder': 'Rechercher des objectifs stratégiques…',
|
|
2470
|
+
'strategy_selector.remove': 'Retirer {{name}}',
|
|
2471
|
+
'strategy_selector.no_results': 'Aucun objectif correspondant.',
|
|
2472
|
+
'strategy_selector.loading': 'Chargement des objectifs…',
|
|
2473
|
+
'strategy_selector.error': 'Impossible de charger les objectifs.',
|
|
2474
|
+
'strategy_selector.retry': 'Réessayer',
|
|
2424
2475
|
// currency selector (fly-currency-selector)
|
|
2425
2476
|
'currency_selector.placeholder': 'Sélectionner une devise',
|
|
2426
2477
|
'currency_selector.placeholder_multi': 'Sélectionner des devises',
|
|
@@ -2460,6 +2511,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
2460
2511
|
'ui.nav.selectModule': 'Choisir un module',
|
|
2461
2512
|
'ui.nav.sections': 'Sections',
|
|
2462
2513
|
'ui.nav.switchModule': 'Changer de module',
|
|
2514
|
+
'ui.pager.next': 'Page suivante',
|
|
2515
|
+
'ui.pager.page': 'Page {{page}}',
|
|
2516
|
+
'ui.pager.previous': 'Page précédente',
|
|
2463
2517
|
'ui.pagination.first': 'Première page',
|
|
2464
2518
|
'ui.pagination.label': 'Pagination',
|
|
2465
2519
|
'ui.pagination.last': 'Dernière page',
|
|
@@ -2744,6 +2798,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
2744
2798
|
'captcha.verified': 'تصدیق ہو گئی',
|
|
2745
2799
|
'captcha.expired': 'تصدیق کی میعاد ختم — دوبارہ کوشش کریں۔',
|
|
2746
2800
|
'captcha.error': 'تصدیق ناکام۔',
|
|
2801
|
+
// file upload (fly-file-upload)
|
|
2802
|
+
'files.fileUpload.dropOrClick': 'فائلیں چھوڑیں یا براؤز کرنے کے لیے کلک کریں',
|
|
2803
|
+
'files.fileUpload.limitHint': 'زیادہ سے زیادہ {{n}} فائلیں، ہر ایک {{mb}} ایم بی',
|
|
2747
2804
|
// standalone auth callback (provideFlyStandaloneAuth)
|
|
2748
2805
|
'auth.callback.signing_in': 'سائن ان ہو رہا ہے…',
|
|
2749
2806
|
// comment-thread (fly-comment-thread)
|
|
@@ -2810,6 +2867,13 @@ const DS_BASELINE_LOCALES = {
|
|
|
2810
2867
|
'people_picker.search_placeholder': 'لوگ تلاش کریں…',
|
|
2811
2868
|
'people_picker.remove': '{{name}} کو ہٹائیں',
|
|
2812
2869
|
'people_picker.change': 'تبدیل کریں',
|
|
2870
|
+
// strategy selector (fly-strategy-selector)
|
|
2871
|
+
'strategy_selector.search_placeholder': 'اسٹریٹجک مقاصد تلاش کریں…',
|
|
2872
|
+
'strategy_selector.remove': '{{name}} کو ہٹائیں',
|
|
2873
|
+
'strategy_selector.no_results': 'کوئی مماثل مقصد نہیں۔',
|
|
2874
|
+
'strategy_selector.loading': 'مقاصد لوڈ ہو رہے ہیں…',
|
|
2875
|
+
'strategy_selector.error': 'مقاصد لوڈ نہیں ہو سکے۔',
|
|
2876
|
+
'strategy_selector.retry': 'دوبارہ کوشش کریں',
|
|
2813
2877
|
// currency selector (fly-currency-selector)
|
|
2814
2878
|
'currency_selector.placeholder': 'کرنسی منتخب کریں',
|
|
2815
2879
|
'currency_selector.placeholder_multi': 'کرنسیاں منتخب کریں',
|
|
@@ -2849,6 +2913,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
2849
2913
|
'ui.nav.selectModule': 'ماڈیول منتخب کریں',
|
|
2850
2914
|
'ui.nav.sections': 'حصے',
|
|
2851
2915
|
'ui.nav.switchModule': 'ماڈیول تبدیل کریں',
|
|
2916
|
+
'ui.pager.next': 'اگلا صفحہ',
|
|
2917
|
+
'ui.pager.page': 'صفحہ {{page}}',
|
|
2918
|
+
'ui.pager.previous': 'پچھلا صفحہ',
|
|
2852
2919
|
'ui.pagination.first': 'پہلا صفحہ',
|
|
2853
2920
|
'ui.pagination.label': 'صفحہ بندی',
|
|
2854
2921
|
'ui.pagination.last': 'آخری صفحہ',
|
|
@@ -4008,12 +4075,12 @@ function connectRemoteLaunch(opts) {
|
|
|
4008
4075
|
const FLY_REMOTE_CONTEXT_STORE_KEY = '__flyRemoteContext__';
|
|
4009
4076
|
const FLY_REMOTE_CONTEXT_EVENT = 'fly:remote-context';
|
|
4010
4077
|
/** The single cross-bundle store, lazily created on `globalThis`. */
|
|
4011
|
-
function store$
|
|
4078
|
+
function store$2() {
|
|
4012
4079
|
const g = globalThis;
|
|
4013
4080
|
return (g[FLY_REMOTE_CONTEXT_STORE_KEY] ??= {});
|
|
4014
4081
|
}
|
|
4015
4082
|
/** Notify every federated copy of the service that the store changed. */
|
|
4016
|
-
function emitSync$
|
|
4083
|
+
function emitSync$2() {
|
|
4017
4084
|
const g = globalThis;
|
|
4018
4085
|
try {
|
|
4019
4086
|
g.dispatchEvent?.(new Event(FLY_REMOTE_CONTEXT_EVENT));
|
|
@@ -4070,33 +4137,33 @@ function toMap(s) {
|
|
|
4070
4137
|
* dispatch is ever needed.
|
|
4071
4138
|
*/
|
|
4072
4139
|
class FlyRemoteContextService {
|
|
4073
|
-
_byApp = signal(toMap(store$
|
|
4140
|
+
_byApp = signal(toMap(store$2()), ...(ngDevMode ? [{ debugName: "_byApp" }] : /* istanbul ignore next */ []));
|
|
4074
4141
|
/** All currently-published contexts, keyed by app id. Reactive. */
|
|
4075
4142
|
contexts = this._byApp.asReadonly();
|
|
4076
4143
|
constructor() {
|
|
4077
4144
|
// Keep this instance's reactive mirror current when ANOTHER federated copy of
|
|
4078
4145
|
// the service — or a remote writing the store slot directly — mutates it.
|
|
4079
4146
|
const g = globalThis;
|
|
4080
|
-
g.addEventListener?.(FLY_REMOTE_CONTEXT_EVENT, () => this._byApp.set(toMap(store$
|
|
4147
|
+
g.addEventListener?.(FLY_REMOTE_CONTEXT_EVENT, () => this._byApp.set(toMap(store$2())));
|
|
4081
4148
|
}
|
|
4082
4149
|
/** Publish (or replace) the active route context for an app. */
|
|
4083
4150
|
publish(ctx) {
|
|
4084
|
-
store$
|
|
4085
|
-
this._byApp.set(toMap(store$
|
|
4086
|
-
emitSync$
|
|
4151
|
+
store$2()[ctx.appId] = ctx;
|
|
4152
|
+
this._byApp.set(toMap(store$2()));
|
|
4153
|
+
emitSync$2();
|
|
4087
4154
|
}
|
|
4088
4155
|
/** Drop an app's context (remote unmounted / window closed). No-op if absent. */
|
|
4089
4156
|
clear(appId) {
|
|
4090
|
-
const s = store$
|
|
4157
|
+
const s = store$2();
|
|
4091
4158
|
if (!(appId in s))
|
|
4092
4159
|
return;
|
|
4093
4160
|
delete s[appId];
|
|
4094
4161
|
this._byApp.set(toMap(s));
|
|
4095
|
-
emitSync$
|
|
4162
|
+
emitSync$2();
|
|
4096
4163
|
}
|
|
4097
4164
|
/** Current context for an app, or `null` when nothing is published. */
|
|
4098
4165
|
context(appId) {
|
|
4099
|
-
return store$
|
|
4166
|
+
return store$2()[appId] ?? null;
|
|
4100
4167
|
}
|
|
4101
4168
|
/**
|
|
4102
4169
|
* Resolve a single route param for an app, e.g. `param('circles', 'id')`.
|
|
@@ -4104,7 +4171,7 @@ class FlyRemoteContextService {
|
|
|
4104
4171
|
* the caller then degrades (the skill asks for the missing id).
|
|
4105
4172
|
*/
|
|
4106
4173
|
param(appId, path) {
|
|
4107
|
-
const value = store$
|
|
4174
|
+
const value = store$2()[appId]?.params[path];
|
|
4108
4175
|
return value != null && value !== '' ? value : null;
|
|
4109
4176
|
}
|
|
4110
4177
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyRemoteContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -9971,15 +10038,144 @@ class FlyBreadcrumbComponent {
|
|
|
9971
10038
|
this.navigate.emit(item);
|
|
9972
10039
|
}
|
|
9973
10040
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyBreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9974
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyBreadcrumbComponent, isStandalone: true, selector: "fly-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, maxVisible: { classPropertyName: "maxVisible", publicName: "maxVisible", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hasCurrentPage: { classPropertyName: "hasCurrentPage", publicName: "hasCurrentPage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navigate: "navigate" }, host: { properties: { "attr.dir": "i18n.direction()" } }, ngImport: i0, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n
|
|
10041
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyBreadcrumbComponent, isStandalone: true, selector: "fly-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, maxVisible: { classPropertyName: "maxVisible", publicName: "maxVisible", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hasCurrentPage: { classPropertyName: "hasCurrentPage", publicName: "hasCurrentPage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navigate: "navigate" }, host: { properties: { "attr.dir": "i18n.direction()" } }, ngImport: i0, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n @for (entry of renderItems(); track entry.kind === 'item' ? entry.item.id : 'overflow'; let i = $index) {\r\n @if (i > 0) {\r\n <svg class=\"fly-breadcrumb__separator\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <path d=\"M9 5l7 7-7 7\" />\r\n </svg>\r\n }\r\n @if (entry.kind === 'overflow') {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--overflow\">\u2026</span>\r\n } @else if (entry.isCurrent) {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--current\" aria-current=\"page\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </span>\r\n } @else {\r\n <span class=\"fly-breadcrumb__crumb\">\r\n @if (entry.item.href) {\r\n <a class=\"fly-breadcrumb__link\" [attr.href]=\"entry.item.href\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </a>\r\n } @else {\r\n <button type=\"button\" class=\"fly-breadcrumb__link fly-breadcrumb__link--button\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </button>\r\n }\r\n </span>\r\n }\r\n }\r\n</nav>\r\n", styles: [":host{display:block;min-inline-size:0}.fly-breadcrumb{display:flex;align-items:center;gap:2px;min-inline-size:0}.fly-breadcrumb__crumb{display:flex;align-items:center;flex:none;min-inline-size:0;font-size:12.5px;line-height:1.4}.fly-breadcrumb__crumb--current{flex:1 1 auto;min-inline-size:0;gap:6px;block-size:26px;padding-inline:4px;font-weight:600;letter-spacing:-.006em;color:var(--text-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host([dir=rtl]) .fly-breadcrumb__crumb--current{letter-spacing:normal}.fly-breadcrumb__crumb--overflow{color:var(--text-color-secondary);-webkit-user-select:none;user-select:none}.fly-breadcrumb__link{display:inline-flex;align-items:center;gap:6px;block-size:26px;padding-inline:8px;margin:0;border:0;border-radius:8px;background:transparent;color:var(--text-color-secondary);font-family:inherit;font-size:12.5px;font-weight:500;line-height:1;text-decoration:none;cursor:pointer;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-breadcrumb__link:hover{background:var(--w08);color:var(--text-color)}.fly-breadcrumb__link:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-breadcrumb__link--button{display:inline-flex}.fly-breadcrumb__icon{flex:none}.fly-breadcrumb__separator{flex:none;color:var(--w28)}:host([dir=rtl]) .fly-breadcrumb__separator{transform:scaleX(-1)}@media(forced-colors:active){.fly-breadcrumb__separator{color:CanvasText}.fly-breadcrumb__link:focus-visible{outline-color:Highlight}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9975
10042
|
}
|
|
9976
10043
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyBreadcrumbComponent, decorators: [{
|
|
9977
10044
|
type: Component,
|
|
9978
10045
|
args: [{ selector: 'fly-breadcrumb', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
9979
10046
|
'[attr.dir]': 'i18n.direction()',
|
|
9980
|
-
}, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n
|
|
10047
|
+
}, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n @for (entry of renderItems(); track entry.kind === 'item' ? entry.item.id : 'overflow'; let i = $index) {\r\n @if (i > 0) {\r\n <svg class=\"fly-breadcrumb__separator\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <path d=\"M9 5l7 7-7 7\" />\r\n </svg>\r\n }\r\n @if (entry.kind === 'overflow') {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--overflow\">\u2026</span>\r\n } @else if (entry.isCurrent) {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--current\" aria-current=\"page\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </span>\r\n } @else {\r\n <span class=\"fly-breadcrumb__crumb\">\r\n @if (entry.item.href) {\r\n <a class=\"fly-breadcrumb__link\" [attr.href]=\"entry.item.href\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </a>\r\n } @else {\r\n <button type=\"button\" class=\"fly-breadcrumb__link fly-breadcrumb__link--button\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </button>\r\n }\r\n </span>\r\n }\r\n }\r\n</nav>\r\n", styles: [":host{display:block;min-inline-size:0}.fly-breadcrumb{display:flex;align-items:center;gap:2px;min-inline-size:0}.fly-breadcrumb__crumb{display:flex;align-items:center;flex:none;min-inline-size:0;font-size:12.5px;line-height:1.4}.fly-breadcrumb__crumb--current{flex:1 1 auto;min-inline-size:0;gap:6px;block-size:26px;padding-inline:4px;font-weight:600;letter-spacing:-.006em;color:var(--text-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host([dir=rtl]) .fly-breadcrumb__crumb--current{letter-spacing:normal}.fly-breadcrumb__crumb--overflow{color:var(--text-color-secondary);-webkit-user-select:none;user-select:none}.fly-breadcrumb__link{display:inline-flex;align-items:center;gap:6px;block-size:26px;padding-inline:8px;margin:0;border:0;border-radius:8px;background:transparent;color:var(--text-color-secondary);font-family:inherit;font-size:12.5px;font-weight:500;line-height:1;text-decoration:none;cursor:pointer;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-breadcrumb__link:hover{background:var(--w08);color:var(--text-color)}.fly-breadcrumb__link:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-breadcrumb__link--button{display:inline-flex}.fly-breadcrumb__icon{flex:none}.fly-breadcrumb__separator{flex:none;color:var(--w28)}:host([dir=rtl]) .fly-breadcrumb__separator{transform:scaleX(-1)}@media(forced-colors:active){.fly-breadcrumb__separator{color:CanvasText}.fly-breadcrumb__link:focus-visible{outline-color:Highlight}}\n"] }]
|
|
9981
10048
|
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], maxVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisible", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hasCurrentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasCurrentPage", required: false }] }], navigate: [{ type: i0.Output, args: ["navigate"] }] } });
|
|
9982
10049
|
|
|
10050
|
+
/** Cross-bundle store key + sync event — same shape as `FLY_MAGIC_BAR_STORE_KEY`. */
|
|
10051
|
+
const FLY_WINDOW_BREADCRUMBS_STORE_KEY = '__flyWindowBreadcrumbs__';
|
|
10052
|
+
const FLY_WINDOW_BREADCRUMBS_EVENT = 'fly:window-breadcrumbs';
|
|
10053
|
+
function store$1() {
|
|
10054
|
+
const g = globalThis;
|
|
10055
|
+
return (g[FLY_WINDOW_BREADCRUMBS_STORE_KEY] ??= { trails: {}, revision: 0 });
|
|
10056
|
+
}
|
|
10057
|
+
function emitSync$1() {
|
|
10058
|
+
const g = globalThis;
|
|
10059
|
+
try {
|
|
10060
|
+
g.dispatchEvent?.(new Event(FLY_WINDOW_BREADCRUMBS_EVENT));
|
|
10061
|
+
}
|
|
10062
|
+
catch {
|
|
10063
|
+
// Non-DOM realm (SSR / a unit env without `Event`) — see the identical note on
|
|
10064
|
+
// `MagicBarRegistry.emitSync`. The instance that mutated the store is
|
|
10065
|
+
// unaffected; any OTHER instance in such a realm stays stale until its own
|
|
10066
|
+
// next mutation calls `sync()` directly.
|
|
10067
|
+
}
|
|
10068
|
+
}
|
|
10069
|
+
/**
|
|
10070
|
+
* Where an app publishes the breadcrumb trail its window's header strip renders.
|
|
10071
|
+
*
|
|
10072
|
+
* The design puts a trail inside the window
|
|
10073
|
+
* (`UX/FlyOS Desktop-app/MainContent.dc.html`, `nav[aria-label="Breadcrumb"]`), and
|
|
10074
|
+
* the window chrome (`WindowComponent`) renders whatever this registry holds for
|
|
10075
|
+
* its window id, falling back to the plain window title when nothing has published.
|
|
10076
|
+
*
|
|
10077
|
+
* ## Why the state lives on `globalThis`
|
|
10078
|
+
*
|
|
10079
|
+
* Same reasoning as {@link MagicBarRegistry} (`services/magic-bar/`), and the same
|
|
10080
|
+
* direction of travel (remote → shell chrome). This service is `providedIn: 'root'`,
|
|
10081
|
+
* but a root instance is not *reliably* shared across the federation boundary: the
|
|
10082
|
+
* shell builds the design system from workspace source while remotes consume the
|
|
10083
|
+
* published package, and Native Federation collapses those into one runtime
|
|
10084
|
+
* instance only when version negotiation succeeds — a split there is invisible at
|
|
10085
|
+
* build time and would otherwise present as a window whose breadcrumb silently
|
|
10086
|
+
* never appears, for federated apps only. Keeping the state on the one substrate
|
|
10087
|
+
* every bundle shares makes this store fork-proof: whichever copy of this service
|
|
10088
|
+
* publishes, every copy's `trail()` agrees. Each instance mirrors the store into a
|
|
10089
|
+
* local signal and re-reads on the sync event, so `WindowComponent`'s template —
|
|
10090
|
+
* which reads `trail()` inside its own reactive context — repaints whichever
|
|
10091
|
+
* bundle actually published.
|
|
10092
|
+
*
|
|
10093
|
+
* Simpler than `MagicBarRegistry` on purpose: a breadcrumb trail is addressed
|
|
10094
|
+
* directly by the window id its publisher already has (via `WINDOW_DATA`), so
|
|
10095
|
+
* there is no owner-vs-active-window arbitration, no per-action indexing and no
|
|
10096
|
+
* publish-fingerprint dedup to carry — `publish()` just replaces the entry.
|
|
10097
|
+
*
|
|
10098
|
+
* ```ts
|
|
10099
|
+
* const win = inject(WINDOW_DATA);
|
|
10100
|
+
* const crumbs = inject(WindowBreadcrumbsRegistry);
|
|
10101
|
+
* crumbs.publish(win.id, [
|
|
10102
|
+
* { id: 'root', label: circleName, navigate: () => this.showList() },
|
|
10103
|
+
* { id: 'trend', label: trend.title },
|
|
10104
|
+
* ]);
|
|
10105
|
+
* ```
|
|
10106
|
+
*/
|
|
10107
|
+
class WindowBreadcrumbsRegistry {
|
|
10108
|
+
_trails = signal(new Map(), ...(ngDevMode ? [{ debugName: "_trails" }] : /* istanbul ignore next */ []));
|
|
10109
|
+
_syncedStore = null;
|
|
10110
|
+
_syncedRevision = -1;
|
|
10111
|
+
/** Every published trail, keyed by window id. Reactive — read it inside a template/computed/effect. */
|
|
10112
|
+
all = this._trails.asReadonly();
|
|
10113
|
+
constructor() {
|
|
10114
|
+
const g = globalThis;
|
|
10115
|
+
g.addEventListener?.(FLY_WINDOW_BREADCRUMBS_EVENT, () => this.sync());
|
|
10116
|
+
this.sync();
|
|
10117
|
+
}
|
|
10118
|
+
/**
|
|
10119
|
+
* This window's published trail, or an empty array.
|
|
10120
|
+
*
|
|
10121
|
+
* A plain method over a signal read, not a per-id `computed`: it is called from
|
|
10122
|
+
* the window template, where the signal read is tracked whatever the call depth,
|
|
10123
|
+
* and a `computed` cache keyed by a mutable window id would outlive its window.
|
|
10124
|
+
*/
|
|
10125
|
+
trail(windowId) {
|
|
10126
|
+
return this._trails().get(windowId) ?? EMPTY_TRAIL;
|
|
10127
|
+
}
|
|
10128
|
+
/**
|
|
10129
|
+
* Replace a window's trail. Publishing an empty array is how an app goes back to
|
|
10130
|
+
* showing the plain window title — it is a state, not a clear.
|
|
10131
|
+
*/
|
|
10132
|
+
publish(windowId, crumbs) {
|
|
10133
|
+
const s = store$1();
|
|
10134
|
+
s.trails = { ...s.trails, [windowId]: [...crumbs] };
|
|
10135
|
+
s.revision++;
|
|
10136
|
+
this.sync();
|
|
10137
|
+
emitSync$1();
|
|
10138
|
+
}
|
|
10139
|
+
/** Drop a window's trail entirely. The window chrome calls this when it is destroyed. */
|
|
10140
|
+
clear(windowId) {
|
|
10141
|
+
const s = store$1();
|
|
10142
|
+
if (!(windowId in s.trails))
|
|
10143
|
+
return;
|
|
10144
|
+
const trails = { ...s.trails };
|
|
10145
|
+
delete trails[windowId];
|
|
10146
|
+
s.trails = trails;
|
|
10147
|
+
s.revision++;
|
|
10148
|
+
this.sync();
|
|
10149
|
+
emitSync$1();
|
|
10150
|
+
}
|
|
10151
|
+
/**
|
|
10152
|
+
* Re-read the shared store. Cheap no-op unless a real mutation landed.
|
|
10153
|
+
*
|
|
10154
|
+
* Keys on store IDENTITY as well as revision — see the identical note on
|
|
10155
|
+
* `MagicBarRegistry.sync`: a revision counter alone cannot distinguish "nothing
|
|
10156
|
+
* changed" from "the `globalThis` slot now holds a brand-new store that happens
|
|
10157
|
+
* to be at the same revision" (every spec's `beforeEach` replaces the store).
|
|
10158
|
+
*/
|
|
10159
|
+
sync() {
|
|
10160
|
+
const s = store$1();
|
|
10161
|
+
if (s === this._syncedStore && s.revision === this._syncedRevision)
|
|
10162
|
+
return;
|
|
10163
|
+
this._syncedStore = s;
|
|
10164
|
+
this._syncedRevision = s.revision;
|
|
10165
|
+
this._trails.set(new Map(Object.entries(s.trails)));
|
|
10166
|
+
}
|
|
10167
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: WindowBreadcrumbsRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
10168
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: WindowBreadcrumbsRegistry, providedIn: 'root' });
|
|
10169
|
+
}
|
|
10170
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: WindowBreadcrumbsRegistry, decorators: [{
|
|
10171
|
+
type: Injectable,
|
|
10172
|
+
args: [{ providedIn: 'root' }]
|
|
10173
|
+
}], ctorParameters: () => [] });
|
|
10174
|
+
/** Shared empty result — a fresh `[]` per read would re-render every window on every pass. */
|
|
10175
|
+
const EMPTY_TRAIL = Object.freeze([]);
|
|
10176
|
+
|
|
10177
|
+
/** Public window-breadcrumbs surface — see `window-breadcrumbs-registry.service.ts`. */
|
|
10178
|
+
|
|
9983
10179
|
/**
|
|
9984
10180
|
* Headless keyboard-navigation logic — roving focus for menus, radiogroups, and
|
|
9985
10181
|
* tablists.
|
|
@@ -10933,10 +11129,10 @@ class FlyFileUploadComponent {
|
|
|
10933
11129
|
const current = this.files().length + this.slots().filter(s => s.status === 'uploading').length;
|
|
10934
11130
|
return current < this.maxFiles();
|
|
10935
11131
|
}, ...(ngDevMode ? [{ debugName: "canAddMore" }] : /* istanbul ignore next */ []));
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
}, ...(ngDevMode ? [{ debugName: "
|
|
11132
|
+
limitHintParams = computed(() => ({
|
|
11133
|
+
n: this.maxFiles(),
|
|
11134
|
+
mb: this.maxFileSizeBytes() / (1024 * 1024),
|
|
11135
|
+
}), ...(ngDevMode ? [{ debugName: "limitHintParams" }] : /* istanbul ignore next */ []));
|
|
10940
11136
|
triggerFileInput() {
|
|
10941
11137
|
this.fileInput()?.nativeElement.click();
|
|
10942
11138
|
}
|
|
@@ -11037,11 +11233,11 @@ class FlyFileUploadComponent {
|
|
|
11037
11233
|
});
|
|
11038
11234
|
}
|
|
11039
11235
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFileUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11040
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyFileUploadComponent, isStandalone: true, selector: "fly-file-upload", inputs: { maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, maxFileSizeBytes: { classPropertyName: "maxFileSizeBytes", publicName: "maxFileSizeBytes", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, sourceApp: { classPropertyName: "sourceApp", publicName: "sourceApp", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityType: { classPropertyName: "sourceEntityType", publicName: "sourceEntityType", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityId: { classPropertyName: "sourceEntityId", publicName: "sourceEntityId", isSignal: true, isRequired: false, transformFunction: null }, existingFileIds: { classPropertyName: "existingFileIds", publicName: "existingFileIds", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { files: "filesChange", filesChanged: "filesChanged" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fly-file-upload\">\r\n @if (canAddMore()) {\r\n <button type=\"button\" class=\"fly-file-upload__dropzone\"\r\n [class.fly-file-upload__dropzone--drag]=\"dragging()\"\r\n (click)=\"triggerFileInput()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n <span class=\"pi pi-cloud-upload fly-file-upload__icon\" aria-hidden=\"true\"></span>\r\n <span class=\"fly-file-upload__label\">{{ 'files.fileUpload.dropOrClick' | translate }}</span>\r\n <span class=\"fly-file-upload__hint\">{{ limitHint() }}</span>\r\n </button>\r\n }\r\n\r\n @if (error()) {\r\n <div class=\"fly-file-upload__error\">{{ error() }}</div>\r\n }\r\n\r\n @if (allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (slot of allSlots(); track slot.file.name + slot.file.size) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconFor(slot) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ slot.file.name }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(slot.file.size) }}</span>\r\n </div>\r\n @if (slot.status === 'uploading') {\r\n <div class=\"fly-file-upload__progress-track\">\r\n <div class=\"fly-file-upload__progress-fill\" [style.width.%]=\"slot.progress\"></div>\r\n </div>\r\n }\r\n @if (slot.status === 'error') {\r\n <span class=\"fly-file-upload__file-error\" [title]=\"slot.error ?? ''\">\r\n <span class=\"pi pi-exclamation-triangle\" aria-hidden=\"true\"></span>\r\n </span>\r\n }\r\n @if (slot.status === 'done') {\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n }\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeSlot(slot)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (existingFiles().length && !allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (f of existingFiles(); track f.id) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconForInfo(f) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ f.fileName }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(f.sizeBytes) }}</span>\r\n </div>\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeExisting(f)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept()\" multiple class=\"fly-file-upload__hidden\" (change)=\"onFilesSelected($event)\" />\r\n</div>\r\n", styles: [".fly-file-upload{display:flex;flex-direction:column;gap:8px}.fly-file-upload__hidden{display:none}.fly-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:20px;border:2px dashed var(--
|
|
11236
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyFileUploadComponent, isStandalone: true, selector: "fly-file-upload", inputs: { maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, maxFileSizeBytes: { classPropertyName: "maxFileSizeBytes", publicName: "maxFileSizeBytes", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, sourceApp: { classPropertyName: "sourceApp", publicName: "sourceApp", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityType: { classPropertyName: "sourceEntityType", publicName: "sourceEntityType", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityId: { classPropertyName: "sourceEntityId", publicName: "sourceEntityId", isSignal: true, isRequired: false, transformFunction: null }, existingFileIds: { classPropertyName: "existingFileIds", publicName: "existingFileIds", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { files: "filesChange", filesChanged: "filesChanged" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fly-file-upload\">\r\n @if (canAddMore()) {\r\n <button type=\"button\" class=\"fly-file-upload__dropzone\"\r\n [class.fly-file-upload__dropzone--drag]=\"dragging()\"\r\n (click)=\"triggerFileInput()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n <span class=\"pi pi-cloud-upload fly-file-upload__icon\" aria-hidden=\"true\"></span>\r\n <span class=\"fly-file-upload__label\">{{ 'files.fileUpload.dropOrClick' | translate }}</span>\r\n <span class=\"fly-file-upload__hint\">{{ 'files.fileUpload.limitHint' | translate: limitHintParams() }}</span>\r\n </button>\r\n }\r\n\r\n @if (error()) {\r\n <div class=\"fly-file-upload__error\">{{ error() }}</div>\r\n }\r\n\r\n @if (allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (slot of allSlots(); track slot.file.name + slot.file.size) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconFor(slot) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ slot.file.name }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(slot.file.size) }}</span>\r\n </div>\r\n @if (slot.status === 'uploading') {\r\n <div class=\"fly-file-upload__progress-track\">\r\n <div class=\"fly-file-upload__progress-fill\" [style.width.%]=\"slot.progress\"></div>\r\n </div>\r\n }\r\n @if (slot.status === 'error') {\r\n <span class=\"fly-file-upload__file-error\" [title]=\"slot.error ?? ''\">\r\n <span class=\"pi pi-exclamation-triangle\" aria-hidden=\"true\"></span>\r\n </span>\r\n }\r\n @if (slot.status === 'done') {\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n }\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeSlot(slot)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (existingFiles().length && !allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (f of existingFiles(); track f.id) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconForInfo(f) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ f.fileName }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(f.sizeBytes) }}</span>\r\n </div>\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeExisting(f)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept()\" multiple class=\"fly-file-upload__hidden\" (change)=\"onFilesSelected($event)\" />\r\n</div>\r\n", styles: [".fly-file-upload{display:flex;flex-direction:column;gap:8px}.fly-file-upload__hidden{display:none}.fly-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:20px;border:2px dashed var(--line, #e5e7eb);border-radius:10px;background:var(--bg-3, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s}.fly-file-upload__dropzone:hover,.fly-file-upload__dropzone--drag{border-color:var(--accent, #0071e3);background:var(--bg-hover, #f3f4f6)}.fly-file-upload__icon{font-size:24px;color:var(--ink-4, #9ca3af)}.fly-file-upload__label{font-size:13px;color:var(--ink-3, #6b7280)}.fly-file-upload__hint{font-size:11px;color:var(--ink-4, #9ca3af)}.fly-file-upload__error{font-size:12px;color:var(--danger, #ef4444);padding:4px 0}.fly-file-upload__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}.fly-file-upload__item{display:flex;align-items:center;gap:10px;padding:8px 12px;border:1px solid var(--line, #e5e7eb);border-radius:8px;background:var(--bg-3, #f9fafb);font-size:13px}.fly-file-upload__file-icon{font-size:18px;color:var(--ink-4, #9ca3af);flex-shrink:0}.fly-file-upload__file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.fly-file-upload__file-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ink, #1f2937)}.fly-file-upload__file-size{font-size:11px;color:var(--ink-4, #9ca3af)}.fly-file-upload__progress-track{width:60px;height:4px;border-radius:2px;background:var(--bg-hover, #e5e7eb);overflow:hidden}.fly-file-upload__progress-fill{height:100%;background:var(--accent, #0071e3);border-radius:2px;transition:width .2s}.fly-file-upload__file-ok{color:var(--success, #22c55e);font-size:14px}.fly-file-upload__file-error{color:var(--danger, #ef4444);font-size:14px}.fly-file-upload__remove-btn{background:none;border:none;cursor:pointer;color:var(--ink-4, #9ca3af);width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:color .15s,background .15s;flex-shrink:0}.fly-file-upload__remove-btn:hover{color:var(--danger, #ef4444);background:var(--bg-hover, #f3f4f6)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11041
11237
|
}
|
|
11042
11238
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFileUploadComponent, decorators: [{
|
|
11043
11239
|
type: Component,
|
|
11044
|
-
args: [{ selector: 'fly-file-upload', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fly-file-upload\">\r\n @if (canAddMore()) {\r\n <button type=\"button\" class=\"fly-file-upload__dropzone\"\r\n [class.fly-file-upload__dropzone--drag]=\"dragging()\"\r\n (click)=\"triggerFileInput()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n <span class=\"pi pi-cloud-upload fly-file-upload__icon\" aria-hidden=\"true\"></span>\r\n <span class=\"fly-file-upload__label\">{{ 'files.fileUpload.dropOrClick' | translate }}</span>\r\n <span class=\"fly-file-upload__hint\">{{ limitHint() }}</span>\r\n </button>\r\n }\r\n\r\n @if (error()) {\r\n <div class=\"fly-file-upload__error\">{{ error() }}</div>\r\n }\r\n\r\n @if (allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (slot of allSlots(); track slot.file.name + slot.file.size) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconFor(slot) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ slot.file.name }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(slot.file.size) }}</span>\r\n </div>\r\n @if (slot.status === 'uploading') {\r\n <div class=\"fly-file-upload__progress-track\">\r\n <div class=\"fly-file-upload__progress-fill\" [style.width.%]=\"slot.progress\"></div>\r\n </div>\r\n }\r\n @if (slot.status === 'error') {\r\n <span class=\"fly-file-upload__file-error\" [title]=\"slot.error ?? ''\">\r\n <span class=\"pi pi-exclamation-triangle\" aria-hidden=\"true\"></span>\r\n </span>\r\n }\r\n @if (slot.status === 'done') {\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n }\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeSlot(slot)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (existingFiles().length && !allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (f of existingFiles(); track f.id) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconForInfo(f) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ f.fileName }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(f.sizeBytes) }}</span>\r\n </div>\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeExisting(f)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept()\" multiple class=\"fly-file-upload__hidden\" (change)=\"onFilesSelected($event)\" />\r\n</div>\r\n", styles: [".fly-file-upload{display:flex;flex-direction:column;gap:8px}.fly-file-upload__hidden{display:none}.fly-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:20px;border:2px dashed var(--
|
|
11240
|
+
args: [{ selector: 'fly-file-upload', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fly-file-upload\">\r\n @if (canAddMore()) {\r\n <button type=\"button\" class=\"fly-file-upload__dropzone\"\r\n [class.fly-file-upload__dropzone--drag]=\"dragging()\"\r\n (click)=\"triggerFileInput()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n <span class=\"pi pi-cloud-upload fly-file-upload__icon\" aria-hidden=\"true\"></span>\r\n <span class=\"fly-file-upload__label\">{{ 'files.fileUpload.dropOrClick' | translate }}</span>\r\n <span class=\"fly-file-upload__hint\">{{ 'files.fileUpload.limitHint' | translate: limitHintParams() }}</span>\r\n </button>\r\n }\r\n\r\n @if (error()) {\r\n <div class=\"fly-file-upload__error\">{{ error() }}</div>\r\n }\r\n\r\n @if (allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (slot of allSlots(); track slot.file.name + slot.file.size) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconFor(slot) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ slot.file.name }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(slot.file.size) }}</span>\r\n </div>\r\n @if (slot.status === 'uploading') {\r\n <div class=\"fly-file-upload__progress-track\">\r\n <div class=\"fly-file-upload__progress-fill\" [style.width.%]=\"slot.progress\"></div>\r\n </div>\r\n }\r\n @if (slot.status === 'error') {\r\n <span class=\"fly-file-upload__file-error\" [title]=\"slot.error ?? ''\">\r\n <span class=\"pi pi-exclamation-triangle\" aria-hidden=\"true\"></span>\r\n </span>\r\n }\r\n @if (slot.status === 'done') {\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n }\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeSlot(slot)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (existingFiles().length && !allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (f of existingFiles(); track f.id) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconForInfo(f) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ f.fileName }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(f.sizeBytes) }}</span>\r\n </div>\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeExisting(f)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept()\" multiple class=\"fly-file-upload__hidden\" (change)=\"onFilesSelected($event)\" />\r\n</div>\r\n", styles: [".fly-file-upload{display:flex;flex-direction:column;gap:8px}.fly-file-upload__hidden{display:none}.fly-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:20px;border:2px dashed var(--line, #e5e7eb);border-radius:10px;background:var(--bg-3, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s}.fly-file-upload__dropzone:hover,.fly-file-upload__dropzone--drag{border-color:var(--accent, #0071e3);background:var(--bg-hover, #f3f4f6)}.fly-file-upload__icon{font-size:24px;color:var(--ink-4, #9ca3af)}.fly-file-upload__label{font-size:13px;color:var(--ink-3, #6b7280)}.fly-file-upload__hint{font-size:11px;color:var(--ink-4, #9ca3af)}.fly-file-upload__error{font-size:12px;color:var(--danger, #ef4444);padding:4px 0}.fly-file-upload__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}.fly-file-upload__item{display:flex;align-items:center;gap:10px;padding:8px 12px;border:1px solid var(--line, #e5e7eb);border-radius:8px;background:var(--bg-3, #f9fafb);font-size:13px}.fly-file-upload__file-icon{font-size:18px;color:var(--ink-4, #9ca3af);flex-shrink:0}.fly-file-upload__file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.fly-file-upload__file-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ink, #1f2937)}.fly-file-upload__file-size{font-size:11px;color:var(--ink-4, #9ca3af)}.fly-file-upload__progress-track{width:60px;height:4px;border-radius:2px;background:var(--bg-hover, #e5e7eb);overflow:hidden}.fly-file-upload__progress-fill{height:100%;background:var(--accent, #0071e3);border-radius:2px;transition:width .2s}.fly-file-upload__file-ok{color:var(--success, #22c55e);font-size:14px}.fly-file-upload__file-error{color:var(--danger, #ef4444);font-size:14px}.fly-file-upload__remove-btn{background:none;border:none;cursor:pointer;color:var(--ink-4, #9ca3af);width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:color .15s,background .15s;flex-shrink:0}.fly-file-upload__remove-btn:hover{color:var(--danger, #ef4444);background:var(--bg-hover, #f3f4f6)}\n"] }]
|
|
11045
11241
|
}], ctorParameters: () => [], propDecorators: { maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], maxFileSizeBytes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSizeBytes", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], sourceApp: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceApp", required: false }] }], sourceEntityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityType", required: false }] }], sourceEntityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityId", required: false }] }], existingFileIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFileIds", required: false }] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }, { type: i0.Output, args: ["filesChange"] }], filesChanged: [{ type: i0.Output, args: ["filesChanged"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
|
|
11046
11242
|
|
|
11047
11243
|
/** Process-wide counter so every instance owns unique element ids (`aria-controls`,
|
|
@@ -12768,6 +12964,53 @@ function resolveLinks(deps, renderableIds) {
|
|
|
12768
12964
|
}
|
|
12769
12965
|
return out;
|
|
12770
12966
|
}
|
|
12967
|
+
/** Straight run (px) a dependency elbow travels out of a bar edge before it turns. */
|
|
12968
|
+
const LINK_STUB = 12;
|
|
12969
|
+
/**
|
|
12970
|
+
* Relationship kind implied by the edge a gesture left and the edge it landed on. The name is
|
|
12971
|
+
* literally `<predecessor endpoint><successor endpoint>`, so this is a lookup, not a rule.
|
|
12972
|
+
*/
|
|
12973
|
+
function linkTypeFor(from, to) {
|
|
12974
|
+
if (from === 'finish')
|
|
12975
|
+
return to === 'start' ? 'FS' : 'FF';
|
|
12976
|
+
return to === 'start' ? 'SS' : 'SF';
|
|
12977
|
+
}
|
|
12978
|
+
/**
|
|
12979
|
+
* Route a dependency elbow for any of the four relationship kinds (render space).
|
|
12980
|
+
*
|
|
12981
|
+
* The kind names the two endpoints, so it decides both the anchor x's and the direction the
|
|
12982
|
+
* arrow travels: leaving a *finish* edge moves forward in time, leaving a *start* edge moves
|
|
12983
|
+
* backward; entering a *start* edge approaches from behind, entering a *finish* edge from
|
|
12984
|
+
* ahead. `fwd` folds RTL in, so none of that branches on direction.
|
|
12985
|
+
*
|
|
12986
|
+
* One turn suffices when the entry stub lies ahead of the exit stub in the approach
|
|
12987
|
+
* direction. When it does not — overlapping or reversed bars, which SS/FF/SF hit constantly —
|
|
12988
|
+
* a straight drop would cut back through the bars, so the elbow detours via the gap between
|
|
12989
|
+
* the two rows instead.
|
|
12990
|
+
*/
|
|
12991
|
+
function routeLink(from, to, type, rtl, rowHeight) {
|
|
12992
|
+
const fwd = rtl ? -1 : 1;
|
|
12993
|
+
const fromFinish = type === 'FS' || type === 'FF';
|
|
12994
|
+
const toStart = type === 'FS' || type === 'SS';
|
|
12995
|
+
const x1 = from.shape === 'milestone' ? from.pointX : fromFinish ? from.endX : from.startX;
|
|
12996
|
+
const x2 = to.shape === 'milestone' ? to.pointX : toStart ? to.startX : to.endX;
|
|
12997
|
+
const y1 = from.midY;
|
|
12998
|
+
const y2 = to.midY;
|
|
12999
|
+
const d1 = (fromFinish ? 1 : -1) * fwd; // exit direction
|
|
13000
|
+
const d2 = (toStart ? 1 : -1) * fwd; // approach direction
|
|
13001
|
+
const exit = x1 + LINK_STUB * d1;
|
|
13002
|
+
const entry = x2 - LINK_STUB * d2;
|
|
13003
|
+
if ((entry - exit) * d2 >= 0) {
|
|
13004
|
+
return { path: `M ${x1} ${y1} H ${exit} V ${y2} H ${x2}`, badgeX: exit, badgeY: (y1 + y2) / 2 };
|
|
13005
|
+
}
|
|
13006
|
+
// Distinct rows always differ in y; the guard only keeps a degenerate input off the bar.
|
|
13007
|
+
const yMid = y1 === y2 ? y1 - rowHeight / 2 : (y1 + y2) / 2;
|
|
13008
|
+
return {
|
|
13009
|
+
path: `M ${x1} ${y1} H ${exit} V ${yMid} H ${entry} V ${y2} H ${x2}`,
|
|
13010
|
+
badgeX: entry,
|
|
13011
|
+
badgeY: yMid,
|
|
13012
|
+
};
|
|
13013
|
+
}
|
|
12771
13014
|
// ── RTL coordinate mapping ───────────────────────────────────────────────────
|
|
12772
13015
|
/** Mirror a logical x into render space. LTR is identity; RTL reflects about the canvas width. */
|
|
12773
13016
|
function mapX(logicalX, innerWidth, rtl) {
|
|
@@ -12868,6 +13111,257 @@ function weekendBands(domain, zoom, pxPerDay) {
|
|
|
12868
13111
|
return bands;
|
|
12869
13112
|
}
|
|
12870
13113
|
|
|
13114
|
+
/** Label-pane width bounds (px) the splitter clamps to. */
|
|
13115
|
+
const GANTT_LABEL_W_MIN = 140;
|
|
13116
|
+
const GANTT_LABEL_W_MAX = 720;
|
|
13117
|
+
/** Keyboard resize steps (plain arrow / with Shift). */
|
|
13118
|
+
const LABEL_STEP = 16;
|
|
13119
|
+
const LABEL_STEP_COARSE = 64;
|
|
13120
|
+
/** Clamp any candidate label-pane width into the splitter's range. */
|
|
13121
|
+
function clampGanttLabelWidth(px) {
|
|
13122
|
+
return Math.max(GANTT_LABEL_W_MIN, Math.min(GANTT_LABEL_W_MAX, Math.round(px)));
|
|
13123
|
+
}
|
|
13124
|
+
/**
|
|
13125
|
+
* The Gantt's label-pane divider: width state plus the pointer and keyboard resize gestures.
|
|
13126
|
+
*
|
|
13127
|
+
* ## Why this is not in the component
|
|
13128
|
+
* `fly-gantt` is a large component by nature — it owns a two-pane layout, a time scale, four row
|
|
13129
|
+
* shapes, drag/resize/link gestures and dependency routing. The pane divider is the one region
|
|
13130
|
+
* with *no* coupling to any of that: it needs a direction, a fallback width and somewhere to
|
|
13131
|
+
* report a committed value, and nothing else. Keeping it here means the component's remaining
|
|
13132
|
+
* bulk is all chart, and the divider's RTL rules — which are the subtle part — are stated in one
|
|
13133
|
+
* place and unit-testable without rendering an SVG.
|
|
13134
|
+
*
|
|
13135
|
+
* ## The RTL rule, once
|
|
13136
|
+
* The label pane sits on the inline-START side in both directions, so "drag outward = wider"
|
|
13137
|
+
* flips physical sign with direction, and the arrow key that widens is the one pointing AWAY
|
|
13138
|
+
* from the pane (`ArrowRight` in LTR, `ArrowLeft` in RTL). Both facts are encoded here rather
|
|
13139
|
+
* than at each call site, which is what stops one of the two gestures being mirrored and the
|
|
13140
|
+
* other not — the failure this consolidation exists to prevent.
|
|
13141
|
+
*/
|
|
13142
|
+
class GanttLabelPane {
|
|
13143
|
+
deps;
|
|
13144
|
+
/** True for the duration of a pointer drag — the host paints a resizing affordance. */
|
|
13145
|
+
resizing = signal(false, ...(ngDevMode ? [{ debugName: "resizing" }] : /* istanbul ignore next */ []));
|
|
13146
|
+
/** The user's dragged width, or `null` while the consumer's input is in charge. */
|
|
13147
|
+
_override = signal(null, ...(ngDevMode ? [{ debugName: "_override" }] : /* istanbul ignore next */ []));
|
|
13148
|
+
/** The width to render: the user's drag when present, else the clamped input. */
|
|
13149
|
+
width = computed(() => clampGanttLabelWidth(this._override() ?? this.deps.inputWidth()), ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
13150
|
+
constructor(deps) {
|
|
13151
|
+
this.deps = deps;
|
|
13152
|
+
}
|
|
13153
|
+
/**
|
|
13154
|
+
* Pointer-drag the divider. Forward-in-time is `+x` LTR and `-x` RTL, and the label pane sits
|
|
13155
|
+
* on the inline-start side in both, so the same sign flip that mirrors the timeline also
|
|
13156
|
+
* mirrors "drag outward = wider".
|
|
13157
|
+
*
|
|
13158
|
+
* Listeners go on `document`, not the divider: a fast drag outruns the 8px-wide handle, and a
|
|
13159
|
+
* pointer that leaves it would otherwise strand the gesture mid-resize.
|
|
13160
|
+
*/
|
|
13161
|
+
onPointerDown(ev) {
|
|
13162
|
+
if (!this.deps.resizable() || ev.button !== 0)
|
|
13163
|
+
return;
|
|
13164
|
+
ev.preventDefault();
|
|
13165
|
+
ev.stopPropagation();
|
|
13166
|
+
const startClientX = ev.clientX;
|
|
13167
|
+
const startWidth = this.width();
|
|
13168
|
+
const dirSign = this.deps.rtl() ? -1 : 1;
|
|
13169
|
+
this.resizing.set(true);
|
|
13170
|
+
const onMove = (e) => {
|
|
13171
|
+
this._override.set(clampGanttLabelWidth(startWidth + (e.clientX - startClientX) * dirSign));
|
|
13172
|
+
};
|
|
13173
|
+
const onUp = () => {
|
|
13174
|
+
document.removeEventListener('pointermove', onMove);
|
|
13175
|
+
document.removeEventListener('pointerup', onUp);
|
|
13176
|
+
this.resizing.set(false);
|
|
13177
|
+
const final = this.width();
|
|
13178
|
+
// Only a width that actually MOVED is committed: a plain click on the divider must not
|
|
13179
|
+
// emit, or a consumer persisting the value would write on every stray press.
|
|
13180
|
+
if (final !== startWidth)
|
|
13181
|
+
this.deps.commit(final);
|
|
13182
|
+
};
|
|
13183
|
+
document.addEventListener('pointermove', onMove);
|
|
13184
|
+
document.addEventListener('pointerup', onUp);
|
|
13185
|
+
this.deps.destroyRef.onDestroy(() => {
|
|
13186
|
+
document.removeEventListener('pointermove', onMove);
|
|
13187
|
+
document.removeEventListener('pointerup', onUp);
|
|
13188
|
+
});
|
|
13189
|
+
}
|
|
13190
|
+
/**
|
|
13191
|
+
* Keyboard resize on the focused divider. ArrowRight/Left grow/shrink by the *inline* meaning
|
|
13192
|
+
* of the key, so under RTL the arrow that points away from the pane is still the one that
|
|
13193
|
+
* widens it. `Home`/`End` jump to the bounds, Enter/Escape restore the input width.
|
|
13194
|
+
*
|
|
13195
|
+
* Typed `Event` because Angular's strict template checker types a `(keydown)` `$event` as
|
|
13196
|
+
* `Event`; the cast is done here once rather than at the binding.
|
|
13197
|
+
*/
|
|
13198
|
+
onKeydown(ev) {
|
|
13199
|
+
if (!this.deps.resizable())
|
|
13200
|
+
return;
|
|
13201
|
+
const key = ev.key;
|
|
13202
|
+
const coarse = ev.shiftKey;
|
|
13203
|
+
const step = coarse ? LABEL_STEP_COARSE : LABEL_STEP;
|
|
13204
|
+
const rtl = this.deps.rtl();
|
|
13205
|
+
const grow = rtl ? 'ArrowLeft' : 'ArrowRight';
|
|
13206
|
+
const shrink = rtl ? 'ArrowRight' : 'ArrowLeft';
|
|
13207
|
+
let next;
|
|
13208
|
+
if (key === grow)
|
|
13209
|
+
next = this.width() + step;
|
|
13210
|
+
else if (key === shrink)
|
|
13211
|
+
next = this.width() - step;
|
|
13212
|
+
else if (key === 'Home')
|
|
13213
|
+
next = GANTT_LABEL_W_MIN;
|
|
13214
|
+
else if (key === 'End')
|
|
13215
|
+
next = GANTT_LABEL_W_MAX;
|
|
13216
|
+
else if (key === 'Enter' || key === 'Escape') {
|
|
13217
|
+
// Stop the grid-level Enter handler from also activating the selected row.
|
|
13218
|
+
ev.preventDefault();
|
|
13219
|
+
ev.stopPropagation();
|
|
13220
|
+
this.reset();
|
|
13221
|
+
return;
|
|
13222
|
+
}
|
|
13223
|
+
else
|
|
13224
|
+
return;
|
|
13225
|
+
ev.preventDefault();
|
|
13226
|
+
ev.stopPropagation();
|
|
13227
|
+
const clamped = clampGanttLabelWidth(next);
|
|
13228
|
+
// Already at a bound: swallow the key (it was ours) but emit nothing.
|
|
13229
|
+
if (clamped === this.width())
|
|
13230
|
+
return;
|
|
13231
|
+
this._override.set(clamped);
|
|
13232
|
+
this.deps.commit(clamped);
|
|
13233
|
+
}
|
|
13234
|
+
/** Drop the user's dragged width and fall back to the consumer's input width. */
|
|
13235
|
+
reset() {
|
|
13236
|
+
if (this._override() === null)
|
|
13237
|
+
return;
|
|
13238
|
+
this._override.set(null);
|
|
13239
|
+
this.deps.commit(this.width());
|
|
13240
|
+
}
|
|
13241
|
+
}
|
|
13242
|
+
|
|
13243
|
+
/**
|
|
13244
|
+
* Drawing a dependency by dragging from one end of a bar to another — the gesture, the drop-target
|
|
13245
|
+
* resolution and the resulting relationship type.
|
|
13246
|
+
*
|
|
13247
|
+
* ## Why this is not in the component
|
|
13248
|
+
* `fly-gantt` already owns a two-pane layout, a time scale, four row shapes and bar move/resize.
|
|
13249
|
+
* This is a self-contained second gesture with one output, and its interesting part is a *rule*
|
|
13250
|
+
* rather than any rendering: which end you START on plus which end you DROP nearest decides which
|
|
13251
|
+
* of the four MS-Project relationships you get. Stating that rule beside the hit-testing that
|
|
13252
|
+
* feeds it — rather than in the middle of a 700-line component — is what makes it reviewable.
|
|
13253
|
+
*
|
|
13254
|
+
* ## The selection is here too, and deliberately
|
|
13255
|
+
* `selectedKey` is the link the user has clicked, which is what the delete affordance and the
|
|
13256
|
+
* keyboard's Delete both act on. It lives with the gesture because both are "the user is
|
|
13257
|
+
* manipulating dependencies", and keeping them together stops a click-selection surviving a drag
|
|
13258
|
+
* that replaced the very link it pointed at.
|
|
13259
|
+
*/
|
|
13260
|
+
class GanttLinkGestures {
|
|
13261
|
+
deps;
|
|
13262
|
+
/** The in-flight drag, or `null`. */
|
|
13263
|
+
gesture = signal(null, ...(ngDevMode ? [{ debugName: "gesture" }] : /* istanbul ignore next */ []));
|
|
13264
|
+
/** The clicked link's key, or `null`. Drives the delete affordance and the Delete key. */
|
|
13265
|
+
selectedKey = signal(null, ...(ngDevMode ? [{ debugName: "selectedKey" }] : /* istanbul ignore next */ []));
|
|
13266
|
+
/** Rubber-band path for the in-flight gesture (render space), or `null` when idle. */
|
|
13267
|
+
path = computed(() => {
|
|
13268
|
+
const g = this.gesture();
|
|
13269
|
+
return g ? `M ${g.x0} ${g.y0} L ${g.x1} ${g.y1}` : null;
|
|
13270
|
+
}, ...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
|
|
13271
|
+
constructor(deps) {
|
|
13272
|
+
this.deps = deps;
|
|
13273
|
+
}
|
|
13274
|
+
/** Clear any link selection — the host calls this when the user selects a row instead. */
|
|
13275
|
+
clearSelection() {
|
|
13276
|
+
this.selectedKey.set(null);
|
|
13277
|
+
}
|
|
13278
|
+
/** Toggle the clicked link's selection. */
|
|
13279
|
+
toggleSelection(key) {
|
|
13280
|
+
this.selectedKey.set(this.selectedKey() === key ? null : key);
|
|
13281
|
+
}
|
|
13282
|
+
/**
|
|
13283
|
+
* Start a link gesture from one end of a bar. Which end it started on, plus which end of the
|
|
13284
|
+
* target row it is dropped nearest to, is what selects the dependency type — so all four
|
|
13285
|
+
* MS-Project relationships are reachable with the same single drag.
|
|
13286
|
+
*
|
|
13287
|
+
* Listeners go on `document`, not the connector: the connector is an 8px dot and any real drag
|
|
13288
|
+
* leaves it immediately.
|
|
13289
|
+
*/
|
|
13290
|
+
start(ev, row, anchor) {
|
|
13291
|
+
if (this.deps.readonly() || row.flat.row.readonly || ev.button !== 0)
|
|
13292
|
+
return;
|
|
13293
|
+
ev.preventDefault();
|
|
13294
|
+
ev.stopPropagation();
|
|
13295
|
+
const local = this.toBodyPoint(ev.clientX, ev.clientY);
|
|
13296
|
+
const x0 = row.shape === 'milestone' ? row.pointX : anchor === 'finish' ? row.endX : row.startX;
|
|
13297
|
+
this.gesture.set({
|
|
13298
|
+
fromId: row.flat.row.id,
|
|
13299
|
+
anchor,
|
|
13300
|
+
x0,
|
|
13301
|
+
y0: row.midY,
|
|
13302
|
+
x1: local.x,
|
|
13303
|
+
y1: local.y,
|
|
13304
|
+
});
|
|
13305
|
+
const onMove = (e) => {
|
|
13306
|
+
const p = this.toBodyPoint(e.clientX, e.clientY);
|
|
13307
|
+
const g = this.gesture();
|
|
13308
|
+
if (g)
|
|
13309
|
+
this.gesture.set({ ...g, x1: p.x, y1: p.y });
|
|
13310
|
+
};
|
|
13311
|
+
const onUp = (e) => {
|
|
13312
|
+
document.removeEventListener('pointermove', onMove);
|
|
13313
|
+
document.removeEventListener('pointerup', onUp);
|
|
13314
|
+
const g = this.gesture();
|
|
13315
|
+
this.gesture.set(null);
|
|
13316
|
+
if (!g)
|
|
13317
|
+
return;
|
|
13318
|
+
const target = this.dropTargetAt(e.clientX, e.clientY);
|
|
13319
|
+
// A row cannot depend on itself; a drop on empty space is simply abandoned.
|
|
13320
|
+
if (target && target.id !== g.fromId) {
|
|
13321
|
+
this.deps.createLink({
|
|
13322
|
+
fromId: g.fromId,
|
|
13323
|
+
toId: target.id,
|
|
13324
|
+
type: linkTypeFor(g.anchor, target.anchor),
|
|
13325
|
+
});
|
|
13326
|
+
}
|
|
13327
|
+
};
|
|
13328
|
+
document.addEventListener('pointermove', onMove);
|
|
13329
|
+
document.addEventListener('pointerup', onUp);
|
|
13330
|
+
this.deps.destroyRef.onDestroy(() => {
|
|
13331
|
+
document.removeEventListener('pointermove', onMove);
|
|
13332
|
+
document.removeEventListener('pointerup', onUp);
|
|
13333
|
+
});
|
|
13334
|
+
}
|
|
13335
|
+
/** Convert client coordinates to a point inside the body SVG (render space). */
|
|
13336
|
+
toBodyPoint(clientX, clientY) {
|
|
13337
|
+
const svg = this.deps.bodySvg();
|
|
13338
|
+
if (!svg)
|
|
13339
|
+
return { x: 0, y: 0 };
|
|
13340
|
+
const rect = svg.getBoundingClientRect();
|
|
13341
|
+
return { x: clientX - rect.left, y: clientY - rect.top };
|
|
13342
|
+
}
|
|
13343
|
+
/**
|
|
13344
|
+
* The row a link gesture was dropped on **and which of its ends** the drop landed nearest —
|
|
13345
|
+
* the second half of the type derivation. A milestone has no width, so both of its ends are the
|
|
13346
|
+
* same point and it is reported as `start`, giving the FS/SS pair anyone actually wants.
|
|
13347
|
+
*/
|
|
13348
|
+
dropTargetAt(clientX, clientY) {
|
|
13349
|
+
const el = document.elementFromPoint(clientX, clientY);
|
|
13350
|
+
const holder = el?.closest('[data-row-id]');
|
|
13351
|
+
const id = holder?.getAttribute('data-row-id');
|
|
13352
|
+
if (!id)
|
|
13353
|
+
return null;
|
|
13354
|
+
const row = this.deps.rows().find((r) => r.flat.row.id === id);
|
|
13355
|
+
if (!row || row.shape === 'milestone' || row.shape === 'empty')
|
|
13356
|
+
return { id, anchor: 'start' };
|
|
13357
|
+
const x = this.toBodyPoint(clientX, clientY).x;
|
|
13358
|
+
return {
|
|
13359
|
+
id,
|
|
13360
|
+
anchor: Math.abs(x - row.endX) < Math.abs(x - row.startX) ? 'finish' : 'start',
|
|
13361
|
+
};
|
|
13362
|
+
}
|
|
13363
|
+
}
|
|
13364
|
+
|
|
12871
13365
|
/** Process-wide counter so each instance owns a unique arrowhead `<marker>` id. */
|
|
12872
13366
|
let _flyGanttUid = 0;
|
|
12873
13367
|
/** Header band heights (two-tier: coarse period over fine period). */
|
|
@@ -12878,27 +13372,6 @@ const HEADER_H = HEADER_UPPER_H + HEADER_LOWER_H;
|
|
|
12878
13372
|
const BAR_V_PAD = 6;
|
|
12879
13373
|
/** Edge-handle hit width (px) for resize + the link connector radius. */
|
|
12880
13374
|
const HANDLE_W = 8;
|
|
12881
|
-
/** Label-pane width bounds (px) the splitter clamps to. */
|
|
12882
|
-
const MIN_LABEL_W = 140;
|
|
12883
|
-
const MAX_LABEL_W = 720;
|
|
12884
|
-
/** Keyboard resize steps (plain arrow / with Shift). */
|
|
12885
|
-
const LABEL_STEP = 16;
|
|
12886
|
-
const LABEL_STEP_COARSE = 64;
|
|
12887
|
-
/** Straight run (px) a dependency elbow travels out of a bar edge before it turns. */
|
|
12888
|
-
const LINK_STUB = 12;
|
|
12889
|
-
/** Clamp any candidate label-pane width into the splitter's range. */
|
|
12890
|
-
function clampLabelWidth(px) {
|
|
12891
|
-
return Math.max(MIN_LABEL_W, Math.min(MAX_LABEL_W, Math.round(px)));
|
|
12892
|
-
}
|
|
12893
|
-
/**
|
|
12894
|
-
* Relationship kind implied by the edge a gesture left and the edge it landed on. The name is
|
|
12895
|
-
* literally `<predecessor endpoint><successor endpoint>`, so this is a lookup, not a rule.
|
|
12896
|
-
*/
|
|
12897
|
-
function linkTypeFor(from, to) {
|
|
12898
|
-
if (from === 'finish')
|
|
12899
|
-
return to === 'start' ? 'FS' : 'FF';
|
|
12900
|
-
return to === 'start' ? 'SS' : 'SF';
|
|
12901
|
-
}
|
|
12902
13375
|
/**
|
|
12903
13376
|
* **`fly-gantt`** — the design-system SVG Gantt chart.
|
|
12904
13377
|
*
|
|
@@ -12970,22 +13443,16 @@ class FlyGanttComponent {
|
|
|
12970
13443
|
HEADER_UPPER_H = HEADER_UPPER_H;
|
|
12971
13444
|
HEADER_LOWER_H = HEADER_LOWER_H;
|
|
12972
13445
|
HANDLE_W = HANDLE_W;
|
|
12973
|
-
MIN_LABEL_W =
|
|
12974
|
-
MAX_LABEL_W =
|
|
13446
|
+
MIN_LABEL_W = GANTT_LABEL_W_MIN;
|
|
13447
|
+
MAX_LABEL_W = GANTT_LABEL_W_MAX;
|
|
12975
13448
|
// ── Interaction state ─────────────────────────────────────────────────────────
|
|
12976
13449
|
selectedId = signal(null, ...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
|
|
12977
|
-
/** Key (`from~to~type`) of the dependency arrow the user has selected, if any. */
|
|
12978
|
-
selectedLinkKey = signal(null, ...(ngDevMode ? [{ debugName: "selectedLinkKey" }] : /* istanbul ignore next */ []));
|
|
12979
13450
|
_collapsed = signal(new Set(), ...(ngDevMode ? [{ debugName: "_collapsed" }] : /* istanbul ignore next */ []));
|
|
12980
13451
|
/** Live drag preview `{id,start,end}` folded into geometry while a gesture runs. */
|
|
12981
13452
|
_dragPreview = signal(null, ...(ngDevMode ? [{ debugName: "_dragPreview" }] : /* istanbul ignore next */ []));
|
|
12982
|
-
/** Rubber-band link gesture render-space anchor + cursor, and its origin row + edge. */
|
|
12983
|
-
linkGesture = signal(null, ...(ngDevMode ? [{ debugName: "linkGesture" }] : /* istanbul ignore next */ []));
|
|
12984
13453
|
dragTooltip = signal(null, ...(ngDevMode ? [{ debugName: "dragTooltip" }] : /* istanbul ignore next */ []));
|
|
12985
13454
|
/** True while a divider drag is in flight (suppresses text selection host-wide). */
|
|
12986
|
-
resizingLabels = signal(false, ...(ngDevMode ? [{ debugName: "resizingLabels" }] : /* istanbul ignore next */ []));
|
|
12987
13455
|
/** User-chosen pane width; `null` = still following the {@link labelWidth} input. */
|
|
12988
|
-
_labelWidthOverride = signal(null, ...(ngDevMode ? [{ debugName: "_labelWidthOverride" }] : /* istanbul ignore next */ []));
|
|
12989
13456
|
bodyRef = viewChild('bodySvg', ...(ngDevMode ? [{ debugName: "bodyRef" }] : /* istanbul ignore next */ []));
|
|
12990
13457
|
// ── Derived model ──────────────────────────────────────────────────────────────
|
|
12991
13458
|
rtl = computed(() => this.i18n.isRtl(), ...(ngDevMode ? [{ debugName: "rtl" }] : /* istanbul ignore next */ []));
|
|
@@ -12993,7 +13460,6 @@ class FlyGanttComponent {
|
|
|
12993
13460
|
domain = computed(() => computeDomain(this.rows(), this.zoom()), ...(ngDevMode ? [{ debugName: "domain" }] : /* istanbul ignore next */ []));
|
|
12994
13461
|
innerWidth = computed(() => Math.max(1, dateToX(this.domain().end, this.domain().start, this.pxPerDay())), ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
|
|
12995
13462
|
/** Label-pane width actually rendered — the user's dragged width, else the input. */
|
|
12996
|
-
effectiveLabelWidth = computed(() => clampLabelWidth(this._labelWidthOverride() ?? this.labelWidth()), ...(ngDevMode ? [{ debugName: "effectiveLabelWidth" }] : /* istanbul ignore next */ []));
|
|
12997
13463
|
/** Full visible list after tree flatten/collapse. */
|
|
12998
13464
|
_flat = computed(() => flattenRows(this.rows(), this._collapsed()), ...(ngDevMode ? [{ debugName: "_flat" }] : /* istanbul ignore next */ []));
|
|
12999
13465
|
/** Capped list actually rendered. */
|
|
@@ -13095,7 +13561,7 @@ class FlyGanttComponent {
|
|
|
13095
13561
|
const to = vmById.get(link.toId);
|
|
13096
13562
|
if (!from || !to)
|
|
13097
13563
|
continue;
|
|
13098
|
-
const routed =
|
|
13564
|
+
const routed = routeLink(from, to, link.type, rtl, this.rowHeight());
|
|
13099
13565
|
out.push({
|
|
13100
13566
|
key: `${link.fromId}~${link.toId}~${link.type}`,
|
|
13101
13567
|
fromId: link.fromId,
|
|
@@ -13151,85 +13617,31 @@ class FlyGanttComponent {
|
|
|
13151
13617
|
}
|
|
13152
13618
|
// ── Label-pane divider ─────────────────────────────────────────────────────────
|
|
13153
13619
|
/**
|
|
13154
|
-
*
|
|
13155
|
-
*
|
|
13156
|
-
*
|
|
13620
|
+
* The divider's own state machine, in {@link GanttLabelPane}. It is composed rather than
|
|
13621
|
+
* inlined because it is the one region of this component with no coupling to the chart: give it
|
|
13622
|
+
* a direction, a fallback width and somewhere to report a committed value and it is complete.
|
|
13623
|
+
* The subtle part — that "wider" flips physical sign under RTL for BOTH the drag and the arrow
|
|
13624
|
+
* keys — is stated once there instead of twice here.
|
|
13157
13625
|
*/
|
|
13626
|
+
_labelPane = new GanttLabelPane({
|
|
13627
|
+
resizable: this.resizableLabels,
|
|
13628
|
+
rtl: this.rtl,
|
|
13629
|
+
inputWidth: this.labelWidth,
|
|
13630
|
+
commit: (width) => this.labelWidthChange.emit(width),
|
|
13631
|
+
destroyRef: this.destroyRef,
|
|
13632
|
+
});
|
|
13633
|
+
/** Painted width of the label pane — the user's dragged value, else the clamped input. */
|
|
13634
|
+
effectiveLabelWidth = this._labelPane.width;
|
|
13635
|
+
/** True mid-drag; drives the host's resizing cursor/affordance. */
|
|
13636
|
+
resizingLabels = this._labelPane.resizing;
|
|
13158
13637
|
onLabelResizePointerDown(ev) {
|
|
13159
|
-
|
|
13160
|
-
return;
|
|
13161
|
-
ev.preventDefault();
|
|
13162
|
-
ev.stopPropagation();
|
|
13163
|
-
const startClientX = ev.clientX;
|
|
13164
|
-
const startWidth = this.effectiveLabelWidth();
|
|
13165
|
-
const dirSign = this.rtl() ? -1 : 1;
|
|
13166
|
-
this.resizingLabels.set(true);
|
|
13167
|
-
const onMove = (e) => {
|
|
13168
|
-
this._labelWidthOverride.set(clampLabelWidth(startWidth + (e.clientX - startClientX) * dirSign));
|
|
13169
|
-
};
|
|
13170
|
-
const onUp = () => {
|
|
13171
|
-
document.removeEventListener('pointermove', onMove);
|
|
13172
|
-
document.removeEventListener('pointerup', onUp);
|
|
13173
|
-
this.resizingLabels.set(false);
|
|
13174
|
-
const final = this.effectiveLabelWidth();
|
|
13175
|
-
if (final !== startWidth)
|
|
13176
|
-
this.labelWidthChange.emit(final);
|
|
13177
|
-
};
|
|
13178
|
-
document.addEventListener('pointermove', onMove);
|
|
13179
|
-
document.addEventListener('pointerup', onUp);
|
|
13180
|
-
this.destroyRef.onDestroy(() => {
|
|
13181
|
-
document.removeEventListener('pointermove', onMove);
|
|
13182
|
-
document.removeEventListener('pointerup', onUp);
|
|
13183
|
-
});
|
|
13638
|
+
this._labelPane.onPointerDown(ev);
|
|
13184
13639
|
}
|
|
13185
|
-
/**
|
|
13186
|
-
* Keyboard resize on the focused divider. ArrowRight/Left grow/shrink by the *inline* meaning
|
|
13187
|
-
* of the key, so under RTL the arrow that points away from the pane is still the one that
|
|
13188
|
-
* widens it. `Home`/`End` jump to the bounds, Enter/Escape restore the input width.
|
|
13189
|
-
*
|
|
13190
|
-
* Typed `Event` for the same reason as {@link onLabelRowKeydown}: Angular's strict template
|
|
13191
|
-
* checker types `(keydown)` `$event` as `Event`, so the cast is done here once.
|
|
13192
|
-
*/
|
|
13193
13640
|
onLabelResizeKeydown(ev) {
|
|
13194
|
-
|
|
13195
|
-
return;
|
|
13196
|
-
const key = ev.key;
|
|
13197
|
-
const coarse = ev.shiftKey;
|
|
13198
|
-
const step = coarse ? LABEL_STEP_COARSE : LABEL_STEP;
|
|
13199
|
-
const grow = this.rtl() ? 'ArrowLeft' : 'ArrowRight';
|
|
13200
|
-
const shrink = this.rtl() ? 'ArrowRight' : 'ArrowLeft';
|
|
13201
|
-
let next;
|
|
13202
|
-
if (key === grow)
|
|
13203
|
-
next = this.effectiveLabelWidth() + step;
|
|
13204
|
-
else if (key === shrink)
|
|
13205
|
-
next = this.effectiveLabelWidth() - step;
|
|
13206
|
-
else if (key === 'Home')
|
|
13207
|
-
next = MIN_LABEL_W;
|
|
13208
|
-
else if (key === 'End')
|
|
13209
|
-
next = MAX_LABEL_W;
|
|
13210
|
-
else if (key === 'Enter' || key === 'Escape') {
|
|
13211
|
-
// Stop the grid-level Enter handler from also activating the selected row.
|
|
13212
|
-
ev.preventDefault();
|
|
13213
|
-
ev.stopPropagation();
|
|
13214
|
-
this.resetLabelWidth();
|
|
13215
|
-
return;
|
|
13216
|
-
}
|
|
13217
|
-
else
|
|
13218
|
-
return;
|
|
13219
|
-
ev.preventDefault();
|
|
13220
|
-
ev.stopPropagation();
|
|
13221
|
-
const clamped = clampLabelWidth(next);
|
|
13222
|
-
if (clamped === this.effectiveLabelWidth())
|
|
13223
|
-
return;
|
|
13224
|
-
this._labelWidthOverride.set(clamped);
|
|
13225
|
-
this.labelWidthChange.emit(clamped);
|
|
13641
|
+
this._labelPane.onKeydown(ev);
|
|
13226
13642
|
}
|
|
13227
|
-
/** Drop the user's dragged width and fall back to the {@link labelWidth} input. */
|
|
13228
13643
|
resetLabelWidth() {
|
|
13229
|
-
|
|
13230
|
-
return;
|
|
13231
|
-
this._labelWidthOverride.set(null);
|
|
13232
|
-
this.labelWidthChange.emit(this.effectiveLabelWidth());
|
|
13644
|
+
this._labelPane.reset();
|
|
13233
13645
|
}
|
|
13234
13646
|
// ── Group collapse ─────────────────────────────────────────────────────────────
|
|
13235
13647
|
isCollapsed(id) {
|
|
@@ -13260,7 +13672,7 @@ class FlyGanttComponent {
|
|
|
13260
13672
|
this.selectedId.set(id);
|
|
13261
13673
|
// A row and a link are alternative selections — picking one drops the other, so Delete is
|
|
13262
13674
|
// never ambiguous about which thing it would remove.
|
|
13263
|
-
this.
|
|
13675
|
+
this._links.clearSelection();
|
|
13264
13676
|
this.rowClick.emit(id);
|
|
13265
13677
|
}
|
|
13266
13678
|
onRowDblClick(id) {
|
|
@@ -13269,14 +13681,14 @@ class FlyGanttComponent {
|
|
|
13269
13681
|
// ── Dependency arrow selection + removal ───────────────────────────────────────
|
|
13270
13682
|
onLinkClick(ev, vm) {
|
|
13271
13683
|
ev.stopPropagation();
|
|
13272
|
-
this.
|
|
13684
|
+
this._links.toggleSelection(vm.key);
|
|
13273
13685
|
}
|
|
13274
13686
|
deleteLink(ev, vm) {
|
|
13275
13687
|
ev.stopPropagation();
|
|
13276
13688
|
ev.preventDefault();
|
|
13277
13689
|
if (this.readonly())
|
|
13278
13690
|
return;
|
|
13279
|
-
this.
|
|
13691
|
+
this._links.clearSelection();
|
|
13280
13692
|
this.dependencyDelete.emit({ fromId: vm.fromId, toId: vm.toId, type: vm.type });
|
|
13281
13693
|
}
|
|
13282
13694
|
// ── Keyboard ───────────────────────────────────────────────────────────────────
|
|
@@ -13413,54 +13825,27 @@ class FlyGanttComponent {
|
|
|
13413
13825
|
}
|
|
13414
13826
|
// ── Pointer drag: dependency create ──────────────────────────────────────────────
|
|
13415
13827
|
/**
|
|
13416
|
-
*
|
|
13417
|
-
*
|
|
13418
|
-
*
|
|
13828
|
+
* The dependency-drawing gesture, in {@link GanttLinkGestures}. Composed rather than inlined
|
|
13829
|
+
* because it is a self-contained second gesture whose interesting part is a RULE — the end you
|
|
13830
|
+
* start on plus the end you drop nearest, selects the relationship type — which reads far better beside
|
|
13831
|
+
* its own hit-testing than in the middle of this component.
|
|
13419
13832
|
*/
|
|
13833
|
+
_links = new GanttLinkGestures({
|
|
13834
|
+
readonly: this.readonly,
|
|
13835
|
+
rows: this.rowVms,
|
|
13836
|
+
bodySvg: () => this.bodyRef()?.nativeElement ?? null,
|
|
13837
|
+
createLink: (dependency) => this.dependencyCreate.emit(dependency),
|
|
13838
|
+
destroyRef: this.destroyRef,
|
|
13839
|
+
});
|
|
13840
|
+
/** Key (`from~to~type`) of the dependency arrow the user has selected, if any. */
|
|
13841
|
+
selectedLinkKey = this._links.selectedKey;
|
|
13842
|
+
/** The in-flight rubber-band gesture, or `null` — the template draws it when present. */
|
|
13843
|
+
linkGesture = this._links.gesture;
|
|
13844
|
+
/** Rubber-band path for an in-flight link gesture (render space). */
|
|
13845
|
+
linkGesturePath = this._links.path;
|
|
13420
13846
|
onLinkPointerDown(ev, vm, anchor) {
|
|
13421
|
-
|
|
13422
|
-
return;
|
|
13423
|
-
ev.preventDefault();
|
|
13424
|
-
ev.stopPropagation();
|
|
13425
|
-
const local = this._toBodyPoint(ev);
|
|
13426
|
-
const x0 = vm.shape === 'milestone' ? vm.pointX : anchor === 'finish' ? vm.endX : vm.startX;
|
|
13427
|
-
this.linkGesture.set({ fromId: vm.flat.row.id, anchor, x0, y0: vm.midY, x1: local.x, y1: local.y });
|
|
13428
|
-
const onMove = (e) => {
|
|
13429
|
-
const p = this._toBodyPoint(e);
|
|
13430
|
-
const g = this.linkGesture();
|
|
13431
|
-
if (g)
|
|
13432
|
-
this.linkGesture.set({ ...g, x1: p.x, y1: p.y });
|
|
13433
|
-
};
|
|
13434
|
-
const onUp = (e) => {
|
|
13435
|
-
document.removeEventListener('pointermove', onMove);
|
|
13436
|
-
document.removeEventListener('pointerup', onUp);
|
|
13437
|
-
const g = this.linkGesture();
|
|
13438
|
-
this.linkGesture.set(null);
|
|
13439
|
-
if (!g)
|
|
13440
|
-
return;
|
|
13441
|
-
const target = this._dropTargetAt(e.clientX, e.clientY);
|
|
13442
|
-
if (target && target.id !== g.fromId) {
|
|
13443
|
-
this.dependencyCreate.emit({
|
|
13444
|
-
fromId: g.fromId,
|
|
13445
|
-
toId: target.id,
|
|
13446
|
-
type: linkTypeFor(g.anchor, target.anchor),
|
|
13447
|
-
});
|
|
13448
|
-
}
|
|
13449
|
-
};
|
|
13450
|
-
document.addEventListener('pointermove', onMove);
|
|
13451
|
-
document.addEventListener('pointerup', onUp);
|
|
13452
|
-
this.destroyRef.onDestroy(() => {
|
|
13453
|
-
document.removeEventListener('pointermove', onMove);
|
|
13454
|
-
document.removeEventListener('pointerup', onUp);
|
|
13455
|
-
});
|
|
13847
|
+
this._links.start(ev, vm, anchor);
|
|
13456
13848
|
}
|
|
13457
|
-
/** Rubber-band path for an in-flight link gesture (render space). */
|
|
13458
|
-
linkGesturePath = computed(() => {
|
|
13459
|
-
const g = this.linkGesture();
|
|
13460
|
-
if (!g)
|
|
13461
|
-
return null;
|
|
13462
|
-
return `M ${g.x0} ${g.y0} L ${g.x1} ${g.y1}`;
|
|
13463
|
-
}, ...(ngDevMode ? [{ debugName: "linkGesturePath" }] : /* istanbul ignore next */ []));
|
|
13464
13849
|
// ── Internals ────────────────────────────────────────────────────────────────────
|
|
13465
13850
|
/**
|
|
13466
13851
|
* Resolve a row's `backgroundColor` into the low-alpha band colour actually painted.
|
|
@@ -13481,70 +13866,6 @@ class FlyGanttComponent {
|
|
|
13481
13866
|
// Reflect each cell: its inline-start edge becomes innerWidth - (x + width).
|
|
13482
13867
|
return ticks.map((t) => ({ ...t, x: w - t.x - t.width }));
|
|
13483
13868
|
}
|
|
13484
|
-
/**
|
|
13485
|
-
* Route a dependency elbow for any of the four relationship kinds (render space).
|
|
13486
|
-
*
|
|
13487
|
-
* The kind names the two endpoints, so it decides both the anchor x's and the direction the
|
|
13488
|
-
* arrow travels: leaving a *finish* edge moves forward in time, leaving a *start* edge moves
|
|
13489
|
-
* backward; entering a *start* edge approaches from behind, entering a *finish* edge from
|
|
13490
|
-
* ahead. `fwd` folds RTL in, so none of that branches on direction.
|
|
13491
|
-
*
|
|
13492
|
-
* One turn suffices when the entry stub lies ahead of the exit stub in the approach
|
|
13493
|
-
* direction. When it does not — overlapping or reversed bars, which SS/FF/SF hit constantly —
|
|
13494
|
-
* a straight drop would cut back through the bars, so the elbow detours via the gap between
|
|
13495
|
-
* the two rows instead.
|
|
13496
|
-
*/
|
|
13497
|
-
_route(from, to, type, rtl) {
|
|
13498
|
-
const fwd = rtl ? -1 : 1;
|
|
13499
|
-
const fromFinish = type === 'FS' || type === 'FF';
|
|
13500
|
-
const toStart = type === 'FS' || type === 'SS';
|
|
13501
|
-
const x1 = from.shape === 'milestone' ? from.pointX : fromFinish ? from.endX : from.startX;
|
|
13502
|
-
const x2 = to.shape === 'milestone' ? to.pointX : toStart ? to.startX : to.endX;
|
|
13503
|
-
const y1 = from.midY;
|
|
13504
|
-
const y2 = to.midY;
|
|
13505
|
-
const d1 = (fromFinish ? 1 : -1) * fwd; // exit direction
|
|
13506
|
-
const d2 = (toStart ? 1 : -1) * fwd; // approach direction
|
|
13507
|
-
const exit = x1 + LINK_STUB * d1;
|
|
13508
|
-
const entry = x2 - LINK_STUB * d2;
|
|
13509
|
-
if ((entry - exit) * d2 >= 0) {
|
|
13510
|
-
return { path: `M ${x1} ${y1} H ${exit} V ${y2} H ${x2}`, badgeX: exit, badgeY: (y1 + y2) / 2 };
|
|
13511
|
-
}
|
|
13512
|
-
// Distinct rows always differ in y; the guard only keeps a degenerate input off the bar.
|
|
13513
|
-
const yMid = y1 === y2 ? y1 - this.rowHeight() / 2 : (y1 + y2) / 2;
|
|
13514
|
-
return {
|
|
13515
|
-
path: `M ${x1} ${y1} H ${exit} V ${yMid} H ${entry} V ${y2} H ${x2}`,
|
|
13516
|
-
badgeX: entry,
|
|
13517
|
-
badgeY: yMid,
|
|
13518
|
-
};
|
|
13519
|
-
}
|
|
13520
|
-
/** Convert a pointer event to a coordinate inside the body SVG (render space). */
|
|
13521
|
-
_toBodyPoint(ev) {
|
|
13522
|
-
const svg = this.bodyRef()?.nativeElement;
|
|
13523
|
-
if (!svg)
|
|
13524
|
-
return { x: 0, y: 0 };
|
|
13525
|
-
const rect = svg.getBoundingClientRect();
|
|
13526
|
-
return { x: ev.clientX - rect.left, y: ev.clientY - rect.top };
|
|
13527
|
-
}
|
|
13528
|
-
/**
|
|
13529
|
-
* The row a link gesture was dropped on **and which of its ends** the drop landed nearest —
|
|
13530
|
-
* the second half of the type derivation. A milestone has no width, so both of its ends are
|
|
13531
|
-
* the same point and it is reported as `start`, giving the FS/SS pair anyone actually wants.
|
|
13532
|
-
*/
|
|
13533
|
-
_dropTargetAt(clientX, clientY) {
|
|
13534
|
-
const el = document.elementFromPoint(clientX, clientY);
|
|
13535
|
-
const holder = el?.closest('[data-row-id]');
|
|
13536
|
-
const id = holder?.getAttribute('data-row-id');
|
|
13537
|
-
if (!id)
|
|
13538
|
-
return null;
|
|
13539
|
-
const vm = this.rowVms().find((v) => v.flat.row.id === id);
|
|
13540
|
-
if (!vm || vm.shape === 'milestone' || vm.shape === 'empty')
|
|
13541
|
-
return { id, anchor: 'start' };
|
|
13542
|
-
const x = this._toBodyPoint({ clientX, clientY }).x;
|
|
13543
|
-
return {
|
|
13544
|
-
id,
|
|
13545
|
-
anchor: Math.abs(x - vm.endX) < Math.abs(x - vm.startX) ? 'finish' : 'start',
|
|
13546
|
-
};
|
|
13547
|
-
}
|
|
13548
13869
|
_ariaFor(row, start, end, shape) {
|
|
13549
13870
|
const t = (k, p) => this.i18n.t(k, p);
|
|
13550
13871
|
if (shape === 'milestone') {
|
|
@@ -19349,6 +19670,294 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
19349
19670
|
args: [FlyTypeaheadComponent]
|
|
19350
19671
|
}] } });
|
|
19351
19672
|
|
|
19673
|
+
/** The platform endpoint the component falls back to. Relative — the host's gateway + auth interceptors apply. */
|
|
19674
|
+
const FLY_STRATEGY_OBJECTIVES_LOOKUP_ENDPOINT = '/api/strategies/objectives/lookup';
|
|
19675
|
+
/**
|
|
19676
|
+
* Tolerant unwrap of the `ApiResponse<List<ObjectiveLookupDto>>` envelope. Anything that
|
|
19677
|
+
* doesn't parse degrades to an empty list — a picker must degrade to "no options", never throw.
|
|
19678
|
+
*/
|
|
19679
|
+
function flyUnwrapObjectiveLookup(res) {
|
|
19680
|
+
const data = res?.data;
|
|
19681
|
+
if (!Array.isArray(data))
|
|
19682
|
+
return [];
|
|
19683
|
+
return data.filter((r) => !!r && typeof r.id === 'string' &&
|
|
19684
|
+
typeof r.title === 'string');
|
|
19685
|
+
}
|
|
19686
|
+
/**
|
|
19687
|
+
* **`fly-strategy-selector`** — pick ONE strategic objective from the Strategies
|
|
19688
|
+
* app, so any Core or External App can optionally link its own entity (a
|
|
19689
|
+
* programme, an initiative, a budget line) to the strategy it serves.
|
|
19690
|
+
*
|
|
19691
|
+
* Pre-canned for the Strategies app's cross-strategy objective lookup
|
|
19692
|
+
* (`GET /api/strategies/objectives/lookup?q=`, `ObjectiveLookupDto` — only
|
|
19693
|
+
* StrategicObjective items on the PUBLISHED version of an Active strategy
|
|
19694
|
+
* surface there): with no data inputs it queries that endpoint itself through
|
|
19695
|
+
* the host's `HttpClient`, so gateway routing and the auth interceptor apply
|
|
19696
|
+
* and a consuming app writes one tag and nothing else. A host that must not
|
|
19697
|
+
* reach the platform directly supplies `[searchFn]` instead — the same
|
|
19698
|
+
* transport-agnostic escape hatch `fly-people-picker` and
|
|
19699
|
+
* `fly-currency-selector` offer.
|
|
19700
|
+
*
|
|
19701
|
+
* A bespoke panel (own search box + floating results box), not a wrapped
|
|
19702
|
+
* `fly-typeahead` — a flat list can't show which strategy an objective belongs
|
|
19703
|
+
* to, and two strategies routinely share near-identical objective titles.
|
|
19704
|
+
* Results are grouped into contiguous {@link FlyStrategyObjectiveGroup} runs
|
|
19705
|
+
* with the parent strategy's own icon as the group header, mirroring
|
|
19706
|
+
* `fly-currency-selector`'s pinned/all group pattern.
|
|
19707
|
+
*
|
|
19708
|
+
* Single-select by design (an entity serves ONE objective link here; a many-
|
|
19709
|
+
* to-many mapping is a matrix surface, not a form field) and built for
|
|
19710
|
+
* OPTIONAL fields: the empty state is a search box, a pick renders as a
|
|
19711
|
+
* removable chip (objective title + parent strategy secondary line), and
|
|
19712
|
+
* {@link selectionChange} emits `null` on removal.
|
|
19713
|
+
*
|
|
19714
|
+
* Emits the full {@link FlyStrategyObjectiveRef}, not just the id: the
|
|
19715
|
+
* consumer freezes the label at link time (the cross-app reference canon —
|
|
19716
|
+
* the link must stay renderable even when the strategy is later archived or
|
|
19717
|
+
* the caller can no longer read it).
|
|
19718
|
+
*
|
|
19719
|
+
* For an edit surface, feed {@link initialSelection} with the saved ref (the
|
|
19720
|
+
* consumer's own frozen copy — no re-resolve round-trip needed). Seeding never
|
|
19721
|
+
* emits {@link selectionChange}, so loading a form does not mark it dirty.
|
|
19722
|
+
*
|
|
19723
|
+
* i18n is self-sufficient through the `strategy_selector.*` keys in
|
|
19724
|
+
* `DS_BASELINE_LOCALES` (en/ar/fr/ur). RTL works via logical CSS.
|
|
19725
|
+
*
|
|
19726
|
+
* @example
|
|
19727
|
+
* ```html
|
|
19728
|
+
* <!-- Queries /api/strategies/objectives/lookup itself: -->
|
|
19729
|
+
* <fly-strategy-selector (selectionChange)="onObjectivePicked($event)" />
|
|
19730
|
+
*
|
|
19731
|
+
* <!-- Edit surface, seeded from the host's frozen ref: -->
|
|
19732
|
+
* <fly-strategy-selector
|
|
19733
|
+
* [initialSelection]="savedObjectiveRef()"
|
|
19734
|
+
* (selectionChange)="onObjectiveChanged($event)" />
|
|
19735
|
+
* ```
|
|
19736
|
+
*/
|
|
19737
|
+
class FlyStrategySelectorComponent {
|
|
19738
|
+
http = inject(HttpClient, { optional: true });
|
|
19739
|
+
// ── Inputs ───────────────────────────────────────────────────────────────────
|
|
19740
|
+
/** Host-supplied loader. Omit to fall back to `GET /api/strategies/objectives/lookup`. */
|
|
19741
|
+
searchFn = input(null, ...(ngDevMode ? [{ debugName: "searchFn" }] : /* istanbul ignore next */ []));
|
|
19742
|
+
/** Saved ref to seed an edit surface with — rendered as the picked chip. Seeded ONCE, on the
|
|
19743
|
+
* first non-null value (a host's load typically lands after construction); later input changes
|
|
19744
|
+
* are ignored so a re-emitting load can't clobber the user's in-progress edit. Seeding does NOT
|
|
19745
|
+
* emit {@link selectionChange}. */
|
|
19746
|
+
initialSelection = input(null, ...(ngDevMode ? [{ debugName: "initialSelection" }] : /* istanbul ignore next */ []));
|
|
19747
|
+
/** Placeholder for the search box. Falls back to the localized `strategy_selector.search_placeholder`. */
|
|
19748
|
+
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
19749
|
+
/** Debounce (ms) between the last keystroke and the search call. */
|
|
19750
|
+
debounceMs = input(250, ...(ngDevMode ? [{ debugName: "debounceMs" }] : /* istanbul ignore next */ []));
|
|
19751
|
+
// ── Outputs ──────────────────────────────────────────────────────────────────
|
|
19752
|
+
/** The picked objective in full (label freezable by the host), or `null` when removed. */
|
|
19753
|
+
selectionChange = output();
|
|
19754
|
+
searchEl;
|
|
19755
|
+
host = inject((ElementRef));
|
|
19756
|
+
// ── State ────────────────────────────────────────────────────────────────────
|
|
19757
|
+
_selected = signal(null, ...(ngDevMode ? [{ debugName: "_selected" }] : /* istanbul ignore next */ []));
|
|
19758
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
19759
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
|
|
19760
|
+
activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
19761
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
19762
|
+
loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : /* istanbul ignore next */ []));
|
|
19763
|
+
/** The most recent search batch, already ordered strategy-first by the server/mock. */
|
|
19764
|
+
_results = signal([], ...(ngDevMode ? [{ debugName: "_results" }] : /* istanbul ignore next */ []));
|
|
19765
|
+
_searchStarted = false;
|
|
19766
|
+
/** One-shot latch: flips true after {@link initialSelection} has seeded the chip. */
|
|
19767
|
+
_seeded = false;
|
|
19768
|
+
_timer = null;
|
|
19769
|
+
_searchSub = null;
|
|
19770
|
+
constructor() {
|
|
19771
|
+
effect(() => {
|
|
19772
|
+
const init = this.initialSelection();
|
|
19773
|
+
if (this._seeded || init === null)
|
|
19774
|
+
return;
|
|
19775
|
+
this._seeded = true;
|
|
19776
|
+
this._selected.set(init);
|
|
19777
|
+
});
|
|
19778
|
+
}
|
|
19779
|
+
selected = this._selected.asReadonly();
|
|
19780
|
+
showSearch = computed(() => this._selected() === null, ...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
|
|
19781
|
+
/** {@link _results} folded into contiguous per-strategy runs — the panel's drilldown grouping.
|
|
19782
|
+
* Also doubles as the flat keyboard-navigation order (a group header is never itself an option,
|
|
19783
|
+
* so nav index N always resolves via {@link navOptions}, not a per-group offset). */
|
|
19784
|
+
groups = computed(() => {
|
|
19785
|
+
const rows = this._results();
|
|
19786
|
+
const out = [];
|
|
19787
|
+
for (const r of rows) {
|
|
19788
|
+
const last = out[out.length - 1];
|
|
19789
|
+
if (last && last.strategyId === r.strategyId)
|
|
19790
|
+
last.items.push(r);
|
|
19791
|
+
else {
|
|
19792
|
+
out.push({
|
|
19793
|
+
strategyId: r.strategyId,
|
|
19794
|
+
strategyTitle: r.strategyTitle,
|
|
19795
|
+
strategyIcon: r.strategyIcon ?? null,
|
|
19796
|
+
items: [r],
|
|
19797
|
+
});
|
|
19798
|
+
}
|
|
19799
|
+
}
|
|
19800
|
+
return out;
|
|
19801
|
+
}, ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
|
|
19802
|
+
navOptions = computed(() => this._results(), ...(ngDevMode ? [{ debugName: "navOptions" }] : /* istanbul ignore next */ []));
|
|
19803
|
+
navIndexOf(id) {
|
|
19804
|
+
return this.navOptions().findIndex((r) => r.id === id);
|
|
19805
|
+
}
|
|
19806
|
+
activeDescendant = computed(() => {
|
|
19807
|
+
const options = this.navOptions();
|
|
19808
|
+
if (!this.isOpen() || options.length === 0)
|
|
19809
|
+
return '';
|
|
19810
|
+
return this.optionId(Math.min(this.activeIndex(), options.length - 1));
|
|
19811
|
+
}, ...(ngDevMode ? [{ debugName: "activeDescendant" }] : /* istanbul ignore next */ []));
|
|
19812
|
+
optionId(index) {
|
|
19813
|
+
return `fly-strategy-selector-opt-${index}`;
|
|
19814
|
+
}
|
|
19815
|
+
// ── Open / close ───────────────────────────────────────────────────────────
|
|
19816
|
+
open() {
|
|
19817
|
+
if (this.isOpen())
|
|
19818
|
+
return;
|
|
19819
|
+
this.isOpen.set(true);
|
|
19820
|
+
this.activeIndex.set(0);
|
|
19821
|
+
// Surface the "top N" once per mount; don't re-hit the server on every refocus.
|
|
19822
|
+
if (!this._searchStarted)
|
|
19823
|
+
this._runSearch(this.searchTerm());
|
|
19824
|
+
queueMicrotask(() => this.searchEl?.nativeElement.focus());
|
|
19825
|
+
}
|
|
19826
|
+
close() {
|
|
19827
|
+
if (!this.isOpen())
|
|
19828
|
+
return;
|
|
19829
|
+
this.isOpen.set(false);
|
|
19830
|
+
}
|
|
19831
|
+
onSearchInput(value) {
|
|
19832
|
+
this.searchTerm.set(value);
|
|
19833
|
+
this.activeIndex.set(0);
|
|
19834
|
+
this.isOpen.set(true);
|
|
19835
|
+
this._scheduleSearch(value.trim());
|
|
19836
|
+
}
|
|
19837
|
+
// ── Keyboard ───────────────────────────────────────────────────────────────
|
|
19838
|
+
onPanelKeydown(event) {
|
|
19839
|
+
const options = this.navOptions();
|
|
19840
|
+
switch (event.key) {
|
|
19841
|
+
case 'ArrowDown':
|
|
19842
|
+
event.preventDefault();
|
|
19843
|
+
this.isOpen.set(true);
|
|
19844
|
+
if (options.length > 0)
|
|
19845
|
+
this.activeIndex.set((this.activeIndex() + 1) % options.length);
|
|
19846
|
+
break;
|
|
19847
|
+
case 'ArrowUp':
|
|
19848
|
+
event.preventDefault();
|
|
19849
|
+
if (options.length > 0) {
|
|
19850
|
+
this.activeIndex.set((this.activeIndex() - 1 + options.length) % options.length);
|
|
19851
|
+
}
|
|
19852
|
+
break;
|
|
19853
|
+
case 'Home':
|
|
19854
|
+
event.preventDefault();
|
|
19855
|
+
this.activeIndex.set(0);
|
|
19856
|
+
break;
|
|
19857
|
+
case 'End':
|
|
19858
|
+
event.preventDefault();
|
|
19859
|
+
this.activeIndex.set(Math.max(0, options.length - 1));
|
|
19860
|
+
break;
|
|
19861
|
+
case 'Enter': {
|
|
19862
|
+
event.preventDefault();
|
|
19863
|
+
const active = options[this.activeIndex()];
|
|
19864
|
+
if (active)
|
|
19865
|
+
this.pick(active);
|
|
19866
|
+
break;
|
|
19867
|
+
}
|
|
19868
|
+
case 'Escape':
|
|
19869
|
+
event.preventDefault();
|
|
19870
|
+
this.close();
|
|
19871
|
+
break;
|
|
19872
|
+
default:
|
|
19873
|
+
break;
|
|
19874
|
+
}
|
|
19875
|
+
}
|
|
19876
|
+
onOptionHover(index) {
|
|
19877
|
+
if (index !== this.activeIndex())
|
|
19878
|
+
this.activeIndex.set(index);
|
|
19879
|
+
}
|
|
19880
|
+
// ── Click-outside ────────────────────────────────────────────────────────────
|
|
19881
|
+
onDocumentMouseDown(ev) {
|
|
19882
|
+
if (!this.isOpen())
|
|
19883
|
+
return;
|
|
19884
|
+
const target = ev.target;
|
|
19885
|
+
if (target && this.host.nativeElement.contains(target))
|
|
19886
|
+
return;
|
|
19887
|
+
this.close();
|
|
19888
|
+
}
|
|
19889
|
+
// ── Search ───────────────────────────────────────────────────────────────────
|
|
19890
|
+
retry() {
|
|
19891
|
+
this._runSearch(this.searchTerm());
|
|
19892
|
+
}
|
|
19893
|
+
_scheduleSearch(q) {
|
|
19894
|
+
this._clearTimer();
|
|
19895
|
+
this._timer = setTimeout(() => this._runSearch(q), this.debounceMs());
|
|
19896
|
+
}
|
|
19897
|
+
_clearTimer() {
|
|
19898
|
+
if (this._timer !== null) {
|
|
19899
|
+
clearTimeout(this._timer);
|
|
19900
|
+
this._timer = null;
|
|
19901
|
+
}
|
|
19902
|
+
}
|
|
19903
|
+
_runSearch(q) {
|
|
19904
|
+
this._clearTimer();
|
|
19905
|
+
this._searchSub?.unsubscribe();
|
|
19906
|
+
this._searchStarted = true;
|
|
19907
|
+
this.loading.set(true);
|
|
19908
|
+
this.loadFailed.set(false);
|
|
19909
|
+
this._searchSub = this._search(q)
|
|
19910
|
+
.pipe(catchError(() => of(null)))
|
|
19911
|
+
.subscribe((rows) => {
|
|
19912
|
+
this.loading.set(false);
|
|
19913
|
+
if (rows === null) {
|
|
19914
|
+
this.loadFailed.set(true);
|
|
19915
|
+
this._results.set([]);
|
|
19916
|
+
return;
|
|
19917
|
+
}
|
|
19918
|
+
this._results.set(rows);
|
|
19919
|
+
this.activeIndex.set(0);
|
|
19920
|
+
});
|
|
19921
|
+
}
|
|
19922
|
+
_search(query) {
|
|
19923
|
+
const fetch = this.searchFn();
|
|
19924
|
+
if (fetch)
|
|
19925
|
+
return fetch(query);
|
|
19926
|
+
// No loader and no transport: degrade to "no options" — a picker must never throw
|
|
19927
|
+
// mid-keystroke. (Only reachable in a host without provideHttpClient and without [searchFn].)
|
|
19928
|
+
if (!this.http)
|
|
19929
|
+
return of([]);
|
|
19930
|
+
return this.http
|
|
19931
|
+
.get(FLY_STRATEGY_OBJECTIVES_LOOKUP_ENDPOINT, { params: { q: query, limit: 10 } })
|
|
19932
|
+
.pipe(map(flyUnwrapObjectiveLookup));
|
|
19933
|
+
}
|
|
19934
|
+
// ── Selection ────────────────────────────────────────────────────────────────
|
|
19935
|
+
pick(ref) {
|
|
19936
|
+
this._selected.set(ref);
|
|
19937
|
+
this.selectionChange.emit(ref);
|
|
19938
|
+
this.close();
|
|
19939
|
+
this.searchTerm.set('');
|
|
19940
|
+
this._results.set([]);
|
|
19941
|
+
this._searchStarted = false;
|
|
19942
|
+
}
|
|
19943
|
+
remove() {
|
|
19944
|
+
this._selected.set(null);
|
|
19945
|
+
this.selectionChange.emit(null);
|
|
19946
|
+
}
|
|
19947
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyStrategySelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
19948
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyStrategySelectorComponent, isStandalone: true, selector: "fly-strategy-selector", inputs: { searchFn: { classPropertyName: "searchFn", publicName: "searchFn", isSignal: true, isRequired: false, transformFunction: null }, initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { listeners: { "document:mousedown": "onDocumentMouseDown($event)" }, properties: { "class.fly-strategy-selector--open": "isOpen()" }, classAttribute: "fly-strategy-selector" }, viewQueries: [{ propertyName: "searchEl", first: true, predicate: ["searchRef"], descendants: true }], ngImport: i0, template: "@if (selected(); as pick) {\r\n <div class=\"fly-strategy-selector__chip\" role=\"group\" [attr.aria-label]=\"pick.title\">\r\n <span class=\"fly-strategy-selector__chip-icon\" aria-hidden=\"true\">\r\n @if (pick.strategyIcon) {\r\n <i class=\"pi {{ pick.strategyIcon }}\"></i>\r\n } @else {\r\n <i class=\"pi pi-compass\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-strategy-selector__chip-body\">\r\n <span class=\"fly-strategy-selector__chip-title\" [title]=\"pick.title\">{{ pick.title }}</span>\r\n <span class=\"fly-strategy-selector__chip-strategy\" [title]=\"pick.strategyTitle\">{{ pick.strategyTitle }}</span>\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-strategy-selector__chip-remove\"\r\n [attr.aria-label]=\"'strategy_selector.remove' | translate: { name: pick.title }\"\r\n (click)=\"remove()\">\r\n \u2715\r\n </button>\r\n </div>\r\n} @else {\r\n <div\r\n class=\"fly-strategy-selector__wrap\"\r\n [flyClickOutsideEnabled]=\"isOpen()\"\r\n (flyClickOutside)=\"close()\"\r\n >\r\n <input\r\n #searchRef\r\n type=\"text\"\r\n class=\"fly-strategy-selector__search-input\"\r\n role=\"combobox\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-controls=\"fly-strategy-selector-listbox\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"placeholder() || ('strategy_selector.search_placeholder' | translate)\"\r\n [placeholder]=\"placeholder() || ('strategy_selector.search_placeholder' | translate)\"\r\n [value]=\"searchTerm()\"\r\n (focus)=\"open()\"\r\n (input)=\"onSearchInput($any($event.target).value)\"\r\n (keydown)=\"onPanelKeydown($event)\"\r\n />\r\n\r\n @if (isOpen()) {\r\n <div class=\"fly-strategy-selector__panel\">\r\n <div\r\n class=\"fly-strategy-selector__listbox\"\r\n role=\"listbox\"\r\n id=\"fly-strategy-selector-listbox\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n >\r\n @if (loading()) {\r\n <div class=\"fly-strategy-selector__status\" role=\"status\">\r\n {{ 'strategy_selector.loading' | translate }}\r\n </div>\r\n } @else if (loadFailed()) {\r\n <div class=\"fly-strategy-selector__status fly-strategy-selector__status--error\" role=\"alert\">\r\n <span>{{ 'strategy_selector.error' | translate }}</span>\r\n <button type=\"button\" class=\"fly-strategy-selector__retry\" (click)=\"retry()\">\r\n {{ 'strategy_selector.retry' | translate }}\r\n </button>\r\n </div>\r\n } @else {\r\n @for (group of groups(); track group.strategyId) {\r\n <div class=\"fly-strategy-selector__group\" role=\"presentation\">\r\n <i class=\"pi {{ group.strategyIcon || 'pi-compass' }}\" aria-hidden=\"true\"></i>\r\n <span class=\"fly-strategy-selector__group-title\">{{ group.strategyTitle }}</span>\r\n </div>\r\n @for (item of group.items; track item.id) {\r\n <div\r\n class=\"fly-strategy-selector__option\"\r\n role=\"option\"\r\n tabindex=\"-1\"\r\n [id]=\"optionId(navIndexOf(item.id))\"\r\n [attr.aria-selected]=\"false\"\r\n [class.fly-strategy-selector__option--active]=\"navIndexOf(item.id) === activeIndex()\"\r\n (click)=\"pick(item)\"\r\n (keydown.enter)=\"pick(item)\"\r\n (mouseenter)=\"onOptionHover(navIndexOf(item.id))\"\r\n >\r\n <span class=\"fly-strategy-selector__option-title\">{{ item.title }}</span>\r\n </div>\r\n }\r\n }\r\n\r\n @if (groups().length === 0) {\r\n <div class=\"fly-strategy-selector__status\" role=\"presentation\">\r\n {{ 'strategy_selector.no_results' | translate }}\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{--_surface: var(--surface-card, #fff);--_surface-panel: var(--glass-bg-elevated, var(--surface-card, #fff));--_panel-blur: var(--glass-blur, 40px);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_surface-hover: var(--surface-hover, #f1f5f9);--_accent: var(--accent);--_primary: var(--primary-color, var(--_accent));--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}:host,:host *,:host *:before,:host *:after{box-sizing:border-box}.fly-strategy-selector__chip{display:flex;align-items:center;gap:8px;padding-block:5px;padding-inline:8px;border:1px solid var(--_border);border-radius:10px;background:var(--_fill);max-inline-size:100%}.fly-strategy-selector__chip-icon{display:inline-flex;flex:none;align-items:center;justify-content:center;inline-size:22px;block-size:22px;border-radius:50%;background:color-mix(in srgb,var(--_accent, var(--_text-subtle)) 14%,transparent);color:var(--_accent, var(--_text-subtle));font-size:12px;line-height:1}.fly-strategy-selector__chip-body{display:flex;flex-direction:column;min-inline-size:0;flex:1}.fly-strategy-selector__chip-title{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__chip-strategy{font-size:11px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__chip-remove{border:none;background:transparent;padding:4px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-strategy-selector__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-strategy-selector__wrap{position:relative;inline-size:100%}.fly-strategy-selector__search-input{inline-size:100%;min-block-size:2.25rem;padding-block:5px;padding-inline:10px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit}.fly-strategy-selector__search-input:focus-visible{outline:2px solid var(--_primary);outline-offset:-1px}.fly-strategy-selector__panel{position:absolute;z-index:60;inset-inline:0;inset-block-start:calc(100% + 4px);display:flex;flex-direction:column;max-block-size:19rem;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface-panel);-webkit-backdrop-filter:blur(var(--_panel-blur));backdrop-filter:blur(var(--_panel-blur));box-shadow:0 12px 32px #0000002e;overflow:hidden}.fly-strategy-selector__listbox{flex:1 1 auto;overflow-y:auto;padding-block:4px}.fly-strategy-selector__group{display:flex;align-items:center;gap:6px;padding-block:6px 3px;padding-inline:10px;color:var(--_text-subtle);font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.fly-strategy-selector__group .pi{font-size:11px}.fly-strategy-selector__group-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__option{padding-block:6px;padding-inline:26px 10px;cursor:pointer}.fly-strategy-selector__option--active{background:var(--_surface-hover)}.fly-strategy-selector__option-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__status{display:flex;align-items:center;gap:8px;padding-block:10px;padding-inline:10px;color:var(--_text-subtle)}.fly-strategy-selector__status--error{color:var(--_danger)}.fly-strategy-selector__retry{border:1px solid var(--_border);border-radius:6px;background:var(--_surface);padding-block:2px;padding-inline:8px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
19949
|
+
}
|
|
19950
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyStrategySelectorComponent, decorators: [{
|
|
19951
|
+
type: Component,
|
|
19952
|
+
args: [{ selector: 'fly-strategy-selector', standalone: true, imports: [CommonModule, TranslatePipe, FlyClickOutsideDirective], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fly-strategy-selector', '[class.fly-strategy-selector--open]': 'isOpen()' }, template: "@if (selected(); as pick) {\r\n <div class=\"fly-strategy-selector__chip\" role=\"group\" [attr.aria-label]=\"pick.title\">\r\n <span class=\"fly-strategy-selector__chip-icon\" aria-hidden=\"true\">\r\n @if (pick.strategyIcon) {\r\n <i class=\"pi {{ pick.strategyIcon }}\"></i>\r\n } @else {\r\n <i class=\"pi pi-compass\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-strategy-selector__chip-body\">\r\n <span class=\"fly-strategy-selector__chip-title\" [title]=\"pick.title\">{{ pick.title }}</span>\r\n <span class=\"fly-strategy-selector__chip-strategy\" [title]=\"pick.strategyTitle\">{{ pick.strategyTitle }}</span>\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-strategy-selector__chip-remove\"\r\n [attr.aria-label]=\"'strategy_selector.remove' | translate: { name: pick.title }\"\r\n (click)=\"remove()\">\r\n \u2715\r\n </button>\r\n </div>\r\n} @else {\r\n <div\r\n class=\"fly-strategy-selector__wrap\"\r\n [flyClickOutsideEnabled]=\"isOpen()\"\r\n (flyClickOutside)=\"close()\"\r\n >\r\n <input\r\n #searchRef\r\n type=\"text\"\r\n class=\"fly-strategy-selector__search-input\"\r\n role=\"combobox\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-controls=\"fly-strategy-selector-listbox\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"placeholder() || ('strategy_selector.search_placeholder' | translate)\"\r\n [placeholder]=\"placeholder() || ('strategy_selector.search_placeholder' | translate)\"\r\n [value]=\"searchTerm()\"\r\n (focus)=\"open()\"\r\n (input)=\"onSearchInput($any($event.target).value)\"\r\n (keydown)=\"onPanelKeydown($event)\"\r\n />\r\n\r\n @if (isOpen()) {\r\n <div class=\"fly-strategy-selector__panel\">\r\n <div\r\n class=\"fly-strategy-selector__listbox\"\r\n role=\"listbox\"\r\n id=\"fly-strategy-selector-listbox\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n >\r\n @if (loading()) {\r\n <div class=\"fly-strategy-selector__status\" role=\"status\">\r\n {{ 'strategy_selector.loading' | translate }}\r\n </div>\r\n } @else if (loadFailed()) {\r\n <div class=\"fly-strategy-selector__status fly-strategy-selector__status--error\" role=\"alert\">\r\n <span>{{ 'strategy_selector.error' | translate }}</span>\r\n <button type=\"button\" class=\"fly-strategy-selector__retry\" (click)=\"retry()\">\r\n {{ 'strategy_selector.retry' | translate }}\r\n </button>\r\n </div>\r\n } @else {\r\n @for (group of groups(); track group.strategyId) {\r\n <div class=\"fly-strategy-selector__group\" role=\"presentation\">\r\n <i class=\"pi {{ group.strategyIcon || 'pi-compass' }}\" aria-hidden=\"true\"></i>\r\n <span class=\"fly-strategy-selector__group-title\">{{ group.strategyTitle }}</span>\r\n </div>\r\n @for (item of group.items; track item.id) {\r\n <div\r\n class=\"fly-strategy-selector__option\"\r\n role=\"option\"\r\n tabindex=\"-1\"\r\n [id]=\"optionId(navIndexOf(item.id))\"\r\n [attr.aria-selected]=\"false\"\r\n [class.fly-strategy-selector__option--active]=\"navIndexOf(item.id) === activeIndex()\"\r\n (click)=\"pick(item)\"\r\n (keydown.enter)=\"pick(item)\"\r\n (mouseenter)=\"onOptionHover(navIndexOf(item.id))\"\r\n >\r\n <span class=\"fly-strategy-selector__option-title\">{{ item.title }}</span>\r\n </div>\r\n }\r\n }\r\n\r\n @if (groups().length === 0) {\r\n <div class=\"fly-strategy-selector__status\" role=\"presentation\">\r\n {{ 'strategy_selector.no_results' | translate }}\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{--_surface: var(--surface-card, #fff);--_surface-panel: var(--glass-bg-elevated, var(--surface-card, #fff));--_panel-blur: var(--glass-blur, 40px);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_surface-hover: var(--surface-hover, #f1f5f9);--_accent: var(--accent);--_primary: var(--primary-color, var(--_accent));--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}:host,:host *,:host *:before,:host *:after{box-sizing:border-box}.fly-strategy-selector__chip{display:flex;align-items:center;gap:8px;padding-block:5px;padding-inline:8px;border:1px solid var(--_border);border-radius:10px;background:var(--_fill);max-inline-size:100%}.fly-strategy-selector__chip-icon{display:inline-flex;flex:none;align-items:center;justify-content:center;inline-size:22px;block-size:22px;border-radius:50%;background:color-mix(in srgb,var(--_accent, var(--_text-subtle)) 14%,transparent);color:var(--_accent, var(--_text-subtle));font-size:12px;line-height:1}.fly-strategy-selector__chip-body{display:flex;flex-direction:column;min-inline-size:0;flex:1}.fly-strategy-selector__chip-title{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__chip-strategy{font-size:11px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__chip-remove{border:none;background:transparent;padding:4px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-strategy-selector__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-strategy-selector__wrap{position:relative;inline-size:100%}.fly-strategy-selector__search-input{inline-size:100%;min-block-size:2.25rem;padding-block:5px;padding-inline:10px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit}.fly-strategy-selector__search-input:focus-visible{outline:2px solid var(--_primary);outline-offset:-1px}.fly-strategy-selector__panel{position:absolute;z-index:60;inset-inline:0;inset-block-start:calc(100% + 4px);display:flex;flex-direction:column;max-block-size:19rem;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface-panel);-webkit-backdrop-filter:blur(var(--_panel-blur));backdrop-filter:blur(var(--_panel-blur));box-shadow:0 12px 32px #0000002e;overflow:hidden}.fly-strategy-selector__listbox{flex:1 1 auto;overflow-y:auto;padding-block:4px}.fly-strategy-selector__group{display:flex;align-items:center;gap:6px;padding-block:6px 3px;padding-inline:10px;color:var(--_text-subtle);font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.fly-strategy-selector__group .pi{font-size:11px}.fly-strategy-selector__group-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__option{padding-block:6px;padding-inline:26px 10px;cursor:pointer}.fly-strategy-selector__option--active{background:var(--_surface-hover)}.fly-strategy-selector__option-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__status{display:flex;align-items:center;gap:8px;padding-block:10px;padding-inline:10px;color:var(--_text-subtle)}.fly-strategy-selector__status--error{color:var(--_danger)}.fly-strategy-selector__retry{border:1px solid var(--_border);border-radius:6px;background:var(--_surface);padding-block:2px;padding-inline:8px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}\n"] }]
|
|
19953
|
+
}], ctorParameters: () => [], propDecorators: { searchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchFn", required: false }] }], initialSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSelection", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], searchEl: [{
|
|
19954
|
+
type: ViewChild,
|
|
19955
|
+
args: ['searchRef']
|
|
19956
|
+
}], onDocumentMouseDown: [{
|
|
19957
|
+
type: HostListener,
|
|
19958
|
+
args: ['document:mousedown', ['$event']]
|
|
19959
|
+
}] } });
|
|
19960
|
+
|
|
19352
19961
|
/** The platform endpoint the catalogue reads. Relative — the host's gateway + auth interceptors apply. */
|
|
19353
19962
|
const FLY_CURRENCIES_BRIEF_ENDPOINT = '/api/currencies/brief';
|
|
19354
19963
|
/**
|
|
@@ -19370,6 +19979,29 @@ function flyUnwrapCurrencies(res) {
|
|
|
19370
19979
|
const data = res?.data;
|
|
19371
19980
|
return Array.isArray(data) ? data : [];
|
|
19372
19981
|
}
|
|
19982
|
+
/** The platform endpoint serving the CALLING TENANT's default currency. Relative, like its
|
|
19983
|
+
* catalogue sibling — the host's gateway + auth interceptors apply, and the answer is scoped to
|
|
19984
|
+
* whatever tenant that auth resolves to. */
|
|
19985
|
+
const FLY_CURRENCY_DEFAULT_ENDPOINT = '/api/currencies/default';
|
|
19986
|
+
/**
|
|
19987
|
+
* Unwraps the single-row shape of `GET /api/currencies/default`.
|
|
19988
|
+
*
|
|
19989
|
+
* `null` is a SUCCESS answer, not a failure: a tenant with no country has no default currency,
|
|
19990
|
+
* and a platform-scoped caller has no tenant whose currency could be meant. Anything that is not
|
|
19991
|
+
* recognisably a currency row — an envelope with `data: null`, an error body, an HTML login page
|
|
19992
|
+
* from a misrouted request — collapses to `null` for the same reason
|
|
19993
|
+
* {@link flyUnwrapCurrencies} returns `[]`: a picker must degrade to "no preselection", never
|
|
19994
|
+
* preselect something the platform did not vouch for.
|
|
19995
|
+
*/
|
|
19996
|
+
function flyUnwrapCurrency(res) {
|
|
19997
|
+
const candidate = res !== null && typeof res === 'object' && 'data' in res
|
|
19998
|
+
? res.data
|
|
19999
|
+
: res;
|
|
20000
|
+
if (candidate === null || typeof candidate !== 'object')
|
|
20001
|
+
return null;
|
|
20002
|
+
const row = candidate;
|
|
20003
|
+
return typeof row.code === 'string' && row.code.length > 0 ? candidate : null;
|
|
20004
|
+
}
|
|
19373
20005
|
/**
|
|
19374
20006
|
* **`FlyCurrencyCatalogService`** — the one ISO-code → {@link FlyCurrency} adapter for the
|
|
19375
20007
|
* platform currency catalogue (`GET /api/currencies/brief`).
|
|
@@ -19490,6 +20122,8 @@ let _flyCurrencySelectorUid = 0;
|
|
|
19490
20122
|
* Re-exported from {@link FlyCurrencyCatalogService}'s module so the picker and the display-only
|
|
19491
20123
|
* catalogue cannot drift on which route serves the rows. */
|
|
19492
20124
|
const CURRENCIES_BRIEF_ENDPOINT = FLY_CURRENCIES_BRIEF_ENDPOINT;
|
|
20125
|
+
/** The platform endpoint behind {@link FlyCurrencySelectorComponent.applyTenantDefault}. */
|
|
20126
|
+
const CURRENCY_DEFAULT_ENDPOINT = FLY_CURRENCY_DEFAULT_ENDPOINT;
|
|
19493
20127
|
/** Cached answer of {@link probeFlagEmojiSupport} — the measurement is stable for a document's lifetime. */
|
|
19494
20128
|
let _flagEmojiSupport = null;
|
|
19495
20129
|
/**
|
|
@@ -19600,6 +20234,34 @@ class FlyCurrencySelectorComponent {
|
|
|
19600
20234
|
currencies = input(null, ...(ngDevMode ? [{ debugName: "currencies" }] : /* istanbul ignore next */ []));
|
|
19601
20235
|
/** Host-supplied loader, used when `[currencies]` is absent. Falls back to `GET /api/currencies/brief`. */
|
|
19602
20236
|
fetchFn = input(null, ...(ngDevMode ? [{ debugName: "fetchFn" }] : /* istanbul ignore next */ []));
|
|
20237
|
+
/**
|
|
20238
|
+
* Pre-fill an EMPTY single-select with the calling tenant's default currency
|
|
20239
|
+
* (`GET /api/currencies/default`, or `[tenantDefaultFetchFn]`). On by default.
|
|
20240
|
+
*
|
|
20241
|
+
* An amount is nearly always denominated in the tenant's reporting currency, and a form that
|
|
20242
|
+
* opens empty makes every user restate a fact the platform already holds — while an untouched
|
|
20243
|
+
* field silently saves null, which downstream money formatting cannot render at all.
|
|
20244
|
+
*
|
|
20245
|
+
* Deliberately narrow. It applies only when ALL of these hold, because outside them a
|
|
20246
|
+
* preselection would be a guess rather than a default:
|
|
20247
|
+
* - `mode="single"` — a multi-select accumulates a SET, and seeding one member of a set
|
|
20248
|
+
* states something the tenant currency does not say.
|
|
20249
|
+
* - not `locked()` — a frozen value is authoritative; proposing one contradicts the lock.
|
|
20250
|
+
* - the control is still empty — a written value always wins.
|
|
20251
|
+
*
|
|
20252
|
+
* It stays armed until something rules it out: a user commit (pick, clear, remove) or a
|
|
20253
|
+
* written non-empty value. A written NULL does not rule it out — the forms layer initializes
|
|
20254
|
+
* a fresh control by writing its empty value on a microtask, which can land AFTER a
|
|
20255
|
+
* synchronously-resolved default has already committed, and reading that write as a user
|
|
20256
|
+
* clear would erase the default. Clearing the field by hand does latch it off, so the value
|
|
20257
|
+
* cannot reappear and read as the clear having not registered.
|
|
20258
|
+
*
|
|
20259
|
+
* A host that supplied `[currencies]` or `[fetchFn]` is never taken to the platform endpoint —
|
|
20260
|
+
* see {@link tenantDefaultFetchFn}, which is how such a host opts back in.
|
|
20261
|
+
*/
|
|
20262
|
+
applyTenantDefault = input(true, ...(ngDevMode ? [{ debugName: "applyTenantDefault" }] : /* istanbul ignore next */ []));
|
|
20263
|
+
/** Host-supplied loader for {@link applyTenantDefault}. Falls back to `GET /api/currencies/default`. */
|
|
20264
|
+
tenantDefaultFetchFn = input(null, ...(ngDevMode ? [{ debugName: "tenantDefaultFetchFn" }] : /* istanbul ignore next */ []));
|
|
19603
20265
|
/** Restrict the offered set to these ISO 4217 codes (case-insensitive). Empty = offer everything. */
|
|
19604
20266
|
allowedCodes = input([], ...(ngDevMode ? [{ debugName: "allowedCodes" }] : /* istanbul ignore next */ []));
|
|
19605
20267
|
/**
|
|
@@ -19660,6 +20322,21 @@ class FlyCurrencySelectorComponent {
|
|
|
19660
20322
|
_selectedCodes = signal([], ...(ngDevMode ? [{ debugName: "_selectedCodes" }] : /* istanbul ignore next */ []));
|
|
19661
20323
|
_cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
19662
20324
|
_fetchStarted = false;
|
|
20325
|
+
/** Guards the once-per-instance tenant-default resolve. */
|
|
20326
|
+
_tenantDefaultStarted = false;
|
|
20327
|
+
/**
|
|
20328
|
+
* Latches once the default has been RULED OUT: a user commit (pick, clear, remove) or a
|
|
20329
|
+
* written non-empty value. Applying the default does NOT latch it, and neither does a null
|
|
20330
|
+
* write: `NgModel` syncs the model's initial value on a microtask (`resolvedPromise.then` in
|
|
20331
|
+
* its `_updateValue`), so a SYNCHRONOUS `tenantDefaultFetchFn` — `of(currency)`, an offline
|
|
20332
|
+
* lookup — commits before that first write lands. When the self-application latched this
|
|
20333
|
+
* guard, the stale null write erased the committed default AND pinned the guard off, so the
|
|
20334
|
+
* default never applied for exactly those hosts (async HTTP fetches always lost the race
|
|
20335
|
+
* and were unaffected).
|
|
20336
|
+
*/
|
|
20337
|
+
_tenantDefaultRuledOut = false;
|
|
20338
|
+
/** The resolved row, kept so a `writeValue(null)` arriving AFTER the fetch can still use it. */
|
|
20339
|
+
_tenantDefaultRow = null;
|
|
19663
20340
|
searchEl;
|
|
19664
20341
|
triggerEl;
|
|
19665
20342
|
_onChange = () => undefined;
|
|
@@ -19675,6 +20352,34 @@ class FlyCurrencySelectorComponent {
|
|
|
19675
20352
|
this._fetchStarted = true;
|
|
19676
20353
|
this._load();
|
|
19677
20354
|
});
|
|
20355
|
+
// A control that HOLDS a value must resolve it, whether or not the user ever opens the
|
|
20356
|
+
// panel: `selected()` maps codes through the loaded rows, so a saved code with no rows
|
|
20357
|
+
// renders as the placeholder — an edit form reading "Select a currency" over a project that
|
|
20358
|
+
// is denominated in AED. The lazy rule is still intact for the case it was written for: an
|
|
20359
|
+
// EMPTY selector on a tab nobody opens fetches nothing.
|
|
20360
|
+
effect(() => {
|
|
20361
|
+
if (this._fetchStarted)
|
|
20362
|
+
return;
|
|
20363
|
+
if (this.currencies() !== null)
|
|
20364
|
+
return;
|
|
20365
|
+
if (this._selectedCodes().length === 0)
|
|
20366
|
+
return;
|
|
20367
|
+
this._fetchStarted = true;
|
|
20368
|
+
this._load();
|
|
20369
|
+
});
|
|
20370
|
+
// The tenant default is resolved EAGERLY, unlike the catalogue: its whole job is to be in
|
|
20371
|
+
// place before the user looks at the field, so deferring it to first open would defeat it.
|
|
20372
|
+
// The cost is bounded - one small request per defaulting selector, against an endpoint that
|
|
20373
|
+
// returns a single row. An effect rather than the constructor body so `applyTenantDefault`
|
|
20374
|
+
// and `locked` are read AFTER the host's bindings are applied, not at their declared values.
|
|
20375
|
+
effect(() => {
|
|
20376
|
+
if (this._tenantDefaultStarted)
|
|
20377
|
+
return;
|
|
20378
|
+
if (!this.applyTenantDefault() || this.isMulti() || this.locked())
|
|
20379
|
+
return;
|
|
20380
|
+
this._tenantDefaultStarted = true;
|
|
20381
|
+
this._loadTenantDefault();
|
|
20382
|
+
});
|
|
19678
20383
|
}
|
|
19679
20384
|
// ── Derived view state ─────────────────────────────────────────────────────
|
|
19680
20385
|
effectiveDisabled = computed(() => this.disabled() || this._cvaDisabled(), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
@@ -19899,13 +20604,74 @@ class FlyCurrencySelectorComponent {
|
|
|
19899
20604
|
this._loaded.set(rows);
|
|
19900
20605
|
});
|
|
19901
20606
|
}
|
|
20607
|
+
_loadTenantDefault() {
|
|
20608
|
+
const fetch = this.tenantDefaultFetchFn();
|
|
20609
|
+
// A host that took over the CATALOGUE owns the default too: `[currencies]`/`[fetchFn]` are
|
|
20610
|
+
// the "do not reach the platform routes" declaration (offline builds, fixtures, apps that
|
|
20611
|
+
// proxy reference data), and honouring it for the rows while quietly firing a platform
|
|
20612
|
+
// request for the default would break exactly the consumers those inputs exist for. Such a
|
|
20613
|
+
// host opts back in by supplying `[tenantDefaultFetchFn]`.
|
|
20614
|
+
const hostOwnsData = this.currencies() !== null || this.fetchFn() !== null;
|
|
20615
|
+
const source = fetch
|
|
20616
|
+
? fetch()
|
|
20617
|
+
: this.http && !hostOwnsData
|
|
20618
|
+
? this.http.get(CURRENCY_DEFAULT_ENDPOINT).pipe(map(flyUnwrapCurrency))
|
|
20619
|
+
: null;
|
|
20620
|
+
// No transport and no loader is not an error state here: an offline surface simply has no
|
|
20621
|
+
// default. Nothing is surfaced to the user - unlike a failed CATALOGUE load, which must be
|
|
20622
|
+
// retryable because it leaves the control unusable.
|
|
20623
|
+
if (!source)
|
|
20624
|
+
return;
|
|
20625
|
+
source.pipe(catchError$1(() => of(null))).subscribe((row) => {
|
|
20626
|
+
this._tenantDefaultRow = row;
|
|
20627
|
+
this._applyTenantDefaultIfEmpty();
|
|
20628
|
+
});
|
|
20629
|
+
}
|
|
20630
|
+
/**
|
|
20631
|
+
* Applies the resolved default to a still-empty control.
|
|
20632
|
+
*
|
|
20633
|
+
* Called from both ends of a race with no fixed winner: the fetch may land before the forms
|
|
20634
|
+
* layer writes, or after it writes the empty value a form is built with. It can also run
|
|
20635
|
+
* AGAIN after applying — the forms layer's deferred initial null write clears the codes and
|
|
20636
|
+
* re-enters here — in which case it re-commits the same row, which also re-syncs the
|
|
20637
|
+
* `FormControl` the null write just reset.
|
|
20638
|
+
*/
|
|
20639
|
+
_applyTenantDefaultIfEmpty() {
|
|
20640
|
+
if (this._tenantDefaultRuledOut)
|
|
20641
|
+
return;
|
|
20642
|
+
if (!this.applyTenantDefault() || this.isMulti() || this.locked())
|
|
20643
|
+
return;
|
|
20644
|
+
const row = this._tenantDefaultRow;
|
|
20645
|
+
if (!row)
|
|
20646
|
+
return;
|
|
20647
|
+
if (this._selectedCodes().length > 0)
|
|
20648
|
+
return;
|
|
20649
|
+
// Seed the row so the trigger can RENDER the pick straight away. Without this the selection
|
|
20650
|
+
// would be invisible until the catalogue loads - which is lazy, on first open - so the field
|
|
20651
|
+
// would look untouched while its form value said otherwise. `_load` replaces the whole set
|
|
20652
|
+
// on success, and leaves this seed alone on failure.
|
|
20653
|
+
if (this.currencies() === null && this._loaded().length === 0)
|
|
20654
|
+
this._loaded.set([row]);
|
|
20655
|
+
this._commit([row.code], { fromTenantDefault: true });
|
|
20656
|
+
}
|
|
19902
20657
|
// ── ControlValueAccessor ───────────────────────────────────────────────────
|
|
19903
20658
|
writeValue(value) {
|
|
19904
20659
|
if (value === null || value === undefined) {
|
|
19905
20660
|
this._selectedCodes.set([]);
|
|
20661
|
+
// An empty write re-tries the default - this is the common `patchValue`-after-load path,
|
|
20662
|
+
// and on a fresh form it is the write that arrives BEFORE the fetch has landed. It is
|
|
20663
|
+
// ALSO NgModel's microtask-deferred initial write arriving AFTER a synchronous fetch has
|
|
20664
|
+
// committed — re-trying restores the default that write just cleared.
|
|
20665
|
+
this._applyTenantDefaultIfEmpty();
|
|
19906
20666
|
return;
|
|
19907
20667
|
}
|
|
19908
|
-
|
|
20668
|
+
const codes = typeof value === 'string' ? [value] : [...value];
|
|
20669
|
+
this._selectedCodes.set(codes);
|
|
20670
|
+
// A written value is the host's own answer; there is no hole left for a default to fill.
|
|
20671
|
+
if (codes.length > 0)
|
|
20672
|
+
this._tenantDefaultRuledOut = true;
|
|
20673
|
+
else
|
|
20674
|
+
this._applyTenantDefaultIfEmpty();
|
|
19909
20675
|
}
|
|
19910
20676
|
registerOnChange(fn) {
|
|
19911
20677
|
this._onChange = fn;
|
|
@@ -19921,7 +20687,14 @@ class FlyCurrencySelectorComponent {
|
|
|
19921
20687
|
const codes = this._selectedCodes();
|
|
19922
20688
|
return this.isMulti() ? [...codes] : (codes[0] ?? null);
|
|
19923
20689
|
}
|
|
19924
|
-
_commit(codes) {
|
|
20690
|
+
_commit(codes, opts) {
|
|
20691
|
+
// Every user-driven change routes through here - pick, toggle, remove, clear - so this is the
|
|
20692
|
+
// one place that has to latch the default off. A cleared field that refilled itself would be
|
|
20693
|
+
// indistinguishable from the clear never having registered. The one caller that must NOT
|
|
20694
|
+
// latch is the default's own application: latching there let the forms layer's deferred
|
|
20695
|
+
// initial writeValue(null) erase a synchronously-fetched default for good.
|
|
20696
|
+
if (!opts?.fromTenantDefault)
|
|
20697
|
+
this._tenantDefaultRuledOut = true;
|
|
19925
20698
|
this._selectedCodes.set([...codes]);
|
|
19926
20699
|
this._onChange(this._controlValue());
|
|
19927
20700
|
this.selectionChange.emit([...codes]);
|
|
@@ -19931,7 +20704,7 @@ class FlyCurrencySelectorComponent {
|
|
|
19931
20704
|
this._onTouched();
|
|
19932
20705
|
}
|
|
19933
20706
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCurrencySelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
19934
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyCurrencySelectorComponent, isStandalone: true, selector: "fly-currency-selector", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, currencies: { classPropertyName: "currencies", publicName: "currencies", isSignal: true, isRequired: false, transformFunction: null }, fetchFn: { classPropertyName: "fetchFn", publicName: "fetchFn", isSignal: true, isRequired: false, transformFunction: null }, allowedCodes: { classPropertyName: "allowedCodes", publicName: "allowedCodes", isSignal: true, isRequired: false, transformFunction: null }, pinnedCodes: { classPropertyName: "pinnedCodes", publicName: "pinnedCodes", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, locked: { classPropertyName: "locked", publicName: "locked", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonKey: { classPropertyName: "lockedReasonKey", publicName: "lockedReasonKey", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonParams: { classPropertyName: "lockedReasonParams", publicName: "lockedReasonParams", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", selectionDetailChange: "selectionDetailChange", openedChange: "openedChange" }, host: { properties: { "class.fly-currency-selector--open": "isOpen()", "class.fly-currency-selector--disabled": "effectiveDisabled()", "class.fly-currency-selector--locked": "locked()" }, classAttribute: "fly-currency-selector" }, providers: [
|
|
20707
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyCurrencySelectorComponent, isStandalone: true, selector: "fly-currency-selector", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, currencies: { classPropertyName: "currencies", publicName: "currencies", isSignal: true, isRequired: false, transformFunction: null }, fetchFn: { classPropertyName: "fetchFn", publicName: "fetchFn", isSignal: true, isRequired: false, transformFunction: null }, applyTenantDefault: { classPropertyName: "applyTenantDefault", publicName: "applyTenantDefault", isSignal: true, isRequired: false, transformFunction: null }, tenantDefaultFetchFn: { classPropertyName: "tenantDefaultFetchFn", publicName: "tenantDefaultFetchFn", isSignal: true, isRequired: false, transformFunction: null }, allowedCodes: { classPropertyName: "allowedCodes", publicName: "allowedCodes", isSignal: true, isRequired: false, transformFunction: null }, pinnedCodes: { classPropertyName: "pinnedCodes", publicName: "pinnedCodes", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, locked: { classPropertyName: "locked", publicName: "locked", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonKey: { classPropertyName: "lockedReasonKey", publicName: "lockedReasonKey", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonParams: { classPropertyName: "lockedReasonParams", publicName: "lockedReasonParams", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", selectionDetailChange: "selectionDetailChange", openedChange: "openedChange" }, host: { properties: { "class.fly-currency-selector--open": "isOpen()", "class.fly-currency-selector--disabled": "effectiveDisabled()", "class.fly-currency-selector--locked": "locked()" }, classAttribute: "fly-currency-selector" }, providers: [
|
|
19935
20708
|
{
|
|
19936
20709
|
provide: NG_VALUE_ACCESSOR,
|
|
19937
20710
|
useExisting: forwardRef(() => FlyCurrencySelectorComponent),
|
|
@@ -19953,7 +20726,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
19953
20726
|
'[class.fly-currency-selector--disabled]': 'effectiveDisabled()',
|
|
19954
20727
|
'[class.fly-currency-selector--locked]': 'locked()',
|
|
19955
20728
|
}, template: "<div\r\n class=\"fly-currency-selector__wrap\"\r\n [flyClickOutsideEnabled]=\"isOpen()\"\r\n (flyClickOutside)=\"close()\"\r\n>\r\n @if (locked()) {\r\n <!-- \u2500\u2500 Locked readout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 NOT a dimmed `disabled` trigger: there is no dropdown affordance here at all \u2014 the\r\n value is frozen on purpose, not merely unavailable right now. `role=\"group\"` +\r\n `aria-label` name the field the way the combobox trigger's `aria-label` does; the\r\n reason is REAL, always-rendered text (not a `title` attribute, which many screen\r\n readers never announce and which no sighted user sees without hovering) and is\r\n additionally wired via `aria-describedby` for AT that reads by relationship rather\r\n than document order. -->\r\n <div\r\n class=\"fly-currency-selector__locked\"\r\n role=\"group\"\r\n [attr.aria-label]=\"ariaLabel() || placeholderText()\"\r\n [attr.aria-describedby]=\"lockedReasonId\"\r\n >\r\n @if (selected().length === 0) {\r\n <span class=\"fly-currency-selector__placeholder\">{{ placeholderText() }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__chips\">\r\n @for (c of selected(); track c.code) {\r\n <span class=\"fly-currency-selector__chip\">\r\n <span class=\"fly-currency-selector__tile\" aria-hidden=\"true\">\r\n @if (flagsRenderable() && c.flag) {\r\n <span class=\"fly-currency-selector__tile-flag\">{{ c.flag }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__tile-code\">{{ tileCode(c) }}</span>\r\n }\r\n </span>\r\n <span class=\"fly-currency-selector__chip-code\">{{ c.code }}</span>\r\n <span class=\"fly-currency-selector__chip-symbol\" aria-hidden=\"true\">{{ c.symbol }}</span>\r\n </span>\r\n }\r\n </span>\r\n }\r\n <span class=\"fly-currency-selector__lock-icon\" aria-hidden=\"true\">🔒</span>\r\n </div>\r\n <p class=\"fly-currency-selector__locked-reason\" [id]=\"lockedReasonId\">{{ lockedReasonText() }}</p>\r\n } @else {\r\n\r\n <!-- \u2500\u2500 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 `aria-label` is never null: a `combobox` does NOT take its accessible name from its\r\n contents the way a plain button does, so the chips/placeholder painted inside it name\r\n nothing. Absent a consumer label the localized placeholder supplies the control's\r\n purpose; its VALUE is still announced from the contents. -->\r\n <button\r\n #triggerRef\r\n type=\"button\"\r\n [id]=\"triggerId\"\r\n class=\"fly-currency-selector__trigger\"\r\n role=\"combobox\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n [attr.aria-controls]=\"listboxId\"\r\n [attr.aria-label]=\"ariaLabel() || placeholderText()\"\r\n [class.fly-currency-selector__trigger--empty]=\"!hasSelection()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (click)=\"toggleOpen()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n >\r\n @if (selected().length === 0) {\r\n <span class=\"fly-currency-selector__placeholder\">{{ placeholderText() }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__chips\">\r\n @for (c of selected(); track c.code) {\r\n <span class=\"fly-currency-selector__chip\">\r\n <span class=\"fly-currency-selector__tile\" aria-hidden=\"true\">\r\n @if (flagsRenderable() && c.flag) {\r\n <span class=\"fly-currency-selector__tile-flag\">{{ c.flag }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__tile-code\">{{ tileCode(c) }}</span>\r\n }\r\n </span>\r\n <span class=\"fly-currency-selector__chip-code\">{{ c.code }}</span>\r\n <span class=\"fly-currency-selector__chip-symbol\" aria-hidden=\"true\">{{ c.symbol }}</span>\r\n @if (isMulti() && !effectiveDisabled()) {\r\n <span\r\n class=\"fly-currency-selector__chip-remove\"\r\n role=\"button\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'\u2715 ' + c.code\"\r\n (click)=\"remove(c.code, $event)\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n >\u2715</span>\r\n }\r\n </span>\r\n }\r\n </span>\r\n }\r\n\r\n @if (clearable() && hasSelection() && !effectiveDisabled()) {\r\n <span\r\n class=\"fly-currency-selector__clear\"\r\n role=\"button\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"clearText()\"\r\n [title]=\"clearText()\"\r\n (click)=\"clear($event)\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n >\u2715</span>\r\n }\r\n\r\n <span class=\"fly-currency-selector__chevron\" aria-hidden=\"true\">\u25BE</span>\r\n </button>\r\n\r\n <!-- \u2500\u2500 Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 (isOpen()) {\r\n <div class=\"fly-currency-selector__panel\" tabindex=\"-1\" (keydown)=\"onPanelKeydown($event)\">\r\n <div class=\"fly-currency-selector__search\">\r\n <input\r\n #searchRef\r\n type=\"text\"\r\n class=\"fly-currency-selector__search-input\"\r\n role=\"combobox\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-expanded]=\"true\"\r\n [attr.aria-controls]=\"listboxId\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"searchPlaceholderText()\"\r\n [placeholder]=\"searchPlaceholderText()\"\r\n [value]=\"searchTerm()\"\r\n (input)=\"onSearchInput($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n @if (loading()) {\r\n <div class=\"fly-currency-selector__status\" role=\"status\">{{ loadingText() }}</div>\r\n } @else if (loadFailed()) {\r\n <div class=\"fly-currency-selector__status fly-currency-selector__status--error\" role=\"alert\">\r\n <span>{{ errorText() }}</span>\r\n <button type=\"button\" class=\"fly-currency-selector__retry\" (click)=\"retry()\">\r\n {{ retryText() }}\r\n </button>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"fly-currency-selector__listbox\"\r\n role=\"listbox\"\r\n [id]=\"listboxId\"\r\n [attr.aria-multiselectable]=\"isMulti() ? true : null\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n >\r\n @if (pinned().length > 0) {\r\n <div class=\"fly-currency-selector__group\" role=\"presentation\">{{ pinnedGroupText() }}</div>\r\n @for (c of pinned(); track c.code) {\r\n <ng-container *ngTemplateOutlet=\"row; context: { $implicit: c }\" />\r\n }\r\n @if (rest().length > 0) {\r\n <div class=\"fly-currency-selector__group\" role=\"presentation\">{{ allGroupText() }}</div>\r\n }\r\n }\r\n @for (c of rest(); track c.code) {\r\n <ng-container *ngTemplateOutlet=\"row; context: { $implicit: c }\" />\r\n }\r\n\r\n @if (!loading() && !loadFailed() && filtered().length === 0) {\r\n <div class=\"fly-currency-selector__status\" role=\"presentation\">{{ noResultsText() }}</div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 One option row, shared by the pinned and full groups \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n<ng-template #row let-c>\r\n <div\r\n class=\"fly-currency-selector__option\"\r\n role=\"option\"\r\n tabindex=\"-1\"\r\n [id]=\"optionId(navIndexOf(c.code))\"\r\n [attr.aria-selected]=\"isSelected(c.code)\"\r\n [class.fly-currency-selector__option--selected]=\"isSelected(c.code)\"\r\n [class.fly-currency-selector__option--active]=\"navIndexOf(c.code) === activeIndex()\"\r\n (click)=\"pick(c)\"\r\n (keydown.enter)=\"pick(c)\"\r\n (mouseenter)=\"onOptionHover(navIndexOf(c.code))\"\r\n >\r\n <span class=\"fly-currency-selector__tile\" aria-hidden=\"true\">\r\n @if (flagsRenderable() && c.flag) {\r\n <span class=\"fly-currency-selector__tile-flag\">{{ c.flag }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__tile-code\">{{ tileCode(c) }}</span>\r\n }\r\n </span>\r\n\r\n <span class=\"fly-currency-selector__option-text\">\r\n <span class=\"fly-currency-selector__option-code\">{{ c.code }}</span>\r\n <span class=\"fly-currency-selector__option-name\">{{ c.name }}</span>\r\n </span>\r\n\r\n <span class=\"fly-currency-selector__option-symbol\" aria-hidden=\"true\">{{ c.symbol }}</span>\r\n\r\n <span class=\"fly-currency-selector__check\" aria-hidden=\"true\">\r\n @if (isSelected(c.code)) { \u2713 }\r\n </span>\r\n </div>\r\n</ng-template>\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-panel: var(--glass-bg-elevated, var(--surface-card, #fff));--_panel-blur: var(--glass-blur, 40px);--_surface-hover: var(--surface-hover, #f1f5f9);--_surface-active: var(--surface-active, #eef2ff);--_border: var(--surface-border, #e2e8f0);--_text: var(--text-color, #0f172a);--_text-subtle: var(--text-color-secondary, #64748b);--_fill: var(--fill-tertiary, #f3f4f6);--_primary: var(--primary-color);--_danger: var(--system-red, #ef4444);--_radius: 8px;display:inline-block;inline-size:100%;min-inline-size:11rem;font-size:13px;color:var(--_text)}.fly-currency-selector__wrap{position:relative;inline-size:100%}.fly-currency-selector__trigger{display:flex;align-items:center;gap:8px;inline-size:100%;min-block-size:2.25rem;padding-block:4px;padding-inline:8px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;text-align:start;cursor:pointer}.fly-currency-selector__trigger:focus-visible{outline:2px solid var(--_primary);outline-offset:2px}.fly-currency-selector__trigger:disabled{opacity:.55;cursor:not-allowed}.fly-currency-selector__placeholder{flex:1 1 auto;padding-inline-start:4px;color:var(--_text-subtle);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-currency-selector__chips{display:flex;flex:1 1 auto;flex-wrap:wrap;gap:4px;min-inline-size:0}.fly-currency-selector__chip{display:inline-flex;align-items:center;gap:5px;padding-block:2px;padding-inline:3px 7px;border-radius:999px;background:var(--_fill);font-size:12px}.fly-currency-selector__chip-code{font-weight:700;letter-spacing:.02em}.fly-currency-selector__chip-symbol{color:var(--_text-subtle)}.fly-currency-selector__chip-remove{padding:1px 3px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer}.fly-currency-selector__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-currency-selector__clear,.fly-currency-selector__chevron{flex:none;color:var(--_text-subtle);font-size:11px;line-height:1}.fly-currency-selector__clear{padding:3px;border-radius:50%;cursor:pointer}.fly-currency-selector__clear:hover{background:var(--_surface-hover);color:var(--_text)}.fly-currency-selector__tile{display:inline-flex;flex:none;align-items:center;justify-content:center;inline-size:26px;block-size:19px;border-radius:4px;background:var(--_fill);overflow:hidden}.fly-currency-selector__tile-flag{font-size:15px;line-height:1}.fly-currency-selector__tile-code{font-size:9px;font-weight:800;letter-spacing:.04em;color:var(--_text-subtle);direction:ltr;unicode-bidi:isolate}.fly-currency-selector__locked{display:flex;align-items:center;gap:8px;inline-size:100%;min-block-size:2.25rem;padding-block:4px;padding-inline:8px;border:1px dashed var(--_border);border-radius:var(--_radius);background:var(--_fill);color:var(--_text);cursor:default}.fly-currency-selector__lock-icon{flex:none;margin-inline-start:auto;font-size:12px;opacity:.7}.fly-currency-selector__locked-reason{margin:4px 0 0;color:var(--_text-subtle);font-size:11px;line-height:1.4}.fly-currency-selector__panel{position:absolute;z-index:60;inset-inline:0;inset-block-start:calc(100% + 4px);display:flex;flex-direction:column;max-block-size:19rem;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface-panel);-webkit-backdrop-filter:blur(var(--_panel-blur));backdrop-filter:blur(var(--_panel-blur));box-shadow:0 12px 32px #0000002e;overflow:hidden}.fly-currency-selector__search{padding:6px;border-block-end:1px solid var(--_border)}.fly-currency-selector__search-input{inline-size:100%;padding-block:5px;padding-inline:8px;border:1px solid var(--_border);border-radius:6px;background:var(--_surface);color:var(--_text);font:inherit}.fly-currency-selector__search-input:focus-visible{outline:2px solid var(--_primary);outline-offset:-1px}.fly-currency-selector__listbox{flex:1 1 auto;overflow-y:auto;padding-block:4px}.fly-currency-selector__group{padding-block:6px 3px;padding-inline:10px;color:var(--_text-subtle);font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}.fly-currency-selector__option{display:flex;align-items:center;gap:9px;padding-block:5px;padding-inline:10px;cursor:pointer}.fly-currency-selector__option--active{background:var(--_surface-hover)}.fly-currency-selector__option--selected{background:var(--_surface-active)}.fly-currency-selector__option-text{display:flex;flex:1 1 auto;align-items:baseline;gap:8px;min-inline-size:0}.fly-currency-selector__option-code{flex:none;font-weight:700;letter-spacing:.02em;direction:ltr;unicode-bidi:isolate}.fly-currency-selector__option-name{flex:1 1 auto;min-inline-size:0;color:var(--_text-subtle);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-currency-selector__option-symbol{flex:none;min-inline-size:2.2em;color:var(--_text);text-align:end}.fly-currency-selector__check{flex:none;inline-size:1em;color:var(--_primary)}.fly-currency-selector__status{display:flex;align-items:center;gap:8px;padding-block:10px;padding-inline:10px;color:var(--_text-subtle)}.fly-currency-selector__status--error{color:var(--_danger)}.fly-currency-selector__retry{border:1px solid var(--_border);border-radius:6px;background:var(--_surface);padding-block:2px;padding-inline:8px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}\n"] }]
|
|
19956
|
-
}], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], currencies: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencies", required: false }] }], fetchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "fetchFn", required: false }] }], allowedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedCodes", required: false }] }], pinnedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedCodes", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], locked: [{ type: i0.Input, args: [{ isSignal: true, alias: "locked", required: false }] }], lockedReasonKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonKey", required: false }] }], lockedReasonParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonParams", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], selectionDetailChange: [{ type: i0.Output, args: ["selectionDetailChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], searchEl: [{
|
|
20729
|
+
}], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], currencies: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencies", required: false }] }], fetchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "fetchFn", required: false }] }], applyTenantDefault: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyTenantDefault", required: false }] }], tenantDefaultFetchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "tenantDefaultFetchFn", required: false }] }], allowedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedCodes", required: false }] }], pinnedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedCodes", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], locked: [{ type: i0.Input, args: [{ isSignal: true, alias: "locked", required: false }] }], lockedReasonKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonKey", required: false }] }], lockedReasonParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonParams", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], selectionDetailChange: [{ type: i0.Output, args: ["selectionDetailChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], searchEl: [{
|
|
19957
20730
|
type: ViewChild,
|
|
19958
20731
|
args: ['searchRef']
|
|
19959
20732
|
}], triggerEl: [{
|
|
@@ -21811,14 +22584,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
21811
22584
|
args: [{ selector: 'fly-meta', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<dl>\r\n @for (item of items(); track $index) {\r\n <div>\r\n <dt>{{ item.labelKey | translate }}</dt>\r\n <dd [class.mono]=\"item.mono ?? false\">{{ item.value }}</dd>\r\n </div>\r\n }\r\n <ng-content />\r\n</dl>\r\n", styles: [":host{display:block}dl{margin:0;display:flex;flex-direction:column;gap:10px}dl ::ng-deep>div{display:grid;grid-template-columns:96px 1fr;gap:var(--sp-3);align-items:baseline;font-size:var(--text-sm)}dl ::ng-deep dt{margin:0;font-size:10.5px;font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-4)}dl ::ng-deep dd{margin:0;min-width:0;color:var(--ink);word-break:break-word;line-height:1.45}dl ::ng-deep dd.mono,dl ::ng-deep dd .mono{font-size:var(--text-2xs)}\n"] }]
|
|
21812
22585
|
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }] } });
|
|
21813
22586
|
|
|
21814
|
-
function canGoPrev(page) {
|
|
22587
|
+
function canGoPrev$1(page) {
|
|
21815
22588
|
return page > 1;
|
|
21816
22589
|
}
|
|
21817
|
-
function canGoNext(page, totalPages) {
|
|
22590
|
+
function canGoNext$1(page, totalPages) {
|
|
21818
22591
|
return page < totalPages;
|
|
21819
22592
|
}
|
|
21820
22593
|
/** Clamp a requested page into `[1, max(1, totalPages)]`. */
|
|
21821
|
-
function clampPage(page, totalPages) {
|
|
22594
|
+
function clampPage$1(page, totalPages) {
|
|
21822
22595
|
return Math.min(Math.max(1, page), Math.max(1, totalPages));
|
|
21823
22596
|
}
|
|
21824
22597
|
|
|
@@ -21853,10 +22626,10 @@ class FlyPaginationComponent {
|
|
|
21853
22626
|
showEdges = input(false, ...(ngDevMode ? [{ debugName: "showEdges" }] : /* istanbul ignore next */ []));
|
|
21854
22627
|
/** Emits the target page (already clamped to bounds). */
|
|
21855
22628
|
pageChange = output();
|
|
21856
|
-
prevEnabled = computed(() => canGoPrev(this.page()), ...(ngDevMode ? [{ debugName: "prevEnabled" }] : /* istanbul ignore next */ []));
|
|
21857
|
-
nextEnabled = computed(() => canGoNext(this.page(), this.totalPages()), ...(ngDevMode ? [{ debugName: "nextEnabled" }] : /* istanbul ignore next */ []));
|
|
22629
|
+
prevEnabled = computed(() => canGoPrev$1(this.page()), ...(ngDevMode ? [{ debugName: "prevEnabled" }] : /* istanbul ignore next */ []));
|
|
22630
|
+
nextEnabled = computed(() => canGoNext$1(this.page(), this.totalPages()), ...(ngDevMode ? [{ debugName: "nextEnabled" }] : /* istanbul ignore next */ []));
|
|
21858
22631
|
goTo(target) {
|
|
21859
|
-
const next = clampPage(target, this.totalPages());
|
|
22632
|
+
const next = clampPage$1(target, this.totalPages());
|
|
21860
22633
|
if (next !== this.page())
|
|
21861
22634
|
this.pageChange.emit(next);
|
|
21862
22635
|
}
|
|
@@ -21868,6 +22641,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
21868
22641
|
args: [{ selector: 'fly-pagination', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"pg\" [attr.aria-label]=\"'ui.pagination.label' | translate\">\r\n @if (rangeText(); as range) {\r\n <span class=\"pg__range\">{{ range }}</span>\r\n }\r\n <div class=\"pg__controls\">\r\n @if (showEdges()) {\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(1)\"\r\n [attr.aria-label]=\"'ui.pagination.first' | translate\"\r\n ><span class=\"pg__chev\" aria-hidden=\"true\">\u00AB</span></button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(page() - 1)\"\r\n [attr.aria-label]=\"'ui.pagination.previous' | translate\"\r\n >\r\n <svg class=\"pg__arrow\" data-arrow=\"prev\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <polyline points=\"15 6 9 12 15 18\" />\r\n </svg>\r\n </button>\r\n <span class=\"pg__current\" aria-live=\"polite\">\r\n {{ 'ui.pagination.pageOf' | translate: { page: page(), total: totalPages() } }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(page() + 1)\"\r\n [attr.aria-label]=\"'ui.pagination.next' | translate\"\r\n >\r\n <svg class=\"pg__arrow\" data-arrow=\"next\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n </button>\r\n @if (showEdges()) {\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(totalPages())\"\r\n [attr.aria-label]=\"'ui.pagination.last' | translate\"\r\n ><span class=\"pg__chev\" aria-hidden=\"true\">\u00BB</span></button>\r\n }\r\n </div>\r\n</nav>\r\n", styles: [".pg{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);margin-block-start:var(--sp-4);font-size:var(--text-sm);color:var(--ink-3)}.pg__range{font-size:var(--text-sm);color:var(--ink-3);direction:ltr;unicode-bidi:isolate}.pg__range b{color:var(--ink);font-weight:var(--fw-medium)}.pg__controls{display:flex;align-items:center;gap:2px;margin-inline-start:auto;font-size:var(--text-sm)}.pg__btn{inline-size:26px;block-size:26px;padding:0;border:1px solid transparent;background:transparent;color:var(--ink-3);border-radius:50%;font-size:var(--text-sm);cursor:pointer;display:inline-grid;place-items:center;transition:background var(--t-state),color var(--t-state)}.pg__btn:hover:not(:disabled){background:var(--w06);color:var(--ink)}.pg__btn:disabled{color:var(--w2);cursor:default}.pg__btn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.pg__arrow{inline-size:14px;block-size:14px}:host-context([dir=rtl]) .pg__arrow{transform:scaleX(-1)}.pg__current{font-size:var(--text-sm);font-weight:var(--fw-semibold);font-variant-numeric:tabular-nums;padding:0 var(--sp-3);background:var(--accent);border:1px solid transparent;border-radius:999px;color:var(--on-accent-fill);height:24px;display:inline-grid;place-items:center;direction:ltr;unicode-bidi:isolate}:host-context(.rtl) .pg__chev,:host-context([dir=rtl]) .pg__chev{display:inline-block;transform:scaleX(-1)}\n"] }]
|
|
21869
22642
|
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], totalPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalPages", required: true }] }], rangeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeText", required: false }] }], showEdges: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEdges", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }] } });
|
|
21870
22643
|
|
|
22644
|
+
/** 0-indexed sibling of `pagination.logic.ts` — see `pager.component.ts` for why. */
|
|
22645
|
+
function canGoPrev(page) {
|
|
22646
|
+
return page > 0;
|
|
22647
|
+
}
|
|
22648
|
+
function canGoNext(page, totalPages) {
|
|
22649
|
+
return page < totalPages - 1;
|
|
22650
|
+
}
|
|
22651
|
+
/** Clamp a requested page into `[0, max(0, totalPages - 1)]`. */
|
|
22652
|
+
function clampPage(page, totalPages) {
|
|
22653
|
+
return Math.min(Math.max(0, page), Math.max(0, totalPages - 1));
|
|
22654
|
+
}
|
|
22655
|
+
|
|
22656
|
+
/**
|
|
22657
|
+
* Numbered list pager — back arrow, one pill per page, forward arrow — for the
|
|
22658
|
+
* lists that want every page as a target rather than `fly-pagination`'s
|
|
22659
|
+
* «prev / page of total / next» band. Shares the list's surface and continues
|
|
22660
|
+
* its border (see `COMPONENTS.md`): pair with a list container that sets
|
|
22661
|
+
* `border-bottom: 0` so the two read as one panel.
|
|
22662
|
+
*
|
|
22663
|
+
* <fly-pager [page]="page()" [totalPages]="totalPages()" (goToPage)="goTo($event)" />
|
|
22664
|
+
*
|
|
22665
|
+
* `page`/the emitted target are 0-indexed (array-index pagination); buttons
|
|
22666
|
+
* display `page + 1`. This is the opposite convention from `fly-pagination`
|
|
22667
|
+
* (1-indexed) — the two are separate components for separate list shapes, not
|
|
22668
|
+
* a shared base, so pick the one matching how the caller already indexes its
|
|
22669
|
+
* pages rather than converting at the call site.
|
|
22670
|
+
*/
|
|
22671
|
+
class FlyPagerComponent {
|
|
22672
|
+
page = input.required(...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
|
|
22673
|
+
totalPages = input.required(...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
|
|
22674
|
+
/** Emits the target page (already clamped to bounds), 0-indexed. */
|
|
22675
|
+
goToPage = output();
|
|
22676
|
+
prevEnabled = computed(() => canGoPrev(this.page()), ...(ngDevMode ? [{ debugName: "prevEnabled" }] : /* istanbul ignore next */ []));
|
|
22677
|
+
nextEnabled = computed(() => canGoNext(this.page(), this.totalPages()), ...(ngDevMode ? [{ debugName: "nextEnabled" }] : /* istanbul ignore next */ []));
|
|
22678
|
+
pages = computed(() => Array.from({ length: Math.max(0, this.totalPages()) }, (_, i) => i), ...(ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []));
|
|
22679
|
+
goTo(target) {
|
|
22680
|
+
const next = clampPage(target, this.totalPages());
|
|
22681
|
+
if (next !== this.page())
|
|
22682
|
+
this.goToPage.emit(next);
|
|
22683
|
+
}
|
|
22684
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyPagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
22685
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyPagerComponent, isStandalone: true, selector: "fly-pager", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null }, totalPages: { classPropertyName: "totalPages", publicName: "totalPages", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { goToPage: "goToPage" }, ngImport: i0, template: "@if (totalPages() > 1) {\r\n <nav class=\"pgr\" [attr.aria-label]=\"'ui.pagination.label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"back\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(page() - 1)\"\r\n [attr.aria-label]=\"'ui.pager.previous' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M14.8 5.2L8 12l6.8 6.8\" />\r\n </svg>\r\n </button>\r\n @for (p of pages(); track p) {\r\n <button\r\n type=\"button\"\r\n class=\"pgr__page\"\r\n [class.pgr__page--current]=\"p === page()\"\r\n [attr.aria-current]=\"p === page() ? 'page' : null\"\r\n (click)=\"goTo(p)\"\r\n [attr.aria-label]=\"'ui.pager.page' | translate: { page: p + 1 }\"\r\n >{{ p + 1 }}</button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"forward\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(page() + 1)\"\r\n [attr.aria-label]=\"'ui.pager.next' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M9.2 5.2L16 12l-6.8 6.8\" />\r\n </svg>\r\n </button>\r\n </nav>\r\n}\r\n", styles: [".pgr{flex:none;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:4px;padding:14px 8px;background:var(--w03);border:1px solid var(--w08);border-top:0}.pgr__arrow{flex:none;inline-size:26px;block-size:26px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--w6);cursor:pointer;display:grid;place-items:center;transition:background var(--t-state),color var(--t-state)}.pgr__arrow svg{inline-size:14px;block-size:14px}.pgr__arrow:hover:not(:disabled){background:var(--w06)}.pgr__arrow:disabled{color:var(--w2);cursor:default}.pgr__arrow:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}:host-context([dir=rtl]) .pgr__arrow[data-arrow] svg{transform:scaleX(-1)}.pgr__page{flex:none;min-inline-size:24px;block-size:24px;padding:0 3px;border:0;border-radius:50%;background:transparent;color:var(--w45);cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;line-height:1;font-variant-numeric:tabular-nums;transition:background var(--t-state),color var(--t-state)}.pgr__page:hover{background:var(--w08)}.pgr__page--current{background:var(--accent);color:var(--on-accent-fill)}.pgr__page--current:hover{background:var(--accent)}.pgr__page:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
22686
|
+
}
|
|
22687
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyPagerComponent, decorators: [{
|
|
22688
|
+
type: Component,
|
|
22689
|
+
args: [{ selector: 'fly-pager', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (totalPages() > 1) {\r\n <nav class=\"pgr\" [attr.aria-label]=\"'ui.pagination.label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"back\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(page() - 1)\"\r\n [attr.aria-label]=\"'ui.pager.previous' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M14.8 5.2L8 12l6.8 6.8\" />\r\n </svg>\r\n </button>\r\n @for (p of pages(); track p) {\r\n <button\r\n type=\"button\"\r\n class=\"pgr__page\"\r\n [class.pgr__page--current]=\"p === page()\"\r\n [attr.aria-current]=\"p === page() ? 'page' : null\"\r\n (click)=\"goTo(p)\"\r\n [attr.aria-label]=\"'ui.pager.page' | translate: { page: p + 1 }\"\r\n >{{ p + 1 }}</button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"forward\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(page() + 1)\"\r\n [attr.aria-label]=\"'ui.pager.next' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M9.2 5.2L16 12l-6.8 6.8\" />\r\n </svg>\r\n </button>\r\n </nav>\r\n}\r\n", styles: [".pgr{flex:none;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:4px;padding:14px 8px;background:var(--w03);border:1px solid var(--w08);border-top:0}.pgr__arrow{flex:none;inline-size:26px;block-size:26px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--w6);cursor:pointer;display:grid;place-items:center;transition:background var(--t-state),color var(--t-state)}.pgr__arrow svg{inline-size:14px;block-size:14px}.pgr__arrow:hover:not(:disabled){background:var(--w06)}.pgr__arrow:disabled{color:var(--w2);cursor:default}.pgr__arrow:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}:host-context([dir=rtl]) .pgr__arrow[data-arrow] svg{transform:scaleX(-1)}.pgr__page{flex:none;min-inline-size:24px;block-size:24px;padding:0 3px;border:0;border-radius:50%;background:transparent;color:var(--w45);cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;line-height:1;font-variant-numeric:tabular-nums;transition:background var(--t-state),color var(--t-state)}.pgr__page:hover{background:var(--w08)}.pgr__page--current{background:var(--accent);color:var(--on-accent-fill)}.pgr__page--current:hover{background:var(--accent)}.pgr__page:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}\n"] }]
|
|
22690
|
+
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], totalPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalPages", required: true }] }], goToPage: [{ type: i0.Output, args: ["goToPage"] }] } });
|
|
22691
|
+
|
|
21871
22692
|
/**
|
|
21872
22693
|
* Listing card shell — one anatomy for the three reference card patterns:
|
|
21873
22694
|
*
|
|
@@ -22828,11 +23649,11 @@ class PageHeaderComponent {
|
|
|
22828
23649
|
return '#';
|
|
22829
23650
|
}
|
|
22830
23651
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: PageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
22831
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: PageHeaderComponent, isStandalone: true, selector: "fly-page-header", inputs: { variant: "variant", title: "title", titleKey: "titleKey", subtitle: "subtitle", subtitleKey: "subtitleKey", titleAr: "titleAr", eyebrow: "eyebrow", eyebrowKey: "eyebrowKey", metricValue: "metricValue", metricLabel: "metricLabel", metricLabelKey: "metricLabelKey", breadcrumbs: "breadcrumbs" }, outputs: { crumbClick: "crumbClick" }, ngImport: i0, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6);container-type:inline-size}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);
|
|
23652
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: PageHeaderComponent, isStandalone: true, selector: "fly-page-header", inputs: { variant: "variant", title: "title", titleKey: "titleKey", subtitle: "subtitle", subtitleKey: "subtitleKey", titleAr: "titleAr", eyebrow: "eyebrow", eyebrowKey: "eyebrowKey", metricValue: "metricValue", metricLabel: "metricLabel", metricLabelKey: "metricLabelKey", breadcrumbs: "breadcrumbs" }, outputs: { crumbClick: "crumbClick" }, ngImport: i0, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6);container-type:inline-size}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);letter-spacing:0;line-height:1.5;font-weight:var(--fw-medium)}.ph__metric{text-align:end}.ph__metric-v{font-family:var(--font-mono, ui-monospace, monospace);font-variant-numeric:tabular-nums;font-size:22px;font-weight:var(--fw-medium);letter-spacing:-.02em;color:var(--ink)}.ph__metric-l{font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em}.ph__hd-actions{display:flex;flex-direction:column;align-items:flex-end;gap:10px}.ph__hd-btns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}.ph--listing,.ph--detail{margin-bottom:0}@container (width <= 640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}@media(width<=640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
22832
23653
|
}
|
|
22833
23654
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: PageHeaderComponent, decorators: [{
|
|
22834
23655
|
type: Component,
|
|
22835
|
-
args: [{ selector: 'fly-page-header', standalone: true, imports: [NgClass, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6);container-type:inline-size}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);
|
|
23656
|
+
args: [{ selector: 'fly-page-header', standalone: true, imports: [NgClass, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6);container-type:inline-size}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);letter-spacing:0;line-height:1.5;font-weight:var(--fw-medium)}.ph__metric{text-align:end}.ph__metric-v{font-family:var(--font-mono, ui-monospace, monospace);font-variant-numeric:tabular-nums;font-size:22px;font-weight:var(--fw-medium);letter-spacing:-.02em;color:var(--ink)}.ph__metric-l{font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em}.ph__hd-actions{display:flex;flex-direction:column;align-items:flex-end;gap:10px}.ph__hd-btns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}.ph--listing,.ph--detail{margin-bottom:0}@container (width <= 640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}@media(width<=640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}\n"] }]
|
|
22836
23657
|
}], propDecorators: { variant: [{
|
|
22837
23658
|
type: Input
|
|
22838
23659
|
}], title: [{
|
|
@@ -23518,14 +24339,14 @@ class FlyAppTopbarComponent {
|
|
|
23518
24339
|
this.rows()[index]?.nativeElement.focus();
|
|
23519
24340
|
}
|
|
23520
24341
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
23521
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppTopbarComponent, isStandalone: true, selector: "fly-app-topbar", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, footerLabel: { classPropertyName: "footerLabel", publicName: "footerLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-topbar--docked": "docked()" } }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], viewQueries: [{ propertyName: "rows", predicate: ["row"], descendants: true, isSignal: true }, { propertyName: "trigger", predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"triggerAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--w1);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-sm);font-size:var(--text-sm);line-height:1;color:var(--label-secondary)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold)}:host(.fly-topbar--docked) .fly-topbar__sep{color:var(--w28)}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:22px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:22px;max-block-size:22px}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--w1)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:.5px solid var(--w08);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
24342
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppTopbarComponent, isStandalone: true, selector: "fly-app-topbar", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, footerLabel: { classPropertyName: "footerLabel", publicName: "footerLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-topbar--docked": "docked()" } }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], viewQueries: [{ propertyName: "rows", predicate: ["row"], descendants: true, isSignal: true }, { propertyName: "trigger", predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"triggerAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--w1);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-md);gap:6px;font-size:var(--text-sm);line-height:1;color:var(--w55)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{background:var(--w06);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold);letter-spacing:-.006em}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__module-label{letter-spacing:normal}:host(.fly-topbar--docked) .fly-topbar__sep{position:relative;display:inline-block;inline-size:7px;block-size:7px;color:transparent;border-block-start:1.5px solid var(--w28);border-inline-end:1.5px solid var(--w28);transform:rotate(45deg)}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__sep{transform:rotate(-135deg)}:host(.fly-topbar--docked) .fly-topbar__actions{display:none}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:14px;max-block-size:14px}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--w1)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:.5px solid var(--w08);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
23522
24343
|
}
|
|
23523
24344
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, decorators: [{
|
|
23524
24345
|
type: Component,
|
|
23525
24346
|
args: [{ selector: 'fly-app-topbar', standalone: true, imports: [TranslatePipe, FlyClickOutsideDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
23526
24347
|
'(document:keydown.escape)': 'onEscape()',
|
|
23527
24348
|
'[class.fly-topbar--docked]': 'docked()',
|
|
23528
|
-
}, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"triggerAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--w1);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-sm);font-size:var(--text-sm);line-height:1;color:var(--label-secondary)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold)}:host(.fly-topbar--docked) .fly-topbar__sep{color:var(--w28)}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:22px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:22px;max-block-size:22px}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--w1)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:.5px solid var(--w08);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"] }]
|
|
24349
|
+
}, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"triggerAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--w1);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-md);gap:6px;font-size:var(--text-sm);line-height:1;color:var(--w55)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{background:var(--w06);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold);letter-spacing:-.006em}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__module-label{letter-spacing:normal}:host(.fly-topbar--docked) .fly-topbar__sep{position:relative;display:inline-block;inline-size:7px;block-size:7px;color:transparent;border-block-start:1.5px solid var(--w28);border-inline-end:1.5px solid var(--w28);transform:rotate(45deg)}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__sep{transform:rotate(-135deg)}:host(.fly-topbar--docked) .fly-topbar__actions{display:none}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:14px;max-block-size:14px}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--w1)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:.5px solid var(--w08);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"] }]
|
|
23529
24350
|
}], ctorParameters: () => [], propDecorators: { brandLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLabelKey", required: false }] }], sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: false }] }], footerLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerLabel", required: false }] }], moduleSelected: [{ type: i0.Output, args: ["moduleSelected"] }], brandSelected: [{ type: i0.Output, args: ["brandSelected"] }], icons: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FlyModuleIconDirective), { isSignal: true }] }], rows: [{ type: i0.ViewChildren, args: ['row', { isSignal: true }] }], trigger: [{ type: i0.ViewChildren, args: ['trigger', { isSignal: true }] }] } });
|
|
23530
24351
|
|
|
23531
24352
|
/**
|
|
@@ -24249,66 +25070,66 @@ class FlySegmentedComponent {
|
|
|
24249
25070
|
this.el.nativeElement.querySelectorAll('[role="radio"]')[next]?.focus();
|
|
24250
25071
|
}
|
|
24251
25072
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySegmentedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
24252
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlySegmentedComponent, isStandalone: true, selector: "fly-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", values: "valuesChange" }, ngImport: i0, template: `
|
|
24253
|
-
<div
|
|
24254
|
-
class="seg"
|
|
24255
|
-
[attr.data-variant]="variant()"
|
|
24256
|
-
[attr.role]="multiple() ? 'group' : 'radiogroup'"
|
|
24257
|
-
tabindex="-1"
|
|
24258
|
-
[attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
|
|
24259
|
-
(keydown)="onKey($event)"
|
|
24260
|
-
>
|
|
24261
|
-
@for (opt of options(); track opt.value) {
|
|
24262
|
-
<button
|
|
24263
|
-
type="button"
|
|
24264
|
-
class="seg__btn"
|
|
24265
|
-
[attr.role]="multiple() ? null : 'radio'"
|
|
24266
|
-
[class.on]="isOn(opt.value)"
|
|
24267
|
-
[attr.aria-checked]="multiple() ? null : isOn(opt.value)"
|
|
24268
|
-
[attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
|
|
24269
|
-
[attr.tabindex]="multiple() ? null : tabIndexFor($index)"
|
|
24270
|
-
[disabled]="disabled()"
|
|
24271
|
-
(click)="select(opt.value)"
|
|
24272
|
-
>
|
|
24273
|
-
{{ opt.labelKey | translate }}
|
|
24274
|
-
@if (opt.count !== undefined) {
|
|
24275
|
-
<span class="seg__count">{{ opt.count }}</span>
|
|
24276
|
-
}
|
|
24277
|
-
</button>
|
|
24278
|
-
}
|
|
24279
|
-
</div>
|
|
25073
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlySegmentedComponent, isStandalone: true, selector: "fly-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", values: "valuesChange" }, ngImport: i0, template: `
|
|
25074
|
+
<div
|
|
25075
|
+
class="seg"
|
|
25076
|
+
[attr.data-variant]="variant()"
|
|
25077
|
+
[attr.role]="multiple() ? 'group' : 'radiogroup'"
|
|
25078
|
+
tabindex="-1"
|
|
25079
|
+
[attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
|
|
25080
|
+
(keydown)="onKey($event)"
|
|
25081
|
+
>
|
|
25082
|
+
@for (opt of options(); track opt.value) {
|
|
25083
|
+
<button
|
|
25084
|
+
type="button"
|
|
25085
|
+
class="seg__btn"
|
|
25086
|
+
[attr.role]="multiple() ? null : 'radio'"
|
|
25087
|
+
[class.on]="isOn(opt.value)"
|
|
25088
|
+
[attr.aria-checked]="multiple() ? null : isOn(opt.value)"
|
|
25089
|
+
[attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
|
|
25090
|
+
[attr.tabindex]="multiple() ? null : tabIndexFor($index)"
|
|
25091
|
+
[disabled]="disabled()"
|
|
25092
|
+
(click)="select(opt.value)"
|
|
25093
|
+
>
|
|
25094
|
+
{{ opt.labelKey | translate }}
|
|
25095
|
+
@if (opt.count !== undefined) {
|
|
25096
|
+
<span class="seg__count">{{ opt.count }}</span>
|
|
25097
|
+
}
|
|
25098
|
+
</button>
|
|
25099
|
+
}
|
|
25100
|
+
</div>
|
|
24280
25101
|
`, isInline: true, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg[data-variant=pills]{gap:6px;padding:0;background:transparent;border:0;border-radius:0}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}.seg[data-variant=pills] .seg__btn{min-block-size:30px;padding:0 12px;border:.5px solid var(--w12);background:var(--w04);color:var(--w7);border-radius:99px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.seg[data-variant=pills] .seg__btn:hover:not(.on){background:var(--w08);border-color:var(--w2);color:var(--ink)}.seg[data-variant=pills] .seg__btn.on{border-color:transparent;background:var(--accent);color:var(--on-accent);font-weight:500;box-shadow:none}.seg[data-variant=pills] .seg__count{padding:2px 6px;border-radius:7px;font-family:var(--font-family);font-size:10.5px;font-weight:var(--fw-semibold);color:inherit}.seg[data-variant=pills] .seg__btn.on .seg__count{background:var(--w22);color:inherit}@media(pointer:coarse){.seg__btn,.seg[data-variant=pills] .seg__btn{min-block-size:44px}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
24281
25102
|
}
|
|
24282
25103
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySegmentedComponent, decorators: [{
|
|
24283
25104
|
type: Component,
|
|
24284
|
-
args: [{ selector: 'fly-segmented', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
24285
|
-
<div
|
|
24286
|
-
class="seg"
|
|
24287
|
-
[attr.data-variant]="variant()"
|
|
24288
|
-
[attr.role]="multiple() ? 'group' : 'radiogroup'"
|
|
24289
|
-
tabindex="-1"
|
|
24290
|
-
[attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
|
|
24291
|
-
(keydown)="onKey($event)"
|
|
24292
|
-
>
|
|
24293
|
-
@for (opt of options(); track opt.value) {
|
|
24294
|
-
<button
|
|
24295
|
-
type="button"
|
|
24296
|
-
class="seg__btn"
|
|
24297
|
-
[attr.role]="multiple() ? null : 'radio'"
|
|
24298
|
-
[class.on]="isOn(opt.value)"
|
|
24299
|
-
[attr.aria-checked]="multiple() ? null : isOn(opt.value)"
|
|
24300
|
-
[attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
|
|
24301
|
-
[attr.tabindex]="multiple() ? null : tabIndexFor($index)"
|
|
24302
|
-
[disabled]="disabled()"
|
|
24303
|
-
(click)="select(opt.value)"
|
|
24304
|
-
>
|
|
24305
|
-
{{ opt.labelKey | translate }}
|
|
24306
|
-
@if (opt.count !== undefined) {
|
|
24307
|
-
<span class="seg__count">{{ opt.count }}</span>
|
|
24308
|
-
}
|
|
24309
|
-
</button>
|
|
24310
|
-
}
|
|
24311
|
-
</div>
|
|
25105
|
+
args: [{ selector: 'fly-segmented', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
25106
|
+
<div
|
|
25107
|
+
class="seg"
|
|
25108
|
+
[attr.data-variant]="variant()"
|
|
25109
|
+
[attr.role]="multiple() ? 'group' : 'radiogroup'"
|
|
25110
|
+
tabindex="-1"
|
|
25111
|
+
[attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
|
|
25112
|
+
(keydown)="onKey($event)"
|
|
25113
|
+
>
|
|
25114
|
+
@for (opt of options(); track opt.value) {
|
|
25115
|
+
<button
|
|
25116
|
+
type="button"
|
|
25117
|
+
class="seg__btn"
|
|
25118
|
+
[attr.role]="multiple() ? null : 'radio'"
|
|
25119
|
+
[class.on]="isOn(opt.value)"
|
|
25120
|
+
[attr.aria-checked]="multiple() ? null : isOn(opt.value)"
|
|
25121
|
+
[attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
|
|
25122
|
+
[attr.tabindex]="multiple() ? null : tabIndexFor($index)"
|
|
25123
|
+
[disabled]="disabled()"
|
|
25124
|
+
(click)="select(opt.value)"
|
|
25125
|
+
>
|
|
25126
|
+
{{ opt.labelKey | translate }}
|
|
25127
|
+
@if (opt.count !== undefined) {
|
|
25128
|
+
<span class="seg__count">{{ opt.count }}</span>
|
|
25129
|
+
}
|
|
25130
|
+
</button>
|
|
25131
|
+
}
|
|
25132
|
+
</div>
|
|
24312
25133
|
`, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg[data-variant=pills]{gap:6px;padding:0;background:transparent;border:0;border-radius:0}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}.seg[data-variant=pills] .seg__btn{min-block-size:30px;padding:0 12px;border:.5px solid var(--w12);background:var(--w04);color:var(--w7);border-radius:99px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.seg[data-variant=pills] .seg__btn:hover:not(.on){background:var(--w08);border-color:var(--w2);color:var(--ink)}.seg[data-variant=pills] .seg__btn.on{border-color:transparent;background:var(--accent);color:var(--on-accent);font-weight:500;box-shadow:none}.seg[data-variant=pills] .seg__count{padding:2px 6px;border-radius:7px;font-family:var(--font-family);font-size:10.5px;font-weight:var(--fw-semibold);color:inherit}.seg[data-variant=pills] .seg__btn.on .seg__count{background:var(--w22);color:inherit}@media(pointer:coarse){.seg__btn,.seg[data-variant=pills] .seg__btn{min-block-size:44px}}\n"] }]
|
|
24313
25134
|
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }, { type: i0.Output, args: ["valuesChange"] }] } });
|
|
24314
25135
|
|
|
@@ -26067,5 +26888,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
26067
26888
|
* Generated bundle index. Do not edit.
|
|
26068
26889
|
*/
|
|
26069
26890
|
|
|
26070
|
-
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 };
|
|
26891
|
+
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_CURRENCY_DEFAULT_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_STRATEGY_OBJECTIVES_LOOKUP_ENDPOINT, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_BREADCRUMBS_EVENT, FLY_WINDOW_BREADCRUMBS_STORE_KEY, 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, FlyPagerComponent, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyStrategySelectorComponent, 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, WindowBreadcrumbsRegistry, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext$1 as canGoNext, canGoPrev$1 as canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage$1 as 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, flyUnwrapCurrency, flyUnwrapLenient, flyUnwrapObjectiveLookup, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, canGoNext as pagerCanGoNext, canGoPrev as pagerCanGoPrev, clampPage as pagerClampPage, 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 };
|
|
26071
26892
|
//# sourceMappingURL=flyos-design-system.mjs.map
|