@rdlabo/ionic-angular-kit 0.0.13 → 0.0.15
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 +16 -10
- package/fesm2022/rdlabo-ionic-angular-kit-printer.mjs +146 -0
- package/fesm2022/rdlabo-ionic-angular-kit-printer.mjs.map +1 -0
- package/fesm2022/rdlabo-ionic-angular-kit-review.mjs +48 -0
- package/fesm2022/rdlabo-ionic-angular-kit-review.mjs.map +1 -0
- package/fesm2022/rdlabo-ionic-angular-kit-theme.mjs +163 -0
- package/fesm2022/rdlabo-ionic-angular-kit-theme.mjs.map +1 -0
- package/fesm2022/rdlabo-ionic-angular-kit.mjs +122 -349
- package/fesm2022/rdlabo-ionic-angular-kit.mjs.map +1 -1
- package/package.json +30 -1
- package/types/rdlabo-ionic-angular-kit-printer.d.ts +81 -0
- package/types/rdlabo-ionic-angular-kit-review.d.ts +45 -0
- package/types/rdlabo-ionic-angular-kit-theme.d.ts +116 -0
- package/types/rdlabo-ionic-angular-kit.d.ts +144 -239
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Injectable, InjectionToken, makeEnvironmentProviders, ElementRef, Directive
|
|
2
|
+
import { inject, Injectable, InjectionToken, makeEnvironmentProviders, ElementRef, Directive } 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';
|
|
8
|
-
import { StatusBar, Style } from '@capacitor/status-bar';
|
|
9
|
-
import { BehaviorSubject, from, throwError, retry, timer, Observable, startWith } from 'rxjs';
|
|
10
|
-
import { Preferences } from '@capacitor/preferences';
|
|
11
|
-
import { InAppReview } from '@capacitor-community/in-app-review';
|
|
12
|
-
import { BRLMPrinterHalftone, BRLMPrinterCustomPaperUnit, BRLMPrinterCustomPaperType, BRLMPrinterPrintQuality, BRLMPrinterHorizontalAlignment, BRLMPrinterVerticalAlignment, BRLMPrinterImageRotation, BRLMPrinterScaleMode } from '@rdlabo/capacitor-brotherprint';
|
|
13
|
-
import domtoimage from 'dom-to-image-more';
|
|
14
8
|
import { Router } from '@angular/router';
|
|
15
9
|
import { map, mergeMap, catchError, timeout, tap } from 'rxjs/operators';
|
|
16
10
|
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
|
|
17
11
|
import { Network } from '@capacitor/network';
|
|
12
|
+
import { from, throwError, retry, timer, Observable, startWith } from 'rxjs';
|
|
18
13
|
|
|
19
14
|
/**
|
|
20
15
|
* Thin, typed wrapper around `@ionic/storage-angular`.
|
|
@@ -185,7 +180,7 @@ const watchModalKeyboard = async (modal) => {
|
|
|
185
180
|
* constructor(private readonly overlay: KitOverlayController) {}
|
|
186
181
|
*
|
|
187
182
|
* async edit(): Promise<void> {
|
|
188
|
-
* const result = await this.overlay.presentModal
|
|
183
|
+
* const result = await this.overlay.presentModal(EditPage, { id: 1 });
|
|
189
184
|
* if (result) {
|
|
190
185
|
* await this.overlay.presentToast({ message: 'Saved' });
|
|
191
186
|
* }
|
|
@@ -204,22 +199,6 @@ class KitOverlayController {
|
|
|
204
199
|
* second alert on top of the first. Shared across both methods so a confirm cannot stack over a close.
|
|
205
200
|
*/
|
|
206
201
|
#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
202
|
async presentModal(component, componentProps, options = {}) {
|
|
224
203
|
void kitImpact();
|
|
225
204
|
const { watchKeyboard, ...modalOptions } = options;
|
|
@@ -381,6 +360,119 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
381
360
|
}]
|
|
382
361
|
}] });
|
|
383
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Reference-counted wrapper around Ionic's `LoadingController` that keeps at most one loading
|
|
365
|
+
* indicator on screen across concurrent async work.
|
|
366
|
+
*
|
|
367
|
+
* @remarks
|
|
368
|
+
* Each {@link presentLoading} increments a counter and each {@link dismissLoading} decrements it; the
|
|
369
|
+
* indicator is presented on the `0 → 1` transition and dismissed on the `N → 0` transition. This
|
|
370
|
+
* removes the flicker / "stuck spinner" bugs that come from every service calling
|
|
371
|
+
* `LoadingController.create/dismiss` independently.
|
|
372
|
+
*
|
|
373
|
+
* All operations are serialized through an internal promise chain, so a `create → present` sequence
|
|
374
|
+
* can never interleave with a concurrent `dismiss`. That is what makes the counter race-safe: a
|
|
375
|
+
* dismiss that arrives while the indicator is still being presented runs *after* `present()` settles
|
|
376
|
+
* and therefore tears the element down instead of leaving it orphaned on screen.
|
|
377
|
+
*
|
|
378
|
+
* Always pair every `presentLoading()` with exactly one `dismissLoading()` — a `try/finally` is the
|
|
379
|
+
* safest shape.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```ts
|
|
383
|
+
* constructor(private readonly loading: KitLoadingController) {}
|
|
384
|
+
*
|
|
385
|
+
* async save(): Promise<void> {
|
|
386
|
+
* await this.loading.presentLoading({ message: 'Saving…' });
|
|
387
|
+
* try {
|
|
388
|
+
* await this.api.save();
|
|
389
|
+
* } finally {
|
|
390
|
+
* await this.loading.dismissLoading();
|
|
391
|
+
* }
|
|
392
|
+
* }
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
class KitLoadingController {
|
|
396
|
+
#loadingCtrl = inject(LoadingController);
|
|
397
|
+
/** Outstanding {@link presentLoading} calls not yet balanced by {@link dismissLoading}. */
|
|
398
|
+
#count = 0;
|
|
399
|
+
/** The single presented loading element, or `null` when none is on screen. */
|
|
400
|
+
#loading = null;
|
|
401
|
+
/**
|
|
402
|
+
* Serializes present/dismiss operations. Each call chains onto this promise, runs after the
|
|
403
|
+
* previous operation has fully settled, then reads {@link #count} and acts accordingly.
|
|
404
|
+
*/
|
|
405
|
+
#queue = Promise.resolve();
|
|
406
|
+
/**
|
|
407
|
+
* Show the loading indicator, or join the one already on screen.
|
|
408
|
+
*
|
|
409
|
+
* @param options - Ionic loading options; only applied by the call that actually creates the
|
|
410
|
+
* indicator (the `0 → 1` transition). Ignored while an indicator is already present.
|
|
411
|
+
* @returns a Promise that resolves once the indicator is on screen (or immediately when one already is)
|
|
412
|
+
*/
|
|
413
|
+
async presentLoading(options = {}) {
|
|
414
|
+
this.#count++;
|
|
415
|
+
try {
|
|
416
|
+
await this.#enqueue(async () => {
|
|
417
|
+
// Create only on the transition into "something is loading"; concurrent callers ride the same
|
|
418
|
+
// element. Re-check the count in case a dismiss already balanced this call while queued.
|
|
419
|
+
if (this.#count > 0 && this.#loading === null) {
|
|
420
|
+
const loading = await this.#loadingCtrl.create(options);
|
|
421
|
+
await loading.present();
|
|
422
|
+
this.#loading = loading;
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
catch (error) {
|
|
427
|
+
// Roll back the reference this call took: a failed create/present must not leave the counter
|
|
428
|
+
// elevated, otherwise a later cycle never reaches N → 0 and the spinner stays stuck on screen.
|
|
429
|
+
this.#count--;
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Release one reference; dismiss the indicator once the last reference is gone.
|
|
435
|
+
*
|
|
436
|
+
* @returns a Promise that resolves once the reference is released (and the indicator dismissed if
|
|
437
|
+
* this was the last one). No-ops when the counter is already at zero.
|
|
438
|
+
*/
|
|
439
|
+
async dismissLoading() {
|
|
440
|
+
if (this.#count === 0) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
this.#count--;
|
|
444
|
+
await this.#enqueue(async () => {
|
|
445
|
+
// Tear down only when the last consumer is gone. Because this runs after any in-flight
|
|
446
|
+
// present() has settled (via the queue), there is never an orphaned loading element.
|
|
447
|
+
if (this.#count === 0 && this.#loading !== null) {
|
|
448
|
+
const loading = this.#loading;
|
|
449
|
+
this.#loading = null;
|
|
450
|
+
await loading.dismiss();
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Append `task` to the serialization chain and return its completion.
|
|
456
|
+
*
|
|
457
|
+
* @remarks
|
|
458
|
+
* The stored chain swallows rejections so a single failing operation cannot wedge every future
|
|
459
|
+
* overlay; the returned promise still rejects so the caller observes the error.
|
|
460
|
+
*/
|
|
461
|
+
#enqueue(task) {
|
|
462
|
+
const run = this.#queue.then(task);
|
|
463
|
+
this.#queue = run.then(() => undefined, () => undefined);
|
|
464
|
+
return run;
|
|
465
|
+
}
|
|
466
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KitLoadingController, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
467
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KitLoadingController, providedIn: 'root' }); }
|
|
468
|
+
}
|
|
469
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KitLoadingController, decorators: [{
|
|
470
|
+
type: Injectable,
|
|
471
|
+
args: [{
|
|
472
|
+
providedIn: 'root',
|
|
473
|
+
}]
|
|
474
|
+
}] });
|
|
475
|
+
|
|
384
476
|
/**
|
|
385
477
|
* The fleet's canonical "network error → offer to reload" alert, as a stateful controller.
|
|
386
478
|
*
|
|
@@ -738,323 +830,6 @@ const kitKeyboardInit = async (elementRef, type) => {
|
|
|
738
830
|
];
|
|
739
831
|
};
|
|
740
832
|
|
|
741
|
-
/**
|
|
742
|
-
* Injection token carrying the {@link KitThemeConfig} for `KitThemeController`.
|
|
743
|
-
*
|
|
744
|
-
* @remarks
|
|
745
|
-
* Provide it through {@link provideKitTheme} rather than registering it directly.
|
|
746
|
-
*/
|
|
747
|
-
const KIT_THEME_CONFIG = new InjectionToken('@rdlabo/ionic-angular-kit:theme');
|
|
748
|
-
/**
|
|
749
|
-
* Wire `KitThemeController` into the application.
|
|
750
|
-
*
|
|
751
|
-
* @param config - theme configuration: the storage key and the light/dark class lists
|
|
752
|
-
* @returns environment providers to add to the application's provider list
|
|
753
|
-
* @example
|
|
754
|
-
* ```ts
|
|
755
|
-
* bootstrapApplication(AppComponent, {
|
|
756
|
-
* providers: [
|
|
757
|
-
* provideKitTheme({
|
|
758
|
-
* storageKey: StorageKeyEnum.theme,
|
|
759
|
-
* darkClasses: ['ion-palette-dark', 'a2ui-dark'],
|
|
760
|
-
* lightClasses: ['a2ui-light'],
|
|
761
|
-
* }),
|
|
762
|
-
* ],
|
|
763
|
-
* });
|
|
764
|
-
* ```
|
|
765
|
-
*/
|
|
766
|
-
const provideKitTheme = (config) => makeEnvironmentProviders([{ provide: KIT_THEME_CONFIG, useValue: config }]);
|
|
767
|
-
|
|
768
|
-
/**
|
|
769
|
-
* Light/dark theme controller: persists the user's choice, follows the OS setting until the user
|
|
770
|
-
* overrides it, toggles the configured palette classes, and syncs the native Android status bar.
|
|
771
|
-
*
|
|
772
|
-
* @remarks
|
|
773
|
-
* Consolidates the theme logic that had drifted across the fleet into one behavior. Notably it fixes
|
|
774
|
-
* a latent leak in one variant where the system-theme listener stayed registered after a manual
|
|
775
|
-
* toggle: {@link changeTheme} always detaches the listener via {@link removeEventListener} before
|
|
776
|
-
* applying the forced theme, so a later OS change can no longer silently flip an app the user pinned.
|
|
777
|
-
*
|
|
778
|
-
* - **Persistence** — the chosen mode is stored via {@link KitStorageService} under the configured key.
|
|
779
|
-
* - **Follow OS until overridden** — on boot with nothing stored, it tracks
|
|
780
|
-
* `prefers-color-scheme` (idempotent registration); once the user calls {@link changeTheme} it stops
|
|
781
|
-
* following and honors the explicit choice.
|
|
782
|
-
* - **Class toggling** — toggles {@link KitThemeConfig.darkClasses} on when dark and
|
|
783
|
-
* {@link KitThemeConfig.lightClasses} on when light, absorbing per-app CSS differences via config.
|
|
784
|
-
* - **Native status bar** — on Android native only, mirrors the Ionic behavior of setting the status
|
|
785
|
-
* bar style to match (iOS derives it from the web content, so it is intentionally left untouched).
|
|
786
|
-
*
|
|
787
|
-
* Subscribe to {@link themeSubject} to reflect the current mode in the UI (e.g. a settings toggle).
|
|
788
|
-
*
|
|
789
|
-
* @example
|
|
790
|
-
* ```ts
|
|
791
|
-
* // On boot (app.component):
|
|
792
|
-
* inject(KitThemeController).setDefaultThemeMode();
|
|
793
|
-
*
|
|
794
|
-
* // From a settings toggle:
|
|
795
|
-
* const theme = inject(KitThemeController);
|
|
796
|
-
* theme.themeSubject.subscribe((mode) => this.isDark.set(mode === 'dark'));
|
|
797
|
-
* theme.changeTheme(true);
|
|
798
|
-
* ```
|
|
799
|
-
*/
|
|
800
|
-
class KitThemeController {
|
|
801
|
-
constructor() {
|
|
802
|
-
this.#storage = inject(KitStorageService);
|
|
803
|
-
this.#document = inject(DOCUMENT);
|
|
804
|
-
this.#config = inject(KIT_THEME_CONFIG);
|
|
805
|
-
/**
|
|
806
|
-
* Emits the active theme, seeded with `'light'`.
|
|
807
|
-
*
|
|
808
|
-
* @remarks
|
|
809
|
-
* A `BehaviorSubject`, so a late subscriber immediately receives the current mode; it emits again
|
|
810
|
-
* on every {@link setDefaultThemeMode} / {@link changeTheme} and on OS theme changes while following.
|
|
811
|
-
*/
|
|
812
|
-
this.themeSubject = new BehaviorSubject('light');
|
|
813
|
-
}
|
|
814
|
-
#storage;
|
|
815
|
-
#document;
|
|
816
|
-
#config;
|
|
817
|
-
#prefersDark;
|
|
818
|
-
#onSystemThemeChange;
|
|
819
|
-
/**
|
|
820
|
-
* Apply the persisted theme, or start following the OS setting when nothing is stored yet.
|
|
821
|
-
*
|
|
822
|
-
* @remarks
|
|
823
|
-
* Call once on boot (e.g. from `app.component`).
|
|
824
|
-
*
|
|
825
|
-
* @returns a Promise that resolves once the initial theme has been applied
|
|
826
|
-
*/
|
|
827
|
-
async setDefaultThemeMode() {
|
|
828
|
-
const stored = await this.#storage.get(this.#config.storageKey);
|
|
829
|
-
if (stored) {
|
|
830
|
-
// 保存済みの選択を強制し、OS 追従は解除する。
|
|
831
|
-
this.#unwatchSystemTheme();
|
|
832
|
-
return this.#applyTheme(stored === 'dark');
|
|
833
|
-
}
|
|
834
|
-
// 未保存 → OS の設定に追従する。
|
|
835
|
-
this.#watchSystemTheme();
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Force a theme, persist it, and stop following the OS setting.
|
|
839
|
-
*
|
|
840
|
-
* @param isDark - `true` for the dark theme, `false` for light
|
|
841
|
-
* @returns a Promise that resolves once the theme has been persisted and applied
|
|
842
|
-
*/
|
|
843
|
-
async changeTheme(isDark) {
|
|
844
|
-
this.#unwatchSystemTheme();
|
|
845
|
-
await this.#storage.set(this.#config.storageKey, isDark ? 'dark' : 'light');
|
|
846
|
-
await this.#applyTheme(isDark);
|
|
847
|
-
}
|
|
848
|
-
#watchSystemTheme() {
|
|
849
|
-
if (this.#prefersDark) {
|
|
850
|
-
// 既に監視中なら二重登録しない(冪等)。
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
this.#prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
|
|
854
|
-
this.#onSystemThemeChange = (e) => void this.#applyTheme(e.matches);
|
|
855
|
-
this.#prefersDark.addEventListener('change', this.#onSystemThemeChange, { passive: true });
|
|
856
|
-
void this.#applyTheme(this.#prefersDark.matches);
|
|
857
|
-
}
|
|
858
|
-
#unwatchSystemTheme() {
|
|
859
|
-
if (this.#prefersDark && this.#onSystemThemeChange) {
|
|
860
|
-
this.#prefersDark.removeEventListener('change', this.#onSystemThemeChange);
|
|
861
|
-
}
|
|
862
|
-
this.#prefersDark = undefined;
|
|
863
|
-
this.#onSystemThemeChange = undefined;
|
|
864
|
-
}
|
|
865
|
-
async #applyTheme(isDark) {
|
|
866
|
-
if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'android') {
|
|
867
|
-
await StatusBar.setStyle({ style: isDark ? Style.Dark : Style.Light });
|
|
868
|
-
}
|
|
869
|
-
const root = this.#document.documentElement;
|
|
870
|
-
for (const cls of this.#config.darkClasses) {
|
|
871
|
-
root.classList.toggle(cls, isDark);
|
|
872
|
-
}
|
|
873
|
-
for (const cls of this.#config.lightClasses) {
|
|
874
|
-
root.classList.toggle(cls, !isDark);
|
|
875
|
-
}
|
|
876
|
-
this.themeSubject.next(isDark ? 'dark' : 'light');
|
|
877
|
-
}
|
|
878
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KitThemeController, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
879
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KitThemeController, providedIn: 'root' }); }
|
|
880
|
-
}
|
|
881
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KitThemeController, decorators: [{
|
|
882
|
-
type: Injectable,
|
|
883
|
-
args: [{
|
|
884
|
-
providedIn: 'root',
|
|
885
|
-
}]
|
|
886
|
-
}] });
|
|
887
|
-
|
|
888
|
-
/**
|
|
889
|
-
* Request the native in-app review dialog, throttled so the user is prompted at most once per window.
|
|
890
|
-
*
|
|
891
|
-
* @remarks
|
|
892
|
-
* A plain function — no DI needed (`@capacitor/preferences`, `@capacitor-community/in-app-review` and
|
|
893
|
-
* `Capacitor` are all static), so the caller invokes it directly and passes its own config rather
|
|
894
|
-
* than injecting a controller. A no-op on non-native platforms. When enough time has elapsed since
|
|
895
|
-
* the last prompt (per {@link KitRequestReviewOptions.throttleMonths}, tracked under
|
|
896
|
-
* {@link KitRequestReviewOptions.storageKey}), it briefly waits for the app to settle, calls
|
|
897
|
-
* `InAppReview.requestReview()`, and records the new timestamp. The wait/throttle/record sequence
|
|
898
|
-
* was previously copy-pasted verbatim across the fleet; centralizing it means a single place to tune
|
|
899
|
-
* the prompt cadence.
|
|
900
|
-
*
|
|
901
|
-
* @param options - the storage key and throttle window; see {@link KitRequestReviewOptions}
|
|
902
|
-
* @returns a Promise that resolves once the request has been made (or immediately if throttled / on web)
|
|
903
|
-
* @example
|
|
904
|
-
* ```ts
|
|
905
|
-
* await kitRequestReview({ storageKey: StorageEnum.lastRequestRate, throttleMonths: 3 });
|
|
906
|
-
* ```
|
|
907
|
-
*/
|
|
908
|
-
const kitRequestReview = async (options) => {
|
|
909
|
-
if (!Capacitor.isNativePlatform()) {
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
await new Promise((resolve) => setTimeout(() => resolve(), 1000));
|
|
913
|
-
const threshold = new Date();
|
|
914
|
-
threshold.setMonth(threshold.getMonth() - options.throttleMonths);
|
|
915
|
-
const { value } = await Preferences.get({ key: options.storageKey });
|
|
916
|
-
if (!value || new Date(Number(value)).getTime() < threshold.getTime()) {
|
|
917
|
-
await InAppReview.requestReview();
|
|
918
|
-
await Preferences.set({ key: options.storageKey, value: new Date().getTime().toString() });
|
|
919
|
-
}
|
|
920
|
-
};
|
|
921
|
-
|
|
922
|
-
/**
|
|
923
|
-
* Rotate a base64 image 90°, returning a new base64 data URL of the same MIME type.
|
|
924
|
-
*
|
|
925
|
-
* @remarks
|
|
926
|
-
* Pure DOM/canvas work — no DI. Used before sending a label to the printer when the artwork must be
|
|
927
|
-
* turned to match the tape orientation. Extracted verbatim from the fleet's printer services so the
|
|
928
|
-
* canvas handling lives in one place.
|
|
929
|
-
*
|
|
930
|
-
* @param imageData - a base64 data URL (e.g. `data:image/png;base64,...`)
|
|
931
|
-
* @returns a Promise resolving to the rotated image as a base64 data URL
|
|
932
|
-
*/
|
|
933
|
-
const kitRotationImage = async (imageData) => {
|
|
934
|
-
const imgType = imageData.substring(5, imageData.indexOf(';'));
|
|
935
|
-
const image = new Image();
|
|
936
|
-
const loaded = () => new Promise((resolve) => {
|
|
937
|
-
image.onload = () => resolve();
|
|
938
|
-
});
|
|
939
|
-
setTimeout(() => (image.src = imageData));
|
|
940
|
-
await loaded();
|
|
941
|
-
const canvas = document.createElement('canvas');
|
|
942
|
-
const ctx = canvas.getContext('2d');
|
|
943
|
-
canvas.width = image.height;
|
|
944
|
-
canvas.height = image.width;
|
|
945
|
-
ctx.rotate((90 * Math.PI) / 180);
|
|
946
|
-
ctx.translate(0, -image.height);
|
|
947
|
-
ctx.drawImage(image, 0, 0, image.width, image.height);
|
|
948
|
-
return canvas.toDataURL(imgType);
|
|
949
|
-
};
|
|
950
|
-
/**
|
|
951
|
-
* Render a DOM element to a base64 PNG for label printing, with the fleet's device-specific fixes.
|
|
952
|
-
*
|
|
953
|
-
* @remarks
|
|
954
|
-
* Pure function — no DI (reads the platform from `Capacitor`, uses the global `document`), so the
|
|
955
|
-
* caller presents its own loading UI around it. Centralizes the hard-won device quirks: on iOS it
|
|
956
|
-
* pads width/height by 2px (otherwise the bottom is clipped), on Android it does not (the padding
|
|
957
|
-
* introduces a black line). Retries the `dom-to-image-more` render up to 10 times because the first
|
|
958
|
-
* pass can occasionally return empty. This is exactly the kind of plumbing where a future fix should
|
|
959
|
-
* land in every app at once.
|
|
960
|
-
*
|
|
961
|
-
* @param element - the element to rasterize (e.g. the label preview host)
|
|
962
|
-
* @param options - rendering options; see {@link KitDomToPngOptions}
|
|
963
|
-
* @returns a Promise resolving to the PNG as a base64 data URL (empty string if every attempt failed)
|
|
964
|
-
* @example
|
|
965
|
-
* ```ts
|
|
966
|
-
* const loading = await this.#loadingCtrl.create({ message: this.text.generating });
|
|
967
|
-
* await loading.present();
|
|
968
|
-
* const png = await kitDomToPng(this.preview().nativeElement, { rotate: true });
|
|
969
|
-
* await loading.dismiss();
|
|
970
|
-
* ```
|
|
971
|
-
*/
|
|
972
|
-
const kitDomToPng = async (element, options) => {
|
|
973
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
974
|
-
const { clientHeight, clientWidth } = element;
|
|
975
|
-
// デバイス毎の問題解決のため、px 調整。
|
|
976
|
-
// iOS: ないと下が途切れる。Android: あると黒線が入る。
|
|
977
|
-
const addClient = Capacitor.getPlatform() === 'ios' ? 2 : 0;
|
|
978
|
-
const dataUrl = await new Promise((resolve) => {
|
|
979
|
-
void (async () => {
|
|
980
|
-
for (let i = 0; i < 10; i++) {
|
|
981
|
-
const url = await domtoimage.toPng(element, {
|
|
982
|
-
width: clientWidth + addClient,
|
|
983
|
-
height: clientHeight + addClient,
|
|
984
|
-
scale: options?.scale ?? 3,
|
|
985
|
-
copyDefaultStyles: false,
|
|
986
|
-
});
|
|
987
|
-
if (url) {
|
|
988
|
-
resolve(url);
|
|
989
|
-
return;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
resolve('');
|
|
993
|
-
})();
|
|
994
|
-
});
|
|
995
|
-
return options?.rotate ? kitRotationImage(dataUrl) : dataUrl;
|
|
996
|
-
};
|
|
997
|
-
/**
|
|
998
|
-
* Assemble the Brother `BRLMPrintOptions` for a die-cut label print, minus the transport fields.
|
|
999
|
-
*
|
|
1000
|
-
* @remarks
|
|
1001
|
-
* Pure function — no DI. Centralizes the fleet's canonical print settings (fit-page scale, centered,
|
|
1002
|
-
* best quality, threshold halftone, 2mm/1mm margins, `gapLength` 2.0) and the tape sizing derived
|
|
1003
|
-
* from the label's `W<width>H<height>` code. The caller merges the printer's `port` / `channelInfo`
|
|
1004
|
-
* onto the result before calling `BrotherPrint.printImage()`, so channel selection and loading UI stay
|
|
1005
|
-
* in the app.
|
|
1006
|
-
*
|
|
1007
|
-
* @param params - model, artwork, label, copies, and halftone threshold; see {@link KitBrotherPrintSettingsParams}
|
|
1008
|
-
* @returns the `BRLMPrintOptions` ready to be spread with `{ port, channelInfo }`
|
|
1009
|
-
* @example
|
|
1010
|
-
* ```ts
|
|
1011
|
-
* const settings = kitBuildBrotherPrintSettings({
|
|
1012
|
-
* modelName, printBase64, label, numberOfCopies: printOptions.printNum, halftoneThreshold: printOptions.halftoneThreshold,
|
|
1013
|
-
* });
|
|
1014
|
-
* await BrotherPrint.printImage({ ...settings, port: channel.port, channelInfo: channel.channelInfo });
|
|
1015
|
-
* ```
|
|
1016
|
-
*/
|
|
1017
|
-
const kitBuildBrotherPrintSettings = (params) => {
|
|
1018
|
-
const startPoint = params.printBase64.indexOf(',');
|
|
1019
|
-
const tapeSize = params.label.match(/W(\d+)H(\d+)/);
|
|
1020
|
-
const tapeWidth = tapeSize && tapeSize.length >= 2 ? parseInt(tapeSize[1], 10) : 0;
|
|
1021
|
-
const tapeLength = tapeSize && tapeSize.length >= 3 ? parseInt(tapeSize[2], 10) : 0;
|
|
1022
|
-
// `BRLMPrintOptions` is a `QL | TD` union; a die-cut label legitimately carries fields from both
|
|
1023
|
-
// groups, so the object is composed via spreads to bypass the union's excess-property checks — the
|
|
1024
|
-
// same technique the source printer services used.
|
|
1025
|
-
return {
|
|
1026
|
-
...{
|
|
1027
|
-
modelName: params.modelName,
|
|
1028
|
-
encodedImage: params.printBase64.slice(startPoint + 1),
|
|
1029
|
-
numberOfCopies: params.numberOfCopies,
|
|
1030
|
-
autoCut: true,
|
|
1031
|
-
scaleMode: BRLMPrinterScaleMode.FitPageAspect,
|
|
1032
|
-
imageRotation: BRLMPrinterImageRotation.Rotate0,
|
|
1033
|
-
verticalAlignment: BRLMPrinterVerticalAlignment.Center,
|
|
1034
|
-
horizontalAlignment: BRLMPrinterHorizontalAlignment.Center,
|
|
1035
|
-
printQuality: BRLMPrinterPrintQuality.Best,
|
|
1036
|
-
},
|
|
1037
|
-
...{
|
|
1038
|
-
labelName: params.label,
|
|
1039
|
-
},
|
|
1040
|
-
...{
|
|
1041
|
-
paperType: BRLMPrinterCustomPaperType.dieCutPaper,
|
|
1042
|
-
paperUnit: BRLMPrinterCustomPaperUnit.mm,
|
|
1043
|
-
halftone: BRLMPrinterHalftone.Threshold,
|
|
1044
|
-
halftoneThreshold: params.halftoneThreshold,
|
|
1045
|
-
tapeWidth: Number(tapeWidth.toFixed(1)),
|
|
1046
|
-
tapeLength: Number(tapeLength.toFixed(1)),
|
|
1047
|
-
gapLength: 2.0,
|
|
1048
|
-
marginTop: 1.0,
|
|
1049
|
-
marginRight: 2.0,
|
|
1050
|
-
marginBottom: 1.0,
|
|
1051
|
-
marginLeft: 2.0,
|
|
1052
|
-
paperMarkPosition: 0,
|
|
1053
|
-
paperMarkLength: 0,
|
|
1054
|
-
},
|
|
1055
|
-
};
|
|
1056
|
-
};
|
|
1057
|
-
|
|
1058
833
|
/**
|
|
1059
834
|
* Injection token that carries the {@link KitAuthConfig} to the authentication guards.
|
|
1060
835
|
*/
|
|
@@ -1561,12 +1336,10 @@ const disableHandler = async (event, work) => {
|
|
|
1561
1336
|
* ```
|
|
1562
1337
|
*/
|
|
1563
1338
|
const kitChangeEventDisabled = (completeEvent, disabled) => {
|
|
1564
|
-
completeEvent
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
return event;
|
|
1569
|
-
});
|
|
1339
|
+
const event = completeEvent();
|
|
1340
|
+
if (event) {
|
|
1341
|
+
event.disabled = disabled;
|
|
1342
|
+
}
|
|
1570
1343
|
};
|
|
1571
1344
|
|
|
1572
1345
|
/**
|
|
@@ -1613,5 +1386,5 @@ const kitCreateDidEnter = (el) => {
|
|
|
1613
1386
|
* Generated bundle index. Do not edit.
|
|
1614
1387
|
*/
|
|
1615
1388
|
|
|
1616
|
-
export { KIT_AUTH_CONFIG, KIT_HTTP_CONFIG, KIT_OVERLAY_CONFIG,
|
|
1389
|
+
export { KIT_AUTH_CONFIG, KIT_HTTP_CONFIG, KIT_OVERLAY_CONFIG, KitAutofillDirective, KitLoadingController, KitOverlayController, KitReloadAlertController, KitStorageService, arrayConcatById, disableHandler, kitAuthInterceptor, kitChangeEventDisabled, kitCreateDidEnter, kitImpact, kitKeyboardInit, kitPresentAuthFailedAlert, kitPresentLanguageActionSheet, kitRequireAuthorizedGuard, kitRequireConfirmingGuard, kitRequiredUnauthorizedGuard, objectEqual, provideKitAuth, provideKitHttp, provideKitOverlay };
|
|
1617
1390
|
//# sourceMappingURL=rdlabo-ionic-angular-kit.mjs.map
|