@tastic/hud 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  // src/TriggerGauge.tsx
2
2
  import { Canvas, Path, Skia } from "@shopify/react-native-skia";
3
+ import { clamp } from "@tastic/core";
3
4
  import { useCallback, useLayoutEffect, useRef, useState } from "react";
4
5
  import { StyleSheet, View } from "react-native";
5
6
  import { Easing, useDerivedValue, useSharedValue, withTiming } from "react-native-reanimated";
@@ -60,7 +61,7 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
60
61
  if (mountProgress.value < 1) {
61
62
  const startFrac = i * MOUNT_STAGGER_MS / totalDurationMs;
62
63
  const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs;
63
- local = Math.min(1, Math.max(0, (mountProgress.value - startFrac) / (endFrac - startFrac)));
64
+ local = clamp((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
64
65
  }
65
66
  const sweep = dashAngleDeg * local;
66
67
  if (sweep <= 0) continue;
@@ -84,7 +85,7 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
84
85
  if (!isLitNow) continue;
85
86
  const startFrac = i * MOUNT_STAGGER_MS / totalDurationMs;
86
87
  const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs;
87
- local = Math.min(1, Math.max(0, (mountProgress.value - startFrac) / (endFrac - startFrac)));
88
+ local = clamp((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
88
89
  } else if (isLitNow === wasLit) {
89
90
  local = 1;
90
91
  } else if (isLitNow) {
package/dist/index.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __esm = (fn, res) => function __init() {
9
7
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -20,14 +18,6 @@ var __copyProps = (to, from, except, desc) => {
20
18
  }
21
19
  return to;
22
20
  };
23
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
- // If the importer is in node compatibility mode or this is not an ESM
25
- // file that has been converted to a CommonJS file using a Babel-
26
- // compatible transform (i.e. "__esModule" has not been set), then set
27
- // "default" to the CommonJS "module.exports" for node compatibility.
28
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
- mod
30
- ));
31
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
22
 
33
23
  // src/TriggerGauge.tsx
@@ -82,7 +72,7 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
82
72
  if (mountProgress.value < 1) {
83
73
  const startFrac = i * MOUNT_STAGGER_MS / totalDurationMs;
84
74
  const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs;
85
- local = Math.min(1, Math.max(0, (mountProgress.value - startFrac) / (endFrac - startFrac)));
75
+ local = (0, import_core2.clamp)((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
86
76
  }
87
77
  const sweep = dashAngleDeg * local;
88
78
  if (sweep <= 0) continue;
@@ -106,7 +96,7 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
106
96
  if (!isLitNow) continue;
107
97
  const startFrac = i * MOUNT_STAGGER_MS / totalDurationMs;
108
98
  const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs;
109
- local = Math.min(1, Math.max(0, (mountProgress.value - startFrac) / (endFrac - startFrac)));
99
+ local = (0, import_core2.clamp)((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
110
100
  } else if (isLitNow === wasLit) {
111
101
  local = 1;
112
102
  } else if (isLitNow) {
@@ -130,18 +120,19 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
130
120
  height: canvasSize,
131
121
  width: canvasSize
132
122
  };
133
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native8.View, { style: [styles4.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
123
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.View, { style: [styles4.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
134
124
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_skia.Path, { path: unlitPath, style: "stroke", strokeWidth: UNLIT_THICKNESS, strokeCap: "round", color: mutedColor, opacity: 0.35 }),
135
125
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_skia.Path, { path: litPath, style: "stroke", strokeWidth: LIT_THICKNESS, strokeCap: "round", color: accentColor, opacity: 1 })
136
126
  ] }) });
137
127
  }
138
- var import_react_native_skia, import_react2, import_react_native8, import_react_native_reanimated, import_jsx_runtime5, UNLIT_THICKNESS, LIT_THICKNESS, DASH_GAP_PX, ARC_GAP, DEFAULT_START_DEG, MIN_DASH_ANGLE_DEG, MOUNT_DURATION_MS, MOUNT_STAGGER_MS, SELECTION_TRANSITION_DURATION_MS, styles4;
128
+ var import_react_native_skia, import_core2, import_react2, import_react_native7, import_react_native_reanimated, import_jsx_runtime5, UNLIT_THICKNESS, LIT_THICKNESS, DASH_GAP_PX, ARC_GAP, DEFAULT_START_DEG, MIN_DASH_ANGLE_DEG, MOUNT_DURATION_MS, MOUNT_STAGGER_MS, SELECTION_TRANSITION_DURATION_MS, styles4;
139
129
  var init_TriggerGauge = __esm({
140
130
  "src/TriggerGauge.tsx"() {
141
131
  "use strict";
142
132
  import_react_native_skia = require("@shopify/react-native-skia");
133
+ import_core2 = require("@tastic/core");
143
134
  import_react2 = require("react");
144
- import_react_native8 = require("react-native");
135
+ import_react_native7 = require("react-native");
145
136
  import_react_native_reanimated = require("react-native-reanimated");
146
137
  import_jsx_runtime5 = require("react/jsx-runtime");
147
138
  UNLIT_THICKNESS = 3;
@@ -153,7 +144,7 @@ var init_TriggerGauge = __esm({
153
144
  MOUNT_DURATION_MS = 450;
154
145
  MOUNT_STAGGER_MS = 40;
155
146
  SELECTION_TRANSITION_DURATION_MS = 260;
156
- styles4 = import_react_native8.StyleSheet.create({
147
+ styles4 = import_react_native7.StyleSheet.create({
157
148
  container: {
158
149
  alignItems: "center",
159
150
  justifyContent: "center",
@@ -186,6 +177,7 @@ var MONO_FONT = import_react_native.Platform.select({ ios: "Menlo", android: "mo
186
177
  // src/InlineColorPicker.tsx
187
178
  var import_auto_paper = require("@rific/auto-paper");
188
179
  var import_feedback_press = require("@rific/feedback-press");
180
+ var import_core = require("@tastic/core");
189
181
  var import_react_native4 = require("react-native");
190
182
  var import_react_native_paper = require("react-native-paper");
191
183
 
@@ -309,7 +301,7 @@ function widthForColumns(columns) {
309
301
  function InlineColorPicker({ id, host, value, onChange, swatches = import_auto_paper.defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, icon = "palette", autoDismiss = true, columns }) {
310
302
  const menuBg = dark ? "#000000" : "#FFFFFF";
311
303
  const { width: windowWidth } = (0, import_react_native4.useWindowDimensions)();
312
- const autoColumns = Math.min(MAX_COLUMNS, Math.max(MIN_COLUMNS, Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP))));
304
+ const autoColumns = (0, import_core.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
313
305
  const resolvedColumns = columns ?? autoColumns;
314
306
  const swatchesWidth = widthForColumns(resolvedColumns);
315
307
  const swatchRows = Math.ceil(swatches.length / resolvedColumns);
@@ -406,23 +398,21 @@ var styles2 = import_react_native4.StyleSheet.create({
406
398
  });
407
399
 
408
400
  // src/loadSkiaWeb.ts
409
- var import_react_native5 = require("react-native");
410
401
  function loadSkiaWeb() {
411
- if (import_react_native5.Platform.OS !== "web") return Promise.resolve();
412
- return import("@shopify/react-native-skia/src/web").then((m) => m.LoadSkiaWeb());
402
+ return Promise.resolve();
413
403
  }
414
404
 
415
405
  // src/PressAwayOverlay.tsx
416
- var import_react_native6 = require("react-native");
406
+ var import_react_native5 = require("react-native");
417
407
  var import_jsx_runtime3 = require("react/jsx-runtime");
418
408
  function PressAwayOverlay({ active, onPress, style }) {
419
409
  if (!active) return null;
420
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native6.StyleSheet.absoluteFill, style] });
410
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native5.StyleSheet.absoluteFill, style] });
421
411
  }
422
412
 
423
413
  // src/ReadyButton.tsx
424
414
  var import_feedback_press2 = require("@rific/feedback-press");
425
- var import_react_native7 = require("react-native");
415
+ var import_react_native6 = require("react-native");
426
416
  var import_react_native_paper2 = require("react-native-paper");
427
417
  var import_jsx_runtime4 = require("react/jsx-runtime");
428
418
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
@@ -431,7 +421,7 @@ function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MON
431
421
  const labelColorStyle = { color: ready ? "#000000" : color };
432
422
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press2.TouchableRipple, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
433
423
  }
434
- var styles3 = import_react_native7.StyleSheet.create({
424
+ var styles3 = import_react_native6.StyleSheet.create({
435
425
  readyButton: {
436
426
  alignItems: "center",
437
427
  borderRadius: 12,
@@ -446,7 +436,7 @@ var styles3 = import_react_native7.StyleSheet.create({
446
436
  // src/SectionedDropdown.tsx
447
437
  var import_auto_paper2 = require("@rific/auto-paper");
448
438
  var import_feedback_press3 = require("@rific/feedback-press");
449
- var import_react_native9 = require("react-native");
439
+ var import_react_native8 = require("react-native");
450
440
  var import_react_native_paper3 = require("react-native-paper");
451
441
 
452
442
  // src/TriggerGaugeHost.tsx
@@ -507,13 +497,13 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
507
497
  const anchorStyle = [styles5.anchor, open && styles5.anchorOpen];
508
498
  const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
509
499
  const dividerStyle = { backgroundColor: menuBorderColor };
510
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native9.View, { style: anchorStyle, children: [
511
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native9.View, { ref: triggerRef, collapsable: false, style: styles5.triggerBox, children: [
500
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: anchorStyle, children: [
501
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { ref: triggerRef, collapsable: false, style: styles5.triggerBox, children: [
512
502
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
513
503
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
514
504
  ] }),
515
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native9.ScrollView, { style: [styles5.menu, menuStyle], contentContainerStyle: styles5.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native9.View, { children: [
516
- sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native9.View, { style: [styles5.divider, dividerStyle] }),
505
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native8.ScrollView, { style: [styles5.menu, menuStyle], contentContainerStyle: styles5.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { children: [
506
+ sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native8.View, { style: [styles5.divider, dividerStyle] }),
517
507
  section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
518
508
  SingleSection,
519
509
  {
@@ -545,9 +535,9 @@ function SingleSection({
545
535
  const itemStyle = [styles5.item, selected && { backgroundColor: accentColor }];
546
536
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
547
537
  const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
548
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native9.View, { style: styles5.itemRow, children: [
538
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles5.itemRow, children: [
549
539
  option.icon && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
550
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native9.View, { style: styles5.itemLabelColumn, children: [
540
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles5.itemLabelColumn, children: [
551
541
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
552
542
  option.description && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
553
543
  ] })
@@ -571,7 +561,7 @@ function MultiSection({
571
561
  const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
572
562
  const itemStyle = [styles5.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
573
563
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
574
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native9.View, { style: styles5.itemRow, children: [
564
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles5.itemRow, children: [
575
565
  option.icon && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
576
566
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Text, { variant: "labelLarge", style: [styles5.itemLabelFlex, labelStyle], children: option.label })
577
567
  ] }) }, option.value);
@@ -579,7 +569,7 @@ function MultiSection({
579
569
  section.allClear && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles5.item, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Text, { variant: "labelLarge", style: [styles5.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
580
570
  ] });
581
571
  }
582
- var styles5 = import_react_native9.StyleSheet.create({
572
+ var styles5 = import_react_native8.StyleSheet.create({
583
573
  allClearLabel: {
584
574
  fontWeight: "bold",
585
575
  textAlign: "center"
@@ -596,7 +586,7 @@ var styles5 = import_react_native9.StyleSheet.create({
596
586
  zIndex: 100
597
587
  },
598
588
  divider: {
599
- height: import_react_native9.StyleSheet.hairlineWidth,
589
+ height: import_react_native8.StyleSheet.hairlineWidth,
600
590
  marginVertical: 4
601
591
  },
602
592
  item: {
package/dist/index.mjs CHANGED
@@ -5,6 +5,7 @@ var MONO_FONT = Platform.select({ ios: "Menlo", android: "monospace", default: "
5
5
  // src/InlineColorPicker.tsx
6
6
  import { defaultColors, getContrastColor } from "@rific/auto-paper";
7
7
  import { TouchableRipple } from "@rific/feedback-press";
8
+ import { clamp } from "@tastic/core";
8
9
  import { ScrollView, StyleSheet as StyleSheet2, useWindowDimensions as useWindowDimensions2, View as View3 } from "react-native";
9
10
  import { Icon } from "react-native-paper";
10
11
 
@@ -128,7 +129,7 @@ function widthForColumns(columns) {
128
129
  function InlineColorPicker({ id, host, value, onChange, swatches = defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, icon = "palette", autoDismiss = true, columns }) {
129
130
  const menuBg = dark ? "#000000" : "#FFFFFF";
130
131
  const { width: windowWidth } = useWindowDimensions2();
131
- const autoColumns = Math.min(MAX_COLUMNS, Math.max(MIN_COLUMNS, Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP))));
132
+ const autoColumns = clamp(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
132
133
  const resolvedColumns = columns ?? autoColumns;
133
134
  const swatchesWidth = widthForColumns(resolvedColumns);
134
135
  const swatchRows = Math.ceil(swatches.length / resolvedColumns);
@@ -225,10 +226,8 @@ var styles2 = StyleSheet2.create({
225
226
  });
226
227
 
227
228
  // src/loadSkiaWeb.ts
228
- import { Platform as Platform2 } from "react-native";
229
229
  function loadSkiaWeb() {
230
- if (Platform2.OS !== "web") return Promise.resolve();
231
- return import("@shopify/react-native-skia/src/web").then((m) => m.LoadSkiaWeb());
230
+ return Promise.resolve();
232
231
  }
233
232
 
234
233
  // src/PressAwayOverlay.tsx
@@ -271,7 +270,7 @@ import { Icon as Icon2, Text as Text2 } from "react-native-paper";
271
270
  // src/TriggerGaugeHost.tsx
272
271
  import { lazy, Suspense } from "react";
273
272
  import { jsx as jsx5 } from "react/jsx-runtime";
274
- var LazyTriggerGauge = lazy(() => loadSkiaWeb().then(() => import("./TriggerGauge-XBKZ3KER.mjs").then((m) => ({ default: m.TriggerGauge }))));
273
+ var LazyTriggerGauge = lazy(() => loadSkiaWeb().then(() => import("./TriggerGauge-2OJOOF6I.mjs").then((m) => ({ default: m.TriggerGauge }))));
275
274
  function TriggerGaugeHost(props) {
276
275
  return /* @__PURE__ */ jsx5(Suspense, { fallback: null, children: /* @__PURE__ */ jsx5(LazyTriggerGauge, { ...props }) });
277
276
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tastic/hud",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Visual component kit for local-multiplayer React Native games: inline (non-modal) popovers, dropdowns, color pickers, gauges, ready buttons, and dialogs, built to stay scoped to one player's own zone on a shared screen",
5
5
  "keywords": [
6
6
  "react-native",
@@ -29,6 +29,7 @@
29
29
  "exports": {
30
30
  ".": {
31
31
  "react-native": "./src/index.ts",
32
+ "browser": "./src/index.ts",
32
33
  "types": "./dist/index.d.ts",
33
34
  "import": "./dist/index.mjs",
34
35
  "require": "./dist/index.js"
@@ -63,6 +64,7 @@
63
64
  "@rific/auto-paper": "^0.9.4",
64
65
  "@rific/feedback-press": "^0.10.4",
65
66
  "@shopify/react-native-skia": "^2.6.2",
67
+ "@tastic/core": "^0.1.2",
66
68
  "@testing-library/dom": "^10.4.1",
67
69
  "@testing-library/react": "^16.3.2",
68
70
  "@types/jest": "^30.0.0",
@@ -92,6 +94,7 @@
92
94
  "@rific/auto-paper": ">=0.9.0",
93
95
  "@rific/feedback-press": ">=0.10.0",
94
96
  "@shopify/react-native-skia": ">=1.5.0",
97
+ "@tastic/core": ">=0.1.0",
95
98
  "react": ">=19.0.0",
96
99
  "react-native": ">=0.76.0",
97
100
  "react-native-paper": ">=5.0.0",
@@ -1,5 +1,6 @@
1
1
  import { defaultColors, getContrastColor, SeedColor } from '@rific/auto-paper'
2
2
  import { TouchableRipple } from '@rific/feedback-press'
3
+ import { clamp } from '@tastic/core'
3
4
  import { ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native'
4
5
  import { Icon } from 'react-native-paper'
5
6
 
@@ -63,7 +64,7 @@ interface Props {
63
64
  export function InlineColorPicker({ id, host, value, onChange, swatches = defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, icon = 'palette', autoDismiss = true, columns }: Props) {
64
65
  const menuBg = dark ? '#000000' : '#FFFFFF'
65
66
  const { width: windowWidth } = useWindowDimensions()
66
- const autoColumns = Math.min(MAX_COLUMNS, Math.max(MIN_COLUMNS, Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP))))
67
+ const autoColumns = clamp(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS)
67
68
  const resolvedColumns = columns ?? autoColumns
68
69
  const swatchesWidth = widthForColumns(resolvedColumns)
69
70
  const swatchRows = Math.ceil(swatches.length / resolvedColumns)
@@ -1,4 +1,5 @@
1
1
  import { Canvas, Path, Skia } from '@shopify/react-native-skia'
2
+ import { clamp } from '@tastic/core'
2
3
  import { useCallback, useLayoutEffect, useRef, useState } from 'react'
3
4
  import { StyleSheet, View } from 'react-native'
4
5
  import { Easing, useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated'
@@ -207,7 +208,7 @@ export function TriggerGauge({ segments, litIndices, size, accentColor, mutedCol
207
208
  if (mountProgress.value < 1) {
208
209
  const startFrac = (i * MOUNT_STAGGER_MS) / totalDurationMs
209
210
  const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs
210
- local = Math.min(1, Math.max(0, (mountProgress.value - startFrac) / (endFrac - startFrac)))
211
+ local = clamp((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1)
211
212
  }
212
213
  const sweep = dashAngleDeg * local
213
214
  if (sweep <= 0) continue
@@ -245,7 +246,7 @@ export function TriggerGauge({ segments, litIndices, size, accentColor, mutedCol
245
246
  if (!isLitNow) continue
246
247
  const startFrac = (i * MOUNT_STAGGER_MS) / totalDurationMs
247
248
  const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs
248
- local = Math.min(1, Math.max(0, (mountProgress.value - startFrac) / (endFrac - startFrac)))
249
+ local = clamp((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1)
249
250
  } else if (isLitNow === wasLit) {
250
251
  local = 1 // steady — lit before and after (or the mount-only branch above already handled unlit)
251
252
  } else if (isLitNow) {
@@ -11,10 +11,9 @@ import { TriggerGaugeProps } from './TriggerGauge'
11
11
  // file therefore never imports TriggerGauge itself except as a type (erased at compile time,
12
12
  // carrying no runtime module reference) — `lazy()` is what actually defers the real import to
13
13
  // runtime, after loadSkiaWeb() resolves. Always lazy, on every platform, rather than a native/web
14
- // branch here: this package builds to a single bundled dist file (see loadSkiaWeb's own comment on
15
- // why), so there's no reliable build-time way to give native a plain, non-lazy passthrough instead —
16
- // loadSkiaWeb() itself already resolves immediately on native, so the Suspense boundary there just
17
- // clears on the next tick, not a perceptible delay. The Suspense fallback renders nothing, so a
14
+ // branch here, purely to keep this file simple loadSkiaWeb() itself already resolves immediately
15
+ // on native (see its own file split), so the Suspense boundary there just clears on the next tick,
16
+ // not a perceptible delay. The Suspense fallback renders nothing, so a
18
17
  // consumer's trigger icon still shows immediately and the gauge ring just pops in a beat later.
19
18
  const LazyTriggerGauge = lazy(() => loadSkiaWeb().then(() => import('./TriggerGauge').then((m) => ({ default: m.TriggerGauge }))))
20
19
 
@@ -1,14 +1,10 @@
1
- import { Platform } from 'react-native'
2
-
3
- // Skia's web target renders through CanvasKit, a WASM build of Skia fetched at runtime — nothing
4
- // using @shopify/react-native-skia's React components can draw a single frame until this resolves.
5
- // Skia on native (via JSI) is ready as soon as the app's native module is linked, so this only
6
- // actually does anything on web a Platform.OS check rather than a `.web.ts` sibling file, since
7
- // this package builds to a single bundled dist file (see package.json's `build` script): a bundler
8
- // that pre-flattens the whole module graph itself (tsup/esbuild) never sees or swaps in a `.web.ts`
9
- // variant the way a consuming app's own Metro bundler would, so the split has to be a runtime
10
- // branch that survives being bundled, not a build-time file choice that wouldn't.
1
+ // Native counterpart to loadSkiaWeb.web.ts — Skia on native (via JSI, no WASM) is ready as soon as
2
+ // the app's native module is linked, so there's nothing to actually wait on here. This file must
3
+ // never import '@shopify/react-native-skia/src/web' (even conditionally behind a Platform.OS check):
4
+ // that module pulls in canvaskit-wasm, which does `require('fs')` a runtime check doesn't stop
5
+ // Metro from statically resolving that import to build the native bundle graph, and Metro has no
6
+ // `fs` for iOS/Android, so the build fails outright. Metro's own platform-extension resolution (this
7
+ // file vs. loadSkiaWeb.web.ts) is what actually keeps the web-only module out of the native graph.
11
8
  export function loadSkiaWeb(): Promise<void> {
12
- if (Platform.OS !== 'web') return Promise.resolve()
13
- return import('@shopify/react-native-skia/src/web').then((m) => m.LoadSkiaWeb())
9
+ return Promise.resolve()
14
10
  }
@@ -0,0 +1,11 @@
1
+ import { LoadSkiaWeb } from '@shopify/react-native-skia/src/web'
2
+
3
+ // Skia's web target renders through CanvasKit, a WASM build of Skia fetched at runtime — nothing
4
+ // using @shopify/react-native-skia's React components can draw a single frame until this resolves.
5
+ // Kept as a thin wrapper (rather than importing LoadSkiaWeb directly at call sites) purely so this
6
+ // file's `.ts` counterpart, loadSkiaWeb.ts, gives native an identically-shaped async no-op — Metro's
7
+ // platform-extension resolution picks whichever one matches the bundle target, so canvaskit-wasm's
8
+ // `require('fs')' never even gets resolved into a native/iOS/Android bundle.
9
+ export function loadSkiaWeb(): Promise<void> {
10
+ return LoadSkiaWeb()
11
+ }