@sprucelabs/spruce-heartwood-utils 38.17.7 → 38.17.9

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 CHANGED
@@ -10,16 +10,20 @@ Heartwood integration toolkit for Spruce skills. Use this package to load remote
10
10
  npm install @sprucelabs/spruce-heartwood-utils
11
11
  ```
12
12
 
13
- This package is designed for use inside a Spruce skill. It expects `@sprucelabs/heartwood-view-controllers`, `@sprucelabs/mercury-client`, and React to already be present in your project — they are pulled in transitively when you scaffold a Heartwood skill.
13
+ ```
14
+ yarn add @sprucelabs/spruce-heartwood-utils
15
+ ```
16
+
17
+ This package is designed for use inside a Spruce skill or a React application.
14
18
 
15
19
  ---
16
20
 
17
21
  ## Project Pitch
18
22
 
19
- Building a Spruce experience usually means combining:
20
- - browser/runtime view infrastructure
21
- - test utilities for remote card/event flows
22
- - animation and layout primitives used in Heartwood views
23
+ This module supports development in 2 ways.
24
+
25
+ 1. It provides first-party utilities for common tasks in Skill development — remote view controllers, card registration, theming, plugins, and test helpers.
26
+ 2. It offers animation and layout primitives for use in React.
23
27
 
24
28
  `@sprucelabs/spruce-heartwood-utils` packages those into one module with explicit entrypoints so you can keep browser bundles clean while still getting first-class testing helpers.
25
29
 
@@ -59,7 +63,6 @@ import {
59
63
  AutoLogoutPlugin,
60
64
  loadActiveThemeForOrg,
61
65
  Sizer,
62
- SimpleEmitter,
63
66
  } from '@sprucelabs/spruce-heartwood-utils/web'
64
67
  ```
65
68
 
@@ -84,8 +87,6 @@ Use split imports so runtime code stays browser-safe while tests get all mocks/h
84
87
  import {
85
88
  CardRegistrar,
86
89
  RemoteViewControllerFactoryImpl,
87
- } from '@sprucelabs/spruce-heartwood-utils/web'
88
- import {
89
90
  remoteVcAssert,
90
91
  MockRemoteViewControllerFactory,
91
92
  } from '@sprucelabs/spruce-heartwood-utils/testing'
@@ -93,33 +94,6 @@ import {
93
94
 
94
95
  ---
95
96
 
96
- ## Table of Contents
97
-
98
- 1. [Project Pitch](#project-pitch)
99
- 2. [What You Get](#what-you-get)
100
- 3. [Import Paths](#import-paths)
101
- 4. [Get Started](#get-started)
102
- 5. [Remote View Controllers](#remote-view-controllers)
103
- 6. [CardRegistrar](#cardregistrar)
104
- 7. [Types](#types)
105
- 8. [Theming](#theming)
106
- 9. [Plugins](#plugins)
107
- 10. [Test Utilities](#test-utilities)
108
- 11. [Device & Layout Utilities](#device--layout-utilities)
109
- 12. [Animation](#animation)
110
- - [Quick Start](#quick-start)
111
- - [Required CSS](#required-css)
112
- - [System Architecture](#system-architecture)
113
- - [AnimationEmitter](#animationemitter)
114
- - [Settings](#settings)
115
- - [queueShow — Show/Hide Queue](#queueshow--showhide-queue)
116
- - [Sizer — Animated Height Container](#sizer--animated-height-container)
117
- - [DelayedPlacer — Absolute Positioning](#delayedplacer--absolute-positioning)
118
- - [sizeUtil — DOM Measurement](#sizeutil--dom-measurement)
119
- - [How the Three Systems Coordinate](#how-the-three-systems-coordinate)
120
-
121
- ---
122
-
123
97
  ## Remote View Controllers
124
98
 
125
99
  When your skill needs to load and render cards that come from other skills at runtime, these are the types you work with. In tests, you swap in `MockRemoteViewControllerFactory` to control which cards appear without making real network calls.
@@ -675,7 +649,6 @@ import {
675
649
  // Utilities
676
650
  sizeUtil, Settings,
677
651
  // Emitter — pass to Sizer and DelayedPlacer to coordinate layout events
678
- SimpleEmitter,
679
652
  // Types
680
653
  AnimationEmitter,
681
654
  SizerProps,
@@ -773,15 +746,15 @@ Pass the **same emitter instance** to `Sizer` and `DelayedPlacer` when they are
773
746
 
774
747
  ### AnimationEmitter
775
748
 
776
- The interface `Sizer` and `DelayedPlacer` use to communicate layout-change events. Pass an instance as the `emitter` prop to coordinate components. The package exports `SimpleEmitter` a lightweight in-memory implementation you can use directly:
749
+ The interface `Sizer` and `DelayedPlacer` use to communicate layout-change events. Pass an instance as the `emitter` prop to coordinate components. The package exports `SkillViewEmitter` for this:
777
750
 
778
751
  ```ts
779
- import { SimpleEmitter } from '@sprucelabs/spruce-heartwood-utils/web'
752
+ import { SkillViewEmitter } from '@sprucelabs/spruce-heartwood-utils/web'
780
753
 
781
- const emitter = new SimpleEmitter()
754
+ const emitter = SkillViewEmitter.getInstance()
782
755
  ```
783
756
 
784
- `SimpleEmitter` implements all three methods (`on`, `off`, `emit`) with a plain listener map. Use it for standalone components, isolated tests, or any context where a full Heartwood emitter is not available.
757
+ `SkillViewEmitter` implements all three methods (`on`, `off`, `emit`) needed by `AnimationEmitter`.
785
758
 
786
759
  The `AnimationEmitter` interface, for reference:
787
760
 
@@ -804,9 +777,9 @@ interface AnimationEmitter {
804
777
  2. **Create an emitter** — one per component tree, stable across renders:
805
778
 
806
779
  ```ts
807
- import { SimpleEmitter } from '@sprucelabs/spruce-heartwood-utils/web'
780
+ import { SkillViewEmitter } from '@sprucelabs/spruce-heartwood-utils/web'
808
781
 
809
- const emitter = useMemo(() => new SimpleEmitter(), [])
782
+ const emitter = useMemo(() => SkillViewEmitter.getInstance(), [])
810
783
  ```
811
784
 
812
785
  3. **Wire to components and emit events** — pass the emitter to `Sizer` and/or `DelayedPlacer`, then signal layout changes:
@@ -1071,14 +1044,14 @@ Wraps children in a div whose `height` is set via inline style to match the meas
1071
1044
 
1072
1045
  > Inside a Heartwood skill view this CSS is already provided — skip this step.
1073
1046
 
1074
- 2. **Wrap your content** — `shouldHideOverflow` clips children during the height transition:
1047
+ 2. **Wrap your content**
1075
1048
 
1076
1049
  ```tsx
1077
- import { Sizer, SimpleEmitter } from '@sprucelabs/spruce-heartwood-utils/web'
1050
+ import { Sizer } from '@sprucelabs/spruce-heartwood-utils/web'
1078
1051
 
1079
- const emitter = useMemo(() => new SimpleEmitter(), [])
1052
+ const emitter = useMemo(() => SkillViewEmitter.getInstance(), [])
1080
1053
 
1081
- <Sizer emitter={emitter} shouldHideOverflow>
1054
+ <Sizer emitter={emitter}>
1082
1055
  <YourContent />
1083
1056
  </Sizer>
1084
1057
  ```
@@ -1104,20 +1077,6 @@ interface SizerProps {
1104
1077
  // CSS class on the outer .sizer div.
1105
1078
  className?: string
1106
1079
 
1107
- // When true, starts at height 0 and defers first measurement by 100ms.
1108
- // Use for content that should animate in from nothing.
1109
- shouldStartAtZero?: boolean
1110
-
1111
- // When true, keeps overflow hidden throughout. When explicitly false,
1112
- // overflow is always visible (adds force-show-overflow class).
1113
- shouldHideOverflow?: boolean
1114
-
1115
- // Debounce delay in ms before each resize measurement fires. Default: 250ms.
1116
- // Lower values make Sizer react faster to content changes; useful when
1117
- // content updates quickly and you want tighter animation timing.
1118
- sizerDelayMs?: number
1119
-
1120
- // Event emitter. Without one, Sizer only resizes on React re-renders,
1121
1080
  // not in response to external layout events.
1122
1081
  emitter?: AnimationEmitter
1123
1082
  }
@@ -1142,23 +1101,23 @@ sizer.current?.hideOverflow(): void // re-apply overflow: hidden
1142
1101
  #### Usage
1143
1102
 
1144
1103
  ```tsx
1145
- import { Sizer, SimpleEmitter } from '@sprucelabs/spruce-heartwood-utils/web'
1104
+ import { Sizer } from '@sprucelabs/spruce-heartwood-utils/web'
1146
1105
 
1147
- // Simplest — just clip overflow during height transition:
1148
- <Sizer shouldHideOverflow>
1106
+ // Simplest
1107
+ <Sizer>
1149
1108
  {isExpanded && <YourExpandableContent />}
1150
1109
  </Sizer>
1151
1110
 
1152
1111
  // Animate in from zero height:
1153
- <Sizer emitter={emitter} shouldStartAtZero>
1112
+ <Sizer emitter={emitter}>
1154
1113
  <YourCard />
1155
1114
  </Sizer>
1156
1115
 
1157
1116
  // Manually trigger resize when content changes outside React state:
1158
- const emitter = useMemo(() => new SimpleEmitter(), [])
1117
+ const emitter = useMemo(() => SkillViewEmitter.getInstance(), [])
1159
1118
  const sizer = useRef<React.ElementRef<typeof Sizer>>(null)
1160
1119
 
1161
- <Sizer shouldHideOverflow ref={sizer} emitter={emitter}>
1120
+ <Sizer ref={sizer} emitter={emitter}>
1162
1121
  <YourDynamicContent
1163
1122
  onContentChange={async () => {
1164
1123
  if (sizer.current?.resize()) {
@@ -1169,7 +1128,7 @@ const sizer = useRef<React.ElementRef<typeof Sizer>>(null)
1169
1128
  </Sizer>
1170
1129
 
1171
1130
  // Staggered entrance with queueShow:
1172
- <Sizer emitter={emitter} shouldHideOverflow shouldStartAtZero>
1131
+ <Sizer emitter={emitter}>
1173
1132
  <div className="your-item hidden" ref={(ref) => { ref && queueShow(ref) }}>
1174
1133
  content
1175
1134
  </div>
@@ -1204,9 +1163,9 @@ When `isEnabled={false}`, children render inline with no wrapper.
1204
1163
  2. **Wrap your content** — `isEnabled`, `className`, and `isFocused` are all required:
1205
1164
 
1206
1165
  ```tsx
1207
- import { DelayedPlacer, SimpleEmitter } from '@sprucelabs/spruce-heartwood-utils/web'
1166
+ import { DelayedPlacer } from '@sprucelabs/spruce-heartwood-utils/web'
1208
1167
 
1209
- const emitter = useMemo(() => new SimpleEmitter(), [])
1168
+ const emitter = useMemo(() => SkillViewEmitter.getInstance(), [])
1210
1169
 
1211
1170
  <DelayedPlacer
1212
1171
  className="placer__card"
@@ -1224,7 +1183,7 @@ When `isEnabled={false}`, children render inline with no wrapper.
1224
1183
 
1225
1184
  ```tsx
1226
1185
  <DelayedPlacer className="placer__card" isEnabled emitter={emitter} isFocused={isFocused}>
1227
- <Sizer emitter={emitter} shouldHideOverflow>
1186
+ <Sizer emitter={emitter}>
1228
1187
  <YourContent />
1229
1188
  </Sizer>
1230
1189
  </DelayedPlacer>
@@ -1246,11 +1205,6 @@ interface DelayedPlacerProps {
1246
1205
 
1247
1206
  // Event emitter. Listens for layout changes to re-measure and re-place.
1248
1207
  emitter?: AnimationEmitter
1249
-
1250
- // Required. Return true when this skill view is focused and in the foreground.
1251
- // Placement is skipped when false to avoid measuring off-screen content.
1252
- // Pass () => true for standalone use outside Heartwood.
1253
- isFocused: () => boolean
1254
1208
  }
1255
1209
  ```
1256
1210
 
@@ -1260,6 +1214,18 @@ interface DelayedPlacerProps {
1260
1214
  delayedPlacer.current?.placeRightAway(): void // re-measure and re-place immediately
1261
1215
  ```
1262
1216
 
1217
+ #### Placer & Sizer Together For Card Placement
1218
+
1219
+ When using `DelayedPlacer` and `Sizer` together, the typical pattern is to place `Sizer` inside `DelayedPlacer` so that height changes trigger placement updates:
1220
+
1221
+ ```tsx
1222
+ <DelayedPlacer className="placer" isEnabled emitter={emitter} isFocused={isFocused}>
1223
+ <Sizer emitter={emitter}>
1224
+ <YourCard />
1225
+ </Sizer>
1226
+ </DelayedPlacer>
1227
+ ```
1228
+
1263
1229
  #### Emitter Events
1264
1230
 
1265
1231
  | Event | Direction | Meaning |
@@ -1274,11 +1240,11 @@ delayedPlacer.current?.placeRightAway(): void // re-measure and re-place immedi
1274
1240
 
1275
1241
  ```tsx
1276
1242
  import React, { useRef, useMemo } from 'react'
1277
- import { DelayedPlacer, SimpleEmitter } from '@sprucelabs/spruce-heartwood-utils/web'
1243
+ import { DelayedPlacer } from '@sprucelabs/spruce-heartwood-utils/web'
1278
1244
 
1279
1245
  function YourPanel({ isFocused }: { isFocused: () => boolean }) {
1280
1246
  const placerRef = useRef<React.ElementRef<typeof DelayedPlacer>>(null)
1281
- const emitter = useMemo(() => new SimpleEmitter(), [])
1247
+ const emitter = useMemo(() => SkillViewEmitter.getInstance(), [])
1282
1248
 
1283
1249
  return (
1284
1250
  <DelayedPlacer
@@ -1365,11 +1331,11 @@ A complete example showing all three systems working together in a card componen
1365
1331
  ```tsx
1366
1332
  import React, { useMemo } from 'react'
1367
1333
  import {
1368
- Sizer, DelayedPlacer, queueShow, Settings, SimpleEmitter,
1334
+ Sizer, DelayedPlacer, queueShow, Settings,
1369
1335
  } from '@sprucelabs/spruce-heartwood-utils/web'
1370
1336
 
1371
1337
  function YourCard({ isPlaced, isFocused }: { isPlaced: boolean; isFocused: () => boolean }) {
1372
- const emitter = useMemo(() => new SimpleEmitter(), [])
1338
+ const emitter = useMemo(() => SkillViewEmitter.getInstance(), [])
1373
1339
 
1374
1340
  return (
1375
1341
  <DelayedPlacer
@@ -1378,7 +1344,7 @@ function YourCard({ isPlaced, isFocused }: { isPlaced: boolean; isFocused: () =>
1378
1344
  emitter={emitter}
1379
1345
  isFocused={isFocused}
1380
1346
  >
1381
- <Sizer emitter={emitter} shouldHideOverflow shouldStartAtZero>
1347
+ <Sizer emitter={emitter}>
1382
1348
  <div
1383
1349
  className="card hidden"
1384
1350
  ref={(ref) => { ref && queueShow(ref) }}
@@ -6,4 +6,3 @@ export type { DelayedPlacerProps } from './DelayedPlacerExport';
6
6
  export { default as sizeUtil } from '../calendars/utils/size.utility';
7
7
  export { default as Settings } from '../Settings';
8
8
  export type { AnimationEmitter } from './types';
9
- export { default as SimpleEmitter } from './SimpleEmitter';
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SimpleEmitter = exports.Settings = exports.sizeUtil = exports.DelayedPlacer = exports.Sizer = exports.stopQueue = exports.clearPendingShowAndQueueHide = exports.clearPendingHideAndQueueShow = exports.callbackImmediately = exports.queueCallback = exports.queueHide = exports.queueShow = exports.hideRightAway = exports.showRightAway = exports.useShowNow = exports.useQueueShow = void 0;
6
+ exports.Settings = exports.sizeUtil = exports.DelayedPlacer = exports.Sizer = exports.stopQueue = exports.clearPendingShowAndQueueHide = exports.clearPendingHideAndQueueShow = exports.callbackImmediately = exports.queueCallback = exports.queueHide = exports.queueShow = exports.hideRightAway = exports.showRightAway = exports.useShowNow = exports.useQueueShow = void 0;
7
7
  // Queue system — all functions and hooks
8
8
  var queueShow_1 = require("../../hooks/queueShow");
9
9
  Object.defineProperty(exports, "useQueueShow", { enumerable: true, get: function () { return queueShow_1.useQueueShow; } });
@@ -27,6 +27,4 @@ var size_utility_1 = require("../calendars/utils/size.utility");
27
27
  Object.defineProperty(exports, "sizeUtil", { enumerable: true, get: function () { return __importDefault(size_utility_1).default; } });
28
28
  var Settings_1 = require("../Settings");
29
29
  Object.defineProperty(exports, "Settings", { enumerable: true, get: function () { return __importDefault(Settings_1).default; } });
30
- var SimpleEmitter_1 = require("./SimpleEmitter");
31
- Object.defineProperty(exports, "SimpleEmitter", { enumerable: true, get: function () { return __importDefault(SimpleEmitter_1).default; } });
32
30
  //# sourceMappingURL=index.js.map
@@ -6,4 +6,3 @@ export type { DelayedPlacerProps } from './DelayedPlacerExport';
6
6
  export { default as sizeUtil } from '../calendars/utils/size.utility';
7
7
  export { default as Settings } from '../Settings';
8
8
  export type { AnimationEmitter } from './types';
9
- export { default as SimpleEmitter } from './SimpleEmitter';
@@ -6,4 +6,3 @@ export { default as DelayedPlacer } from './DelayedPlacerExport.js';
6
6
  // Utilities (useful for consumers who want to measure DOM nodes)
7
7
  export { default as sizeUtil } from '../calendars/utils/size.utility.js';
8
8
  export { default as Settings } from '../Settings.js';
9
- export { default as SimpleEmitter } from './SimpleEmitter.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sprucelabs/spruce-heartwood-utils",
3
3
  "description": "Heartwood Utilities",
4
- "version": "38.17.7",
4
+ "version": "38.17.9",
5
5
  "skill": {
6
6
  "namespace": "heartwood"
7
7
  },