@vrixton/pixel-pet 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # PixelPet
2
2
 
3
- A pixel art pet for React: a cat, a dog, an axolotl, a parakeet, or a hamster. It walks along the bottom of its container, falls asleep when nobody's around, reacts to clicks, changes its mood over time, and can speak on demand.
3
+ A pixel art pet for React: a cat, a dog, an axolotl, a parakeet, or a hamster. It walks along the bottom of its container, dozes off when nobody's around, reacts to clicks, changes its mood over time, and can speak on demand. It can also open a menu of actions that your app decides, and rest in a corner until the user wakes it up.
4
4
 
5
5
  It was built for the handheld-console interface of [V-DEX](https://v-dex.vercel.app), where it lives inside the device's screen.
6
6
 
@@ -57,7 +57,7 @@ export function Screen() {
57
57
  }
58
58
  ```
59
59
 
60
- The message shows right away, without hovering, and screen readers announce it. The pet wakes up if it was asleep and stands still while it talks. A new message replaces the current one, a click on the pet interrupts it, and hovering over the pet pauses the timer so the message can be read.
60
+ The message shows right away, without hovering, and screen readers announce it. The pet wakes up if it was dozing and stands still while it talks. While it is [resting](#resting), `say()` is ignored. A new message replaces the current one, a click on the pet interrupts it, and hovering over the pet pauses the timer so the message can be read.
61
61
 
62
62
  | Option | Type | Default | Description |
63
63
  | --- | --- | --- | --- |
@@ -66,21 +66,97 @@ The message shows right away, without hovering, and screen readers announce it.
66
66
 
67
67
  Call `pet.current?.hush()` to close the current message right away, for example when the user dismisses it.
68
68
 
69
+ ## Reacting with an emotion
70
+
71
+ `emote()` plays one of the pet's emotions so it can react to your app's own events, such as a form that was sent:
72
+
73
+ ```tsx
74
+ pet.current?.emote('happy');
75
+ ```
76
+
77
+ It works like the pet's own mood changes: the animation plays for five seconds, and its message shows on hover or keyboard focus. `emote()` is behavior and `say()` is speech, so use `say(message, { emotion })` when the user should read something right away. `emote()` is ignored in showcase mode and while the pet is resting.
78
+
79
+ ## Actions menu
80
+
81
+ Pass `actions` to turn the pet into a menu button. Each action is one item, and the pet calls its `onSelect` when the user picks it. The pet never interprets an action's `id`: it is only the React key and your own identifier.
82
+
83
+ ```tsx
84
+ const actions: PetMenuAction[] = [
85
+ { id: 'chat', label: 'Chat with Danko', onSelect: openChat },
86
+ { id: 'rest', label: 'Rest on the right', onSelect: () => pet.current?.rest('right') },
87
+ ];
88
+
89
+ <PixelPet ref={pet} petName="Danko" actions={actions} />
90
+ ```
91
+
92
+ A click, a tap, Enter, or Space opens the menu above the pet. When the pet is close to an edge, the menu shifts sideways to stay inside the container, the same way the speech bubble does. While the menu is open, the pet stands still in its idle animation and hides its bubble.
93
+
94
+ The menu closes when the user picks an action, clicks outside, presses Escape, or uses the close button in its corner. The focus then goes back to the pet, before `onSelect` runs, so an action that opens something else can move the focus there.
95
+
96
+ The menu follows the WAI-ARIA menu button pattern: the arrow keys move between the items, Home and End jump to the first and last ones, and Tab closes it. ArrowDown and ArrowUp on the pet open it on the first or the last item. Use `menuLabels` to name the menu and its close button in another language. Both accept `{name}`:
97
+
98
+ ```tsx
99
+ <PixelPet
100
+ petName="Danko"
101
+ actions={actions}
102
+ menuLabels={{ menu: 'Acciones de {name}', close: 'Cerrar el menú' }}
103
+ />
104
+ ```
105
+
106
+ With actions, a click only opens the menu: the pet shows no emotion, so it never looks upset about the action the user is about to pick. Opening the menu four times in five seconds makes it angry instead: the menu stays closed, the angry message explains why, and the pet ignores clicks for two seconds.
107
+
108
+ Without `actions`, with an empty list, or in showcase mode, a click behaves as it always did.
109
+
110
+ ## Resting
111
+
112
+ `rest()` sends the pet to the left, the center, or the right of its container, where it falls asleep. It walks there first, or appears there right away when it can't walk.
113
+
114
+ ```tsx
115
+ pet.current?.rest('right');
116
+ ```
117
+
118
+ While resting, only a direct click on the pet wakes it up. Scrolling, moving the mouse, typing, `say()`, and `emote()` leave it alone. A click while it is still on its way cancels the trip. Calling `rest()` again while it is resting or on its way does nothing, and it is ignored in showcase mode.
119
+
120
+ `onRestingChange` tells you when the state changes: it receives the spot once the pet is really asleep there, and `null` when the user wakes it up. A trip that was canceled reports nothing.
121
+
122
+ To remember the state, for example across visits, pass `resting` and keep the value yourself. The pet doesn't store anything:
123
+
124
+ ```tsx
125
+ const [resting, setResting] = useState<RestAt | null>(() => readRestingFromStorage());
126
+
127
+ <PixelPet
128
+ ref={pet}
129
+ petName="Danko"
130
+ resting={resting}
131
+ onRestingChange={(at) => {
132
+ setResting(at);
133
+ saveRestingToStorage(at);
134
+ }}
135
+ />
136
+ ```
137
+
138
+ When `resting` already holds a spot on the first render, the pet appears asleep there without walking. Changing it later sends the pet there. With `resting`, the prop is the source of truth: a click reports `null` through `onRestingChange`, but the pet keeps sleeping until the prop changes. If you pass `resting` and ignore `onRestingChange`, the pet never wakes up.
139
+
69
140
  ## Props
70
141
 
71
142
  | Prop | Type | Default | Description |
72
143
  | --- | --- | --- | --- |
73
- | `petName` | `string` | `'Static'` | The name shown in the mood messages. |
144
+ | `petName` | `string` | `'Minerva'` | The name shown in the mood messages. |
74
145
  | `variant` | `'cat' \| 'dog' \| 'axolotl' \| 'parakeet' \| 'hamster'` | `'cat'` | Which of the bundled sprites to use. |
75
146
  | `spriteSrc` | `string` | none | A URL for your own sprite sheet. It takes priority over `variant`. |
76
147
  | `color` | `string` | `'#FDDBA0'` | The pet's main color, used for its name and for the border of neutral speech bubbles. |
77
148
  | `label` | `string` | `petName` | The accessible name of the pet's button. |
149
+ | `mode` | `'live' \| 'showcase'` | `'live'` | Use `'showcase'` for a pet on display, such as in a pet picker: it only idles, falls asleep, and says its name when clicked. |
78
150
  | `walk` | `boolean` | `true` | Set it to `false` to keep the pet in place. |
79
- | `messages` | `PetMessages` | English messages | Replaces the text shown for each emotion. |
151
+ | `messages` | `PetMessages` | English messages | Replaces the text shown for each emotion and the showcase greeting. |
152
+ | `actions` | `PetMenuAction[]` | none | Turns a click into a menu with these actions. See [Actions menu](#actions-menu). Ignored in showcase mode. |
153
+ | `menuLabels` | `PetMenuLabels` | `'{name} actions'`, `'Close menu'` | The accessible names of the menu and its close button. |
154
+ | `resting` | `'left' \| 'center' \| 'right' \| null` | none | Controls where the pet is resting, or `null` when it is awake. See [Resting](#resting). |
155
+ | `onRestingChange` | `(at) => void` | none | Called with the spot once the pet falls asleep there, and with `null` when the user wakes it up. |
80
156
  | `dialogStyle` | `DialogStyleProps` | none | Styles for every bubble. |
81
157
  | `emotionDialogStyles` | `Partial<Record<PetEmotion, DialogStyleProps>>` | none | Styles for the bubble of a single emotion. |
82
- | `onClick` | `(event) => void` | none | Called when the user clicks or taps the pet. Call `event.preventDefault()` inside it to skip the pet's own reaction. |
83
- | `ref` | `Ref<PixelPetHandle>` | none | Gives you the `say()` and `hush()` methods. |
158
+ | `onClick` | `(event) => void` | none | Called when the user clicks or taps the pet. Call `event.preventDefault()` inside it to skip the pet's own reaction, including opening the menu. |
159
+ | `ref` | `Ref<PixelPetHandle>` | none | Gives you the `say()`, `hush()`, `emote()`, and `rest()` methods. |
84
160
 
85
161
  ## Reusing a configuration
86
162
 
@@ -111,6 +187,7 @@ Messages can be plain strings with two markers, so they fit in translation files
111
187
  happy: '{name} está **feliz**.',
112
188
  angry: '{name} está **abrumado**.',
113
189
  curious: '{name} tiene mucha **curiosidad**.',
190
+ greeting: '¡Hola, soy {name}!',
114
191
  }}
115
192
  />
116
193
  ```
@@ -142,9 +219,13 @@ A sheet passed through `spriteSrc` has to follow the same layout as the bundled
142
219
 
143
220
  ## How it behaves
144
221
 
145
- The pet wanders on its own, sometimes walking and sometimes standing, and turns around at the edges of the container. Every 30 seconds it may switch to a random mood for 5 seconds; hovering over it or focusing it with the keyboard shows what it's feeling. Clicking it shows its last mood for three seconds, and clicking it more than five times in ten seconds makes it angry. After a click, the bubble stays hidden while the pointer rests on the pet; moving the pointer away and back shows it again. After two minutes without mouse, touch, keyboard, or scroll wheel activity on the page, it falls asleep, and any activity wakes it up.
222
+ The pet wanders on its own, sometimes walking and sometimes standing, and turns around at the edges of the container. Every 30 seconds it may switch to a random mood for 5 seconds. Focusing it with the keyboard shows what it's feeling, and so does hovering over it with a mouse or a pen, which also holds the mood and keeps the pet in place until the pointer leaves, so the message can be read. Touch screens have no hover, so there the message only shows on a click. Without `actions`, clicking it shows its last mood for three seconds, and clicking it more than five times in ten seconds makes it angry. After a click, the bubble stays hidden while the pointer rests on the pet; moving the pointer away and back shows it again.
223
+
224
+ After two minutes without mouse, touch, keyboard, or scroll wheel activity on the page, it dozes off for a couple of seconds and wakes up by itself with a little start. It does it again every two minutes while the page stays quiet. It only stays asleep when you ask it to [rest](#resting), so a sleeping pet always means the same thing.
225
+
226
+ With `mode="showcase"`, the pet stays in place playing its idle animation, with no random moods and no bubble on hover. Clicking it shows `messages.greeting`, or "Hi, I'm {name}!" by default, for three seconds, without getting angry however many times it is clicked. It still falls asleep after two minutes without activity and wakes up with any activity, because a showcase pet can't rest, so its sleep can't be confused with resting. `say()` and `hush()` keep working.
146
227
 
147
- The pet is a real button, so it can be reached with the keyboard, has a visible focus outline, and is announced with its name. When the user prefers reduced motion, it stays still, its animation pauses, and the bubble appears without its pop-in effect.
228
+ The pet is a real button, so it can be reached with the keyboard, has a visible focus outline, and is announced with its name. With actions, it is announced as a menu button and its menu can be used entirely from the keyboard. When the user prefers reduced motion, it stays still, its animation pauses, and the bubble, the menu, and the start after a doze appear without motion.
148
229
 
149
230
  ## Development
150
231
 
Binary file
package/dist/index.css CHANGED
@@ -1 +1 @@
1
- .pixel-pet{position:absolute;inset:0;pointer-events:none}.pixel-pet-container{position:absolute;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;user-select:none;will-change:transform;pointer-events:auto}.pixel-pet-status{position:absolute;top:0;left:0;right:0;display:flex;justify-content:center}.pixel-pet-dialog{padding:6px 10px;font-size:12px;min-width:80px;width:max-content;max-width:240px;box-sizing:border-box;text-align:center;animation:pixel-pet-pop-in .2s ease-out;pointer-events:none;position:absolute;bottom:27px;border:1px solid;color:#fff;box-shadow:none}.pixel-pet-highlight-pet{color:var(--pixel-pet-color, #FDDBA0);font-weight:700}.pixel-pet-highlight-happy{color:#ffeb3b;font-weight:700}.pixel-pet-highlight-angry{color:#f04037;font-weight:700}.pixel-pet-highlight-curious{color:#8be383;font-weight:700}.pixel-pet-sprite{display:block;padding:0;border:0;background-color:transparent;color:inherit;font:inherit;appearance:none;cursor:pointer;image-rendering:pixelated;image-rendering:crisp-edges;background-repeat:no-repeat;width:60px;height:60px;filter:drop-shadow(0px 4px 6px rgba(0,0,0,.6)) saturate(140%)}.pixel-pet-cat{background-image:url("./cat-sprite-4GTCYV2F.png")}.pixel-pet-dog{background-image:url("./dog-sprite-VCBZ3EYT.png")}.pixel-pet-axolotl{background-image:url("./axolotl-sprite-HPGKQ5TQ.png")}.pixel-pet-parakeet{background-image:url("./parakeet-sprite-RZ7M7WXL.png")}.pixel-pet-hamster{background-image:url("./hamster-sprite-FECZBAMR.png")}.pixel-pet-sprite:focus-visible{outline:2px solid currentColor;outline-offset:2px}.pixel-pet-sprite.pixel-pet-flip-x{transform:scaleX(-1)}.pixel-pet-state-idle{animation:pixel-pet-idle 1s steps(9) infinite}.pixel-pet-state-walk{animation:pixel-pet-walk 1.3s steps(9) infinite}.pixel-pet-state-happy{animation:pixel-pet-happy .8s steps(9) infinite;height:65px}.pixel-pet-state-sleep{animation:pixel-pet-sleep 2s steps(9) infinite;height:45px;width:65px}.pixel-pet-state-angry{animation:pixel-pet-angry .8s steps(9) infinite;width:65px}.pixel-pet-state-curious{animation:pixel-pet-curious 3s steps(9) infinite}@keyframes pixel-pet-idle{0%{background-position:0 -5px}to{background-position:-605px -5px}}@keyframes pixel-pet-walk{0%{background-position:0 -73px}to{background-position:-600px -73px}}@keyframes pixel-pet-happy{0%{background-position:0 -135px}to{background-position:-610px -135px}}@keyframes pixel-pet-sleep{0%{background-position:0px -205px}to{background-position:-610px -205px}}@keyframes pixel-pet-angry{0%{background-position:0 -250px}to{background-position:-610px -250px}}@keyframes pixel-pet-curious{0%{background-position:0 -315px}to{background-position:-610px -315px}}@keyframes pixel-pet-pop-in{0%{transform:scale(.8);opacity:0}to{transform:scale(1);opacity:1}}@media(prefers-reduced-motion:reduce){.pixel-pet-sprite{animation-play-state:paused}.pixel-pet-dialog{animation:none}}
1
+ .pixel-pet{position:absolute;inset:0;pointer-events:none}.pixel-pet-container{position:absolute;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;user-select:none;will-change:transform;pointer-events:auto}.pixel-pet-startle{animation:pixel-pet-startle .3s ease-out}.pixel-pet-status{position:absolute;top:0;left:0;right:0;display:flex;justify-content:center}.pixel-pet-dialog{padding:6px 10px;font-size:12px;min-width:80px;width:max-content;max-width:240px;box-sizing:border-box;text-align:center;animation:pixel-pet-pop-in .2s ease-out;pointer-events:none;position:absolute;bottom:27px;border:1px solid;color:#fff;box-shadow:none}.pixel-pet-menu{position:absolute;bottom:calc(100% + 27px);z-index:1;box-sizing:border-box;width:max-content;min-width:120px;max-width:240px;padding:4px 26px 4px 4px;font-size:12px;animation:pixel-pet-pop-in .2s ease-out}.pixel-pet-menu-close{position:absolute;top:4px;right:4px;display:flex;align-items:center;justify-content:center;width:18px;height:18px;padding:0;border:0;background-color:transparent;color:inherit;font:inherit;line-height:1;cursor:pointer}.pixel-pet-menu-list{margin:0;padding:0;list-style:none}.pixel-pet-menu-item{display:block;width:100%;padding:6px 10px;border:0;background-color:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.pixel-pet-menu-item:hover,.pixel-pet-menu-item:focus-visible{background-color:#ffffff1f}.pixel-pet-menu-item:focus-visible{outline:1px solid var(--pixel-pet-color, #FDDBA0);outline-offset:-1px}.pixel-pet-highlight-pet{color:var(--pixel-pet-color, #FDDBA0);font-weight:700}.pixel-pet-highlight-happy{color:#ffeb3b;font-weight:700}.pixel-pet-highlight-angry{color:#f04037;font-weight:700}.pixel-pet-highlight-curious{color:#8be383;font-weight:700}.pixel-pet-sprite{display:block;padding:0;border:0;background-color:transparent;color:inherit;font:inherit;appearance:none;cursor:pointer;image-rendering:pixelated;image-rendering:crisp-edges;background-repeat:no-repeat;width:60px;height:60px;filter:drop-shadow(0px 4px 6px rgba(0,0,0,.6)) saturate(140%)}.pixel-pet-cat{background-image:url("./cat-sprite-LNU2JEGA.png")}.pixel-pet-dog{background-image:url("./dog-sprite-VCBZ3EYT.png")}.pixel-pet-axolotl{background-image:url("./axolotl-sprite-HPGKQ5TQ.png")}.pixel-pet-parakeet{background-image:url("./parakeet-sprite-RZ7M7WXL.png")}.pixel-pet-hamster{background-image:url("./hamster-sprite-FECZBAMR.png")}.pixel-pet-sprite:focus-visible{outline:2px solid currentColor;outline-offset:2px}.pixel-pet-sprite.pixel-pet-flip-x{transform:scaleX(-1)}.pixel-pet-state-idle{animation:pixel-pet-idle 1s steps(9) infinite}.pixel-pet-state-walk{animation:pixel-pet-walk 1.3s steps(9) infinite}.pixel-pet-state-happy{animation:pixel-pet-happy .8s steps(9) infinite;height:65px}.pixel-pet-state-sleep{animation:pixel-pet-sleep 2s steps(9) infinite;height:45px;width:65px}.pixel-pet-state-angry{animation:pixel-pet-angry .8s steps(9) infinite;width:65px}.pixel-pet-state-curious{animation:pixel-pet-curious 3s steps(9) infinite}@keyframes pixel-pet-idle{0%{background-position:0 -5px}to{background-position:-605px -5px}}@keyframes pixel-pet-walk{0%{background-position:0 -73px}to{background-position:-600px -73px}}@keyframes pixel-pet-happy{0%{background-position:0 -135px}to{background-position:-610px -135px}}@keyframes pixel-pet-sleep{0%{background-position:0px -205px}to{background-position:-610px -205px}}@keyframes pixel-pet-angry{0%{background-position:0 -250px}to{background-position:-610px -250px}}@keyframes pixel-pet-curious{0%{background-position:0 -315px}to{background-position:-610px -315px}}@keyframes pixel-pet-startle{0%,to{translate:0 0}35%{translate:0 -3px}}@keyframes pixel-pet-pop-in{0%{transform:scale(.8);opacity:0}to{transform:scale(1);opacity:1}}@media(prefers-reduced-motion:reduce){.pixel-pet-sprite{animation-play-state:paused}.pixel-pet-dialog,.pixel-pet-menu,.pixel-pet-startle{animation:none}}
package/dist/index.d.mts CHANGED
@@ -1,5 +1,11 @@
1
1
  import React from 'react';
2
2
 
3
+ interface PetMenuAction {
4
+ id: string;
5
+ label: string;
6
+ onSelect: () => void;
7
+ }
8
+
3
9
  interface DialogStyleProps {
4
10
  backgroundColor?: string;
5
11
  textColor?: string;
@@ -11,7 +17,13 @@ interface DialogStyleProps {
11
17
  type PetEmotion = 'happy' | 'angry' | 'curious';
12
18
  type PetAction = 'idle' | 'walk' | 'sleep';
13
19
  type PetVariant = 'cat' | 'dog' | 'axolotl' | 'parakeet' | 'hamster';
14
- type PetMessages = Partial<Record<PetEmotion, React.ReactNode>>;
20
+ type PetMode = 'live' | 'showcase';
21
+ type PetMessages = Partial<Record<PetEmotion | 'greeting', React.ReactNode>>;
22
+ type RestAt = 'left' | 'center' | 'right';
23
+ interface PetMenuLabels {
24
+ menu?: string;
25
+ close?: string;
26
+ }
15
27
  interface PetSayOptions {
16
28
  emotion?: PetEmotion;
17
29
  duration?: number;
@@ -19,6 +31,8 @@ interface PetSayOptions {
19
31
  interface PixelPetHandle {
20
32
  say: (message: React.ReactNode, options?: PetSayOptions) => void;
21
33
  hush: () => void;
34
+ emote: (emotion: PetEmotion) => void;
35
+ rest: (at: RestAt) => void;
22
36
  }
23
37
  interface PixelPetProps {
24
38
  petName?: string;
@@ -26,8 +40,13 @@ interface PixelPetProps {
26
40
  spriteSrc?: string;
27
41
  color?: string;
28
42
  label?: string;
43
+ mode?: PetMode;
29
44
  walk?: boolean;
30
45
  messages?: PetMessages;
46
+ actions?: PetMenuAction[];
47
+ menuLabels?: PetMenuLabels;
48
+ resting?: RestAt | null;
49
+ onRestingChange?: (at: RestAt | null) => void;
31
50
  dialogStyle?: DialogStyleProps;
32
51
  emotionDialogStyles?: Partial<Record<PetEmotion, DialogStyleProps>>;
33
52
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
@@ -35,4 +54,4 @@ interface PixelPetProps {
35
54
  }
36
55
  declare const PixelPet: React.FC<PixelPetProps>;
37
56
 
38
- export { type DialogStyleProps, type PetAction, type PetEmotion, type PetMessages, type PetSayOptions, type PetVariant, PixelPet, type PixelPetHandle, type PixelPetProps };
57
+ export { type DialogStyleProps, type PetAction, type PetEmotion, type PetMenuAction, type PetMenuLabels, type PetMessages, type PetMode, type PetSayOptions, type PetVariant, PixelPet, type PixelPetHandle, type PixelPetProps, type RestAt };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import React from 'react';
2
2
 
3
+ interface PetMenuAction {
4
+ id: string;
5
+ label: string;
6
+ onSelect: () => void;
7
+ }
8
+
3
9
  interface DialogStyleProps {
4
10
  backgroundColor?: string;
5
11
  textColor?: string;
@@ -11,7 +17,13 @@ interface DialogStyleProps {
11
17
  type PetEmotion = 'happy' | 'angry' | 'curious';
12
18
  type PetAction = 'idle' | 'walk' | 'sleep';
13
19
  type PetVariant = 'cat' | 'dog' | 'axolotl' | 'parakeet' | 'hamster';
14
- type PetMessages = Partial<Record<PetEmotion, React.ReactNode>>;
20
+ type PetMode = 'live' | 'showcase';
21
+ type PetMessages = Partial<Record<PetEmotion | 'greeting', React.ReactNode>>;
22
+ type RestAt = 'left' | 'center' | 'right';
23
+ interface PetMenuLabels {
24
+ menu?: string;
25
+ close?: string;
26
+ }
15
27
  interface PetSayOptions {
16
28
  emotion?: PetEmotion;
17
29
  duration?: number;
@@ -19,6 +31,8 @@ interface PetSayOptions {
19
31
  interface PixelPetHandle {
20
32
  say: (message: React.ReactNode, options?: PetSayOptions) => void;
21
33
  hush: () => void;
34
+ emote: (emotion: PetEmotion) => void;
35
+ rest: (at: RestAt) => void;
22
36
  }
23
37
  interface PixelPetProps {
24
38
  petName?: string;
@@ -26,8 +40,13 @@ interface PixelPetProps {
26
40
  spriteSrc?: string;
27
41
  color?: string;
28
42
  label?: string;
43
+ mode?: PetMode;
29
44
  walk?: boolean;
30
45
  messages?: PetMessages;
46
+ actions?: PetMenuAction[];
47
+ menuLabels?: PetMenuLabels;
48
+ resting?: RestAt | null;
49
+ onRestingChange?: (at: RestAt | null) => void;
31
50
  dialogStyle?: DialogStyleProps;
32
51
  emotionDialogStyles?: Partial<Record<PetEmotion, DialogStyleProps>>;
33
52
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
@@ -35,4 +54,4 @@ interface PixelPetProps {
35
54
  }
36
55
  declare const PixelPet: React.FC<PixelPetProps>;
37
56
 
38
- export { type DialogStyleProps, type PetAction, type PetEmotion, type PetMessages, type PetSayOptions, type PetVariant, PixelPet, type PixelPetHandle, type PixelPetProps };
57
+ export { type DialogStyleProps, type PetAction, type PetEmotion, type PetMenuAction, type PetMenuLabels, type PetMessages, type PetMode, type PetSayOptions, type PetVariant, PixelPet, type PixelPetHandle, type PixelPetProps, type RestAt };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  'use client';
2
- "use strict";var Y=Object.defineProperty;var De=Object.getOwnPropertyDescriptor;var _e=Object.getOwnPropertyNames;var Ae=Object.prototype.hasOwnProperty;var Ie=(i,s)=>{for(var u in s)Y(i,u,{get:s[u],enumerable:!0})},Le=(i,s,u,b)=>{if(s&&typeof s=="object"||typeof s=="function")for(let d of _e(s))!Ae.call(i,d)&&d!==u&&Y(i,d,{get:()=>s[d],enumerable:!(b=De(s,d))||b.enumerable});return i};var Fe=i=>Le(Y({},"__esModule",{value:!0}),i);var Ye={};Ie(Ye,{PixelPet:()=>Ve});module.exports=Fe(Ye);var t=require("react");var c=require("react/jsx-runtime"),re=["pointermove","pointerdown","keydown","wheel"],le="(prefers-reduced-motion: reduce)",K=4,Ne=240,ie=5e3,We=3e3,He=/(\{name\}|\*\*[^*]+\*\*|__[^_]+__)/,q={happy:"{name} is feeling **happy**!",angry:"{name} is feeling **overwhelmed**!",curious:"{name} is very **curious**"},se=65,ae=100,Oe=.04,Be=100,Ue=i=>{if(typeof window.matchMedia!="function")return()=>{};let s=window.matchMedia(le);return s.addEventListener("change",i),()=>s.removeEventListener("change",i)},$e=()=>typeof window.matchMedia=="function"&&window.matchMedia(le).matches,Xe=()=>(0,t.useSyncExternalStore)(Ue,$e,()=>!1),Ge={happy:{backgroundColor:"#1e1c06eb",borderColor:"#ffeb3b"},angry:{backgroundColor:"#210908eb",borderColor:"#F04037"},curious:{backgroundColor:"#1f341deb",borderColor:"#8BE383"}},ze={backgroundColor:"#05080ceb",borderColor:"var(--pixel-pet-color, #FDDBA0)"},Ve=({petName:i="Static",variant:s="cat",spriteSrc:u,color:b,label:d,walk:ce=!0,messages:F,dialogStyle:x={},emotionDialogStyles:pe={},onClick:ue,ref:de})=>{let[a,p]=(0,t.useState)("idle"),fe=Xe(),y=ce&&!fe,N=(0,t.useRef)(null),C=(0,t.useRef)(null),Q=(0,t.useRef)(null),[W,me]=(0,t.useState)(null),[E,S]=(0,t.useState)(null),[ge,j]=(0,t.useState)(!1),[xe,J]=(0,t.useState)(!1),[he,Z]=(0,t.useState)(!1),[be,ee]=(0,t.useState)(!1),[f,ye]=(0,t.useState)(null),R=(0,t.useRef)(ae),[H,Ee]=(0,t.useState)(0),[O,Re]=(0,t.useState)("right"),D=(e,n)=>typeof e!="string"?e:e.split(He).map((o,r)=>r%2===0?o:o==="{name}"||o.startsWith("__")?(0,c.jsx)("span",{className:"pixel-pet-highlight-pet",children:o==="{name}"?i:o.slice(2,-2)},r):(0,c.jsx)("span",{className:`pixel-pet-highlight-${n??"pet"}`,children:o.slice(2,-2)},r)),Te={happy:D(F?.happy??q.happy,"happy"),angry:D(F?.angry??q.angry,"angry"),curious:D(F?.curious??q.curious,"curious")},te=(0,t.useRef)(a),_=(0,t.useRef)(null),T=(0,t.useRef)(!1),A=(0,t.useRef)(!1),l=(0,t.useRef)(null),oe=(0,t.useRef)("happy"),B=(0,t.useRef)([]),k=(0,t.useRef)(null),P=(0,t.useRef)(!1),v=(0,t.useRef)(null),U=(0,t.useRef)(ie),$=(e,n=!1)=>{a==="sleep"&&p("idle"),oe.current=e,S(e),Z(n),l.current&&clearTimeout(l.current),l.current=setTimeout(()=>{A.current||S(null)},5e3)},M=e=>{P.current=e!==null,ye(e)},I=()=>{v.current&&clearTimeout(v.current)},ne=e=>{I(),e!==1/0&&(v.current=setTimeout(()=>M(null),e))};(0,t.useImperativeHandle)(de,()=>({say:(e,n={})=>{p(o=>o==="sleep"?"idle":o),M({message:e,emotion:n.emotion??null}),U.current=n.duration??ie,ne(U.current)},hush:()=>{I(),M(null)}}),[]),(0,t.useEffect)(()=>{let e,n=()=>{p(o=>o==="sleep"?"idle":o),clearTimeout(e),e=setTimeout(()=>{S(null),M(null),p("sleep")},12e4)};return re.forEach(o=>{window.addEventListener(o,n,{passive:!0})}),n(),()=>{clearTimeout(e),re.forEach(o=>{window.removeEventListener(o,n)})}},[]),(0,t.useEffect)(()=>{te.current=a},[a]),(0,t.useEffect)(()=>{let e=setInterval(()=>{if(te.current==="sleep"||P.current)return;let n=["happy","angry","curious"],o=n[Math.floor(Math.random()*n.length)];$(o)},3e4);return()=>{clearInterval(e)}},[]),(0,t.useEffect)(()=>()=>{l.current&&clearTimeout(l.current),v.current&&clearTimeout(v.current),k.current&&clearTimeout(k.current)},[]),(0,t.useEffect)(()=>{if(!(a==="sleep"||!y)){if(a==="walk"){let e=Math.floor(Math.random()*175001)+5e3;_.current=setTimeout(()=>{p("idle"),T.current=!0,setTimeout(()=>{T.current=!1},3e4)},e)}else{let e=()=>{if(!T.current&&Math.random()>.4){p("walk");return}_.current=setTimeout(e,5e3)};e()}return()=>{_.current&&clearTimeout(_.current)}}},[a,y]);let X=e=>{R.current=e,C.current&&(C.current.style.transform=`translateX(${e}px)`)};(0,t.useEffect)(()=>{if(a!=="walk"||E!==null||f!==null||!y)return;let e=0,n=H-se;if(n<=e)return;let o=0,r=null,g=w=>{let z=r===null?0:Math.min(w-r,Be);r=w;let h=R.current+(O==="right"?1:-1)*Oe*z;if(h<=e||h>=n){if(Math.random()>.5){p("idle"),T.current=!0,setTimeout(()=>{T.current=!1},3e4);return}X(h<=e?e+2:n-2),Re(V=>V==="right"?"left":"right");return}X(h),o=requestAnimationFrame(g)};return o=requestAnimationFrame(g),()=>cancelAnimationFrame(o)},[a,E,f,O,y,H]),(0,t.useEffect)(()=>{let e=N.current;if(!e)return;let n=r=>{Ee(r),X(Math.max(Math.min(R.current,r-se),0))};if(n(e.clientWidth),typeof ResizeObserver>"u")return;let o=new ResizeObserver(([r])=>{r&&n(r.contentRect.width)});return o.observe(e),()=>o.disconnect()},[]);let ke=e=>{if(ue?.(e),e.defaultPrevented)return;let n=Date.now();a==="sleep"&&p("idle"),P.current&&(I(),M(null)),A.current=!1,ee(!0),k.current&&clearTimeout(k.current),k.current=setTimeout(()=>Z(!1),We);let o=[...B.current,n].filter(r=>n-r<=1e4);B.current=o,o.length>5?(B.current=[],$("angry",!0)):$(oe.current,!0)},Pe=()=>{j(!0),P.current&&I(),E&&(A.current=!0,l.current&&clearTimeout(l.current))},ve=()=>{j(!1),ee(!1),P.current&&ne(U.current),E&&(A.current=!1,l.current&&clearTimeout(l.current),l.current=setTimeout(()=>{S(null)},5e3))},L=f!==null&&a!=="sleep",m=a==="sleep"?null:L?f.emotion:E,G=L||m!==null&&(ge&&!be||xe||he);(0,t.useLayoutEffect)(()=>{let e=N.current,n=C.current,o=Q.current;if(!G||!e||!n||!o)return;let r=e.clientWidth,g=Math.max(Math.min(Ne,r-K*2),0),w=Math.min(o.offsetWidth,g),z=R.current+n.offsetWidth/2,h=K,V=r-K-w,Ce=z-w/2,Se=Math.max(Math.min(Ce,V),h);me({left:Se-R.current,maxWidth:g})},[G,m,f,H]);let Me=()=>m?`pixel-pet-state-${m}`:(!y||L)&&a==="walk"?"pixel-pet-state-idle":`pixel-pet-state-${a}`,we=e=>{let n=e?Ge[e]:ze,o=e&&pe[e]||{},r=o.backgroundColor||x.backgroundColor||n.backgroundColor,g=o.borderColor||x.borderColor||n.borderColor;return{backgroundColor:r,color:o.textColor||x.textColor||"#ffffff",border:`${o.borderWidth||x.borderWidth||"1px"} solid ${g}`,borderRadius:o.borderRadius||x.borderRadius||"0",fontFamily:o.fontFamily||x.fontFamily||"monospace"}};return(0,c.jsx)("div",{className:"pixel-pet",ref:N,style:b?{"--pixel-pet-color":b}:void 0,children:(0,c.jsxs)("div",{ref:C,className:"pixel-pet-container",style:{transform:`translateX(${ae}px)`},onMouseEnter:Pe,onMouseLeave:ve,children:[(0,c.jsx)("div",{className:"pixel-pet-status",role:"status",children:G&&(0,c.jsx)("div",{ref:Q,className:"pixel-pet-dialog",style:{...we(m),...W&&{left:W.left,maxWidth:W.maxWidth}},children:L?D(f.message,f.emotion):m&&Te[m]})}),(0,c.jsx)("button",{type:"button","aria-label":(d??i).split("{name}").join(i),className:`pixel-pet-sprite pixel-pet-${s} ${Me()} ${O==="left"?"pixel-pet-flip-x":""}`,style:u?{backgroundImage:`url("${u}")`}:void 0,onClick:ke,onFocus:e=>J(e.currentTarget.matches(":focus-visible")),onBlur:()=>J(!1)})]})})};0&&(module.exports={PixelPet});
2
+ "use strict";var Me=Object.defineProperty;var Tt=Object.getOwnPropertyDescriptor;var Pt=Object.getOwnPropertyNames;var Mt=Object.prototype.hasOwnProperty;var kt=(i,l)=>{for(var p in l)Me(i,p,{get:l[p],enumerable:!0})},wt=(i,l,p,E)=>{if(l&&typeof l=="object"||typeof l=="function")for(let x of Pt(l))!Mt.call(i,x)&&x!==p&&Me(i,x,{get:()=>l[x],enumerable:!(E=Tt(l,x))||E.enumerable});return i};var vt=i=>wt(Me({},"__esModule",{value:!0}),i);var Kt={};kt(Kt,{PixelPet:()=>Xt});module.exports=vt(Kt);var t=require("react");var X=require("react"),D=require("react/jsx-runtime"),ze=({id:i,initialFocus:l,label:p,closeLabel:E,actions:x,style:K,onSelect:G,onClose:w,restoreFocus:W,ref:te})=>{let m=(0,X.useRef)(null),z=()=>[...m.current?.querySelectorAll('[role="menuitem"]')??[]];(0,X.useEffect)(()=>{let u=z();u[l==="last"?u.length-1:0]?.focus()},[]),(0,X.useLayoutEffect)(()=>{let u=m.current?.parentElement;return()=>{u?.contains(document.activeElement)&&W()}},[]);let h=u=>{let b=z(),s=b.indexOf(document.activeElement);b[u(s,b.length)]?.focus()};return(0,D.jsxs)("div",{ref:te,className:"pixel-pet-menu",style:K,onKeyDown:u=>{switch(u.key){case"Escape":u.stopPropagation(),w();break;case"Tab":w();break;case"ArrowDown":h((b,s)=>(b+1)%s);break;case"ArrowUp":h((b,s)=>b<=0?s-1:b-1);break;case"Home":h(()=>0);break;case"End":h((b,s)=>s-1);break;default:return}u.preventDefault()},children:[(0,D.jsx)("button",{type:"button",className:"pixel-pet-menu-close","aria-label":E,tabIndex:-1,onClick:w,children:(0,D.jsx)("span",{"aria-hidden":"true",children:"\xD7"})}),(0,D.jsx)("ul",{ref:m,id:i,role:"menu","aria-label":p,className:"pixel-pet-menu-list",children:x.map(u=>(0,D.jsx)("li",{role:"none",children:(0,D.jsx)("button",{type:"button",role:"menuitem",tabIndex:-1,className:"pixel-pet-menu-item",onClick:()=>G(u),children:u.label})},u.id))})]})};var k=require("react/jsx-runtime"),Ve=["pointermove","pointerdown","keydown","wheel"],et="(prefers-reduced-motion: reduce)",ke=4,At=240,Ye=5e3,we=3e3,St=/(\{name\}|\*\*[^*]+\*\*|__[^_]+__)/,ve={happy:"{name} is feeling **happy**!",angry:"{name} is feeling **overwhelmed**!",curious:"{name} is very **curious**"},Dt="Hi, I'm {name}!",Lt="{name} actions",Ct="Close menu",Se=65,qe=100,Qe=.04,je=100,Je=12e4,It=2500,_t=300,Ft=4,Nt=5e3,Wt=2e3,Ht=i=>{if(typeof window.matchMedia!="function")return()=>{};let l=window.matchMedia(et);return l.addEventListener("change",i),()=>l.removeEventListener("change",i)},Ut=()=>typeof window.matchMedia=="function"&&window.matchMedia(et).matches,Ot=()=>(0,t.useSyncExternalStore)(Ht,Ut,()=>!1),Ae=(i,l)=>{let p=Math.max(l-Se,0);return i==="left"?0:i==="right"?p:p/2},Ze=(i,l,p)=>{let E=Math.max(Math.min(At,i-ke*2),0),x=Math.min(p,E),K=ke,G=i-ke-x;return{left:Math.max(Math.min(l-x/2,G),K),maxWidth:E}},Bt={happy:{backgroundColor:"#1e1c06eb",borderColor:"#ffeb3b"},angry:{backgroundColor:"#210908eb",borderColor:"#F04037"},curious:{backgroundColor:"#1f341deb",borderColor:"#8BE383"}},$t={backgroundColor:"#05080ceb",borderColor:"var(--pixel-pet-color, #FDDBA0)"},Xt=({petName:i="Minerva",variant:l="cat",spriteSrc:p,color:E,label:x,mode:K="live",walk:G=!0,messages:w,actions:W,menuLabels:te,resting:m,onRestingChange:z,dialogStyle:h={},emotionDialogStyles:De={},onClick:u,ref:b})=>{let[s,y]=(0,t.useState)("idle"),tt=Ot(),f=K==="showcase",R=G&&!tt&&!f,H=!f&&W!==void 0&&W.length>0,Le=(0,t.useId)(),L=(0,t.useRef)(null),V=(0,t.useRef)(null),ne=(0,t.useRef)(null),Ce=(0,t.useRef)(null),ce=(0,t.useRef)(null),[pe,nt]=(0,t.useState)(null),[me,rt]=(0,t.useState)(null),[Ie,C]=(0,t.useState)(!1);!H&&Ie&&C(!1);let[ot,it]=(0,t.useState)("first"),a=H&&Ie,fe=(0,t.useRef)(a),Y=m!==void 0,[c,st]=(0,t.useState)(()=>Y&&m!==null&&!f?{at:m,phase:"asleep",origin:"prop"}:null),U=(0,t.useRef)(c),v=c!==null,q=(0,t.useRef)(null),_e=(0,t.useRef)(m),de=(0,t.useRef)(z),[Q,O]=(0,t.useState)(null),[lt,Fe]=(0,t.useState)(!1),[at,Ne]=(0,t.useState)(!1),[ut,We]=(0,t.useState)(!1),[ct,He]=(0,t.useState)(!1),[pt,Ue]=(0,t.useState)(!1),[I,mt]=(0,t.useState)(null),T=(0,t.useRef)(qe),[B,ft]=(0,t.useState)(0),[ge,Oe]=(0,t.useState)("right"),xe=e=>e.split("{name}").join(i),re=(e,n)=>typeof e!="string"?e:e.split(St).map((r,o)=>o%2===0?r:r==="{name}"||r.startsWith("__")?(0,k.jsx)("span",{className:"pixel-pet-highlight-pet",children:r==="{name}"?i:r.slice(2,-2)},o):(0,k.jsx)("span",{className:`pixel-pet-highlight-${n??"pet"}`,children:r.slice(2,-2)},o)),dt={happy:re(w?.happy??ve.happy,"happy"),angry:re(w?.angry??ve.angry,"angry"),curious:re(w?.curious??ve.curious,"curious")},oe=(0,t.useRef)(s),ie=(0,t.useRef)(null),j=(0,t.useRef)(!1),se=(0,t.useRef)(!1),P=(0,t.useRef)(null),Be=(0,t.useRef)("happy"),he=(0,t.useRef)([]),J=(0,t.useRef)(null),be=(0,t.useRef)([]),$e=(0,t.useRef)(0),$=(0,t.useRef)(!1),Z=(0,t.useRef)(null),le=(0,t.useRef)(Ye),ye=(e,n=!1)=>{oe.current==="sleep"&&y("idle"),Be.current=e,O(e),We(n),P.current&&clearTimeout(P.current),P.current=setTimeout(()=>{se.current||O(null)},5e3)},_=e=>{$.current=e!==null,mt(e)},A=e=>{U.current=e,q.current=null,st(e)},Ee=({at:e,origin:n})=>{if(n==="prop"){A({at:e,phase:"asleep",origin:n});return}Y?(A(null),q.current=e):A({at:e,phase:"asleep",origin:n}),de.current?.(e)},gt=e=>{if(e.phase==="walking"&&e.origin==="method"){A(null);return}Y||A(null),de.current?.(null)},ee=()=>{Z.current&&clearTimeout(Z.current)},Re=e=>{ee(),e!==1/0&&(Z.current=setTimeout(()=>_(null),e))};(0,t.useImperativeHandle)(b,()=>({say:(e,n={})=>{U.current||(y(r=>r==="sleep"?"idle":r),_({message:e,emotion:n.emotion??null}),le.current=n.duration??Ye,Re(le.current))},hush:()=>{ee(),_(null)},emote:e=>{f||U.current||ye(e)},rest:e=>{if(f||U.current)return;let n={at:e,phase:"walking",origin:"method"};if(R){A(n);return}S(Ae(e,L.current?.clientWidth??0)),Ee(n)}}),[f,R,Y]),(0,t.useLayoutEffect)(()=>{de.current=z}),(0,t.useLayoutEffect)(()=>{if(_e.current===m||(_e.current=m,!Y||f))return;if(m===null){A(null);return}let e=q.current===m||!R;A({at:m,phase:e?"asleep":"walking",origin:"prop"})},[m]),(0,t.useEffect)(()=>{v&&(ee(),_(null),O(null),C(!1),y("idle"))},[v]),(0,t.useEffect)(()=>{let e,n,r,o=()=>{O(null),_(null),y("sleep")},d=()=>{e=setTimeout(d,Je),!(U.current||fe.current||$.current)&&(O(null),y("sleep"),n=setTimeout(()=>{oe.current==="sleep"&&(y("idle"),Ue(!0),r=setTimeout(()=>Ue(!1),_t))},It))},g=()=>{f&&y(M=>M==="sleep"?"idle":M),clearTimeout(e),e=setTimeout(f?o:d,Je)};return Ve.forEach(M=>{window.addEventListener(M,g,{passive:!0})}),g(),()=>{clearTimeout(e),clearTimeout(n),clearTimeout(r),Ve.forEach(M=>{window.removeEventListener(M,g)})}},[f]),(0,t.useEffect)(()=>{oe.current=s},[s]),(0,t.useEffect)(()=>{fe.current=a},[a]),(0,t.useEffect)(()=>{if(f)return;let e=setInterval(()=>{if(oe.current==="sleep"||$.current||fe.current||U.current)return;let n=["happy","angry","curious"],r=n[Math.floor(Math.random()*n.length)];ye(r)},3e4);return()=>{clearInterval(e)}},[f]),(0,t.useEffect)(()=>()=>{P.current&&clearTimeout(P.current),Z.current&&clearTimeout(Z.current),J.current&&clearTimeout(J.current)},[]),(0,t.useEffect)(()=>{if(!(s==="sleep"||!R||a||v)){if(s==="walk"){let e=Math.floor(Math.random()*175001)+5e3;ie.current=setTimeout(()=>{y("idle"),j.current=!0,setTimeout(()=>{j.current=!1},3e4)},e)}else{let e=()=>{if(!j.current&&Math.random()>.4){q.current=null,y("walk");return}ie.current=setTimeout(e,5e3)};e()}return()=>{ie.current&&clearTimeout(ie.current)}}},[s,R,a,v]);let S=e=>{T.current=e,V.current&&(V.current.style.transform=`translateX(${e}px)`)};(0,t.useEffect)(()=>{if(s!=="walk"||Q!==null||I!==null||!R||a||v)return;let e=0,n=B-Se;if(n<=e)return;let r=0,o=null,d=g=>{let M=o===null?0:Math.min(g-o,je);o=g;let N=T.current+(ge==="right"?1:-1)*Qe*M;if(N<=e||N>=n){if(Math.random()>.5){y("idle"),j.current=!0,setTimeout(()=>{j.current=!1},3e4);return}S(N<=e?e+2:n-2),Oe(ue=>ue==="right"?"left":"right");return}S(N),r=requestAnimationFrame(d)};return r=requestAnimationFrame(d),()=>cancelAnimationFrame(r)},[s,Q,I,ge,R,B,a,v]),(0,t.useEffect)(()=>{let e=L.current;if(c?.phase!=="walking"||!e)return;let n=Ae(c.at,e.clientWidth);if(!R){S(n),Ee(c);return}Oe(n>=T.current?"right":"left");let r=0,o=null,d=g=>{let M=o===null?0:Math.min(g-o,je);o=g;let N=n-T.current,ue=Qe*M;if(Math.abs(N)<=ue){S(n),Ee(c);return}S(T.current+Math.sign(N)*ue),r=requestAnimationFrame(d)};return r=requestAnimationFrame(d),()=>cancelAnimationFrame(r)},[c,B,R]),(0,t.useLayoutEffect)(()=>{let e=L.current;c?.phase!=="asleep"||!e||S(Ae(c.at,e.clientWidth))},[c,B]),(0,t.useEffect)(()=>{let e=L.current;if(!e)return;let n=o=>{q.current=null,ft(o),S(Math.max(Math.min(T.current,o-Se),0))};if(n(e.clientWidth),typeof ResizeObserver>"u")return;let r=new ResizeObserver(([o])=>{o&&n(o.contentRect.width)});return r.observe(e),()=>r.disconnect()},[]);let Te=e=>{$.current&&(ee(),_(null)),se.current=!1,He(!0),J.current&&clearTimeout(J.current),J.current=setTimeout(()=>We(!1),we),ye(e,!0)},Xe=e=>{let n=Date.now();if(n<$e.current)return;let r=[...be.current,n].filter(o=>n-o<=Nt);if(r.length>=Ft){be.current=[],$e.current=n+Wt,Te("angry");return}be.current=r,it(e),C(!0)},xt=e=>{if(u?.(e),e.defaultPrevented)return;if(c){gt(c);return}let n=Date.now();if(s==="sleep"&&y("idle"),H){a?C(!1):Xe("first");return}if(f){_({message:w?.greeting??Dt,emotion:null}),le.current=we,Re(we);return}let r=[...he.current,n].filter(o=>n-o<=1e4);he.current=r,r.length>5?(he.current=[],Te("angry")):Te(Be.current)},Ke=()=>{C(!1),ne.current?.focus()},ht=e=>{Ke(),e.onSelect()},bt=e=>{!H||c||(e.key==="Escape"&&a?(e.preventDefault(),e.stopPropagation(),C(!1)):(e.key==="ArrowDown"||e.key==="ArrowUp")&&!a&&(e.preventDefault(),Xe(e.key==="ArrowDown"?"first":"last")))},yt=e=>{e.pointerType!=="touch"&&(Fe(!0),$.current&&ee(),Q&&(se.current=!0,P.current&&clearTimeout(P.current)))},Et=e=>{e.pointerType!=="touch"&&(Fe(!1),He(!1),$.current&&Re(le.current),Q&&(se.current=!1,P.current&&clearTimeout(P.current),P.current=setTimeout(()=>{O(null)},5e3)))},ae=I!==null&&s!=="sleep"&&!v,F=s==="sleep"||a||v?null:ae?I.emotion:f?null:Q,Pe=!a&&(ae||F!==null&&(lt&&!ct||at||ut));(0,t.useLayoutEffect)(()=>{let e=L.current,n=V.current,r=Ce.current;if(!Pe||!e||!n||!r)return;let o=T.current+n.offsetWidth/2,{left:d,maxWidth:g}=Ze(e.clientWidth,o,r.offsetWidth);nt({left:d-T.current,maxWidth:g})},[Pe,F,I,B]),(0,t.useEffect)(()=>{if(!a)return;let e=n=>{let r=n.target;ce.current?.contains(r)||ne.current?.contains(r)||C(!1)};return document.addEventListener("pointerdown",e,!0),()=>document.removeEventListener("pointerdown",e,!0)},[a]),(0,t.useLayoutEffect)(()=>{let e=L.current,n=V.current,r=ce.current;if(!a||!e||!n||!r)return;let o=T.current+n.offsetWidth/2,{left:d,maxWidth:g}=Ze(e.clientWidth,o,r.offsetWidth);rt({left:d-T.current,maxWidth:g})},[a,W,B]);let Rt=()=>c?c.phase==="walking"?"pixel-pet-state-walk":"pixel-pet-state-sleep":a?"pixel-pet-state-idle":F?`pixel-pet-state-${F}`:(!R||ae)&&s==="walk"?"pixel-pet-state-idle":`pixel-pet-state-${s}`,Ge=e=>{let n=e?Bt[e]:$t,r=e&&De[e]||{},o=r.backgroundColor||h.backgroundColor||n.backgroundColor,d=r.borderColor||h.borderColor||n.borderColor;return{backgroundColor:o,color:r.textColor||h.textColor||"#ffffff",border:`${r.borderWidth||h.borderWidth||"1px"} solid ${d}`,borderRadius:r.borderRadius||h.borderRadius||"0",fontFamily:r.fontFamily||h.fontFamily||"monospace"}};return(0,k.jsx)("div",{className:"pixel-pet",ref:L,style:E?{"--pixel-pet-color":E}:void 0,children:(0,k.jsxs)("div",{ref:V,className:`pixel-pet-container${pt?" pixel-pet-startle":""}`,style:{transform:`translateX(${qe}px)`},onPointerEnter:yt,onPointerLeave:Et,children:[(0,k.jsx)("div",{className:"pixel-pet-status",role:"status",children:Pe&&(0,k.jsx)("div",{ref:Ce,className:"pixel-pet-dialog",style:{...Ge(F),...pe&&{left:pe.left,maxWidth:pe.maxWidth}},children:ae?re(I.message,I.emotion):F&&dt[F]})}),a&&(0,k.jsx)(ze,{ref:ce,id:Le,initialFocus:ot,label:xe(te?.menu??Lt),closeLabel:xe(te?.close??Ct),actions:W,style:{...Ge(null),...me&&{left:me.left,maxWidth:me.maxWidth}},onSelect:ht,onClose:Ke,restoreFocus:()=>ne.current?.focus()}),(0,k.jsx)("button",{ref:ne,type:"button","aria-label":xe(x??i),"aria-haspopup":H?"menu":void 0,"aria-expanded":H?a:void 0,"aria-controls":a?Le:void 0,className:`pixel-pet-sprite pixel-pet-${l} ${Rt()} ${ge==="left"?"pixel-pet-flip-x":""}`,style:p?{backgroundImage:`url("${p}")`}:void 0,onClick:xt,onKeyDown:bt,onFocus:e=>Ne(e.currentTarget.matches(":focus-visible")),onBlur:()=>Ne(!1)})]})})};0&&(module.exports={PixelPet});
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
1
  'use client';
2
- import{useState as a,useEffect as d,useImperativeHandle as _e,useLayoutEffect as Ae,useRef as r,useSyncExternalStore as Ie}from"react";import{jsx as x,jsxs as Ge}from"react/jsx-runtime";var re=["pointermove","pointerdown","keydown","wheel"],le="(prefers-reduced-motion: reduce)",V=4,Le=240,ie=5e3,Fe=3e3,Ne=/(\{name\}|\*\*[^*]+\*\*|__[^_]+__)/,Y={happy:"{name} is feeling **happy**!",angry:"{name} is feeling **overwhelmed**!",curious:"{name} is very **curious**"},se=65,ae=100,We=.04,He=100,Oe=f=>{if(typeof window.matchMedia!="function")return()=>{};let M=window.matchMedia(le);return M.addEventListener("change",f),()=>M.removeEventListener("change",f)},Be=()=>typeof window.matchMedia=="function"&&window.matchMedia(le).matches,Ue=()=>Ie(Oe,Be,()=>!1),$e={happy:{backgroundColor:"#1e1c06eb",borderColor:"#ffeb3b"},angry:{backgroundColor:"#210908eb",borderColor:"#F04037"},curious:{backgroundColor:"#1f341deb",borderColor:"#8BE383"}},Xe={backgroundColor:"#05080ceb",borderColor:"var(--pixel-pet-color, #FDDBA0)"},je=({petName:f="Static",variant:M="cat",spriteSrc:K,color:q,label:ce,walk:pe=!0,messages:L,dialogStyle:m={},emotionDialogStyles:ue={},onClick:de,ref:fe})=>{let[i,l]=a("idle"),me=Ue(),h=pe&&!me,F=r(null),w=r(null),Q=r(null),[N,ge]=a(null),[b,C]=a(null),[xe,j]=a(!1),[he,J]=a(!1),[be,Z]=a(!1),[ye,ee]=a(!1),[c,Ee]=a(null),y=r(ae),[W,Re]=a(0),[H,Te]=a("right"),S=(e,o)=>typeof e!="string"?e:e.split(Ne).map((t,n)=>n%2===0?t:t==="{name}"||t.startsWith("__")?x("span",{className:"pixel-pet-highlight-pet",children:t==="{name}"?f:t.slice(2,-2)},n):x("span",{className:`pixel-pet-highlight-${o??"pet"}`,children:t.slice(2,-2)},n)),ke={happy:S(L?.happy??Y.happy,"happy"),angry:S(L?.angry??Y.angry,"angry"),curious:S(L?.curious??Y.curious,"curious")},te=r(i),D=r(null),E=r(!1),_=r(!1),s=r(null),oe=r("happy"),O=r([]),R=r(null),T=r(!1),k=r(null),B=r(ie),U=(e,o=!1)=>{i==="sleep"&&l("idle"),oe.current=e,C(e),Z(o),s.current&&clearTimeout(s.current),s.current=setTimeout(()=>{_.current||C(null)},5e3)},P=e=>{T.current=e!==null,Ee(e)},A=()=>{k.current&&clearTimeout(k.current)},ne=e=>{A(),e!==1/0&&(k.current=setTimeout(()=>P(null),e))};_e(fe,()=>({say:(e,o={})=>{l(t=>t==="sleep"?"idle":t),P({message:e,emotion:o.emotion??null}),B.current=o.duration??ie,ne(B.current)},hush:()=>{A(),P(null)}}),[]),d(()=>{let e,o=()=>{l(t=>t==="sleep"?"idle":t),clearTimeout(e),e=setTimeout(()=>{C(null),P(null),l("sleep")},12e4)};return re.forEach(t=>{window.addEventListener(t,o,{passive:!0})}),o(),()=>{clearTimeout(e),re.forEach(t=>{window.removeEventListener(t,o)})}},[]),d(()=>{te.current=i},[i]),d(()=>{let e=setInterval(()=>{if(te.current==="sleep"||T.current)return;let o=["happy","angry","curious"],t=o[Math.floor(Math.random()*o.length)];U(t)},3e4);return()=>{clearInterval(e)}},[]),d(()=>()=>{s.current&&clearTimeout(s.current),k.current&&clearTimeout(k.current),R.current&&clearTimeout(R.current)},[]),d(()=>{if(!(i==="sleep"||!h)){if(i==="walk"){let e=Math.floor(Math.random()*175001)+5e3;D.current=setTimeout(()=>{l("idle"),E.current=!0,setTimeout(()=>{E.current=!1},3e4)},e)}else{let e=()=>{if(!E.current&&Math.random()>.4){l("walk");return}D.current=setTimeout(e,5e3)};e()}return()=>{D.current&&clearTimeout(D.current)}}},[i,h]);let $=e=>{y.current=e,w.current&&(w.current.style.transform=`translateX(${e}px)`)};d(()=>{if(i!=="walk"||b!==null||c!==null||!h)return;let e=0,o=W-se;if(o<=e)return;let t=0,n=null,u=v=>{let G=n===null?0:Math.min(v-n,He);n=v;let g=y.current+(H==="right"?1:-1)*We*G;if(g<=e||g>=o){if(Math.random()>.5){l("idle"),E.current=!0,setTimeout(()=>{E.current=!1},3e4);return}$(g<=e?e+2:o-2),Te(z=>z==="right"?"left":"right");return}$(g),t=requestAnimationFrame(u)};return t=requestAnimationFrame(u),()=>cancelAnimationFrame(t)},[i,b,c,H,h,W]),d(()=>{let e=F.current;if(!e)return;let o=n=>{Re(n),$(Math.max(Math.min(y.current,n-se),0))};if(o(e.clientWidth),typeof ResizeObserver>"u")return;let t=new ResizeObserver(([n])=>{n&&o(n.contentRect.width)});return t.observe(e),()=>t.disconnect()},[]);let Pe=e=>{if(de?.(e),e.defaultPrevented)return;let o=Date.now();i==="sleep"&&l("idle"),T.current&&(A(),P(null)),_.current=!1,ee(!0),R.current&&clearTimeout(R.current),R.current=setTimeout(()=>Z(!1),Fe);let t=[...O.current,o].filter(n=>o-n<=1e4);O.current=t,t.length>5?(O.current=[],U("angry",!0)):U(oe.current,!0)},ve=()=>{j(!0),T.current&&A(),b&&(_.current=!0,s.current&&clearTimeout(s.current))},Me=()=>{j(!1),ee(!1),T.current&&ne(B.current),b&&(_.current=!1,s.current&&clearTimeout(s.current),s.current=setTimeout(()=>{C(null)},5e3))},I=c!==null&&i!=="sleep",p=i==="sleep"?null:I?c.emotion:b,X=I||p!==null&&(xe&&!ye||he||be);Ae(()=>{let e=F.current,o=w.current,t=Q.current;if(!X||!e||!o||!t)return;let n=e.clientWidth,u=Math.max(Math.min(Le,n-V*2),0),v=Math.min(t.offsetWidth,u),G=y.current+o.offsetWidth/2,g=V,z=n-V-v,Se=G-v/2,De=Math.max(Math.min(Se,z),g);ge({left:De-y.current,maxWidth:u})},[X,p,c,W]);let we=()=>p?`pixel-pet-state-${p}`:(!h||I)&&i==="walk"?"pixel-pet-state-idle":`pixel-pet-state-${i}`,Ce=e=>{let o=e?$e[e]:Xe,t=e&&ue[e]||{},n=t.backgroundColor||m.backgroundColor||o.backgroundColor,u=t.borderColor||m.borderColor||o.borderColor;return{backgroundColor:n,color:t.textColor||m.textColor||"#ffffff",border:`${t.borderWidth||m.borderWidth||"1px"} solid ${u}`,borderRadius:t.borderRadius||m.borderRadius||"0",fontFamily:t.fontFamily||m.fontFamily||"monospace"}};return x("div",{className:"pixel-pet",ref:F,style:q?{"--pixel-pet-color":q}:void 0,children:Ge("div",{ref:w,className:"pixel-pet-container",style:{transform:`translateX(${ae}px)`},onMouseEnter:ve,onMouseLeave:Me,children:[x("div",{className:"pixel-pet-status",role:"status",children:X&&x("div",{ref:Q,className:"pixel-pet-dialog",style:{...Ce(p),...N&&{left:N.left,maxWidth:N.maxWidth}},children:I?S(c.message,c.emotion):p&&ke[p]})}),x("button",{type:"button","aria-label":(ce??f).split("{name}").join(f),className:`pixel-pet-sprite pixel-pet-${M} ${we()} ${H==="left"?"pixel-pet-flip-x":""}`,style:K?{backgroundImage:`url("${K}")`}:void 0,onClick:Pe,onFocus:e=>J(e.currentTarget.matches(":focus-visible")),onBlur:()=>J(!1)})]})})};export{je as PixelPet};
2
+ import{useState as u,useEffect as b,useId as vt,useImperativeHandle as At,useLayoutEffect as ne,useRef as o,useSyncExternalStore as St}from"react";import{useEffect as Pt,useLayoutEffect as Mt,useRef as kt}from"react";import{jsx as te,jsxs as wt}from"react/jsx-runtime";var Ve=({id:c,initialFocus:y,label:M,closeLabel:S,actions:W,style:K,onSelect:G,onClose:k,restoreFocus:H,ref:re})=>{let p=kt(null),z=()=>[...p.current?.querySelectorAll('[role="menuitem"]')??[]];Pt(()=>{let l=z();l[y==="last"?l.length-1:0]?.focus()},[]),Mt(()=>{let l=p.current?.parentElement;return()=>{l?.contains(document.activeElement)&&H()}},[]);let g=l=>{let x=z(),i=x.indexOf(document.activeElement);x[l(i,x.length)]?.focus()};return wt("div",{ref:re,className:"pixel-pet-menu",style:K,onKeyDown:l=>{switch(l.key){case"Escape":l.stopPropagation(),k();break;case"Tab":k();break;case"ArrowDown":g((x,i)=>(x+1)%i);break;case"ArrowUp":g((x,i)=>x<=0?i-1:x-1);break;case"Home":g(()=>0);break;case"End":g((x,i)=>i-1);break;default:return}l.preventDefault()},children:[te("button",{type:"button",className:"pixel-pet-menu-close","aria-label":S,tabIndex:-1,onClick:k,children:te("span",{"aria-hidden":"true",children:"\xD7"})}),te("ul",{ref:p,id:c,role:"menu","aria-label":M,className:"pixel-pet-menu-list",children:W.map(l=>te("li",{role:"none",children:te("button",{type:"button",role:"menuitem",tabIndex:-1,className:"pixel-pet-menu-item",onClick:()=>G(l),children:l.label})},l.id))})]})};import{jsx as N,jsxs as Gt}from"react/jsx-runtime";var Ye=["pointermove","pointerdown","keydown","wheel"],tt="(prefers-reduced-motion: reduce)",we=4,Dt=240,qe=5e3,ve=3e3,Lt=/(\{name\}|\*\*[^*]+\*\*|__[^_]+__)/,Ae={happy:"{name} is feeling **happy**!",angry:"{name} is feeling **overwhelmed**!",curious:"{name} is very **curious**"},Ct="Hi, I'm {name}!",It="{name} actions",_t="Close menu",De=65,Qe=100,je=.04,Je=100,Ze=12e4,Ft=2500,Nt=300,Wt=4,Ht=5e3,Ut=2e3,Ot=c=>{if(typeof window.matchMedia!="function")return()=>{};let y=window.matchMedia(tt);return y.addEventListener("change",c),()=>y.removeEventListener("change",c)},Bt=()=>typeof window.matchMedia=="function"&&window.matchMedia(tt).matches,$t=()=>St(Ot,Bt,()=>!1),Se=(c,y)=>{let M=Math.max(y-De,0);return c==="left"?0:c==="right"?M:M/2},et=(c,y,M)=>{let S=Math.max(Math.min(Dt,c-we*2),0),W=Math.min(M,S),K=we,G=c-we-W;return{left:Math.max(Math.min(y-W/2,G),K),maxWidth:S}},Xt={happy:{backgroundColor:"#1e1c06eb",borderColor:"#ffeb3b"},angry:{backgroundColor:"#210908eb",borderColor:"#F04037"},curious:{backgroundColor:"#1f341deb",borderColor:"#8BE383"}},Kt={backgroundColor:"#05080ceb",borderColor:"var(--pixel-pet-color, #FDDBA0)"},on=({petName:c="Minerva",variant:y="cat",spriteSrc:M,color:S,label:W,mode:K="live",walk:G=!0,messages:k,actions:H,menuLabels:re,resting:p,onRestingChange:z,dialogStyle:g={},emotionDialogStyles:Le={},onClick:l,ref:x})=>{let[i,h]=u("idle"),nt=$t(),m=K==="showcase",E=G&&!nt&&!m,U=!m&&H!==void 0&&H.length>0,Ce=vt(),D=o(null),V=o(null),oe=o(null),Ie=o(null),me=o(null),[fe,rt]=u(null),[de,ot]=u(null),[_e,L]=u(!1);!U&&_e&&L(!1);let[it,st]=u("first"),s=U&&_e,ge=o(s),Y=p!==void 0,[a,lt]=u(()=>Y&&p!==null&&!m?{at:p,phase:"asleep",origin:"prop"}:null),O=o(a),w=a!==null,q=o(null),Fe=o(p),xe=o(z),[Q,B]=u(null),[at,Ne]=u(!1),[ut,We]=u(!1),[ct,He]=u(!1),[pt,Ue]=u(!1),[mt,Oe]=u(!1),[C,ft]=u(null),R=o(Qe),[$,dt]=u(0),[he,Be]=u("right"),be=e=>e.split("{name}").join(c),ie=(e,t)=>typeof e!="string"?e:e.split(Lt).map((n,r)=>r%2===0?n:n==="{name}"||n.startsWith("__")?N("span",{className:"pixel-pet-highlight-pet",children:n==="{name}"?c:n.slice(2,-2)},r):N("span",{className:`pixel-pet-highlight-${t??"pet"}`,children:n.slice(2,-2)},r)),gt={happy:ie(k?.happy??Ae.happy,"happy"),angry:ie(k?.angry??Ae.angry,"angry"),curious:ie(k?.curious??Ae.curious,"curious")},se=o(i),le=o(null),j=o(!1),ae=o(!1),T=o(null),$e=o("happy"),ye=o([]),J=o(null),Ee=o([]),Xe=o(0),X=o(!1),Z=o(null),ue=o(qe),Re=(e,t=!1)=>{se.current==="sleep"&&h("idle"),$e.current=e,B(e),He(t),T.current&&clearTimeout(T.current),T.current=setTimeout(()=>{ae.current||B(null)},5e3)},I=e=>{X.current=e!==null,ft(e)},v=e=>{O.current=e,q.current=null,lt(e)},Te=({at:e,origin:t})=>{if(t==="prop"){v({at:e,phase:"asleep",origin:t});return}Y?(v(null),q.current=e):v({at:e,phase:"asleep",origin:t}),xe.current?.(e)},xt=e=>{if(e.phase==="walking"&&e.origin==="method"){v(null);return}Y||v(null),xe.current?.(null)},ee=()=>{Z.current&&clearTimeout(Z.current)},Pe=e=>{ee(),e!==1/0&&(Z.current=setTimeout(()=>I(null),e))};At(x,()=>({say:(e,t={})=>{O.current||(h(n=>n==="sleep"?"idle":n),I({message:e,emotion:t.emotion??null}),ue.current=t.duration??qe,Pe(ue.current))},hush:()=>{ee(),I(null)},emote:e=>{m||O.current||Re(e)},rest:e=>{if(m||O.current)return;let t={at:e,phase:"walking",origin:"method"};if(E){v(t);return}A(Se(e,D.current?.clientWidth??0)),Te(t)}}),[m,E,Y]),ne(()=>{xe.current=z}),ne(()=>{if(Fe.current===p||(Fe.current=p,!Y||m))return;if(p===null){v(null);return}let e=q.current===p||!E;v({at:p,phase:e?"asleep":"walking",origin:"prop"})},[p]),b(()=>{w&&(ee(),I(null),B(null),L(!1),h("idle"))},[w]),b(()=>{let e,t,n,r=()=>{B(null),I(null),h("sleep")},f=()=>{e=setTimeout(f,Ze),!(O.current||ge.current||X.current)&&(B(null),h("sleep"),t=setTimeout(()=>{se.current==="sleep"&&(h("idle"),Oe(!0),n=setTimeout(()=>Oe(!1),Nt))},Ft))},d=()=>{m&&h(P=>P==="sleep"?"idle":P),clearTimeout(e),e=setTimeout(m?r:f,Ze)};return Ye.forEach(P=>{window.addEventListener(P,d,{passive:!0})}),d(),()=>{clearTimeout(e),clearTimeout(t),clearTimeout(n),Ye.forEach(P=>{window.removeEventListener(P,d)})}},[m]),b(()=>{se.current=i},[i]),b(()=>{ge.current=s},[s]),b(()=>{if(m)return;let e=setInterval(()=>{if(se.current==="sleep"||X.current||ge.current||O.current)return;let t=["happy","angry","curious"],n=t[Math.floor(Math.random()*t.length)];Re(n)},3e4);return()=>{clearInterval(e)}},[m]),b(()=>()=>{T.current&&clearTimeout(T.current),Z.current&&clearTimeout(Z.current),J.current&&clearTimeout(J.current)},[]),b(()=>{if(!(i==="sleep"||!E||s||w)){if(i==="walk"){let e=Math.floor(Math.random()*175001)+5e3;le.current=setTimeout(()=>{h("idle"),j.current=!0,setTimeout(()=>{j.current=!1},3e4)},e)}else{let e=()=>{if(!j.current&&Math.random()>.4){q.current=null,h("walk");return}le.current=setTimeout(e,5e3)};e()}return()=>{le.current&&clearTimeout(le.current)}}},[i,E,s,w]);let A=e=>{R.current=e,V.current&&(V.current.style.transform=`translateX(${e}px)`)};b(()=>{if(i!=="walk"||Q!==null||C!==null||!E||s||w)return;let e=0,t=$-De;if(t<=e)return;let n=0,r=null,f=d=>{let P=r===null?0:Math.min(d-r,Je);r=d;let F=R.current+(he==="right"?1:-1)*je*P;if(F<=e||F>=t){if(Math.random()>.5){h("idle"),j.current=!0,setTimeout(()=>{j.current=!1},3e4);return}A(F<=e?e+2:t-2),Be(pe=>pe==="right"?"left":"right");return}A(F),n=requestAnimationFrame(f)};return n=requestAnimationFrame(f),()=>cancelAnimationFrame(n)},[i,Q,C,he,E,$,s,w]),b(()=>{let e=D.current;if(a?.phase!=="walking"||!e)return;let t=Se(a.at,e.clientWidth);if(!E){A(t),Te(a);return}Be(t>=R.current?"right":"left");let n=0,r=null,f=d=>{let P=r===null?0:Math.min(d-r,Je);r=d;let F=t-R.current,pe=je*P;if(Math.abs(F)<=pe){A(t),Te(a);return}A(R.current+Math.sign(F)*pe),n=requestAnimationFrame(f)};return n=requestAnimationFrame(f),()=>cancelAnimationFrame(n)},[a,$,E]),ne(()=>{let e=D.current;a?.phase!=="asleep"||!e||A(Se(a.at,e.clientWidth))},[a,$]),b(()=>{let e=D.current;if(!e)return;let t=r=>{q.current=null,dt(r),A(Math.max(Math.min(R.current,r-De),0))};if(t(e.clientWidth),typeof ResizeObserver>"u")return;let n=new ResizeObserver(([r])=>{r&&t(r.contentRect.width)});return n.observe(e),()=>n.disconnect()},[]);let Me=e=>{X.current&&(ee(),I(null)),ae.current=!1,Ue(!0),J.current&&clearTimeout(J.current),J.current=setTimeout(()=>He(!1),ve),Re(e,!0)},Ke=e=>{let t=Date.now();if(t<Xe.current)return;let n=[...Ee.current,t].filter(r=>t-r<=Ht);if(n.length>=Wt){Ee.current=[],Xe.current=t+Ut,Me("angry");return}Ee.current=n,st(e),L(!0)},ht=e=>{if(l?.(e),e.defaultPrevented)return;if(a){xt(a);return}let t=Date.now();if(i==="sleep"&&h("idle"),U){s?L(!1):Ke("first");return}if(m){I({message:k?.greeting??Ct,emotion:null}),ue.current=ve,Pe(ve);return}let n=[...ye.current,t].filter(r=>t-r<=1e4);ye.current=n,n.length>5?(ye.current=[],Me("angry")):Me($e.current)},Ge=()=>{L(!1),oe.current?.focus()},bt=e=>{Ge(),e.onSelect()},yt=e=>{!U||a||(e.key==="Escape"&&s?(e.preventDefault(),e.stopPropagation(),L(!1)):(e.key==="ArrowDown"||e.key==="ArrowUp")&&!s&&(e.preventDefault(),Ke(e.key==="ArrowDown"?"first":"last")))},Et=e=>{e.pointerType!=="touch"&&(Ne(!0),X.current&&ee(),Q&&(ae.current=!0,T.current&&clearTimeout(T.current)))},Rt=e=>{e.pointerType!=="touch"&&(Ne(!1),Ue(!1),X.current&&Pe(ue.current),Q&&(ae.current=!1,T.current&&clearTimeout(T.current),T.current=setTimeout(()=>{B(null)},5e3)))},ce=C!==null&&i!=="sleep"&&!w,_=i==="sleep"||s||w?null:ce?C.emotion:m?null:Q,ke=!s&&(ce||_!==null&&(at&&!pt||ut||ct));ne(()=>{let e=D.current,t=V.current,n=Ie.current;if(!ke||!e||!t||!n)return;let r=R.current+t.offsetWidth/2,{left:f,maxWidth:d}=et(e.clientWidth,r,n.offsetWidth);rt({left:f-R.current,maxWidth:d})},[ke,_,C,$]),b(()=>{if(!s)return;let e=t=>{let n=t.target;me.current?.contains(n)||oe.current?.contains(n)||L(!1)};return document.addEventListener("pointerdown",e,!0),()=>document.removeEventListener("pointerdown",e,!0)},[s]),ne(()=>{let e=D.current,t=V.current,n=me.current;if(!s||!e||!t||!n)return;let r=R.current+t.offsetWidth/2,{left:f,maxWidth:d}=et(e.clientWidth,r,n.offsetWidth);ot({left:f-R.current,maxWidth:d})},[s,H,$]);let Tt=()=>a?a.phase==="walking"?"pixel-pet-state-walk":"pixel-pet-state-sleep":s?"pixel-pet-state-idle":_?`pixel-pet-state-${_}`:(!E||ce)&&i==="walk"?"pixel-pet-state-idle":`pixel-pet-state-${i}`,ze=e=>{let t=e?Xt[e]:Kt,n=e&&Le[e]||{},r=n.backgroundColor||g.backgroundColor||t.backgroundColor,f=n.borderColor||g.borderColor||t.borderColor;return{backgroundColor:r,color:n.textColor||g.textColor||"#ffffff",border:`${n.borderWidth||g.borderWidth||"1px"} solid ${f}`,borderRadius:n.borderRadius||g.borderRadius||"0",fontFamily:n.fontFamily||g.fontFamily||"monospace"}};return N("div",{className:"pixel-pet",ref:D,style:S?{"--pixel-pet-color":S}:void 0,children:Gt("div",{ref:V,className:`pixel-pet-container${mt?" pixel-pet-startle":""}`,style:{transform:`translateX(${Qe}px)`},onPointerEnter:Et,onPointerLeave:Rt,children:[N("div",{className:"pixel-pet-status",role:"status",children:ke&&N("div",{ref:Ie,className:"pixel-pet-dialog",style:{...ze(_),...fe&&{left:fe.left,maxWidth:fe.maxWidth}},children:ce?ie(C.message,C.emotion):_&&gt[_]})}),s&&N(Ve,{ref:me,id:Ce,initialFocus:it,label:be(re?.menu??It),closeLabel:be(re?.close??_t),actions:H,style:{...ze(null),...de&&{left:de.left,maxWidth:de.maxWidth}},onSelect:bt,onClose:Ge,restoreFocus:()=>oe.current?.focus()}),N("button",{ref:oe,type:"button","aria-label":be(W??c),"aria-haspopup":U?"menu":void 0,"aria-expanded":U?s:void 0,"aria-controls":s?Ce:void 0,className:`pixel-pet-sprite pixel-pet-${y} ${Tt()} ${he==="left"?"pixel-pet-flip-x":""}`,style:M?{backgroundImage:`url("${M}")`}:void 0,onClick:ht,onKeyDown:yt,onFocus:e=>We(e.currentTarget.matches(":focus-visible")),onBlur:()=>We(!1)})]})})};export{on as PixelPet};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrixton/pixel-pet",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "A pixel art pet for React that walks, sleeps, reacts to clicks, and speaks on demand.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -24,18 +24,10 @@
24
24
  "sideEffects": [
25
25
  "**/*.css"
26
26
  ],
27
- "scripts": {
28
- "build": "tsup",
29
- "dev": "tsup --watch",
30
- "playground": "vite",
31
- "test": "vitest run",
32
- "test:watch": "vitest",
33
- "prepublishOnly": "pnpm test && pnpm build"
34
- },
35
27
  "peerDependencies": {
36
28
  "react": "^19"
37
29
  },
38
- "devDependencies": {
30
+ "devDependencies": {
39
31
  "@testing-library/jest-dom": "^7.0.1",
40
32
  "@testing-library/react": "^16.3.3",
41
33
  "@testing-library/user-event": "^14.6.7",
@@ -69,8 +61,14 @@
69
61
  "publishConfig": {
70
62
  "access": "public"
71
63
  },
72
- "packageManager": "pnpm@12.5.1",
73
64
  "engines": {
74
65
  "node": ">=24"
66
+ },
67
+ "scripts": {
68
+ "build": "tsup",
69
+ "dev": "tsup --watch",
70
+ "playground": "vite",
71
+ "test": "vitest run",
72
+ "test:watch": "vitest"
75
73
  }
76
- }
74
+ }
Binary file