@pistonite/pure 0.26.8 → 0.27.1

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.
@@ -3,6 +3,8 @@
3
3
  * operations are asynchronous.
4
4
  *
5
5
  * See {@link makeErcType} for how to use
6
+ *
7
+ * @deprecated use Emp
6
8
  */
7
9
  export type AsyncErc<TName, TRepr = number> = {
8
10
  readonly type: TName;
@@ -55,6 +57,8 @@ export type AsyncErc<TName, TRepr = number> = {
55
57
  * Weak reference to an externally ref-counted object.
56
58
  *
57
59
  * See {@link makeErcType} for how to use
60
+ *
61
+ * @deprecated use Emp
58
62
  */
59
63
  export type AsyncErcRef<TName, TRepr = number> = {
60
64
  readonly type: TName;
@@ -74,11 +78,15 @@ export type AsyncErcRef<TName, TRepr = number> = {
74
78
  getStrong: () => Promise<AsyncErc<TName, TRepr>>;
75
79
  };
76
80
 
81
+ /**
82
+ * @deprecated use Emp
83
+ */
77
84
  export type AsyncErcRefType<T> =
78
- T extends AsyncErc<infer TName, infer TRepr>
79
- ? AsyncErcRef<TName, TRepr>
80
- : never;
85
+ T extends AsyncErc<infer TName, infer TRepr> ? AsyncErcRef<TName, TRepr> : never;
81
86
 
87
+ /**
88
+ * @deprecated use Emp
89
+ */
82
90
  export type AsyncErcTypeConstructor<TName, TRepr> = {
83
91
  /**
84
92
  * A marker value for the underlying object type.
@@ -101,7 +109,9 @@ export type AsyncErcTypeConstructor<TName, TRepr> = {
101
109
  addRef: (value: TRepr) => Promise<TRepr> | TRepr;
102
110
  };
103
111
 
104
- /** See {@link makeErcType} */
112
+ /**
113
+ * @deprecated use Emp
114
+ */
105
115
  export const makeAsyncErcType = <TName, TRepr>({
106
116
  marker,
107
117
  free,
@@ -109,12 +119,9 @@ export const makeAsyncErcType = <TName, TRepr>({
109
119
  }: AsyncErcTypeConstructor<TName, TRepr>): ((
110
120
  value: TRepr | undefined,
111
121
  ) => AsyncErc<TName, TRepr>) => {
112
- const createStrongRef = (
113
- value: TRepr | undefined,
114
- ): AsyncErc<TName, TRepr> => {
115
- let weakRef:
116
- | (AsyncErcRef<TName, TRepr> & { invalidate: () => void })
117
- | undefined = undefined;
122
+ const createStrongRef = (value: TRepr | undefined): AsyncErc<TName, TRepr> => {
123
+ let weakRef: (AsyncErcRef<TName, TRepr> & { invalidate: () => void }) | undefined =
124
+ undefined;
118
125
  const invalidateWeakRef = () => {
119
126
  if (!weakRef) {
120
127
  return;
@@ -14,10 +14,7 @@ export type CellConstructor<T> = {
14
14
  export type Cell<T> = {
15
15
  get(): T;
16
16
  set(value: T): void;
17
- subscribe(
18
- callback: (value: T) => void,
19
- notifyImmediately?: boolean,
20
- ): () => void;
17
+ subscribe(callback: (value: T) => void, notifyImmediately?: boolean): () => void;
21
18
  };
22
19
 
23
20
  class CellImpl<T> implements Cell<T> {
@@ -41,10 +38,7 @@ class CellImpl<T> implements Cell<T> {
41
38
  }
42
39
  }
43
40
 
44
- public subscribe(
45
- callback: (value: T) => void,
46
- notifyImmediately?: boolean,
47
- ): () => void {
41
+ public subscribe(callback: (value: T) => void, notifyImmediately?: boolean): () => void {
48
42
  this.subscribers.push(callback);
49
43
  const unsubscribe = () => {
50
44
  const index = this.subscribers.indexOf(callback);
@@ -0,0 +1,88 @@
1
+ /**
2
+ * A non-null, **E**ngine-**m**anaged **P**ointer
3
+ *
4
+ * This uses the ECMA FinalizationRegistry, to free the resource,
5
+ * once this object is garbage-collected.
6
+ *
7
+ * The free function may be async.
8
+ *
9
+ * ## Use case
10
+ * When JS interoperates with a system language like C/C++ or Rust,
11
+ * it is often needed to transfer objects into JS context. If the object
12
+ * is big, copy-transfer could be expensive, so the more ideal solution
13
+ * is to transfer a "handle", or a pointer, to JS, and leaving the actual
14
+ * object in the native memory. Then, JS code and pass the pointer to external
15
+ * code to use or extract data from the object when needed.
16
+ *
17
+ * This approach is just like passing raw pointers in C, which means it is
18
+ * extremely succeptible to memory corruption bugs like double-free or
19
+ * use-after-free. Unlike C++ or Rust, JS also doesn't have destructors
20
+ * that run when an object leaves the scope (proposal for the `using` keyword exists,
21
+ * but you can actually double-free the object with `using`).
22
+ *
23
+ * C-style manual memory management might be sufficient for simple bindings,
24
+ * but for more complex scenarios, automatic memory management is needed,
25
+ * by tying the free call to the GC of a JS object.
26
+ *
27
+ * ## Recommended practice
28
+ * 1. The external code should transfer the ownership of the object
29
+ * to JS when passing it to JS. JS will then put the handle into an Emp
30
+ * to be automatically managed. This means the external code should now
31
+ * never free the object, and let JS free it instead.
32
+ * 2. The inner value of the Emp must only be used for calling
33
+ * external code, and the Emp must be kept alive for all runtimes, including
34
+ * those with heavy optimization that may reclaim the Emp during the call,
35
+ * if it's not referenced afterwards. See {@link scopedCapture}.
36
+ * The inner value must not dangle around outside of the Emp that owns it.
37
+ *
38
+ * ## Pointer Size
39
+ * In 32-bit context like WASM32, the inner value can be a `number`.
40
+ * In 64-bit context, `number` might be fine for some systems, but `bigint`
41
+ * is recommended.
42
+ */
43
+ export type Emp<T, TRepr> = {
44
+ /** The type marker for T. This only marks the type for TypeScript and does not exist at runtime */
45
+ readonly __phantom: T;
46
+ /** The underlying pointer value */
47
+ readonly value: TRepr;
48
+ };
49
+
50
+ export type EmpConstructor<T, TRepr> = {
51
+ /**
52
+ * The marker for the Emp type, used to distinguish between multiple types
53
+ *
54
+ * Typically, this is a unique symbol:
55
+ * ```typescript
56
+ * const MyNativeType = Symbol("MyNativeType");
57
+ * export type MyNativeType = typeof MyNativeType;
58
+ *
59
+ * const makeMyNativeTypeEmp = makeEmpType({
60
+ * marker: MyNativeType,
61
+ * free: (ptr) => void freeMyNativeType(ptr)
62
+ * })
63
+ * ```
64
+ *
65
+ * Note that this is not used in runtime, but just used for type inference,
66
+ * so you can also skip passing it and specify the type parameter instead
67
+ */
68
+ marker?: T;
69
+
70
+ /**
71
+ * Function to free the underlying object. Called when this Emp is garbage-collected
72
+ */
73
+ free: (ptr: TRepr) => void | Promise<void>;
74
+ };
75
+
76
+ /**
77
+ * Create a factory function for an {@link Emp} type.
78
+ */
79
+ export const makeEmpType = <T, TRepr>({
80
+ free,
81
+ }: EmpConstructor<T, TRepr>): ((ptr: TRepr) => Emp<T, TRepr>) => {
82
+ const registry = new FinalizationRegistry(free);
83
+ return (ptr: TRepr) => {
84
+ const obj = Object.freeze({ value: ptr });
85
+ registry.register(obj, ptr);
86
+ return obj as Emp<T, TRepr>;
87
+ };
88
+ };
@@ -119,143 +119,140 @@ describe.each`
119
119
  indirection | allocator
120
120
  ${"single"} | ${new Allocator(false)}
121
121
  ${"double"} | ${new Allocator(true)}
122
- `(
123
- "Erc - $indirection indirection",
124
- ({ allocator }: { allocator: Allocator }) => {
125
- afterEach(() => {
126
- allocator.cleanup();
127
- });
122
+ `("Erc - $indirection indirection", ({ allocator }: { allocator: Allocator }) => {
123
+ afterEach(() => {
124
+ allocator.cleanup();
125
+ });
128
126
 
129
- it("allocate and deallocate correctly", () => {
130
- const test = allocator.makeTestErc(allocator.allocValue("Hello"));
131
- expect(allocator.getValue(test.value)).toBe("Hello");
132
- test.free();
133
- allocator.expectNoLeak();
134
- });
127
+ it("allocate and deallocate correctly", () => {
128
+ const test = allocator.makeTestErc(allocator.allocValue("Hello"));
129
+ expect(allocator.getValue(test.value)).toBe("Hello");
130
+ test.free();
131
+ allocator.expectNoLeak();
132
+ });
135
133
 
136
- it("frees if assigned new value", () => {
137
- const test = allocator.makeTestErc(allocator.allocValue("Hello"));
138
- test.assign(allocator.allocValue("World"));
139
- expect(allocator.getValue(test.value)).toBe("World");
140
- test.free();
141
- allocator.expectNoLeak();
142
- });
134
+ it("frees if assigned new value", () => {
135
+ const test = allocator.makeTestErc(allocator.allocValue("Hello"));
136
+ test.assign(allocator.allocValue("World"));
137
+ expect(allocator.getValue(test.value)).toBe("World");
138
+ test.free();
139
+ allocator.expectNoLeak();
140
+ });
143
141
 
144
- it("does not free when taking value", () => {
145
- const test = allocator.makeTestErc(allocator.allocValue("Hello"));
146
- const raw = test.take();
147
- expect(allocator.getValue(raw)).toBe("Hello");
148
- expect(test.value).toBeUndefined();
149
- if (raw === undefined) {
150
- throw new Error("Raw value is undefined");
151
- }
152
- allocator.makeTestErc(raw).free();
153
- allocator.expectNoLeak();
154
- });
142
+ it("does not free when taking value", () => {
143
+ const test = allocator.makeTestErc(allocator.allocValue("Hello"));
144
+ const raw = test.take();
145
+ expect(allocator.getValue(raw)).toBe("Hello");
146
+ expect(test.value).toBeUndefined();
147
+ if (raw === undefined) {
148
+ throw new Error("Raw value is undefined");
149
+ }
150
+ allocator.makeTestErc(raw).free();
151
+ allocator.expectNoLeak();
152
+ });
155
153
 
156
- it("invalidates weak references on free", () => {
157
- const test = allocator.makeTestErc(allocator.allocValue("Hello"));
158
- const testWeak = test.getWeak();
159
- expect(allocator.getValue(testWeak.value)).toBe("Hello");
160
- test.free();
161
- expect(testWeak.value).toBeUndefined();
162
- allocator.expectNoLeak();
163
- });
154
+ it("invalidates weak references on free", () => {
155
+ const test = allocator.makeTestErc(allocator.allocValue("Hello"));
156
+ const testWeak = test.getWeak();
157
+ expect(allocator.getValue(testWeak.value)).toBe("Hello");
158
+ test.free();
159
+ expect(testWeak.value).toBeUndefined();
160
+ allocator.expectNoLeak();
161
+ });
164
162
 
165
- it("invalidates weak references on assign", () => {
166
- const test = allocator.makeTestErc(allocator.allocValue("Hello"));
167
- const testWeak = test.getWeak();
168
- expect(allocator.getValue(testWeak.value)).toBe("Hello");
169
- test.assign(allocator.allocValue("World"));
170
- expect(testWeak.value).toBeUndefined();
171
- test.free();
172
- allocator.expectNoLeak();
173
- });
163
+ it("invalidates weak references on assign", () => {
164
+ const test = allocator.makeTestErc(allocator.allocValue("Hello"));
165
+ const testWeak = test.getWeak();
166
+ expect(allocator.getValue(testWeak.value)).toBe("Hello");
167
+ test.assign(allocator.allocValue("World"));
168
+ expect(testWeak.value).toBeUndefined();
169
+ test.free();
170
+ allocator.expectNoLeak();
171
+ });
174
172
 
175
- it("handles assign and take of different references correctly", () => {
176
- const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
177
- const test2 = allocator.makeTestErc(allocator.allocValue("World"));
178
- expect(allocator.getValue(test1.value)).toBe("Hello");
179
- expect(allocator.getValue(test2.value)).toBe("World");
180
- test1.assign(test2.take());
181
- expect(allocator.getValue(test1.value)).toBe("World");
182
- test1.free();
183
- allocator.expectNoLeak();
184
- });
173
+ it("handles assign and take of different references correctly", () => {
174
+ const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
175
+ const test2 = allocator.makeTestErc(allocator.allocValue("World"));
176
+ expect(allocator.getValue(test1.value)).toBe("Hello");
177
+ expect(allocator.getValue(test2.value)).toBe("World");
178
+ test1.assign(test2.take());
179
+ expect(allocator.getValue(test1.value)).toBe("World");
180
+ test1.free();
181
+ allocator.expectNoLeak();
182
+ });
185
183
 
186
- it("handles assign and take of same references correctly", () => {
187
- const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
188
- const test2 = test1.getStrong();
189
- test1.assign(test2.take());
190
- expect(allocator.getValue(test1.value)).toBe("Hello");
191
- test1.free();
192
- test2.free(); // should be no-op
193
- allocator.expectNoLeak();
194
- });
184
+ it("handles assign and take of same references correctly", () => {
185
+ const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
186
+ const test2 = test1.getStrong();
187
+ test1.assign(test2.take());
188
+ expect(allocator.getValue(test1.value)).toBe("Hello");
189
+ test1.free();
190
+ test2.free(); // should be no-op
191
+ allocator.expectNoLeak();
192
+ });
195
193
 
196
- it("assigning another Erc directly should cause double free", async () => {
197
- const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
198
- const test2 = test1.getStrong();
199
- test1.assign(test2.value);
200
- expect(allocator.getValue(test1.value)).toBe("Hello");
201
- test1.free();
194
+ it("assigning another Erc directly should cause double free", async () => {
195
+ const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
196
+ const test2 = test1.getStrong();
197
+ test1.assign(test2.value);
198
+ expect(allocator.getValue(test1.value)).toBe("Hello");
199
+ test1.free();
202
200
 
203
- const freeTest2 = async () => {
204
- test2.free();
205
- };
206
- await expect(freeTest2).rejects.toThrow("Double free detected");
207
- allocator.expectNoLeak();
208
- });
201
+ const freeTest2 = async () => {
202
+ test2.free();
203
+ };
204
+ await expect(freeTest2).rejects.toThrow("Double free detected");
205
+ allocator.expectNoLeak();
206
+ });
209
207
 
210
- it("handles assign and take of same references correctly (same Erc)", () => {
211
- const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
212
- test1.assign(test1.take());
213
- expect(allocator.getValue(test1.value)).toBe("Hello");
214
- test1.free();
215
- allocator.expectNoLeak();
216
- });
208
+ it("handles assign and take of same references correctly (same Erc)", () => {
209
+ const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
210
+ test1.assign(test1.take());
211
+ expect(allocator.getValue(test1.value)).toBe("Hello");
212
+ test1.free();
213
+ allocator.expectNoLeak();
214
+ });
217
215
 
218
- it("assigning another Erc directly should cause double free (same Erc)", async () => {
219
- const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
220
- test1.assign(test1.value);
221
- const getTest1Value = async () => {
222
- allocator.getValue(test1.value);
223
- };
224
- await expect(getTest1Value).rejects.toThrow("Dangling pointer");
216
+ it("assigning another Erc directly should cause double free (same Erc)", async () => {
217
+ const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
218
+ test1.assign(test1.value);
219
+ const getTest1Value = async () => {
220
+ allocator.getValue(test1.value);
221
+ };
222
+ await expect(getTest1Value).rejects.toThrow("Dangling pointer");
225
223
 
226
- const freeTest1 = async () => {
227
- test1.free();
228
- };
229
- await expect(freeTest1).rejects.toThrow("Double free detected");
230
- allocator.expectNoLeak();
231
- });
224
+ const freeTest1 = async () => {
225
+ test1.free();
226
+ };
227
+ await expect(freeTest1).rejects.toThrow("Double free detected");
228
+ allocator.expectNoLeak();
229
+ });
232
230
 
233
- it("inc ref count with strong reference", () => {
234
- const test = allocator.makeTestErc(allocator.allocValue("Hello"));
235
- const test2 = test.getStrong();
236
- expect(allocator.getValue(test.value)).toBe("Hello");
237
- expect(allocator.getValue(test2.value)).toBe("Hello");
238
- test.free();
239
- expect(allocator.getValue(test2.value)).toBe("Hello");
240
- test2.free();
241
- allocator.expectNoLeak();
242
- });
231
+ it("inc ref count with strong reference", () => {
232
+ const test = allocator.makeTestErc(allocator.allocValue("Hello"));
233
+ const test2 = test.getStrong();
234
+ expect(allocator.getValue(test.value)).toBe("Hello");
235
+ expect(allocator.getValue(test2.value)).toBe("Hello");
236
+ test.free();
237
+ expect(allocator.getValue(test2.value)).toBe("Hello");
238
+ test2.free();
239
+ allocator.expectNoLeak();
240
+ });
243
241
 
244
- it("inc ref count with strong reference from weak reference", () => {
245
- const test = allocator.makeTestErc(allocator.allocValue("Hello"));
246
- const testWeak = test.getWeak();
247
- expect(allocator.getValue(testWeak.value)).toBe("Hello");
248
- const test2 = testWeak.getStrong();
249
- expect(allocator.getValue(testWeak.value)).toBe("Hello");
250
- expect(allocator.getValue(test2.value)).toBe("Hello");
251
- const test2Weak = test2.getWeak();
252
- test.free();
253
- expect(testWeak.value).toBeUndefined();
254
- expect(allocator.getValue(test2.value)).toBe("Hello");
255
- expect(allocator.getValue(test2Weak.value)).toBe("Hello");
256
- test2.free();
257
- expect(test2Weak.value).toBeUndefined();
258
- allocator.expectNoLeak();
259
- });
260
- },
261
- );
242
+ it("inc ref count with strong reference from weak reference", () => {
243
+ const test = allocator.makeTestErc(allocator.allocValue("Hello"));
244
+ const testWeak = test.getWeak();
245
+ expect(allocator.getValue(testWeak.value)).toBe("Hello");
246
+ const test2 = testWeak.getStrong();
247
+ expect(allocator.getValue(testWeak.value)).toBe("Hello");
248
+ expect(allocator.getValue(test2.value)).toBe("Hello");
249
+ const test2Weak = test2.getWeak();
250
+ test.free();
251
+ expect(testWeak.value).toBeUndefined();
252
+ expect(allocator.getValue(test2.value)).toBe("Hello");
253
+ expect(allocator.getValue(test2Weak.value)).toBe("Hello");
254
+ test2.free();
255
+ expect(test2Weak.value).toBeUndefined();
256
+ allocator.expectNoLeak();
257
+ });
258
+ });
package/src/memory/erc.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * A holder for an externally ref-counted object.
3
3
  *
4
4
  * See {@link makeErcType} for how to use
5
+ * @deprecated use Emp
5
6
  */
6
7
  export type Erc<TName, TRepr = number> = {
7
8
  readonly type: TName;
@@ -54,6 +55,7 @@ export type Erc<TName, TRepr = number> = {
54
55
  * Weak reference to an externally ref-counted object.
55
56
  *
56
57
  * See {@link makeErcType} for how to use
58
+ * @deprecated use Emp
57
59
  */
58
60
  export type ErcRef<TName, TRepr = number> = {
59
61
  readonly type: TName;
@@ -73,9 +75,14 @@ export type ErcRef<TName, TRepr = number> = {
73
75
  getStrong: () => Erc<TName, TRepr>;
74
76
  };
75
77
 
76
- export type ErcRefType<T> =
77
- T extends Erc<infer TName, infer TRepr> ? ErcRef<TName, TRepr> : never;
78
+ /**
79
+ * @deprecated use Emp
80
+ */
81
+ export type ErcRefType<T> = T extends Erc<infer TName, infer TRepr> ? ErcRef<TName, TRepr> : never;
78
82
 
83
+ /**
84
+ * @deprecated use Emp
85
+ */
79
86
  export type ErcTypeConstructor<TName, TRepr> = {
80
87
  /**
81
88
  * A marker value for the underlying object type.
@@ -241,18 +248,15 @@ export type ErcTypeConstructor<TName, TRepr> = {
241
248
  * myFoo1.assign(myFoo1.value); // this will free the value since ref count is 0, and result in a dangling pointer
242
249
  * ```
243
250
  *
251
+ * @deprecated use Emp
244
252
  */
245
253
  export const makeErcType = <TName, TRepr>({
246
254
  marker,
247
255
  free,
248
256
  addRef,
249
- }: ErcTypeConstructor<TName, TRepr>): ((
250
- value: TRepr | undefined,
251
- ) => Erc<TName, TRepr>) => {
257
+ }: ErcTypeConstructor<TName, TRepr>): ((value: TRepr | undefined) => Erc<TName, TRepr>) => {
252
258
  const createStrongRef = (value: TRepr | undefined): Erc<TName, TRepr> => {
253
- let weakRef:
254
- | (ErcRef<TName, TRepr> & { invalidate: () => void })
255
- | undefined = undefined;
259
+ let weakRef: (ErcRef<TName, TRepr> & { invalidate: () => void }) | undefined = undefined;
256
260
  const invalidateWeakRef = () => {
257
261
  if (!weakRef) {
258
262
  return;
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export { cell, type CellConstructor, type Cell } from "./cell.ts";
8
8
  export { persist, type PersistConstructor, type Persist } from "./persist.ts";
9
- export * from "./erc.ts";
10
9
  export * from "./async_erc.ts";
10
+ export * from "./emp.ts";
11
+ export * from "./erc.ts";
11
12
  export * from "./idgen.ts";
@@ -106,10 +106,7 @@ class PersistImpl<T> implements Persist<T> {
106
106
  this.cell.set(value);
107
107
  }
108
108
 
109
- public subscribe(
110
- callback: (value: T) => void,
111
- notifyImmediately?: boolean,
112
- ): () => void {
109
+ public subscribe(callback: (value: T) => void, notifyImmediately?: boolean): () => void {
113
110
  return this.cell.subscribe(callback, notifyImmediately);
114
111
  }
115
112
 
@@ -0,0 +1,118 @@
1
+ import { ilog } from "../log/internal.ts";
2
+ import { cell } from "../memory";
3
+
4
+ let cachedIsMobile: boolean | undefined = undefined;
5
+
6
+ /** Check if the current UserAgent is a mobile device */
7
+ export const isMobile = (): boolean => {
8
+ // just very primitive UA parsing for now,
9
+ // we don't need to take another dependency just to detect
10
+ // a few platforms
11
+ if (cachedIsMobile === undefined) {
12
+ const ua = navigator?.userAgent || "";
13
+ if (ua.match(/(Windows NT|Macintosh|)/i)) {
14
+ cachedIsMobile = false;
15
+ } else if (ua.match(/(Mobil|iPhone|Android|iPod|iPad)/)) {
16
+ cachedIsMobile = true;
17
+ } else if (ua.includes("Linux")) {
18
+ cachedIsMobile = false;
19
+ } else {
20
+ ilog.warn("unable to determine device type, assuming desktop");
21
+ cachedIsMobile = true;
22
+ }
23
+ }
24
+ return cachedIsMobile;
25
+ };
26
+
27
+ // stores current display mode
28
+ const displayMode = cell({ initial: "" });
29
+
30
+ /**
31
+ * Options for display mode detection
32
+ *
33
+ * See {@link initDisplayMode}
34
+ */
35
+ export type DisplayModeOptions<T extends string> = {
36
+ /**
37
+ * Set the initial value, if the platform doesn't support detecting
38
+ * the display mode. `detect()` will be used instead if supported
39
+ */
40
+ initial?: T;
41
+ /**
42
+ * Function to determine the display mode based on viewport width and height,
43
+ * and if the device is mobile
44
+ */
45
+ detect: (width: number, height: number, isMobile: boolean) => T;
46
+ };
47
+
48
+ /**
49
+ * Initialize display mode detection for the app.
50
+ *
51
+ * The display mode may be based on the viewport dimensions
52
+ * and if the device is mobile. Also note that you can use {@link isMobile}
53
+ * without the display mode framework.
54
+ *
55
+ * The display modes are strings that should be passed as a type parameter
56
+ * to {@link initDisplayMode}. You can create an alias in your code for
57
+ * getting the typed version of {@link addDisplayModeSubscriber}, {@link getDisplayMode},
58
+ * and {@link useDisplayMode} from `pure-react`.
59
+ *
60
+ * ```typescript
61
+ * import {
62
+ * addDisplayModeSubscriber as pureAddDisplayModeSubscriber,
63
+ * getDisplayMode as pureGetDisplayMode,
64
+ * } from "@pistonite/pure/pref";
65
+ * import { useDisplayMode as pureUseDisplayMode } from "@pistonite/pure-react";
66
+ *
67
+ * export const MyDisplayModes = ["mode1", "mode2"] as const;
68
+ * export type MyDisplayMode = (typeof MyDisplayModes)[number];
69
+ *
70
+ * export const addDisplayModeSubscriber = pureAddDisplayModeSubscriber<MyDisplayMode>;
71
+ * export const getDisplayMode = pureGetDisplayMode<MyDisplayMode>;
72
+ * export const useDisplayMode = pureUseDisplayMode<MyDisplayMode>;
73
+ * ```
74
+ *
75
+ * Note that this is not necessary in some simple use cases. For example,
76
+ * adjusting styles based on the viewport width can be done with CSS:
77
+ * ```css
78
+ * @media screen and (max-width: 800px) {
79
+ * /* styles for narrow mode * /
80
+ * }
81
+ * ```
82
+ *
83
+ * Use this only if the display mode needs to be detected programmatically.
84
+ */
85
+ export const initDisplayMode = <T extends string>(options: DisplayModeOptions<T>) => {
86
+ const detectCallback = () => {
87
+ const mode = options.detect(window.innerWidth, window.innerHeight, isMobile());
88
+ displayMode.set(mode);
89
+ };
90
+ if (
91
+ window &&
92
+ window.addEventListener &&
93
+ window.innerWidth !== undefined &&
94
+ window.innerHeight !== undefined
95
+ ) {
96
+ window.addEventListener("resize", detectCallback);
97
+ detectCallback();
98
+ } else if (options.initial !== undefined) {
99
+ displayMode.set(options.initial);
100
+ } else {
101
+ ilog.warn(
102
+ "display mode cannot be initialized, since detection is not supported and initial is not set",
103
+ );
104
+ }
105
+ };
106
+
107
+ /** Subscribe to display mode changes */
108
+ export const addDisplayModeSubscriber = <T extends string>(
109
+ subscriber: (mode: T) => void,
110
+ notifyImmediately?: boolean,
111
+ ) => {
112
+ return displayMode.subscribe(subscriber as (x: string) => void, notifyImmediately);
113
+ };
114
+
115
+ /** Get the current display mode */
116
+ export const getDisplayMode = <T extends string>(): T => {
117
+ return displayMode.get() as T;
118
+ };
package/src/pref/index.ts CHANGED
@@ -10,3 +10,4 @@
10
10
  export * from "./dark.ts";
11
11
  export * from "./inject_style.ts";
12
12
  export * from "./locale.ts";
13
+ export * from "./device.ts";