@sprucelabs/spruce-heartwood-utils 38.17.2 → 38.17.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.
- package/README.md +90 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,16 @@ Tools for building Spruce skills that integrate with Heartwood — remote card l
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @sprucelabs/spruce-heartwood-utils
|
|
11
|
+
```
|
|
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.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
7
17
|
## Table of Contents
|
|
8
18
|
|
|
9
19
|
1. [Remote View Controllers](#remote-view-controllers)
|
|
@@ -269,8 +279,15 @@ plugin.disableAutoLogout() // cancel a pending auto-logout
|
|
|
269
279
|
Use this type when you want to reference the plugin without coupling to the concrete class — for example, when declaring a field that holds either the real plugin or a spy:
|
|
270
280
|
|
|
271
281
|
```ts
|
|
272
|
-
import { AutoLogoutViewPlugin } from '@sprucelabs/spruce-heartwood-utils'
|
|
282
|
+
import type { AutoLogoutViewPlugin } from '@sprucelabs/spruce-heartwood-utils'
|
|
273
283
|
|
|
284
|
+
// Field declaration — works with both AutoLogoutPlugin and SpyAutoLogoutPlugin:
|
|
285
|
+
private autoLogoutPlugin: AutoLogoutViewPlugin
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The interface shape:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
274
291
|
interface AutoLogoutViewPlugin extends ViewControllerPlugin {
|
|
275
292
|
enableAutoLogout(durationSec: number): void
|
|
276
293
|
disableAutoLogout(): void
|
|
@@ -397,9 +414,17 @@ import { fakeGetViews } from '@sprucelabs/spruce-heartwood-utils'
|
|
|
397
414
|
|
|
398
415
|
// Stub two remote views:
|
|
399
416
|
await fakeGetViews.fakeGetViews(['your-skill.card-a', 'your-skill.card-b'])
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The optional second argument is the name of a method to instrument on each stub VC. When provided, each stub controller gets a function at that name that records whether it was called and what arguments it received:
|
|
400
420
|
|
|
401
|
-
|
|
421
|
+
```ts
|
|
402
422
|
await fakeGetViews.fakeGetViews(['your-skill.card'], 'load')
|
|
423
|
+
|
|
424
|
+
// After your code triggers the stub:
|
|
425
|
+
const stub = /* get rendered card controller from your vc */
|
|
426
|
+
assert.isTrue(stub.wasHit)
|
|
427
|
+
assert.isEqualDeep(stub.passedParams, [expectedArgs])
|
|
403
428
|
```
|
|
404
429
|
|
|
405
430
|
> **Note:** The export is the `heartwoodEventFaker` object. Call it as `fakeGetViews.fakeGetViews(...)`.
|
|
@@ -565,6 +590,8 @@ import {
|
|
|
565
590
|
Sizer, DelayedPlacer,
|
|
566
591
|
// Utilities
|
|
567
592
|
sizeUtil, Settings,
|
|
593
|
+
// Emitter — pass to Sizer and DelayedPlacer to coordinate layout events
|
|
594
|
+
SimpleEmitter,
|
|
568
595
|
// Types
|
|
569
596
|
AnimationEmitter,
|
|
570
597
|
SizerProps,
|
|
@@ -662,7 +689,17 @@ Pass the **same emitter instance** to `Sizer` and `DelayedPlacer` when they are
|
|
|
662
689
|
|
|
663
690
|
### AnimationEmitter
|
|
664
691
|
|
|
665
|
-
The interface `Sizer` and `DelayedPlacer` use to communicate layout-change events.
|
|
692
|
+
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:
|
|
693
|
+
|
|
694
|
+
```ts
|
|
695
|
+
import { SimpleEmitter } from '@sprucelabs/spruce-heartwood-utils'
|
|
696
|
+
|
|
697
|
+
const emitter = new SimpleEmitter()
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
`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.
|
|
701
|
+
|
|
702
|
+
The `AnimationEmitter` interface, for reference:
|
|
666
703
|
|
|
667
704
|
```ts
|
|
668
705
|
interface AnimationEmitter {
|
|
@@ -834,6 +871,13 @@ enqueue(() => this.handleReady())
|
|
|
834
871
|
|
|
835
872
|
Cancels a pending hide and queues a show for elements toggled back before their hide ran.
|
|
836
873
|
|
|
874
|
+
```ts
|
|
875
|
+
function clearPendingHideAndQueueShow(
|
|
876
|
+
refOrNode: React.RefObject<HTMLElement> | HTMLElement,
|
|
877
|
+
delay?: number // default: 40ms
|
|
878
|
+
): void
|
|
879
|
+
```
|
|
880
|
+
|
|
837
881
|
> Prefer `showRightAway()` for most toggle cases.
|
|
838
882
|
|
|
839
883
|
---
|
|
@@ -842,6 +886,13 @@ Cancels a pending hide and queues a show for elements toggled back before their
|
|
|
842
886
|
|
|
843
887
|
Cancels a pending show and queues a hide.
|
|
844
888
|
|
|
889
|
+
```ts
|
|
890
|
+
function clearPendingShowAndQueueHide(
|
|
891
|
+
refOrNode: React.RefObject<HTMLElement> | HTMLElement,
|
|
892
|
+
delay?: number // default: 40ms
|
|
893
|
+
): void
|
|
894
|
+
```
|
|
895
|
+
|
|
845
896
|
> Prefer `hideRightAway()` for most toggle cases.
|
|
846
897
|
|
|
847
898
|
---
|
|
@@ -874,12 +925,19 @@ return <div ref={ref} className="your-panel hidden">...</div>
|
|
|
874
925
|
|
|
875
926
|
#### `useShowNow(ref, delay?)` — React Hook
|
|
876
927
|
|
|
877
|
-
Like `useQueueShow` but places itself at the front of the queue on every render.
|
|
928
|
+
Like `useQueueShow` but places itself at the front of the queue on every render. Use for elements that must appear before any other queued items — such as a primary heading that should always be visible before supporting content fades in.
|
|
878
929
|
|
|
879
930
|
```ts
|
|
880
931
|
function useShowNow(ref: React.RefObject<HTMLElement>, delay?: number): void
|
|
881
932
|
```
|
|
882
933
|
|
|
934
|
+
```tsx
|
|
935
|
+
const ref = useRef<HTMLHeadingElement>(null)
|
|
936
|
+
useShowNow(ref)
|
|
937
|
+
|
|
938
|
+
return <h1 ref={ref} className="your-heading hidden">Title</h1>
|
|
939
|
+
```
|
|
940
|
+
|
|
883
941
|
---
|
|
884
942
|
|
|
885
943
|
### Sizer — Animated Height Container
|
|
@@ -906,6 +964,11 @@ interface SizerProps {
|
|
|
906
964
|
// overflow is always visible (adds force-show-overflow class).
|
|
907
965
|
shouldHideOverflow?: boolean
|
|
908
966
|
|
|
967
|
+
// Debounce delay in ms before each resize measurement fires. Default: 250ms.
|
|
968
|
+
// Lower values make Sizer react faster to content changes; useful when
|
|
969
|
+
// content updates quickly and you want tighter animation timing.
|
|
970
|
+
sizerDelayMs?: number
|
|
971
|
+
|
|
909
972
|
// Event emitter. Without one, Sizer only resizes on React re-renders,
|
|
910
973
|
// not in response to external layout events.
|
|
911
974
|
emitter?: AnimationEmitter
|
|
@@ -916,6 +979,8 @@ interface SizerProps {
|
|
|
916
979
|
|
|
917
980
|
```ts
|
|
918
981
|
sizer.current?.resize(): boolean // measure and apply new height; returns true if it changed
|
|
982
|
+
sizer.current?.showOverflow(): void // temporarily allow overflow (e.g. while a dropdown inside is open)
|
|
983
|
+
sizer.current?.hideOverflow(): void // re-apply overflow: hidden
|
|
919
984
|
```
|
|
920
985
|
|
|
921
986
|
#### Emitter Events
|
|
@@ -1014,28 +1079,34 @@ delayedPlacer.current?.placeRightAway(): void // re-measure and re-place immedi
|
|
|
1014
1079
|
#### Usage
|
|
1015
1080
|
|
|
1016
1081
|
```tsx
|
|
1017
|
-
|
|
1082
|
+
import React, { useRef, useMemo } from 'react'
|
|
1083
|
+
import { DelayedPlacer, SimpleEmitter } from '@sprucelabs/spruce-heartwood-utils'
|
|
1018
1084
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
}
|
|
1085
|
+
function YourPanel({ isFocused }: { isFocused: () => boolean }) {
|
|
1086
|
+
const placerRef = useRef<React.ElementRef<typeof DelayedPlacer>>(null)
|
|
1087
|
+
const emitter = useMemo(() => new SimpleEmitter(), [])
|
|
1023
1088
|
|
|
1024
|
-
render() {
|
|
1025
1089
|
return (
|
|
1026
1090
|
<DelayedPlacer
|
|
1027
|
-
className="
|
|
1028
|
-
isEnabled={
|
|
1029
|
-
emitter={
|
|
1091
|
+
className="placer__panel"
|
|
1092
|
+
isEnabled={true}
|
|
1093
|
+
emitter={emitter}
|
|
1030
1094
|
ref={placerRef}
|
|
1031
|
-
isFocused={
|
|
1095
|
+
isFocused={isFocused}
|
|
1032
1096
|
>
|
|
1033
|
-
{
|
|
1097
|
+
{yourContent}
|
|
1034
1098
|
</DelayedPlacer>
|
|
1035
1099
|
)
|
|
1036
1100
|
}
|
|
1037
1101
|
```
|
|
1038
1102
|
|
|
1103
|
+
When you need to re-place after a content change that happens outside React state:
|
|
1104
|
+
|
|
1105
|
+
```ts
|
|
1106
|
+
// Trigger placement immediately, bypassing the debounce:
|
|
1107
|
+
placerRef.current?.placeRightAway()
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1039
1110
|
> `isEnabled`, `className`, and `isFocused` are all required. The child must be `position: absolute` in CSS for placement to have visual effect.
|
|
1040
1111
|
|
|
1041
1112
|
---
|
|
@@ -1098,6 +1169,7 @@ sizeUtil.bodyWidth = () => 1200
|
|
|
1098
1169
|
A complete example showing all three systems working together in a card component:
|
|
1099
1170
|
|
|
1100
1171
|
```tsx
|
|
1172
|
+
import React, { useMemo } from 'react'
|
|
1101
1173
|
import {
|
|
1102
1174
|
Sizer, DelayedPlacer, queueShow, Settings, SimpleEmitter,
|
|
1103
1175
|
} from '@sprucelabs/spruce-heartwood-utils'
|
|
@@ -1139,6 +1211,9 @@ queueShow removes .hidden from each element with a 40ms stagger
|
|
|
1139
1211
|
**Test setup for animation components:**
|
|
1140
1212
|
|
|
1141
1213
|
```ts
|
|
1214
|
+
import { Settings, stopQueue } from '@sprucelabs/spruce-heartwood-utils'
|
|
1215
|
+
import { skillViewState } from '@sprucelabs/spruce-heartwood-utils/build/components/skillViews/skillViewState'
|
|
1216
|
+
|
|
1142
1217
|
protected async beforeEach() {
|
|
1143
1218
|
Settings.disableAnimations()
|
|
1144
1219
|
skillViewState.isFullScreen = false
|