@inglorious/utils 3.7.3 → 4.0.0
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 +67 -3
- package/package.json +89 -9
- package/src/algorithms/decision-tree.js +3 -3
- package/src/algorithms/path-finding.js +6 -6
- package/src/data-structures/board.js +97 -15
- package/src/data-structures/index.js +2 -2
- package/src/data-structures/objects.js +352 -34
- package/src/data-structures/string.js +13 -0
- package/src/data-structures/tree.js +1 -1
- package/src/functions/functions.js +168 -0
- package/src/functions/index.js +1 -4
- package/src/math/circle.js +5 -7
- package/src/math/index.js +0 -4
- package/src/math/line.js +3 -3
- package/src/math/point.js +10 -9
- package/src/math/quaternions.js +20 -4
- package/src/math/rectangle.js +4 -4
- package/src/math/segment.js +11 -5
- package/src/math/vectors.js +471 -65
- package/src/physics/acceleration.js +2 -3
- package/src/physics/friction.js +2 -2
- package/src/physics/gravity.js +2 -2
- package/src/physics/jump.js +0 -4
- package/src/physics/velocity.js +2 -3
- package/src/v.js +1 -1
- package/types/algorithms/decision-tree.d.ts +27 -0
- package/types/algorithms/index.d.ts +2 -0
- package/types/algorithms/path-finding.d.ts +69 -0
- package/types/data-structures/array.d.ts +56 -0
- package/types/data-structures/board.d.ts +121 -0
- package/types/data-structures/boolean.d.ts +4 -0
- package/types/data-structures/heap.d.ts +69 -0
- package/types/data-structures/index.d.ts +8 -0
- package/types/data-structures/objects.d.ts +237 -0
- package/types/data-structures/set.d.ts +4 -0
- package/types/data-structures/string.d.ts +7 -0
- package/types/data-structures/tree.d.ts +21 -0
- package/types/functions/functions.d.ts +57 -0
- package/types/functions/index.d.ts +1 -0
- package/types/index.d.ts +6 -4
- package/types/math/circle.d.ts +22 -0
- package/types/math/hitmask.d.ts +20 -0
- package/types/math/index.d.ts +14 -0
- package/types/math/line.d.ts +11 -0
- package/types/math/linear-interpolation.d.ts +2 -0
- package/types/math/numbers.d.ts +26 -0
- package/types/math/point.d.ts +25 -0
- package/types/math/quaternions.d.ts +10 -0
- package/types/math/rectangle.d.ts +36 -0
- package/types/math/rng.d.ts +14 -0
- package/types/math/segment.d.ts +23 -0
- package/types/math/statistics.d.ts +11 -0
- package/types/math/triangle.d.ts +5 -0
- package/types/math/trigonometry.d.ts +26 -0
- package/types/math/vectors.d.ts +190 -0
- package/types/physics/acceleration.d.ts +24 -0
- package/types/physics/friction.d.ts +11 -0
- package/types/physics/gravity.d.ts +20 -0
- package/types/physics/index.d.ts +5 -0
- package/types/physics/jump.d.ts +10 -0
- package/types/physics/velocity.d.ts +20 -0
- package/types/v.d.ts +7 -3
- package/src/data-structures/object.js +0 -320
- package/src/functions/function.js +0 -9
- package/src/math/quaternion.js +0 -22
- package/src/math/vector.js +0 -426
- package/types/algorithms.d.ts +0 -26
- package/types/data-structures.d.ts +0 -53
- package/types/math.d.ts +0 -90
|
@@ -1,6 +1,89 @@
|
|
|
1
|
-
import { isFunction } from "../functions/
|
|
1
|
+
import { isFunction } from "../functions/functions.js"
|
|
2
|
+
import { v } from "../v.js"
|
|
2
3
|
import { isArray } from "./array.js"
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
const INITIAL_LEVEL = 0
|
|
6
|
+
const LAST_KEY_OFFSET = 1
|
|
7
|
+
const NEXT_KEY_OFFSET = 1
|
|
8
|
+
const NEXT_LEVEL = 2
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a deep clone of a given value using the structured clone algorithm.
|
|
12
|
+
* This is a modern and efficient way to deep-clone objects.
|
|
13
|
+
*
|
|
14
|
+
* Note: `structuredClone` has some limitations. It cannot clone functions,
|
|
15
|
+
* DOM nodes, property descriptors, getters, setters, or certain other
|
|
16
|
+
* non-serializable objects. It will throw a `DataCloneError` in such cases.
|
|
17
|
+
*
|
|
18
|
+
* For more details, see:
|
|
19
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
|
|
20
|
+
*
|
|
21
|
+
* This is similar to Lodash's `_.cloneDeep`.
|
|
22
|
+
*
|
|
23
|
+
* @template T
|
|
24
|
+
* @param {T} obj - The value to clone.
|
|
25
|
+
* @returns {T} A deep clone of the input value.
|
|
26
|
+
*/
|
|
27
|
+
export function clone(obj) {
|
|
28
|
+
return deserialize(serialize(obj))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Assigns default properties to a target object from a source object.
|
|
33
|
+
* For each key in `defaultProps`, if `target[key]` is `null` or `undefined`,
|
|
34
|
+
* it is set to `defaultProps[key]`.
|
|
35
|
+
*
|
|
36
|
+
* This function modifies the target object in place.
|
|
37
|
+
*
|
|
38
|
+
* @param {Object} target The object to apply defaults to.
|
|
39
|
+
* @param {Object} defaultProps The object containing the default properties.
|
|
40
|
+
* @returns {Object} The modified target object.
|
|
41
|
+
*/
|
|
42
|
+
export function defaults(target, defaultProps) {
|
|
43
|
+
for (const key in defaultProps) {
|
|
44
|
+
target[key] ??= defaultProps[key]
|
|
45
|
+
}
|
|
46
|
+
return target
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Deserializes an object, converting plain object representations back into
|
|
51
|
+
* their original types, such as vector-like objects. This is the inverse
|
|
52
|
+
* operation of `serialize`.
|
|
53
|
+
*
|
|
54
|
+
* - Recursively deserializes nested objects.
|
|
55
|
+
* - Converts objects with a `_type: "vector"` property and `coords` array
|
|
56
|
+
* back into a vector-like object using the `v` factory function.
|
|
57
|
+
* - Copies all other property values as-is.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} str - The JSON string to deserialize.
|
|
60
|
+
* @returns {Object} The deserialized object.
|
|
61
|
+
*/
|
|
62
|
+
export function deserialize(str) {
|
|
63
|
+
const data = JSON.parse(str)
|
|
64
|
+
|
|
65
|
+
return revive(data)
|
|
66
|
+
|
|
67
|
+
function revive(value) {
|
|
68
|
+
if (isArray(value)) {
|
|
69
|
+
return value.map(revive)
|
|
70
|
+
}
|
|
71
|
+
if (isObject(value)) {
|
|
72
|
+
if (value._type === "vector" && value.coords) {
|
|
73
|
+
return v(...value.coords)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const deserialized = {}
|
|
77
|
+
for (const key in value) {
|
|
78
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
79
|
+
deserialized[key] = revive(value[key])
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return deserialized
|
|
83
|
+
}
|
|
84
|
+
return value
|
|
85
|
+
}
|
|
86
|
+
}
|
|
4
87
|
|
|
5
88
|
/**
|
|
6
89
|
* Creates a new object by deeply merging a target object with one or more source objects.
|
|
@@ -17,6 +100,111 @@ export function extend(target, ...sources) {
|
|
|
17
100
|
return extendWith(undefined, target, ...sources)
|
|
18
101
|
}
|
|
19
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Creates a new object by deeply merging a target object with one or more source objects,
|
|
105
|
+
* using a custom merger function to handle specific properties.
|
|
106
|
+
* This function is immutable.
|
|
107
|
+
*
|
|
108
|
+
* @param {Function} [merger] - A function to customize merging behavior. It receives `(targetValue, sourceValue)`. If it returns `undefined`, the default merge logic is used.
|
|
109
|
+
* @param {Object} target - The base object.
|
|
110
|
+
* @param {...Object} sources - The source objects to merge.
|
|
111
|
+
* @returns {Object} A new object with the merged properties.
|
|
112
|
+
*/
|
|
113
|
+
export function extendWith(merger, target, ...sources) {
|
|
114
|
+
return mergeWith(merger, {}, target, ...sources)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Filters the properties of an object based on a callback function.
|
|
119
|
+
*
|
|
120
|
+
* @param {Object} obj - The object to filter.
|
|
121
|
+
* @param {Function} callback - A function that determines whether a property should be included.
|
|
122
|
+
* Receives (key, value, obj) as arguments.
|
|
123
|
+
* @returns {Object} A new object with the filtered properties.
|
|
124
|
+
*/
|
|
125
|
+
export function filter(obj, callback) {
|
|
126
|
+
return Object.fromEntries(
|
|
127
|
+
Object.entries(obj).filter(([key, value], obj) =>
|
|
128
|
+
callback(key, value, obj),
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Finds the first property in an object that satisfies the callback function.
|
|
135
|
+
*
|
|
136
|
+
* @param {Object} obj - The object to search.
|
|
137
|
+
* @param {Function} callback - A function that determines whether a property matches.
|
|
138
|
+
* Receives (key, value, obj) as arguments.
|
|
139
|
+
* @returns {Object} An object containing the first matching property, or an empty object if none match.
|
|
140
|
+
*/
|
|
141
|
+
export function find(obj, callback) {
|
|
142
|
+
return Object.fromEntries([
|
|
143
|
+
Object.entries(obj).find(([key, value], obj) => callback(key, value, obj)),
|
|
144
|
+
])
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Gets the value at a specified path of an object. If the resolved value is
|
|
149
|
+
* `undefined`, the `defaultValue` is returned in its place. This function
|
|
150
|
+
* supports dot-notation for nested properties and numbers for array indices.
|
|
151
|
+
*
|
|
152
|
+
* This is similar to Lodash's `_.get`.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* const object = { 'a': [{ 'b': { 'c': 3 } }] };
|
|
156
|
+
* get(object, 'a.0.b.c');
|
|
157
|
+
* // => 3
|
|
158
|
+
*
|
|
159
|
+
* get(object, 'a.0.d', 'default');
|
|
160
|
+
* // => 'default'
|
|
161
|
+
*
|
|
162
|
+
* @param {Object} obj - The object to query.
|
|
163
|
+
* @param {string} path - The path of the property to retrieve.
|
|
164
|
+
* @param {*} [defaultValue=undefined] - The value returned for `undefined` resolved values.
|
|
165
|
+
* @returns {*} Returns the resolved value, else the `defaultValue`.
|
|
166
|
+
*/
|
|
167
|
+
export function get(obj, path, defaultValue = undefined) {
|
|
168
|
+
if (!path) return defaultValue
|
|
169
|
+
|
|
170
|
+
const keys = path.split(".")
|
|
171
|
+
let result = obj
|
|
172
|
+
|
|
173
|
+
for (const key of keys) {
|
|
174
|
+
if (result == null || typeof result !== "object") {
|
|
175
|
+
return defaultValue
|
|
176
|
+
}
|
|
177
|
+
result = result[key]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return result !== undefined ? result : defaultValue
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Checks if a value is a plain object.
|
|
185
|
+
*
|
|
186
|
+
* @param {*} value - The value to check.
|
|
187
|
+
* @returns {boolean} True if the value is a plain object, false otherwise.
|
|
188
|
+
*/
|
|
189
|
+
export function isObject(value) {
|
|
190
|
+
return value != null && value.constructor === Object
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Maps the properties of an object using a callback function.
|
|
195
|
+
*
|
|
196
|
+
* @param {Object} obj - The object to map.
|
|
197
|
+
* @param {Function} callback - A function that transforms each property.
|
|
198
|
+
* Receives (key, value, obj) as arguments.
|
|
199
|
+
* @returns {Object} A new object with the mapped properties.
|
|
200
|
+
*/
|
|
201
|
+
export function map(obj, callback) {
|
|
202
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
203
|
+
acc[key] = callback(key, value, obj)
|
|
204
|
+
return acc
|
|
205
|
+
}, {})
|
|
206
|
+
}
|
|
207
|
+
|
|
20
208
|
/**
|
|
21
209
|
* Merges multiple source objects into a target object.
|
|
22
210
|
* This function is mutable; it modifies the `target` object in place.
|
|
@@ -32,20 +220,6 @@ export function merge(target, ...sources) {
|
|
|
32
220
|
return mergeWith(undefined, target, ...sources)
|
|
33
221
|
}
|
|
34
222
|
|
|
35
|
-
/**
|
|
36
|
-
* Creates a new object by deeply merging a target object with one or more source objects,
|
|
37
|
-
* using a custom merger function to handle specific properties.
|
|
38
|
-
* This function is immutable.
|
|
39
|
-
*
|
|
40
|
-
* @param {Function} [merger] - A function to customize merging behavior. It receives `(targetValue, sourceValue)`. If it returns `undefined`, the default merge logic is used.
|
|
41
|
-
* @param {Object} target - The base object.
|
|
42
|
-
* @param {...Object} sources - The source objects to merge.
|
|
43
|
-
* @returns {Object} A new object with the merged properties.
|
|
44
|
-
*/
|
|
45
|
-
export function extendWith(merger, target, ...sources) {
|
|
46
|
-
return mergeWith(merger, {}, target, ...sources)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
223
|
/**
|
|
50
224
|
* Merges multiple source objects into a target object, using a custom merger function.
|
|
51
225
|
* This function is mutable; it modifies the `target` object in place.
|
|
@@ -61,6 +235,168 @@ export function mergeWith(merger, target, ...sources) {
|
|
|
61
235
|
.reduce((acc, source) => deepMerge(acc, source, merger), target)
|
|
62
236
|
}
|
|
63
237
|
|
|
238
|
+
/**
|
|
239
|
+
* A utility function inspired by Immer's `produce` API. It provides a convenient
|
|
240
|
+
* way to work with immutable data structures by allowing "mutations" on a
|
|
241
|
+
* temporary draft.
|
|
242
|
+
*
|
|
243
|
+
* **Important:** Unlike Immer, which uses structural sharing via proxies for
|
|
244
|
+
* high performance, this implementation performs a full deep clone of the base
|
|
245
|
+
* state on every call using `deserialize(serialize())`. This can be
|
|
246
|
+
* inefficient for large or complex states. It is intended for simple use cases
|
|
247
|
+
* where the convenience of the API outweighs the performance cost.
|
|
248
|
+
*
|
|
249
|
+
* The recipe function receives a draft copy of the state. It can either
|
|
250
|
+
* mutate the draft and return nothing (`undefined`), or it can return a
|
|
251
|
+
* completely new value, which will become the next state.
|
|
252
|
+
*
|
|
253
|
+
* Can be called in two ways:
|
|
254
|
+
* - **Standard:** `produce(baseState, recipe, ...args)`
|
|
255
|
+
* - **Curried:** `produce(recipe)` returns a new function `(baseState, ...args) => newState`
|
|
256
|
+
*
|
|
257
|
+
* @template T
|
|
258
|
+
* @param {T|function(T, ...*): (T|void)} baseState The initial state, or a recipe for currying.
|
|
259
|
+
* @param {function(T, ...*): (T|void)} [recipe] The recipe function.
|
|
260
|
+
* @param {...*} args Additional arguments to pass to the recipe.
|
|
261
|
+
* @returns {T | function(T, ...*): T} A new state, or a producer function if curried.
|
|
262
|
+
*/
|
|
263
|
+
export function produce(baseState, recipe, ...args) {
|
|
264
|
+
if (typeof baseState === "function" && recipe === undefined) {
|
|
265
|
+
const recipeFn = baseState
|
|
266
|
+
return (state, ...recipeArgs) => produce(state, recipeFn, ...recipeArgs)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const draft = clone(baseState)
|
|
270
|
+
const result = recipe(draft, ...args)
|
|
271
|
+
return result === undefined ? draft : result
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Serializes an object, converting special types like vectors into a plain
|
|
276
|
+
* object representation. This is useful for processes like saving state to a
|
|
277
|
+
* file or sending it over a network.
|
|
278
|
+
*
|
|
279
|
+
* - Recursively serializes nested objects.
|
|
280
|
+
* - Converts objects with a `__isVector__` property into a serializable
|
|
281
|
+
* format: `{ _type: "vector", coords: [...] }`.
|
|
282
|
+
* - Copies all other property values as-is.
|
|
283
|
+
*
|
|
284
|
+
* @param {Object} obj - The object to serialize.
|
|
285
|
+
* @returns {string} The serialized JSON string.
|
|
286
|
+
*/
|
|
287
|
+
export function serialize(obj) {
|
|
288
|
+
function replacer(key, value) {
|
|
289
|
+
// Handle top-level vector
|
|
290
|
+
if (value?.__isVector__) {
|
|
291
|
+
return {
|
|
292
|
+
_type: "vector",
|
|
293
|
+
coords: Array.from(value),
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (isObject(value)) {
|
|
298
|
+
const serialized = {}
|
|
299
|
+
|
|
300
|
+
for (const k in value) {
|
|
301
|
+
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
|
302
|
+
serialized[k] = replacer(undefined, value[k])
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return serialized
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return value
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return JSON.stringify(obj, replacer)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Sets the value at a specified path of an object. If a portion of the path
|
|
317
|
+
* doesn't exist, it's created. Arrays are created for missing index properties
|
|
318
|
+
* while objects are created for all other missing properties. This function
|
|
319
|
+
* mutates the object.
|
|
320
|
+
*
|
|
321
|
+
* This is similar to Lodash's `_.set`.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* const object = { 'a': [{ 'b': { 'c': 3 } }] };
|
|
325
|
+
* set(object, 'a.0.b.d', 4);
|
|
326
|
+
* // object is now { 'a': [{ 'b': { 'c': 3, 'd': 4 } }] }
|
|
327
|
+
*
|
|
328
|
+
* set(object, 'x.0.y', 5);
|
|
329
|
+
* // object is now { 'a': [...], 'x': [{ 'y': 5 }] }
|
|
330
|
+
*
|
|
331
|
+
* @param {Object} obj - The object to modify.
|
|
332
|
+
* @param {string} path - The path of the property to set.
|
|
333
|
+
* @param {*} value - The value to set.
|
|
334
|
+
* @returns {Object} Returns the modified object.
|
|
335
|
+
*/
|
|
336
|
+
export function set(obj, path, value) {
|
|
337
|
+
if (!path) return obj
|
|
338
|
+
|
|
339
|
+
const keys = path.split(".")
|
|
340
|
+
let current = obj
|
|
341
|
+
|
|
342
|
+
for (let i = 0; i < keys.length - LAST_KEY_OFFSET; i++) {
|
|
343
|
+
const key = keys[i]
|
|
344
|
+
const nextKey = keys[i + NEXT_KEY_OFFSET]
|
|
345
|
+
|
|
346
|
+
// Create missing intermediate objects
|
|
347
|
+
if (!(key in current) || typeof current[key] !== "object") {
|
|
348
|
+
// Create array if next key is a number, object otherwise
|
|
349
|
+
current[key] = /^\d+$/.test(nextKey) ? [] : {}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
current = current[key]
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
current[keys[keys.length - LAST_KEY_OFFSET]] = value
|
|
356
|
+
return obj
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Converts an object or array to a formatted string representation.
|
|
361
|
+
*
|
|
362
|
+
* @param {*} obj - The object or array to convert.
|
|
363
|
+
* @param {number} [indentationLevel=INITIAL_LEVEL] - The current indentation level (used for nested structures).
|
|
364
|
+
* @returns {string} A string representation of the input object or array.
|
|
365
|
+
*/
|
|
366
|
+
export function toString(obj, indentationLevel = INITIAL_LEVEL) {
|
|
367
|
+
if (Array.isArray(obj)) {
|
|
368
|
+
return `[
|
|
369
|
+
${obj
|
|
370
|
+
.map(
|
|
371
|
+
(item) =>
|
|
372
|
+
" ".repeat(indentationLevel + NEXT_LEVEL) +
|
|
373
|
+
toString(item, indentationLevel + NEXT_LEVEL),
|
|
374
|
+
)
|
|
375
|
+
.join(",\n")}
|
|
376
|
+
${" ".repeat(indentationLevel)}]`
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (typeof obj === "object" && obj != null) {
|
|
380
|
+
return `{
|
|
381
|
+
${Object.entries(obj)
|
|
382
|
+
.map(
|
|
383
|
+
([key, value]) =>
|
|
384
|
+
`${" ".repeat(indentationLevel + NEXT_LEVEL)}${key}: ${toString(
|
|
385
|
+
value,
|
|
386
|
+
indentationLevel + NEXT_LEVEL,
|
|
387
|
+
)}`,
|
|
388
|
+
)
|
|
389
|
+
.join(",\n")}
|
|
390
|
+
${" ".repeat(indentationLevel)}}`
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (typeof obj === "string") {
|
|
394
|
+
return `"${obj}"`
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return obj
|
|
398
|
+
}
|
|
399
|
+
|
|
64
400
|
/**
|
|
65
401
|
* Recursively merges properties from a source object into a target object.
|
|
66
402
|
* This is a helper function for `merge` and `extend`.
|
|
@@ -93,21 +429,3 @@ function deepMerge(target, source, merger) {
|
|
|
93
429
|
}
|
|
94
430
|
return target
|
|
95
431
|
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Assigns default properties to a target object from a source object.
|
|
99
|
-
* For each key in `defaultProps`, if `target[key]` is `null` or `undefined`,
|
|
100
|
-
* it is set to `defaultProps[key]`.
|
|
101
|
-
*
|
|
102
|
-
* This function modifies the target object in place.
|
|
103
|
-
*
|
|
104
|
-
* @param {Object} target The object to apply defaults to.
|
|
105
|
-
* @param {Object} defaultProps The object containing the default properties.
|
|
106
|
-
* @returns {Object} The modified target object.
|
|
107
|
-
*/
|
|
108
|
-
export function defaults(target, defaultProps) {
|
|
109
|
-
for (const key in defaultProps) {
|
|
110
|
-
target[key] ??= defaultProps[key]
|
|
111
|
-
}
|
|
112
|
-
return target
|
|
113
|
-
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert kebab-case or PascalCase to camelCase.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} input - The string to convert.
|
|
5
|
+
* @returns {string} The camelCased string.
|
|
6
|
+
*/
|
|
7
|
+
export function toCamelCase(input) {
|
|
8
|
+
const [firstChar, ...rest] = input.replace(/-([a-z0-9])/gi, (_, c) =>
|
|
9
|
+
c.toUpperCase(),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
return [firstChar.toLowerCase(), ...rest].join("")
|
|
13
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const DEFAULT_DELAY = 0
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Composes multiple functions from right to left, as if every function wraps the next one.
|
|
3
5
|
* The first function (rightmost) can take multiple arguments; the remaining functions must be unary.
|
|
@@ -14,6 +16,84 @@ export function compose(...fns) {
|
|
|
14
16
|
return (...args) => rest.reduce((acc, fn) => fn(acc), last(...args))
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Creates a debounced function that delays invoking `fn` until after `delay`
|
|
21
|
+
* milliseconds have elapsed since the last call.
|
|
22
|
+
*
|
|
23
|
+
* @param {Function} fn - The function to debounce.
|
|
24
|
+
* @param {number} [delay=0] - The number of milliseconds to delay.
|
|
25
|
+
* @returns {Function & { cancel: Function, flush: Function, pending: Function }} The debounced function.
|
|
26
|
+
*/
|
|
27
|
+
export function debounce(fn, delay = DEFAULT_DELAY) {
|
|
28
|
+
let timeoutId
|
|
29
|
+
let lastArgs
|
|
30
|
+
let lastThis
|
|
31
|
+
let result
|
|
32
|
+
|
|
33
|
+
function clearPending() {
|
|
34
|
+
lastArgs = undefined
|
|
35
|
+
lastThis = undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function invoke() {
|
|
39
|
+
const args = lastArgs
|
|
40
|
+
const context = lastThis
|
|
41
|
+
|
|
42
|
+
clearPending()
|
|
43
|
+
result = fn.apply(context, args)
|
|
44
|
+
return result
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function debounced(...args) {
|
|
48
|
+
lastArgs = args
|
|
49
|
+
lastThis = this
|
|
50
|
+
|
|
51
|
+
if (timeoutId !== undefined) {
|
|
52
|
+
clearTimeout(timeoutId)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
timeoutId = setTimeout(() => {
|
|
56
|
+
timeoutId = undefined
|
|
57
|
+
invoke()
|
|
58
|
+
}, delay)
|
|
59
|
+
|
|
60
|
+
return result
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
debounced.cancel = () => {
|
|
64
|
+
if (timeoutId !== undefined) {
|
|
65
|
+
clearTimeout(timeoutId)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
timeoutId = undefined
|
|
69
|
+
clearPending()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
debounced.flush = () => {
|
|
73
|
+
if (timeoutId === undefined) {
|
|
74
|
+
return result
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
clearTimeout(timeoutId)
|
|
78
|
+
timeoutId = undefined
|
|
79
|
+
return invoke()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
debounced.pending = () => timeoutId !== undefined
|
|
83
|
+
|
|
84
|
+
return debounced
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Checks if a value is a function.
|
|
89
|
+
*
|
|
90
|
+
* @param {*} func - The value to check.
|
|
91
|
+
* @returns {boolean} True if the value is a function, false otherwise.
|
|
92
|
+
*/
|
|
93
|
+
export function isFunction(func) {
|
|
94
|
+
return typeof func === "function"
|
|
95
|
+
}
|
|
96
|
+
|
|
17
97
|
/**
|
|
18
98
|
* Pipes multiple functions from left to right, as if the functions are applied one by one.
|
|
19
99
|
* The first function (leftmost) can take multiple arguments; the remaining functions must be unary.
|
|
@@ -29,3 +109,91 @@ export function pipe(...fns) {
|
|
|
29
109
|
const [first, ...rest] = fns
|
|
30
110
|
return (...args) => rest.reduce((acc, fn) => fn(acc), first(...args))
|
|
31
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Creates a throttled function that invokes `fn` immediately on the leading
|
|
115
|
+
* edge, then suppresses further calls until `delay` milliseconds have
|
|
116
|
+
* elapsed.
|
|
117
|
+
*
|
|
118
|
+
* By default (`hasTrailing: false`), any calls made while suppressed are
|
|
119
|
+
* dropped entirely — only the leading call's arguments are ever used, and
|
|
120
|
+
* the very last call of a burst may be lost. Pass `{ hasTrailing: true }` to
|
|
121
|
+
* additionally invoke `fn` once more, after `delay` has elapsed, with the
|
|
122
|
+
* arguments from the most recent suppressed call — guaranteeing the final
|
|
123
|
+
* state of a burst is never silently dropped, at the cost of `fn` running
|
|
124
|
+
* again asynchronously after the burst settles.
|
|
125
|
+
*
|
|
126
|
+
* @param {Function} fn - The function to throttle.
|
|
127
|
+
* @param {number} [delay=0] - The number of milliseconds to suppress repeated calls.
|
|
128
|
+
* @param {Object} [options]
|
|
129
|
+
* @param {boolean} [options.hasTrailing=false] - Whether to invoke `fn` once
|
|
130
|
+
* more after `delay`, using the arguments of the last suppressed call.
|
|
131
|
+
* @returns {Function & { cancel: Function, pending: Function }} The throttled function.
|
|
132
|
+
*/
|
|
133
|
+
export function throttle(
|
|
134
|
+
fn,
|
|
135
|
+
delay = DEFAULT_DELAY,
|
|
136
|
+
{ hasTrailing = false } = {},
|
|
137
|
+
) {
|
|
138
|
+
let timeoutId
|
|
139
|
+
let result
|
|
140
|
+
let lastArgs
|
|
141
|
+
let lastThis
|
|
142
|
+
let trailingCallPending = false
|
|
143
|
+
|
|
144
|
+
function clearPending() {
|
|
145
|
+
lastArgs = undefined
|
|
146
|
+
lastThis = undefined
|
|
147
|
+
trailingCallPending = false
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function startWindow() {
|
|
151
|
+
timeoutId = setTimeout(() => {
|
|
152
|
+
timeoutId = undefined
|
|
153
|
+
|
|
154
|
+
// NOTE: this runs asynchronously, after `delay` has elapsed — so this
|
|
155
|
+
// is the one path where `fn` is not invoked synchronously with the
|
|
156
|
+
// triggering call. Callers that pass draft/proxy objects through `fn`
|
|
157
|
+
// (e.g. a mutative entity) must treat this like a debounced call: only
|
|
158
|
+
// hold onto primitives across the throttle boundary, and re-fetch any
|
|
159
|
+
// live object when `fn` actually runs here.
|
|
160
|
+
if (trailingCallPending) {
|
|
161
|
+
const args = lastArgs
|
|
162
|
+
const context = lastThis
|
|
163
|
+
clearPending()
|
|
164
|
+
result = fn.apply(context, args)
|
|
165
|
+
}
|
|
166
|
+
}, delay)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function throttled(...args) {
|
|
170
|
+
lastArgs = args
|
|
171
|
+
lastThis = this
|
|
172
|
+
|
|
173
|
+
if (timeoutId !== undefined) {
|
|
174
|
+
if (hasTrailing) {
|
|
175
|
+
trailingCallPending = true
|
|
176
|
+
}
|
|
177
|
+
return result
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
result = fn.apply(this, args)
|
|
181
|
+
lastArgs = undefined
|
|
182
|
+
lastThis = undefined
|
|
183
|
+
startWindow()
|
|
184
|
+
return result
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throttled.cancel = () => {
|
|
188
|
+
if (timeoutId !== undefined) {
|
|
189
|
+
clearTimeout(timeoutId)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
timeoutId = undefined
|
|
193
|
+
clearPending()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
throttled.pending = () => timeoutId !== undefined || trailingCallPending
|
|
197
|
+
|
|
198
|
+
return throttled
|
|
199
|
+
}
|
package/src/functions/index.js
CHANGED
package/src/math/circle.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("../../types/math").
|
|
3
|
-
* @typedef {import("../../types/math").Circle} Circle
|
|
4
|
-
* @typedef {import("../../types/math").Rectangle} Rectangle
|
|
5
|
-
* @typedef {import("../../types/math").Platform} Platform
|
|
2
|
+
* @typedef {import("../../types/math/point").PointInput} PointInput
|
|
3
|
+
* @typedef {import("../../types/math/circle").Circle} Circle
|
|
4
|
+
* @typedef {import("../../types/math/rectangle").Rectangle} Rectangle
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import { intersectsCircle as pointIntersectsCircle } from "./point.js"
|
|
9
8
|
import { hypothenuse } from "./triangle.js"
|
|
10
|
-
import { clamp } from "./
|
|
11
|
-
import { subtract } from "./vectors.js"
|
|
9
|
+
import { clamp, subtract } from "./vectors.js"
|
|
12
10
|
|
|
13
11
|
const HALF = 2
|
|
14
12
|
const SQUARED = 2
|
|
@@ -17,7 +15,7 @@ const INITIAL_SUM = 0
|
|
|
17
15
|
/**
|
|
18
16
|
* Checks if a circle intersects with a point.
|
|
19
17
|
* @param {Circle} circle - The circle to check.
|
|
20
|
-
* @param {
|
|
18
|
+
* @param {PointInput} point - The point to check.
|
|
21
19
|
* @returns {boolean} True if the point intersects the circle, false otherwise.
|
|
22
20
|
*/
|
|
23
21
|
export function intersectsPoint(circle, point) {
|
package/src/math/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import * as line from "./line.js"
|
|
|
4
4
|
import * as linearInterpolation from "./linear-interpolation.js"
|
|
5
5
|
import * as numbers from "./numbers.js"
|
|
6
6
|
import * as point from "./point.js"
|
|
7
|
-
import * as quaternion from "./quaternion.js"
|
|
8
7
|
import * as quaternions from "./quaternions.js"
|
|
9
8
|
import * as rectangle from "./rectangle.js"
|
|
10
9
|
import * as rng from "./rng.js"
|
|
@@ -12,7 +11,6 @@ import * as segment from "./segment.js"
|
|
|
12
11
|
import * as statistics from "./statistics.js"
|
|
13
12
|
import * as triangle from "./triangle.js"
|
|
14
13
|
import * as trigonometry from "./trigonometry.js"
|
|
15
|
-
import * as vector from "./vector.js"
|
|
16
14
|
import * as vectors from "./vectors.js"
|
|
17
15
|
|
|
18
16
|
export {
|
|
@@ -22,7 +20,6 @@ export {
|
|
|
22
20
|
linearInterpolation,
|
|
23
21
|
numbers,
|
|
24
22
|
point,
|
|
25
|
-
quaternion,
|
|
26
23
|
quaternions,
|
|
27
24
|
rectangle,
|
|
28
25
|
rng,
|
|
@@ -30,6 +27,5 @@ export {
|
|
|
30
27
|
statistics,
|
|
31
28
|
triangle,
|
|
32
29
|
trigonometry,
|
|
33
|
-
vector,
|
|
34
30
|
vectors,
|
|
35
31
|
}
|
package/src/math/line.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("../../types/math").Line} Line
|
|
3
|
-
* @typedef {import("../../types/math").Point} Point
|
|
4
|
-
* @typedef {import("../../types/math").Circle} Circle
|
|
2
|
+
* @typedef {import("../../types/math/line").Line} Line
|
|
3
|
+
* @typedef {import("../../types/math/point").Point} Point
|
|
4
|
+
* @typedef {import("../../types/math/circle").Circle} Circle
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { abs } from "./numbers.js"
|