@pistonite/pure 0.28.0 → 0.29.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.
- package/LICENSE +1 -1
- package/README.md +23 -4
- package/dist/_dts_/src/log/index.d.ts +37 -0
- package/dist/_dts_/src/log/index.d.ts.map +1 -0
- package/dist/_dts_/src/log/logger.d.ts +27 -0
- package/dist/_dts_/src/log/logger.d.ts.map +1 -0
- package/dist/_dts_/src/memory/cell.d.ts +25 -0
- package/dist/_dts_/src/memory/cell.d.ts.map +1 -0
- package/dist/_dts_/src/memory/emp.d.ts +87 -0
- package/dist/_dts_/src/memory/emp.d.ts.map +1 -0
- package/dist/_dts_/src/memory/idgen.d.ts +18 -0
- package/dist/_dts_/src/memory/idgen.d.ts.map +1 -0
- package/dist/_dts_/src/memory/index.d.ts +10 -0
- package/dist/_dts_/src/memory/index.d.ts.map +1 -0
- package/dist/_dts_/src/memory/persist.d.ts +38 -0
- package/dist/_dts_/src/memory/persist.d.ts.map +1 -0
- package/dist/_dts_/src/result/index.d.ts +191 -0
- package/dist/_dts_/src/result/index.d.ts.map +1 -0
- package/dist/_dts_/src/sync/RwLock.d.ts +30 -0
- package/dist/_dts_/src/sync/RwLock.d.ts.map +1 -0
- package/dist/_dts_/src/sync/batch.d.ts +112 -0
- package/dist/_dts_/src/sync/batch.d.ts.map +1 -0
- package/dist/_dts_/src/sync/capture.d.ts +11 -0
- package/dist/_dts_/src/sync/capture.d.ts.map +1 -0
- package/dist/_dts_/src/sync/debounce.d.ts +105 -0
- package/dist/_dts_/src/sync/debounce.d.ts.map +1 -0
- package/dist/_dts_/src/sync/index.d.ts +15 -0
- package/dist/_dts_/src/sync/index.d.ts.map +1 -0
- package/dist/_dts_/src/sync/latest.d.ts +86 -0
- package/dist/_dts_/src/sync/latest.d.ts.map +1 -0
- package/dist/_dts_/src/sync/mutex.d.ts +14 -0
- package/dist/_dts_/src/sync/mutex.d.ts.map +1 -0
- package/dist/_dts_/src/sync/once.d.ts +84 -0
- package/dist/_dts_/src/sync/once.d.ts.map +1 -0
- package/dist/_dts_/src/sync/serial.d.ts +162 -0
- package/dist/_dts_/src/sync/serial.d.ts.map +1 -0
- package/dist/_dts_/src/sync/util.d.ts +19 -0
- package/dist/_dts_/src/sync/util.d.ts.map +1 -0
- package/dist/log/index.js +57 -0
- package/dist/log/index.js.map +1 -0
- package/dist/memory/index.js +92 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/result/index.js +29 -0
- package/dist/result/index.js.map +1 -0
- package/dist/sync/index.js +252 -0
- package/dist/sync/index.js.map +1 -0
- package/package.json +22 -13
- package/src/env.d.ts +1 -0
- package/src/log/index.ts +36 -11
- package/src/log/logger.ts +93 -115
- package/src/memory/cell.ts +21 -11
- package/src/memory/emp.ts +34 -23
- package/src/memory/idgen.test.ts +1 -1
- package/src/memory/index.ts +1 -4
- package/src/memory/persist.ts +9 -12
- package/src/result/index.ts +12 -4
- package/src/sync/batch.test.ts +1 -1
- package/src/sync/batch.ts +12 -17
- package/src/sync/capture.ts +2 -0
- package/src/sync/debounce.test.ts +1 -1
- package/src/sync/debounce.ts +12 -15
- package/src/sync/index.ts +2 -3
- package/src/sync/latest.test.ts +1 -1
- package/src/sync/latest.ts +19 -16
- package/src/sync/once.test.ts +1 -1
- package/src/sync/once.ts +13 -8
- package/src/sync/serial.test.ts +1 -1
- package/src/sync/serial.ts +14 -12
- package/src/sync/util.ts +2 -2
- package/src/fs/FsError.ts +0 -55
- package/src/fs/FsFile.ts +0 -67
- package/src/fs/FsFileImpl.ts +0 -219
- package/src/fs/FsFileMgr.ts +0 -29
- package/src/fs/FsFileStandalone.ts +0 -21
- package/src/fs/FsFileStandaloneImplFileAPI.ts +0 -54
- package/src/fs/FsFileStandaloneImplHandleAPI.ts +0 -147
- package/src/fs/FsFileSystem.ts +0 -71
- package/src/fs/FsFileSystemInternal.ts +0 -30
- package/src/fs/FsImplEntryAPI.ts +0 -149
- package/src/fs/FsImplFileAPI.ts +0 -116
- package/src/fs/FsImplHandleAPI.ts +0 -199
- package/src/fs/FsOpen.ts +0 -271
- package/src/fs/FsOpenFile.ts +0 -256
- package/src/fs/FsPath.ts +0 -137
- package/src/fs/FsSave.ts +0 -216
- package/src/fs/FsSupportStatus.ts +0 -87
- package/src/fs/index.ts +0 -123
- package/src/log/internal.ts +0 -14
- package/src/memory/async_erc.ts +0 -186
- package/src/memory/erc.test.ts +0 -258
- package/src/memory/erc.ts +0 -320
- package/src/pref/dark.ts +0 -151
- package/src/pref/device.ts +0 -118
- package/src/pref/index.ts +0 -13
- package/src/pref/inject_style.ts +0 -22
- package/src/pref/locale.ts +0 -296
package/src/memory/erc.ts
DELETED
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A holder for an externally ref-counted object.
|
|
3
|
-
*
|
|
4
|
-
* See {@link makeErcType} for how to use
|
|
5
|
-
* @deprecated use Emp
|
|
6
|
-
*/
|
|
7
|
-
export type Erc<TName, TRepr = number> = {
|
|
8
|
-
readonly type: TName;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Underlying object representation.
|
|
12
|
-
*
|
|
13
|
-
* The repr should not be undefinable. undefined means nullptr
|
|
14
|
-
*/
|
|
15
|
-
readonly value: TRepr | undefined;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Free the underlying object.
|
|
19
|
-
*
|
|
20
|
-
* All weak references will be invalidated, and this Erc will become
|
|
21
|
-
* empty
|
|
22
|
-
*/
|
|
23
|
-
free: () => void;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Assign a new value to this Erc.
|
|
27
|
-
*
|
|
28
|
-
* The old value will be freed, and all weak references will be invalidated.
|
|
29
|
-
*/
|
|
30
|
-
assign: (value: TRepr | undefined) => void;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Take the inner value without freeing it.
|
|
34
|
-
*
|
|
35
|
-
* All weak references will be invalidated, and this Erc will become
|
|
36
|
-
* empty
|
|
37
|
-
*/
|
|
38
|
-
take: () => TRepr | undefined;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Create a weak reference to the inner value.
|
|
42
|
-
*
|
|
43
|
-
* When this Erc is freed, all weak references will be invalidated.
|
|
44
|
-
*/
|
|
45
|
-
getWeak: () => ErcRef<TName, TRepr>;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Create a strong reference to the inner value, essentially
|
|
49
|
-
* incrementing the ref count.
|
|
50
|
-
*/
|
|
51
|
-
getStrong: () => Erc<TName, TRepr>;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Weak reference to an externally ref-counted object.
|
|
56
|
-
*
|
|
57
|
-
* See {@link makeErcType} for how to use
|
|
58
|
-
* @deprecated use Emp
|
|
59
|
-
*/
|
|
60
|
-
export type ErcRef<TName, TRepr = number> = {
|
|
61
|
-
readonly type: TName;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* The underlying object representation.
|
|
65
|
-
*
|
|
66
|
-
* This may become undefined across async calls if the weak reference
|
|
67
|
-
* is invalidated
|
|
68
|
-
*/
|
|
69
|
-
readonly value: TRepr | undefined;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Create a strong reference to the inner value, essentially
|
|
73
|
-
* incrementing the ref count.
|
|
74
|
-
*/
|
|
75
|
-
getStrong: () => Erc<TName, TRepr>;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* @deprecated use Emp
|
|
80
|
-
*/
|
|
81
|
-
export type ErcRefType<T> = T extends Erc<infer TName, infer TRepr> ? ErcRef<TName, TRepr> : never;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @deprecated use Emp
|
|
85
|
-
*/
|
|
86
|
-
export type ErcTypeConstructor<TName, TRepr> = {
|
|
87
|
-
/**
|
|
88
|
-
* A marker value for the underlying object type.
|
|
89
|
-
*
|
|
90
|
-
* This is commonly a string literal or a symbol.
|
|
91
|
-
*/
|
|
92
|
-
marker: TName;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* The function to free the underlying object.
|
|
96
|
-
*/
|
|
97
|
-
free: (value: TRepr) => void;
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Given a value, increase the ref count and return the new reference.
|
|
101
|
-
* The returned representation should be a different value if double indirection
|
|
102
|
-
* is used (each value is a pointer to the smart pointer), or the same value
|
|
103
|
-
* if single indirection is used (the value is pointing to the object itself).
|
|
104
|
-
*/
|
|
105
|
-
addRef: (value: TRepr) => TRepr;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Create a constructor function that serves as the type for an externally ref-counted
|
|
110
|
-
* object type. Erc instances can then be created for manually managing memory for
|
|
111
|
-
* external objects, typically through FFI.
|
|
112
|
-
*
|
|
113
|
-
* Since JS is garbage collected and has no way to enforce certain memory management,
|
|
114
|
-
* the programmer must ensure that Erc instances are handled correctly!!!
|
|
115
|
-
*
|
|
116
|
-
* ## Defining Erc type
|
|
117
|
-
* ```typescript
|
|
118
|
-
* import { makeErcType, type Erc } from "@pistonite/pure/memory";
|
|
119
|
-
*
|
|
120
|
-
* // 2 functions are required to create an Erc type:
|
|
121
|
-
* // free: free the underlying value (essentially decrementing the ref count)
|
|
122
|
-
* // addRef: increment the ref count and return the new reference
|
|
123
|
-
*
|
|
124
|
-
* // here, assume `number` is the JS type used to represent the external object
|
|
125
|
-
* // for example, this can be a pointer to a C++ object
|
|
126
|
-
* declare function freeFoo(obj: number) => void;
|
|
127
|
-
* declare function addRefFoo(obj: number) => number;
|
|
128
|
-
*
|
|
129
|
-
* // assume another function to create (allocate) the object
|
|
130
|
-
* declare function createFoo(): number;
|
|
131
|
-
*
|
|
132
|
-
* // The recommended way is to create a unique symbol for tracking
|
|
133
|
-
* // the external type, you can also use a string literal
|
|
134
|
-
* const Foo = Symbol("Foo");
|
|
135
|
-
* type Foo = typeof Foo;
|
|
136
|
-
*
|
|
137
|
-
* // now, we can create the Erc type
|
|
138
|
-
* const makeFooErc = makeErcType({
|
|
139
|
-
* marker: Foo,
|
|
140
|
-
* free: freeFoo,
|
|
141
|
-
* addRef: addRefFoo,
|
|
142
|
-
* });
|
|
143
|
-
*
|
|
144
|
-
* // and create Erc instances
|
|
145
|
-
* const myFoo: Erc<Foo> = makeFooErc(createFoo());
|
|
146
|
-
* ```
|
|
147
|
-
*
|
|
148
|
-
* ## Using Erc (strong reference)
|
|
149
|
-
* Each `Erc` instance is a strong reference, corresponding to some ref-counted
|
|
150
|
-
* object externally. Therefore, owner of the `Erc` instance should
|
|
151
|
-
* not expose the `Erc` instance to others (for example, returning it from a function),
|
|
152
|
-
* since it will lead to memory leak or double free.
|
|
153
|
-
* ```typescript
|
|
154
|
-
* // create a foo instance externally, and wrap it with Erc
|
|
155
|
-
* const myFoo = makeFooErc(createFoo());
|
|
156
|
-
*
|
|
157
|
-
* // if ownership of myFoo should be returned to external, use `take()`
|
|
158
|
-
* // this will make myFoo empty, and doSomethingWithFooExternally should free it
|
|
159
|
-
* doSomethingWithFooExternally(myFoo.take());
|
|
160
|
-
*
|
|
161
|
-
* // you can also free it directly
|
|
162
|
-
* foo.free();
|
|
163
|
-
* ```
|
|
164
|
-
*
|
|
165
|
-
* ## Using ErcRef (weak reference)
|
|
166
|
-
* Calling `getWeak` on an `Erc` will return a weak reference that has the same
|
|
167
|
-
* inner value. The weak reference is safe to be passed around and copied.
|
|
168
|
-
* ```typescript
|
|
169
|
-
* const myFooWeak = myFoo.getWeak();
|
|
170
|
-
* ```
|
|
171
|
-
*
|
|
172
|
-
* The weak references are tracked by the `Erc` instance.
|
|
173
|
-
* In the example above, when `myFoo` is freed, all weak references created by
|
|
174
|
-
* `getWeak` will be invalidated. If some other code kept the inner value of
|
|
175
|
-
* the weak reference, it will become a dangling pointer.
|
|
176
|
-
*
|
|
177
|
-
* To avoid this, `getStrong` can be used to create a strong reference if
|
|
178
|
-
* the weak reference is still valid, to ensure that the underlying object
|
|
179
|
-
* is never freed while still in use
|
|
180
|
-
* ```typescript
|
|
181
|
-
* const myFooWeak = myFoo.getWeak();
|
|
182
|
-
*
|
|
183
|
-
* // assume we have some async code that needs to use myFoo
|
|
184
|
-
* declare async function doSomethingWithFoo(foo: FooErcRef): Promise<void>;
|
|
185
|
-
*
|
|
186
|
-
* // Below is BAD!
|
|
187
|
-
* await doSomethingWithFoo(myFooWeak);
|
|
188
|
-
* // Reason: doSomethingWithFoo is async, so it's possible that
|
|
189
|
-
* // myFooWeak is invalidated when it's still needed. If the implementation
|
|
190
|
-
* // does not check for that, it could be deferencing a dangling pointer
|
|
191
|
-
* // (of course, it could actually be fine depending on the implementation of doSomethingWithFoo)
|
|
192
|
-
*
|
|
193
|
-
* // Recommendation is to use strong reference for async operations
|
|
194
|
-
* const myFooStrong = myFooWeak.getStrong();
|
|
195
|
-
* await doSomethingWithFoo(myFooStrong.getWeak()); // will never be freed while awaiting
|
|
196
|
-
* // now we free
|
|
197
|
-
* myFooStrong.free();
|
|
198
|
-
*
|
|
199
|
-
* ```
|
|
200
|
-
*
|
|
201
|
-
* ## Assigning to Erc
|
|
202
|
-
* Each `Erc` instance should only ever have one external reference. So you should not
|
|
203
|
-
* assign to an `Erc` variable directly:
|
|
204
|
-
* ```typescript
|
|
205
|
-
* // DO NOT DO THIS
|
|
206
|
-
* let myFoo = makeFooErc(createFoo());
|
|
207
|
-
* myFoo = makeFooErc(createFoo()); // previous Erc is overriden without proper clean up
|
|
208
|
-
* ```
|
|
209
|
-
*
|
|
210
|
-
* If you want to attach a new value to an existing `Erc`, use the `assign` method:
|
|
211
|
-
* ```typescript
|
|
212
|
-
* const myFoo = makeFooErc(createFoo());
|
|
213
|
-
* myFoo.assign(createFoo()); // previous Erc is freed, and the new one is assigned
|
|
214
|
-
* myFoo.free(); // new one is freed
|
|
215
|
-
* ```
|
|
216
|
-
* The example above does not cause leaks, since the previous Erc is freed
|
|
217
|
-
*
|
|
218
|
-
* However, if you call assign with the value of another `Erc`, it will cause memory
|
|
219
|
-
* issues:
|
|
220
|
-
* ```typescript
|
|
221
|
-
* // DO NOT DO THIS
|
|
222
|
-
* const myFoo1 = makeFooErc(createFoo());
|
|
223
|
-
* const myFoo2 = makeFooErc(createFoo());
|
|
224
|
-
* myFoo1.assign(myFoo2.value); // myFoo1 is freed, and myFoo2 is assigned
|
|
225
|
-
* // BAD: now both myFoo1 and myFoo2 references the same object, but the ref count is 1
|
|
226
|
-
* myFoo1.free(); // no issue here, object is freed, but myFoo2 now holds a dangling pointer
|
|
227
|
-
* myFoo2.free(); // double free!
|
|
228
|
-
*
|
|
229
|
-
* // The correct way to do this is to use `take`:
|
|
230
|
-
* const myFoo1 = makeFooErc(createFoo());
|
|
231
|
-
* const myFoo2 = makeFooErc(createFoo());
|
|
232
|
-
* myFoo1.assign(myFoo2.take()); // myFoo1 is freed, and myFoo2 is assigned, myFoo2 is empty
|
|
233
|
-
* myFoo1.free(); // no issue here, object is freed
|
|
234
|
-
* // myFoo2 is empty, so calling free() has no effect
|
|
235
|
-
* ```
|
|
236
|
-
*
|
|
237
|
-
* Assign also works if both `Erc` are 2 references of the same object:
|
|
238
|
-
* ```typescript
|
|
239
|
-
* const myFoo1 = makeFooErc(createFoo()); // ref count is 1
|
|
240
|
-
* const myFoo2 = myFoo1.getStrong(); // ref count is 2
|
|
241
|
-
* myFoo1.assign(myFoo2.take()); // frees old value, ref count is 1
|
|
242
|
-
*
|
|
243
|
-
* // This also works:
|
|
244
|
-
* const myFoo1 = makeFooErc(createFoo()); // ref count is 1
|
|
245
|
-
* myFoo1.assign(myFoo1.take()); // take() makes myFoo1 empty, so assign() doesn't free it
|
|
246
|
-
*
|
|
247
|
-
* // DO NOT DO THIS:
|
|
248
|
-
* myFoo1.assign(myFoo1.value); // this will free the value since ref count is 0, and result in a dangling pointer
|
|
249
|
-
* ```
|
|
250
|
-
*
|
|
251
|
-
* @deprecated use Emp
|
|
252
|
-
*/
|
|
253
|
-
export const makeErcType = <TName, TRepr>({
|
|
254
|
-
marker,
|
|
255
|
-
free,
|
|
256
|
-
addRef,
|
|
257
|
-
}: ErcTypeConstructor<TName, TRepr>): ((value: TRepr | undefined) => Erc<TName, TRepr>) => {
|
|
258
|
-
const createStrongRef = (value: TRepr | undefined): Erc<TName, TRepr> => {
|
|
259
|
-
let weakRef: (ErcRef<TName, TRepr> & { invalidate: () => void }) | undefined = undefined;
|
|
260
|
-
const invalidateWeakRef = () => {
|
|
261
|
-
if (!weakRef) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
const oldWeakRef = weakRef;
|
|
265
|
-
weakRef = undefined;
|
|
266
|
-
oldWeakRef.invalidate();
|
|
267
|
-
};
|
|
268
|
-
const createWeakRef = (initialValue: TRepr | undefined) => {
|
|
269
|
-
const weak = {
|
|
270
|
-
type: marker,
|
|
271
|
-
value: initialValue,
|
|
272
|
-
invalidate: () => {
|
|
273
|
-
weak.value = undefined;
|
|
274
|
-
},
|
|
275
|
-
getStrong: () => {
|
|
276
|
-
if (weak.value === undefined) {
|
|
277
|
-
return createStrongRef(undefined);
|
|
278
|
-
}
|
|
279
|
-
return createStrongRef(addRef(weak.value));
|
|
280
|
-
},
|
|
281
|
-
};
|
|
282
|
-
return weak;
|
|
283
|
-
};
|
|
284
|
-
const erc = {
|
|
285
|
-
type: marker,
|
|
286
|
-
value,
|
|
287
|
-
free: () => {
|
|
288
|
-
if (erc.value !== undefined) {
|
|
289
|
-
invalidateWeakRef();
|
|
290
|
-
free(erc.value);
|
|
291
|
-
erc.value = undefined;
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
assign: (newValue: TRepr | undefined) => {
|
|
295
|
-
erc.free();
|
|
296
|
-
erc.value = newValue;
|
|
297
|
-
},
|
|
298
|
-
take: () => {
|
|
299
|
-
invalidateWeakRef();
|
|
300
|
-
const oldValue = erc.value;
|
|
301
|
-
erc.value = undefined;
|
|
302
|
-
return oldValue;
|
|
303
|
-
},
|
|
304
|
-
getWeak: () => {
|
|
305
|
-
if (!weakRef) {
|
|
306
|
-
weakRef = createWeakRef(erc.value);
|
|
307
|
-
}
|
|
308
|
-
return weakRef;
|
|
309
|
-
},
|
|
310
|
-
getStrong: () => {
|
|
311
|
-
if (erc.value === undefined) {
|
|
312
|
-
return createStrongRef(undefined);
|
|
313
|
-
}
|
|
314
|
-
return createStrongRef(addRef(erc.value));
|
|
315
|
-
},
|
|
316
|
-
};
|
|
317
|
-
return erc;
|
|
318
|
-
};
|
|
319
|
-
return createStrongRef;
|
|
320
|
-
};
|
package/src/pref/dark.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { persist } from "../memory/persist.ts";
|
|
2
|
-
import { injectStyle } from "./inject_style.ts";
|
|
3
|
-
|
|
4
|
-
const dark = persist({
|
|
5
|
-
initial: false,
|
|
6
|
-
key: "Pure.Dark",
|
|
7
|
-
storage: localStorage,
|
|
8
|
-
serialize: (value) => (value ? "1" : ""),
|
|
9
|
-
deserialize: (value) => !!value,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Returns if dark mode is prefered in the browser environment
|
|
14
|
-
*
|
|
15
|
-
* If `window.matchMedia` is not available, it will return `false`
|
|
16
|
-
*/
|
|
17
|
-
export const prefersDarkMode = (): boolean => {
|
|
18
|
-
return !!globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/** Value for the `color-scheme` CSS property */
|
|
22
|
-
export type ColorScheme = "light" | "dark";
|
|
23
|
-
/** Option for initializing dark mode */
|
|
24
|
-
export type DarkOptions = {
|
|
25
|
-
/**
|
|
26
|
-
* Initial value for dark mode
|
|
27
|
-
*
|
|
28
|
-
* If not set, it will default to calling `prefersDarkMode()`.
|
|
29
|
-
*
|
|
30
|
-
* If `persist` is `true`, it will also check the value from localStorage
|
|
31
|
-
*/
|
|
32
|
-
initial?: boolean;
|
|
33
|
-
/** Persist the dark mode preference to localStorage */
|
|
34
|
-
persist?: boolean;
|
|
35
|
-
/**
|
|
36
|
-
* The selector to set `color-scheme` property
|
|
37
|
-
*
|
|
38
|
-
* Defaults to `:root`. If set to empty string, CSS will not be updated
|
|
39
|
-
*/
|
|
40
|
-
selector?: string;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Init Dark mode wrappers
|
|
45
|
-
*
|
|
46
|
-
* ## Detect user preference
|
|
47
|
-
* User preference is detected with `matchMedia` API, if available.
|
|
48
|
-
* ```typescript
|
|
49
|
-
* import { prefersDarkMode } from "@pistonite/pure/dark";
|
|
50
|
-
*
|
|
51
|
-
* console.log(prefersDarkMode());
|
|
52
|
-
* ```
|
|
53
|
-
*
|
|
54
|
-
* ## Global dark mode state
|
|
55
|
-
* `initDark` initializes the dark mode state.
|
|
56
|
-
* ```typescript
|
|
57
|
-
* import { initDark, isDark, setDark, addDarkSubscriber } from "@pistonite/pure/dark";
|
|
58
|
-
*
|
|
59
|
-
* initDark();
|
|
60
|
-
* console.log(isDark());
|
|
61
|
-
*
|
|
62
|
-
* addDarkSubscriber((dark) => { console.log("Dark mode changed: ", dark); });
|
|
63
|
-
* setDark(true); // will trigger the subscriber
|
|
64
|
-
* ```
|
|
65
|
-
*
|
|
66
|
-
* ## Use with React
|
|
67
|
-
* A React hook is provided in the [`pure-react`](https://jsr.io/@pistonite/pure-react/doc/pref) package
|
|
68
|
-
* to get the dark mode state from React components.
|
|
69
|
-
*
|
|
70
|
-
* Use `setDark` to change the dark mode state from React compoenents like you would from anywhere else.
|
|
71
|
-
*
|
|
72
|
-
* ## Persisting to localStorage
|
|
73
|
-
* You can persist the dark mode preference to by passing `persist: true` to `initDark`.
|
|
74
|
-
* This will make `initDark` also load the preference from localStorage.
|
|
75
|
-
* ```typescript
|
|
76
|
-
* import { initDark } from "@pistonite/pure/dark";
|
|
77
|
-
*
|
|
78
|
-
* initDark({ persist: true });
|
|
79
|
-
* ```
|
|
80
|
-
*
|
|
81
|
-
* ## Setting `color-scheme` CSS property
|
|
82
|
-
* The `color-scheme` property handles dark mode for native components like buttons
|
|
83
|
-
* and scrollbars. By default, `initDark` will handle setting this property for the `:root` selector.
|
|
84
|
-
* You can override this by passing a `selector` option.
|
|
85
|
-
* ```typescript
|
|
86
|
-
* import { initDark } from "@pistonite/pure/dark";
|
|
87
|
-
*
|
|
88
|
-
* // will set `.my-app { color-scheme: dark }`
|
|
89
|
-
* initDark({ selector: ".my-app" });
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
export const initDark = (options: DarkOptions = {}): void => {
|
|
93
|
-
const _dark = options.initial || prefersDarkMode();
|
|
94
|
-
|
|
95
|
-
const selector = options.selector ?? ":root";
|
|
96
|
-
if (selector) {
|
|
97
|
-
// notify immediately to update the style initially
|
|
98
|
-
addDarkSubscriber((dark: boolean) => {
|
|
99
|
-
updateStyle(dark, selector);
|
|
100
|
-
}, true /* notify */);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (options.persist) {
|
|
104
|
-
dark.init(_dark);
|
|
105
|
-
} else {
|
|
106
|
-
dark.disable();
|
|
107
|
-
dark.set(_dark);
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Clears the persisted dark mode preference
|
|
113
|
-
*
|
|
114
|
-
* If you are doing this, you should probably call `setDark`
|
|
115
|
-
* with `prefersDarkMode()` or some initial value immediately before this,
|
|
116
|
-
* so the current dark mode is set to user's preferred mode.
|
|
117
|
-
*
|
|
118
|
-
* Note if `persist` is `true` when initializing,
|
|
119
|
-
* subsequence `setDark` calls will still persist the value.
|
|
120
|
-
*/
|
|
121
|
-
export const clearPersistedDarkPerference = (): void => {
|
|
122
|
-
dark.clear();
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Gets the current value of dark mode
|
|
127
|
-
*/
|
|
128
|
-
export const isDark = (): boolean => dark.get();
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Set the value of dark mode
|
|
132
|
-
*/
|
|
133
|
-
export const setDark = (value: boolean): void => {
|
|
134
|
-
dark.set(value);
|
|
135
|
-
};
|
|
136
|
-
/**
|
|
137
|
-
* Add a subscriber to dark mode changes and return a function to remove the subscriber
|
|
138
|
-
*
|
|
139
|
-
* If `notifyImmediately` is `true`, the subscriber will be called immediately with the current value
|
|
140
|
-
*/
|
|
141
|
-
export const addDarkSubscriber = (
|
|
142
|
-
subscriber: (dark: boolean) => void,
|
|
143
|
-
notifyImmediately?: boolean,
|
|
144
|
-
): (() => void) => {
|
|
145
|
-
return dark.subscribe(subscriber, notifyImmediately);
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const updateStyle = (dark: boolean, selector: string) => {
|
|
149
|
-
const text = `${selector} { color-scheme: ${dark ? "dark" : "light"}; }`;
|
|
150
|
-
injectStyle("pure-pref-dark", text);
|
|
151
|
-
};
|
package/src/pref/device.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* # pure/pref
|
|
3
|
-
* Preference utilities (things like locale and theme).
|
|
4
|
-
*
|
|
5
|
-
* These deal with raw CSS and DOM API, and is probably not
|
|
6
|
-
* very useful outside of browser environment.
|
|
7
|
-
*
|
|
8
|
-
* @module
|
|
9
|
-
*/
|
|
10
|
-
export * from "./dark.ts";
|
|
11
|
-
export * from "./inject_style.ts";
|
|
12
|
-
export * from "./locale.ts";
|
|
13
|
-
export * from "./device.ts";
|
package/src/pref/inject_style.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Inject a css string into a style tag identified by the id
|
|
3
|
-
*
|
|
4
|
-
* Will remove the old style tag(s) if exist
|
|
5
|
-
*/
|
|
6
|
-
export function injectStyle(id: string, style: string) {
|
|
7
|
-
const styleTags = document.querySelectorAll(`style[data-inject="${id}"]`);
|
|
8
|
-
if (styleTags.length !== 1) {
|
|
9
|
-
const styleTag = document.createElement("style");
|
|
10
|
-
styleTag.setAttribute("data-inject", id);
|
|
11
|
-
styleTag.innerText = style;
|
|
12
|
-
document.head.appendChild(styleTag);
|
|
13
|
-
setTimeout(() => {
|
|
14
|
-
styleTags.forEach((tag) => tag.remove());
|
|
15
|
-
}, 0);
|
|
16
|
-
} else {
|
|
17
|
-
const e = styleTags[0] as HTMLStyleElement;
|
|
18
|
-
if (e.innerText !== style) {
|
|
19
|
-
e.innerText = style;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|