@ihabdevteam/core 0.13.2 → 0.15.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.
@@ -5213,6 +5213,16 @@ export declare interface SpeakerPos {
5213
5213
  deg: number;
5214
5214
  /** 동심원 링 번호 (1=가장 안쪽, 기본값 1) */
5215
5215
  row?: number;
5216
+ /**
5217
+ * 이 스피커에 걸린 자판 키. 주면 버튼 모서리에 `ShortcutBadge` 가 얹힌다.
5218
+ *
5219
+ * 키가 모자란 스피커에는 주지 않으면 된다 — 배지도 안 붙는다(예: 27개 배치의
5220
+ * 가운데 층은 13개인데 자판 줄은 9칸이라 넷은 키가 없다).
5221
+ *
5222
+ * 무엇을 눌렀는지 판정하는 일은 부르는 쪽 몫이다. 한글 입력 상태에서는
5223
+ * `event.key` 가 "ㅈ" 이 되므로 `event.code`(늘 `KeyW`)로 보는 편이 안전하다.
5224
+ */
5225
+ shortcut?: string;
5216
5226
  }
5217
5227
 
5218
5228
  /** @public */
@@ -55,6 +55,10 @@
55
55
  --scb-wrong-bg: #ef4444;
56
56
  --scb-wrong-border: #ef4444;
57
57
  --scb-wrong-fg: #ffffff;
58
+ /* 호버·포커스도 상태 색과 같은 방식으로 연다 — 소비자가 바꿔 끼울 수 있게. */
59
+ --scb-idle-hover-bg: var(--color-whiteLine);
60
+ --scb-idle-hover-border: var(--color-lightGray);
61
+ --scb-focus-ring: var(--color-main);
58
62
  }
59
63
 
60
64
  .sound-compass__speaker-btn--idle {
@@ -132,6 +136,37 @@
132
136
  }
133
137
 
134
138
  /* 스피커 버튼 wrap의 정적 속성만 분리(나머지는 상태·좌표 기반 동적 값이라 인라인 유지) */
139
+ /* 호버·포커스 — `all: unset` 이 브라우저 기본 상태까지 지워, 누를 수 있는 자리인지
140
+ 눈으로 알 길이 없었다(실측: 이 파일에 :hover·:focus 규칙이 0개였다). 커서만
141
+ pointer 로 바뀔 뿐이었다.
142
+
143
+ 고를 수 없을 때는 감싼 자리가 `pointer-events: none` 이라 :hover 가 애초에 안
144
+ 걸리고, tabIndex 도 -1 이라 :focus-visible 도 안 걸린다. 그래서 이 규칙들은
145
+ clickable 일 때만 보인다. */
146
+ .sound-compass__speaker-btn:hover {
147
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--scb-focus-ring) 14%, transparent);
148
+ }
149
+ .sound-compass__speaker-btn--idle:hover {
150
+ background: var(--scb-idle-hover-bg);
151
+ border-color: var(--scb-idle-hover-border);
152
+ }
153
+ /* :focus-visible 이라 마우스로 누를 때는 링이 안 뜨고 키보드로 옮길 때만 뜬다. */
154
+ .sound-compass__speaker-btn:focus-visible {
155
+ outline: 2px solid var(--scb-focus-ring);
156
+ outline-offset: 2px;
157
+ }
158
+
159
+ /* 자판 키 배지 — 눌러야 하는 것은 버튼이므로 클릭을 가로채지 않는다.
160
+ 상태가 바뀐 스피커가 다른 스피커 위로 올라가도(z-index 10) 배지는 제 버튼
161
+ 위에 남아야 하므로 버튼(z-index 1)보다 한 겹 위에 둔다. */
162
+ .sound-compass__speaker-shortcut {
163
+ pointer-events: none;
164
+ z-index: 2;
165
+ /* 모서리에 걸친 채로 자라도록 가운데를 기준으로 키운다. */
166
+ transform: scale(var(--sc-shortcut-scale, 1));
167
+ transform-origin: center;
168
+ }
169
+
135
170
  .sound-compass__speaker-pos {
136
171
  position: absolute;
137
172
  }
@@ -10,6 +10,7 @@ import Check from 'lucide-react/dist/esm/icons/check.mjs';
10
10
  import Volume2 from 'lucide-react/dist/esm/icons/volume-2.mjs';
11
11
  import User from 'lucide-react/dist/esm/icons/user.mjs';
12
12
  import X from 'lucide-react/dist/esm/icons/x.mjs';
13
+ import ShortcutBadge from './ShortcutBadge';
13
14
  import './SoundCompass.css';
14
15
  const lerp = (a, b, t) => a + (b - a) * t;
15
16
  /** @public */
@@ -151,6 +152,12 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
151
152
  const state = getSpeakerState(sp);
152
153
  const pulse = shouldPulse(sp);
153
154
  const baseStyle = {
155
+ // 배지는 24px 고정인데 버튼은 40~112px 을 오간다. 그대로 두면 버튼 대비
156
+ // 0.60배에서 0.21배까지 세 배가 흔들려, 작은 버튼에서는 배지가 버튼을 덮고
157
+ // 큰 버튼에서는 잃어버린다(실측). 버튼과 함께 자라게 하되 양끝을 묶어
158
+ // 0.34~0.45배 안에 머문다. ShortcutBadge 는 style 을 받지 않으므로
159
+ // 감싼 자리에 변수를 얹고 CSS 가 읽어 간다.
160
+ ['--sc-shortcut-scale']: String(Math.max(0.75, Math.min(1.6, buttonSize / 56))),
154
161
  pointerEvents: (clickable ? 'auto' : 'none'),
155
162
  cursor: clickable ? 'pointer' : 'default',
156
163
  // 호출된(=idle이 아닌) 스피커를 다른 스피커 위로 끌어올렸다가, 초기화되면 다시 낮춘다
@@ -162,6 +169,6 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
162
169
  const iconSize = Math.round(Math.max(10, Math.min(56, BTN * 0.38)));
163
170
  const numberSize = Math.round(Math.max(7, Math.min(18, BTN * 0.2)));
164
171
  const numberOnlySize = Math.round(Math.max(10, Math.min(56, BTN * 0.4)));
165
- return (_jsxs("div", { className: "sound-compass__speaker-pos", style: { ...baseStyle, left: x - BTN / 2, top: y - BTN / 2 }, onClick: () => clickable && onSelectSpeaker?.(sp.id), children: [pulse && _jsx("div", { className: "sound-compass__pulse", style: { width: BTN, height: BTN } }), _jsxs("button", { type: "button", tabIndex: -1, className: `sound-compass__speaker-btn sound-compass__speaker-btn--${state}`, style: { width: BTN, height: BTN }, "aria-label": sp.label, children: [showIcon && getStateIcon(state, iconSize), _jsx("span", { className: "sound-compass__speaker-number", style: { fontSize: showIcon ? numberSize : numberOnlySize }, children: idx + 1 })] })] }, sp.id));
172
+ return (_jsxs("div", { className: "sound-compass__speaker-pos", style: { ...baseStyle, left: x - BTN / 2, top: y - BTN / 2 }, children: [pulse && _jsx("div", { className: "sound-compass__pulse", style: { width: BTN, height: BTN } }), _jsxs("button", { type: "button", tabIndex: clickable ? 0 : -1, "aria-disabled": clickable ? undefined : true, onClick: clickable ? () => onSelectSpeaker?.(sp.id) : undefined, className: `sound-compass__speaker-btn sound-compass__speaker-btn--${state}`, style: { width: BTN, height: BTN }, "aria-label": sp.label, children: [showIcon && getStateIcon(state, iconSize), _jsx("span", { className: "sound-compass__speaker-number", style: { fontSize: showIcon ? numberSize : numberOnlySize }, children: idx + 1 })] }), sp.shortcut && (_jsx(ShortcutBadge, { keys: sp.shortcut, anchor: "top-right", className: "sound-compass__speaker-shortcut" }))] }, sp.id));
166
173
  })] }) }));
167
174
  }
@@ -8,4 +8,14 @@ export interface SpeakerPos {
8
8
  deg: number;
9
9
  /** 동심원 링 번호 (1=가장 안쪽, 기본값 1) */
10
10
  row?: number;
11
+ /**
12
+ * 이 스피커에 걸린 자판 키. 주면 버튼 모서리에 `ShortcutBadge` 가 얹힌다.
13
+ *
14
+ * 키가 모자란 스피커에는 주지 않으면 된다 — 배지도 안 붙는다(예: 27개 배치의
15
+ * 가운데 층은 13개인데 자판 줄은 9칸이라 넷은 키가 없다).
16
+ *
17
+ * 무엇을 눌렀는지 판정하는 일은 부르는 쪽 몫이다. 한글 입력 상태에서는
18
+ * `event.key` 가 "ㅈ" 이 되므로 `event.code`(늘 `KeyW`)로 보는 편이 안전하다.
19
+ */
20
+ shortcut?: string;
11
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ihabdevteam/core",
3
- "version": "0.13.2",
3
+ "version": "0.15.0",
4
4
  "private": false,
5
5
  "main": "dist/ihabdevteam-core.cjs.js",
6
6
  "module": "dist/ihabdevteam-core.esm.js",