@rdlabo/ionic-angular-kit 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -127,8 +127,8 @@ export class MyPage {
|
|
|
127
127
|
readonly #overlay = inject(KitOverlayController);
|
|
128
128
|
|
|
129
129
|
async openDetail(): Promise<void> {
|
|
130
|
-
const result = await this.#overlay.presentModal
|
|
131
|
-
// result is
|
|
130
|
+
const result = await this.#overlay.presentModal(DetailPage, { item });
|
|
131
|
+
// result type is inferred from `declare static modalReturn` on DetailPage
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
async confirm(): Promise<void> {
|
|
@@ -149,11 +149,12 @@ export class MyPage {
|
|
|
149
149
|
**API**
|
|
150
150
|
|
|
151
151
|
```typescript
|
|
152
|
-
presentModal<
|
|
153
|
-
component:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
)
|
|
152
|
+
presentModal<C extends ModalOptions['component']>(
|
|
153
|
+
component: C,
|
|
154
|
+
...args: ModalPresentArgs<C>, // props inferred from input() fields; options?: KitModalPresentOptions
|
|
155
|
+
): Promise<ModalReturnOf<C> | undefined>
|
|
156
|
+
// Props inferred from the component's input() fields (required/optional).
|
|
157
|
+
// Return type inferred from `declare static modalReturn: T` on the component (void if absent).
|
|
157
158
|
|
|
158
159
|
presentPopover<O>(
|
|
159
160
|
component: PopoverOptions['component'],
|
|
@@ -182,9 +183,14 @@ alertConfirm(options: {
|
|
|
182
183
|
**Best practice — the modal launcher pattern.** Never call `modalController.create(...)` inline in a component. Instead, each modal/popover page exports a typed launcher next to itself and every call site goes through `KitOverlayController`:
|
|
183
184
|
|
|
184
185
|
```typescript
|
|
185
|
-
// detail.page.ts
|
|
186
|
-
export
|
|
187
|
-
|
|
186
|
+
// detail.page.ts — component declares its return type:
|
|
187
|
+
export class DetailPage {
|
|
188
|
+
declare static modalReturn: DetailResult;
|
|
189
|
+
readonly item = input.required<Item>();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export const launchDetailPage = (overlay: KitOverlayController, props: { item: Item }): Promise<DetailResult | undefined> =>
|
|
193
|
+
overlay.presentModal(DetailPage, props, { backdropDismiss: false });
|
|
188
194
|
```
|
|
189
195
|
|
|
190
196
|
This centralizes presentation options, keeps component props and dismiss data type-safe, and makes every modal discoverable. A well-disciplined app has **zero** inline `controller.create()` calls.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, Injectable, InjectionToken, makeEnvironmentProviders, ElementRef, Directive, DOCUMENT } from '@angular/core';
|
|
3
3
|
import { Storage } from '@ionic/storage-angular';
|
|
4
|
-
import { ModalController, PopoverController, ToastController, AlertController, NavController } from '@ionic/angular/standalone';
|
|
4
|
+
import { ModalController, PopoverController, ToastController, AlertController, LoadingController, NavController } from '@ionic/angular/standalone';
|
|
5
5
|
import { Capacitor } from '@capacitor/core';
|
|
6
6
|
import { Keyboard } from '@capacitor/keyboard';
|
|
7
7
|
import { ImpactStyle, Haptics } from '@capacitor/haptics';
|
|
@@ -185,7 +185,7 @@ const watchModalKeyboard = async (modal) => {
|
|
|
185
185
|
* constructor(private readonly overlay: KitOverlayController) {}
|
|
186
186
|
*
|
|
187
187
|
* async edit(): Promise<void> {
|
|
188
|
-
* const result = await this.overlay.presentModal
|
|
188
|
+
* const result = await this.overlay.presentModal(EditPage, { id: 1 });
|
|
189
189
|
* if (result) {
|
|
190
190
|
* await this.overlay.presentToast({ message: 'Saved' });
|
|
191
191
|
* }
|
|
@@ -204,22 +204,6 @@ class KitOverlayController {
|
|
|
204
204
|
* second alert on top of the first. Shared across both methods so a confirm cannot stack over a close.
|
|
205
205
|
*/
|
|
206
206
|
#alertPresenting = false;
|
|
207
|
-
/**
|
|
208
|
-
* Present a modal and resolve with the data passed to its dismissal.
|
|
209
|
-
*
|
|
210
|
-
* @typeParam O - type of the data returned when the modal is dismissed
|
|
211
|
-
* @param component - the component to render inside the modal
|
|
212
|
-
* @param componentProps - props to pass to the modal component
|
|
213
|
-
* @param options - additional modal options, including {@link KitModalPresentOptions.watchKeyboard}
|
|
214
|
-
* @returns the dismiss data, or `undefined` when the modal is dismissed without data
|
|
215
|
-
* @remarks
|
|
216
|
-
* Presenting a modal triggers light native haptic feedback as an intentional kit UX choice,
|
|
217
|
-
* consistent with {@link presentPopover} and {@link presentToast}.
|
|
218
|
-
* @example
|
|
219
|
-
* ```ts
|
|
220
|
-
* const data = await overlay.presentModal<{ saved: boolean }>(EditPage, { id: 1 }, { watchKeyboard: true });
|
|
221
|
-
* ```
|
|
222
|
-
*/
|
|
223
207
|
async presentModal(component, componentProps, options = {}) {
|
|
224
208
|
void kitImpact();
|
|
225
209
|
const { watchKeyboard, ...modalOptions } = options;
|
|
@@ -381,6 +365,119 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
381
365
|
}]
|
|
382
366
|
}] });
|
|
383
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Reference-counted wrapper around Ionic's `LoadingController` that keeps at most one loading
|
|
370
|
+
* indicator on screen across concurrent async work.
|
|
371
|
+
*
|
|
372
|
+
* @remarks
|
|
373
|
+
* Each {@link presentLoading} increments a counter and each {@link dismissLoading} decrements it; the
|
|
374
|
+
* indicator is presented on the `0 → 1` transition and dismissed on the `N → 0` transition. This
|
|
375
|
+
* removes the flicker / "stuck spinner" bugs that come from every service calling
|
|
376
|
+
* `LoadingController.create/dismiss` independently.
|
|
377
|
+
*
|
|
378
|
+
* All operations are serialized through an internal promise chain, so a `create → present` sequence
|
|
379
|
+
* can never interleave with a concurrent `dismiss`. That is what makes the counter race-safe: a
|
|
380
|
+
* dismiss that arrives while the indicator is still being presented runs *after* `present()` settles
|
|
381
|
+
* and therefore tears the element down instead of leaving it orphaned on screen.
|
|
382
|
+
*
|
|
383
|
+
* Always pair every `presentLoading()` with exactly one `dismissLoading()` — a `try/finally` is the
|
|
384
|
+
* safest shape.
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```ts
|
|
388
|
+
* constructor(private readonly loading: KitLoadingController) {}
|
|
389
|
+
*
|
|
390
|
+
* async save(): Promise<void> {
|
|
391
|
+
* await this.loading.presentLoading({ message: 'Saving…' });
|
|
392
|
+
* try {
|
|
393
|
+
* await this.api.save();
|
|
394
|
+
* } finally {
|
|
395
|
+
* await this.loading.dismissLoading();
|
|
396
|
+
* }
|
|
397
|
+
* }
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
class KitLoadingController {
|
|
401
|
+
#loadingCtrl = inject(LoadingController);
|
|
402
|
+
/** Outstanding {@link presentLoading} calls not yet balanced by {@link dismissLoading}. */
|
|
403
|
+
#count = 0;
|
|
404
|
+
/** The single presented loading element, or `null` when none is on screen. */
|
|
405
|
+
#loading = null;
|
|
406
|
+
/**
|
|
407
|
+
* Serializes present/dismiss operations. Each call chains onto this promise, runs after the
|
|
408
|
+
* previous operation has fully settled, then reads {@link #count} and acts accordingly.
|
|
409
|
+
*/
|
|
410
|
+
#queue = Promise.resolve();
|
|
411
|
+
/**
|
|
412
|
+
* Show the loading indicator, or join the one already on screen.
|
|
413
|
+
*
|
|
414
|
+
* @param options - Ionic loading options; only applied by the call that actually creates the
|
|
415
|
+
* indicator (the `0 → 1` transition). Ignored while an indicator is already present.
|
|
416
|
+
* @returns a Promise that resolves once the indicator is on screen (or immediately when one already is)
|
|
417
|
+
*/
|
|
418
|
+
async presentLoading(options = {}) {
|
|
419
|
+
this.#count++;
|
|
420
|
+
try {
|
|
421
|
+
await this.#enqueue(async () => {
|
|
422
|
+
// Create only on the transition into "something is loading"; concurrent callers ride the same
|
|
423
|
+
// element. Re-check the count in case a dismiss already balanced this call while queued.
|
|
424
|
+
if (this.#count > 0 && this.#loading === null) {
|
|
425
|
+
const loading = await this.#loadingCtrl.create(options);
|
|
426
|
+
await loading.present();
|
|
427
|
+
this.#loading = loading;
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
// Roll back the reference this call took: a failed create/present must not leave the counter
|
|
433
|
+
// elevated, otherwise a later cycle never reaches N → 0 and the spinner stays stuck on screen.
|
|
434
|
+
this.#count--;
|
|
435
|
+
throw error;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Release one reference; dismiss the indicator once the last reference is gone.
|
|
440
|
+
*
|
|
441
|
+
* @returns a Promise that resolves once the reference is released (and the indicator dismissed if
|
|
442
|
+
* this was the last one). No-ops when the counter is already at zero.
|
|
443
|
+
*/
|
|
444
|
+
async dismissLoading() {
|
|
445
|
+
if (this.#count === 0) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
this.#count--;
|
|
449
|
+
await this.#enqueue(async () => {
|
|
450
|
+
// Tear down only when the last consumer is gone. Because this runs after any in-flight
|
|
451
|
+
// present() has settled (via the queue), there is never an orphaned loading element.
|
|
452
|
+
if (this.#count === 0 && this.#loading !== null) {
|
|
453
|
+
const loading = this.#loading;
|
|
454
|
+
this.#loading = null;
|
|
455
|
+
await loading.dismiss();
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Append `task` to the serialization chain and return its completion.
|
|
461
|
+
*
|
|
462
|
+
* @remarks
|
|
463
|
+
* The stored chain swallows rejections so a single failing operation cannot wedge every future
|
|
464
|
+
* overlay; the returned promise still rejects so the caller observes the error.
|
|
465
|
+
*/
|
|
466
|
+
#enqueue(task) {
|
|
467
|
+
const run = this.#queue.then(task);
|
|
468
|
+
this.#queue = run.then(() => undefined, () => undefined);
|
|
469
|
+
return run;
|
|
470
|
+
}
|
|
471
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KitLoadingController, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
472
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KitLoadingController, providedIn: 'root' }); }
|
|
473
|
+
}
|
|
474
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KitLoadingController, decorators: [{
|
|
475
|
+
type: Injectable,
|
|
476
|
+
args: [{
|
|
477
|
+
providedIn: 'root',
|
|
478
|
+
}]
|
|
479
|
+
}] });
|
|
480
|
+
|
|
384
481
|
/**
|
|
385
482
|
* The fleet's canonical "network error → offer to reload" alert, as a stateful controller.
|
|
386
483
|
*
|
|
@@ -933,11 +1030,11 @@ const kitRequestReview = async (options) => {
|
|
|
933
1030
|
const kitRotationImage = async (imageData) => {
|
|
934
1031
|
const imgType = imageData.substring(5, imageData.indexOf(';'));
|
|
935
1032
|
const image = new Image();
|
|
936
|
-
const loaded =
|
|
1033
|
+
const loaded = new Promise((resolve) => {
|
|
937
1034
|
image.onload = () => resolve();
|
|
938
1035
|
});
|
|
939
|
-
|
|
940
|
-
await loaded
|
|
1036
|
+
image.src = imageData;
|
|
1037
|
+
await loaded;
|
|
941
1038
|
const canvas = document.createElement('canvas');
|
|
942
1039
|
const ctx = canvas.getContext('2d');
|
|
943
1040
|
canvas.width = image.height;
|
|
@@ -975,23 +1072,19 @@ const kitDomToPng = async (element, options) => {
|
|
|
975
1072
|
// デバイス毎の問題解決のため、px 調整。
|
|
976
1073
|
// iOS: ないと下が途切れる。Android: あると黒線が入る。
|
|
977
1074
|
const addClient = Capacitor.getPlatform() === 'ios' ? 2 : 0;
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
resolve('');
|
|
993
|
-
})();
|
|
994
|
-
});
|
|
1075
|
+
let dataUrl = '';
|
|
1076
|
+
for (let i = 0; i < 10; i++) {
|
|
1077
|
+
const url = await domtoimage.toPng(element, {
|
|
1078
|
+
width: clientWidth + addClient,
|
|
1079
|
+
height: clientHeight + addClient,
|
|
1080
|
+
scale: options?.scale ?? 3,
|
|
1081
|
+
copyDefaultStyles: false,
|
|
1082
|
+
});
|
|
1083
|
+
if (url) {
|
|
1084
|
+
dataUrl = url;
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
995
1088
|
return options?.rotate ? kitRotationImage(dataUrl) : dataUrl;
|
|
996
1089
|
};
|
|
997
1090
|
/**
|
|
@@ -1561,12 +1654,10 @@ const disableHandler = async (event, work) => {
|
|
|
1561
1654
|
* ```
|
|
1562
1655
|
*/
|
|
1563
1656
|
const kitChangeEventDisabled = (completeEvent, disabled) => {
|
|
1564
|
-
completeEvent
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
return event;
|
|
1569
|
-
});
|
|
1657
|
+
const event = completeEvent();
|
|
1658
|
+
if (event) {
|
|
1659
|
+
event.disabled = disabled;
|
|
1660
|
+
}
|
|
1570
1661
|
};
|
|
1571
1662
|
|
|
1572
1663
|
/**
|
|
@@ -1613,5 +1704,5 @@ const kitCreateDidEnter = (el) => {
|
|
|
1613
1704
|
* Generated bundle index. Do not edit.
|
|
1614
1705
|
*/
|
|
1615
1706
|
|
|
1616
|
-
export { KIT_AUTH_CONFIG, KIT_HTTP_CONFIG, KIT_OVERLAY_CONFIG, KIT_THEME_CONFIG, KitAutofillDirective, KitOverlayController, KitReloadAlertController, KitStorageService, KitThemeController, arrayConcatById, disableHandler, kitAuthInterceptor, kitBuildBrotherPrintSettings, kitChangeEventDisabled, kitCreateDidEnter, kitDomToPng, kitImpact, kitKeyboardInit, kitPresentAuthFailedAlert, kitPresentLanguageActionSheet, kitRequestReview, kitRequireAuthorizedGuard, kitRequireConfirmingGuard, kitRequiredUnauthorizedGuard, kitRotationImage, objectEqual, provideKitAuth, provideKitHttp, provideKitOverlay, provideKitTheme };
|
|
1707
|
+
export { KIT_AUTH_CONFIG, KIT_HTTP_CONFIG, KIT_OVERLAY_CONFIG, KIT_THEME_CONFIG, KitAutofillDirective, KitLoadingController, KitOverlayController, KitReloadAlertController, KitStorageService, KitThemeController, arrayConcatById, disableHandler, kitAuthInterceptor, kitBuildBrotherPrintSettings, kitChangeEventDisabled, kitCreateDidEnter, kitDomToPng, kitImpact, kitKeyboardInit, kitPresentAuthFailedAlert, kitPresentLanguageActionSheet, kitRequestReview, kitRequireAuthorizedGuard, kitRequireConfirmingGuard, kitRequiredUnauthorizedGuard, kitRotationImage, objectEqual, provideKitAuth, provideKitHttp, provideKitOverlay, provideKitTheme };
|
|
1617
1708
|
//# sourceMappingURL=rdlabo-ionic-angular-kit.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rdlabo-ionic-angular-kit.mjs","sources":["../../../projects/kit/src/lib/storage/kit-storage.service.ts","../../../projects/kit/src/lib/overlay/overlay-config.ts","../../../projects/kit/src/lib/utils/haptics.ts","../../../projects/kit/src/lib/overlay/kit-overlay.controller.ts","../../../projects/kit/src/lib/overlay/kit-reload-alert.controller.ts","../../../projects/kit/src/lib/overlay/kit-auth-failed-alert.ts","../../../projects/kit/src/lib/overlay/kit-language-action-sheet.ts","../../../projects/kit/src/lib/directives/autofill.directive.ts","../../../projects/kit/src/lib/keyboard/kit-keyboard.ts","../../../projects/kit/src/lib/theme/theme-config.ts","../../../projects/kit/src/lib/theme/kit-theme.controller.ts","../../../projects/kit/src/lib/review/kit-request-review.ts","../../../projects/kit/src/lib/printer/kit-printer.ts","../../../projects/kit/src/lib/auth/auth-guards.ts","../../../projects/kit/src/lib/http/kit-http.interceptor.ts","../../../projects/kit/src/lib/utils/array.ts","../../../projects/kit/src/lib/utils/object.ts","../../../projects/kit/src/lib/utils/dom.ts","../../../projects/kit/src/lib/utils/ionic-scroll-event.ts","../../../projects/kit/src/lib/utils/ionic-view-enter.ts","../../../projects/kit/src/public-api.ts","../../../projects/kit/src/rdlabo-ionic-angular-kit.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { Storage } from '@ionic/storage-angular';\n\n/**\n * Thin, typed wrapper around `@ionic/storage-angular`.\n *\n * Starts `create()` exactly once and stores the resulting ready Promise. Every operation awaits\n * that Promise before touching the underlying store, so calls made before initialization completes\n * are queued rather than dropped.\n *\n * @remarks\n * A naive wrapper that reads the store synchronously would silently no-op (or throw) when invoked\n * before `create()` resolves, losing early writes. Awaiting the one-time ready Promise on every\n * operation removes that race without forcing callers to coordinate initialization themselves.\n *\n * @example\n * ```ts\n * constructor(private readonly storage: KitStorageService) {}\n *\n * async ngOnInit(): Promise<void> {\n * await this.storage.set('token', 'abc123');\n * const token = await this.storage.get<string>('token');\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class KitStorageService {\n /** One-time `create()` ready Promise; awaited before every operation so early calls are not lost. */\n readonly #ready: Promise<Storage> = inject(Storage).create();\n\n /**\n * Persist a value under the given key.\n *\n * @typeParam T - type of the value being stored\n * @param key - key to store the value under\n * @param value - value to persist; overwrites any existing value for the key\n * @returns a Promise that resolves once the value has been written\n * @example\n * ```ts\n * await storage.set('user', { id: 1, name: 'Ada' });\n * ```\n */\n async set<T>(key: string, value: T): Promise<void> {\n await (await this.#ready).set(key, value);\n }\n\n /**\n * Read the value stored under the given key.\n *\n * @typeParam T - expected type of the stored value\n * @param key - key to read\n * @returns the stored value, or `null` when the key is absent\n * @example\n * ```ts\n * const user = await storage.get<{ id: number }>('user');\n * ```\n */\n async get<T>(key: string): Promise<T | null> {\n return (await (await this.#ready).get(key)) ?? null;\n }\n\n /**\n * Remove the value stored under the given key.\n *\n * @param key - key to remove; a no-op when the key is absent\n * @returns a Promise that resolves once the key has been removed\n */\n async remove(key: string): Promise<void> {\n await (await this.#ready).remove(key);\n }\n\n /**\n * Remove every key/value pair from the store.\n *\n * @returns a Promise that resolves once the store has been emptied\n */\n async clear(): Promise<void> {\n await (await this.#ready).clear();\n }\n\n /**\n * List every key currently present in the store.\n *\n * @returns an array of all stored keys\n */\n async keys(): Promise<string[]> {\n return (await this.#ready).keys();\n }\n}\n","import type { EnvironmentProviders } from '@angular/core';\nimport { InjectionToken, makeEnvironmentProviders } from '@angular/core';\n\n/**\n * User-visible button labels consumed by `KitOverlayController`.\n *\n * @remarks\n * The kit deliberately ships no i18n strings of its own and has no hard dependency on\n * `@angular/localize`. The consuming application always provides these labels: multilingual apps\n * pass `$localize`-resolved strings, single-language apps pass plain literals.\n */\nexport interface KitLabels {\n /** Text for the \"close\" button used by alerts and toasts. */\n readonly close: string;\n /** Text for the \"cancel\" button used by confirmation alerts. */\n readonly cancel: string;\n}\n\n/**\n * Overlay configuration injected via `provideKitOverlay()`.\n *\n * @remarks\n * All fields are required; the consuming application must supply a complete configuration.\n */\nexport interface KitOverlayConfig {\n /** Button labels used across the overlays presented by `KitOverlayController`. */\n readonly labels: KitLabels;\n}\n\n/**\n * Injection token carrying the {@link KitOverlayConfig} for `KitOverlayController`.\n *\n * @remarks\n * Provide it through {@link provideKitOverlay} rather than registering it directly.\n */\nexport const KIT_OVERLAY_CONFIG = new InjectionToken<KitOverlayConfig>('@rdlabo/ionic-angular-kit:overlay');\n\n/**\n * Wire `KitOverlayController` into the application by providing its button labels.\n *\n * @param config - overlay configuration, including the button labels to inject\n * @returns environment providers to add to the application's provider list\n * @example\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideKitOverlay({ labels: { close: $localize`Close`, cancel: $localize`Cancel` } }),\n * ],\n * });\n * ```\n */\nexport const provideKitOverlay = (config: KitOverlayConfig): EnvironmentProviders =>\n makeEnvironmentProviders([{ provide: KIT_OVERLAY_CONFIG, useValue: config }]);\n","import { Capacitor } from '@capacitor/core';\nimport { Haptics, ImpactStyle } from '@capacitor/haptics';\n\n/**\n * Trigger native haptic impact feedback.\n *\n * Delegates to `@capacitor/haptics` `Haptics.impact()` when running on a native platform. On the\n * web (or any non-native platform) it is a no-op and resolves without doing anything.\n *\n * @remarks\n * This haptic side effect was previously bundled implicitly into toast/modal presentation. It has\n * been decoupled into this explicit function so callers opt in to the feedback deliberately, rather\n * than receiving it as a hidden side effect of presenting UI.\n *\n * @param style - The impact intensity to play. Defaults to {@link ImpactStyle.Light}.\n * @returns A promise that resolves once the feedback has been requested (immediately on the web).\n * @example\n * ```ts\n * import { ImpactStyle } from '@capacitor/haptics';\n *\n * // Light tap (default)\n * await kitImpact();\n *\n * // Stronger feedback, e.g. on a confirming action\n * await kitImpact(ImpactStyle.Heavy);\n * ```\n */\nexport const kitImpact = async (style: ImpactStyle = ImpactStyle.Light): Promise<void> => {\n if (Capacitor.isNativePlatform()) {\n await Haptics.impact({ style });\n }\n};\n","import { inject, Injectable } from '@angular/core';\nimport type { ModalOptions, PopoverOptions, ToastOptions } from '@ionic/angular/standalone';\nimport { AlertController, ModalController, PopoverController, ToastController } from '@ionic/angular/standalone';\nimport type { PluginListenerHandle } from '@capacitor/core';\nimport { Capacitor } from '@capacitor/core';\nimport { Keyboard } from '@capacitor/keyboard';\nimport { KIT_OVERLAY_CONFIG } from './overlay-config';\nimport { kitImpact } from '../utils/haptics';\n\n/**\n * Options for {@link KitOverlayController.presentModal}.\n *\n * @remarks\n * Extends Ionic's `ModalOptions` but omits `component` and `componentProps`, which are passed as\n * dedicated arguments instead.\n */\nexport interface KitModalPresentOptions extends Omit<ModalOptions, 'component' | 'componentProps'> {\n /**\n * When `true`, expand the sheet to its maximum breakpoint while the native keyboard is shown.\n *\n * @remarks\n * Only has an effect on native platforms; ignored on the web.\n */\n watchKeyboard?: boolean;\n}\n\n/**\n * Options for {@link KitOverlayController.alertClose}.\n */\nexport interface KitAlertCloseOptions {\n /** Alert header text. */\n header: string;\n /** Alert body message. */\n message: string;\n /** Optional alert sub-header text shown beneath the header. */\n subHeader?: string;\n}\n\n/**\n * Options for {@link KitOverlayController.alertConfirm}.\n *\n * @remarks\n * Extends {@link KitAlertCloseOptions} with the confirm-button text.\n */\nexport interface KitAlertConfirmOptions extends KitAlertCloseOptions {\n /**\n * Text for the OK (confirm) button.\n *\n * @remarks\n * Action-specific, so it is supplied by the caller rather than taken from the shared labels.\n */\n okText: string;\n}\n\n/**\n * Attach a native keyboard listener that grows the modal to its maximum breakpoint when the\n * keyboard appears.\n *\n * @param modal - the presented modal element to resize\n * @returns a listener handle; on non-native platforms a no-op handle whose `remove()` does nothing\n * @internal\n */\nconst watchModalKeyboard = async (modal: HTMLIonModalElement): Promise<PluginListenerHandle> => {\n if (!Capacitor.isNativePlatform()) {\n return { remove: async () => undefined };\n }\n return Keyboard.addListener('keyboardDidShow', () => modal.setCurrentBreakpoint(1));\n};\n\n/**\n * Ergonomic wrapper that consolidates Ionic's overlay controllers (Modal / Toast / Alert).\n *\n * @remarks\n * Folds the repetitive create → present → onDidDismiss sequence into single calls and returns the\n * relevant result directly. It holds no application-specific policy such as navigation; compose\n * those concerns on the consuming side.\n *\n * @example\n * ```ts\n * constructor(private readonly overlay: KitOverlayController) {}\n *\n * async edit(): Promise<void> {\n * const result = await this.overlay.presentModal<EditResult>(EditPage, { id: 1 });\n * if (result) {\n * await this.overlay.presentToast({ message: 'Saved' });\n * }\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class KitOverlayController {\n readonly #modalCtrl = inject(ModalController);\n readonly #popoverCtrl = inject(PopoverController);\n readonly #toastCtrl = inject(ToastController);\n readonly #alertCtrl = inject(AlertController);\n readonly #labels = inject(KIT_OVERLAY_CONFIG).labels;\n\n /**\n * Guards against stacking alerts: while one {@link alertClose} / {@link alertConfirm} is on screen,\n * a concurrent call resolves immediately (close: no-op, confirm: `false`) instead of presenting a\n * second alert on top of the first. Shared across both methods so a confirm cannot stack over a close.\n */\n #alertPresenting = false;\n\n /**\n * Present a modal and resolve with the data passed to its dismissal.\n *\n * @typeParam O - type of the data returned when the modal is dismissed\n * @param component - the component to render inside the modal\n * @param componentProps - props to pass to the modal component\n * @param options - additional modal options, including {@link KitModalPresentOptions.watchKeyboard}\n * @returns the dismiss data, or `undefined` when the modal is dismissed without data\n * @remarks\n * Presenting a modal triggers light native haptic feedback as an intentional kit UX choice,\n * consistent with {@link presentPopover} and {@link presentToast}.\n * @example\n * ```ts\n * const data = await overlay.presentModal<{ saved: boolean }>(EditPage, { id: 1 }, { watchKeyboard: true });\n * ```\n */\n async presentModal<O = unknown>(\n component: ModalOptions['component'],\n componentProps?: ModalOptions['componentProps'],\n options: KitModalPresentOptions = {},\n ): Promise<O | undefined> {\n void kitImpact();\n const { watchKeyboard, ...modalOptions } = options;\n const modal = await this.#modalCtrl.create({ component, componentProps, ...modalOptions });\n await modal.present();\n const handle = watchKeyboard ? await watchModalKeyboard(modal) : null;\n const { data } = await modal.onDidDismiss<O>();\n await handle?.remove();\n return data;\n }\n\n /**\n * Present a popover and resolve with the data passed to its dismissal.\n *\n * @typeParam O - type of the data returned when the popover is dismissed\n * @param component - the component to render inside the popover\n * @param componentProps - props to pass to the popover component\n * @param options - additional popover options (for example `event` to anchor it, or `cssClass`)\n * @returns the dismiss data, or `undefined` when the popover is dismissed without data\n * @remarks\n * Presenting a popover triggers light native haptic feedback as an intentional kit UX choice,\n * consistent with {@link presentModal} and {@link presentToast}.\n * @example\n * ```ts\n * const choice = await overlay.presentPopover<MenuChoice>(MenuPopover, { items }, { event });\n * ```\n */\n async presentPopover<O = unknown>(\n component: PopoverOptions['component'],\n componentProps?: PopoverOptions['componentProps'],\n options: Omit<PopoverOptions, 'component' | 'componentProps'> = {},\n ): Promise<O | undefined> {\n void kitImpact();\n const popover = await this.#popoverCtrl.create({ component, componentProps, ...options });\n await popover.present();\n const { data } = await popover.onDidDismiss<O>();\n return data;\n }\n\n /**\n * Present a toast using kit defaults that the caller may override.\n *\n * @remarks\n * Defaults to a bottom position, a 2000ms duration, a vertical swipe gesture, and a close button\n * from the configured labels; any of these can be overridden via `options`. Presenting a toast\n * also triggers light native haptic feedback as an intentional kit UX choice.\n *\n * Bottom is the fleet-wide default (top left the toast fighting the tab bar and the keyboard).\n * For a bottom toast with no explicit `positionAnchor`, if a visible `ion-tab-bar` is present the\n * toast is automatically anchored above it (Ionic places a bottom toast above its `positionAnchor`),\n * so the toast never sits behind the tabs. Avoiding the on-screen keyboard is handled by the native\n * keyboard resize — the anchored/bottom toast rides the shrinking viewport above the keyboard;\n * Ionic itself has no toast keyboard-avoidance option. An app can override either via `options`.\n *\n * @param options - Ionic toast options that override the kit defaults\n * @returns the presented toast element\n * @example\n * ```ts\n * await overlay.presentToast({ message: 'Copied to clipboard' });\n * ```\n */\n async presentToast(options: ToastOptions): Promise<HTMLIonToastElement> {\n void kitImpact();\n const merged: ToastOptions = {\n position: 'bottom',\n duration: 2000,\n buttons: [this.#labels.close],\n swipeGesture: 'vertical',\n ...options,\n };\n // Anchor a bottom toast above the tab bar when one is visibly present and the caller did not\n // set an explicit anchor, so the toast clears the tabs (and rides the keyboard-resized viewport).\n if (merged.position === 'bottom' && merged.positionAnchor === undefined) {\n const tabBar = document.querySelector('ion-tab-bar');\n if (tabBar && tabBar.getBoundingClientRect().height > 0) {\n merged.positionAnchor = tabBar as HTMLElement;\n }\n }\n const toast = await this.#toastCtrl.create(merged);\n await toast.present();\n return toast;\n }\n\n /**\n * Present a notification alert with a single \"close\" button and wait for it to be dismissed.\n *\n * @param options - alert content (header, message, optional sub-header)\n * @returns a Promise that resolves once the alert has been dismissed\n * @remarks\n * No-ops when another alert is already presenting (see {@link alertClose} / {@link alertConfirm}\n * stacking guard).\n * @example\n * ```ts\n * await overlay.alertClose({ header: 'Done', message: 'Your changes were saved.' });\n * ```\n */\n async alertClose(options: KitAlertCloseOptions): Promise<void> {\n if (this.#alertPresenting) {\n return;\n }\n this.#alertPresenting = true;\n try {\n const alert = await this.#alertCtrl.create({\n header: options.header,\n subHeader: options.subHeader,\n message: options.message,\n buttons: [this.#labels.close],\n });\n await alert.present();\n await alert.onWillDismiss();\n } finally {\n this.#alertPresenting = false;\n }\n }\n\n /**\n * Present a confirmation alert with cancel and OK buttons.\n *\n * @param options - alert content plus the OK button text via {@link KitAlertConfirmOptions.okText}\n * @returns `true` when the user presses OK, `false` otherwise (cancel or backdrop dismissal)\n * @example\n * ```ts\n * const ok = await overlay.alertConfirm({\n * header: 'Delete item?',\n * message: 'This cannot be undone.',\n * okText: 'Delete',\n * });\n * if (ok) {\n * await remove();\n * }\n * ```\n * @remarks\n * Returns `false` immediately when another alert is already presenting (see {@link alertClose} /\n * {@link alertConfirm} stacking guard).\n */\n async alertConfirm(options: KitAlertConfirmOptions): Promise<boolean> {\n if (this.#alertPresenting) {\n return false;\n }\n this.#alertPresenting = true;\n try {\n const alert = await this.#alertCtrl.create({\n header: options.header,\n subHeader: options.subHeader,\n message: options.message,\n buttons: [\n { text: this.#labels.cancel, role: 'cancel' },\n { text: options.okText, role: 'confirm' },\n ],\n });\n await alert.present();\n const { role } = await alert.onWillDismiss();\n return role === 'confirm';\n } finally {\n this.#alertPresenting = false;\n }\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { AlertController } from '@ionic/angular/standalone';\nimport { KIT_OVERLAY_CONFIG } from './overlay-config';\n\n/**\n * Content for {@link KitReloadAlertController.present}.\n */\nexport interface KitReloadAlertOptions {\n /** Alert header text. */\n header: string;\n /** Alert body message. */\n message: string;\n /**\n * Text for the reload (confirm) button, e.g. \"リフレッシュ\".\n *\n * @remarks\n * Action-specific, so it is supplied by the caller rather than taken from the shared labels.\n * The cancel button uses the configured {@link KitLabels.cancel}.\n */\n okText: string;\n}\n\n/**\n * The fleet's canonical \"network error → offer to reload\" alert, as a stateful controller.\n *\n * @remarks\n * Consolidates the good-UX variant that had drifted across the fleet into one behavior:\n *\n * - **De-dup** — never stacks; a second {@link present} while an alert is already shown is a no-op.\n * - **Backdrop lock** — `backdropDismiss: false`, so a critical network error can't be dismissed by\n * an accidental backdrop tap; the user consciously chooses cancel or reload.\n * - **Auto-dismiss on reconnect** — the presented alert is tracked, so {@link dismiss} (called from a\n * later successful response) clears a now-stale error alert instead of leaving it on screen.\n * - **Reload on confirm** — the confirm button calls `location.reload()`.\n *\n * All user-facing text is supplied by the caller so the kit stays free of any hardcoded i18n; the\n * cancel button reuses {@link KitOverlayConfig.labels}. Because it performs navigation\n * (`location.reload()`) and holds state, it is a dedicated controller rather than part of\n * {@link KitOverlayController}, which stays free of navigation policy.\n *\n * @example\n * ```ts\n * // In an HTTP interceptor:\n * const reload = inject(KitReloadAlertController);\n * // ...on a network-class error while connected:\n * await reload.present({ header: 'ネットワークエラー', message: `…(${status})`, okText: 'リフレッシュ' });\n * // ...on any later successful response:\n * await reload.dismiss();\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class KitReloadAlertController {\n readonly #alertCtrl = inject(AlertController);\n readonly #labels = inject(KIT_OVERLAY_CONFIG).labels;\n #alert: HTMLIonAlertElement | null = null;\n\n /**\n * Present the reload alert, unless one is already on screen.\n *\n * @param options - alert content plus the reload-button text\n * @returns a Promise that resolves once the alert has been presented (or immediately if suppressed)\n */\n async present(options: KitReloadAlertOptions): Promise<void> {\n // この controller 経由でも直書き ion-alert でも、多重表示しない。\n if (this.#alert || document.querySelector('ion-alert')) {\n return;\n }\n const alert = await this.#alertCtrl.create({\n header: options.header,\n message: options.message,\n backdropDismiss: false,\n buttons: [\n { text: this.#labels.cancel, role: 'cancel' },\n {\n text: options.okText,\n handler: () => {\n location.reload();\n },\n },\n ],\n });\n this.#alert = alert;\n void alert.onDidDismiss().then(() => {\n // 別の present で置き換わっていない限り、追跡を解除する。\n if (this.#alert === alert) {\n this.#alert = null;\n }\n });\n await alert.present();\n }\n\n /**\n * Dismiss the tracked reload alert if one is showing.\n *\n * @remarks\n * Typically called from a later successful response so a stale \"network error\" alert clears once\n * connectivity is restored. A no-op when nothing is showing.\n *\n * @returns a Promise that resolves once the alert has been dismissed (or immediately if none)\n */\n async dismiss(): Promise<void> {\n const alert = this.#alert;\n this.#alert = null;\n await alert?.dismiss();\n }\n}\n","import type { AlertController } from '@ionic/angular/standalone';\n\n/**\n * Content for {@link kitPresentAuthFailedAlert}.\n */\nexport interface KitAuthFailedAlertOptions {\n /** Alert header, e.g. \"ログインできませんでした\". */\n header: string;\n /** Optional sub-header; typically the short server error code/name. */\n subHeader?: string;\n /** Alert body message; typically the server-provided detail. */\n message: string;\n /** Text for the single close button, e.g. \"閉じる\". */\n closeText: string;\n}\n\n/**\n * Present the fleet's canonical \"sign-in / token exchange failed\" alert.\n *\n * @remarks\n * Folds together the alert every token-exchange app duplicated verbatim when a startup re-login\n * fails: an informative alert (header + optional server error as sub-header + detail message) with a\n * single close button that reloads the app (`location.reload()`) so the user restarts cleanly. The\n * caller is still responsible for signing the user out around this call.\n *\n * All user-facing text is supplied by the caller so the kit stays free of any hardcoded i18n. Kept\n * as a standalone helper (taking the `AlertController`) rather than a method on\n * {@link KitOverlayController}, which holds no navigation policy such as `location.reload()`.\n *\n * @param alertCtrl - Ionic's `AlertController`\n * @param options - alert content plus the close-button text\n * @returns a Promise that resolves once the alert has been presented\n * @example\n * ```ts\n * onAuthorized: async () => {\n * const logged = await auth.tokenLogin().catch(async (e) => {\n * await kitPresentAuthFailedAlert(alertCtrl, {\n * header: 'ログインできませんでした',\n * subHeader: e.error.error,\n * message: e.error.detail,\n * closeText: '閉じる',\n * });\n * await auth.signOut();\n * return undefined;\n * });\n * // ...\n * };\n * ```\n */\nexport const kitPresentAuthFailedAlert = async (\n alertCtrl: AlertController,\n options: KitAuthFailedAlertOptions,\n): Promise<void> => {\n const alert = await alertCtrl.create({\n header: options.header,\n subHeader: options.subHeader,\n message: options.message,\n buttons: [\n {\n text: options.closeText,\n role: 'cancel',\n handler: () => {\n location.reload();\n },\n },\n ],\n });\n await alert.present();\n};\n","import { ActionSheetController } from '@ionic/angular/standalone';\n\n/** One selectable language in {@link kitPresentLanguageActionSheet}. */\nexport interface KitLanguageOption {\n /** Button label shown to the user (e.g. `English`, `日本語`). */\n readonly text: string;\n /** Locale identifier returned when this option is chosen (e.g. `en-US`, `ja`). */\n readonly data: string;\n}\n\n/**\n * Options for {@link kitPresentLanguageActionSheet}.\n *\n * @remarks\n * The kit ships no strings or URLs of its own: labels, the locale list, and the redirect-URL mapping\n * are all supplied by the caller, so a multilingual app passes `$localize`-resolved text and its own\n * per-locale build paths.\n */\nexport interface KitLanguageActionSheetOptions {\n /** Action-sheet header text. */\n readonly header: string;\n /** Selectable languages, in display order. */\n readonly locales: readonly KitLanguageOption[];\n /** Text for the cancel button. */\n readonly cancelText: string;\n /** The currently active locale; selecting the same value is a no-op. Normalize before passing. */\n readonly currentLocale: string;\n /** The current in-app path (e.g. `router.url`), stashed so the app can restore it after the reload. */\n readonly currentPath: string;\n /** `sessionStorage` key under which {@link currentPath} is stored. */\n readonly pathnameStorageKey: string;\n /** Maps a chosen locale to the URL to navigate to (the app's per-locale build entry point). */\n readonly buildRedirectUrl: (locale: string) => string;\n /** Gate for the redirect — pass `false` (e.g. outside production) to present without navigating. */\n readonly enabled: boolean;\n}\n\n/**\n * Present a language picker and, on a new selection, reload the app at that locale's entry point.\n *\n * @remarks\n * A plain function (the `ActionSheetController` is passed in, so nothing is injected) that unifies the\n * language-switch flow duplicated across apps. On a changed selection while {@link enabled} it stashes\n * the current path in `sessionStorage` (so the app can return the user to where they were), records the\n * chosen locale in `localStorage` under `'locale'`, and calls `window.location.replace()` with the\n * app-provided URL. Because it performs navigation, it is a standalone helper rather than part of a\n * controller (mirroring `kitPresentReloadAlert` / `kitPresentAuthFailedAlert`). Centralizing it means a\n * future improvement to the switch flow lands in every app at once.\n *\n * @param actionSheetCtrl - the Ionic `ActionSheetController`\n * @param options - labels, locale list, and redirect configuration; see {@link KitLanguageActionSheetOptions}\n * @returns a Promise that resolves once presented (and, on a new selection, after the reload is triggered)\n * @example\n * ```ts\n * await kitPresentLanguageActionSheet(inject(ActionSheetController), {\n * header: $localize`言語設定`,\n * locales: [{ text: 'English', data: 'en-US' }, { text: '日本語', data: 'ja' }],\n * cancelText: $localize`キャンセル`,\n * currentLocale: normalizedLocale,\n * currentPath: this.#router.url,\n * pathnameStorageKey: StorageKeyEnum.pathnameBeforeRedirect,\n * buildRedirectUrl: (locale) => location.origin + (localePath[locale.toLowerCase()] ?? '/index.html'),\n * enabled: environment.production,\n * });\n * ```\n */\nexport const kitPresentLanguageActionSheet = async (\n actionSheetCtrl: ActionSheetController,\n options: KitLanguageActionSheetOptions,\n): Promise<void> => {\n const actionSheet = await actionSheetCtrl.create({\n header: options.header,\n buttons: [\n ...options.locales.map((locale) => ({ text: locale.text, data: locale.data })),\n { text: options.cancelText, role: 'cancel' },\n ],\n });\n await actionSheet.present();\n\n const { data } = await actionSheet.onDidDismiss();\n if (options.enabled && data && data !== options.currentLocale) {\n sessionStorage.setItem(options.pathnameStorageKey, options.currentPath);\n localStorage.setItem('locale', data);\n window.location.replace(options.buildRedirectUrl(data));\n }\n};\n","import type { OnInit } from '@angular/core';\nimport { Directive, ElementRef, inject } from '@angular/core';\nimport { Capacitor } from '@capacitor/core';\n\n/**\n * Work around iOS `ion-input` autofill values not propagating to the Angular form model.\n *\n * On iOS, when the browser autofills an `ion-input` (for example a saved password), the value\n * is written to the underlying native `<input>` element but the corresponding `change` event is\n * not forwarded to the host `ion-input`, so the Angular form control (and `ngModel`) never sees\n * the autofilled value. This directive listens for the first `change` event on the inner input\n * element and mirrors its value back onto the host element, restoring two-way binding.\n *\n * Apply it to any `ion-input` that participates in a form and may be autofilled by attaching the\n * `rdlaboAutofill` attribute.\n *\n * @remarks\n * The directive is a no-op on every platform other than iOS, where the value already propagates\n * correctly. A short timeout is used because `ion-input` creates its inner `<input>` element\n * asynchronously after the host element is initialized.\n *\n * @example\n * ```html\n * <ion-input rdlaboAutofill type=\"password\" [(ngModel)]=\"password\"></ion-input>\n * ```\n */\n@Directive({\n selector: '[rdlaboAutofill]',\n standalone: true,\n})\nexport class KitAutofillDirective implements OnInit {\n readonly #el = inject(ElementRef);\n\n constructor() {}\n\n /**\n * Register the iOS autofill workaround once the directive is initialized.\n *\n * Returns immediately on non-iOS platforms. On iOS, after a short delay it attaches a one-shot,\n * passive `change` listener to the inner `<input>` element that `ion-input` renders, copying the\n * autofilled value back onto the host element so the Angular form model stays in sync. Any error\n * while locating the inner input (for example if the element is not yet present) is swallowed.\n *\n * @returns Nothing.\n */\n ngOnInit(): void {\n if (Capacitor.getPlatform() !== 'ios') {\n return;\n }\n setTimeout(() => {\n try {\n this.#el.nativeElement.children[0].addEventListener(\n 'change',\n (e: Event) => {\n this.#el.nativeElement.value = (e.target as HTMLInputElement).value;\n },\n {\n capture: false,\n once: true,\n passive: true,\n },\n );\n } catch {\n /* empty */\n }\n }, 100); // Need some time for the ion-input to create the input element\n }\n}\n","import type { ElementRef } from '@angular/core';\nimport { Capacitor } from '@capacitor/core';\nimport type { PluginListenerHandle } from '@capacitor/core';\nimport { Keyboard } from '@capacitor/keyboard';\n\n/**\n * How {@link kitKeyboardInit} adjusts the target element when the native keyboard appears.\n *\n * - `transform` — CSS `translateY(-keyboardHeight + safeAreaBottom)` for a smooth iOS animation\n * (typical for an `ion-footer`).\n * - `offset` — set the `--offset-bottom` custom property to the negative keyboard height.\n * - `keyboard-offset` — set the `--padding-bottom` custom property to the keyboard height.\n */\nexport type KitKeyboardAdjust = 'transform' | 'offset' | 'keyboard-offset';\n\nconst keyboardWillShow = (elementRef: ElementRef, type: KitKeyboardAdjust): Promise<PluginListenerHandle> =>\n Keyboard.addListener('keyboardWillShow', (info) => {\n if (Capacitor.getPlatform() === 'android') {\n if (elementRef.nativeElement.tagName === 'ION-FOOTER' && type === 'transform') {\n // https://github.com/ionic-team/ionic-framework/blob/main/core/src/components/footer/footer.tsx\n elementRef.nativeElement.classList.remove('footer-toolbar-padding');\n }\n return;\n }\n\n elementRef.nativeElement.classList.add('show-keyboard');\n // SSR-safe: this callback only runs on a native keyboard event, so the global `document` /\n // `window` are never touched on the server (kitKeyboardInit returns early when not native).\n const bodyStyleDeclaration = window.getComputedStyle(document.querySelector('body') as Element);\n const safeArea = parseInt(bodyStyleDeclaration.getPropertyValue('--ion-safe-area-bottom'), 10);\n\n if (type === 'transform') {\n elementRef.nativeElement.style.transition = 'transform 420ms';\n elementRef.nativeElement.style.willChange = 'transform';\n requestAnimationFrame(\n () => (elementRef.nativeElement.style.transform = `translateY(${info.keyboardHeight * -1 + safeArea}px)`),\n );\n } else if (type === 'offset') {\n requestAnimationFrame(() => {\n const keyboardOffset = elementRef.nativeElement.style.getPropertyValue('--keyboard-offset');\n if (!keyboardOffset || parseInt(keyboardOffset, 10) === 0) {\n elementRef.nativeElement.style.setProperty('--offset-bottom', `${info.keyboardHeight * -1}px`);\n }\n });\n } else {\n requestAnimationFrame(() => {\n const keyboardOffset = elementRef.nativeElement.style.getPropertyValue('--keyboard-offset');\n if (!keyboardOffset || parseInt(keyboardOffset, 10) === 0) {\n elementRef.nativeElement.style.setProperty('--padding-bottom', `${info.keyboardHeight}px`);\n }\n });\n }\n });\n\nconst keyboardWillHide = (elementRef: ElementRef, type: KitKeyboardAdjust): Promise<PluginListenerHandle> =>\n Keyboard.addListener('keyboardWillHide', () => {\n if (Capacitor.getPlatform() === 'android') {\n if (elementRef.nativeElement.tagName === 'ION-FOOTER' && type === 'transform') {\n elementRef.nativeElement.classList.add('footer-toolbar-padding');\n }\n return;\n }\n\n elementRef.nativeElement.classList.remove('show-keyboard');\n\n if (type === 'transform') {\n elementRef.nativeElement.style.transition = 'transform 0ms';\n elementRef.nativeElement.style.transform = `translateY(0px)`;\n elementRef.nativeElement.style.willChange = 'transform';\n } else if (type === 'offset') {\n elementRef.nativeElement.style.setProperty('--offset-bottom', '0px');\n } else {\n elementRef.nativeElement.style.setProperty('--padding-bottom', '0px');\n }\n });\n\nconst keyboardDidShow = (elementRef: ElementRef): Promise<PluginListenerHandle> =>\n Keyboard.addListener('keyboardDidShow', () => {\n elementRef.nativeElement.style.willChange = 'auto';\n });\n\nconst keyboardDidHide = (elementRef: ElementRef): Promise<PluginListenerHandle> =>\n Keyboard.addListener('keyboardDidHide', () => {\n elementRef.nativeElement.style.willChange = 'auto';\n });\n\n/**\n * Register native keyboard listeners that reposition an element when the keyboard shows/hides.\n *\n * @remarks\n * A plain function — no DI needed (it reads the platform from `Capacitor` and uses the global\n * `document`), so a component calls it directly instead of injecting a controller. SSR-safe: the\n * global `document` / `window` are only read inside native keyboard-event callbacks, which never\n * fire on the server — the `Capacitor.isNativePlatform()` guard returns `[]` first, and nothing is\n * touched at module load. A no-op on non-native platforms (returns `[]`). On native it handles the\n * iOS/Android differences (Android\n * only toggles the `footer-toolbar-padding` class on an `ion-footer` for the `transform` mode,\n * working around an Ionic footer bug). The caller owns the returned handles and must `remove()`\n * them when the view is destroyed.\n *\n * @param elementRef - The element to reposition (e.g. an `ion-footer`).\n * @param type - The adjustment strategy; see {@link KitKeyboardAdjust}.\n * @returns The registered listener handles (empty on non-native platforms).\n * @example\n * ```ts\n * export class ComposePage {\n * readonly #footer = viewChild.required<ElementRef>('footer');\n * #handles: PluginListenerHandle[] = [];\n *\n * async ngAfterViewInit() {\n * this.#handles = await kitKeyboardInit(this.#footer(), 'transform');\n * }\n * ngOnDestroy() {\n * this.#handles.forEach((h) => h.remove());\n * }\n * }\n * ```\n */\nexport const kitKeyboardInit = async (elementRef: ElementRef, type: KitKeyboardAdjust): Promise<PluginListenerHandle[]> => {\n if (!Capacitor.isNativePlatform()) {\n return [];\n }\n return [\n await keyboardWillShow(elementRef, type),\n await keyboardWillHide(elementRef, type),\n await keyboardDidShow(elementRef),\n await keyboardDidHide(elementRef),\n ];\n};\n","import type { EnvironmentProviders } from '@angular/core';\nimport { InjectionToken, makeEnvironmentProviders } from '@angular/core';\n\n/**\n * Theme configuration injected via `provideKitTheme()` and consumed by `KitThemeController`.\n *\n * @remarks\n * All fields are required; the consuming application supplies a complete configuration so every app\n * in the fleet has the same shape. The class lists absorb the per-app CSS drift: the kit toggles\n * `darkClasses` on when dark and `lightClasses` on when light, so an app that only uses Ionic's\n * palette passes `darkClasses: ['ion-palette-dark'], lightClasses: []`, while an app with an extra\n * design-system palette adds its own classes (e.g. `darkClasses: ['ion-palette-dark', 'a2ui-dark']`,\n * `lightClasses: ['a2ui-light']`).\n */\nexport interface KitThemeConfig {\n /** Key under which the chosen theme (`'light'` | `'dark'`) is persisted via `KitStorageService`. */\n readonly storageKey: string;\n /** Classes toggled **on** the document element when the dark theme is active. */\n readonly darkClasses: readonly string[];\n /** Classes toggled **on** the document element when the light theme is active. */\n readonly lightClasses: readonly string[];\n}\n\n/**\n * Injection token carrying the {@link KitThemeConfig} for `KitThemeController`.\n *\n * @remarks\n * Provide it through {@link provideKitTheme} rather than registering it directly.\n */\nexport const KIT_THEME_CONFIG = new InjectionToken<KitThemeConfig>('@rdlabo/ionic-angular-kit:theme');\n\n/**\n * Wire `KitThemeController` into the application.\n *\n * @param config - theme configuration: the storage key and the light/dark class lists\n * @returns environment providers to add to the application's provider list\n * @example\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideKitTheme({\n * storageKey: StorageKeyEnum.theme,\n * darkClasses: ['ion-palette-dark', 'a2ui-dark'],\n * lightClasses: ['a2ui-light'],\n * }),\n * ],\n * });\n * ```\n */\nexport const provideKitTheme = (config: KitThemeConfig): EnvironmentProviders =>\n makeEnvironmentProviders([{ provide: KIT_THEME_CONFIG, useValue: config }]);\n","import { DOCUMENT, inject, Injectable } from '@angular/core';\nimport { Capacitor } from '@capacitor/core';\nimport { StatusBar, Style } from '@capacitor/status-bar';\nimport { BehaviorSubject } from 'rxjs';\n\nimport { KitStorageService } from '../storage/kit-storage.service';\nimport { KIT_THEME_CONFIG } from './theme-config';\n\n/** The active theme mode. */\nexport type KitThemeMode = 'light' | 'dark';\n\n/**\n * Light/dark theme controller: persists the user's choice, follows the OS setting until the user\n * overrides it, toggles the configured palette classes, and syncs the native Android status bar.\n *\n * @remarks\n * Consolidates the theme logic that had drifted across the fleet into one behavior. Notably it fixes\n * a latent leak in one variant where the system-theme listener stayed registered after a manual\n * toggle: {@link changeTheme} always detaches the listener via {@link removeEventListener} before\n * applying the forced theme, so a later OS change can no longer silently flip an app the user pinned.\n *\n * - **Persistence** — the chosen mode is stored via {@link KitStorageService} under the configured key.\n * - **Follow OS until overridden** — on boot with nothing stored, it tracks\n * `prefers-color-scheme` (idempotent registration); once the user calls {@link changeTheme} it stops\n * following and honors the explicit choice.\n * - **Class toggling** — toggles {@link KitThemeConfig.darkClasses} on when dark and\n * {@link KitThemeConfig.lightClasses} on when light, absorbing per-app CSS differences via config.\n * - **Native status bar** — on Android native only, mirrors the Ionic behavior of setting the status\n * bar style to match (iOS derives it from the web content, so it is intentionally left untouched).\n *\n * Subscribe to {@link themeSubject} to reflect the current mode in the UI (e.g. a settings toggle).\n *\n * @example\n * ```ts\n * // On boot (app.component):\n * inject(KitThemeController).setDefaultThemeMode();\n *\n * // From a settings toggle:\n * const theme = inject(KitThemeController);\n * theme.themeSubject.subscribe((mode) => this.isDark.set(mode === 'dark'));\n * theme.changeTheme(true);\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class KitThemeController {\n readonly #storage = inject(KitStorageService);\n readonly #document = inject(DOCUMENT);\n readonly #config = inject(KIT_THEME_CONFIG);\n\n /**\n * Emits the active theme, seeded with `'light'`.\n *\n * @remarks\n * A `BehaviorSubject`, so a late subscriber immediately receives the current mode; it emits again\n * on every {@link setDefaultThemeMode} / {@link changeTheme} and on OS theme changes while following.\n */\n readonly themeSubject = new BehaviorSubject<KitThemeMode>('light');\n\n #prefersDark?: MediaQueryList;\n #onSystemThemeChange?: (e: MediaQueryListEvent) => void;\n\n /**\n * Apply the persisted theme, or start following the OS setting when nothing is stored yet.\n *\n * @remarks\n * Call once on boot (e.g. from `app.component`).\n *\n * @returns a Promise that resolves once the initial theme has been applied\n */\n async setDefaultThemeMode(): Promise<void> {\n const stored = await this.#storage.get<KitThemeMode>(this.#config.storageKey);\n if (stored) {\n // 保存済みの選択を強制し、OS 追従は解除する。\n this.#unwatchSystemTheme();\n return this.#applyTheme(stored === 'dark');\n }\n\n // 未保存 → OS の設定に追従する。\n this.#watchSystemTheme();\n }\n\n /**\n * Force a theme, persist it, and stop following the OS setting.\n *\n * @param isDark - `true` for the dark theme, `false` for light\n * @returns a Promise that resolves once the theme has been persisted and applied\n */\n async changeTheme(isDark: boolean): Promise<void> {\n this.#unwatchSystemTheme();\n await this.#storage.set(this.#config.storageKey, isDark ? 'dark' : 'light');\n await this.#applyTheme(isDark);\n }\n\n #watchSystemTheme(): void {\n if (this.#prefersDark) {\n // 既に監視中なら二重登録しない(冪等)。\n return;\n }\n this.#prefersDark = window.matchMedia('(prefers-color-scheme: dark)');\n this.#onSystemThemeChange = (e) => void this.#applyTheme(e.matches);\n this.#prefersDark.addEventListener('change', this.#onSystemThemeChange, { passive: true });\n void this.#applyTheme(this.#prefersDark.matches);\n }\n\n #unwatchSystemTheme(): void {\n if (this.#prefersDark && this.#onSystemThemeChange) {\n this.#prefersDark.removeEventListener('change', this.#onSystemThemeChange);\n }\n this.#prefersDark = undefined;\n this.#onSystemThemeChange = undefined;\n }\n\n async #applyTheme(isDark: boolean): Promise<void> {\n if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'android') {\n await StatusBar.setStyle({ style: isDark ? Style.Dark : Style.Light });\n }\n const root = this.#document.documentElement;\n for (const cls of this.#config.darkClasses) {\n root.classList.toggle(cls, isDark);\n }\n for (const cls of this.#config.lightClasses) {\n root.classList.toggle(cls, !isDark);\n }\n this.themeSubject.next(isDark ? 'dark' : 'light');\n }\n}\n","import { Capacitor } from '@capacitor/core';\nimport { Preferences } from '@capacitor/preferences';\nimport { InAppReview } from '@capacitor-community/in-app-review';\n\n/**\n * Options for {@link kitRequestReview}.\n */\nexport interface KitRequestReviewOptions {\n /**\n * Key under which the timestamp of the last review request is stored (via `@capacitor/preferences`).\n *\n * @remarks\n * Supplied by the caller so the kit ships no storage keys of its own; each app passes its own enum\n * value.\n */\n readonly storageKey: string;\n /**\n * Minimum number of months between review prompts.\n *\n * @remarks\n * A prompt is only shown when this much time has elapsed since the last one (or when there is no\n * record yet), so the OS review dialog is never nagged repeatedly.\n */\n readonly throttleMonths: number;\n}\n\n/**\n * Request the native in-app review dialog, throttled so the user is prompted at most once per window.\n *\n * @remarks\n * A plain function — no DI needed (`@capacitor/preferences`, `@capacitor-community/in-app-review` and\n * `Capacitor` are all static), so the caller invokes it directly and passes its own config rather\n * than injecting a controller. A no-op on non-native platforms. When enough time has elapsed since\n * the last prompt (per {@link KitRequestReviewOptions.throttleMonths}, tracked under\n * {@link KitRequestReviewOptions.storageKey}), it briefly waits for the app to settle, calls\n * `InAppReview.requestReview()`, and records the new timestamp. The wait/throttle/record sequence\n * was previously copy-pasted verbatim across the fleet; centralizing it means a single place to tune\n * the prompt cadence.\n *\n * @param options - the storage key and throttle window; see {@link KitRequestReviewOptions}\n * @returns a Promise that resolves once the request has been made (or immediately if throttled / on web)\n * @example\n * ```ts\n * await kitRequestReview({ storageKey: StorageEnum.lastRequestRate, throttleMonths: 3 });\n * ```\n */\nexport const kitRequestReview = async (options: KitRequestReviewOptions): Promise<void> => {\n if (!Capacitor.isNativePlatform()) {\n return;\n }\n\n await new Promise<void>((resolve) => setTimeout(() => resolve(), 1000));\n const threshold = new Date();\n threshold.setMonth(threshold.getMonth() - options.throttleMonths);\n\n const { value } = await Preferences.get({ key: options.storageKey });\n if (!value || new Date(Number(value)).getTime() < threshold.getTime()) {\n await InAppReview.requestReview();\n await Preferences.set({ key: options.storageKey, value: new Date().getTime().toString() });\n }\n};\n","import { Capacitor } from '@capacitor/core';\nimport type { BRLMPrinterLabelName, BRLMPrinterModelName, BRLMPrintOptions } from '@rdlabo/capacitor-brotherprint';\nimport {\n BRLMPrinterCustomPaperType,\n BRLMPrinterCustomPaperUnit,\n BRLMPrinterHalftone,\n BRLMPrinterHorizontalAlignment,\n BRLMPrinterImageRotation,\n BRLMPrinterPrintQuality,\n BRLMPrinterScaleMode,\n BRLMPrinterVerticalAlignment,\n} from '@rdlabo/capacitor-brotherprint';\nimport domtoimage from 'dom-to-image-more';\n\n/**\n * Rotate a base64 image 90°, returning a new base64 data URL of the same MIME type.\n *\n * @remarks\n * Pure DOM/canvas work — no DI. Used before sending a label to the printer when the artwork must be\n * turned to match the tape orientation. Extracted verbatim from the fleet's printer services so the\n * canvas handling lives in one place.\n *\n * @param imageData - a base64 data URL (e.g. `data:image/png;base64,...`)\n * @returns a Promise resolving to the rotated image as a base64 data URL\n */\nexport const kitRotationImage = async (imageData: string): Promise<string> => {\n const imgType = imageData.substring(5, imageData.indexOf(';'));\n\n const image = new Image();\n const loaded = () =>\n new Promise<void>((resolve) => {\n image.onload = () => resolve();\n });\n setTimeout(() => (image.src = imageData));\n await loaded();\n\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n\n canvas.width = image.height;\n canvas.height = image.width;\n\n ctx!.rotate((90 * Math.PI) / 180);\n ctx!.translate(0, -image.height);\n ctx!.drawImage(image, 0, 0, image.width, image.height);\n\n return canvas.toDataURL(imgType);\n};\n\n/** Options for {@link kitDomToPng}. */\nexport interface KitDomToPngOptions {\n /** When `true`, the rendered PNG is rotated 90° via {@link kitRotationImage}. Defaults to `false`. */\n readonly rotate?: boolean;\n /** Rendering scale passed to `dom-to-image-more`. Defaults to `3` (the fleet's print resolution). */\n readonly scale?: number;\n}\n\n/**\n * Render a DOM element to a base64 PNG for label printing, with the fleet's device-specific fixes.\n *\n * @remarks\n * Pure function — no DI (reads the platform from `Capacitor`, uses the global `document`), so the\n * caller presents its own loading UI around it. Centralizes the hard-won device quirks: on iOS it\n * pads width/height by 2px (otherwise the bottom is clipped), on Android it does not (the padding\n * introduces a black line). Retries the `dom-to-image-more` render up to 10 times because the first\n * pass can occasionally return empty. This is exactly the kind of plumbing where a future fix should\n * land in every app at once.\n *\n * @param element - the element to rasterize (e.g. the label preview host)\n * @param options - rendering options; see {@link KitDomToPngOptions}\n * @returns a Promise resolving to the PNG as a base64 data URL (empty string if every attempt failed)\n * @example\n * ```ts\n * const loading = await this.#loadingCtrl.create({ message: this.text.generating });\n * await loading.present();\n * const png = await kitDomToPng(this.preview().nativeElement, { rotate: true });\n * await loading.dismiss();\n * ```\n */\nexport const kitDomToPng = async (element: HTMLElement, options?: KitDomToPngOptions): Promise<string> => {\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n const { clientHeight, clientWidth } = element;\n\n // デバイス毎の問題解決のため、px 調整。\n // iOS: ないと下が途切れる。Android: あると黒線が入る。\n const addClient = Capacitor.getPlatform() === 'ios' ? 2 : 0;\n\n const dataUrl: string = await new Promise((resolve) => {\n void (async () => {\n for (let i = 0; i < 10; i++) {\n const url = await domtoimage.toPng(element, {\n width: clientWidth + addClient,\n height: clientHeight + addClient,\n scale: options?.scale ?? 3,\n copyDefaultStyles: false,\n });\n if (url) {\n resolve(url);\n return;\n }\n }\n resolve('');\n })();\n });\n\n return options?.rotate ? kitRotationImage(dataUrl) : dataUrl;\n};\n\n/** Parameters for {@link kitBuildBrotherPrintSettings}. */\nexport interface KitBrotherPrintSettingsParams {\n /** The target printer model. */\n readonly modelName: BRLMPrinterModelName;\n /** The label artwork as a base64 data URL (the `data:...,` prefix is stripped internally). */\n readonly printBase64: string;\n /** The selected label/paper (its `W<width>H<height>` code drives the tape dimensions). */\n readonly label: BRLMPrinterLabelName;\n /** Number of copies to print. Passed by the caller (apps differ: some use the print option, some fix 1). */\n readonly numberOfCopies: number;\n /** Halftone threshold for the print. */\n readonly halftoneThreshold: number;\n}\n\n/**\n * Assemble the Brother `BRLMPrintOptions` for a die-cut label print, minus the transport fields.\n *\n * @remarks\n * Pure function — no DI. Centralizes the fleet's canonical print settings (fit-page scale, centered,\n * best quality, threshold halftone, 2mm/1mm margins, `gapLength` 2.0) and the tape sizing derived\n * from the label's `W<width>H<height>` code. The caller merges the printer's `port` / `channelInfo`\n * onto the result before calling `BrotherPrint.printImage()`, so channel selection and loading UI stay\n * in the app.\n *\n * @param params - model, artwork, label, copies, and halftone threshold; see {@link KitBrotherPrintSettingsParams}\n * @returns the `BRLMPrintOptions` ready to be spread with `{ port, channelInfo }`\n * @example\n * ```ts\n * const settings = kitBuildBrotherPrintSettings({\n * modelName, printBase64, label, numberOfCopies: printOptions.printNum, halftoneThreshold: printOptions.halftoneThreshold,\n * });\n * await BrotherPrint.printImage({ ...settings, port: channel.port, channelInfo: channel.channelInfo });\n * ```\n */\nexport const kitBuildBrotherPrintSettings = (params: KitBrotherPrintSettingsParams): BRLMPrintOptions => {\n const startPoint = params.printBase64.indexOf(',');\n const tapeSize = params.label.match(/W(\\d+)H(\\d+)/);\n const tapeWidth = tapeSize && tapeSize.length >= 2 ? parseInt(tapeSize[1], 10) : 0;\n const tapeLength = tapeSize && tapeSize.length >= 3 ? parseInt(tapeSize[2], 10) : 0;\n\n // `BRLMPrintOptions` is a `QL | TD` union; a die-cut label legitimately carries fields from both\n // groups, so the object is composed via spreads to bypass the union's excess-property checks — the\n // same technique the source printer services used.\n return {\n ...{\n modelName: params.modelName,\n encodedImage: params.printBase64.slice(startPoint + 1),\n numberOfCopies: params.numberOfCopies,\n autoCut: true,\n\n scaleMode: BRLMPrinterScaleMode.FitPageAspect,\n imageRotation: BRLMPrinterImageRotation.Rotate0,\n verticalAlignment: BRLMPrinterVerticalAlignment.Center,\n horizontalAlignment: BRLMPrinterHorizontalAlignment.Center,\n printQuality: BRLMPrinterPrintQuality.Best,\n },\n ...{\n labelName: params.label,\n },\n ...{\n paperType: BRLMPrinterCustomPaperType.dieCutPaper,\n paperUnit: BRLMPrinterCustomPaperUnit.mm,\n halftone: BRLMPrinterHalftone.Threshold,\n halftoneThreshold: params.halftoneThreshold,\n tapeWidth: Number(tapeWidth.toFixed(1)),\n tapeLength: Number(tapeLength.toFixed(1)),\n gapLength: 2.0,\n\n marginTop: 1.0,\n marginRight: 2.0,\n marginBottom: 1.0,\n marginLeft: 2.0,\n\n paperMarkPosition: 0,\n paperMarkLength: 0,\n },\n };\n};\n","import type { EnvironmentProviders } from '@angular/core';\nimport { inject, InjectionToken, makeEnvironmentProviders } from '@angular/core';\nimport type { CanActivateFn, RouterStateSnapshot, UrlTree } from '@angular/router';\nimport { Router } from '@angular/router';\nimport { NavController } from '@ionic/angular/standalone';\nimport type { Observable } from 'rxjs';\nimport { map, mergeMap } from 'rxjs/operators';\n\n/**\n * Discriminated set of authentication states the guards react to.\n *\n * @remarks\n * The application is responsible for emitting these values through {@link KitAuthConfig.authState}.\n * An application that does not use a value (for example email confirmation) simply never emits it.\n *\n * - `user` — fully authenticated and verified.\n * - `confirm` — awaiting email confirmation.\n * - `required` — not authenticated.\n * - `anonymous` — signed in anonymously; the user can still be guided toward full registration.\n */\nexport type KitAuthState = 'user' | 'confirm' | 'required' | 'anonymous';\n\n/**\n * Redirect targets (route paths) used by the guards when access is denied.\n *\n * @remarks\n * Every field is required and must be provided per application, because the guards have no\n * knowledge of the host application's route layout.\n */\nexport interface KitAuthRedirects {\n /** Used by {@link kitRequiredUnauthorizedGuard}: where to navigate when the user is already authenticated (`user`). */\n readonly whenAuthorized: string;\n /** Used by {@link kitRequiredUnauthorizedGuard}: where to navigate when the user is awaiting email confirmation (`confirm`). */\n readonly whenConfirming: string;\n /** Used by {@link kitRequireConfirmingGuard}: where to navigate when the state is not `confirm`. */\n readonly whenNotConfirming: string;\n /** Used by {@link kitRequireAuthorizedGuard}: where to navigate when the state is not `user` and the fallback is not allowed. */\n readonly whenUnauthorized: string;\n}\n\n/**\n * Configuration consumed by the authentication guards, injected through {@link provideKitAuth}.\n *\n * @remarks\n * `authState` and `redirects` are required. The `onAuthorized` / `onUnauthenticated` hooks are\n * optional and default to allowing the authenticated user through (`true`) and falling through to\n * the default redirect (`false`) respectively, so an app only supplies the ones with real logic.\n */\nexport interface KitAuthConfig {\n /**\n * Source of the current authentication state.\n *\n * @remarks\n * Typically backed by the application's own auth service (for example `AuthService.isAuth()`).\n *\n * @returns A stream of {@link KitAuthState} values.\n */\n authState(): Observable<KitAuthState>;\n /**\n * Application-specific work that runs in {@link kitRequireAuthorizedGuard} after the state is confirmed to be `user`.\n *\n * @remarks\n * Typical responsibilities include token login, permission checks, terms-of-service acceptance,\n * or restoring a previously requested redirect. Optional; defaults to `true` (allow activation).\n *\n * @param state - The router state snapshot of the route being activated.\n * @returns `true` to allow activation, or a `UrlTree` to perform a custom redirect.\n */\n onAuthorized?(state: RouterStateSnapshot): Promise<boolean | UrlTree>;\n /**\n * Fallback that runs in {@link kitRequireAuthorizedGuard} when the state is `required` (not authenticated).\n *\n * @remarks\n * For example, attempt an anonymous sign-in and allow the route. Optional; defaults to `false`\n * (fall through to the default `whenUnauthorized` redirect).\n *\n * @param state - The router state snapshot of the route being activated.\n * @returns `true` to allow activation, a `UrlTree` for a custom redirect, or `false` to use the default redirect.\n */\n onUnauthenticated?(state: RouterStateSnapshot): Promise<boolean | UrlTree>;\n /** Redirect targets used by the guards. */\n redirects: KitAuthRedirects;\n}\n\n/**\n * Injection token that carries the {@link KitAuthConfig} to the authentication guards.\n */\nexport const KIT_AUTH_CONFIG = new InjectionToken<KitAuthConfig>('@rdlabo/ionic-angular-kit:auth');\n\n/**\n * Wire the authentication guard configuration into the application's dependency injection.\n *\n * @remarks\n * The factory runs inside an injection context, so it may call `inject()` (for example\n * `inject(AuthService)`) to build the configuration.\n *\n * @param configFactory - Factory that returns the {@link KitAuthConfig} for the application.\n * @returns Environment providers to add to the application bootstrap.\n *\n * @example\n * ```ts\n * provideKitAuth(() => {\n * const auth = inject(AuthService);\n * return {\n * // onAuthorized / onUnauthenticated are optional (default: allow / fall through to redirect).\n * authState: () => auth.isAuth(),\n * redirects: {\n * whenAuthorized: '/',\n * whenConfirming: '/auth/confirm',\n * whenNotConfirming: '/auth/signin',\n * whenUnauthorized: 'auth',\n * },\n * };\n * });\n * ```\n */\nexport const provideKitAuth = (configFactory: () => KitAuthConfig): EnvironmentProviders =>\n makeEnvironmentProviders([{ provide: KIT_AUTH_CONFIG, useFactory: configFactory }]);\n\n/**\n * Guard that requires the user to be unauthenticated (for example sign-in or sign-up pages).\n *\n * @remarks\n * Allows the `required` and `anonymous` states (an anonymous user is permitted to proceed to a\n * registration page). An authenticated user (`user`) is sent to `whenAuthorized`, and a user\n * awaiting confirmation (`confirm`) is sent to `whenConfirming`.\n *\n * @returns A stream emitting `true` to allow activation, or `false` after triggering a redirect.\n *\n * @example\n * ```ts\n * const routes: Routes = [{ path: 'signin', component: SigninPage, canActivate: [kitRequiredUnauthorizedGuard] }];\n * ```\n */\nexport const kitRequiredUnauthorizedGuard: CanActivateFn = () => {\n const { authState, redirects } = inject(KIT_AUTH_CONFIG);\n const router = inject(Router);\n const navCtrl = inject(NavController);\n\n return authState().pipe(\n map((data) => {\n if (data === 'user') {\n navCtrl.setDirection('root');\n router.navigate([redirects.whenAuthorized]);\n return false;\n } else if (data === 'confirm') {\n router.navigate([redirects.whenConfirming]);\n return false;\n }\n // 'required' | 'anonymous'\n return true;\n }),\n );\n};\n\n/**\n * Guard that requires the user to be awaiting email confirmation (`confirm`).\n *\n * @remarks\n * Any other state triggers a redirect: an `anonymous` user is sent to the authenticated area\n * (`whenAuthorized`), and every remaining state is sent to `whenNotConfirming`.\n *\n * @returns A stream emitting `true` to allow activation, or `false` after triggering a redirect.\n *\n * @example\n * ```ts\n * const routes: Routes = [{ path: 'confirm', component: ConfirmPage, canActivate: [kitRequireConfirmingGuard] }];\n * ```\n */\nexport const kitRequireConfirmingGuard: CanActivateFn = () => {\n const { authState, redirects } = inject(KIT_AUTH_CONFIG);\n const router = inject(Router);\n const navCtrl = inject(NavController);\n\n return authState().pipe(\n map((data) => {\n if (data === 'confirm') {\n return true;\n }\n navCtrl.setDirection('root');\n router.navigate([data === 'anonymous' ? redirects.whenAuthorized : redirects.whenNotConfirming]);\n return false;\n }),\n );\n};\n\n/**\n * Guard that requires the user to be fully authenticated (`user`).\n *\n * @remarks\n * - `user` — runs {@link KitAuthConfig.onAuthorized} (token login, permission checks, and so on).\n * - `anonymous` — allowed as-is, for applications that permit anonymous browsing.\n * - `required` / `confirm` — runs {@link KitAuthConfig.onUnauthenticated}; if it resolves to `false`,\n * the user is redirected to `whenUnauthorized`.\n *\n * @param _route - The activated route snapshot (unused).\n * @param state - The router state snapshot, forwarded to the configuration hooks.\n * @returns A stream emitting the activation result: `true`, a `UrlTree`, or `false` after a redirect.\n *\n * @example\n * ```ts\n * const routes: Routes = [{ path: 'home', component: HomePage, canActivate: [kitRequireAuthorizedGuard] }];\n * ```\n */\nexport const kitRequireAuthorizedGuard: CanActivateFn = (_route, state) => {\n const { authState, onAuthorized, onUnauthenticated, redirects } = inject(KIT_AUTH_CONFIG);\n const router = inject(Router);\n const navCtrl = inject(NavController);\n\n return authState().pipe(\n mergeMap(async (data) => {\n if (data === 'user') {\n // 既定は「許可」。tokenLogin / 権限確認等が必要なアプリだけ onAuthorized を渡す。\n return onAuthorized ? onAuthorized(state) : true;\n }\n if (data === 'anonymous') {\n return true;\n }\n // 既定は false(whenUnauthorized へ)。匿名ログイン等のフォールバックが要るアプリだけ渡す。\n const fallback = onUnauthenticated ? await onUnauthenticated(state) : false;\n if (fallback !== false) {\n return fallback;\n }\n navCtrl.setDirection('root');\n router.navigate([redirects.whenUnauthorized]);\n return false;\n }),\n );\n};\n","import type { EnvironmentProviders } from '@angular/core';\nimport { inject, InjectionToken, makeEnvironmentProviders } from '@angular/core';\nimport type { HttpEvent, HttpInterceptorFn, HttpRequest } from '@angular/common/http';\nimport { HttpErrorResponse, HttpResponse } from '@angular/common/http';\nimport { Network } from '@capacitor/network';\nimport type { Observable } from 'rxjs';\nimport { from, retry, throwError, timer } from 'rxjs';\nimport { catchError, map, mergeMap, tap, timeout } from 'rxjs/operators';\n\n/**\n * HTTP methods that are safe to retry automatically.\n *\n * @remarks\n * Non-idempotent methods (`POST` / `PATCH` / `DELETE`) are never auto-retried, because a response\n * lost *after* the server processed the write would be replayed into a duplicate write (\"saved twice\n * from one tap\"). A non-idempotent request that is genuinely safe to replay opts in by carrying an\n * `Idempotency-Key` header (which the server must honor).\n *\n * @internal\n */\nconst RETRYABLE_METHODS = ['GET', 'HEAD', 'OPTIONS'];\n\n/**\n * Transient HTTP statuses worth retrying: `0` (network/transport failure), `408` (request timeout),\n * `429` (rate limited), and the `502` / `503` / `504` gateway-availability family. Every other status\n * is thrown immediately — a whitelist is safer than a blacklist for deciding what to replay.\n *\n * @internal\n */\nconst RETRYABLE_STATUSES = [0, 408, 429, 502, 503, 504];\n\n/**\n * Maximum number of automatic retries for a retryable request.\n *\n * @internal\n */\nconst MAX_RETRIES = 2;\n\n/**\n * Fleet-wide per-request timeout. A request with no response within this window fails with a\n * synthetic `408` (a {@link RETRYABLE_STATUSES | retryable status}). Deliberately generous — it\n * catches a genuinely hung request (dead server) without cutting off legitimately slow work such as\n * a large upload or an AI generation. `timeout({ each })` resets on every emission, so a streaming\n * response that keeps emitting is unaffected.\n *\n * @internal\n */\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/**\n * Parse a `Retry-After` header (delta-seconds or an HTTP-date) into milliseconds, or `null` when it\n * is absent or unparseable.\n *\n * @internal\n */\nconst parseRetryAfterMs = (error: HttpErrorResponse): number | null => {\n const header = error.headers?.get('Retry-After');\n if (!header) {\n return null;\n }\n const seconds = Number(header);\n if (Number.isFinite(seconds)) {\n return Math.max(0, seconds * 1000);\n }\n const dateMs = Date.parse(header);\n return Number.isNaN(dateMs) ? null : Math.max(0, dateMs - Date.now());\n};\n\n/**\n * Configuration that customizes the behavior of {@link kitAuthInterceptor}, injected through {@link provideKitHttp}.\n *\n * @remarks\n * The interceptor fixes the retry policy and control flow; only the hooks below are app-specific:\n *\n * - Retries only {@link RETRYABLE_METHODS | idempotent methods} (or a request bearing an\n * `Idempotency-Key`) on a {@link RETRYABLE_STATUSES | transient status}, up to {@link MAX_RETRIES}\n * times with a short jittered backoff (honoring `Retry-After`). Writes are never auto-retried.\n * - When the device is offline it fails fast to {@link KitHttpConfig.offlineFallback} instead of\n * waiting out the retries.\n * - On a final error it classifies by status and calls the matching hook (see each hook below).\n *\n * Only {@link KitHttpConfig.getAuthHeaders} is required — it has no safe default. Every other hook is\n * optional and defaults to a no-op (or `{}` / `false` / `null` as appropriate), so an app configures\n * only the behavior that actually differs from the canonical baseline.\n */\nexport interface KitHttpConfig {\n /**\n * Produce authentication and metadata headers for the outgoing request.\n *\n * @param request - The outgoing request about to be sent.\n * @returns A map of header names to values, resolved asynchronously.\n */\n getAuthHeaders(request: HttpRequest<unknown>): Promise<Record<string, string>>;\n /**\n * Produce additional headers for the outgoing request.\n *\n * @remarks\n * Optional; defaults to adding no extra headers.\n *\n * @param request - The outgoing request about to be sent.\n * @returns A map of header names to values; return `{}` when none are needed.\n */\n buildExtraHeaders?(request: HttpRequest<unknown>): Record<string, string>;\n /**\n * Treat an otherwise-successful response as an error.\n *\n * @remarks\n * Optional. Some backends use a 2xx status (for example `204` / `206`) to signal a condition the\n * app wants to surface as an error rather than a success. Return `true` to throw the response so it\n * flows through the error path; the status is not in {@link RETRYABLE_STATUSES}, so it is not\n * retried and propagates to the caller. Defaults to treating every 2xx as a success.\n *\n * @param response - The successful `HttpResponse` about to be delivered.\n * @returns `true` to reject the response as an error.\n */\n treatAsError?(response: HttpResponse<unknown>): boolean;\n /**\n * Called for every successful response that completed an actual network round trip.\n *\n * @remarks\n * Responses synthesized by {@link KitHttpConfig.offlineFallback} are produced after `catchError`\n * and therefore never reach this hook, so it observes genuine successes only. A typical use is to\n * reset an \"offline\" flag once connectivity is restored. Optional; defaults to a no-op.\n *\n * @param event - The successful `HttpResponse`.\n */\n onResponse?(event: HttpResponse<unknown>): void;\n /**\n * Decide whether to pass the request straight through, skipping auth, retry, and error handling.\n *\n * @remarks\n * Useful for external URLs such as S3 or a CDN. Optional; defaults to `false` (never bypass).\n *\n * @param request - The outgoing request.\n * @returns `true` to bypass the interceptor pipeline.\n */\n bypass?(request: HttpRequest<unknown>): boolean;\n /**\n * Provide an offline short-circuit when a request fails.\n *\n * @remarks\n * Returning a non-null observable replaces the error with that response (for example a queued\n * offline result). Optional; defaults to `null` (no fallback, normal error handling proceeds).\n *\n * @param request - The request that failed (after headers were applied).\n * @param error - The error response that triggered the fallback.\n * @returns A replacement event stream, or `null` for no fallback.\n */\n offlineFallback?(request: HttpRequest<unknown>, error: HttpErrorResponse): Observable<HttpEvent<unknown>> | null;\n /**\n * Side effect to run on a `401` response (for example an expired token).\n *\n * @remarks\n * Optional; defaults to a no-op.\n *\n * @param request - The request that received the `401`.\n */\n onUnauthorized?(request: HttpRequest<unknown>): void;\n /**\n * Side effect to run on a `403` response (a permission error).\n *\n * @remarks\n * Optional; defaults to a no-op.\n *\n * @param request - The request that received the `403`.\n */\n onForbidden?(request: HttpRequest<unknown>): void;\n /**\n * UX hook for a genuine network / transport failure (status `0`) while the device reports itself\n * connected — i.e. the server is unreachable rather than the phone being offline.\n *\n * @remarks\n * Optional; defaults to a no-op. Narrow by design: it fires only for status `0` (not for `404`,\n * `429`, `5xx`, …, which have their own hooks), so a \"connection lost, reload?\" prompt is not shown\n * for server-side problems. When the device is offline it is not called at all — `offlineFallback`\n * owns that path. The kit ships {@link KitReloadAlertController} as the canonical implementation\n * (with de-dup so concurrent failures show a single alert, and auto-dismiss on reconnect).\n *\n * @param status - The HTTP status code (`0`), or a string descriptor for non-HTTP failures.\n * @returns Optionally a promise to await before continuing.\n */\n onNetworkError?(status: number | string): Promise<void> | void;\n /**\n * UX hook for a transient server-availability failure (`502` / `503` / `504`), fired after retries\n * are exhausted.\n *\n * @remarks\n * Optional; defaults to a no-op. Distinct from {@link KitHttpConfig.onNetworkError} — the device's\n * connection is fine, the server is momentarily unavailable — so the app can say \"server busy, try\n * again shortly\" rather than prompt a reload.\n *\n * @param status - `502`, `503`, or `504`.\n * @param retryAfterSeconds - The server's `Retry-After` hint in seconds, when provided.\n */\n onServerBusy?(status: number, retryAfterSeconds?: number): void;\n /**\n * UX hook for a `429 Too Many Requests` response, fired after retries are exhausted.\n *\n * @remarks\n * Optional; defaults to a no-op.\n *\n * @param retryAfterSeconds - The server's `Retry-After` hint in seconds, when provided.\n */\n onRateLimited?(retryAfterSeconds?: number): void;\n /**\n * UX hook for `400` / `422` / `500` responses that carry a server-provided message.\n *\n * @remarks\n * Optional; defaults to a no-op. Note that the message comes straight from the API; prefer a\n * user-facing `userMessage` / `code` in your error contract over showing a raw developer message.\n *\n * @param message - The message extracted from the error body.\n */\n onServerError?(message: string): void;\n /**\n * Side effect for a failure while *producing* the auth headers (`getAuthHeaders` rejected).\n *\n * @remarks\n * Optional; defaults to a no-op. Because the request is never sent in this case, it does not reach\n * the response-error hooks; classify it here (for example a failed token refresh) so it does not\n * fail silently.\n *\n * @param request - The request whose headers could not be produced.\n * @param error - The error thrown by `getAuthHeaders`.\n */\n onAuthError?(request: HttpRequest<unknown>, error: unknown): void;\n}\n\n/**\n * Injection token that carries the {@link KitHttpConfig} to {@link kitAuthInterceptor}.\n */\nexport const KIT_HTTP_CONFIG = new InjectionToken<KitHttpConfig>('@rdlabo/ionic-angular-kit:http');\n\n/**\n * Wire the {@link kitAuthInterceptor} configuration into the application's dependency injection.\n *\n * @remarks\n * Register the interceptor itself separately via `provideHttpClient(withInterceptors([kitAuthInterceptor]))`.\n * The factory runs inside an injection context, so it may call `inject()`.\n *\n * @param configFactory - Factory that returns the {@link KitHttpConfig} for the application.\n * @returns Environment providers to add to the application bootstrap.\n *\n * @example\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideHttpClient(withInterceptors([kitAuthInterceptor])),\n * provideKitHttp(() => {\n * const auth = inject(AuthService);\n * const reload = inject(KitReloadAlertController);\n * return {\n * // Only getAuthHeaders is required; every other hook is optional and defaults to a no-op.\n * getAuthHeaders: async () => ({ Authorization: `Bearer ${await auth.token()}` }),\n * onUnauthorized: () => auth.signOut(),\n * onNetworkError: (status) =>\n * reload.present({ header: 'Network error', message: `Reload? (${status})`, okText: 'Reload' }),\n * onResponse: () => void reload.dismiss(),\n * };\n * }),\n * ],\n * });\n * ```\n */\nexport const provideKitHttp = (configFactory: () => KitHttpConfig): EnvironmentProviders =>\n makeEnvironmentProviders([{ provide: KIT_HTTP_CONFIG, useFactory: configFactory }]);\n\n/**\n * Classify a final (post-retry) error and invoke the matching {@link KitHttpConfig} hook.\n *\n * @internal\n */\nconst dispatchError = (config: KitHttpConfig, req: HttpRequest<unknown>, error: HttpErrorResponse): void => {\n const status = error.status;\n const retryAfterMs = parseRetryAfterMs(error);\n const retryAfterSeconds = retryAfterMs === null ? undefined : Math.round(retryAfterMs / 1000);\n\n if (status === 401) {\n config.onUnauthorized?.(req);\n } else if (status === 403) {\n config.onForbidden?.(req);\n } else if (status === 0) {\n // Genuine network/transport failure. Only surface it when the device is actually connected\n // (server unreachable); when offline, offlineFallback owns the UX — a reload prompt won't help.\n void Network.getStatus().then((network) => {\n if (network.connected) {\n config.onNetworkError?.(status);\n }\n });\n } else if (status === 429) {\n config.onRateLimited?.(retryAfterSeconds);\n } else if ([502, 503, 504].includes(status)) {\n config.onServerBusy?.(status, retryAfterSeconds);\n } else if ([400, 422, 500].includes(status) && error.error?.message) {\n config.onServerError?.(error.error.message);\n }\n // Every other status (404, 418, …) is left to the caller — no generic alert.\n};\n\n/**\n * Canonical functional HTTP interceptor that applies authentication, retries, and error handling.\n *\n * @remarks\n * Behavior, driven by the injected {@link KitHttpConfig}:\n *\n * 1. Requests for which `bypass` returns `true` are forwarded untouched.\n * 2. If `getAuthHeaders` rejects, `onAuthError` is called and the request is not sent.\n * 3. Otherwise the headers from `getAuthHeaders` and `buildExtraHeaders` are merged onto a cloned request.\n * 4. On failure the request is retried up to {@link MAX_RETRIES} times, but **only** when the device\n * is online, the method is a {@link RETRYABLE_METHODS | retryable method} (or carries an\n * `Idempotency-Key`), and the status is a {@link RETRYABLE_STATUSES | transient status}. The\n * backoff is `retryCount * 500ms` plus up to 250ms of jitter, or the server's `Retry-After`.\n * When the device is offline it stops retrying immediately.\n * 5. On the final error, `offlineFallback` is consulted first; otherwise the error is classified by\n * status (see {@link dispatchError}): `401`→`onUnauthorized`, `403`→`onForbidden`, `0`→\n * `onNetworkError` (when connected), `429`→`onRateLimited`, `502`/`503`/`504`→`onServerBusy`, and\n * `400`/`422`/`500` with a body message→`onServerError`.\n *\n * @param request - The outgoing request.\n * @param next - The next handler in the interceptor chain.\n * @returns A stream of HTTP events for the (possibly modified, retried, or replaced) request.\n *\n * @example\n * ```ts\n * provideHttpClient(withInterceptors([kitAuthInterceptor]));\n * ```\n */\nexport const kitAuthInterceptor: HttpInterceptorFn = (request, next) => {\n const config = inject(KIT_HTTP_CONFIG);\n\n if (config.bypass?.(request)) {\n return next(request);\n }\n\n return from(Promise.resolve(config.getAuthHeaders(request))).pipe(\n catchError((headerError: unknown) => {\n // getAuthHeaders failed → the request is never sent; classify it instead of failing silently.\n config.onAuthError?.(request, headerError);\n return throwError(() => headerError);\n }),\n mergeMap((authHeaders) => {\n const req = request.clone({ setHeaders: { ...authHeaders, ...config.buildExtraHeaders?.(request) } });\n const retryable = RETRYABLE_METHODS.includes(req.method) || req.headers.has('Idempotency-Key');\n\n const base = next(req).pipe(\n timeout({\n each: DEFAULT_TIMEOUT_MS,\n with: () => throwError(() => new HttpErrorResponse({ status: 408, statusText: 'Request Timeout', url: req.url })),\n }),\n );\n\n return base.pipe(\n map((event) => {\n // A backend may signal an error condition with a 2xx status (e.g. 204/206); surface it as an error.\n if (event instanceof HttpResponse && config.treatAsError?.(event)) {\n throw event;\n }\n return event;\n }),\n retry({\n count: MAX_RETRIES,\n delay: (error: HttpErrorResponse, retryCount: number) =>\n from(Network.getStatus()).pipe(\n mergeMap((network) => {\n // Offline → don't wait out the retries; fail fast so offlineFallback can take over.\n if (!network.connected) {\n return throwError(() => error);\n }\n // Only replay idempotent requests, and only on a transient status.\n if (!retryable || !RETRYABLE_STATUSES.includes(error.status)) {\n return throwError(() => error);\n }\n // Short linear backoff (500ms, 1000ms, …) plus jitter to de-correlate a fleet of\n // clients reconnecting at once; the server's Retry-After wins when present.\n const backoff = parseRetryAfterMs(error) ?? retryCount * 500 + Math.random() * 250;\n return timer(backoff);\n }),\n ),\n }),\n tap((event) => {\n if (event instanceof HttpResponse) {\n config.onResponse?.(event);\n }\n }),\n catchError((error: HttpErrorResponse) => {\n const fallback = config.offlineFallback?.(req, error);\n if (fallback) {\n return fallback;\n }\n dispatchError(config, req, error);\n return throwError(() => error);\n }),\n );\n }),\n );\n};\n","/**\n * Merge a newly fetched page of items into an existing list by id, keeping the result sorted.\n *\n * Items are merged by the numeric `key` field. On a duplicate `key` the item from `arrayNew` wins\n * and replaces the one in `arrayOld`. Old items whose `key` falls *inside* the window spanned by\n * `arrayNew` (between its first and last `key`) are dropped, since the freshly fetched page is the\n * authoritative copy of that window; old items *outside* the window are kept. The merged items are\n * finally sorted by `key`, ascending or descending according to `order`.\n *\n * The algorithm is:\n * 1. If both inputs are empty, return an empty array.\n * 2. Read the first (`lead`) and last (`last`) `key` values of `arrayNew` as the bounds of the\n * page's window. Keep the old items whose `key` lies *outside* that window (at or beyond the\n * extremes) and drop those strictly inside it, handling both a high-to-low page (`lead > last`)\n * and a low-to-high page (`lead < last`). A single-value page (`lead === last`) keeps all old items.\n * 3. Remove old items whose `key` already exists in `arrayNew` (new wins on duplicates).\n * 4. Concatenate `arrayNew` with the surviving old items and sort by `key` in the requested\n * direction.\n *\n * @remarks\n * Designed for infinite-scroll / paginated list merging, where each fetched page may overlap the\n * previously held items and the server returns a contiguous, ordered window of records. The `key`\n * field is treated as a number for range comparison and sorting.\n *\n * @typeParam T - Element type of both arrays. Its `key` property must be a numeric value.\n * @param arrayOld - The previously accumulated list. May be empty or nullish-safe (empty when falsy).\n * @param arrayNew - The newly fetched page of items; its `key` range defines the window that its own\n * items replace, and its items take precedence on duplicates.\n * @param key - The property of `T` used as the unique, numeric id for matching, range filtering, and sorting.\n * @param order - Sort direction by `key`: `'ASC'` for ascending or `'DESC'` for descending. Defaults to `'DESC'`.\n * @param secondaryKey - Optional second property for extra de-duplication. When provided, any\n * surviving old item whose `secondaryKey` value matches that of *any* new item is dropped before\n * the `key` merge. Useful when a record keeps a stable `key` but its secondary identity changes\n * between pages (e.g. items keyed by `id` but also grouped by `parentId`). Omit to skip this step.\n * @returns A new array containing `arrayNew` merged with the out-of-window, non-duplicate old items, sorted by `key`.\n * @example\n * ```ts\n * interface Post {\n * id: number;\n * title: string;\n * }\n *\n * const loaded: Post[] = [\n * { id: 30, title: 'c' },\n * { id: 20, title: 'b' },\n * { id: 10, title: 'a' },\n * ];\n * const nextPage: Post[] = [\n * { id: 20, title: 'b (updated)' },\n * { id: 15, title: 'a.5' },\n * ];\n *\n * // Descending merge: id 30 lies above the new page's [15, 20] window so it is kept; id 20 is\n * // inside the window and is replaced by the new value; id 10 lies below the window and is kept.\n * const merged = arrayConcatById(loaded, nextPage, 'id', 'DESC');\n * // => [{ id: 30, title: 'c' }, { id: 20, title: 'b (updated)' }, { id: 15, title: 'a.5' }, { id: 10, title: 'a' }]\n * ```\n */\nexport const arrayConcatById = <T>(\n arrayOld: T[],\n arrayNew: T[],\n key: keyof T,\n order: 'ASC' | 'DESC' = 'DESC',\n secondaryKey?: keyof T,\n): T[] => {\n if (!arrayNew.length && !arrayOld.length) {\n return [];\n }\n const lead = arrayNew[0][key] as number;\n const last = arrayNew[arrayNew.length - 1][key] as number;\n\n const filteredOld = (arrayOld || []).filter((vol) => {\n const value = vol[key] as number;\n return (lead > last && (value >= lead || value <= last)) || (lead < last && (value <= lead || value >= last)) || lead === last;\n });\n\n const secondaryFilteredOld = secondaryKey\n ? filteredOld.filter((vol) => !arrayNew.some((element) => element[secondaryKey] === vol[secondaryKey]))\n : filteredOld;\n\n const oldData = secondaryFilteredOld.filter((vol) => !arrayNew.some((element) => element[key] === vol[key]));\n const data = arrayNew.concat(oldData);\n\n const direction = order === 'ASC' ? 1 : -1;\n return data.sort((a, b) => {\n const x = a[key] as number;\n const y = b[key] as number;\n if (x > y) {\n return direction;\n }\n if (x < y) {\n return direction * -1;\n }\n return 0;\n });\n};\n","/**\n * Order-independent deep equality check.\n *\n * @remarks\n * Returns `true` when `a` and `b` serialize to the same value after their (nested) object entries are\n * sorted by key, so property order does not affect the result. Intended for cheap \"did this state\n * object change?\" comparisons.\n *\n * Caveats of the JSON-serialization approach: values that JSON drops or coerces (`undefined`,\n * functions, `NaN`, `Date`, `Map`/`Set`) are compared by their serialized form, and array element\n * order is treated as significant (arrays are not reordered in a meaningful way). Use a structural\n * deep-equal library when those cases matter.\n *\n * @param a - First object.\n * @param b - Second object.\n * @returns `true` when the two objects are deeply equal ignoring key order.\n * @example\n * ```ts\n * objectEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); // => true\n * objectEqual({ a: 1 }, { a: 2 }); // => false\n * ```\n */\nexport const objectEqual = (a: object, b: object): boolean => {\n if (Object.is(a, b)) {\n return true;\n }\n const sortDeep = (obj: unknown): unknown => {\n if (typeof obj !== 'object' || !obj) {\n return undefined;\n }\n return Object.entries(obj)\n .sort()\n .map(([entryKey, value]) => [entryKey, typeof value === 'object' ? sortDeep(value) : value]);\n };\n return JSON.stringify(sortDeep(a)) === JSON.stringify(sortDeep(b));\n};\n","/**\n * Disable the button that triggered an event while an async operation runs, re-enabling it after.\n *\n * @remarks\n * Prevents the common double-submit / double-tap bug: the `event.target` button is disabled, the\n * work is awaited, and the button is re-enabled — even if the work rejects (the rejection is\n * swallowed here so the button always recovers; handle errors inside `work` if you need to react).\n *\n * @param event - The DOM event whose `target` is the button to disable (e.g. a click event).\n * @param work - The async operation to run while the button is disabled.\n * @returns A Promise that resolves once the work has settled and the button has been re-enabled.\n * @example\n * ```ts\n * async submit(event: Event): Promise<void> {\n * await disableHandler(event, this.save());\n * }\n * ```\n */\nexport const disableHandler = async (event: Event, work: Promise<void | boolean>): Promise<void> => {\n const target = event.target as HTMLButtonElement;\n target.disabled = true;\n await work.catch((): undefined => undefined);\n target.disabled = false;\n};\n","import { WritableSignal } from '@angular/core';\n\n/**\n * Toggle the `disabled` flag of a signal-held `ion-infinite-scroll` / `ion-refresher` element.\n *\n * @remarks\n * A tiny pure helper for the common pattern of stashing the completing scroll/refresher element in a\n * signal (e.g. captured from the event) and later enabling/disabling it — for instance disabling\n * infinite scroll once the last page has loaded. A no-op when the signal holds no element.\n *\n * @param completeEvent - a writable signal holding the infinite-scroll / refresher element (or nullish)\n * @param disabled - the value to set on the element's `disabled` property\n * @example\n * ```ts\n * const infinite = signal<HTMLIonInfiniteScrollElement | null>(null);\n * // ...on ionInfinite: infinite.set(ev.target); ...when no more pages:\n * kitChangeEventDisabled(infinite, true);\n * ```\n */\nexport const kitChangeEventDisabled = (\n completeEvent: WritableSignal<HTMLIonInfiniteScrollElement | HTMLIonRefresherElement | null | undefined>,\n disabled: boolean,\n): void => {\n completeEvent.update((event) => {\n if (event) {\n event.disabled = disabled;\n }\n return event;\n });\n};\n","import { ElementRef } from '@angular/core';\nimport { Observable, startWith } from 'rxjs';\n\n/**\n * Observe an Ionic page's \"is currently entered\" state from its lifecycle DOM events.\n *\n * @remarks\n * Emits `true` on `ionViewDidEnter` and `false` on `ionViewWillEnter` / `ionViewWillLeave`, seeded\n * with `false` via `startWith`. Useful to pause/resume work (timers, video, expensive rendering)\n * while a page is off-screen in Ionic's stack navigation, without wiring the four lifecycle hooks by\n * hand. The listeners are removed when the Observable is unsubscribed.\n *\n * @param el - an `ElementRef` for the page host element (the `ion-page`)\n * @returns an Observable that emits whether the page is currently entered\n * @example\n * ```ts\n * export class FeedPage {\n * readonly #host = inject(ElementRef);\n * readonly isEntered = toSignal(kitCreateDidEnter(this.#host), { initialValue: false });\n * }\n * ```\n */\nexport const kitCreateDidEnter = (el: ElementRef): Observable<boolean> => {\n return new Observable<boolean>((observer) => {\n const willEnter = () => observer.next(false);\n const didEnter = () => observer.next(true);\n const willLeave = () => observer.next(false);\n\n el.nativeElement.addEventListener('ionViewWillEnter', willEnter);\n el.nativeElement.addEventListener('ionViewDidEnter', didEnter);\n el.nativeElement.addEventListener('ionViewWillLeave', willLeave);\n\n return () => {\n el.nativeElement.removeEventListener('ionViewWillEnter', willEnter);\n el.nativeElement.removeEventListener('ionViewDidEnter', didEnter);\n el.nativeElement.removeEventListener('ionViewWillLeave', willLeave);\n };\n }).pipe(startWith(false));\n};\n","/*\n * Public API Surface of @rdlabo/ionic-angular-kit\n */\n\n// Storage: typed wrapper around the platform key/value store.\nexport * from './lib/storage/kit-storage.service';\n\n// Overlay: wrapper around the Ionic Modal / Toast / Alert controllers.\nexport * from './lib/overlay/overlay-config';\nexport * from './lib/overlay/kit-overlay.controller';\nexport * from './lib/overlay/kit-reload-alert.controller';\nexport * from './lib/overlay/kit-auth-failed-alert';\nexport * from './lib/overlay/kit-language-action-sheet';\n\n// Directives.\nexport * from './lib/directives/autofill.directive';\n\n// Keyboard: native keyboard reposition listeners.\nexport * from './lib/keyboard/kit-keyboard';\n\n// Theme: light/dark controller with OS-follow and native status-bar sync.\nexport * from './lib/theme/theme-config';\nexport * from './lib/theme/kit-theme.controller';\n\n// Review: throttled native in-app review request.\nexport * from './lib/review/kit-request-review';\n\n// Printer: pure Brother label plumbing (DOM→PNG, rotation, print-settings assembly).\nexport * from './lib/printer/kit-printer';\n\n// Auth: functional route guards.\nexport * from './lib/auth/auth-guards';\n\n// HTTP: functional interceptor.\nexport * from './lib/http/kit-http.interceptor';\n\n// Utils: framework-agnostic pure helpers.\nexport * from './lib/utils/haptics';\nexport * from './lib/utils/array';\nexport * from './lib/utils/object';\nexport * from './lib/utils/dom';\nexport * from './lib/utils/ionic-scroll-event';\nexport * from './lib/utils/ionic-view-enter';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rdlabo-ionic-angular-kit.mjs","sources":["../../../projects/kit/src/lib/storage/kit-storage.service.ts","../../../projects/kit/src/lib/overlay/overlay-config.ts","../../../projects/kit/src/lib/utils/haptics.ts","../../../projects/kit/src/lib/overlay/kit-overlay.controller.ts","../../../projects/kit/src/lib/overlay/kit-loading.controller.ts","../../../projects/kit/src/lib/overlay/kit-reload-alert.controller.ts","../../../projects/kit/src/lib/overlay/kit-auth-failed-alert.ts","../../../projects/kit/src/lib/overlay/kit-language-action-sheet.ts","../../../projects/kit/src/lib/directives/autofill.directive.ts","../../../projects/kit/src/lib/keyboard/kit-keyboard.ts","../../../projects/kit/src/lib/theme/theme-config.ts","../../../projects/kit/src/lib/theme/kit-theme.controller.ts","../../../projects/kit/src/lib/review/kit-request-review.ts","../../../projects/kit/src/lib/printer/kit-printer.ts","../../../projects/kit/src/lib/auth/auth-guards.ts","../../../projects/kit/src/lib/http/kit-http.interceptor.ts","../../../projects/kit/src/lib/utils/array.ts","../../../projects/kit/src/lib/utils/object.ts","../../../projects/kit/src/lib/utils/dom.ts","../../../projects/kit/src/lib/utils/ionic-scroll-event.ts","../../../projects/kit/src/lib/utils/ionic-view-enter.ts","../../../projects/kit/src/public-api.ts","../../../projects/kit/src/rdlabo-ionic-angular-kit.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { Storage } from '@ionic/storage-angular';\n\n/**\n * Thin, typed wrapper around `@ionic/storage-angular`.\n *\n * Starts `create()` exactly once and stores the resulting ready Promise. Every operation awaits\n * that Promise before touching the underlying store, so calls made before initialization completes\n * are queued rather than dropped.\n *\n * @remarks\n * A naive wrapper that reads the store synchronously would silently no-op (or throw) when invoked\n * before `create()` resolves, losing early writes. Awaiting the one-time ready Promise on every\n * operation removes that race without forcing callers to coordinate initialization themselves.\n *\n * @example\n * ```ts\n * constructor(private readonly storage: KitStorageService) {}\n *\n * async ngOnInit(): Promise<void> {\n * await this.storage.set('token', 'abc123');\n * const token = await this.storage.get<string>('token');\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class KitStorageService {\n /** One-time `create()` ready Promise; awaited before every operation so early calls are not lost. */\n readonly #ready: Promise<Storage> = inject(Storage).create();\n\n /**\n * Persist a value under the given key.\n *\n * @typeParam T - type of the value being stored\n * @param key - key to store the value under\n * @param value - value to persist; overwrites any existing value for the key\n * @returns a Promise that resolves once the value has been written\n * @example\n * ```ts\n * await storage.set('user', { id: 1, name: 'Ada' });\n * ```\n */\n async set<T>(key: string, value: T): Promise<void> {\n await (await this.#ready).set(key, value);\n }\n\n /**\n * Read the value stored under the given key.\n *\n * @typeParam T - expected type of the stored value\n * @param key - key to read\n * @returns the stored value, or `null` when the key is absent\n * @example\n * ```ts\n * const user = await storage.get<{ id: number }>('user');\n * ```\n */\n async get<T>(key: string): Promise<T | null> {\n return (await (await this.#ready).get(key)) ?? null;\n }\n\n /**\n * Remove the value stored under the given key.\n *\n * @param key - key to remove; a no-op when the key is absent\n * @returns a Promise that resolves once the key has been removed\n */\n async remove(key: string): Promise<void> {\n await (await this.#ready).remove(key);\n }\n\n /**\n * Remove every key/value pair from the store.\n *\n * @returns a Promise that resolves once the store has been emptied\n */\n async clear(): Promise<void> {\n await (await this.#ready).clear();\n }\n\n /**\n * List every key currently present in the store.\n *\n * @returns an array of all stored keys\n */\n async keys(): Promise<string[]> {\n return (await this.#ready).keys();\n }\n}\n","import type { EnvironmentProviders } from '@angular/core';\nimport { InjectionToken, makeEnvironmentProviders } from '@angular/core';\n\n/**\n * User-visible button labels consumed by `KitOverlayController`.\n *\n * @remarks\n * The kit deliberately ships no i18n strings of its own and has no hard dependency on\n * `@angular/localize`. The consuming application always provides these labels: multilingual apps\n * pass `$localize`-resolved strings, single-language apps pass plain literals.\n */\nexport interface KitLabels {\n /** Text for the \"close\" button used by alerts and toasts. */\n readonly close: string;\n /** Text for the \"cancel\" button used by confirmation alerts. */\n readonly cancel: string;\n}\n\n/**\n * Overlay configuration injected via `provideKitOverlay()`.\n *\n * @remarks\n * All fields are required; the consuming application must supply a complete configuration.\n */\nexport interface KitOverlayConfig {\n /** Button labels used across the overlays presented by `KitOverlayController`. */\n readonly labels: KitLabels;\n}\n\n/**\n * Injection token carrying the {@link KitOverlayConfig} for `KitOverlayController`.\n *\n * @remarks\n * Provide it through {@link provideKitOverlay} rather than registering it directly.\n */\nexport const KIT_OVERLAY_CONFIG = new InjectionToken<KitOverlayConfig>('@rdlabo/ionic-angular-kit:overlay');\n\n/**\n * Wire `KitOverlayController` into the application by providing its button labels.\n *\n * @param config - overlay configuration, including the button labels to inject\n * @returns environment providers to add to the application's provider list\n * @example\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideKitOverlay({ labels: { close: $localize`Close`, cancel: $localize`Cancel` } }),\n * ],\n * });\n * ```\n */\nexport const provideKitOverlay = (config: KitOverlayConfig): EnvironmentProviders =>\n makeEnvironmentProviders([{ provide: KIT_OVERLAY_CONFIG, useValue: config }]);\n","import { Capacitor } from '@capacitor/core';\nimport { Haptics, ImpactStyle } from '@capacitor/haptics';\n\n/**\n * Trigger native haptic impact feedback.\n *\n * Delegates to `@capacitor/haptics` `Haptics.impact()` when running on a native platform. On the\n * web (or any non-native platform) it is a no-op and resolves without doing anything.\n *\n * @remarks\n * This haptic side effect was previously bundled implicitly into toast/modal presentation. It has\n * been decoupled into this explicit function so callers opt in to the feedback deliberately, rather\n * than receiving it as a hidden side effect of presenting UI.\n *\n * @param style - The impact intensity to play. Defaults to {@link ImpactStyle.Light}.\n * @returns A promise that resolves once the feedback has been requested (immediately on the web).\n * @example\n * ```ts\n * import { ImpactStyle } from '@capacitor/haptics';\n *\n * // Light tap (default)\n * await kitImpact();\n *\n * // Stronger feedback, e.g. on a confirming action\n * await kitImpact(ImpactStyle.Heavy);\n * ```\n */\nexport const kitImpact = async (style: ImpactStyle = ImpactStyle.Light): Promise<void> => {\n if (Capacitor.isNativePlatform()) {\n await Haptics.impact({ style });\n }\n};\n","import { inject, Injectable } from '@angular/core';\nimport type { InputSignalWithTransform } from '@angular/core';\nimport type { ModalOptions, PopoverOptions, ToastOptions } from '@ionic/angular/standalone';\nimport { AlertController, ModalController, PopoverController, ToastController } from '@ionic/angular/standalone';\nimport type { PluginListenerHandle } from '@capacitor/core';\nimport { Capacitor } from '@capacitor/core';\nimport { Keyboard } from '@capacitor/keyboard';\nimport { KIT_OVERLAY_CONFIG } from './overlay-config';\nimport { kitImpact } from '../utils/haptics';\n\n/**\n * Options for {@link KitOverlayController.presentModal}.\n *\n * @remarks\n * Extends Ionic's `ModalOptions` but omits `component` and `componentProps`, which are passed as\n * dedicated arguments instead.\n */\nexport interface KitModalPresentOptions extends Omit<ModalOptions, 'component' | 'componentProps'> {\n /**\n * When `true`, expand the sheet to its maximum breakpoint while the native keyboard is shown.\n *\n * @remarks\n * Only has an effect on native platforms; ignored on the web.\n */\n watchKeyboard?: boolean;\n}\n\n/**\n * Optional static metadata a modal component may declare to make {@link KitOverlayController.presentModal}\n * type-safe in its dismiss data: the value passed to `dismiss()` is inferred from `modalReturn`.\n *\n * @remarks\n * Props do *not* need to be declared here — {@link KitOverlayController.presentModal} infers them directly\n * from the component's `input()` fields (see {@link ModalPropsOf}), so `input()` stays the single source of\n * truth and can never drift from a hand-written declaration. Only the dismiss-data shape, which has no\n * counterpart on the component, is declared — as a `declare static modalReturn` phantom type with no runtime\n * value, so it adds nothing to the bundle.\n *\n * @example\n * ```ts\n * export class EditPage {\n * declare static modalReturn: { saved: boolean };\n * readonly id = input.required<number>(); // props are inferred from here\n * readonly note = input<string>(); // default-less input() → optional prop\n * }\n *\n * // Caller — `id` is required (input.required), `note` optional; result is `{ saved: boolean } | undefined`:\n * const result = await overlay.presentModal(EditPage, { id: 1 });\n * ```\n */\nexport interface ModalMetadata<R = unknown> {\n /** Shape of the data the modal resolves with when dismissed. */\n modalReturn?: R;\n}\n\n/**\n * Write type of a signal `input()` field (unwraps `input.required`, `input()` and transform inputs).\n *\n * @remarks\n * Matched with `any` (not `unknown`): `InputSignalWithTransform`'s `TransformT` is contravariant, so\n * `InputSignal<number>` is not assignable to `InputSignalWithTransform<unknown, unknown>`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype InputWriteType<F> = F extends InputSignalWithTransform<any, infer W> ? W : never;\n\n/** Instance type of a component constructor; `never` for non-class components (string / HTMLElement refs). */\ntype InstanceOf<C> = C extends abstract new (...args: never[]) => infer I ? I : never;\n\n/** Keys of the `input()` signal fields on a component instance. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype InputFieldKeys<I> = { [K in keyof I]-?: I[K] extends InputSignalWithTransform<any, any> ? K : never }[keyof I];\n\n/**\n * Props object inferred from a component's `input()` fields. Required vs. optional is decided by the write\n * type: `input.required<T>()` / `input<T>(default)` yield `T` (no `undefined`) → required prop, while a\n * default-less `input<T>()` yields `T | undefined` → optional prop.\n *\n * @remarks\n * Angular's types cannot distinguish `input.required<T>()` from a defaulted `input<T>(default)` — both are\n * `InputSignal<T>` — so a defaulted input is (safely) treated as required. Declare inputs you want to omit at\n * the call site as default-less `input<T>()`.\n */\ntype ModalPropsOf<I> = { [K in InputFieldKeys<I> as undefined extends InputWriteType<I[K]> ? never : K]: InputWriteType<I[K]> } & {\n [K in InputFieldKeys<I> as undefined extends InputWriteType<I[K]> ? K : never]?: InputWriteType<I[K]>;\n};\n\n/** `input()` keys whose write type excludes `undefined` (required / defaulted inputs). Empty when none. */\ntype RequiredInputKeys<I> = { [K in InputFieldKeys<I>]: undefined extends InputWriteType<I[K]> ? never : K }[InputFieldKeys<I>];\n\n/**\n * Dismiss-data type inferred from a component's static {@link ModalMetadata.modalReturn}. A component that\n * declares no `modalReturn` resolves to `void`: it is treated as returning no dismiss data, so the compiler\n * rejects any attempt to read the result. Declare `modalReturn` on modals that do resolve with data.\n */\ntype ModalReturnOf<C> = C extends { modalReturn: infer R } ? R : void;\n\n/** Loose trailing args (optional, untyped props) — used when a component exposes no signal `input()` fields. */\ntype LooseModalPresentArgs = [componentProps?: ModalOptions['componentProps'], options?: KitModalPresentOptions];\n\n/**\n * Trailing `presentModal` args derived from a component's `input()` fields: props are inferred and typed via\n * {@link ModalPropsOf}. When the component declares at least one required input the props argument is required;\n * when every input is optional the props argument itself is optional. Components with no signal inputs (plain\n * classes, `@Input()`-decorator components, or non-class refs) fall back to loose, untyped props.\n */\ntype ModalPresentArgs<C, I = InstanceOf<C>> = [I] extends [never]\n ? LooseModalPresentArgs\n : [InputFieldKeys<I>] extends [never]\n ? LooseModalPresentArgs\n : [RequiredInputKeys<I>] extends [never]\n ? [componentProps?: ModalPropsOf<I>, options?: KitModalPresentOptions]\n : [componentProps: ModalPropsOf<I>, options?: KitModalPresentOptions];\n\n/**\n * Options for {@link KitOverlayController.alertClose}.\n */\nexport interface KitAlertCloseOptions {\n /** Alert header text. */\n header: string;\n /** Alert body message. */\n message: string;\n /** Optional alert sub-header text shown beneath the header. */\n subHeader?: string;\n}\n\n/**\n * Options for {@link KitOverlayController.alertConfirm}.\n *\n * @remarks\n * Extends {@link KitAlertCloseOptions} with the confirm-button text.\n */\nexport interface KitAlertConfirmOptions extends KitAlertCloseOptions {\n /**\n * Text for the OK (confirm) button.\n *\n * @remarks\n * Action-specific, so it is supplied by the caller rather than taken from the shared labels.\n */\n okText: string;\n}\n\n/**\n * Attach a native keyboard listener that grows the modal to its maximum breakpoint when the\n * keyboard appears.\n *\n * @param modal - the presented modal element to resize\n * @returns a listener handle; on non-native platforms a no-op handle whose `remove()` does nothing\n * @internal\n */\nconst watchModalKeyboard = async (modal: HTMLIonModalElement): Promise<PluginListenerHandle> => {\n if (!Capacitor.isNativePlatform()) {\n return { remove: async () => undefined };\n }\n return Keyboard.addListener('keyboardDidShow', () => modal.setCurrentBreakpoint(1));\n};\n\n/**\n * Ergonomic wrapper that consolidates Ionic's overlay controllers (Modal / Toast / Alert).\n *\n * @remarks\n * Folds the repetitive create → present → onDidDismiss sequence into single calls and returns the\n * relevant result directly. It holds no application-specific policy such as navigation; compose\n * those concerns on the consuming side.\n *\n * @example\n * ```ts\n * constructor(private readonly overlay: KitOverlayController) {}\n *\n * async edit(): Promise<void> {\n * const result = await this.overlay.presentModal(EditPage, { id: 1 });\n * if (result) {\n * await this.overlay.presentToast({ message: 'Saved' });\n * }\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class KitOverlayController {\n readonly #modalCtrl = inject(ModalController);\n readonly #popoverCtrl = inject(PopoverController);\n readonly #toastCtrl = inject(ToastController);\n readonly #alertCtrl = inject(AlertController);\n readonly #labels = inject(KIT_OVERLAY_CONFIG).labels;\n\n /**\n * Guards against stacking alerts: while one {@link alertClose} / {@link alertConfirm} is on screen,\n * a concurrent call resolves immediately (close: no-op, confirm: `false`) instead of presenting a\n * second alert on top of the first. Shared across both methods so a confirm cannot stack over a close.\n */\n #alertPresenting = false;\n\n /**\n * Present a modal and resolve with the data passed to its dismissal.\n *\n * @typeParam O - type of the data returned when the modal is dismissed\n * @param component - the component to render inside the modal\n * @param componentProps - props to pass to the modal component\n * @param options - additional modal options, including {@link KitModalPresentOptions.watchKeyboard}\n * @returns the dismiss data, or `undefined` when the modal is dismissed without data\n * @remarks\n * Presenting a modal triggers light native haptic feedback as an intentional kit UX choice,\n * consistent with {@link presentPopover} and {@link presentToast}.\n *\n * Props are inferred from the component's `input()` fields (see {@link ModalPropsOf}): required inputs\n * become required props, so the compiler rejects a call that omits them. The return type is inferred from\n * a static `modalReturn` (see {@link ModalMetadata}); a component with no `modalReturn` resolves to `void`\n * — the modal is treated as returning no dismiss data.\n * @example\n * ```ts\n * // Inferred — `id` required (input.required), `note` optional; result is `{ saved: boolean } | undefined`:\n * const result = await overlay.presentModal(EditPage, { id: 1 });\n * ```\n */\n presentModal<C extends ModalOptions['component']>(component: C, ...args: ModalPresentArgs<C>): Promise<ModalReturnOf<C> | undefined>;\n async presentModal(\n component: ModalOptions['component'],\n componentProps?: ModalOptions['componentProps'],\n options: KitModalPresentOptions = {},\n ): Promise<unknown> {\n void kitImpact();\n const { watchKeyboard, ...modalOptions } = options;\n const modal = await this.#modalCtrl.create({ component, componentProps, ...modalOptions });\n await modal.present();\n const handle = watchKeyboard ? await watchModalKeyboard(modal) : null;\n const { data } = await modal.onDidDismiss<unknown>();\n await handle?.remove();\n return data;\n }\n\n /**\n * Present a popover and resolve with the data passed to its dismissal.\n *\n * @typeParam O - type of the data returned when the popover is dismissed\n * @param component - the component to render inside the popover\n * @param componentProps - props to pass to the popover component\n * @param options - additional popover options (for example `event` to anchor it, or `cssClass`)\n * @returns the dismiss data, or `undefined` when the popover is dismissed without data\n * @remarks\n * Presenting a popover triggers light native haptic feedback as an intentional kit UX choice,\n * consistent with {@link presentModal} and {@link presentToast}.\n * @example\n * ```ts\n * const choice = await overlay.presentPopover<MenuChoice>(MenuPopover, { items }, { event });\n * ```\n */\n async presentPopover<O = unknown>(\n component: PopoverOptions['component'],\n componentProps?: PopoverOptions['componentProps'],\n options: Omit<PopoverOptions, 'component' | 'componentProps'> = {},\n ): Promise<O | undefined> {\n void kitImpact();\n const popover = await this.#popoverCtrl.create({ component, componentProps, ...options });\n await popover.present();\n const { data } = await popover.onDidDismiss<O>();\n return data;\n }\n\n /**\n * Present a toast using kit defaults that the caller may override.\n *\n * @remarks\n * Defaults to a bottom position, a 2000ms duration, a vertical swipe gesture, and a close button\n * from the configured labels; any of these can be overridden via `options`. Presenting a toast\n * also triggers light native haptic feedback as an intentional kit UX choice.\n *\n * Bottom is the fleet-wide default (top left the toast fighting the tab bar and the keyboard).\n * For a bottom toast with no explicit `positionAnchor`, if a visible `ion-tab-bar` is present the\n * toast is automatically anchored above it (Ionic places a bottom toast above its `positionAnchor`),\n * so the toast never sits behind the tabs. Avoiding the on-screen keyboard is handled by the native\n * keyboard resize — the anchored/bottom toast rides the shrinking viewport above the keyboard;\n * Ionic itself has no toast keyboard-avoidance option. An app can override either via `options`.\n *\n * @param options - Ionic toast options that override the kit defaults\n * @returns the presented toast element\n * @example\n * ```ts\n * await overlay.presentToast({ message: 'Copied to clipboard' });\n * ```\n */\n async presentToast(options: ToastOptions): Promise<HTMLIonToastElement> {\n void kitImpact();\n const merged: ToastOptions = {\n position: 'bottom',\n duration: 2000,\n buttons: [this.#labels.close],\n swipeGesture: 'vertical',\n ...options,\n };\n // Anchor a bottom toast above the tab bar when one is visibly present and the caller did not\n // set an explicit anchor, so the toast clears the tabs (and rides the keyboard-resized viewport).\n if (merged.position === 'bottom' && merged.positionAnchor === undefined) {\n const tabBar = document.querySelector('ion-tab-bar');\n if (tabBar && tabBar.getBoundingClientRect().height > 0) {\n merged.positionAnchor = tabBar as HTMLElement;\n }\n }\n const toast = await this.#toastCtrl.create(merged);\n await toast.present();\n return toast;\n }\n\n /**\n * Present a notification alert with a single \"close\" button and wait for it to be dismissed.\n *\n * @param options - alert content (header, message, optional sub-header)\n * @returns a Promise that resolves once the alert has been dismissed\n * @remarks\n * No-ops when another alert is already presenting (see {@link alertClose} / {@link alertConfirm}\n * stacking guard).\n * @example\n * ```ts\n * await overlay.alertClose({ header: 'Done', message: 'Your changes were saved.' });\n * ```\n */\n async alertClose(options: KitAlertCloseOptions): Promise<void> {\n if (this.#alertPresenting) {\n return;\n }\n this.#alertPresenting = true;\n try {\n const alert = await this.#alertCtrl.create({\n header: options.header,\n subHeader: options.subHeader,\n message: options.message,\n buttons: [this.#labels.close],\n });\n await alert.present();\n await alert.onWillDismiss();\n } finally {\n this.#alertPresenting = false;\n }\n }\n\n /**\n * Present a confirmation alert with cancel and OK buttons.\n *\n * @param options - alert content plus the OK button text via {@link KitAlertConfirmOptions.okText}\n * @returns `true` when the user presses OK, `false` otherwise (cancel or backdrop dismissal)\n * @example\n * ```ts\n * const ok = await overlay.alertConfirm({\n * header: 'Delete item?',\n * message: 'This cannot be undone.',\n * okText: 'Delete',\n * });\n * if (ok) {\n * await remove();\n * }\n * ```\n * @remarks\n * Returns `false` immediately when another alert is already presenting (see {@link alertClose} /\n * {@link alertConfirm} stacking guard).\n */\n async alertConfirm(options: KitAlertConfirmOptions): Promise<boolean> {\n if (this.#alertPresenting) {\n return false;\n }\n this.#alertPresenting = true;\n try {\n const alert = await this.#alertCtrl.create({\n header: options.header,\n subHeader: options.subHeader,\n message: options.message,\n buttons: [\n { text: this.#labels.cancel, role: 'cancel' },\n { text: options.okText, role: 'confirm' },\n ],\n });\n await alert.present();\n const { role } = await alert.onWillDismiss();\n return role === 'confirm';\n } finally {\n this.#alertPresenting = false;\n }\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport type { LoadingOptions } from '@ionic/angular/standalone';\nimport { LoadingController } from '@ionic/angular/standalone';\n\n/**\n * Reference-counted wrapper around Ionic's `LoadingController` that keeps at most one loading\n * indicator on screen across concurrent async work.\n *\n * @remarks\n * Each {@link presentLoading} increments a counter and each {@link dismissLoading} decrements it; the\n * indicator is presented on the `0 → 1` transition and dismissed on the `N → 0` transition. This\n * removes the flicker / \"stuck spinner\" bugs that come from every service calling\n * `LoadingController.create/dismiss` independently.\n *\n * All operations are serialized through an internal promise chain, so a `create → present` sequence\n * can never interleave with a concurrent `dismiss`. That is what makes the counter race-safe: a\n * dismiss that arrives while the indicator is still being presented runs *after* `present()` settles\n * and therefore tears the element down instead of leaving it orphaned on screen.\n *\n * Always pair every `presentLoading()` with exactly one `dismissLoading()` — a `try/finally` is the\n * safest shape.\n *\n * @example\n * ```ts\n * constructor(private readonly loading: KitLoadingController) {}\n *\n * async save(): Promise<void> {\n * await this.loading.presentLoading({ message: 'Saving…' });\n * try {\n * await this.api.save();\n * } finally {\n * await this.loading.dismissLoading();\n * }\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class KitLoadingController {\n readonly #loadingCtrl = inject(LoadingController);\n\n /** Outstanding {@link presentLoading} calls not yet balanced by {@link dismissLoading}. */\n #count = 0;\n\n /** The single presented loading element, or `null` when none is on screen. */\n #loading: HTMLIonLoadingElement | null = null;\n\n /**\n * Serializes present/dismiss operations. Each call chains onto this promise, runs after the\n * previous operation has fully settled, then reads {@link #count} and acts accordingly.\n */\n #queue: Promise<void> = Promise.resolve();\n\n /**\n * Show the loading indicator, or join the one already on screen.\n *\n * @param options - Ionic loading options; only applied by the call that actually creates the\n * indicator (the `0 → 1` transition). Ignored while an indicator is already present.\n * @returns a Promise that resolves once the indicator is on screen (or immediately when one already is)\n */\n async presentLoading(options: LoadingOptions = {}): Promise<void> {\n this.#count++;\n try {\n await this.#enqueue(async () => {\n // Create only on the transition into \"something is loading\"; concurrent callers ride the same\n // element. Re-check the count in case a dismiss already balanced this call while queued.\n if (this.#count > 0 && this.#loading === null) {\n const loading = await this.#loadingCtrl.create(options);\n await loading.present();\n this.#loading = loading;\n }\n });\n } catch (error) {\n // Roll back the reference this call took: a failed create/present must not leave the counter\n // elevated, otherwise a later cycle never reaches N → 0 and the spinner stays stuck on screen.\n this.#count--;\n throw error;\n }\n }\n\n /**\n * Release one reference; dismiss the indicator once the last reference is gone.\n *\n * @returns a Promise that resolves once the reference is released (and the indicator dismissed if\n * this was the last one). No-ops when the counter is already at zero.\n */\n async dismissLoading(): Promise<void> {\n if (this.#count === 0) {\n return;\n }\n this.#count--;\n await this.#enqueue(async () => {\n // Tear down only when the last consumer is gone. Because this runs after any in-flight\n // present() has settled (via the queue), there is never an orphaned loading element.\n if (this.#count === 0 && this.#loading !== null) {\n const loading = this.#loading;\n this.#loading = null;\n await loading.dismiss();\n }\n });\n }\n\n /**\n * Append `task` to the serialization chain and return its completion.\n *\n * @remarks\n * The stored chain swallows rejections so a single failing operation cannot wedge every future\n * overlay; the returned promise still rejects so the caller observes the error.\n */\n #enqueue(task: () => Promise<void>): Promise<void> {\n const run = this.#queue.then(task);\n this.#queue = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { AlertController } from '@ionic/angular/standalone';\nimport { KIT_OVERLAY_CONFIG } from './overlay-config';\n\n/**\n * Content for {@link KitReloadAlertController.present}.\n */\nexport interface KitReloadAlertOptions {\n /** Alert header text. */\n header: string;\n /** Alert body message. */\n message: string;\n /**\n * Text for the reload (confirm) button, e.g. \"リフレッシュ\".\n *\n * @remarks\n * Action-specific, so it is supplied by the caller rather than taken from the shared labels.\n * The cancel button uses the configured {@link KitLabels.cancel}.\n */\n okText: string;\n}\n\n/**\n * The fleet's canonical \"network error → offer to reload\" alert, as a stateful controller.\n *\n * @remarks\n * Consolidates the good-UX variant that had drifted across the fleet into one behavior:\n *\n * - **De-dup** — never stacks; a second {@link present} while an alert is already shown is a no-op.\n * - **Backdrop lock** — `backdropDismiss: false`, so a critical network error can't be dismissed by\n * an accidental backdrop tap; the user consciously chooses cancel or reload.\n * - **Auto-dismiss on reconnect** — the presented alert is tracked, so {@link dismiss} (called from a\n * later successful response) clears a now-stale error alert instead of leaving it on screen.\n * - **Reload on confirm** — the confirm button calls `location.reload()`.\n *\n * All user-facing text is supplied by the caller so the kit stays free of any hardcoded i18n; the\n * cancel button reuses {@link KitOverlayConfig.labels}. Because it performs navigation\n * (`location.reload()`) and holds state, it is a dedicated controller rather than part of\n * {@link KitOverlayController}, which stays free of navigation policy.\n *\n * @example\n * ```ts\n * // In an HTTP interceptor:\n * const reload = inject(KitReloadAlertController);\n * // ...on a network-class error while connected:\n * await reload.present({ header: 'ネットワークエラー', message: `…(${status})`, okText: 'リフレッシュ' });\n * // ...on any later successful response:\n * await reload.dismiss();\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class KitReloadAlertController {\n readonly #alertCtrl = inject(AlertController);\n readonly #labels = inject(KIT_OVERLAY_CONFIG).labels;\n #alert: HTMLIonAlertElement | null = null;\n\n /**\n * Present the reload alert, unless one is already on screen.\n *\n * @param options - alert content plus the reload-button text\n * @returns a Promise that resolves once the alert has been presented (or immediately if suppressed)\n */\n async present(options: KitReloadAlertOptions): Promise<void> {\n // この controller 経由でも直書き ion-alert でも、多重表示しない。\n if (this.#alert || document.querySelector('ion-alert')) {\n return;\n }\n const alert = await this.#alertCtrl.create({\n header: options.header,\n message: options.message,\n backdropDismiss: false,\n buttons: [\n { text: this.#labels.cancel, role: 'cancel' },\n {\n text: options.okText,\n handler: () => {\n location.reload();\n },\n },\n ],\n });\n this.#alert = alert;\n void alert.onDidDismiss().then(() => {\n // 別の present で置き換わっていない限り、追跡を解除する。\n if (this.#alert === alert) {\n this.#alert = null;\n }\n });\n await alert.present();\n }\n\n /**\n * Dismiss the tracked reload alert if one is showing.\n *\n * @remarks\n * Typically called from a later successful response so a stale \"network error\" alert clears once\n * connectivity is restored. A no-op when nothing is showing.\n *\n * @returns a Promise that resolves once the alert has been dismissed (or immediately if none)\n */\n async dismiss(): Promise<void> {\n const alert = this.#alert;\n this.#alert = null;\n await alert?.dismiss();\n }\n}\n","import type { AlertController } from '@ionic/angular/standalone';\n\n/**\n * Content for {@link kitPresentAuthFailedAlert}.\n */\nexport interface KitAuthFailedAlertOptions {\n /** Alert header, e.g. \"ログインできませんでした\". */\n header: string;\n /** Optional sub-header; typically the short server error code/name. */\n subHeader?: string;\n /** Alert body message; typically the server-provided detail. */\n message: string;\n /** Text for the single close button, e.g. \"閉じる\". */\n closeText: string;\n}\n\n/**\n * Present the fleet's canonical \"sign-in / token exchange failed\" alert.\n *\n * @remarks\n * Folds together the alert every token-exchange app duplicated verbatim when a startup re-login\n * fails: an informative alert (header + optional server error as sub-header + detail message) with a\n * single close button that reloads the app (`location.reload()`) so the user restarts cleanly. The\n * caller is still responsible for signing the user out around this call.\n *\n * All user-facing text is supplied by the caller so the kit stays free of any hardcoded i18n. Kept\n * as a standalone helper (taking the `AlertController`) rather than a method on\n * {@link KitOverlayController}, which holds no navigation policy such as `location.reload()`.\n *\n * @param alertCtrl - Ionic's `AlertController`\n * @param options - alert content plus the close-button text\n * @returns a Promise that resolves once the alert has been presented\n * @example\n * ```ts\n * onAuthorized: async () => {\n * const logged = await auth.tokenLogin().catch(async (e) => {\n * await kitPresentAuthFailedAlert(alertCtrl, {\n * header: 'ログインできませんでした',\n * subHeader: e.error.error,\n * message: e.error.detail,\n * closeText: '閉じる',\n * });\n * await auth.signOut();\n * return undefined;\n * });\n * // ...\n * };\n * ```\n */\nexport const kitPresentAuthFailedAlert = async (\n alertCtrl: AlertController,\n options: KitAuthFailedAlertOptions,\n): Promise<void> => {\n const alert = await alertCtrl.create({\n header: options.header,\n subHeader: options.subHeader,\n message: options.message,\n buttons: [\n {\n text: options.closeText,\n role: 'cancel',\n handler: () => {\n location.reload();\n },\n },\n ],\n });\n await alert.present();\n};\n","import { ActionSheetController } from '@ionic/angular/standalone';\n\n/** One selectable language in {@link kitPresentLanguageActionSheet}. */\nexport interface KitLanguageOption {\n /** Button label shown to the user (e.g. `English`, `日本語`). */\n readonly text: string;\n /** Locale identifier returned when this option is chosen (e.g. `en-US`, `ja`). */\n readonly data: string;\n}\n\n/**\n * Options for {@link kitPresentLanguageActionSheet}.\n *\n * @remarks\n * The kit ships no strings or URLs of its own: labels, the locale list, and the redirect-URL mapping\n * are all supplied by the caller, so a multilingual app passes `$localize`-resolved text and its own\n * per-locale build paths.\n */\nexport interface KitLanguageActionSheetOptions {\n /** Action-sheet header text. */\n readonly header: string;\n /** Selectable languages, in display order. */\n readonly locales: readonly KitLanguageOption[];\n /** Text for the cancel button. */\n readonly cancelText: string;\n /** The currently active locale; selecting the same value is a no-op. Normalize before passing. */\n readonly currentLocale: string;\n /** The current in-app path (e.g. `router.url`), stashed so the app can restore it after the reload. */\n readonly currentPath: string;\n /** `sessionStorage` key under which {@link currentPath} is stored. */\n readonly pathnameStorageKey: string;\n /** Maps a chosen locale to the URL to navigate to (the app's per-locale build entry point). */\n readonly buildRedirectUrl: (locale: string) => string;\n /** Gate for the redirect — pass `false` (e.g. outside production) to present without navigating. */\n readonly enabled: boolean;\n}\n\n/**\n * Present a language picker and, on a new selection, reload the app at that locale's entry point.\n *\n * @remarks\n * A plain function (the `ActionSheetController` is passed in, so nothing is injected) that unifies the\n * language-switch flow duplicated across apps. On a changed selection while {@link enabled} it stashes\n * the current path in `sessionStorage` (so the app can return the user to where they were), records the\n * chosen locale in `localStorage` under `'locale'`, and calls `window.location.replace()` with the\n * app-provided URL. Because it performs navigation, it is a standalone helper rather than part of a\n * controller (mirroring `kitPresentReloadAlert` / `kitPresentAuthFailedAlert`). Centralizing it means a\n * future improvement to the switch flow lands in every app at once.\n *\n * @param actionSheetCtrl - the Ionic `ActionSheetController`\n * @param options - labels, locale list, and redirect configuration; see {@link KitLanguageActionSheetOptions}\n * @returns a Promise that resolves once presented (and, on a new selection, after the reload is triggered)\n * @example\n * ```ts\n * await kitPresentLanguageActionSheet(inject(ActionSheetController), {\n * header: $localize`言語設定`,\n * locales: [{ text: 'English', data: 'en-US' }, { text: '日本語', data: 'ja' }],\n * cancelText: $localize`キャンセル`,\n * currentLocale: normalizedLocale,\n * currentPath: this.#router.url,\n * pathnameStorageKey: StorageKeyEnum.pathnameBeforeRedirect,\n * buildRedirectUrl: (locale) => location.origin + (localePath[locale.toLowerCase()] ?? '/index.html'),\n * enabled: environment.production,\n * });\n * ```\n */\nexport const kitPresentLanguageActionSheet = async (\n actionSheetCtrl: ActionSheetController,\n options: KitLanguageActionSheetOptions,\n): Promise<void> => {\n const actionSheet = await actionSheetCtrl.create({\n header: options.header,\n buttons: [\n ...options.locales.map((locale) => ({ text: locale.text, data: locale.data })),\n { text: options.cancelText, role: 'cancel' },\n ],\n });\n await actionSheet.present();\n\n const { data } = await actionSheet.onDidDismiss();\n if (options.enabled && data && data !== options.currentLocale) {\n sessionStorage.setItem(options.pathnameStorageKey, options.currentPath);\n localStorage.setItem('locale', data);\n window.location.replace(options.buildRedirectUrl(data));\n }\n};\n","import type { OnInit } from '@angular/core';\nimport { Directive, ElementRef, inject } from '@angular/core';\nimport { Capacitor } from '@capacitor/core';\n\n/**\n * Work around iOS `ion-input` autofill values not propagating to the Angular form model.\n *\n * On iOS, when the browser autofills an `ion-input` (for example a saved password), the value\n * is written to the underlying native `<input>` element but the corresponding `change` event is\n * not forwarded to the host `ion-input`, so the Angular form control (and `ngModel`) never sees\n * the autofilled value. This directive listens for the first `change` event on the inner input\n * element and mirrors its value back onto the host element, restoring two-way binding.\n *\n * Apply it to any `ion-input` that participates in a form and may be autofilled by attaching the\n * `rdlaboAutofill` attribute.\n *\n * @remarks\n * The directive is a no-op on every platform other than iOS, where the value already propagates\n * correctly. A short timeout is used because `ion-input` creates its inner `<input>` element\n * asynchronously after the host element is initialized.\n *\n * @example\n * ```html\n * <ion-input rdlaboAutofill type=\"password\" [(ngModel)]=\"password\"></ion-input>\n * ```\n */\n@Directive({\n selector: '[rdlaboAutofill]',\n standalone: true,\n})\nexport class KitAutofillDirective implements OnInit {\n readonly #el = inject(ElementRef);\n\n constructor() {}\n\n /**\n * Register the iOS autofill workaround once the directive is initialized.\n *\n * Returns immediately on non-iOS platforms. On iOS, after a short delay it attaches a one-shot,\n * passive `change` listener to the inner `<input>` element that `ion-input` renders, copying the\n * autofilled value back onto the host element so the Angular form model stays in sync. Any error\n * while locating the inner input (for example if the element is not yet present) is swallowed.\n *\n * @returns Nothing.\n */\n ngOnInit(): void {\n if (Capacitor.getPlatform() !== 'ios') {\n return;\n }\n setTimeout(() => {\n try {\n this.#el.nativeElement.children[0].addEventListener(\n 'change',\n (e: Event) => {\n this.#el.nativeElement.value = (e.target as HTMLInputElement).value;\n },\n {\n capture: false,\n once: true,\n passive: true,\n },\n );\n } catch {\n /* empty */\n }\n }, 100); // Need some time for the ion-input to create the input element\n }\n}\n","import type { ElementRef } from '@angular/core';\nimport { Capacitor } from '@capacitor/core';\nimport type { PluginListenerHandle } from '@capacitor/core';\nimport { Keyboard } from '@capacitor/keyboard';\n\n/**\n * How {@link kitKeyboardInit} adjusts the target element when the native keyboard appears.\n *\n * - `transform` — CSS `translateY(-keyboardHeight + safeAreaBottom)` for a smooth iOS animation\n * (typical for an `ion-footer`).\n * - `offset` — set the `--offset-bottom` custom property to the negative keyboard height.\n * - `keyboard-offset` — set the `--padding-bottom` custom property to the keyboard height.\n */\nexport type KitKeyboardAdjust = 'transform' | 'offset' | 'keyboard-offset';\n\nconst keyboardWillShow = (elementRef: ElementRef, type: KitKeyboardAdjust): Promise<PluginListenerHandle> =>\n Keyboard.addListener('keyboardWillShow', (info) => {\n if (Capacitor.getPlatform() === 'android') {\n if (elementRef.nativeElement.tagName === 'ION-FOOTER' && type === 'transform') {\n // https://github.com/ionic-team/ionic-framework/blob/main/core/src/components/footer/footer.tsx\n elementRef.nativeElement.classList.remove('footer-toolbar-padding');\n }\n return;\n }\n\n elementRef.nativeElement.classList.add('show-keyboard');\n // SSR-safe: this callback only runs on a native keyboard event, so the global `document` /\n // `window` are never touched on the server (kitKeyboardInit returns early when not native).\n const bodyStyleDeclaration = window.getComputedStyle(document.querySelector('body') as Element);\n const safeArea = parseInt(bodyStyleDeclaration.getPropertyValue('--ion-safe-area-bottom'), 10);\n\n if (type === 'transform') {\n elementRef.nativeElement.style.transition = 'transform 420ms';\n elementRef.nativeElement.style.willChange = 'transform';\n requestAnimationFrame(\n () => (elementRef.nativeElement.style.transform = `translateY(${info.keyboardHeight * -1 + safeArea}px)`),\n );\n } else if (type === 'offset') {\n requestAnimationFrame(() => {\n const keyboardOffset = elementRef.nativeElement.style.getPropertyValue('--keyboard-offset');\n if (!keyboardOffset || parseInt(keyboardOffset, 10) === 0) {\n elementRef.nativeElement.style.setProperty('--offset-bottom', `${info.keyboardHeight * -1}px`);\n }\n });\n } else {\n requestAnimationFrame(() => {\n const keyboardOffset = elementRef.nativeElement.style.getPropertyValue('--keyboard-offset');\n if (!keyboardOffset || parseInt(keyboardOffset, 10) === 0) {\n elementRef.nativeElement.style.setProperty('--padding-bottom', `${info.keyboardHeight}px`);\n }\n });\n }\n });\n\nconst keyboardWillHide = (elementRef: ElementRef, type: KitKeyboardAdjust): Promise<PluginListenerHandle> =>\n Keyboard.addListener('keyboardWillHide', () => {\n if (Capacitor.getPlatform() === 'android') {\n if (elementRef.nativeElement.tagName === 'ION-FOOTER' && type === 'transform') {\n elementRef.nativeElement.classList.add('footer-toolbar-padding');\n }\n return;\n }\n\n elementRef.nativeElement.classList.remove('show-keyboard');\n\n if (type === 'transform') {\n elementRef.nativeElement.style.transition = 'transform 0ms';\n elementRef.nativeElement.style.transform = `translateY(0px)`;\n elementRef.nativeElement.style.willChange = 'transform';\n } else if (type === 'offset') {\n elementRef.nativeElement.style.setProperty('--offset-bottom', '0px');\n } else {\n elementRef.nativeElement.style.setProperty('--padding-bottom', '0px');\n }\n });\n\nconst keyboardDidShow = (elementRef: ElementRef): Promise<PluginListenerHandle> =>\n Keyboard.addListener('keyboardDidShow', () => {\n elementRef.nativeElement.style.willChange = 'auto';\n });\n\nconst keyboardDidHide = (elementRef: ElementRef): Promise<PluginListenerHandle> =>\n Keyboard.addListener('keyboardDidHide', () => {\n elementRef.nativeElement.style.willChange = 'auto';\n });\n\n/**\n * Register native keyboard listeners that reposition an element when the keyboard shows/hides.\n *\n * @remarks\n * A plain function — no DI needed (it reads the platform from `Capacitor` and uses the global\n * `document`), so a component calls it directly instead of injecting a controller. SSR-safe: the\n * global `document` / `window` are only read inside native keyboard-event callbacks, which never\n * fire on the server — the `Capacitor.isNativePlatform()` guard returns `[]` first, and nothing is\n * touched at module load. A no-op on non-native platforms (returns `[]`). On native it handles the\n * iOS/Android differences (Android\n * only toggles the `footer-toolbar-padding` class on an `ion-footer` for the `transform` mode,\n * working around an Ionic footer bug). The caller owns the returned handles and must `remove()`\n * them when the view is destroyed.\n *\n * @param elementRef - The element to reposition (e.g. an `ion-footer`).\n * @param type - The adjustment strategy; see {@link KitKeyboardAdjust}.\n * @returns The registered listener handles (empty on non-native platforms).\n * @example\n * ```ts\n * export class ComposePage {\n * readonly #footer = viewChild.required<ElementRef>('footer');\n * #handles: PluginListenerHandle[] = [];\n *\n * async ngAfterViewInit() {\n * this.#handles = await kitKeyboardInit(this.#footer(), 'transform');\n * }\n * ngOnDestroy() {\n * this.#handles.forEach((h) => h.remove());\n * }\n * }\n * ```\n */\nexport const kitKeyboardInit = async (elementRef: ElementRef, type: KitKeyboardAdjust): Promise<PluginListenerHandle[]> => {\n if (!Capacitor.isNativePlatform()) {\n return [];\n }\n return [\n await keyboardWillShow(elementRef, type),\n await keyboardWillHide(elementRef, type),\n await keyboardDidShow(elementRef),\n await keyboardDidHide(elementRef),\n ];\n};\n","import type { EnvironmentProviders } from '@angular/core';\nimport { InjectionToken, makeEnvironmentProviders } from '@angular/core';\n\n/**\n * Theme configuration injected via `provideKitTheme()` and consumed by `KitThemeController`.\n *\n * @remarks\n * All fields are required; the consuming application supplies a complete configuration so every app\n * in the fleet has the same shape. The class lists absorb the per-app CSS drift: the kit toggles\n * `darkClasses` on when dark and `lightClasses` on when light, so an app that only uses Ionic's\n * palette passes `darkClasses: ['ion-palette-dark'], lightClasses: []`, while an app with an extra\n * design-system palette adds its own classes (e.g. `darkClasses: ['ion-palette-dark', 'a2ui-dark']`,\n * `lightClasses: ['a2ui-light']`).\n */\nexport interface KitThemeConfig {\n /** Key under which the chosen theme (`'light'` | `'dark'`) is persisted via `KitStorageService`. */\n readonly storageKey: string;\n /** Classes toggled **on** the document element when the dark theme is active. */\n readonly darkClasses: readonly string[];\n /** Classes toggled **on** the document element when the light theme is active. */\n readonly lightClasses: readonly string[];\n}\n\n/**\n * Injection token carrying the {@link KitThemeConfig} for `KitThemeController`.\n *\n * @remarks\n * Provide it through {@link provideKitTheme} rather than registering it directly.\n */\nexport const KIT_THEME_CONFIG = new InjectionToken<KitThemeConfig>('@rdlabo/ionic-angular-kit:theme');\n\n/**\n * Wire `KitThemeController` into the application.\n *\n * @param config - theme configuration: the storage key and the light/dark class lists\n * @returns environment providers to add to the application's provider list\n * @example\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideKitTheme({\n * storageKey: StorageKeyEnum.theme,\n * darkClasses: ['ion-palette-dark', 'a2ui-dark'],\n * lightClasses: ['a2ui-light'],\n * }),\n * ],\n * });\n * ```\n */\nexport const provideKitTheme = (config: KitThemeConfig): EnvironmentProviders =>\n makeEnvironmentProviders([{ provide: KIT_THEME_CONFIG, useValue: config }]);\n","import { DOCUMENT, inject, Injectable } from '@angular/core';\nimport { Capacitor } from '@capacitor/core';\nimport { StatusBar, Style } from '@capacitor/status-bar';\nimport { BehaviorSubject } from 'rxjs';\n\nimport { KitStorageService } from '../storage/kit-storage.service';\nimport { KIT_THEME_CONFIG } from './theme-config';\n\n/** The active theme mode. */\nexport type KitThemeMode = 'light' | 'dark';\n\n/**\n * Light/dark theme controller: persists the user's choice, follows the OS setting until the user\n * overrides it, toggles the configured palette classes, and syncs the native Android status bar.\n *\n * @remarks\n * Consolidates the theme logic that had drifted across the fleet into one behavior. Notably it fixes\n * a latent leak in one variant where the system-theme listener stayed registered after a manual\n * toggle: {@link changeTheme} always detaches the listener via {@link removeEventListener} before\n * applying the forced theme, so a later OS change can no longer silently flip an app the user pinned.\n *\n * - **Persistence** — the chosen mode is stored via {@link KitStorageService} under the configured key.\n * - **Follow OS until overridden** — on boot with nothing stored, it tracks\n * `prefers-color-scheme` (idempotent registration); once the user calls {@link changeTheme} it stops\n * following and honors the explicit choice.\n * - **Class toggling** — toggles {@link KitThemeConfig.darkClasses} on when dark and\n * {@link KitThemeConfig.lightClasses} on when light, absorbing per-app CSS differences via config.\n * - **Native status bar** — on Android native only, mirrors the Ionic behavior of setting the status\n * bar style to match (iOS derives it from the web content, so it is intentionally left untouched).\n *\n * Subscribe to {@link themeSubject} to reflect the current mode in the UI (e.g. a settings toggle).\n *\n * @example\n * ```ts\n * // On boot (app.component):\n * inject(KitThemeController).setDefaultThemeMode();\n *\n * // From a settings toggle:\n * const theme = inject(KitThemeController);\n * theme.themeSubject.subscribe((mode) => this.isDark.set(mode === 'dark'));\n * theme.changeTheme(true);\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class KitThemeController {\n readonly #storage = inject(KitStorageService);\n readonly #document = inject(DOCUMENT);\n readonly #config = inject(KIT_THEME_CONFIG);\n\n /**\n * Emits the active theme, seeded with `'light'`.\n *\n * @remarks\n * A `BehaviorSubject`, so a late subscriber immediately receives the current mode; it emits again\n * on every {@link setDefaultThemeMode} / {@link changeTheme} and on OS theme changes while following.\n */\n readonly themeSubject = new BehaviorSubject<KitThemeMode>('light');\n\n #prefersDark?: MediaQueryList;\n #onSystemThemeChange?: (e: MediaQueryListEvent) => void;\n\n /**\n * Apply the persisted theme, or start following the OS setting when nothing is stored yet.\n *\n * @remarks\n * Call once on boot (e.g. from `app.component`).\n *\n * @returns a Promise that resolves once the initial theme has been applied\n */\n async setDefaultThemeMode(): Promise<void> {\n const stored = await this.#storage.get<KitThemeMode>(this.#config.storageKey);\n if (stored) {\n // 保存済みの選択を強制し、OS 追従は解除する。\n this.#unwatchSystemTheme();\n return this.#applyTheme(stored === 'dark');\n }\n\n // 未保存 → OS の設定に追従する。\n this.#watchSystemTheme();\n }\n\n /**\n * Force a theme, persist it, and stop following the OS setting.\n *\n * @param isDark - `true` for the dark theme, `false` for light\n * @returns a Promise that resolves once the theme has been persisted and applied\n */\n async changeTheme(isDark: boolean): Promise<void> {\n this.#unwatchSystemTheme();\n await this.#storage.set(this.#config.storageKey, isDark ? 'dark' : 'light');\n await this.#applyTheme(isDark);\n }\n\n #watchSystemTheme(): void {\n if (this.#prefersDark) {\n // 既に監視中なら二重登録しない(冪等)。\n return;\n }\n this.#prefersDark = window.matchMedia('(prefers-color-scheme: dark)');\n this.#onSystemThemeChange = (e) => void this.#applyTheme(e.matches);\n this.#prefersDark.addEventListener('change', this.#onSystemThemeChange, { passive: true });\n void this.#applyTheme(this.#prefersDark.matches);\n }\n\n #unwatchSystemTheme(): void {\n if (this.#prefersDark && this.#onSystemThemeChange) {\n this.#prefersDark.removeEventListener('change', this.#onSystemThemeChange);\n }\n this.#prefersDark = undefined;\n this.#onSystemThemeChange = undefined;\n }\n\n async #applyTheme(isDark: boolean): Promise<void> {\n if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'android') {\n await StatusBar.setStyle({ style: isDark ? Style.Dark : Style.Light });\n }\n const root = this.#document.documentElement;\n for (const cls of this.#config.darkClasses) {\n root.classList.toggle(cls, isDark);\n }\n for (const cls of this.#config.lightClasses) {\n root.classList.toggle(cls, !isDark);\n }\n this.themeSubject.next(isDark ? 'dark' : 'light');\n }\n}\n","import { Capacitor } from '@capacitor/core';\nimport { Preferences } from '@capacitor/preferences';\nimport { InAppReview } from '@capacitor-community/in-app-review';\n\n/**\n * Options for {@link kitRequestReview}.\n */\nexport interface KitRequestReviewOptions {\n /**\n * Key under which the timestamp of the last review request is stored (via `@capacitor/preferences`).\n *\n * @remarks\n * Supplied by the caller so the kit ships no storage keys of its own; each app passes its own enum\n * value.\n */\n readonly storageKey: string;\n /**\n * Minimum number of months between review prompts.\n *\n * @remarks\n * A prompt is only shown when this much time has elapsed since the last one (or when there is no\n * record yet), so the OS review dialog is never nagged repeatedly.\n */\n readonly throttleMonths: number;\n}\n\n/**\n * Request the native in-app review dialog, throttled so the user is prompted at most once per window.\n *\n * @remarks\n * A plain function — no DI needed (`@capacitor/preferences`, `@capacitor-community/in-app-review` and\n * `Capacitor` are all static), so the caller invokes it directly and passes its own config rather\n * than injecting a controller. A no-op on non-native platforms. When enough time has elapsed since\n * the last prompt (per {@link KitRequestReviewOptions.throttleMonths}, tracked under\n * {@link KitRequestReviewOptions.storageKey}), it briefly waits for the app to settle, calls\n * `InAppReview.requestReview()`, and records the new timestamp. The wait/throttle/record sequence\n * was previously copy-pasted verbatim across the fleet; centralizing it means a single place to tune\n * the prompt cadence.\n *\n * @param options - the storage key and throttle window; see {@link KitRequestReviewOptions}\n * @returns a Promise that resolves once the request has been made (or immediately if throttled / on web)\n * @example\n * ```ts\n * await kitRequestReview({ storageKey: StorageEnum.lastRequestRate, throttleMonths: 3 });\n * ```\n */\nexport const kitRequestReview = async (options: KitRequestReviewOptions): Promise<void> => {\n if (!Capacitor.isNativePlatform()) {\n return;\n }\n\n await new Promise<void>((resolve) => setTimeout(() => resolve(), 1000));\n const threshold = new Date();\n threshold.setMonth(threshold.getMonth() - options.throttleMonths);\n\n const { value } = await Preferences.get({ key: options.storageKey });\n if (!value || new Date(Number(value)).getTime() < threshold.getTime()) {\n await InAppReview.requestReview();\n await Preferences.set({ key: options.storageKey, value: new Date().getTime().toString() });\n }\n};\n","import { Capacitor } from '@capacitor/core';\nimport type { BRLMPrinterLabelName, BRLMPrinterModelName, BRLMPrintOptions } from '@rdlabo/capacitor-brotherprint';\nimport {\n BRLMPrinterCustomPaperType,\n BRLMPrinterCustomPaperUnit,\n BRLMPrinterHalftone,\n BRLMPrinterHorizontalAlignment,\n BRLMPrinterImageRotation,\n BRLMPrinterPrintQuality,\n BRLMPrinterScaleMode,\n BRLMPrinterVerticalAlignment,\n} from '@rdlabo/capacitor-brotherprint';\nimport domtoimage from 'dom-to-image-more';\n\n/**\n * Rotate a base64 image 90°, returning a new base64 data URL of the same MIME type.\n *\n * @remarks\n * Pure DOM/canvas work — no DI. Used before sending a label to the printer when the artwork must be\n * turned to match the tape orientation. Extracted verbatim from the fleet's printer services so the\n * canvas handling lives in one place.\n *\n * @param imageData - a base64 data URL (e.g. `data:image/png;base64,...`)\n * @returns a Promise resolving to the rotated image as a base64 data URL\n */\nexport const kitRotationImage = async (imageData: string): Promise<string> => {\n const imgType = imageData.substring(5, imageData.indexOf(';'));\n\n const image = new Image();\n const loaded = new Promise<void>((resolve) => {\n image.onload = () => resolve();\n });\n image.src = imageData;\n await loaded;\n\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n\n canvas.width = image.height;\n canvas.height = image.width;\n\n ctx!.rotate((90 * Math.PI) / 180);\n ctx!.translate(0, -image.height);\n ctx!.drawImage(image, 0, 0, image.width, image.height);\n\n return canvas.toDataURL(imgType);\n};\n\n/** Options for {@link kitDomToPng}. */\nexport interface KitDomToPngOptions {\n /** When `true`, the rendered PNG is rotated 90° via {@link kitRotationImage}. Defaults to `false`. */\n readonly rotate?: boolean;\n /** Rendering scale passed to `dom-to-image-more`. Defaults to `3` (the fleet's print resolution). */\n readonly scale?: number;\n}\n\n/**\n * Render a DOM element to a base64 PNG for label printing, with the fleet's device-specific fixes.\n *\n * @remarks\n * Pure function — no DI (reads the platform from `Capacitor`, uses the global `document`), so the\n * caller presents its own loading UI around it. Centralizes the hard-won device quirks: on iOS it\n * pads width/height by 2px (otherwise the bottom is clipped), on Android it does not (the padding\n * introduces a black line). Retries the `dom-to-image-more` render up to 10 times because the first\n * pass can occasionally return empty. This is exactly the kind of plumbing where a future fix should\n * land in every app at once.\n *\n * @param element - the element to rasterize (e.g. the label preview host)\n * @param options - rendering options; see {@link KitDomToPngOptions}\n * @returns a Promise resolving to the PNG as a base64 data URL (empty string if every attempt failed)\n * @example\n * ```ts\n * const loading = await this.#loadingCtrl.create({ message: this.text.generating });\n * await loading.present();\n * const png = await kitDomToPng(this.preview().nativeElement, { rotate: true });\n * await loading.dismiss();\n * ```\n */\nexport const kitDomToPng = async (element: HTMLElement, options?: KitDomToPngOptions): Promise<string> => {\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n const { clientHeight, clientWidth } = element;\n\n // デバイス毎の問題解決のため、px 調整。\n // iOS: ないと下が途切れる。Android: あると黒線が入る。\n const addClient = Capacitor.getPlatform() === 'ios' ? 2 : 0;\n\n let dataUrl = '';\n for (let i = 0; i < 10; i++) {\n const url = await domtoimage.toPng(element, {\n width: clientWidth + addClient,\n height: clientHeight + addClient,\n scale: options?.scale ?? 3,\n copyDefaultStyles: false,\n });\n if (url) {\n dataUrl = url;\n break;\n }\n }\n\n return options?.rotate ? kitRotationImage(dataUrl) : dataUrl;\n};\n\n/** Parameters for {@link kitBuildBrotherPrintSettings}. */\nexport interface KitBrotherPrintSettingsParams {\n /** The target printer model. */\n readonly modelName: BRLMPrinterModelName;\n /** The label artwork as a base64 data URL (the `data:...,` prefix is stripped internally). */\n readonly printBase64: string;\n /** The selected label/paper (its `W<width>H<height>` code drives the tape dimensions). */\n readonly label: BRLMPrinterLabelName;\n /** Number of copies to print. Passed by the caller (apps differ: some use the print option, some fix 1). */\n readonly numberOfCopies: number;\n /** Halftone threshold for the print. */\n readonly halftoneThreshold: number;\n}\n\n/**\n * Assemble the Brother `BRLMPrintOptions` for a die-cut label print, minus the transport fields.\n *\n * @remarks\n * Pure function — no DI. Centralizes the fleet's canonical print settings (fit-page scale, centered,\n * best quality, threshold halftone, 2mm/1mm margins, `gapLength` 2.0) and the tape sizing derived\n * from the label's `W<width>H<height>` code. The caller merges the printer's `port` / `channelInfo`\n * onto the result before calling `BrotherPrint.printImage()`, so channel selection and loading UI stay\n * in the app.\n *\n * @param params - model, artwork, label, copies, and halftone threshold; see {@link KitBrotherPrintSettingsParams}\n * @returns the `BRLMPrintOptions` ready to be spread with `{ port, channelInfo }`\n * @example\n * ```ts\n * const settings = kitBuildBrotherPrintSettings({\n * modelName, printBase64, label, numberOfCopies: printOptions.printNum, halftoneThreshold: printOptions.halftoneThreshold,\n * });\n * await BrotherPrint.printImage({ ...settings, port: channel.port, channelInfo: channel.channelInfo });\n * ```\n */\nexport const kitBuildBrotherPrintSettings = (params: KitBrotherPrintSettingsParams): BRLMPrintOptions => {\n const startPoint = params.printBase64.indexOf(',');\n const tapeSize = params.label.match(/W(\\d+)H(\\d+)/);\n const tapeWidth = tapeSize && tapeSize.length >= 2 ? parseInt(tapeSize[1], 10) : 0;\n const tapeLength = tapeSize && tapeSize.length >= 3 ? parseInt(tapeSize[2], 10) : 0;\n\n // `BRLMPrintOptions` is a `QL | TD` union; a die-cut label legitimately carries fields from both\n // groups, so the object is composed via spreads to bypass the union's excess-property checks — the\n // same technique the source printer services used.\n return {\n ...{\n modelName: params.modelName,\n encodedImage: params.printBase64.slice(startPoint + 1),\n numberOfCopies: params.numberOfCopies,\n autoCut: true,\n\n scaleMode: BRLMPrinterScaleMode.FitPageAspect,\n imageRotation: BRLMPrinterImageRotation.Rotate0,\n verticalAlignment: BRLMPrinterVerticalAlignment.Center,\n horizontalAlignment: BRLMPrinterHorizontalAlignment.Center,\n printQuality: BRLMPrinterPrintQuality.Best,\n },\n ...{\n labelName: params.label,\n },\n ...{\n paperType: BRLMPrinterCustomPaperType.dieCutPaper,\n paperUnit: BRLMPrinterCustomPaperUnit.mm,\n halftone: BRLMPrinterHalftone.Threshold,\n halftoneThreshold: params.halftoneThreshold,\n tapeWidth: Number(tapeWidth.toFixed(1)),\n tapeLength: Number(tapeLength.toFixed(1)),\n gapLength: 2.0,\n\n marginTop: 1.0,\n marginRight: 2.0,\n marginBottom: 1.0,\n marginLeft: 2.0,\n\n paperMarkPosition: 0,\n paperMarkLength: 0,\n },\n };\n};\n","import type { EnvironmentProviders } from '@angular/core';\nimport { inject, InjectionToken, makeEnvironmentProviders } from '@angular/core';\nimport type { CanActivateFn, RouterStateSnapshot, UrlTree } from '@angular/router';\nimport { Router } from '@angular/router';\nimport { NavController } from '@ionic/angular/standalone';\nimport type { Observable } from 'rxjs';\nimport { map, mergeMap } from 'rxjs/operators';\n\n/**\n * Discriminated set of authentication states the guards react to.\n *\n * @remarks\n * The application is responsible for emitting these values through {@link KitAuthConfig.authState}.\n * An application that does not use a value (for example email confirmation) simply never emits it.\n *\n * - `user` — fully authenticated and verified.\n * - `confirm` — awaiting email confirmation.\n * - `required` — not authenticated.\n * - `anonymous` — signed in anonymously; the user can still be guided toward full registration.\n */\nexport type KitAuthState = 'user' | 'confirm' | 'required' | 'anonymous';\n\n/**\n * Redirect targets (route paths) used by the guards when access is denied.\n *\n * @remarks\n * Every field is required and must be provided per application, because the guards have no\n * knowledge of the host application's route layout.\n */\nexport interface KitAuthRedirects {\n /** Used by {@link kitRequiredUnauthorizedGuard}: where to navigate when the user is already authenticated (`user`). */\n readonly whenAuthorized: string;\n /** Used by {@link kitRequiredUnauthorizedGuard}: where to navigate when the user is awaiting email confirmation (`confirm`). */\n readonly whenConfirming: string;\n /** Used by {@link kitRequireConfirmingGuard}: where to navigate when the state is not `confirm`. */\n readonly whenNotConfirming: string;\n /** Used by {@link kitRequireAuthorizedGuard}: where to navigate when the state is not `user` and the fallback is not allowed. */\n readonly whenUnauthorized: string;\n}\n\n/**\n * Configuration consumed by the authentication guards, injected through {@link provideKitAuth}.\n *\n * @remarks\n * `authState` and `redirects` are required. The `onAuthorized` / `onUnauthenticated` hooks are\n * optional and default to allowing the authenticated user through (`true`) and falling through to\n * the default redirect (`false`) respectively, so an app only supplies the ones with real logic.\n */\nexport interface KitAuthConfig {\n /**\n * Source of the current authentication state.\n *\n * @remarks\n * Typically backed by the application's own auth service (for example `AuthService.isAuth()`).\n *\n * @returns A stream of {@link KitAuthState} values.\n */\n authState(): Observable<KitAuthState>;\n /**\n * Application-specific work that runs in {@link kitRequireAuthorizedGuard} after the state is confirmed to be `user`.\n *\n * @remarks\n * Typical responsibilities include token login, permission checks, terms-of-service acceptance,\n * or restoring a previously requested redirect. Optional; defaults to `true` (allow activation).\n *\n * @param state - The router state snapshot of the route being activated.\n * @returns `true` to allow activation, or a `UrlTree` to perform a custom redirect.\n */\n onAuthorized?(state: RouterStateSnapshot): Promise<boolean | UrlTree>;\n /**\n * Fallback that runs in {@link kitRequireAuthorizedGuard} when the state is `required` (not authenticated).\n *\n * @remarks\n * For example, attempt an anonymous sign-in and allow the route. Optional; defaults to `false`\n * (fall through to the default `whenUnauthorized` redirect).\n *\n * @param state - The router state snapshot of the route being activated.\n * @returns `true` to allow activation, a `UrlTree` for a custom redirect, or `false` to use the default redirect.\n */\n onUnauthenticated?(state: RouterStateSnapshot): Promise<boolean | UrlTree>;\n /** Redirect targets used by the guards. */\n redirects: KitAuthRedirects;\n}\n\n/**\n * Injection token that carries the {@link KitAuthConfig} to the authentication guards.\n */\nexport const KIT_AUTH_CONFIG = new InjectionToken<KitAuthConfig>('@rdlabo/ionic-angular-kit:auth');\n\n/**\n * Wire the authentication guard configuration into the application's dependency injection.\n *\n * @remarks\n * The factory runs inside an injection context, so it may call `inject()` (for example\n * `inject(AuthService)`) to build the configuration.\n *\n * @param configFactory - Factory that returns the {@link KitAuthConfig} for the application.\n * @returns Environment providers to add to the application bootstrap.\n *\n * @example\n * ```ts\n * provideKitAuth(() => {\n * const auth = inject(AuthService);\n * return {\n * // onAuthorized / onUnauthenticated are optional (default: allow / fall through to redirect).\n * authState: () => auth.isAuth(),\n * redirects: {\n * whenAuthorized: '/',\n * whenConfirming: '/auth/confirm',\n * whenNotConfirming: '/auth/signin',\n * whenUnauthorized: 'auth',\n * },\n * };\n * });\n * ```\n */\nexport const provideKitAuth = (configFactory: () => KitAuthConfig): EnvironmentProviders =>\n makeEnvironmentProviders([{ provide: KIT_AUTH_CONFIG, useFactory: configFactory }]);\n\n/**\n * Guard that requires the user to be unauthenticated (for example sign-in or sign-up pages).\n *\n * @remarks\n * Allows the `required` and `anonymous` states (an anonymous user is permitted to proceed to a\n * registration page). An authenticated user (`user`) is sent to `whenAuthorized`, and a user\n * awaiting confirmation (`confirm`) is sent to `whenConfirming`.\n *\n * @returns A stream emitting `true` to allow activation, or `false` after triggering a redirect.\n *\n * @example\n * ```ts\n * const routes: Routes = [{ path: 'signin', component: SigninPage, canActivate: [kitRequiredUnauthorizedGuard] }];\n * ```\n */\nexport const kitRequiredUnauthorizedGuard: CanActivateFn = () => {\n const { authState, redirects } = inject(KIT_AUTH_CONFIG);\n const router = inject(Router);\n const navCtrl = inject(NavController);\n\n return authState().pipe(\n map((data) => {\n if (data === 'user') {\n navCtrl.setDirection('root');\n router.navigate([redirects.whenAuthorized]);\n return false;\n } else if (data === 'confirm') {\n router.navigate([redirects.whenConfirming]);\n return false;\n }\n // 'required' | 'anonymous'\n return true;\n }),\n );\n};\n\n/**\n * Guard that requires the user to be awaiting email confirmation (`confirm`).\n *\n * @remarks\n * Any other state triggers a redirect: an `anonymous` user is sent to the authenticated area\n * (`whenAuthorized`), and every remaining state is sent to `whenNotConfirming`.\n *\n * @returns A stream emitting `true` to allow activation, or `false` after triggering a redirect.\n *\n * @example\n * ```ts\n * const routes: Routes = [{ path: 'confirm', component: ConfirmPage, canActivate: [kitRequireConfirmingGuard] }];\n * ```\n */\nexport const kitRequireConfirmingGuard: CanActivateFn = () => {\n const { authState, redirects } = inject(KIT_AUTH_CONFIG);\n const router = inject(Router);\n const navCtrl = inject(NavController);\n\n return authState().pipe(\n map((data) => {\n if (data === 'confirm') {\n return true;\n }\n navCtrl.setDirection('root');\n router.navigate([data === 'anonymous' ? redirects.whenAuthorized : redirects.whenNotConfirming]);\n return false;\n }),\n );\n};\n\n/**\n * Guard that requires the user to be fully authenticated (`user`).\n *\n * @remarks\n * - `user` — runs {@link KitAuthConfig.onAuthorized} (token login, permission checks, and so on).\n * - `anonymous` — allowed as-is, for applications that permit anonymous browsing.\n * - `required` / `confirm` — runs {@link KitAuthConfig.onUnauthenticated}; if it resolves to `false`,\n * the user is redirected to `whenUnauthorized`.\n *\n * @param _route - The activated route snapshot (unused).\n * @param state - The router state snapshot, forwarded to the configuration hooks.\n * @returns A stream emitting the activation result: `true`, a `UrlTree`, or `false` after a redirect.\n *\n * @example\n * ```ts\n * const routes: Routes = [{ path: 'home', component: HomePage, canActivate: [kitRequireAuthorizedGuard] }];\n * ```\n */\nexport const kitRequireAuthorizedGuard: CanActivateFn = (_route, state) => {\n const { authState, onAuthorized, onUnauthenticated, redirects } = inject(KIT_AUTH_CONFIG);\n const router = inject(Router);\n const navCtrl = inject(NavController);\n\n return authState().pipe(\n mergeMap(async (data) => {\n if (data === 'user') {\n // 既定は「許可」。tokenLogin / 権限確認等が必要なアプリだけ onAuthorized を渡す。\n return onAuthorized ? onAuthorized(state) : true;\n }\n if (data === 'anonymous') {\n return true;\n }\n // 既定は false(whenUnauthorized へ)。匿名ログイン等のフォールバックが要るアプリだけ渡す。\n const fallback = onUnauthenticated ? await onUnauthenticated(state) : false;\n if (fallback !== false) {\n return fallback;\n }\n navCtrl.setDirection('root');\n router.navigate([redirects.whenUnauthorized]);\n return false;\n }),\n );\n};\n","import type { EnvironmentProviders } from '@angular/core';\nimport { inject, InjectionToken, makeEnvironmentProviders } from '@angular/core';\nimport type { HttpEvent, HttpInterceptorFn, HttpRequest } from '@angular/common/http';\nimport { HttpErrorResponse, HttpResponse } from '@angular/common/http';\nimport { Network } from '@capacitor/network';\nimport type { Observable } from 'rxjs';\nimport { from, retry, throwError, timer } from 'rxjs';\nimport { catchError, map, mergeMap, tap, timeout } from 'rxjs/operators';\n\n/**\n * HTTP methods that are safe to retry automatically.\n *\n * @remarks\n * Non-idempotent methods (`POST` / `PATCH` / `DELETE`) are never auto-retried, because a response\n * lost *after* the server processed the write would be replayed into a duplicate write (\"saved twice\n * from one tap\"). A non-idempotent request that is genuinely safe to replay opts in by carrying an\n * `Idempotency-Key` header (which the server must honor).\n *\n * @internal\n */\nconst RETRYABLE_METHODS = ['GET', 'HEAD', 'OPTIONS'];\n\n/**\n * Transient HTTP statuses worth retrying: `0` (network/transport failure), `408` (request timeout),\n * `429` (rate limited), and the `502` / `503` / `504` gateway-availability family. Every other status\n * is thrown immediately — a whitelist is safer than a blacklist for deciding what to replay.\n *\n * @internal\n */\nconst RETRYABLE_STATUSES = [0, 408, 429, 502, 503, 504];\n\n/**\n * Maximum number of automatic retries for a retryable request.\n *\n * @internal\n */\nconst MAX_RETRIES = 2;\n\n/**\n * Fleet-wide per-request timeout. A request with no response within this window fails with a\n * synthetic `408` (a {@link RETRYABLE_STATUSES | retryable status}). Deliberately generous — it\n * catches a genuinely hung request (dead server) without cutting off legitimately slow work such as\n * a large upload or an AI generation. `timeout({ each })` resets on every emission, so a streaming\n * response that keeps emitting is unaffected.\n *\n * @internal\n */\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/**\n * Parse a `Retry-After` header (delta-seconds or an HTTP-date) into milliseconds, or `null` when it\n * is absent or unparseable.\n *\n * @internal\n */\nconst parseRetryAfterMs = (error: HttpErrorResponse): number | null => {\n const header = error.headers?.get('Retry-After');\n if (!header) {\n return null;\n }\n const seconds = Number(header);\n if (Number.isFinite(seconds)) {\n return Math.max(0, seconds * 1000);\n }\n const dateMs = Date.parse(header);\n return Number.isNaN(dateMs) ? null : Math.max(0, dateMs - Date.now());\n};\n\n/**\n * Configuration that customizes the behavior of {@link kitAuthInterceptor}, injected through {@link provideKitHttp}.\n *\n * @remarks\n * The interceptor fixes the retry policy and control flow; only the hooks below are app-specific:\n *\n * - Retries only {@link RETRYABLE_METHODS | idempotent methods} (or a request bearing an\n * `Idempotency-Key`) on a {@link RETRYABLE_STATUSES | transient status}, up to {@link MAX_RETRIES}\n * times with a short jittered backoff (honoring `Retry-After`). Writes are never auto-retried.\n * - When the device is offline it fails fast to {@link KitHttpConfig.offlineFallback} instead of\n * waiting out the retries.\n * - On a final error it classifies by status and calls the matching hook (see each hook below).\n *\n * Only {@link KitHttpConfig.getAuthHeaders} is required — it has no safe default. Every other hook is\n * optional and defaults to a no-op (or `{}` / `false` / `null` as appropriate), so an app configures\n * only the behavior that actually differs from the canonical baseline.\n */\nexport interface KitHttpConfig {\n /**\n * Produce authentication and metadata headers for the outgoing request.\n *\n * @param request - The outgoing request about to be sent.\n * @returns A map of header names to values, resolved asynchronously.\n */\n getAuthHeaders(request: HttpRequest<unknown>): Promise<Record<string, string>>;\n /**\n * Produce additional headers for the outgoing request.\n *\n * @remarks\n * Optional; defaults to adding no extra headers.\n *\n * @param request - The outgoing request about to be sent.\n * @returns A map of header names to values; return `{}` when none are needed.\n */\n buildExtraHeaders?(request: HttpRequest<unknown>): Record<string, string>;\n /**\n * Treat an otherwise-successful response as an error.\n *\n * @remarks\n * Optional. Some backends use a 2xx status (for example `204` / `206`) to signal a condition the\n * app wants to surface as an error rather than a success. Return `true` to throw the response so it\n * flows through the error path; the status is not in {@link RETRYABLE_STATUSES}, so it is not\n * retried and propagates to the caller. Defaults to treating every 2xx as a success.\n *\n * @param response - The successful `HttpResponse` about to be delivered.\n * @returns `true` to reject the response as an error.\n */\n treatAsError?(response: HttpResponse<unknown>): boolean;\n /**\n * Called for every successful response that completed an actual network round trip.\n *\n * @remarks\n * Responses synthesized by {@link KitHttpConfig.offlineFallback} are produced after `catchError`\n * and therefore never reach this hook, so it observes genuine successes only. A typical use is to\n * reset an \"offline\" flag once connectivity is restored. Optional; defaults to a no-op.\n *\n * @param event - The successful `HttpResponse`.\n */\n onResponse?(event: HttpResponse<unknown>): void;\n /**\n * Decide whether to pass the request straight through, skipping auth, retry, and error handling.\n *\n * @remarks\n * Useful for external URLs such as S3 or a CDN. Optional; defaults to `false` (never bypass).\n *\n * @param request - The outgoing request.\n * @returns `true` to bypass the interceptor pipeline.\n */\n bypass?(request: HttpRequest<unknown>): boolean;\n /**\n * Provide an offline short-circuit when a request fails.\n *\n * @remarks\n * Returning a non-null observable replaces the error with that response (for example a queued\n * offline result). Optional; defaults to `null` (no fallback, normal error handling proceeds).\n *\n * @param request - The request that failed (after headers were applied).\n * @param error - The error response that triggered the fallback.\n * @returns A replacement event stream, or `null` for no fallback.\n */\n offlineFallback?(request: HttpRequest<unknown>, error: HttpErrorResponse): Observable<HttpEvent<unknown>> | null;\n /**\n * Side effect to run on a `401` response (for example an expired token).\n *\n * @remarks\n * Optional; defaults to a no-op.\n *\n * @param request - The request that received the `401`.\n */\n onUnauthorized?(request: HttpRequest<unknown>): void;\n /**\n * Side effect to run on a `403` response (a permission error).\n *\n * @remarks\n * Optional; defaults to a no-op.\n *\n * @param request - The request that received the `403`.\n */\n onForbidden?(request: HttpRequest<unknown>): void;\n /**\n * UX hook for a genuine network / transport failure (status `0`) while the device reports itself\n * connected — i.e. the server is unreachable rather than the phone being offline.\n *\n * @remarks\n * Optional; defaults to a no-op. Narrow by design: it fires only for status `0` (not for `404`,\n * `429`, `5xx`, …, which have their own hooks), so a \"connection lost, reload?\" prompt is not shown\n * for server-side problems. When the device is offline it is not called at all — `offlineFallback`\n * owns that path. The kit ships {@link KitReloadAlertController} as the canonical implementation\n * (with de-dup so concurrent failures show a single alert, and auto-dismiss on reconnect).\n *\n * @param status - The HTTP status code (`0`), or a string descriptor for non-HTTP failures.\n * @returns Optionally a promise to await before continuing.\n */\n onNetworkError?(status: number | string): Promise<void> | void;\n /**\n * UX hook for a transient server-availability failure (`502` / `503` / `504`), fired after retries\n * are exhausted.\n *\n * @remarks\n * Optional; defaults to a no-op. Distinct from {@link KitHttpConfig.onNetworkError} — the device's\n * connection is fine, the server is momentarily unavailable — so the app can say \"server busy, try\n * again shortly\" rather than prompt a reload.\n *\n * @param status - `502`, `503`, or `504`.\n * @param retryAfterSeconds - The server's `Retry-After` hint in seconds, when provided.\n */\n onServerBusy?(status: number, retryAfterSeconds?: number): void;\n /**\n * UX hook for a `429 Too Many Requests` response, fired after retries are exhausted.\n *\n * @remarks\n * Optional; defaults to a no-op.\n *\n * @param retryAfterSeconds - The server's `Retry-After` hint in seconds, when provided.\n */\n onRateLimited?(retryAfterSeconds?: number): void;\n /**\n * UX hook for `400` / `422` / `500` responses that carry a server-provided message.\n *\n * @remarks\n * Optional; defaults to a no-op. Note that the message comes straight from the API; prefer a\n * user-facing `userMessage` / `code` in your error contract over showing a raw developer message.\n *\n * @param message - The message extracted from the error body.\n */\n onServerError?(message: string): void;\n /**\n * Side effect for a failure while *producing* the auth headers (`getAuthHeaders` rejected).\n *\n * @remarks\n * Optional; defaults to a no-op. Because the request is never sent in this case, it does not reach\n * the response-error hooks; classify it here (for example a failed token refresh) so it does not\n * fail silently.\n *\n * @param request - The request whose headers could not be produced.\n * @param error - The error thrown by `getAuthHeaders`.\n */\n onAuthError?(request: HttpRequest<unknown>, error: unknown): void;\n}\n\n/**\n * Injection token that carries the {@link KitHttpConfig} to {@link kitAuthInterceptor}.\n */\nexport const KIT_HTTP_CONFIG = new InjectionToken<KitHttpConfig>('@rdlabo/ionic-angular-kit:http');\n\n/**\n * Wire the {@link kitAuthInterceptor} configuration into the application's dependency injection.\n *\n * @remarks\n * Register the interceptor itself separately via `provideHttpClient(withInterceptors([kitAuthInterceptor]))`.\n * The factory runs inside an injection context, so it may call `inject()`.\n *\n * @param configFactory - Factory that returns the {@link KitHttpConfig} for the application.\n * @returns Environment providers to add to the application bootstrap.\n *\n * @example\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideHttpClient(withInterceptors([kitAuthInterceptor])),\n * provideKitHttp(() => {\n * const auth = inject(AuthService);\n * const reload = inject(KitReloadAlertController);\n * return {\n * // Only getAuthHeaders is required; every other hook is optional and defaults to a no-op.\n * getAuthHeaders: async () => ({ Authorization: `Bearer ${await auth.token()}` }),\n * onUnauthorized: () => auth.signOut(),\n * onNetworkError: (status) =>\n * reload.present({ header: 'Network error', message: `Reload? (${status})`, okText: 'Reload' }),\n * onResponse: () => void reload.dismiss(),\n * };\n * }),\n * ],\n * });\n * ```\n */\nexport const provideKitHttp = (configFactory: () => KitHttpConfig): EnvironmentProviders =>\n makeEnvironmentProviders([{ provide: KIT_HTTP_CONFIG, useFactory: configFactory }]);\n\n/**\n * Classify a final (post-retry) error and invoke the matching {@link KitHttpConfig} hook.\n *\n * @internal\n */\nconst dispatchError = (config: KitHttpConfig, req: HttpRequest<unknown>, error: HttpErrorResponse): void => {\n const status = error.status;\n const retryAfterMs = parseRetryAfterMs(error);\n const retryAfterSeconds = retryAfterMs === null ? undefined : Math.round(retryAfterMs / 1000);\n\n if (status === 401) {\n config.onUnauthorized?.(req);\n } else if (status === 403) {\n config.onForbidden?.(req);\n } else if (status === 0) {\n // Genuine network/transport failure. Only surface it when the device is actually connected\n // (server unreachable); when offline, offlineFallback owns the UX — a reload prompt won't help.\n void Network.getStatus().then((network) => {\n if (network.connected) {\n config.onNetworkError?.(status);\n }\n });\n } else if (status === 429) {\n config.onRateLimited?.(retryAfterSeconds);\n } else if ([502, 503, 504].includes(status)) {\n config.onServerBusy?.(status, retryAfterSeconds);\n } else if ([400, 422, 500].includes(status) && error.error?.message) {\n config.onServerError?.(error.error.message);\n }\n // Every other status (404, 418, …) is left to the caller — no generic alert.\n};\n\n/**\n * Canonical functional HTTP interceptor that applies authentication, retries, and error handling.\n *\n * @remarks\n * Behavior, driven by the injected {@link KitHttpConfig}:\n *\n * 1. Requests for which `bypass` returns `true` are forwarded untouched.\n * 2. If `getAuthHeaders` rejects, `onAuthError` is called and the request is not sent.\n * 3. Otherwise the headers from `getAuthHeaders` and `buildExtraHeaders` are merged onto a cloned request.\n * 4. On failure the request is retried up to {@link MAX_RETRIES} times, but **only** when the device\n * is online, the method is a {@link RETRYABLE_METHODS | retryable method} (or carries an\n * `Idempotency-Key`), and the status is a {@link RETRYABLE_STATUSES | transient status}. The\n * backoff is `retryCount * 500ms` plus up to 250ms of jitter, or the server's `Retry-After`.\n * When the device is offline it stops retrying immediately.\n * 5. On the final error, `offlineFallback` is consulted first; otherwise the error is classified by\n * status (see {@link dispatchError}): `401`→`onUnauthorized`, `403`→`onForbidden`, `0`→\n * `onNetworkError` (when connected), `429`→`onRateLimited`, `502`/`503`/`504`→`onServerBusy`, and\n * `400`/`422`/`500` with a body message→`onServerError`.\n *\n * @param request - The outgoing request.\n * @param next - The next handler in the interceptor chain.\n * @returns A stream of HTTP events for the (possibly modified, retried, or replaced) request.\n *\n * @example\n * ```ts\n * provideHttpClient(withInterceptors([kitAuthInterceptor]));\n * ```\n */\nexport const kitAuthInterceptor: HttpInterceptorFn = (request, next) => {\n const config = inject(KIT_HTTP_CONFIG);\n\n if (config.bypass?.(request)) {\n return next(request);\n }\n\n return from(Promise.resolve(config.getAuthHeaders(request))).pipe(\n catchError((headerError: unknown) => {\n // getAuthHeaders failed → the request is never sent; classify it instead of failing silently.\n config.onAuthError?.(request, headerError);\n return throwError(() => headerError);\n }),\n mergeMap((authHeaders) => {\n const req = request.clone({ setHeaders: { ...authHeaders, ...config.buildExtraHeaders?.(request) } });\n const retryable = RETRYABLE_METHODS.includes(req.method) || req.headers.has('Idempotency-Key');\n\n const base = next(req).pipe(\n timeout({\n each: DEFAULT_TIMEOUT_MS,\n with: () => throwError(() => new HttpErrorResponse({ status: 408, statusText: 'Request Timeout', url: req.url })),\n }),\n );\n\n return base.pipe(\n map((event) => {\n // A backend may signal an error condition with a 2xx status (e.g. 204/206); surface it as an error.\n if (event instanceof HttpResponse && config.treatAsError?.(event)) {\n throw event;\n }\n return event;\n }),\n retry({\n count: MAX_RETRIES,\n delay: (error: HttpErrorResponse, retryCount: number) =>\n from(Network.getStatus()).pipe(\n mergeMap((network) => {\n // Offline → don't wait out the retries; fail fast so offlineFallback can take over.\n if (!network.connected) {\n return throwError(() => error);\n }\n // Only replay idempotent requests, and only on a transient status.\n if (!retryable || !RETRYABLE_STATUSES.includes(error.status)) {\n return throwError(() => error);\n }\n // Short linear backoff (500ms, 1000ms, …) plus jitter to de-correlate a fleet of\n // clients reconnecting at once; the server's Retry-After wins when present.\n const backoff = parseRetryAfterMs(error) ?? retryCount * 500 + Math.random() * 250;\n return timer(backoff);\n }),\n ),\n }),\n tap((event) => {\n if (event instanceof HttpResponse) {\n config.onResponse?.(event);\n }\n }),\n catchError((error: HttpErrorResponse) => {\n const fallback = config.offlineFallback?.(req, error);\n if (fallback) {\n return fallback;\n }\n dispatchError(config, req, error);\n return throwError(() => error);\n }),\n );\n }),\n );\n};\n","/**\n * Merge a newly fetched page of items into an existing list by id, keeping the result sorted.\n *\n * Items are merged by the numeric `key` field. On a duplicate `key` the item from `arrayNew` wins\n * and replaces the one in `arrayOld`. Old items whose `key` falls *inside* the window spanned by\n * `arrayNew` (between its first and last `key`) are dropped, since the freshly fetched page is the\n * authoritative copy of that window; old items *outside* the window are kept. The merged items are\n * finally sorted by `key`, ascending or descending according to `order`.\n *\n * The algorithm is:\n * 1. If both inputs are empty, return an empty array.\n * 2. Read the first (`lead`) and last (`last`) `key` values of `arrayNew` as the bounds of the\n * page's window. Keep the old items whose `key` lies *outside* that window (at or beyond the\n * extremes) and drop those strictly inside it, handling both a high-to-low page (`lead > last`)\n * and a low-to-high page (`lead < last`). A single-value page (`lead === last`) keeps all old items.\n * 3. Remove old items whose `key` already exists in `arrayNew` (new wins on duplicates).\n * 4. Concatenate `arrayNew` with the surviving old items and sort by `key` in the requested\n * direction.\n *\n * @remarks\n * Designed for infinite-scroll / paginated list merging, where each fetched page may overlap the\n * previously held items and the server returns a contiguous, ordered window of records. The `key`\n * field is treated as a number for range comparison and sorting.\n *\n * @typeParam T - Element type of both arrays. Its `key` property must be a numeric value.\n * @param arrayOld - The previously accumulated list. May be empty or nullish-safe (empty when falsy).\n * @param arrayNew - The newly fetched page of items; its `key` range defines the window that its own\n * items replace, and its items take precedence on duplicates.\n * @param key - The property of `T` used as the unique, numeric id for matching, range filtering, and sorting.\n * @param order - Sort direction by `key`: `'ASC'` for ascending or `'DESC'` for descending. Defaults to `'DESC'`.\n * @param secondaryKey - Optional second property for extra de-duplication. When provided, any\n * surviving old item whose `secondaryKey` value matches that of *any* new item is dropped before\n * the `key` merge. Useful when a record keeps a stable `key` but its secondary identity changes\n * between pages (e.g. items keyed by `id` but also grouped by `parentId`). Omit to skip this step.\n * @returns A new array containing `arrayNew` merged with the out-of-window, non-duplicate old items, sorted by `key`.\n * @example\n * ```ts\n * interface Post {\n * id: number;\n * title: string;\n * }\n *\n * const loaded: Post[] = [\n * { id: 30, title: 'c' },\n * { id: 20, title: 'b' },\n * { id: 10, title: 'a' },\n * ];\n * const nextPage: Post[] = [\n * { id: 20, title: 'b (updated)' },\n * { id: 15, title: 'a.5' },\n * ];\n *\n * // Descending merge: id 30 lies above the new page's [15, 20] window so it is kept; id 20 is\n * // inside the window and is replaced by the new value; id 10 lies below the window and is kept.\n * const merged = arrayConcatById(loaded, nextPage, 'id', 'DESC');\n * // => [{ id: 30, title: 'c' }, { id: 20, title: 'b (updated)' }, { id: 15, title: 'a.5' }, { id: 10, title: 'a' }]\n * ```\n */\nexport const arrayConcatById = <T>(\n arrayOld: T[],\n arrayNew: T[],\n key: keyof T,\n order: 'ASC' | 'DESC' = 'DESC',\n secondaryKey?: keyof T,\n): T[] => {\n if (!arrayNew.length && !arrayOld.length) {\n return [];\n }\n const lead = arrayNew[0][key] as number;\n const last = arrayNew[arrayNew.length - 1][key] as number;\n\n const filteredOld = (arrayOld || []).filter((vol) => {\n const value = vol[key] as number;\n return (lead > last && (value >= lead || value <= last)) || (lead < last && (value <= lead || value >= last)) || lead === last;\n });\n\n const secondaryFilteredOld = secondaryKey\n ? filteredOld.filter((vol) => !arrayNew.some((element) => element[secondaryKey] === vol[secondaryKey]))\n : filteredOld;\n\n const oldData = secondaryFilteredOld.filter((vol) => !arrayNew.some((element) => element[key] === vol[key]));\n const data = arrayNew.concat(oldData);\n\n const direction = order === 'ASC' ? 1 : -1;\n return data.sort((a, b) => {\n const x = a[key] as number;\n const y = b[key] as number;\n if (x > y) {\n return direction;\n }\n if (x < y) {\n return direction * -1;\n }\n return 0;\n });\n};\n","/**\n * Order-independent deep equality check.\n *\n * @remarks\n * Returns `true` when `a` and `b` serialize to the same value after their (nested) object entries are\n * sorted by key, so property order does not affect the result. Intended for cheap \"did this state\n * object change?\" comparisons.\n *\n * Caveats of the JSON-serialization approach: values that JSON drops or coerces (`undefined`,\n * functions, `NaN`, `Date`, `Map`/`Set`) are compared by their serialized form, and array element\n * order is treated as significant (arrays are not reordered in a meaningful way). Use a structural\n * deep-equal library when those cases matter.\n *\n * @param a - First object.\n * @param b - Second object.\n * @returns `true` when the two objects are deeply equal ignoring key order.\n * @example\n * ```ts\n * objectEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); // => true\n * objectEqual({ a: 1 }, { a: 2 }); // => false\n * ```\n */\nexport const objectEqual = (a: object, b: object): boolean => {\n if (Object.is(a, b)) {\n return true;\n }\n const sortDeep = (obj: unknown): unknown => {\n if (typeof obj !== 'object' || !obj) {\n return undefined;\n }\n return Object.entries(obj)\n .sort()\n .map(([entryKey, value]) => [entryKey, typeof value === 'object' ? sortDeep(value) : value]);\n };\n return JSON.stringify(sortDeep(a)) === JSON.stringify(sortDeep(b));\n};\n","/**\n * Disable the button that triggered an event while an async operation runs, re-enabling it after.\n *\n * @remarks\n * Prevents the common double-submit / double-tap bug: the `event.target` button is disabled, the\n * work is awaited, and the button is re-enabled — even if the work rejects (the rejection is\n * swallowed here so the button always recovers; handle errors inside `work` if you need to react).\n *\n * @param event - The DOM event whose `target` is the button to disable (e.g. a click event).\n * @param work - The async operation to run while the button is disabled.\n * @returns A Promise that resolves once the work has settled and the button has been re-enabled.\n * @example\n * ```ts\n * async submit(event: Event): Promise<void> {\n * await disableHandler(event, this.save());\n * }\n * ```\n */\nexport const disableHandler = async (event: Event, work: Promise<void | boolean>): Promise<void> => {\n const target = event.target as HTMLButtonElement;\n target.disabled = true;\n await work.catch((): undefined => undefined);\n target.disabled = false;\n};\n","import { WritableSignal } from '@angular/core';\n\n/**\n * Toggle the `disabled` flag of a signal-held `ion-infinite-scroll` / `ion-refresher` element.\n *\n * @remarks\n * A tiny pure helper for the common pattern of stashing the completing scroll/refresher element in a\n * signal (e.g. captured from the event) and later enabling/disabling it — for instance disabling\n * infinite scroll once the last page has loaded. A no-op when the signal holds no element.\n *\n * @param completeEvent - a writable signal holding the infinite-scroll / refresher element (or nullish)\n * @param disabled - the value to set on the element's `disabled` property\n * @example\n * ```ts\n * const infinite = signal<HTMLIonInfiniteScrollElement | null>(null);\n * // ...on ionInfinite: infinite.set(ev.target); ...when no more pages:\n * kitChangeEventDisabled(infinite, true);\n * ```\n */\nexport const kitChangeEventDisabled = (\n completeEvent: WritableSignal<HTMLIonInfiniteScrollElement | HTMLIonRefresherElement | null | undefined>,\n disabled: boolean,\n): void => {\n const event = completeEvent();\n if (event) {\n event.disabled = disabled;\n }\n};\n","import { ElementRef } from '@angular/core';\nimport { Observable, startWith } from 'rxjs';\n\n/**\n * Observe an Ionic page's \"is currently entered\" state from its lifecycle DOM events.\n *\n * @remarks\n * Emits `true` on `ionViewDidEnter` and `false` on `ionViewWillEnter` / `ionViewWillLeave`, seeded\n * with `false` via `startWith`. Useful to pause/resume work (timers, video, expensive rendering)\n * while a page is off-screen in Ionic's stack navigation, without wiring the four lifecycle hooks by\n * hand. The listeners are removed when the Observable is unsubscribed.\n *\n * @param el - an `ElementRef` for the page host element (the `ion-page`)\n * @returns an Observable that emits whether the page is currently entered\n * @example\n * ```ts\n * export class FeedPage {\n * readonly #host = inject(ElementRef);\n * readonly isEntered = toSignal(kitCreateDidEnter(this.#host), { initialValue: false });\n * }\n * ```\n */\nexport const kitCreateDidEnter = (el: ElementRef): Observable<boolean> => {\n return new Observable<boolean>((observer) => {\n const willEnter = () => observer.next(false);\n const didEnter = () => observer.next(true);\n const willLeave = () => observer.next(false);\n\n el.nativeElement.addEventListener('ionViewWillEnter', willEnter);\n el.nativeElement.addEventListener('ionViewDidEnter', didEnter);\n el.nativeElement.addEventListener('ionViewWillLeave', willLeave);\n\n return () => {\n el.nativeElement.removeEventListener('ionViewWillEnter', willEnter);\n el.nativeElement.removeEventListener('ionViewDidEnter', didEnter);\n el.nativeElement.removeEventListener('ionViewWillLeave', willLeave);\n };\n }).pipe(startWith(false));\n};\n","/*\n * Public API Surface of @rdlabo/ionic-angular-kit\n */\n\n// Storage: typed wrapper around the platform key/value store.\nexport * from './lib/storage/kit-storage.service';\n\n// Overlay: wrapper around the Ionic Modal / Toast / Alert controllers.\nexport * from './lib/overlay/overlay-config';\nexport * from './lib/overlay/kit-overlay.controller';\nexport * from './lib/overlay/kit-loading.controller';\nexport * from './lib/overlay/kit-reload-alert.controller';\nexport * from './lib/overlay/kit-auth-failed-alert';\nexport * from './lib/overlay/kit-language-action-sheet';\n\n// Directives.\nexport * from './lib/directives/autofill.directive';\n\n// Keyboard: native keyboard reposition listeners.\nexport * from './lib/keyboard/kit-keyboard';\n\n// Theme: light/dark controller with OS-follow and native status-bar sync.\nexport * from './lib/theme/theme-config';\nexport * from './lib/theme/kit-theme.controller';\n\n// Review: throttled native in-app review request.\nexport * from './lib/review/kit-request-review';\n\n// Printer: pure Brother label plumbing (DOM→PNG, rotation, print-settings assembly).\nexport * from './lib/printer/kit-printer';\n\n// Auth: functional route guards.\nexport * from './lib/auth/auth-guards';\n\n// HTTP: functional interceptor.\nexport * from './lib/http/kit-http.interceptor';\n\n// Utils: framework-agnostic pure helpers.\nexport * from './lib/utils/haptics';\nexport * from './lib/utils/array';\nexport * from './lib/utils/object';\nexport * from './lib/utils/dom';\nexport * from './lib/utils/ionic-scroll-event';\nexport * from './lib/utils/ionic-view-enter';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rdlabo/ionic-angular-kit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@angular/common": "^21.0.0",
|
|
6
6
|
"@angular/core": "^21.0.0",
|
|
@@ -18,6 +18,23 @@
|
|
|
18
18
|
"dom-to-image-more": "^3.0.0",
|
|
19
19
|
"rxjs": "^7.8.0"
|
|
20
20
|
},
|
|
21
|
+
"peerDependenciesMeta": {
|
|
22
|
+
"@capacitor/preferences": {
|
|
23
|
+
"optional": true
|
|
24
|
+
},
|
|
25
|
+
"@capacitor/status-bar": {
|
|
26
|
+
"optional": true
|
|
27
|
+
},
|
|
28
|
+
"@capacitor-community/in-app-review": {
|
|
29
|
+
"optional": true
|
|
30
|
+
},
|
|
31
|
+
"@rdlabo/capacitor-brotherprint": {
|
|
32
|
+
"optional": true
|
|
33
|
+
},
|
|
34
|
+
"dom-to-image-more": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
21
38
|
"dependencies": {
|
|
22
39
|
"tslib": "^2.3.0"
|
|
23
40
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, EnvironmentProviders, OnInit, ElementRef, WritableSignal } from '@angular/core';
|
|
3
|
-
import { ModalOptions, PopoverOptions, ToastOptions, AlertController, ActionSheetController } from '@ionic/angular/standalone';
|
|
2
|
+
import { InjectionToken, EnvironmentProviders, InputSignalWithTransform, OnInit, ElementRef, WritableSignal } from '@angular/core';
|
|
3
|
+
import { ModalOptions, PopoverOptions, ToastOptions, LoadingOptions, AlertController, ActionSheetController } from '@ionic/angular/standalone';
|
|
4
4
|
import { PluginListenerHandle } from '@capacitor/core';
|
|
5
5
|
import { BehaviorSubject, Observable } from 'rxjs';
|
|
6
6
|
import { BRLMPrinterModelName, BRLMPrinterLabelName, BRLMPrintOptions } from '@rdlabo/capacitor-brotherprint';
|
|
@@ -143,6 +143,83 @@ interface KitModalPresentOptions extends Omit<ModalOptions, 'component' | 'compo
|
|
|
143
143
|
*/
|
|
144
144
|
watchKeyboard?: boolean;
|
|
145
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Optional static metadata a modal component may declare to make {@link KitOverlayController.presentModal}
|
|
148
|
+
* type-safe in its dismiss data: the value passed to `dismiss()` is inferred from `modalReturn`.
|
|
149
|
+
*
|
|
150
|
+
* @remarks
|
|
151
|
+
* Props do *not* need to be declared here — {@link KitOverlayController.presentModal} infers them directly
|
|
152
|
+
* from the component's `input()` fields (see {@link ModalPropsOf}), so `input()` stays the single source of
|
|
153
|
+
* truth and can never drift from a hand-written declaration. Only the dismiss-data shape, which has no
|
|
154
|
+
* counterpart on the component, is declared — as a `declare static modalReturn` phantom type with no runtime
|
|
155
|
+
* value, so it adds nothing to the bundle.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* export class EditPage {
|
|
160
|
+
* declare static modalReturn: { saved: boolean };
|
|
161
|
+
* readonly id = input.required<number>(); // props are inferred from here
|
|
162
|
+
* readonly note = input<string>(); // default-less input() → optional prop
|
|
163
|
+
* }
|
|
164
|
+
*
|
|
165
|
+
* // Caller — `id` is required (input.required), `note` optional; result is `{ saved: boolean } | undefined`:
|
|
166
|
+
* const result = await overlay.presentModal(EditPage, { id: 1 });
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
interface ModalMetadata<R = unknown> {
|
|
170
|
+
/** Shape of the data the modal resolves with when dismissed. */
|
|
171
|
+
modalReturn?: R;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Write type of a signal `input()` field (unwraps `input.required`, `input()` and transform inputs).
|
|
175
|
+
*
|
|
176
|
+
* @remarks
|
|
177
|
+
* Matched with `any` (not `unknown`): `InputSignalWithTransform`'s `TransformT` is contravariant, so
|
|
178
|
+
* `InputSignal<number>` is not assignable to `InputSignalWithTransform<unknown, unknown>`.
|
|
179
|
+
*/
|
|
180
|
+
type InputWriteType<F> = F extends InputSignalWithTransform<any, infer W> ? W : never;
|
|
181
|
+
/** Instance type of a component constructor; `never` for non-class components (string / HTMLElement refs). */
|
|
182
|
+
type InstanceOf<C> = C extends abstract new (...args: never[]) => infer I ? I : never;
|
|
183
|
+
/** Keys of the `input()` signal fields on a component instance. */
|
|
184
|
+
type InputFieldKeys<I> = {
|
|
185
|
+
[K in keyof I]-?: I[K] extends InputSignalWithTransform<any, any> ? K : never;
|
|
186
|
+
}[keyof I];
|
|
187
|
+
/**
|
|
188
|
+
* Props object inferred from a component's `input()` fields. Required vs. optional is decided by the write
|
|
189
|
+
* type: `input.required<T>()` / `input<T>(default)` yield `T` (no `undefined`) → required prop, while a
|
|
190
|
+
* default-less `input<T>()` yields `T | undefined` → optional prop.
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* Angular's types cannot distinguish `input.required<T>()` from a defaulted `input<T>(default)` — both are
|
|
194
|
+
* `InputSignal<T>` — so a defaulted input is (safely) treated as required. Declare inputs you want to omit at
|
|
195
|
+
* the call site as default-less `input<T>()`.
|
|
196
|
+
*/
|
|
197
|
+
type ModalPropsOf<I> = {
|
|
198
|
+
[K in InputFieldKeys<I> as undefined extends InputWriteType<I[K]> ? never : K]: InputWriteType<I[K]>;
|
|
199
|
+
} & {
|
|
200
|
+
[K in InputFieldKeys<I> as undefined extends InputWriteType<I[K]> ? K : never]?: InputWriteType<I[K]>;
|
|
201
|
+
};
|
|
202
|
+
/** `input()` keys whose write type excludes `undefined` (required / defaulted inputs). Empty when none. */
|
|
203
|
+
type RequiredInputKeys<I> = {
|
|
204
|
+
[K in InputFieldKeys<I>]: undefined extends InputWriteType<I[K]> ? never : K;
|
|
205
|
+
}[InputFieldKeys<I>];
|
|
206
|
+
/**
|
|
207
|
+
* Dismiss-data type inferred from a component's static {@link ModalMetadata.modalReturn}. A component that
|
|
208
|
+
* declares no `modalReturn` resolves to `void`: it is treated as returning no dismiss data, so the compiler
|
|
209
|
+
* rejects any attempt to read the result. Declare `modalReturn` on modals that do resolve with data.
|
|
210
|
+
*/
|
|
211
|
+
type ModalReturnOf<C> = C extends {
|
|
212
|
+
modalReturn: infer R;
|
|
213
|
+
} ? R : void;
|
|
214
|
+
/** Loose trailing args (optional, untyped props) — used when a component exposes no signal `input()` fields. */
|
|
215
|
+
type LooseModalPresentArgs = [componentProps?: ModalOptions['componentProps'], options?: KitModalPresentOptions];
|
|
216
|
+
/**
|
|
217
|
+
* Trailing `presentModal` args derived from a component's `input()` fields: props are inferred and typed via
|
|
218
|
+
* {@link ModalPropsOf}. When the component declares at least one required input the props argument is required;
|
|
219
|
+
* when every input is optional the props argument itself is optional. Components with no signal inputs (plain
|
|
220
|
+
* classes, `@Input()`-decorator components, or non-class refs) fall back to loose, untyped props.
|
|
221
|
+
*/
|
|
222
|
+
type ModalPresentArgs<C, I = InstanceOf<C>> = [I] extends [never] ? LooseModalPresentArgs : [InputFieldKeys<I>] extends [never] ? LooseModalPresentArgs : [RequiredInputKeys<I>] extends [never] ? [componentProps?: ModalPropsOf<I>, options?: KitModalPresentOptions] : [componentProps: ModalPropsOf<I>, options?: KitModalPresentOptions];
|
|
146
223
|
/**
|
|
147
224
|
* Options for {@link KitOverlayController.alertClose}.
|
|
148
225
|
*/
|
|
@@ -182,7 +259,7 @@ interface KitAlertConfirmOptions extends KitAlertCloseOptions {
|
|
|
182
259
|
* constructor(private readonly overlay: KitOverlayController) {}
|
|
183
260
|
*
|
|
184
261
|
* async edit(): Promise<void> {
|
|
185
|
-
* const result = await this.overlay.presentModal
|
|
262
|
+
* const result = await this.overlay.presentModal(EditPage, { id: 1 });
|
|
186
263
|
* if (result) {
|
|
187
264
|
* await this.overlay.presentToast({ message: 'Saved' });
|
|
188
265
|
* }
|
|
@@ -202,12 +279,18 @@ declare class KitOverlayController {
|
|
|
202
279
|
* @remarks
|
|
203
280
|
* Presenting a modal triggers light native haptic feedback as an intentional kit UX choice,
|
|
204
281
|
* consistent with {@link presentPopover} and {@link presentToast}.
|
|
282
|
+
*
|
|
283
|
+
* Props are inferred from the component's `input()` fields (see {@link ModalPropsOf}): required inputs
|
|
284
|
+
* become required props, so the compiler rejects a call that omits them. The return type is inferred from
|
|
285
|
+
* a static `modalReturn` (see {@link ModalMetadata}); a component with no `modalReturn` resolves to `void`
|
|
286
|
+
* — the modal is treated as returning no dismiss data.
|
|
205
287
|
* @example
|
|
206
288
|
* ```ts
|
|
207
|
-
*
|
|
289
|
+
* // Inferred — `id` required (input.required), `note` optional; result is `{ saved: boolean } | undefined`:
|
|
290
|
+
* const result = await overlay.presentModal(EditPage, { id: 1 });
|
|
208
291
|
* ```
|
|
209
292
|
*/
|
|
210
|
-
presentModal<
|
|
293
|
+
presentModal<C extends ModalOptions['component']>(component: C, ...args: ModalPresentArgs<C>): Promise<ModalReturnOf<C> | undefined>;
|
|
211
294
|
/**
|
|
212
295
|
* Present a popover and resolve with the data passed to its dismissal.
|
|
213
296
|
*
|
|
@@ -287,6 +370,59 @@ declare class KitOverlayController {
|
|
|
287
370
|
static ɵprov: i0.ɵɵInjectableDeclaration<KitOverlayController>;
|
|
288
371
|
}
|
|
289
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Reference-counted wrapper around Ionic's `LoadingController` that keeps at most one loading
|
|
375
|
+
* indicator on screen across concurrent async work.
|
|
376
|
+
*
|
|
377
|
+
* @remarks
|
|
378
|
+
* Each {@link presentLoading} increments a counter and each {@link dismissLoading} decrements it; the
|
|
379
|
+
* indicator is presented on the `0 → 1` transition and dismissed on the `N → 0` transition. This
|
|
380
|
+
* removes the flicker / "stuck spinner" bugs that come from every service calling
|
|
381
|
+
* `LoadingController.create/dismiss` independently.
|
|
382
|
+
*
|
|
383
|
+
* All operations are serialized through an internal promise chain, so a `create → present` sequence
|
|
384
|
+
* can never interleave with a concurrent `dismiss`. That is what makes the counter race-safe: a
|
|
385
|
+
* dismiss that arrives while the indicator is still being presented runs *after* `present()` settles
|
|
386
|
+
* and therefore tears the element down instead of leaving it orphaned on screen.
|
|
387
|
+
*
|
|
388
|
+
* Always pair every `presentLoading()` with exactly one `dismissLoading()` — a `try/finally` is the
|
|
389
|
+
* safest shape.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* constructor(private readonly loading: KitLoadingController) {}
|
|
394
|
+
*
|
|
395
|
+
* async save(): Promise<void> {
|
|
396
|
+
* await this.loading.presentLoading({ message: 'Saving…' });
|
|
397
|
+
* try {
|
|
398
|
+
* await this.api.save();
|
|
399
|
+
* } finally {
|
|
400
|
+
* await this.loading.dismissLoading();
|
|
401
|
+
* }
|
|
402
|
+
* }
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
declare class KitLoadingController {
|
|
406
|
+
#private;
|
|
407
|
+
/**
|
|
408
|
+
* Show the loading indicator, or join the one already on screen.
|
|
409
|
+
*
|
|
410
|
+
* @param options - Ionic loading options; only applied by the call that actually creates the
|
|
411
|
+
* indicator (the `0 → 1` transition). Ignored while an indicator is already present.
|
|
412
|
+
* @returns a Promise that resolves once the indicator is on screen (or immediately when one already is)
|
|
413
|
+
*/
|
|
414
|
+
presentLoading(options?: LoadingOptions): Promise<void>;
|
|
415
|
+
/**
|
|
416
|
+
* Release one reference; dismiss the indicator once the last reference is gone.
|
|
417
|
+
*
|
|
418
|
+
* @returns a Promise that resolves once the reference is released (and the indicator dismissed if
|
|
419
|
+
* this was the last one). No-ops when the counter is already at zero.
|
|
420
|
+
*/
|
|
421
|
+
dismissLoading(): Promise<void>;
|
|
422
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<KitLoadingController, never>;
|
|
423
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<KitLoadingController>;
|
|
424
|
+
}
|
|
425
|
+
|
|
290
426
|
/**
|
|
291
427
|
* Content for {@link KitReloadAlertController.present}.
|
|
292
428
|
*/
|
|
@@ -1331,5 +1467,5 @@ declare const kitChangeEventDisabled: (completeEvent: WritableSignal<HTMLIonInfi
|
|
|
1331
1467
|
*/
|
|
1332
1468
|
declare const kitCreateDidEnter: (el: ElementRef) => Observable<boolean>;
|
|
1333
1469
|
|
|
1334
|
-
export { KIT_AUTH_CONFIG, KIT_HTTP_CONFIG, KIT_OVERLAY_CONFIG, KIT_THEME_CONFIG, KitAutofillDirective, KitOverlayController, KitReloadAlertController, KitStorageService, KitThemeController, arrayConcatById, disableHandler, kitAuthInterceptor, kitBuildBrotherPrintSettings, kitChangeEventDisabled, kitCreateDidEnter, kitDomToPng, kitImpact, kitKeyboardInit, kitPresentAuthFailedAlert, kitPresentLanguageActionSheet, kitRequestReview, kitRequireAuthorizedGuard, kitRequireConfirmingGuard, kitRequiredUnauthorizedGuard, kitRotationImage, objectEqual, provideKitAuth, provideKitHttp, provideKitOverlay, provideKitTheme };
|
|
1335
|
-
export type { KitAlertCloseOptions, KitAlertConfirmOptions, KitAuthConfig, KitAuthFailedAlertOptions, KitAuthRedirects, KitAuthState, KitBrotherPrintSettingsParams, KitDomToPngOptions, KitHttpConfig, KitKeyboardAdjust, KitLabels, KitLanguageActionSheetOptions, KitLanguageOption, KitModalPresentOptions, KitOverlayConfig, KitReloadAlertOptions, KitRequestReviewOptions, KitThemeConfig, KitThemeMode };
|
|
1470
|
+
export { KIT_AUTH_CONFIG, KIT_HTTP_CONFIG, KIT_OVERLAY_CONFIG, KIT_THEME_CONFIG, KitAutofillDirective, KitLoadingController, KitOverlayController, KitReloadAlertController, KitStorageService, KitThemeController, arrayConcatById, disableHandler, kitAuthInterceptor, kitBuildBrotherPrintSettings, kitChangeEventDisabled, kitCreateDidEnter, kitDomToPng, kitImpact, kitKeyboardInit, kitPresentAuthFailedAlert, kitPresentLanguageActionSheet, kitRequestReview, kitRequireAuthorizedGuard, kitRequireConfirmingGuard, kitRequiredUnauthorizedGuard, kitRotationImage, objectEqual, provideKitAuth, provideKitHttp, provideKitOverlay, provideKitTheme };
|
|
1471
|
+
export type { KitAlertCloseOptions, KitAlertConfirmOptions, KitAuthConfig, KitAuthFailedAlertOptions, KitAuthRedirects, KitAuthState, KitBrotherPrintSettingsParams, KitDomToPngOptions, KitHttpConfig, KitKeyboardAdjust, KitLabels, KitLanguageActionSheetOptions, KitLanguageOption, KitModalPresentOptions, KitOverlayConfig, KitReloadAlertOptions, KitRequestReviewOptions, KitThemeConfig, KitThemeMode, ModalMetadata };
|