@rdlabo/ionic-angular-kit 0.0.2 → 0.0.3
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 +20 -8
- package/ng-package.json +7 -0
- package/package.json +3 -15
- package/src/lib/auth/auth-guards.spec.ts +210 -0
- package/src/lib/auth/auth-guards.ts +227 -0
- package/src/lib/directives/autofill.directive.ts +68 -0
- package/src/lib/http/kit-http.interceptor.spec.ts +300 -0
- package/src/lib/http/kit-http.interceptor.ts +236 -0
- package/src/lib/overlay/kit-overlay.controller.spec.ts +233 -0
- package/src/lib/overlay/kit-overlay.controller.ts +206 -0
- package/src/lib/overlay/kit-reload-alert.controller.spec.ts +105 -0
- package/src/lib/overlay/kit-reload-alert.controller.ts +108 -0
- package/src/lib/overlay/overlay-config.ts +53 -0
- package/src/lib/storage/kit-storage.service.spec.ts +127 -0
- package/src/lib/storage/kit-storage.service.ts +91 -0
- package/src/lib/utils/array.spec.ts +33 -0
- package/src/lib/utils/array.ts +82 -0
- package/src/lib/utils/haptics.ts +32 -0
- package/src/public-api.ts +24 -0
- package/tsconfig.lib.json +14 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +10 -0
- package/fesm2022/rdlabo-ionic-angular-kit.mjs +0 -750
- package/fesm2022/rdlabo-ionic-angular-kit.mjs.map +0 -1
- package/types/rdlabo-ionic-angular-kit.d.ts +0 -733
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { EnvironmentProviders } from '@angular/core';
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* User-visible button labels consumed by `KitOverlayController`.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* The kit deliberately ships no i18n strings of its own and has no hard dependency on
|
|
9
|
+
* `@angular/localize`. The consuming application always provides these labels: multilingual apps
|
|
10
|
+
* pass `$localize`-resolved strings, single-language apps pass plain literals.
|
|
11
|
+
*/
|
|
12
|
+
export interface KitLabels {
|
|
13
|
+
/** Text for the "close" button used by alerts and toasts. */
|
|
14
|
+
readonly close: string;
|
|
15
|
+
/** Text for the "cancel" button used by confirmation alerts. */
|
|
16
|
+
readonly cancel: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Overlay configuration injected via `provideKitOverlay()`.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* All fields are required; the consuming application must supply a complete configuration.
|
|
24
|
+
*/
|
|
25
|
+
export interface KitOverlayConfig {
|
|
26
|
+
/** Button labels used across the overlays presented by `KitOverlayController`. */
|
|
27
|
+
readonly labels: KitLabels;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Injection token carrying the {@link KitOverlayConfig} for `KitOverlayController`.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* Provide it through {@link provideKitOverlay} rather than registering it directly.
|
|
35
|
+
*/
|
|
36
|
+
export const KIT_OVERLAY_CONFIG = new InjectionToken<KitOverlayConfig>('@rdlabo/ionic-angular-kit:overlay');
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Wire `KitOverlayController` into the application by providing its button labels.
|
|
40
|
+
*
|
|
41
|
+
* @param config - overlay configuration, including the button labels to inject
|
|
42
|
+
* @returns environment providers to add to the application's provider list
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* bootstrapApplication(AppComponent, {
|
|
46
|
+
* providers: [
|
|
47
|
+
* provideKitOverlay({ labels: { close: $localize`Close`, cancel: $localize`Cancel` } }),
|
|
48
|
+
* ],
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export const provideKitOverlay = (config: KitOverlayConfig): EnvironmentProviders =>
|
|
53
|
+
makeEnvironmentProviders([{ provide: KIT_OVERLAY_CONFIG, useValue: config }]);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { provideZonelessChangeDetection } from '@angular/core';
|
|
2
|
+
import { TestBed } from '@angular/core/testing';
|
|
3
|
+
|
|
4
|
+
import { KitStorageService } from './kit-storage.service';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Fake storage engine (in-memory Map)
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
class FakeStorageEngine {
|
|
10
|
+
private readonly store = new Map<string, unknown>();
|
|
11
|
+
async set(key: string, value: unknown): Promise<void> {
|
|
12
|
+
this.store.set(key, value);
|
|
13
|
+
}
|
|
14
|
+
async get(key: string): Promise<unknown | null> {
|
|
15
|
+
return this.store.has(key) ? this.store.get(key) : null;
|
|
16
|
+
}
|
|
17
|
+
async remove(key: string): Promise<void> {
|
|
18
|
+
this.store.delete(key);
|
|
19
|
+
}
|
|
20
|
+
async clear(): Promise<void> {
|
|
21
|
+
this.store.clear();
|
|
22
|
+
}
|
|
23
|
+
async keys(): Promise<string[]> {
|
|
24
|
+
return [...this.store.keys()];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Fake @ionic/storage-angular Storage class — create() resolves to FakeStorageEngine.
|
|
29
|
+
// We do NOT vi.mock the module; instead we provide this value directly as the DI token
|
|
30
|
+
// to avoid ESM/transform issues with the real package.
|
|
31
|
+
class FakeStorage {
|
|
32
|
+
create() {
|
|
33
|
+
return Promise.resolve(new FakeStorageEngine());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Tests
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
describe('KitStorageService', () => {
|
|
41
|
+
let service: KitStorageService;
|
|
42
|
+
|
|
43
|
+
// We need the real Storage class as the DI token. Import it lazily to
|
|
44
|
+
// avoid hard-wiring an ESM import at module-top level where Vitest might
|
|
45
|
+
// not have transformed it yet. Using vi.mock avoids the issue entirely.
|
|
46
|
+
beforeEach(async () => {
|
|
47
|
+
// Dynamically import so the vi.mock below (hoisted) takes effect first.
|
|
48
|
+
const { Storage } = await import('@ionic/storage-angular');
|
|
49
|
+
|
|
50
|
+
TestBed.configureTestingModule({
|
|
51
|
+
providers: [provideZonelessChangeDetection(), KitStorageService, { provide: Storage, useValue: new FakeStorage() }],
|
|
52
|
+
});
|
|
53
|
+
service = TestBed.inject(KitStorageService);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('set → get round-trip for string', async () => {
|
|
57
|
+
await service.set<string>('key', 'hello');
|
|
58
|
+
const result = await service.get<string>('key');
|
|
59
|
+
expect(result).toBe('hello');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('set → get round-trip for object', async () => {
|
|
63
|
+
const obj = { a: 1, b: true };
|
|
64
|
+
await service.set('obj', obj);
|
|
65
|
+
const result = await service.get<typeof obj>('obj');
|
|
66
|
+
expect(result).toEqual(obj);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('get returns null for a missing key', async () => {
|
|
70
|
+
const result = await service.get<string>('does-not-exist');
|
|
71
|
+
expect(result).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('remove deletes the key so get returns null', async () => {
|
|
75
|
+
await service.set('x', 42);
|
|
76
|
+
await service.remove('x');
|
|
77
|
+
const result = await service.get<number>('x');
|
|
78
|
+
expect(result).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('clear removes all keys', async () => {
|
|
82
|
+
await service.set('a', 1);
|
|
83
|
+
await service.set('b', 2);
|
|
84
|
+
await service.clear();
|
|
85
|
+
expect(await service.get('a')).toBeNull();
|
|
86
|
+
expect(await service.get('b')).toBeNull();
|
|
87
|
+
expect(await service.keys()).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('keys returns every stored key', async () => {
|
|
91
|
+
await service.set('p', 1);
|
|
92
|
+
await service.set('q', 2);
|
|
93
|
+
const result = await service.keys();
|
|
94
|
+
expect(result).toContain('p');
|
|
95
|
+
expect(result).toContain('q');
|
|
96
|
+
expect(result).toHaveLength(2);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('set called without separate init is not lost (readiness guarantee)', async () => {
|
|
100
|
+
// Create a fresh service in a fresh TestBed for this isolation test.
|
|
101
|
+
// The key contract: the service never requires the caller to separately
|
|
102
|
+
// "await storage.create()" before using it — #ready handles that internally.
|
|
103
|
+
const { Storage } = await import('@ionic/storage-angular');
|
|
104
|
+
let resolveCreate!: (engine: FakeStorageEngine) => void;
|
|
105
|
+
const engine = new FakeStorageEngine();
|
|
106
|
+
const delayedStorage = {
|
|
107
|
+
// create() returns a promise that we control manually
|
|
108
|
+
create: () => new Promise<FakeStorageEngine>((r) => (resolveCreate = r)),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
TestBed.resetTestingModule();
|
|
112
|
+
TestBed.configureTestingModule({
|
|
113
|
+
providers: [provideZonelessChangeDetection(), KitStorageService, { provide: Storage, useValue: delayedStorage }],
|
|
114
|
+
});
|
|
115
|
+
const svc = TestBed.inject(KitStorageService);
|
|
116
|
+
|
|
117
|
+
// Issue a set before storage is ready
|
|
118
|
+
const setPromise = svc.set('early', 'value');
|
|
119
|
+
// Now resolve the storage creation
|
|
120
|
+
resolveCreate(engine);
|
|
121
|
+
await setPromise;
|
|
122
|
+
|
|
123
|
+
// Verify the set was not lost
|
|
124
|
+
const result = await svc.get<string>('early');
|
|
125
|
+
expect(result).toBe('value');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { inject, Injectable } from '@angular/core';
|
|
2
|
+
import { Storage } from '@ionic/storage-angular';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Thin, typed wrapper around `@ionic/storage-angular`.
|
|
6
|
+
*
|
|
7
|
+
* Starts `create()` exactly once and stores the resulting ready Promise. Every operation awaits
|
|
8
|
+
* that Promise before touching the underlying store, so calls made before initialization completes
|
|
9
|
+
* are queued rather than dropped.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* A naive wrapper that reads the store synchronously would silently no-op (or throw) when invoked
|
|
13
|
+
* before `create()` resolves, losing early writes. Awaiting the one-time ready Promise on every
|
|
14
|
+
* operation removes that race without forcing callers to coordinate initialization themselves.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* constructor(private readonly storage: KitStorageService) {}
|
|
19
|
+
*
|
|
20
|
+
* async ngOnInit(): Promise<void> {
|
|
21
|
+
* await this.storage.set('token', 'abc123');
|
|
22
|
+
* const token = await this.storage.get<string>('token');
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
@Injectable({
|
|
27
|
+
providedIn: 'root',
|
|
28
|
+
})
|
|
29
|
+
export class KitStorageService {
|
|
30
|
+
/** One-time `create()` ready Promise; awaited before every operation so early calls are not lost. */
|
|
31
|
+
readonly #ready: Promise<Storage> = inject(Storage).create();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Persist a value under the given key.
|
|
35
|
+
*
|
|
36
|
+
* @typeParam T - type of the value being stored
|
|
37
|
+
* @param key - key to store the value under
|
|
38
|
+
* @param value - value to persist; overwrites any existing value for the key
|
|
39
|
+
* @returns a Promise that resolves once the value has been written
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* await storage.set('user', { id: 1, name: 'Ada' });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
async set<T>(key: string, value: T): Promise<void> {
|
|
46
|
+
await (await this.#ready).set(key, value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read the value stored under the given key.
|
|
51
|
+
*
|
|
52
|
+
* @typeParam T - expected type of the stored value
|
|
53
|
+
* @param key - key to read
|
|
54
|
+
* @returns the stored value, or `null` when the key is absent
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const user = await storage.get<{ id: number }>('user');
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
async get<T>(key: string): Promise<T | null> {
|
|
61
|
+
return (await (await this.#ready).get(key)) ?? null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Remove the value stored under the given key.
|
|
66
|
+
*
|
|
67
|
+
* @param key - key to remove; a no-op when the key is absent
|
|
68
|
+
* @returns a Promise that resolves once the key has been removed
|
|
69
|
+
*/
|
|
70
|
+
async remove(key: string): Promise<void> {
|
|
71
|
+
await (await this.#ready).remove(key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Remove every key/value pair from the store.
|
|
76
|
+
*
|
|
77
|
+
* @returns a Promise that resolves once the store has been emptied
|
|
78
|
+
*/
|
|
79
|
+
async clear(): Promise<void> {
|
|
80
|
+
await (await this.#ready).clear();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* List every key currently present in the store.
|
|
85
|
+
*
|
|
86
|
+
* @returns an array of all stored keys
|
|
87
|
+
*/
|
|
88
|
+
async keys(): Promise<string[]> {
|
|
89
|
+
return (await this.#ready).keys();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { arrayConcatById } from './array';
|
|
2
|
+
|
|
3
|
+
interface Row {
|
|
4
|
+
id: number;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe('arrayConcatById', () => {
|
|
9
|
+
it('returns [] when both inputs are empty', () => {
|
|
10
|
+
expect(arrayConcatById<Row>([], [], 'id')).toEqual([]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('merges new rows over old by key and sorts DESC by default', () => {
|
|
14
|
+
const old: Row[] = [
|
|
15
|
+
{ id: 3, label: 'old-3' },
|
|
16
|
+
{ id: 2, label: 'old-2' },
|
|
17
|
+
{ id: 1, label: 'old-1' },
|
|
18
|
+
];
|
|
19
|
+
const fresh: Row[] = [
|
|
20
|
+
{ id: 3, label: 'new-3' },
|
|
21
|
+
{ id: 2, label: 'new-2' },
|
|
22
|
+
];
|
|
23
|
+
const result = arrayConcatById<Row>(old, fresh, 'id');
|
|
24
|
+
expect(result.map((r) => r.id)).toEqual([3, 2, 1]);
|
|
25
|
+
expect(result.find((r) => r.id === 3)?.label).toBe('new-3');
|
|
26
|
+
expect(result.find((r) => r.id === 1)?.label).toBe('old-1');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('sorts ASC when requested', () => {
|
|
30
|
+
const result = arrayConcatById<Row>([{ id: 1, label: 'a' }], [{ id: 2, label: 'b' }], 'id', 'ASC');
|
|
31
|
+
expect(result.map((r) => r.id)).toEqual([1, 2]);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge a newly fetched page of items into an existing list by id, keeping the result sorted.
|
|
3
|
+
*
|
|
4
|
+
* Items are merged by the numeric `key` field. On a duplicate `key` the item from `arrayNew` wins
|
|
5
|
+
* and replaces the one in `arrayOld`. Old items whose `key` falls *inside* the window spanned by
|
|
6
|
+
* `arrayNew` (between its first and last `key`) are dropped, since the freshly fetched page is the
|
|
7
|
+
* authoritative copy of that window; old items *outside* the window are kept. The merged items are
|
|
8
|
+
* finally sorted by `key`, ascending or descending according to `order`.
|
|
9
|
+
*
|
|
10
|
+
* The algorithm is:
|
|
11
|
+
* 1. If both inputs are empty, return an empty array.
|
|
12
|
+
* 2. Read the first (`lead`) and last (`last`) `key` values of `arrayNew` as the bounds of the
|
|
13
|
+
* page's window. Keep the old items whose `key` lies *outside* that window (at or beyond the
|
|
14
|
+
* extremes) and drop those strictly inside it, handling both a high-to-low page (`lead > last`)
|
|
15
|
+
* and a low-to-high page (`lead < last`). A single-value page (`lead === last`) keeps all old items.
|
|
16
|
+
* 3. Remove old items whose `key` already exists in `arrayNew` (new wins on duplicates).
|
|
17
|
+
* 4. Concatenate `arrayNew` with the surviving old items and sort by `key` in the requested
|
|
18
|
+
* direction.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Designed for infinite-scroll / paginated list merging, where each fetched page may overlap the
|
|
22
|
+
* previously held items and the server returns a contiguous, ordered window of records. The `key`
|
|
23
|
+
* field is treated as a number for range comparison and sorting.
|
|
24
|
+
*
|
|
25
|
+
* @typeParam T - Element type of both arrays. Its `key` property must be a numeric value.
|
|
26
|
+
* @param arrayOld - The previously accumulated list. May be empty or nullish-safe (empty when falsy).
|
|
27
|
+
* @param arrayNew - The newly fetched page of items; its `key` range defines the window that its own
|
|
28
|
+
* items replace, and its items take precedence on duplicates.
|
|
29
|
+
* @param key - The property of `T` used as the unique, numeric id for matching, range filtering, and sorting.
|
|
30
|
+
* @param order - Sort direction by `key`: `'ASC'` for ascending or `'DESC'` for descending. Defaults to `'DESC'`.
|
|
31
|
+
* @returns A new array containing `arrayNew` merged with the out-of-window, non-duplicate old items, sorted by `key`.
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* interface Post {
|
|
35
|
+
* id: number;
|
|
36
|
+
* title: string;
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* const loaded: Post[] = [
|
|
40
|
+
* { id: 30, title: 'c' },
|
|
41
|
+
* { id: 20, title: 'b' },
|
|
42
|
+
* { id: 10, title: 'a' },
|
|
43
|
+
* ];
|
|
44
|
+
* const nextPage: Post[] = [
|
|
45
|
+
* { id: 20, title: 'b (updated)' },
|
|
46
|
+
* { id: 15, title: 'a.5' },
|
|
47
|
+
* ];
|
|
48
|
+
*
|
|
49
|
+
* // Descending merge: id 30 lies above the new page's [15, 20] window so it is kept; id 20 is
|
|
50
|
+
* // inside the window and is replaced by the new value; id 10 lies below the window and is kept.
|
|
51
|
+
* const merged = arrayConcatById(loaded, nextPage, 'id', 'DESC');
|
|
52
|
+
* // => [{ id: 30, title: 'c' }, { id: 20, title: 'b (updated)' }, { id: 15, title: 'a.5' }, { id: 10, title: 'a' }]
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export const arrayConcatById = <T>(arrayOld: T[], arrayNew: T[], key: keyof T, order: 'ASC' | 'DESC' = 'DESC'): T[] => {
|
|
56
|
+
if (!arrayNew.length && !arrayOld.length) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const lead = arrayNew[0][key] as number;
|
|
60
|
+
const last = arrayNew[arrayNew.length - 1][key] as number;
|
|
61
|
+
|
|
62
|
+
const filteredOld = (arrayOld || []).filter((vol) => {
|
|
63
|
+
const value = vol[key] as number;
|
|
64
|
+
return (lead > last && (value >= lead || value <= last)) || (lead < last && (value <= lead || value >= last)) || lead === last;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const oldData = filteredOld.filter((vol) => !arrayNew.some((element) => element[key] === vol[key]));
|
|
68
|
+
const data = arrayNew.concat(oldData);
|
|
69
|
+
|
|
70
|
+
const direction = order === 'ASC' ? 1 : -1;
|
|
71
|
+
return data.sort((a, b) => {
|
|
72
|
+
const x = a[key] as number;
|
|
73
|
+
const y = b[key] as number;
|
|
74
|
+
if (x > y) {
|
|
75
|
+
return direction;
|
|
76
|
+
}
|
|
77
|
+
if (x < y) {
|
|
78
|
+
return direction * -1;
|
|
79
|
+
}
|
|
80
|
+
return 0;
|
|
81
|
+
});
|
|
82
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Capacitor } from '@capacitor/core';
|
|
2
|
+
import { Haptics, ImpactStyle } from '@capacitor/haptics';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Trigger native haptic impact feedback.
|
|
6
|
+
*
|
|
7
|
+
* Delegates to `@capacitor/haptics` `Haptics.impact()` when running on a native platform. On the
|
|
8
|
+
* web (or any non-native platform) it is a no-op and resolves without doing anything.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* This haptic side effect was previously bundled implicitly into toast/modal presentation. It has
|
|
12
|
+
* been decoupled into this explicit function so callers opt in to the feedback deliberately, rather
|
|
13
|
+
* than receiving it as a hidden side effect of presenting UI.
|
|
14
|
+
*
|
|
15
|
+
* @param style - The impact intensity to play. Defaults to {@link ImpactStyle.Light}.
|
|
16
|
+
* @returns A promise that resolves once the feedback has been requested (immediately on the web).
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { ImpactStyle } from '@capacitor/haptics';
|
|
20
|
+
*
|
|
21
|
+
* // Light tap (default)
|
|
22
|
+
* await kitImpact();
|
|
23
|
+
*
|
|
24
|
+
* // Stronger feedback, e.g. on a confirming action
|
|
25
|
+
* await kitImpact(ImpactStyle.Heavy);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const kitImpact = async (style: ImpactStyle = ImpactStyle.Light): Promise<void> => {
|
|
29
|
+
if (Capacitor.isNativePlatform()) {
|
|
30
|
+
await Haptics.impact({ style });
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Public API Surface of @rdlabo/ionic-angular-kit
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Storage: typed wrapper around the platform key/value store.
|
|
6
|
+
export * from './lib/storage/kit-storage.service';
|
|
7
|
+
|
|
8
|
+
// Overlay: wrapper around the Ionic Modal / Toast / Alert controllers.
|
|
9
|
+
export * from './lib/overlay/overlay-config';
|
|
10
|
+
export * from './lib/overlay/kit-overlay.controller';
|
|
11
|
+
export * from './lib/overlay/kit-reload-alert.controller';
|
|
12
|
+
|
|
13
|
+
// Directives.
|
|
14
|
+
export * from './lib/directives/autofill.directive';
|
|
15
|
+
|
|
16
|
+
// Auth: functional route guards.
|
|
17
|
+
export * from './lib/auth/auth-guards';
|
|
18
|
+
|
|
19
|
+
// HTTP: functional interceptor.
|
|
20
|
+
export * from './lib/http/kit-http.interceptor';
|
|
21
|
+
|
|
22
|
+
// Utils: framework-agnostic pure helpers.
|
|
23
|
+
export * from './lib/utils/haptics';
|
|
24
|
+
export * from './lib/utils/array';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "../../tsconfig.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"outDir": "../../out-tsc/lib",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"inlineSources": true,
|
|
10
|
+
"types": []
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*.ts"],
|
|
13
|
+
"exclude": ["**/*.spec.ts"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "./tsconfig.lib.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"declarationMap": false
|
|
7
|
+
},
|
|
8
|
+
"angularCompilerOptions": {
|
|
9
|
+
"compilationMode": "partial"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "../../tsconfig.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"outDir": "../../out-tsc/spec",
|
|
7
|
+
"types": ["vitest/globals"]
|
|
8
|
+
},
|
|
9
|
+
"include": ["src/**/*.d.ts", "src/**/*.spec.ts"]
|
|
10
|
+
}
|