@ihabdevteam/core 0.21.0 → 0.23.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.
@@ -5119,7 +5119,7 @@ export declare interface SliderProps {
5119
5119
  export declare function sortToValue(key: string, asc: boolean): string | undefined;
5120
5120
 
5121
5121
  /** @public */
5122
- export declare function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, pulseSelected, pulseSelectedDuration, pulseScale, clickable, onSelectSpeaker, buttonSize, showIcon, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): JSX.Element;
5122
+ export declare function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, pulseSelected, pulseSelectedDuration, pulseScale, clickable, onSelectSpeaker, buttonSize, showIcon, onCenterClick, centerLabel, centerDisabled, centerAttention, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): JSX.Element;
5123
5123
 
5124
5124
  export declare interface SoundCompassExam {
5125
5125
  /** `<SoundCompass {...bind} />` 로 펼쳐 넣는 제어 props */
@@ -5247,6 +5247,25 @@ export declare interface SoundCompassProps {
5247
5247
  * 아이콘이 붙고 번호가 함께 작아져(실측: 36px → 18px) 번호로 부르는 화면이 무너진다.
5248
5248
  */
5249
5249
  showIcon?: boolean | 'answered';
5250
+ /**
5251
+ * 가운데(피험자 자리)를 눌렀을 때. **주면 그 자리가 진짜 `<button>` 이 된다** —
5252
+ * 초점이 닿고 Enter·Space 로 눌리고 포커스 링이 뜬다.
5253
+ *
5254
+ * 주지 않으면 예전 그대로 그림일 뿐이다(`<div>` · `pointer-events: none`).
5255
+ * 소리를 듣는 단추를 그 자리에 두면 자리로도 말이 된다 — 소리는 그 사람을
5256
+ * 향해 나온다.
5257
+ */
5258
+ onCenterClick?: () => void;
5259
+ /** 가운데 단추의 이름표. `onCenterClick` 을 줄 때만 쓰인다. */
5260
+ centerLabel?: string;
5261
+ /** 가운데 단추를 못 누르게. 재생 중처럼 지금은 누를 때가 아닌 자리에 쓴다. */
5262
+ centerDisabled?: boolean;
5263
+ /**
5264
+ * 가운데 단추에 눈을 끄는 울림을 건다. 스피커 재생 울림과 같은 그림·같은 색이라
5265
+ * 화면이 한 벌로 읽힌다. 못 누를 때(`centerDisabled`)는 걸리지 않는다 —
5266
+ * 누를 수 없는 자리로 눈을 끌면 안 된다.
5267
+ */
5268
+ centerAttention?: boolean;
5250
5269
  /**
5251
5270
  * `speakers[].shortcut` 배지가 버튼의 어느 모서리에 붙을지. 기본 우상단.
5252
5271
  *
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "sideEffects": [
4
+ "**/*.css"
5
+ ]
6
+ }
@@ -33,6 +33,20 @@
33
33
  --scb-idle-hover-border: var(--color-lightGray);
34
34
  /* 호버의 글자색. 기본은 idle 과 같다 — 주지 않으면 예전 그대로다. */
35
35
  --scb-idle-hover-fg: var(--scb-idle-fg);
36
+ --scb-focus-ring: var(--color-main);
37
+
38
+ /* 가운데 단추 색 — 스피커 버튼(--scb-*)과 같은 결로 연다. 기본값은 지금 그림
39
+ 그대로라, 단추로 쓰지 않는 소비자에게는 변화가 없다. */
40
+ --scc-bg: var(--color-white);
41
+ --scc-border: var(--color-whiteLine);
42
+ --scc-fg: var(--color-lightGray);
43
+ --scc-hover-bg: var(--color-deepWhite);
44
+ --scc-hover-border: var(--color-lightGray);
45
+ --scc-hover-fg: var(--color-darkGray);
46
+ /* 못 누를 때. 눌러야 하는 자리인지 한눈에 갈려야 한다. */
47
+ --scc-disabled-bg: var(--color-deepWhite);
48
+ --scc-disabled-border: var(--color-whiteLine);
49
+ --scc-disabled-fg: var(--color-whiteLine);
36
50
  }
37
51
 
38
52
  .sound-compass__status {
@@ -67,8 +81,6 @@
67
81
  border-width: 1px;
68
82
  border-style: solid;
69
83
  transition: background-color .15s, border-color .15s, color .15s;
70
-
71
- --scb-focus-ring: var(--color-main);
72
84
  }
73
85
 
74
86
  .sound-compass__speaker-btn--idle {
@@ -118,13 +130,13 @@
118
130
  left: 50%;
119
131
  transform: translate(-50%, -50%);
120
132
  pointer-events: none;
121
- background: var(--color-white);
122
- border: 1px solid var(--color-whiteLine);
133
+ background: var(--scc-bg);
134
+ border: 1px solid var(--scc-border);
123
135
  border-radius: 50%;
124
136
  display: flex;
125
137
  align-items: center;
126
138
  justify-content: center;
127
- color: var(--color-lightGray);
139
+ color: var(--scc-fg);
128
140
  }
129
141
 
130
142
  /* ── Button 모드 전용 스타일 (speakerType='button') ── */
@@ -138,6 +150,48 @@
138
150
  100% { transform: scale(var(--sc-pulse-scale, 2.8)); opacity: 0; }
139
151
  }
140
152
 
153
+ /* 가운데를 단추로 쓸 때. `onCenterClick` 을 준 자리에만 붙는다 — 안 주면 위의
154
+ 그림 그대로이고 pointer-events 도 none 이라 변화가 없다.
155
+
156
+ `<button>` 의 기본 모양만 걷어낸다. 위 규칙이 이미 배치·색을 다 정하므로
157
+ `all: unset` 까지 갈 까닭이 없다 — 그러면 position 까지 지워져 자리가 무너진다. */
158
+ .sound-compass__center--button {
159
+ appearance: none;
160
+ font: inherit;
161
+ padding: 0;
162
+ cursor: pointer;
163
+ pointer-events: auto;
164
+ transition: background-color .15s, border-color .15s, color .15s;
165
+ }
166
+ .sound-compass__center--button:hover {
167
+ background: var(--scc-hover-bg);
168
+ border-color: var(--scc-hover-border);
169
+ color: var(--scc-hover-fg);
170
+ }
171
+ /* 스피커 버튼과 같은 링을 쓴다 — 화면 안에서 초점이 한 가지 모양으로 읽히게. */
172
+ .sound-compass__center--button:focus-visible {
173
+ outline: 2px solid var(--scb-focus-ring);
174
+ outline-offset: 2px;
175
+ }
176
+ /* 못 누를 때는 눌러도 안 되는 것이 눈으로 보여야 한다. */
177
+ .sound-compass__center--disabled {
178
+ pointer-events: none;
179
+ cursor: default;
180
+ background: var(--scc-disabled-bg);
181
+ border-color: var(--scc-disabled-border);
182
+ color: var(--scc-disabled-fg);
183
+ }
184
+
185
+ /* 가운데 울림은 그 자리에 겹쳐 놓는다 — 가운데 원과 같은 크기·같은 자리에서
186
+ 번져야 한 몸으로 보인다. 뒤에 깔리도록 단추보다 낮게 둔다.
187
+
188
+ **가운데 맞춤을 transform 으로 하지 않는다.** 이 원소는 keyframe 이 transform 에
189
+ scale 을 거는 자리라, translate 를 함께 걸면 애니메이션이 도는 동안 그것이 통째로
190
+ 날아간다 — 배지에서 이미 겪은 그 충돌이다. 좌표를 미리 빼서 얹는다. */
191
+ .sound-compass__pulse--center {
192
+ z-index: 0;
193
+ }
194
+
141
195
  .sound-compass__pulse {
142
196
  position: absolute;
143
197
  top: 0; left: 0;
@@ -91,6 +91,25 @@ export interface SoundCompassProps {
91
91
  * 아이콘이 붙고 번호가 함께 작아져(실측: 36px → 18px) 번호로 부르는 화면이 무너진다.
92
92
  */
93
93
  showIcon?: boolean | 'answered';
94
+ /**
95
+ * 가운데(피험자 자리)를 눌렀을 때. **주면 그 자리가 진짜 `<button>` 이 된다** —
96
+ * 초점이 닿고 Enter·Space 로 눌리고 포커스 링이 뜬다.
97
+ *
98
+ * 주지 않으면 예전 그대로 그림일 뿐이다(`<div>` · `pointer-events: none`).
99
+ * 소리를 듣는 단추를 그 자리에 두면 자리로도 말이 된다 — 소리는 그 사람을
100
+ * 향해 나온다.
101
+ */
102
+ onCenterClick?: () => void;
103
+ /** 가운데 단추의 이름표. `onCenterClick` 을 줄 때만 쓰인다. */
104
+ centerLabel?: string;
105
+ /** 가운데 단추를 못 누르게. 재생 중처럼 지금은 누를 때가 아닌 자리에 쓴다. */
106
+ centerDisabled?: boolean;
107
+ /**
108
+ * 가운데 단추에 눈을 끄는 울림을 건다. 스피커 재생 울림과 같은 그림·같은 색이라
109
+ * 화면이 한 벌로 읽힌다. 못 누를 때(`centerDisabled`)는 걸리지 않는다 —
110
+ * 누를 수 없는 자리로 눈을 끌면 안 된다.
111
+ */
112
+ centerAttention?: boolean;
94
113
  /**
95
114
  * `speakers[].shortcut` 배지가 버튼의 어느 모서리에 붙을지. 기본 우상단.
96
115
  *
@@ -118,4 +137,4 @@ export interface SoundCompassProps {
118
137
  scaleShortcut?: boolean;
119
138
  }
120
139
  /** @public */
121
- export default function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, pulseSelected, pulseSelectedDuration, pulseScale, clickable, onSelectSpeaker, buttonSize, showIcon, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): import("react").JSX.Element;
140
+ export default function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, pulseSelected, pulseSelectedDuration, pulseScale, clickable, onSelectSpeaker, buttonSize, showIcon, onCenterClick, centerLabel, centerDisabled, centerAttention, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): import("react").JSX.Element;
@@ -14,7 +14,7 @@ import ShortcutBadge from './ShortcutBadge';
14
14
  import './SoundCompass.css';
15
15
  const lerp = (a, b, t) => a + (b - a) * t;
16
16
  /** @public */
17
- export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSpeakerId = null, playedIds, selectedId = null, answerId = null, showFeedback = false, pulseSelected = false, pulseSelectedDuration, pulseScale, clickable = false, onSelectSpeaker, buttonSize = 40, showIcon = true, shortcutAnchor = 'top-right', shortcutScale, scaleShortcut = false, }) {
17
+ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSpeakerId = null, playedIds, selectedId = null, answerId = null, showFeedback = false, pulseSelected = false, pulseSelectedDuration, pulseScale, clickable = false, onSelectSpeaker, buttonSize = 40, showIcon = true, onCenterClick, centerLabel, centerDisabled = false, centerAttention = false, shortcutAnchor = 'top-right', shortcutScale, scaleShortcut = false, }) {
18
18
  const t = Math.max(0, Math.min(1, viewT));
19
19
  const CENTER = size / 2;
20
20
  // ── 탑뷰 좌표 ──────────────────────────────────────────────────────────
@@ -156,7 +156,8 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
156
156
  return (_jsx("div", { className: "sound-compass", children: _jsxs("div", { className: "sound-compass__compass-wrap", style: { width: size, height: size }, children: [_jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "sound-compass__svg", children: [t === 0 && speakers.map(sp => {
157
157
  const { x, y } = getSpeakerXY(sp);
158
158
  return (_jsx("line", { x1: personPos.x, y1: personPos.y, x2: x, y2: y, stroke: "#dde3ec", strokeWidth: "1" }, sp.id));
159
- }), rowArcPaths.map(a => (_jsx("path", { d: a.d, fill: "none", stroke: "#dde3ec", strokeWidth: "1" }, a.key)))] }), _jsx("div", { className: "sound-compass__center", style: { width: centerSize, height: centerSize, left: personPos.x, top: personPos.y }, children: _jsx(User, { size: centerIconSize, color: "currentColor", strokeWidth: 1.5 }) }), speakers.map((sp, idx) => {
159
+ }), rowArcPaths.map(a => (_jsx("path", { d: a.d, fill: "none", stroke: "#dde3ec", strokeWidth: "1" }, a.key)))] }), centerAttention && !centerDisabled && (_jsx("div", { className: "sound-compass__pulse sound-compass__pulse--active sound-compass__pulse--center", style: { width: centerSize, height: centerSize,
160
+ left: personPos.x - centerSize / 2, top: personPos.y - centerSize / 2 } })), onCenterClick ? (_jsx("button", { type: "button", className: `sound-compass__center sound-compass__center--button${centerDisabled ? ' sound-compass__center--disabled' : ''}`, style: { width: centerSize, height: centerSize, left: personPos.x, top: personPos.y }, "aria-label": centerLabel, "aria-disabled": centerDisabled || undefined, tabIndex: centerDisabled ? -1 : 0, onClick: centerDisabled ? undefined : onCenterClick, children: _jsx(User, { size: centerIconSize, color: "currentColor", strokeWidth: 1.5 }) })) : (_jsx("div", { className: "sound-compass__center", style: { width: centerSize, height: centerSize, left: personPos.x, top: personPos.y }, children: _jsx(User, { size: centerIconSize, color: "currentColor", strokeWidth: 1.5 }) })), speakers.map((sp, idx) => {
160
161
  const { x, y } = getSpeakerXY(sp);
161
162
  const state = getSpeakerState(sp);
162
163
  const pulse = pulseKind(sp);
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@ihabdevteam/core",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "private": false,
5
5
  "main": "dist/ihabdevteam-core.cjs.js",
6
- "module": "dist/ihabdevteam-core.esm.js",
6
+ "module": "dist/packages/core/src/index.js",
7
7
  "style": "dist/ihabdevteam-core.css",
8
8
  "types": "dist/ihabdevteam-core.d.ts",
9
9
  "typesVersions": {
@@ -101,7 +101,7 @@
101
101
  "dist/**/*.css"
102
102
  ],
103
103
  "scripts": {
104
- "build": "(rm -rf dist node_modules/.tmp/tsconfig.core.tsbuildinfo 2>/dev/null || true) && tsc -p tsconfig.json && postcss src/styles.css -o dist/ihabdevteam-core.css && CORE_CSS_NO_PREFIX=1 postcss src/tokens.css -o dist/ihabdevteam-core-tokens.css && cp src/fonts.css dist/ihabdevteam-core-fonts.css && mkdir -p dist/packages/core/src && find src -type f \\( -name '*.css' -o -name '*.svg' -o -name '*.mp3' -o -name '*.png' \\) | while IFS= read -r f; do d=$(dirname \"$f\"); mkdir -p \"dist/packages/core/$d\"; cp \"$f\" \"dist/packages/core/$d/\"; done && mkdir -p dist/logo && if [ -d src/logo ]; then cp -r src/logo/. dist/logo/; elif [ -d ../../logo ]; then cp -r ../../logo/. dist/logo/; elif [ -d ../../public/logo ]; then cp -r ../../public/logo/. dist/logo/; fi && node scripts/check-build-outputs.mjs && rollup -c && cp dist/packages/core/src/index.d.ts dist/ihabdevteam-core.d.ts",
104
+ "build": "(rm -rf dist node_modules/.tmp/tsconfig.core.tsbuildinfo 2>/dev/null || true) && tsc -p tsconfig.json && postcss src/styles.css -o dist/ihabdevteam-core.css && CORE_CSS_NO_PREFIX=1 postcss src/tokens.css -o dist/ihabdevteam-core-tokens.css && cp src/fonts.css dist/ihabdevteam-core-fonts.css && mkdir -p dist/packages/core/src && find src -type f \\( -name '*.css' -o -name '*.svg' -o -name '*.mp3' -o -name '*.png' \\) | while IFS= read -r f; do d=$(dirname \"$f\"); mkdir -p \"dist/packages/core/$d\"; cp \"$f\" \"dist/packages/core/$d/\"; done && mkdir -p dist/logo && if [ -d src/logo ]; then cp -r src/logo/. dist/logo/; elif [ -d ../../logo ]; then cp -r ../../logo/. dist/logo/; elif [ -d ../../public/logo ]; then cp -r ../../public/logo/. dist/logo/; fi && node scripts/write-dist-package-json.mjs && node scripts/check-build-outputs.mjs && rollup -c && cp dist/packages/core/src/index.d.ts dist/ihabdevteam-core.d.ts",
105
105
  "extract-api": "api-extractor run --local --verbose",
106
106
  "postbuild": "npm run extract-api",
107
107
  "prepare": "npm run build",
@@ -119,7 +119,8 @@
119
119
  "check:size": "size-limit",
120
120
  "type-test": "tsd",
121
121
  "test": "vitest --run --environment jsdom",
122
- "coverage": "vitest --coverage --run --environment jsdom"
122
+ "coverage": "vitest --coverage --run --environment jsdom",
123
+ "audit:consumers": "node ./scripts/audit-consumers.cjs"
123
124
  },
124
125
  "vitest": {
125
126
  "setupFiles": [