@semio/utils 0.0.0 → 0.1.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/dist/index.js CHANGED
@@ -30,18 +30,30 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AnimatableGroupType: () => AnimatableGroupType,
33
34
  EMAIL: () => EMAIL,
34
35
  PlayerContext: () => PlayerContext,
36
+ PlayerDirection: () => PlayerDirection,
35
37
  PlayerSlice: () => PlayerSlice,
36
38
  Result: () => Result,
37
39
  alpha: () => alpha,
38
40
  altColor: () => altColor,
39
41
  angularDistance: () => angularDistance,
40
42
  closestFrame: () => closestFrame,
43
+ colorChroma: () => colorChroma,
44
+ colorLightness: () => colorLightness,
45
+ composeContexts: () => composeContexts,
41
46
  composeProgress: () => composeProgress,
42
47
  composeProgressiveResult: () => composeProgressiveResult,
48
+ createContextChecker: () => createContextChecker,
49
+ createContextHook: () => createContextHook,
50
+ createContextProvider: () => createContextProvider,
51
+ createContextSolution: () => createContextSolution,
43
52
  downloadBlob: () => downloadBlob,
44
53
  downloadJSONFile: () => downloadJSONFile,
54
+ eulerRotationBetween: () => eulerRotationBetween,
55
+ eulerSlerp: () => eulerSlerp,
56
+ eulerToQuaternion: () => eulerToQuaternion,
45
57
  eulerToRotationMatrix: () => eulerToRotationMatrix,
46
58
  getDegenerateHexagonPath: () => getDegenerateHexagonPath,
47
59
  getHexagonPath: () => getHexagonPath,
@@ -62,8 +74,12 @@ __export(index_exports, {
62
74
  instanceOfRawVector2: () => instanceOfRawVector2,
63
75
  instanceOfRawVector3: () => instanceOfRawVector3,
64
76
  isMapDeepEqual: () => isMapDeepEqual,
77
+ isNearNeutralMid: () => isNearNeutralMid,
78
+ isOrthographicCamera: () => isOrthographicCamera,
79
+ isPerspectiveCamera: () => isPerspectiveCamera,
65
80
  isRawObject: () => isRawObject,
66
81
  log: () => log,
82
+ mergeValueMaps: () => mergeValueMaps,
67
83
  newPlayer: () => newPlayer,
68
84
  now: () => now,
69
85
  numberToDurationString: () => numberToDurationString,
@@ -74,24 +90,41 @@ __export(index_exports, {
74
90
  pause: () => pause,
75
91
  play: () => play,
76
92
  pointsToPath: () => pointsToPath,
93
+ quaternionConjugate: () => quaternionConjugate,
94
+ quaternionMultiply: () => quaternionMultiply,
95
+ quaternionSlerp: () => quaternionSlerp,
96
+ quaternionToEuler: () => quaternionToEuler,
97
+ randomHexString: () => randomHexString,
77
98
  rawHSLToHex: () => rawHSLToHex,
78
99
  rawRGBToHex: () => rawRGBToHex,
79
100
  reset: () => reset,
101
+ reversed: () => reversed,
102
+ rgbChroma: () => rgbChroma,
103
+ rgbLightness: () => rgbLightness,
80
104
  rgbToRgba: () => rgbToRgba,
81
105
  rotationMatrixToAngle: () => rotationMatrixToAngle,
82
106
  seek: () => seek,
83
- setBounds: () => setBounds,
84
- setDuration: () => setDuration,
85
- setViewport: () => setViewport,
107
+ shouldUseBlendForLabelColor: () => shouldUseBlendForLabelColor,
108
+ storedEulerXYZtoRPY: () => storedEulerXYZtoRPY,
86
109
  stringifyMapped: () => stringifyMapped,
87
110
  toDegrees: () => toDegrees,
88
111
  toRadians: () => toRadians,
89
- update: () => update,
112
+ updated: () => updated,
90
113
  useDeep: () => useDeep,
91
114
  useDeepSelector: () => useDeepSelector,
92
115
  useLazy: () => useLazy,
93
116
  useMediaQuery: () => useMediaQuery,
94
- usePlayerStore: () => usePlayerStore
117
+ useOptionalContext: () => useOptionalContext,
118
+ usePlayerStore: () => usePlayerStore,
119
+ useValuesStore: () => useValuesStore,
120
+ warnUnexpectedFeatureValue: () => warnUnexpectedFeatureValue,
121
+ withBounce: () => withBounce,
122
+ withBounds: () => withBounds,
123
+ withDirection: () => withDirection,
124
+ withDuration: () => withDuration,
125
+ withLooping: () => withLooping,
126
+ withOptionalContext: () => withOptionalContext,
127
+ withSpeed: () => withSpeed
95
128
  });
96
129
  module.exports = __toCommonJS(index_exports);
97
130
 
@@ -116,103 +149,114 @@ function getId(lookup) {
116
149
  }
117
150
 
118
151
  // src/player.ts
152
+ var PlayerDirection = /* @__PURE__ */ ((PlayerDirection3) => {
153
+ PlayerDirection3["Reverse"] = "reverse";
154
+ PlayerDirection3["Forward"] = "forward";
155
+ return PlayerDirection3;
156
+ })(PlayerDirection || {});
119
157
  function reset(player, stamp) {
120
158
  const p = { ...player };
121
159
  const nowVal = now();
122
160
  p._currentTime = nowVal;
123
161
  p._previousTime = nowVal;
124
162
  p.stamp = stamp ? stamp : 0;
163
+ p._currentDirection = p.direction;
164
+ p._bounced = false;
165
+ p._enteredBounds = false;
125
166
  return p;
126
167
  }
127
168
  function now() {
128
169
  return (typeof performance === "undefined" ? Date : performance).now();
129
170
  }
130
- function update(player, coldStart) {
171
+ function updated(player, coldStart) {
131
172
  const p = { ...player };
132
173
  const duration = p.duration;
133
174
  const t = now();
134
175
  p._previousTime = coldStart ? t : p._currentTime;
135
176
  p._currentTime = t;
136
177
  const currentInBounds = p.stamp >= p.bounds[0] && p.stamp <= p.bounds[1];
137
- const currentViewportCenter = (p.viewport[1] + p.viewport[0]) / 2;
138
- const nearViewportCenterCurrent = Math.abs(p.stamp - currentViewportCenter) < 0.01;
139
- const delta = (p._currentTime - p._previousTime) * p.timescale / duration;
178
+ if (currentInBounds && !p._enteredBounds) {
179
+ p._enteredBounds = true;
180
+ }
181
+ const effectiveTimescale = p.speed * (p._currentDirection === "forward" /* Forward */ ? 1 : -1);
182
+ const delta = (p._currentTime - p._previousTime) * effectiveTimescale;
140
183
  const updatedStamp = p.stamp + delta;
141
- const [start, end] = currentInBounds ? p.bounds : [0, 1];
142
- let attachOverride = false;
184
+ const [start, end] = !p._enteredBounds && !currentInBounds ? [0, duration] : p.bounds;
185
+ const movingTowardBounds = p.stamp < start && p._currentDirection === "forward" /* Forward */ || p.stamp > end && p._currentDirection === "reverse" /* Reverse */;
143
186
  if (updatedStamp > end) {
144
- if (p.playback === "loop") {
145
- p.stamp = start;
146
- } else if (p.playback === "bounce") {
147
- p.stamp = end;
148
- p.timescale *= -1;
187
+ if (movingTowardBounds) {
188
+ p.stamp = updatedStamp;
149
189
  } else {
150
- p.stamp = start;
151
- p.running = false;
152
- }
153
- attachOverride = true;
154
- } else if (currentInBounds && updatedStamp < start) {
155
- if (p.playback === "loop") {
156
- p.stamp = start;
157
- if (p.timescale < 0) {
190
+ if (p.bounce && (p.looping || !p._bounced)) {
191
+ p.stamp = end;
192
+ p._currentDirection = p._currentDirection === "forward" /* Forward */ ? "reverse" /* Reverse */ : "forward" /* Forward */;
193
+ p._bounced = true;
194
+ } else if (p.looping) {
195
+ p.stamp = start;
196
+ } else {
197
+ p.stamp = end;
158
198
  p.running = false;
159
199
  }
160
- } else if (p.playback === "bounce") {
161
- p.stamp = start;
162
- p.timescale *= -1;
200
+ }
201
+ } else if (updatedStamp < start) {
202
+ if (movingTowardBounds) {
203
+ p.stamp = updatedStamp;
163
204
  } else {
164
- p.stamp = start;
165
- p.running = false;
205
+ if (p.bounce && (p.looping || !p._bounced)) {
206
+ p.stamp = start;
207
+ p._currentDirection = p._currentDirection === "forward" /* Forward */ ? "reverse" /* Reverse */ : "forward" /* Forward */;
208
+ p._bounced = true;
209
+ } else if (p.looping) {
210
+ p.stamp = end;
211
+ } else {
212
+ p.stamp = start;
213
+ p.running = false;
214
+ }
166
215
  }
167
- attachOverride = true;
168
216
  } else {
169
217
  p.stamp = updatedStamp;
170
218
  }
171
- const newNearViewportCenter = Math.abs(p.stamp - currentViewportCenter) < 0.01;
172
- const boundsInsideViewport = p.viewport[0] <= p.bounds[0] || p.viewport[1] >= p.bounds[1];
173
- if (p.running && !boundsInsideViewport && (nearViewportCenterCurrent || newNearViewportCenter || attachOverride)) {
174
- p.viewport = getFittedViewport(p.stamp, p.viewport);
175
- } else if (p.running && !boundsInsideViewport && !newNearViewportCenter) {
176
- const oomphOffset = currentViewportCenter < p.stamp ? delta : 0;
177
- const idealViewport = getFittedViewport(p.stamp + oomphOffset, p.viewport);
178
- p.viewport = [
179
- p.viewport[0] * 0.6 + idealViewport[0] * 0.4,
180
- p.viewport[1] * 0.6 + idealViewport[1] * 0.4
181
- ];
182
- }
183
219
  return p;
184
220
  }
185
- function setBounds(player, bounds) {
221
+ function withBounds(player, bounds) {
186
222
  const p = { ...player };
187
223
  p.bounds = bounds;
188
224
  return p;
189
225
  }
190
- function setViewport(player, viewport) {
191
- const p = { ...player };
192
- p.viewport = viewport;
193
- return p;
194
- }
195
- function play(player, speed) {
226
+ function play(player, speed, direction) {
196
227
  const p = { ...player };
197
228
  p.running = true;
198
- p.timescale = speed ?? 1;
199
- if (p.playback === "once" && p.stamp === p.bounds[1]) {
229
+ if (speed !== void 0) p.speed = Math.abs(speed);
230
+ if (direction !== void 0) {
231
+ p.direction = direction;
232
+ }
233
+ p._currentDirection = p.direction;
234
+ p._bounced = false;
235
+ p._enteredBounds = false;
236
+ if (!p.looping && !p.bounce) {
237
+ if (p.direction === "forward" /* Forward */ && p.stamp === p.bounds[1]) {
238
+ p.stamp = p.bounds[0];
239
+ } else if (p.direction === "reverse" /* Reverse */ && p.stamp === p.bounds[0]) {
240
+ p.stamp = p.bounds[1];
241
+ }
242
+ }
243
+ const tolerance = 1;
244
+ if (p._currentDirection === "forward" /* Forward */ && Math.abs(p.stamp - p.bounds[1]) <= tolerance) {
200
245
  p.stamp = p.bounds[0];
246
+ } else if (p._currentDirection === "reverse" /* Reverse */ && Math.abs(p.stamp - p.bounds[0]) <= tolerance) {
247
+ p.stamp = p.bounds[1];
201
248
  }
202
249
  return p;
203
250
  }
204
251
  function pause(player) {
205
252
  const p = { ...player };
206
253
  p.running = false;
207
- p.timescale = 0;
208
254
  return p;
209
255
  }
210
256
  function seek(player, stamp) {
211
- const p = reset(player, stamp);
212
- p.viewport = getFittedViewport(stamp, p.viewport);
213
- return p;
257
+ return reset(player, stamp);
214
258
  }
215
- function setDuration(player, duration) {
259
+ function withDuration(player, duration) {
216
260
  const p = { ...player };
217
261
  p.duration = duration;
218
262
  return p;
@@ -223,25 +267,52 @@ function newPlayer() {
223
267
  _previousTime: now(),
224
268
  _currentTime: now(),
225
269
  stamp: 0,
226
- timescale: 0,
227
- bounds: [0, 1],
228
- playback: "loop",
229
- viewport: [0, 1],
230
- duration: 1e3
270
+ speed: 1,
271
+ direction: "forward" /* Forward */,
272
+ _currentDirection: "forward" /* Forward */,
273
+ _bounced: false,
274
+ _enteredBounds: false,
275
+ bounds: [0, 5e3],
276
+ bounce: false,
277
+ looping: true,
278
+ duration: 5e3
231
279
  };
232
280
  }
233
- var getFittedViewport = (playhead, proposedViewport) => {
234
- const viewportWidth = proposedViewport[1] - proposedViewport[0];
235
- const proposedLeft = playhead - viewportWidth / 2;
236
- const proposedRight = playhead + viewportWidth / 2;
237
- if (proposedLeft >= 0 && proposedRight <= 1) {
238
- return [playhead - viewportWidth / 2, playhead + viewportWidth / 2];
239
- } else if (proposedLeft < 0) {
240
- return [0, viewportWidth];
241
- } else {
242
- return [1 - viewportWidth, 1];
281
+ function withSpeed(player, speed) {
282
+ const p = { ...player };
283
+ if (speed < 0) {
284
+ throw new Error("Speed must be a positive number.");
243
285
  }
244
- };
286
+ p.speed = speed;
287
+ return p;
288
+ }
289
+ function withDirection(player, direction) {
290
+ const p = { ...player };
291
+ p.direction = direction;
292
+ p._currentDirection = direction;
293
+ p._bounced = false;
294
+ p._enteredBounds = false;
295
+ return p;
296
+ }
297
+ function reversed(player) {
298
+ const p = { ...player };
299
+ const newDirection = p.direction === "forward" /* Forward */ ? "reverse" /* Reverse */ : "forward" /* Forward */;
300
+ p.direction = newDirection;
301
+ p._currentDirection = newDirection;
302
+ p._bounced = false;
303
+ p._enteredBounds = false;
304
+ return p;
305
+ }
306
+ function withBounce(player, bounce) {
307
+ const p = { ...player };
308
+ p.bounce = bounce;
309
+ return p;
310
+ }
311
+ function withLooping(player, looping) {
312
+ const p = { ...player };
313
+ p.looping = looping;
314
+ return p;
315
+ }
245
316
 
246
317
  // src/player-store.ts
247
318
  var import_react = require("react");
@@ -253,29 +324,50 @@ THREE.Object3D.DEFAULT_UP.set(0, 0, 1);
253
324
  (0, import_immer.enableMapSet)();
254
325
  var PlayerSlice = (set) => ({
255
326
  player: newPlayer(),
256
- // Set the duration of the player
327
+ // Set the duration of the player. Also rescales bounds if they were at
328
+ // the old default. Uses `produce` (rather than the spread+`withX` pattern
329
+ // the other actions use) because the bounds rescaling is a multi-field
330
+ // atomic conditional write. Viewport rescaling is handled separately by
331
+ // the viewport-follow subscription.
257
332
  updateDuration: (duration) => {
258
333
  set(
259
334
  (0, import_immer.produce)((state) => {
335
+ const oldDuration = state.player.duration;
260
336
  state.player.duration = duration;
337
+ if (oldDuration > 0 && duration > 0) {
338
+ const b = state.player.bounds;
339
+ if (Math.abs(b[1] - oldDuration) < 1) {
340
+ state.player.bounds = [b[0], duration];
341
+ }
342
+ }
261
343
  })
262
344
  );
263
345
  },
264
- // Set the timescale of the animation playback
265
- updateTimescale: (speed) => {
266
- set(
267
- (0, import_immer.produce)((state) => {
268
- state.player.timescale = speed;
269
- })
270
- );
346
+ // Set the speed of the animation playback
347
+ updateSpeed: (speed) => {
348
+ set((state) => ({
349
+ player: withSpeed(state.player, speed)
350
+ }));
351
+ },
352
+ // Set the direction of the animation playback
353
+ updateDirection: (direction) => {
354
+ set((state) => ({
355
+ player: withDirection(state.player, direction)
356
+ }));
357
+ },
358
+ // Reverse the current direction of the animation playback
359
+ reverseDirection: () => {
360
+ set((state) => ({
361
+ player: reversed(state.player)
362
+ }));
271
363
  },
272
364
  // Set the time of the animation
273
365
  resetPlayer: (time) => {
274
366
  set((state) => ({ player: reset(state.player, time) }));
275
367
  },
276
368
  // Play the animation
277
- playPlayer: (speed) => {
278
- set((state) => ({ player: play(state.player, speed) }));
369
+ playPlayer: (speed, direction) => {
370
+ set((state) => ({ player: play(state.player, speed, direction) }));
279
371
  },
280
372
  // Pause the animation
281
373
  pausePlayer: () => {
@@ -284,13 +376,13 @@ var PlayerSlice = (set) => ({
284
376
  // Update the timer
285
377
  updatePlayer: (coldStart) => {
286
378
  set((state) => ({
287
- player: update(state.player, coldStart ?? false)
379
+ player: updated(state.player, coldStart ?? false)
288
380
  }));
289
381
  },
290
382
  // Set the player bounds
291
383
  updatePlayerBounds: (start, end) => {
292
384
  set((state) => ({
293
- player: setBounds(state.player, [start, end])
385
+ player: withBounds(state.player, [start, end])
294
386
  }));
295
387
  },
296
388
  updatePlayerBound: (bound, time) => {
@@ -298,54 +390,32 @@ var PlayerSlice = (set) => ({
298
390
  const t = time ?? state.player.stamp;
299
391
  if (bound === "start" && state.player.bounds[1] <= t) {
300
392
  return {
301
- player: setBounds(state.player, [state.player.bounds[1], t])
393
+ player: withBounds(state.player, [state.player.bounds[1], t])
302
394
  };
303
395
  } else if (bound === "end" && state.player.bounds[0] >= t) {
304
396
  return {
305
- player: setBounds(state.player, [t, state.player.bounds[0]])
397
+ player: withBounds(state.player, [t, state.player.bounds[0]])
306
398
  };
307
399
  } else if (bound === "start") {
308
400
  return {
309
- player: setBounds(state.player, [t, state.player.bounds[1]])
401
+ player: withBounds(state.player, [t, state.player.bounds[1]])
310
402
  };
311
403
  } else {
312
404
  return {
313
- player: setBounds(state.player, [state.player.bounds[0], t])
405
+ player: withBounds(state.player, [state.player.bounds[0], t])
314
406
  };
315
407
  }
316
408
  });
317
409
  },
318
- // Set the player viewport
319
- updatePlayerViewport: (start, end) => {
410
+ updateBounce: (bounce) => {
320
411
  set((state) => ({
321
- player: setViewport(state.player, [start, end])
412
+ player: withBounce(state.player, bounce)
322
413
  }));
323
414
  },
324
- updatePlayerViewportCenter: (time) => {
415
+ updateLooping: (looping) => {
325
416
  set((state) => ({
326
- player: seek(state.player, time ?? state.player.stamp)
417
+ player: withLooping(state.player, looping)
327
418
  }));
328
- },
329
- updatePlayerViewportBound: (bound, time) => {
330
- set((state) => {
331
- if (bound === "start" && state.player.viewport[1] <= time) {
332
- return {
333
- player: setViewport(state.player, [state.player.viewport[1], time])
334
- };
335
- } else if (bound === "end" && state.player.viewport[0] >= time) {
336
- return {
337
- player: setViewport(state.player, [time, state.player.viewport[0]])
338
- };
339
- } else if (bound === "start") {
340
- return {
341
- player: setViewport(state.player, [time, state.player.viewport[1]])
342
- };
343
- } else {
344
- return {
345
- player: setViewport(state.player, [state.player.viewport[0], time])
346
- };
347
- }
348
- });
349
419
  }
350
420
  });
351
421
  var usePlayerStore = (0, import_zustand.create)()(
@@ -354,6 +424,17 @@ var usePlayerStore = (0, import_zustand.create)()(
354
424
  var PlayerContext = (0, import_react.createContext)(null);
355
425
 
356
426
  // src/animated-values.ts
427
+ var AnimatableGroupType = /* @__PURE__ */ ((AnimatableGroupType2) => {
428
+ AnimatableGroupType2["Vector2"] = "vector2";
429
+ AnimatableGroupType2["Vector3"] = "vector3";
430
+ AnimatableGroupType2["Euler"] = "euler";
431
+ AnimatableGroupType2["RGB"] = "rgb";
432
+ AnimatableGroupType2["HSL"] = "hsl";
433
+ AnimatableGroupType2["Group"] = "group";
434
+ AnimatableGroupType2["Entity"] = "entity";
435
+ AnimatableGroupType2["Instance"] = "instance";
436
+ return AnimatableGroupType2;
437
+ })(AnimatableGroupType || {});
357
438
  function instanceOfRawBoolean(object) {
358
439
  return typeof object === "boolean";
359
440
  }
@@ -367,10 +448,10 @@ function instanceOfRawVector3(object) {
367
448
  return object.x !== void 0 && object.y !== void 0 && object.z !== void 0;
368
449
  }
369
450
  function instanceOfRawVector2(object) {
370
- return object.x !== void 0 && object.y !== void 0;
451
+ return object.x !== void 0 && object.y !== void 0 && object.z === void 0;
371
452
  }
372
453
  function instanceOfRawEuler(object) {
373
- return object.x !== void 0 && object.y !== void 0 && object.z !== void 0;
454
+ return object.r !== void 0 && object.p !== void 0 && object.y !== void 0;
374
455
  }
375
456
  function instanceOfRawColor(object) {
376
457
  return object.r !== void 0 && object.g !== void 0 && object.b !== void 0 || object.h !== void 0 && object.s !== void 0 && object.l !== void 0;
@@ -381,6 +462,12 @@ function instanceOfRawRGB(object) {
381
462
  function instanceOfRawHSL(object) {
382
463
  return object.h !== void 0 && object.s !== void 0 && object.l !== void 0;
383
464
  }
465
+ function warnUnexpectedFeatureValue(feature, expected, value) {
466
+ console.warn(
467
+ `Feature "${feature}" expected ${expected}, received ${typeof value}:`,
468
+ value
469
+ );
470
+ }
384
471
  function isRawObject(value) {
385
472
  if (instanceOfRawVector3(value) || instanceOfRawVector2(value) || instanceOfRawEuler(value) || instanceOfRawColor(value) || instanceOfRawRGB(value) || instanceOfRawHSL(value))
386
473
  return true;
@@ -462,9 +549,11 @@ function getDegenerateHexagonPath(x, y, size, height) {
462
549
  }
463
550
 
464
551
  // src/closest-frame.ts
465
- function closestFrame(completion, count) {
466
- const frameIdx = Math.round(completion * count);
467
- return frameIdx / count;
552
+ function closestFrame(stampMs, framerate) {
553
+ if (framerate <= 0) return stampMs;
554
+ const frameDurationMs = 1e3 / framerate;
555
+ const frameIdx = Math.round(stampMs / frameDurationMs);
556
+ return frameIdx * frameDurationMs;
468
557
  }
469
558
 
470
559
  // src/angles.ts
@@ -559,19 +648,62 @@ function rawRGBToHex({ r, g, b }) {
559
648
  function rawHSLToHex({ h, s, l }) {
560
649
  return (0, import_color.default)({ h, s, l }).hex();
561
650
  }
651
+ function randomHexString(constrainLuminosity, constrainSaturation) {
652
+ const h = Math.floor(Math.random() * 255);
653
+ const s = Math.floor(constrainSaturation ? constrainSaturation * 100 : Math.random() * 100);
654
+ const l = Math.floor(constrainLuminosity ? constrainLuminosity * 100 : Math.random() * 100);
655
+ return (0, import_color.default)({ h, s, l }).hex();
656
+ }
657
+ function rgbChroma([r, g, b]) {
658
+ const max = Math.max(r, g, b);
659
+ const min = Math.min(r, g, b);
660
+ return (max - min) / 255;
661
+ }
662
+ function rgbLightness([r, g, b]) {
663
+ const max = Math.max(r, g, b);
664
+ const min = Math.min(r, g, b);
665
+ return (max + min) / (2 * 255);
666
+ }
667
+ function colorChroma(color) {
668
+ return rgbChroma(hexToRgbArray(color));
669
+ }
670
+ function colorLightness(color) {
671
+ return rgbLightness(hexToRgbArray(color));
672
+ }
673
+ function shouldUseBlendForLabelColor(color, opts) {
674
+ if (!color) return false;
675
+ let rgb;
676
+ try {
677
+ rgb = hexToRgbArray(color);
678
+ } catch {
679
+ return false;
680
+ }
681
+ const chroma = rgbChroma(rgb);
682
+ const lightness = rgbLightness(rgb);
683
+ const { chromaCutoff = 0.08, minLightness = 0.28, maxLightness = 0.75 } = opts ?? {};
684
+ const isNearGray = chroma < chromaCutoff;
685
+ const isMidLightness = lightness > minLightness && lightness < maxLightness;
686
+ return !(isNearGray && isMidLightness);
687
+ }
688
+ function isNearNeutralMid(color, opts) {
689
+ const { chromaCutoff = 0.08, minLightness = 0.28, maxLightness = 0.75 } = opts ?? {};
690
+ const c = colorChroma(color);
691
+ const l = colorLightness(color);
692
+ return c < chromaCutoff && l > minLightness && l < maxLightness;
693
+ }
562
694
 
563
695
  // src/use-deep.ts
564
696
  var import_react3 = require("react");
565
697
  var import_deep_equal = __toESM(require("deep-equal"));
566
698
  function useDeepSelector(selector) {
567
- const prev = (0, import_react3.useRef)();
699
+ const prev = (0, import_react3.useRef)(void 0);
568
700
  return (state) => {
569
701
  const next = selector(state);
570
702
  return (0, import_deep_equal.default)(prev.current, next) ? prev.current : prev.current = next;
571
703
  };
572
704
  }
573
705
  function useDeep(initializer, dependencies) {
574
- const prevValue = (0, import_react3.useRef)();
706
+ const prevValue = (0, import_react3.useRef)(void 0);
575
707
  const prevDependencies = (0, import_react3.useRef)([]);
576
708
  return (0, import_deep_equal.default)(prevDependencies.current, dependencies) ? prevValue.current : prevValue.current = initializer();
577
709
  }
@@ -709,6 +841,98 @@ function angularDistance(euler1, euler2) {
709
841
  ];
710
842
  return rotationMatrixToAngle(R12);
711
843
  }
844
+ function eulerToQuaternion(euler) {
845
+ const [z, y, x] = euler;
846
+ const cx = Math.cos(x * 0.5);
847
+ const sx = Math.sin(x * 0.5);
848
+ const cy = Math.cos(y * 0.5);
849
+ const sy = Math.sin(y * 0.5);
850
+ const cz = Math.cos(z * 0.5);
851
+ const sz = Math.sin(z * 0.5);
852
+ const w = cx * cy * cz + sx * sy * sz;
853
+ const qx = sx * cy * cz - cx * sy * sz;
854
+ const qy = cx * sy * cz + sx * cy * sz;
855
+ const qz = cx * cy * sz - sx * sy * cz;
856
+ return [w, qx, qy, qz];
857
+ }
858
+ function quaternionToEuler(q) {
859
+ const [w, x, y, z] = q;
860
+ const sinrCosp = 2 * (w * x + y * z);
861
+ const cosrCosp = 1 - 2 * (x * x + y * y);
862
+ const roll = Math.atan2(sinrCosp, cosrCosp);
863
+ const sinp = 2 * (w * y - z * x);
864
+ let pitch;
865
+ if (Math.abs(sinp) >= 1) {
866
+ pitch = Math.sign(sinp) * Math.PI / 2;
867
+ } else {
868
+ pitch = Math.asin(sinp);
869
+ }
870
+ const sinyCosp = 2 * (w * z + x * y);
871
+ const cosyCosp = 1 - 2 * (y * y + z * z);
872
+ const yaw = Math.atan2(sinyCosp, cosyCosp);
873
+ return [yaw, pitch, roll];
874
+ }
875
+ function quaternionMultiply(q1, q2) {
876
+ const [w1, x1, y1, z1] = q1;
877
+ const [w2, x2, y2, z2] = q2;
878
+ const w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2;
879
+ const x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2;
880
+ const y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2;
881
+ const z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2;
882
+ return [w, x, y, z];
883
+ }
884
+ function quaternionConjugate(q) {
885
+ const [w, x, y, z] = q;
886
+ return [w, -x, -y, -z];
887
+ }
888
+ function quaternionSlerp(q1, q2, t) {
889
+ const [w1, x1, y1, z1] = q1;
890
+ const [w2, x2, y2, z2] = q2;
891
+ let dot = w1 * w2 + x1 * x2 + y1 * y2 + z1 * z2;
892
+ let q2Adjusted = [w2, x2, y2, z2];
893
+ if (dot < 0) {
894
+ q2Adjusted = [-w2, -x2, -y2, -z2];
895
+ dot = -dot;
896
+ }
897
+ const DOT_THRESHOLD = 0.9995;
898
+ if (dot > DOT_THRESHOLD) {
899
+ const result = [
900
+ w1 + t * (q2Adjusted[0] - w1),
901
+ x1 + t * (q2Adjusted[1] - x1),
902
+ y1 + t * (q2Adjusted[2] - y1),
903
+ z1 + t * (q2Adjusted[3] - z1)
904
+ ];
905
+ const length = Math.sqrt(
906
+ result[0] * result[0] + result[1] * result[1] + result[2] * result[2] + result[3] * result[3]
907
+ );
908
+ return [result[0] / length, result[1] / length, result[2] / length, result[3] / length];
909
+ }
910
+ const theta0 = Math.acos(Math.abs(dot));
911
+ const sinTheta0 = Math.sin(theta0);
912
+ const theta = theta0 * t;
913
+ const sinTheta = Math.sin(theta);
914
+ const s0 = Math.cos(theta) - dot * sinTheta / sinTheta0;
915
+ const s1 = sinTheta / sinTheta0;
916
+ return [
917
+ s0 * w1 + s1 * q2Adjusted[0],
918
+ s0 * x1 + s1 * q2Adjusted[1],
919
+ s0 * y1 + s1 * q2Adjusted[2],
920
+ s0 * z1 + s1 * q2Adjusted[3]
921
+ ];
922
+ }
923
+ function eulerSlerp(euler1, euler2, t) {
924
+ const q1 = eulerToQuaternion(euler1);
925
+ const q2 = eulerToQuaternion(euler2);
926
+ const qResult = quaternionSlerp(q1, q2, t);
927
+ return quaternionToEuler(qResult);
928
+ }
929
+ function eulerRotationBetween(euler1, euler2) {
930
+ const q1 = eulerToQuaternion(euler1);
931
+ const q2 = eulerToQuaternion(euler2);
932
+ const q1Conjugate = quaternionConjugate(q1);
933
+ const qDiff = quaternionMultiply(q2, q1Conjugate);
934
+ return quaternionToEuler(qDiff);
935
+ }
712
936
 
713
937
  // src/overlapping-segments.ts
714
938
  function overlappingSegments(original) {
@@ -903,31 +1127,228 @@ var EMAIL = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\]
903
1127
 
904
1128
  // src/map-deep-equal.ts
905
1129
  var import_fast_deep_equal = __toESM(require("fast-deep-equal"));
906
- function isMapDeepEqual(map1, map2) {
907
- if (map1.size !== map2.size) return false;
1130
+ function isMapDeepEqual(map1, map2, options = {}) {
1131
+ if (map1 === map2) return true;
1132
+ const { filter } = options || {};
1133
+ if (!filter) {
1134
+ if (map1.size !== map2.size) return false;
1135
+ for (const [key, value] of map1.entries()) {
1136
+ if (!map2.has(key)) return false;
1137
+ const otherValue = map2.get(key);
1138
+ if (value !== otherValue && !(0, import_fast_deep_equal.default)(value, otherValue)) {
1139
+ return false;
1140
+ }
1141
+ }
1142
+ return true;
1143
+ }
1144
+ const filteredEntries1 = [];
1145
+ const filteredEntries2 = [];
908
1146
  for (const [key, value] of map1.entries()) {
909
- if (!map2.has(key)) return false;
910
- const otherValue = map2.get(key);
911
- if (!(0, import_fast_deep_equal.default)(value, otherValue)) {
1147
+ if (filter(key, value)) {
1148
+ filteredEntries1.push([key, value]);
1149
+ }
1150
+ }
1151
+ for (const [key, value] of map2.entries()) {
1152
+ if (filter(key, value)) {
1153
+ filteredEntries2.push([key, value]);
1154
+ }
1155
+ }
1156
+ if (filteredEntries1.length !== filteredEntries2.length) {
1157
+ return false;
1158
+ }
1159
+ const filteredMap2 = new Map(filteredEntries2);
1160
+ for (const [key, value] of filteredEntries1) {
1161
+ if (!filteredMap2.has(key)) return false;
1162
+ const otherValue = filteredMap2.get(key);
1163
+ if (value !== otherValue && !(0, import_fast_deep_equal.default)(value, otherValue)) {
912
1164
  return false;
913
1165
  }
914
1166
  }
915
1167
  return true;
916
1168
  }
1169
+
1170
+ // src/migrate-euler.ts
1171
+ function storedEulerXYZtoRPY(robotData) {
1172
+ if ("rotation" in robotData.features) {
1173
+ if (!robotData.features.rotation.animated) {
1174
+ const rotationValue = robotData.features.rotation.value;
1175
+ if (!instanceOfRawEuler(rotationValue)) {
1176
+ const oldRot = rotationValue;
1177
+ const newRot = { r: oldRot.x, p: oldRot.y, y: oldRot.z };
1178
+ robotData.features.rotation.value = newRot;
1179
+ }
1180
+ } else {
1181
+ const rotationValue = robotData.features.rotation.value;
1182
+ if (!instanceOfRawEuler(rotationValue.default)) {
1183
+ const oldRot = rotationValue.default;
1184
+ const newRot = { r: oldRot.x, p: oldRot.y, y: oldRot.z };
1185
+ robotData.features.rotation.value.default = newRot;
1186
+ }
1187
+ }
1188
+ }
1189
+ if ("origin" in robotData) {
1190
+ const rotationValue = robotData.origin.rotation;
1191
+ if (!instanceOfRawEuler(rotationValue)) {
1192
+ const oldRot = rotationValue;
1193
+ const newRot = { r: oldRot.x, p: oldRot.y, y: oldRot.z };
1194
+ robotData.origin.rotation = newRot;
1195
+ }
1196
+ }
1197
+ return robotData;
1198
+ }
1199
+
1200
+ // src/react/context-providers.tsx
1201
+ var import_react5 = require("react");
1202
+ function createContextHook(context, name) {
1203
+ return function useRequiredContext() {
1204
+ const value = (0, import_react5.useContext)(context);
1205
+ if (!value) {
1206
+ throw new Error(
1207
+ `${name} context is required. Make sure you're using the component within a ${name}Provider.`
1208
+ );
1209
+ }
1210
+ return value;
1211
+ };
1212
+ }
1213
+ function withOptionalContext(CoreComponent, contextConfig) {
1214
+ const { context, defaultValue } = contextConfig;
1215
+ return function ComponentWithContext(props) {
1216
+ const { skipContext = false, ...coreProps } = props;
1217
+ const existingContext = (0, import_react5.useContext)(context);
1218
+ if (skipContext || existingContext) {
1219
+ return (0, import_react5.createElement)(CoreComponent, coreProps);
1220
+ }
1221
+ return (0, import_react5.createElement)(
1222
+ context.Provider,
1223
+ { value: defaultValue },
1224
+ (0, import_react5.createElement)(CoreComponent, coreProps)
1225
+ );
1226
+ };
1227
+ }
1228
+ function composeContexts(contexts) {
1229
+ return function ComposedContexts({ children }) {
1230
+ return contexts.reduceRight((acc, { Provider, condition }) => {
1231
+ if (condition && !condition()) {
1232
+ return acc;
1233
+ }
1234
+ return (0, import_react5.createElement)(Provider, {}, acc);
1235
+ }, children);
1236
+ };
1237
+ }
1238
+ function createContextProvider(context, defaultValue) {
1239
+ return function ContextProvider({
1240
+ children,
1241
+ value = defaultValue
1242
+ }) {
1243
+ return (0, import_react5.createElement)(context.Provider, { value }, children);
1244
+ };
1245
+ }
1246
+ function createContextSolution(config) {
1247
+ const { context, defaultValue, name } = config;
1248
+ const useRequiredContext = createContextHook(context, name);
1249
+ const Provider = createContextProvider(context, defaultValue);
1250
+ const withOptional = (Component) => withOptionalContext(Component, config);
1251
+ return {
1252
+ useContext: useRequiredContext,
1253
+ Provider,
1254
+ withOptional,
1255
+ context: config.context
1256
+ };
1257
+ }
1258
+ function useOptionalContext(context) {
1259
+ return (0, import_react5.useContext)(context);
1260
+ }
1261
+ function createContextChecker(context) {
1262
+ return function useHasContext() {
1263
+ return (0, import_react5.useContext)(context) !== null;
1264
+ };
1265
+ }
1266
+
1267
+ // src/values-store.ts
1268
+ var import_zustand2 = require("zustand");
1269
+ var import_middleware2 = require("zustand/middleware");
1270
+ function mergeValueMaps(base, updates) {
1271
+ if (updates.size === 0) return base;
1272
+ let hasChange = false;
1273
+ updates.forEach((value, key) => {
1274
+ if (hasChange) return;
1275
+ if (value === void 0) {
1276
+ if (base.has(key)) hasChange = true;
1277
+ } else if (base.get(key) !== value) {
1278
+ hasChange = true;
1279
+ }
1280
+ });
1281
+ if (!hasChange) return base;
1282
+ const merged = new Map(base);
1283
+ updates.forEach((value, key) => {
1284
+ if (value === void 0) {
1285
+ merged.delete(key);
1286
+ } else {
1287
+ merged.set(key, value);
1288
+ }
1289
+ });
1290
+ return merged;
1291
+ }
1292
+ var useValuesStore = (0, import_zustand2.create)()(
1293
+ (0, import_middleware2.subscribeWithSelector)((set) => ({
1294
+ values: /* @__PURE__ */ new Map(),
1295
+ velocities: /* @__PURE__ */ new Map(),
1296
+ updateValues: (updates) => {
1297
+ set((state) => ({
1298
+ values: mergeValueMaps(state.values, updates)
1299
+ }));
1300
+ },
1301
+ updateVelocities: (updates) => {
1302
+ set((state) => ({
1303
+ velocities: mergeValueMaps(state.velocities, updates)
1304
+ }));
1305
+ },
1306
+ setTrackValue: (trackKey, value) => {
1307
+ set((state) => {
1308
+ const next = new Map(state.values);
1309
+ next.set(trackKey, value);
1310
+ return { values: next };
1311
+ });
1312
+ },
1313
+ replaceAll: (values, velocities) => {
1314
+ set({ values, velocities });
1315
+ }
1316
+ }))
1317
+ );
1318
+
1319
+ // src/three-guards.ts
1320
+ function isOrthographicCamera(camera) {
1321
+ return "isOrthographicCamera" in camera;
1322
+ }
1323
+ function isPerspectiveCamera(camera) {
1324
+ return "isPerspectiveCamera" in camera;
1325
+ }
917
1326
  // Annotate the CommonJS export names for ESM import in node:
918
1327
  0 && (module.exports = {
1328
+ AnimatableGroupType,
919
1329
  EMAIL,
920
1330
  PlayerContext,
1331
+ PlayerDirection,
921
1332
  PlayerSlice,
922
1333
  Result,
923
1334
  alpha,
924
1335
  altColor,
925
1336
  angularDistance,
926
1337
  closestFrame,
1338
+ colorChroma,
1339
+ colorLightness,
1340
+ composeContexts,
927
1341
  composeProgress,
928
1342
  composeProgressiveResult,
1343
+ createContextChecker,
1344
+ createContextHook,
1345
+ createContextProvider,
1346
+ createContextSolution,
929
1347
  downloadBlob,
930
1348
  downloadJSONFile,
1349
+ eulerRotationBetween,
1350
+ eulerSlerp,
1351
+ eulerToQuaternion,
931
1352
  eulerToRotationMatrix,
932
1353
  getDegenerateHexagonPath,
933
1354
  getHexagonPath,
@@ -948,8 +1369,12 @@ function isMapDeepEqual(map1, map2) {
948
1369
  instanceOfRawVector2,
949
1370
  instanceOfRawVector3,
950
1371
  isMapDeepEqual,
1372
+ isNearNeutralMid,
1373
+ isOrthographicCamera,
1374
+ isPerspectiveCamera,
951
1375
  isRawObject,
952
1376
  log,
1377
+ mergeValueMaps,
953
1378
  newPlayer,
954
1379
  now,
955
1380
  numberToDurationString,
@@ -960,22 +1385,39 @@ function isMapDeepEqual(map1, map2) {
960
1385
  pause,
961
1386
  play,
962
1387
  pointsToPath,
1388
+ quaternionConjugate,
1389
+ quaternionMultiply,
1390
+ quaternionSlerp,
1391
+ quaternionToEuler,
1392
+ randomHexString,
963
1393
  rawHSLToHex,
964
1394
  rawRGBToHex,
965
1395
  reset,
1396
+ reversed,
1397
+ rgbChroma,
1398
+ rgbLightness,
966
1399
  rgbToRgba,
967
1400
  rotationMatrixToAngle,
968
1401
  seek,
969
- setBounds,
970
- setDuration,
971
- setViewport,
1402
+ shouldUseBlendForLabelColor,
1403
+ storedEulerXYZtoRPY,
972
1404
  stringifyMapped,
973
1405
  toDegrees,
974
1406
  toRadians,
975
- update,
1407
+ updated,
976
1408
  useDeep,
977
1409
  useDeepSelector,
978
1410
  useLazy,
979
1411
  useMediaQuery,
980
- usePlayerStore
1412
+ useOptionalContext,
1413
+ usePlayerStore,
1414
+ useValuesStore,
1415
+ warnUnexpectedFeatureValue,
1416
+ withBounce,
1417
+ withBounds,
1418
+ withDirection,
1419
+ withDuration,
1420
+ withLooping,
1421
+ withOptionalContext,
1422
+ withSpeed
981
1423
  });