@vrixton/pixel-pet 0.2.1 → 0.4.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
@@ -75,8 +75,9 @@ Call `pet.current?.hush()` to close the current message right away, for example
75
75
  | `spriteSrc` | `string` | none | A URL for your own sprite sheet. It takes priority over `variant`. |
76
76
  | `color` | `string` | `'#FDDBA0'` | The pet's main color, used for its name and for the border of neutral speech bubbles. |
77
77
  | `label` | `string` | `petName` | The accessible name of the pet's button. |
78
+ | `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
79
  | `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. |
80
+ | `messages` | `PetMessages` | English messages | Replaces the text shown for each emotion and the showcase greeting. |
80
81
  | `dialogStyle` | `DialogStyleProps` | none | Styles for every bubble. |
81
82
  | `emotionDialogStyles` | `Partial<Record<PetEmotion, DialogStyleProps>>` | none | Styles for the bubble of a single emotion. |
82
83
  | `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. |
@@ -111,11 +112,12 @@ Messages can be plain strings with two markers, so they fit in translation files
111
112
  happy: '{name} está **feliz**.',
112
113
  angry: '{name} está **abrumado**.',
113
114
  curious: '{name} tiene mucha **curiosidad**.',
115
+ greeting: '¡Hola, soy {name}!',
114
116
  }}
115
117
  />
116
118
  ```
117
119
 
118
- `{name}` becomes the pet's name in its color, and `**word**` shows that word in the color of the emotion. The markers also work in `say()`: there, `**word**` takes the color of the requested emotion, or the pet's color when there is none. In `label`, which screen readers read aloud, only `{name}` is replaced. Any emotion you leave out keeps its English message.
120
+ `{name}` becomes the pet's name in its color, `__word__` shows any word in the pet's color, and `**word**` shows that word in the color of the emotion. If your translation system already fills in `{name}` itself, wrap it as `__{name}__`, so the pet receives `__Bit__` and still paints the name. The markers also work in `say()`: there, `**word**` takes the color of the requested emotion, or the pet's color when there is none. In `label`, which screen readers read aloud, only `{name}` is replaced. Any emotion you leave out keeps its English message.
119
121
 
120
122
  Messages can also be any React node when you need more control. The highlight classes are `pixel-pet-highlight-pet`, `pixel-pet-highlight-happy`, `pixel-pet-highlight-angry`, and `pixel-pet-highlight-curious`.
121
123
 
@@ -144,6 +146,8 @@ A sheet passed through `spriteSrc` has to follow the same layout as the bundled
144
146
 
145
147
  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.
146
148
 
149
+ 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 `say()` and `hush()` keep working.
150
+
147
151
  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.
148
152
 
149
153
  ## Development
package/dist/index.d.mts CHANGED
@@ -11,7 +11,8 @@ interface DialogStyleProps {
11
11
  type PetEmotion = 'happy' | 'angry' | 'curious';
12
12
  type PetAction = 'idle' | 'walk' | 'sleep';
13
13
  type PetVariant = 'cat' | 'dog' | 'axolotl' | 'parakeet' | 'hamster';
14
- type PetMessages = Partial<Record<PetEmotion, React.ReactNode>>;
14
+ type PetMode = 'live' | 'showcase';
15
+ type PetMessages = Partial<Record<PetEmotion | 'greeting', React.ReactNode>>;
15
16
  interface PetSayOptions {
16
17
  emotion?: PetEmotion;
17
18
  duration?: number;
@@ -26,6 +27,7 @@ interface PixelPetProps {
26
27
  spriteSrc?: string;
27
28
  color?: string;
28
29
  label?: string;
30
+ mode?: PetMode;
29
31
  walk?: boolean;
30
32
  messages?: PetMessages;
31
33
  dialogStyle?: DialogStyleProps;
@@ -35,4 +37,4 @@ interface PixelPetProps {
35
37
  }
36
38
  declare const PixelPet: React.FC<PixelPetProps>;
37
39
 
38
- export { type DialogStyleProps, type PetAction, type PetEmotion, type PetMessages, type PetSayOptions, type PetVariant, PixelPet, type PixelPetHandle, type PixelPetProps };
40
+ export { type DialogStyleProps, type PetAction, type PetEmotion, type PetMessages, type PetMode, type PetSayOptions, type PetVariant, PixelPet, type PixelPetHandle, type PixelPetProps };
package/dist/index.d.ts CHANGED
@@ -11,7 +11,8 @@ interface DialogStyleProps {
11
11
  type PetEmotion = 'happy' | 'angry' | 'curious';
12
12
  type PetAction = 'idle' | 'walk' | 'sleep';
13
13
  type PetVariant = 'cat' | 'dog' | 'axolotl' | 'parakeet' | 'hamster';
14
- type PetMessages = Partial<Record<PetEmotion, React.ReactNode>>;
14
+ type PetMode = 'live' | 'showcase';
15
+ type PetMessages = Partial<Record<PetEmotion | 'greeting', React.ReactNode>>;
15
16
  interface PetSayOptions {
16
17
  emotion?: PetEmotion;
17
18
  duration?: number;
@@ -26,6 +27,7 @@ interface PixelPetProps {
26
27
  spriteSrc?: string;
27
28
  color?: string;
28
29
  label?: string;
30
+ mode?: PetMode;
29
31
  walk?: boolean;
30
32
  messages?: PetMessages;
31
33
  dialogStyle?: DialogStyleProps;
@@ -35,4 +37,4 @@ interface PixelPetProps {
35
37
  }
36
38
  declare const PixelPet: React.FC<PixelPetProps>;
37
39
 
38
- export { type DialogStyleProps, type PetAction, type PetEmotion, type PetMessages, type PetSayOptions, type PetVariant, PixelPet, type PixelPetHandle, type PixelPetProps };
40
+ export { type DialogStyleProps, type PetAction, type PetEmotion, type PetMessages, type PetMode, type PetSayOptions, type PetVariant, PixelPet, type PixelPetHandle, type PixelPetProps };
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 Ae=Object.getOwnPropertyNames;var Ie=Object.prototype.hasOwnProperty;var Le=(i,s)=>{for(var u in s)Y(i,u,{get:s[u],enumerable:!0})},_e=(i,s,u,b)=>{if(s&&typeof s=="object"||typeof s=="function")for(let d of Ae(s))!Ie.call(i,d)&&d!==u&&Y(i,d,{get:()=>s[d],enumerable:!(b=De(s,d))||b.enumerable});return i};var Fe=i=>_e(Y({},"__esModule",{value:!0}),i);var Ye={};Le(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}"?(0,c.jsx)("span",{className:"pixel-pet-highlight-pet",children:i},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),A=(0,t.useRef)(null),T=(0,t.useRef)(!1),I=(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(()=>{I.current||S(null)},5e3)},M=e=>{P.current=e!==null,ye(e)},L=()=>{v.current&&clearTimeout(v.current)},ne=e=>{L(),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:()=>{L(),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;A.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}A.current=setTimeout(e,5e3)};e()}return()=>{A.current&&clearTimeout(A.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&&(L(),M(null)),I.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&&L(),E&&(I.current=!0,l.current&&clearTimeout(l.current))},ve=()=>{j(!1),ee(!1),P.current&&ne(U.current),E&&(I.current=!1,l.current&&clearTimeout(l.current),l.current=setTimeout(()=>{S(null)},5e3))},_=f!==null&&a!=="sleep",m=a==="sleep"?null:_?f.emotion:E,G=_||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||_)&&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:_?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 q=Object.defineProperty;var Ae=Object.getOwnPropertyDescriptor;var Le=Object.getOwnPropertyNames;var Fe=Object.prototype.hasOwnProperty;var Ne=(i,s)=>{for(var u in s)q(i,u,{get:s[u],enumerable:!0})},We=(i,s,u,y)=>{if(s&&typeof s=="object"||typeof s=="function")for(let d of Le(s))!Fe.call(i,d)&&d!==u&&q(i,d,{get:()=>s[d],enumerable:!(y=Ae(s,d))||y.enumerable});return i};var He=i=>We(q({},"__esModule",{value:!0}),i);var Qe={};Ne(Qe,{PixelPet:()=>qe});module.exports=He(Qe);var t=require("react");var c=require("react/jsx-runtime"),se=["pointermove","pointerdown","keydown","wheel"],pe="(prefers-reduced-motion: reduce)",Q=4,Oe=240,ae=5e3,j=3e3,Be=/(\{name\}|\*\*[^*]+\*\*|__[^_]+__)/,J={happy:"{name} is feeling **happy**!",angry:"{name} is feeling **overwhelmed**!",curious:"{name} is very **curious**"},Ue="Hi, I'm {name}!",le=65,ce=100,$e=.04,Ge=100,Xe=i=>{if(typeof window.matchMedia!="function")return()=>{};let s=window.matchMedia(pe);return s.addEventListener("change",i),()=>s.removeEventListener("change",i)},ze=()=>typeof window.matchMedia=="function"&&window.matchMedia(pe).matches,Ve=()=>(0,t.useSyncExternalStore)(Xe,ze,()=>!1),Ye={happy:{backgroundColor:"#1e1c06eb",borderColor:"#ffeb3b"},angry:{backgroundColor:"#210908eb",borderColor:"#F04037"},curious:{backgroundColor:"#1f341deb",borderColor:"#8BE383"}},Ke={backgroundColor:"#05080ceb",borderColor:"var(--pixel-pet-color, #FDDBA0)"},qe=({petName:i="Static",variant:s="cat",spriteSrc:u,color:y,label:d,mode:ue="live",walk:de=!0,messages:S,dialogStyle:x={},emotionDialogStyles:fe={},onClick:me,ref:ge})=>{let[a,p]=(0,t.useState)("idle"),xe=Ve(),E=ue==="showcase",T=de&&!xe&&!E,H=(0,t.useRef)(null),D=(0,t.useRef)(null),Z=(0,t.useRef)(null),[O,he]=(0,t.useState)(null),[R,I]=(0,t.useState)(null),[be,ee]=(0,t.useState)(!1),[ye,te]=(0,t.useState)(!1),[Ee,oe]=(0,t.useState)(!1),[Te,ne]=(0,t.useState)(!1),[f,Re]=(0,t.useState)(null),k=(0,t.useRef)(ce),[B,ke]=(0,t.useState)(0),[U,Pe]=(0,t.useState)("right"),_=(e,n)=>typeof e!="string"?e:e.split(Be).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)),ve={happy:_(S?.happy??J.happy,"happy"),angry:_(S?.angry??J.angry,"angry"),curious:_(S?.curious??J.curious,"curious")},re=(0,t.useRef)(a),A=(0,t.useRef)(null),P=(0,t.useRef)(!1),L=(0,t.useRef)(!1),l=(0,t.useRef)(null),ie=(0,t.useRef)("happy"),$=(0,t.useRef)([]),v=(0,t.useRef)(null),M=(0,t.useRef)(!1),w=(0,t.useRef)(null),F=(0,t.useRef)(ae),G=(e,n=!1)=>{a==="sleep"&&p("idle"),ie.current=e,I(e),oe(n),l.current&&clearTimeout(l.current),l.current=setTimeout(()=>{L.current||I(null)},5e3)},h=e=>{M.current=e!==null,Re(e)},N=()=>{w.current&&clearTimeout(w.current)},X=e=>{N(),e!==1/0&&(w.current=setTimeout(()=>h(null),e))};(0,t.useImperativeHandle)(ge,()=>({say:(e,n={})=>{p(o=>o==="sleep"?"idle":o),h({message:e,emotion:n.emotion??null}),F.current=n.duration??ae,X(F.current)},hush:()=>{N(),h(null)}}),[]),(0,t.useEffect)(()=>{let e,n=()=>{p(o=>o==="sleep"?"idle":o),clearTimeout(e),e=setTimeout(()=>{I(null),h(null),p("sleep")},12e4)};return se.forEach(o=>{window.addEventListener(o,n,{passive:!0})}),n(),()=>{clearTimeout(e),se.forEach(o=>{window.removeEventListener(o,n)})}},[]),(0,t.useEffect)(()=>{re.current=a},[a]),(0,t.useEffect)(()=>{if(E)return;let e=setInterval(()=>{if(re.current==="sleep"||M.current)return;let n=["happy","angry","curious"],o=n[Math.floor(Math.random()*n.length)];G(o)},3e4);return()=>{clearInterval(e)}},[E]),(0,t.useEffect)(()=>()=>{l.current&&clearTimeout(l.current),w.current&&clearTimeout(w.current),v.current&&clearTimeout(v.current)},[]),(0,t.useEffect)(()=>{if(!(a==="sleep"||!T)){if(a==="walk"){let e=Math.floor(Math.random()*175001)+5e3;A.current=setTimeout(()=>{p("idle"),P.current=!0,setTimeout(()=>{P.current=!1},3e4)},e)}else{let e=()=>{if(!P.current&&Math.random()>.4){p("walk");return}A.current=setTimeout(e,5e3)};e()}return()=>{A.current&&clearTimeout(A.current)}}},[a,T]);let z=e=>{k.current=e,D.current&&(D.current.style.transform=`translateX(${e}px)`)};(0,t.useEffect)(()=>{if(a!=="walk"||R!==null||f!==null||!T)return;let e=0,n=B-le;if(n<=e)return;let o=0,r=null,g=C=>{let Y=r===null?0:Math.min(C-r,Ge);r=C;let b=k.current+(U==="right"?1:-1)*$e*Y;if(b<=e||b>=n){if(Math.random()>.5){p("idle"),P.current=!0,setTimeout(()=>{P.current=!1},3e4);return}z(b<=e?e+2:n-2),Pe(K=>K==="right"?"left":"right");return}z(b),o=requestAnimationFrame(g)};return o=requestAnimationFrame(g),()=>cancelAnimationFrame(o)},[a,R,f,U,T,B]),(0,t.useEffect)(()=>{let e=H.current;if(!e)return;let n=r=>{ke(r),z(Math.max(Math.min(k.current,r-le),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 Me=e=>{if(me?.(e),e.defaultPrevented)return;let n=Date.now();if(a==="sleep"&&p("idle"),E){h({message:S?.greeting??Ue,emotion:null}),F.current=j,X(j);return}M.current&&(N(),h(null)),L.current=!1,ne(!0),v.current&&clearTimeout(v.current),v.current=setTimeout(()=>oe(!1),j);let o=[...$.current,n].filter(r=>n-r<=1e4);$.current=o,o.length>5?($.current=[],G("angry",!0)):G(ie.current,!0)},we=()=>{ee(!0),M.current&&N(),R&&(L.current=!0,l.current&&clearTimeout(l.current))},Ce=()=>{ee(!1),ne(!1),M.current&&X(F.current),R&&(L.current=!1,l.current&&clearTimeout(l.current),l.current=setTimeout(()=>{I(null)},5e3))},W=f!==null&&a!=="sleep",m=a==="sleep"?null:W?f.emotion:E?null:R,V=W||m!==null&&(be&&!Te||ye||Ee);(0,t.useLayoutEffect)(()=>{let e=H.current,n=D.current,o=Z.current;if(!V||!e||!n||!o)return;let r=e.clientWidth,g=Math.max(Math.min(Oe,r-Q*2),0),C=Math.min(o.offsetWidth,g),Y=k.current+n.offsetWidth/2,b=Q,K=r-Q-C,Ie=Y-C/2,_e=Math.max(Math.min(Ie,K),b);he({left:_e-k.current,maxWidth:g})},[V,m,f,B]);let Se=()=>m?`pixel-pet-state-${m}`:(!T||W)&&a==="walk"?"pixel-pet-state-idle":`pixel-pet-state-${a}`,De=e=>{let n=e?Ye[e]:Ke,o=e&&fe[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:H,style:y?{"--pixel-pet-color":y}:void 0,children:(0,c.jsxs)("div",{ref:D,className:"pixel-pet-container",style:{transform:`translateX(${ce}px)`},onMouseEnter:we,onMouseLeave:Ce,children:[(0,c.jsx)("div",{className:"pixel-pet-status",role:"status",children:V&&(0,c.jsx)("div",{ref:Z,className:"pixel-pet-dialog",style:{...De(m),...O&&{left:O.left,maxWidth:O.maxWidth}},children:W?_(f.message,f.emotion):m&&ve[m]})}),(0,c.jsx)("button",{type:"button","aria-label":(d??i).split("{name}").join(i),className:`pixel-pet-sprite pixel-pet-${s} ${Se()} ${U==="left"?"pixel-pet-flip-x":""}`,style:u?{backgroundImage:`url("${u}")`}:void 0,onClick:Me,onFocus:e=>te(e.currentTarget.matches(":focus-visible")),onBlur:()=>te(!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 Ae,useLayoutEffect as Ie,useRef as r,useSyncExternalStore as Le}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,_e=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=()=>Le(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:_,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}"?x("span",{className:"pixel-pet-highlight-pet",children:f},n):x("span",{className:`pixel-pet-highlight-${o??"pet"}`,children:t.slice(2,-2)},n)),ke={happy:S(_?.happy??Y.happy,"happy"),angry:S(_?.angry??Y.angry,"angry"),curious:S(_?.curious??Y.curious,"curious")},te=r(i),D=r(null),E=r(!1),A=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(()=>{A.current||C(null)},5e3)},P=e=>{T.current=e!==null,Ee(e)},I=()=>{k.current&&clearTimeout(k.current)},ne=e=>{I(),e!==1/0&&(k.current=setTimeout(()=>P(null),e))};Ae(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:()=>{I(),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&&(I(),P(null)),A.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&&I(),b&&(A.current=!0,s.current&&clearTimeout(s.current))},Me=()=>{j(!1),ee(!1),T.current&&ne(B.current),b&&(A.current=!1,s.current&&clearTimeout(s.current),s.current=setTimeout(()=>{C(null)},5e3))},L=c!==null&&i!=="sleep",p=i==="sleep"?null:L?c.emotion:b,X=L||p!==null&&(xe&&!ye||he||be);Ie(()=>{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(_e,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||L)&&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:L?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 a,useEffect as d,useImperativeHandle as Le,useLayoutEffect as Fe,useRef as r,useSyncExternalStore as Ne}from"react";import{jsx as h,jsxs as Ye}from"react/jsx-runtime";var se=["pointermove","pointerdown","keydown","wheel"],pe="(prefers-reduced-motion: reduce)",K=4,We=240,ae=5e3,q=3e3,He=/(\{name\}|\*\*[^*]+\*\*|__[^_]+__)/,Q={happy:"{name} is feeling **happy**!",angry:"{name} is feeling **overwhelmed**!",curious:"{name} is very **curious**"},Oe="Hi, I'm {name}!",le=65,ce=100,Be=.04,Ue=100,$e=f=>{if(typeof window.matchMedia!="function")return()=>{};let w=window.matchMedia(pe);return w.addEventListener("change",f),()=>w.removeEventListener("change",f)},Ge=()=>typeof window.matchMedia=="function"&&window.matchMedia(pe).matches,Xe=()=>Ne($e,Ge,()=>!1),ze={happy:{backgroundColor:"#1e1c06eb",borderColor:"#ffeb3b"},angry:{backgroundColor:"#210908eb",borderColor:"#F04037"},curious:{backgroundColor:"#1f341deb",borderColor:"#8BE383"}},Ve={backgroundColor:"#05080ceb",borderColor:"var(--pixel-pet-color, #FDDBA0)"},et=({petName:f="Static",variant:w="cat",spriteSrc:j,color:J,label:ue,mode:de="live",walk:fe=!0,messages:C,dialogStyle:m={},emotionDialogStyles:me={},onClick:ge,ref:xe})=>{let[i,l]=a("idle"),he=Xe(),b=de==="showcase",y=fe&&!he&&!b,W=r(null),S=r(null),Z=r(null),[H,be]=a(null),[E,D]=a(null),[ye,ee]=a(!1),[Ee,te]=a(!1),[Te,oe]=a(!1),[Re,ne]=a(!1),[c,ke]=a(null),T=r(ce),[O,Pe]=a(0),[B,ve]=a("right"),I=(e,o)=>typeof e!="string"?e:e.split(He).map((t,n)=>n%2===0?t:t==="{name}"||t.startsWith("__")?h("span",{className:"pixel-pet-highlight-pet",children:t==="{name}"?f:t.slice(2,-2)},n):h("span",{className:`pixel-pet-highlight-${o??"pet"}`,children:t.slice(2,-2)},n)),Me={happy:I(C?.happy??Q.happy,"happy"),angry:I(C?.angry??Q.angry,"angry"),curious:I(C?.curious??Q.curious,"curious")},re=r(i),_=r(null),R=r(!1),A=r(!1),s=r(null),ie=r("happy"),U=r([]),k=r(null),P=r(!1),v=r(null),L=r(ae),$=(e,o=!1)=>{i==="sleep"&&l("idle"),ie.current=e,D(e),oe(o),s.current&&clearTimeout(s.current),s.current=setTimeout(()=>{A.current||D(null)},5e3)},g=e=>{P.current=e!==null,ke(e)},F=()=>{v.current&&clearTimeout(v.current)},G=e=>{F(),e!==1/0&&(v.current=setTimeout(()=>g(null),e))};Le(xe,()=>({say:(e,o={})=>{l(t=>t==="sleep"?"idle":t),g({message:e,emotion:o.emotion??null}),L.current=o.duration??ae,G(L.current)},hush:()=>{F(),g(null)}}),[]),d(()=>{let e,o=()=>{l(t=>t==="sleep"?"idle":t),clearTimeout(e),e=setTimeout(()=>{D(null),g(null),l("sleep")},12e4)};return se.forEach(t=>{window.addEventListener(t,o,{passive:!0})}),o(),()=>{clearTimeout(e),se.forEach(t=>{window.removeEventListener(t,o)})}},[]),d(()=>{re.current=i},[i]),d(()=>{if(b)return;let e=setInterval(()=>{if(re.current==="sleep"||P.current)return;let o=["happy","angry","curious"],t=o[Math.floor(Math.random()*o.length)];$(t)},3e4);return()=>{clearInterval(e)}},[b]),d(()=>()=>{s.current&&clearTimeout(s.current),v.current&&clearTimeout(v.current),k.current&&clearTimeout(k.current)},[]),d(()=>{if(!(i==="sleep"||!y)){if(i==="walk"){let e=Math.floor(Math.random()*175001)+5e3;_.current=setTimeout(()=>{l("idle"),R.current=!0,setTimeout(()=>{R.current=!1},3e4)},e)}else{let e=()=>{if(!R.current&&Math.random()>.4){l("walk");return}_.current=setTimeout(e,5e3)};e()}return()=>{_.current&&clearTimeout(_.current)}}},[i,y]);let X=e=>{T.current=e,S.current&&(S.current.style.transform=`translateX(${e}px)`)};d(()=>{if(i!=="walk"||E!==null||c!==null||!y)return;let e=0,o=O-le;if(o<=e)return;let t=0,n=null,u=M=>{let V=n===null?0:Math.min(M-n,Ue);n=M;let x=T.current+(B==="right"?1:-1)*Be*V;if(x<=e||x>=o){if(Math.random()>.5){l("idle"),R.current=!0,setTimeout(()=>{R.current=!1},3e4);return}X(x<=e?e+2:o-2),ve(Y=>Y==="right"?"left":"right");return}X(x),t=requestAnimationFrame(u)};return t=requestAnimationFrame(u),()=>cancelAnimationFrame(t)},[i,E,c,B,y,O]),d(()=>{let e=W.current;if(!e)return;let o=n=>{Pe(n),X(Math.max(Math.min(T.current,n-le),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 we=e=>{if(ge?.(e),e.defaultPrevented)return;let o=Date.now();if(i==="sleep"&&l("idle"),b){g({message:C?.greeting??Oe,emotion:null}),L.current=q,G(q);return}P.current&&(F(),g(null)),A.current=!1,ne(!0),k.current&&clearTimeout(k.current),k.current=setTimeout(()=>oe(!1),q);let t=[...U.current,o].filter(n=>o-n<=1e4);U.current=t,t.length>5?(U.current=[],$("angry",!0)):$(ie.current,!0)},Ce=()=>{ee(!0),P.current&&F(),E&&(A.current=!0,s.current&&clearTimeout(s.current))},Se=()=>{ee(!1),ne(!1),P.current&&G(L.current),E&&(A.current=!1,s.current&&clearTimeout(s.current),s.current=setTimeout(()=>{D(null)},5e3))},N=c!==null&&i!=="sleep",p=i==="sleep"?null:N?c.emotion:b?null:E,z=N||p!==null&&(ye&&!Re||Ee||Te);Fe(()=>{let e=W.current,o=S.current,t=Z.current;if(!z||!e||!o||!t)return;let n=e.clientWidth,u=Math.max(Math.min(We,n-K*2),0),M=Math.min(t.offsetWidth,u),V=T.current+o.offsetWidth/2,x=K,Y=n-K-M,_e=V-M/2,Ae=Math.max(Math.min(_e,Y),x);be({left:Ae-T.current,maxWidth:u})},[z,p,c,O]);let De=()=>p?`pixel-pet-state-${p}`:(!y||N)&&i==="walk"?"pixel-pet-state-idle":`pixel-pet-state-${i}`,Ie=e=>{let o=e?ze[e]:Ve,t=e&&me[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 h("div",{className:"pixel-pet",ref:W,style:J?{"--pixel-pet-color":J}:void 0,children:Ye("div",{ref:S,className:"pixel-pet-container",style:{transform:`translateX(${ce}px)`},onMouseEnter:Ce,onMouseLeave:Se,children:[h("div",{className:"pixel-pet-status",role:"status",children:z&&h("div",{ref:Z,className:"pixel-pet-dialog",style:{...Ie(p),...H&&{left:H.left,maxWidth:H.maxWidth}},children:N?I(c.message,c.emotion):p&&Me[p]})}),h("button",{type:"button","aria-label":(ue??f).split("{name}").join(f),className:`pixel-pet-sprite pixel-pet-${w} ${De()} ${B==="left"?"pixel-pet-flip-x":""}`,style:j?{backgroundImage:`url("${j}")`}:void 0,onClick:we,onFocus:e=>te(e.currentTarget.matches(":focus-visible")),onBlur:()=>te(!1)})]})})};export{et as PixelPet};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrixton/pixel-pet",
3
- "version": "0.2.1",
3
+ "version": "0.4.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",