@rpg-engine/long-bow 0.8.236 → 0.8.238
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/dist/long-bow.cjs.development.js +22 -15
- package/dist/long-bow.cjs.development.js.map +1 -1
- package/dist/long-bow.cjs.production.min.js +1 -1
- package/dist/long-bow.cjs.production.min.js.map +1 -1
- package/dist/long-bow.esm.js +22 -15
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Spellbook/Spell.tsx +12 -1
- package/src/components/Spellbook/Spellbook.tsx +4 -1
- package/src/components/Spellbook/__tests__/SpellInfoWrapper.test.tsx +26 -0
- package/src/components/Spellbook/__tests__/Spellbook.test.tsx +12 -0
- package/src/components/Spellbook/cards/SpellInfoWrapper.tsx +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ISpell, ItemRarities } from '@rpg-engine/shared';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import styled, { css } from 'styled-components';
|
|
3
|
+
import styled, { css, keyframes } from 'styled-components';
|
|
4
4
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
5
5
|
import { SpellInfoWrapper } from './cards/SpellInfoWrapper';
|
|
6
6
|
|
|
@@ -87,6 +87,11 @@ export const Spell: React.FC<ISpellProps> = ({
|
|
|
87
87
|
);
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
const assignmentPulse = keyframes`
|
|
91
|
+
0%, 100% { background-color: rgba(255, 247, 215, .5); }
|
|
92
|
+
50% { background-color: rgba(255, 252, 235, .9); }
|
|
93
|
+
`;
|
|
94
|
+
|
|
90
95
|
const Card = styled.button<{ $assigning: boolean }>`
|
|
91
96
|
box-sizing: border-box;
|
|
92
97
|
display: flex;
|
|
@@ -104,6 +109,12 @@ const Card = styled.button<{ $assigning: boolean }>`
|
|
|
104
109
|
font: inherit;
|
|
105
110
|
cursor: pointer;
|
|
106
111
|
transition: background .12s, border-color .12s;
|
|
112
|
+
animation-name: ${({ $assigning }) => $assigning ? assignmentPulse : 'none'};
|
|
113
|
+
animation-duration: 1.4s;
|
|
114
|
+
animation-timing-function: ease-in-out;
|
|
115
|
+
animation-iteration-count: infinite;
|
|
116
|
+
&:disabled { animation-name: none; }
|
|
117
|
+
@media (prefers-reduced-motion: reduce) { animation-name: none; }
|
|
107
118
|
&:hover:not(:disabled) { background: rgba(255, 252, 235, .9); border-color: #8a5a14; box-shadow: inset 0 0 0 1px #8a5a14; }
|
|
108
119
|
&:focus-visible { outline: 3px solid #3a2110; outline-offset: 2px; }
|
|
109
120
|
/* RPGUI greys and recolors every :disabled (0,2,0): keep the dashed card and its red/blue stats */
|
|
@@ -371,7 +371,10 @@ const PageTurn = styled(SmallButton)`
|
|
|
371
371
|
flex: none;
|
|
372
372
|
width: 124px;
|
|
373
373
|
${DESKTOP} { width: 108px; padding: 0 5px; }
|
|
374
|
-
${PHONE} {
|
|
374
|
+
${PHONE} {
|
|
375
|
+
flex: 1 1 0; width: auto; min-width: 44px; padding: 0 4px; gap: 4px; font-size: 9px;
|
|
376
|
+
svg { flex: 0 0 10px; }
|
|
377
|
+
}
|
|
375
378
|
`;
|
|
376
379
|
// Same Previous / Page n of N / Next order as the shared Pagination; the base-value footnote takes the
|
|
377
380
|
// left page so the page turners sit together at the outer corner of the right page.
|
|
@@ -100,3 +100,29 @@ it('positions focused details visibly without waiting for a mouse movement', ()
|
|
|
100
100
|
expect(tooltip.style.opacity).toBe('1');
|
|
101
101
|
expect(tooltip.style.transform).toBe('translate(170px, 120px)');
|
|
102
102
|
});
|
|
103
|
+
|
|
104
|
+
it('closes mouse details after leaving a clicked card without blurring it', () => {
|
|
105
|
+
render(<SpellInfoWrapper spell={mockSpells[0]}><button>Cast</button></SpellInfoWrapper>);
|
|
106
|
+
const button = screen.getByRole('button');
|
|
107
|
+
// Mouse clicks focus buttons without enabling keyboard focus-visible.
|
|
108
|
+
const matches = button.matches.bind(button);
|
|
109
|
+
jest.spyOn(button, 'matches').mockImplementation(selector => selector === ':focus-visible' ? false : matches(selector));
|
|
110
|
+
fireEvent.mouseEnter(button);
|
|
111
|
+
act(() => button.focus());
|
|
112
|
+
fireEvent.click(button);
|
|
113
|
+
expect(screen.getByRole('tooltip')).toBeTruthy();
|
|
114
|
+
fireEvent.mouseLeave(button);
|
|
115
|
+
expect(screen.queryByRole('tooltip')).toBeNull();
|
|
116
|
+
expect(document.activeElement).toBe(button);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('keeps keyboard details when the pointer leaves a keyboard-focused card', () => {
|
|
120
|
+
render(<SpellInfoWrapper spell={mockSpells[0]}><button>Cast</button></SpellInfoWrapper>);
|
|
121
|
+
const button = screen.getByRole('button');
|
|
122
|
+
jest.spyOn(button, 'matches').mockReturnValue(true);
|
|
123
|
+
act(() => button.focus());
|
|
124
|
+
fireEvent.mouseLeave(button);
|
|
125
|
+
expect(screen.getByRole('tooltip')).toBeTruthy();
|
|
126
|
+
act(() => button.blur());
|
|
127
|
+
expect(screen.queryByRole('tooltip')).toBeNull();
|
|
128
|
+
});
|
|
@@ -69,6 +69,8 @@ it('blocks cast on low skills, mana and cooldown but permits assignment without
|
|
|
69
69
|
rerender(<Spell {...card} charSkillLevels={{ strength: 20 }} charMana={0} isSettingShortcut />);
|
|
70
70
|
fireEvent.click(screen.getByRole('button'));
|
|
71
71
|
expect(onClick).toHaveBeenCalledWith(spell.key);
|
|
72
|
+
rerender(<Spell {...card} charSkillLevels={{ strength: 2 }} isSettingShortcut />);
|
|
73
|
+
expect(getComputedStyle(screen.getByRole('button')).animationName).toBe('none');
|
|
72
74
|
});
|
|
73
75
|
|
|
74
76
|
it('clears an occupied shortcut without starting assignment', () => {
|
|
@@ -111,3 +113,13 @@ it('spells out requirement, mana cost and cooldown and only prompts when assigni
|
|
|
111
113
|
expect(screen.getAllByText('Requires Magic Resistance 4')).toHaveLength(1);
|
|
112
114
|
expect((screen.getByRole('button') as HTMLButtonElement).disabled).toBe(true);
|
|
113
115
|
});
|
|
116
|
+
|
|
117
|
+
it('pulses only assignable cards until assignment ends', () => {
|
|
118
|
+
render(<Spellbook {...props} />);
|
|
119
|
+
expect(getComputedStyle(screen.getByRole('button', { name: 'Cast Spell 8' })).animationName).toBe('none');
|
|
120
|
+
fireEvent.click(screen.getByRole('button', { name: 'Set shortcut 1' }));
|
|
121
|
+
expect(getComputedStyle(screen.getByRole('button', { name: 'Assign Spell 8' })).animationName).not.toBe('none');
|
|
122
|
+
expect(getComputedStyle(screen.getByRole('button', { name: 'Assign Spell 8' })).animationName).not.toBe('');
|
|
123
|
+
fireEvent.click(screen.getByRole('button', { name: 'Cancel assignment' }));
|
|
124
|
+
expect(getComputedStyle(screen.getByRole('button', { name: 'Cast Spell 8' })).animationName).toBe('none');
|
|
125
|
+
});
|
|
@@ -65,7 +65,9 @@ export const SpellInfoWrapper: React.FC<ISpellInfoWrapperProps> = ({
|
|
|
65
65
|
if (!isTooltipMobileVisible) setIsTooltipVisible(true);
|
|
66
66
|
}}
|
|
67
67
|
onMouseLeave={event => {
|
|
68
|
-
if (!event.currentTarget.contains(document.activeElement)
|
|
68
|
+
if (!event.currentTarget.contains(document.activeElement) || !document.activeElement?.matches(':focus-visible')) {
|
|
69
|
+
setIsTooltipVisible(false);
|
|
70
|
+
}
|
|
69
71
|
}}
|
|
70
72
|
onTouchStart={handleTouchStart}
|
|
71
73
|
onTouchEnd={clearHold}
|