@ihabdevteam/core 0.322.0 → 0.324.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.
Files changed (198) hide show
  1. package/CHANGELOG.md +557 -0
  2. package/dist/components.cjs.js +183 -183
  3. package/dist/components.esm.css +1 -1
  4. package/dist/hooks.cjs.js +3 -3
  5. package/dist/i18n.cjs.js +3 -3
  6. package/dist/ihabdevteam-core.cjs.js +187 -187
  7. package/dist/ihabdevteam-core.d.ts +55 -17
  8. package/dist/packages/core/src/components/Accordion.js +3 -3
  9. package/dist/packages/core/src/components/Alert.js +2 -2
  10. package/dist/packages/core/src/components/BarChart.js +120 -119
  11. package/dist/packages/core/src/components/BottomNav.js +18 -18
  12. package/dist/packages/core/src/components/BulkBar.js +25 -25
  13. package/dist/packages/core/src/components/Button.js +16 -16
  14. package/dist/packages/core/src/components/Calendar.js +17 -17
  15. package/dist/packages/core/src/components/ChartSection.js +30 -30
  16. package/dist/packages/core/src/components/CountTile.js +38 -38
  17. package/dist/packages/core/src/components/DatePicker.js +59 -59
  18. package/dist/packages/core/src/components/Dialog.js +14 -14
  19. package/dist/packages/core/src/components/Effect/EffectPartyPopperHost.js +8 -8
  20. package/dist/packages/core/src/components/FilterPanel.js +6 -6
  21. package/dist/packages/core/src/components/FilterSectionInfo.js +6 -6
  22. package/dist/packages/core/src/components/GameIntro.js +1 -1
  23. package/dist/packages/core/src/components/Icon.js +2 -2
  24. package/dist/packages/core/src/components/ItemActions.js +16 -16
  25. package/dist/packages/core/src/components/LandingHeader.js +6 -6
  26. package/dist/packages/core/src/components/LineChart.js +24 -24
  27. package/dist/packages/core/src/components/Loader.js +5 -5
  28. package/dist/packages/core/src/components/NoticeBox.js +3 -3
  29. package/dist/packages/core/src/components/PageTitle.js +2 -2
  30. package/dist/packages/core/src/components/Pagination.js +4 -4
  31. package/dist/packages/core/src/components/Panel.js +2 -2
  32. package/dist/packages/core/src/components/PanelRow.js +6 -13
  33. package/dist/packages/core/src/components/PassCard/PassCard.js +9 -9
  34. package/dist/packages/core/src/components/PassCard/passCardProduct.js +2 -2
  35. package/dist/packages/core/src/components/PaymentPlan.js +5 -5
  36. package/dist/packages/core/src/components/Popup/ConfirmPopup.js +29 -29
  37. package/dist/packages/core/src/components/Popup/CouponInputPopup.js +2 -2
  38. package/dist/packages/core/src/components/Popup/DeviceSideTable.js +18 -25
  39. package/dist/packages/core/src/components/Popup/ListPopup.js +3 -3
  40. package/dist/packages/core/src/components/Popup/LoadTablePopup.js +7 -7
  41. package/dist/packages/core/src/components/Popup/Popup.js +27 -27
  42. package/dist/packages/core/src/components/Popup/SaveTablePopup.js +5 -5
  43. package/dist/packages/core/src/components/Popup/TrainingResultPopup.js +4 -4
  44. package/dist/packages/core/src/components/Popup/customerDetailUtils.js +18 -18
  45. package/dist/packages/core/src/components/Popup/useDiscardGuard.js +4 -4
  46. package/dist/packages/core/src/components/ProgressIndicator.js +5 -5
  47. package/dist/packages/core/src/components/PromoBannerBg/promoBannerBg.js +16 -16
  48. package/dist/packages/core/src/components/Rating.js +20 -20
  49. package/dist/packages/core/src/components/Report/ReportDocument.js +21 -21
  50. package/dist/packages/core/src/components/RingChart.js +5 -5
  51. package/dist/packages/core/src/components/SectionHeadFloat.js +15 -15
  52. package/dist/packages/core/src/components/SegmentedControl.js +34 -34
  53. package/dist/packages/core/src/components/SideNav.js +37 -37
  54. package/dist/packages/core/src/components/Slider.js +2 -2
  55. package/dist/packages/core/src/components/SoundCompass.js +5 -5
  56. package/dist/packages/core/src/components/Table.js +257 -98
  57. package/dist/packages/core/src/components/Tabs.js +12 -12
  58. package/dist/packages/core/src/components/TextField.js +34 -34
  59. package/dist/packages/core/src/components/Toast.js +6 -6
  60. package/dist/packages/core/src/components/Tooltip/MenuTooltip.js +4 -4
  61. package/dist/packages/core/src/components/Tooltip/Tooltip.js +22 -22
  62. package/dist/packages/core/src/components/Tooltip/TrainingTooltip.js +4 -4
  63. package/dist/packages/core/src/components/TopBanner.js +28 -28
  64. package/dist/packages/core/src/components/Training/AudioPlayerWaveForm.js +11 -11
  65. package/dist/packages/core/src/components/Training/CategoryIcon.js +2 -2
  66. package/dist/packages/core/src/components/Training/NoisePlayerContext.js +12 -12
  67. package/dist/packages/core/src/components/Training/NoiseSettingsTooltip.js +2 -2
  68. package/dist/packages/core/src/components/Training/TrainingBlock.js +11 -11
  69. package/dist/packages/core/src/components/Training/TrainingBlockButton.js +10 -10
  70. package/dist/packages/core/src/components/Training/TrainingBlockGrid.js +21 -21
  71. package/dist/packages/core/src/components/Training/TrainingCard.css +13 -1
  72. package/dist/packages/core/src/components/Training/TrainingCard.js +2 -2
  73. package/dist/packages/core/src/components/Training/TrainingCardGroup.js +6 -6
  74. package/dist/packages/core/src/components/Training/TrainingPagePopup.js +81 -81
  75. package/dist/packages/core/src/components/Training/TrainingPagePopupBottom.js +17 -17
  76. package/dist/packages/core/src/components/Training/TrainingStepAudioPlayer.js +127 -127
  77. package/dist/packages/core/src/components/Training/TrainingStepCard.js +18 -18
  78. package/dist/packages/core/src/components/Training/TrainingStepConfirmWithText.js +4 -4
  79. package/dist/packages/core/src/components/Training/TrainingStepOrderList.js +12 -12
  80. package/dist/packages/core/src/components/Training/TrainingStepTextBox.js +12 -12
  81. package/dist/packages/core/src/components/Training/TrainingStepTitle.js +4 -4
  82. package/dist/packages/core/src/components/Training/TrainingWordCard.js +17 -17
  83. package/dist/packages/core/src/components/Training/WordTablePopup.js +5 -5
  84. package/dist/packages/core/src/components/Training/audioPlaybackRules.js +8 -8
  85. package/dist/packages/core/src/components/Training/audioTimeline.js +25 -25
  86. package/dist/packages/core/src/components/Training/oneShotSound.js +13 -13
  87. package/dist/packages/core/src/components/Training/trainingMissStats.js +20 -20
  88. package/dist/packages/core/src/components/Training/trainingPopupUtils.js +7 -7
  89. package/dist/packages/core/src/components/Training/useAudioPreview.js +15 -15
  90. package/dist/packages/core/src/components/Training/useFloatingFocus.js +14 -14
  91. package/dist/packages/core/src/components/Training/useNoiseFade.js +22 -22
  92. package/dist/packages/core/src/components/Training/useObjectUrls.js +2 -2
  93. package/dist/packages/core/src/components/Training/usePlaybackSequencer.d.ts +2 -2
  94. package/dist/packages/core/src/components/Training/usePlaybackSequencer.js +23 -23
  95. package/dist/packages/core/src/components/Training/useProgressTicker.js +33 -33
  96. package/dist/packages/core/src/components/Training/useTTSPlayback.js +17 -17
  97. package/dist/packages/core/src/components/Training/useWaveReady.js +11 -11
  98. package/dist/packages/core/src/components/UpdateAlert.js +6 -6
  99. package/dist/packages/core/src/components/Word/PhonemeSelectBlock.js +7 -1
  100. package/dist/packages/core/src/components/Word/TableFilterPanel.js +3 -3
  101. package/dist/packages/core/src/components/Word/WordSearchResultBlock.js +10 -10
  102. package/dist/packages/core/src/components/Word/WordTableBlock.js +28 -28
  103. package/dist/packages/core/src/components/Word/WordTableButton.css +16 -2
  104. package/dist/packages/core/src/components/Word/WordTableThumbnail.css +6 -1
  105. package/dist/packages/core/src/components/Word/generatedDraft.js +14 -14
  106. package/dist/packages/core/src/components/Word/useWordTableDisplaySettings.js +7 -7
  107. package/dist/packages/core/src/components/Word/wordScoreStats.js +15 -15
  108. package/dist/packages/core/src/components/Word/wordTablePrint.js +10 -10
  109. package/dist/packages/core/src/components/Word/wordTableRecommend.js +11 -11
  110. package/dist/packages/core/src/components/Word/wordTableReview.js +28 -28
  111. package/dist/packages/core/src/components/Word/wordTableSettings.d.ts +8 -11
  112. package/dist/packages/core/src/components/Word/wordTableSettings.js +48 -36
  113. package/dist/packages/core/src/components/WordTable.js +130 -130
  114. package/dist/packages/core/src/components/barChartData.d.ts +16 -1
  115. package/dist/packages/core/src/components/barChartData.js +49 -29
  116. package/dist/packages/core/src/components/barChartTypes.js +8 -8
  117. package/dist/packages/core/src/components/tableTrackList.js +11 -11
  118. package/dist/packages/core/src/components/updateAlertStack.js +28 -28
  119. package/dist/packages/core/src/components/useBarChartControl.js +4 -6
  120. package/dist/packages/core/src/components/useComboboxSuggestions.js +17 -17
  121. package/dist/packages/core/src/components/useToastLifecycle.js +18 -18
  122. package/dist/packages/core/src/components/wordTableLayout.d.ts +17 -4
  123. package/dist/packages/core/src/components/wordTableLayout.js +5 -5
  124. package/dist/packages/core/src/context/I18nProvider.js +6 -6
  125. package/dist/packages/core/src/hooks/focusReturn.js +27 -27
  126. package/dist/packages/core/src/hooks/inputModality.js +13 -13
  127. package/dist/packages/core/src/hooks/touchToggle.js +39 -39
  128. package/dist/packages/core/src/hooks/useAnchoredMenu.js +30 -30
  129. package/dist/packages/core/src/hooks/useDailyTrainingSelection.js +28 -28
  130. package/dist/packages/core/src/hooks/useDocumentTitle.js +10 -10
  131. package/dist/packages/core/src/hooks/useEditableList.js +4 -4
  132. package/dist/packages/core/src/hooks/useFocusTrap.js +2 -2
  133. package/dist/packages/core/src/hooks/useFontScale.js +6 -6
  134. package/dist/packages/core/src/hooks/useIcon.js +7 -7
  135. package/dist/packages/core/src/hooks/useListControls.js +2 -2
  136. package/dist/packages/core/src/hooks/usePageResetOnChange.js +3 -3
  137. package/dist/packages/core/src/hooks/usePasswordChange.js +4 -4
  138. package/dist/packages/core/src/hooks/usePendingPress.js +7 -7
  139. package/dist/packages/core/src/hooks/useRelativeDay.js +4 -4
  140. package/dist/packages/core/src/hooks/useSoundCompassExam.js +6 -6
  141. package/dist/packages/core/src/hooks/useTablePaging.d.ts +0 -11
  142. package/dist/packages/core/src/hooks/useTablePaging.js +29 -34
  143. package/dist/packages/core/src/hooks/useTrainingSessionLog.js +35 -35
  144. package/dist/packages/core/src/hooks/useVisitSchedule.js +4 -4
  145. package/dist/packages/core/src/hooks/useWordAttributeFilter.js +14 -14
  146. package/dist/packages/core/src/hooks/useWordMeanings.js +5 -5
  147. package/dist/packages/core/src/hooks/useWordTrainingFilter.js +7 -7
  148. package/dist/packages/core/src/i18n/locales/en.json +14 -2
  149. package/dist/packages/core/src/i18n/locales/ko.json +13 -1
  150. package/dist/packages/core/src/types/wordSearch.d.ts +9 -0
  151. package/dist/packages/core/src/utils/audioMeter.js +5 -5
  152. package/dist/packages/core/src/utils/audioPeaks.d.ts +1 -1
  153. package/dist/packages/core/src/utils/audioPeaks.js +18 -18
  154. package/dist/packages/core/src/utils/blockGrid.js +4 -4
  155. package/dist/packages/core/src/utils/chunkSkew.js +9 -9
  156. package/dist/packages/core/src/utils/clipboard.js +3 -3
  157. package/dist/packages/core/src/utils/dailySelectionUtils.js +12 -12
  158. package/dist/packages/core/src/utils/date.js +12 -12
  159. package/dist/packages/core/src/utils/devWarn.js +4 -4
  160. package/dist/packages/core/src/utils/deviceKind.d.ts +37 -7
  161. package/dist/packages/core/src/utils/deviceKind.js +53 -10
  162. package/dist/packages/core/src/utils/errorMessage.js +7 -7
  163. package/dist/packages/core/src/utils/filterPanelSummary.js +14 -14
  164. package/dist/packages/core/src/utils/format.js +4 -4
  165. package/dist/packages/core/src/utils/getTrainingData.js +9 -9
  166. package/dist/packages/core/src/utils/imageConfig.js +17 -17
  167. package/dist/packages/core/src/utils/koreanSyllable.js +18 -18
  168. package/dist/packages/core/src/utils/navScroll.js +2 -2
  169. package/dist/packages/core/src/utils/phonemeConvert.d.ts +7 -0
  170. package/dist/packages/core/src/utils/phonemeConvert.js +19 -10
  171. package/dist/packages/core/src/utils/phonemeGroups.js +2 -2
  172. package/dist/packages/core/src/utils/phonemePairSearch.d.ts +1 -0
  173. package/dist/packages/core/src/utils/phonemePairSearch.js +129 -105
  174. package/dist/packages/core/src/utils/phonemePairSearchTypes.d.ts +2 -0
  175. package/dist/packages/core/src/utils/phonemeSectionCodes.d.ts +52 -0
  176. package/dist/packages/core/src/utils/phonemeSectionCodes.js +76 -0
  177. package/dist/packages/core/src/utils/prefetchChunk.js +6 -6
  178. package/dist/packages/core/src/utils/safeHref.js +4 -4
  179. package/dist/packages/core/src/utils/savedWordTables.js +5 -5
  180. package/dist/packages/core/src/utils/speak.js +12 -12
  181. package/dist/packages/core/src/utils/textSort.d.ts +0 -10
  182. package/dist/packages/core/src/utils/textSort.js +38 -48
  183. package/dist/packages/core/src/utils/theme.js +3 -3
  184. package/dist/packages/core/src/utils/trainingItemResults.js +3 -3
  185. package/dist/packages/core/src/utils/ttsConfig.js +2 -2
  186. package/dist/packages/core/src/utils/wordAttributeFilterCodes.d.ts +1 -1
  187. package/dist/packages/core/src/utils/wordAttributeFilterCodes.js +65 -45
  188. package/dist/packages/core/src/utils/wordContrast.js +3 -3
  189. package/dist/packages/core/src/utils/wordPairIndex.js +4 -4
  190. package/dist/packages/core/src/utils/wordTrainingGenerators.js +4 -4
  191. package/dist/packages/core/src/utils/wordTrainingPool.js +15 -15
  192. package/dist/packages/core/src/utils/wordTrainingRuntime.js +6 -6
  193. package/dist/packages/core/src/utils/wornDate.d.ts +3 -3
  194. package/dist/packages/core/src/utils/wornDate.js +14 -14
  195. package/dist/packages/core/src/version.d.ts +1 -1
  196. package/dist/packages/core/src/version.js +1 -1
  197. package/dist/utils.cjs.js +4 -4
  198. package/package.json +1 -1
@@ -8,27 +8,27 @@ import { FOCUSABLE_SELECTOR } from './useFocusTrap';
8
8
  * 미리보기와 함께 사라진다. 확인 창을 닫으면 떼어진 단추에 focus() 해 아무 일도 없이 body 에 떨어졌다.
9
9
  * 각 창의 판(panel) → 그 창이 돌려줄 곳을 적어 두고, 돌려줄 곳이 떼어졌으면 그것을 품었던 창의 돌려줄 곳을 따라 올라간다.
10
10
  */
11
- const 돌아갈곳표 = new WeakMap();
11
+ const returnTargets = new WeakMap();
12
12
  /** 창 판과, 그 창이 열릴 때 포커스였던 요소를 적는다 */
13
13
  export function rememberFocusReturn(panel, previouslyFocused) {
14
14
  if (panel)
15
- 돌아갈곳표.set(panel, previouslyFocused);
15
+ returnTargets.set(panel, previouslyFocused);
16
16
  }
17
17
  /** 실제로 돌려줄 곳 — 문서에 붙어 있으면 그대로, 떼어졌으면 그것을 품었던 창이 적어 둔 곳을 따라간다 */
18
18
  export function resolveFocusReturn(previouslyFocused) {
19
- let 대상 = previouslyFocused;
20
- for (let 번 = 0; 대상 && !대상.isConnected && 번 < 10; 번++) {
21
- let 창 = 대상;
22
- while (창 && !돌아갈곳표.has(창))
23
- 창 = 창.parentElement;
24
- if (!창)
25
- return 대상;
26
- 대상 = 돌아갈곳표.get(창) ?? null;
19
+ let target = previouslyFocused;
20
+ for (let hop = 0; target && !target.isConnected && hop < 10; hop++) {
21
+ let node = target;
22
+ while (node && !returnTargets.has(node))
23
+ node = node.parentElement;
24
+ if (!node)
25
+ return target;
26
+ target = returnTargets.get(node) ?? null;
27
27
  }
28
- return 대상;
28
+ return target;
29
29
  }
30
30
  /* 포커스를 받을 수 있는가 — 붙어 있고, 막히지 않았고, inert·aria-hidden·hidden 아래가 아니다(레이아웃은 재지 않는다: 시험 환경에 없다) */
31
- function 받을수있나(el) {
31
+ function canFocus(el) {
32
32
  if (!el || !el.isConnected || el === document.body)
33
33
  return false;
34
34
  if (typeof el.focus !== 'function')
@@ -53,24 +53,24 @@ function 받을수있나(el) {
53
53
  * 옮긴 요소를 돌려준다(못 옮기면 null).
54
54
  */
55
55
  export function moveFocusOutside(container, opener, anchor) {
56
- const 기준 = container?.isConnected ? container : anchor?.isConnected ? anchor : null;
57
- if (받을수있나(opener) && !(container && container.contains(opener))) {
56
+ const anchorNode = container?.isConnected ? container : anchor?.isConnected ? anchor : null;
57
+ if (canFocus(opener) && !(container && container.contains(opener))) {
58
58
  opener.focus();
59
59
  return opener;
60
60
  }
61
- if (!기준)
61
+ if (!anchorNode)
62
62
  return null;
63
- const 모두 = Array.from(document.querySelectorAll(FOCUSABLE_SELECTOR))
64
- .filter((el) => !(container && container.contains(el)) && 받을수있나(el));
65
- let 앞 = null;
66
- let 뒤 = null;
67
- for (const el of 모두) {
68
- if (기준.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_PRECEDING)
69
- 앞 = el;
70
- else if (!뒤)
71
- 뒤 = el;
63
+ const allFocusable = Array.from(document.querySelectorAll(FOCUSABLE_SELECTOR))
64
+ .filter((el) => !(container && container.contains(el)) && canFocus(el));
65
+ let before = null;
66
+ let after = null;
67
+ for (const el of allFocusable) {
68
+ if (anchorNode.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_PRECEDING)
69
+ before = el;
70
+ else if (!after)
71
+ after = el;
72
72
  }
73
- const 대상 = 앞 ?? 뒤;
74
- 대상?.focus();
75
- return 대상;
73
+ const target = before ?? after;
74
+ target?.focus();
75
+ return target;
76
76
  }
@@ -6,26 +6,26 @@
6
6
  * 초점을 옮겨 그 단추의 초점 툴팁이 떴다(듣기 → '재생 / 멈춤', 문장 미리보기 → '글자 크기 감소'). Safari 는 누른 단추에 초점을
7
7
  * 안 줘 body 에 남는다. 문서 전체에서 키 누름·포인터 누름을 들어 마지막 것을 기억한다(캡처 단계, 한 번만 붙인다).
8
8
  */
9
- let 마지막 = null;
10
- let 붙음 = false;
11
- function 붙이기() {
12
- if (붙음 || typeof document === 'undefined')
9
+ let last = null;
10
+ let attached = false;
11
+ function attach() {
12
+ if (attached || typeof document === 'undefined')
13
13
  return;
14
- 붙음 = true;
14
+ attached = true;
15
15
  document.addEventListener('keydown', (e) => {
16
16
  if (e.metaKey || e.ctrlKey || e.altKey)
17
17
  return;
18
- 마지막 = 'keyboard';
18
+ last = 'keyboard';
19
19
  }, true);
20
- const 포인터 = () => { 마지막 = 'pointer'; };
21
- document.addEventListener('pointerdown', 포인터, true);
22
- document.addEventListener('mousedown', 포인터, true);
23
- document.addEventListener('touchstart', 포인터, { capture: true, passive: true });
20
+ const onPointer = () => { last = 'pointer'; };
21
+ document.addEventListener('pointerdown', onPointer, true);
22
+ document.addEventListener('mousedown', onPointer, true);
23
+ document.addEventListener('touchstart', onPointer, { capture: true, passive: true });
24
24
  }
25
25
  // 불러오는 즉시 듣는다 — 첫 누름부터 알아야 한다
26
- 붙이기();
26
+ attach();
27
27
  /** 마지막 입력이 키보드였으면 true(모르면 false) */
28
28
  export function wasKeyboardPress() {
29
- 붙이기();
30
- return 마지막 === 'keyboard';
29
+ attach();
30
+ return last === 'keyboard';
31
31
  }
@@ -12,25 +12,25 @@ import { useLatestRef } from './useLatestRef';
12
12
  * 마우스 동작은 그대로 두고, pointerType 이 touch 일 때만: 들어오고 나감은 무시 · 뗄 때 뒤집기 · 방금 톡으로 온 focus 는 무시 ·
13
13
  * 바깥을 톡하면 닫기.
14
14
  */
15
- let 마지막터치 = 0;
16
- let 붙음 = false;
17
- function 붙이기() {
18
- if (붙음 || typeof document === 'undefined')
15
+ let lastTouchAt = 0;
16
+ let attached = false;
17
+ function attach() {
18
+ if (attached || typeof document === 'undefined')
19
19
  return;
20
- 붙음 = true;
20
+ attached = true;
21
21
  document.addEventListener('pointerdown', (e) => { if (e.pointerType === 'touch')
22
- 마지막터치 = Date.now(); }, true);
23
- document.addEventListener('touchstart', () => { 마지막터치 = Date.now(); }, { capture: true, passive: true });
22
+ lastTouchAt = Date.now(); }, true);
23
+ document.addEventListener('touchstart', () => { lastTouchAt = Date.now(); }, { capture: true, passive: true });
24
24
  }
25
- 붙이기();
25
+ attach();
26
26
  /** 터치 포인터 이벤트인가 */
27
27
  export function isTouchPointer(e) {
28
28
  return !!e && e.pointerType === 'touch';
29
29
  }
30
30
  /** 방금(1초 안) 화면을 톡했나 — 톡 뒤 따라오는 focus·호환 마우스 이벤트를 거를 때 */
31
31
  export function recentlyTouched(ms = 1000) {
32
- 붙이기();
33
- return Date.now() - 마지막터치 < ms;
32
+ attach();
33
+ return Date.now() - lastTouchAt < ms;
34
34
  }
35
35
  /**
36
36
  * 톡으로 뒤집는 손잡이. `open` 은 지금 열림, `setOpen(next, el)` 은 열기(el 은 앵커)·닫기.
@@ -45,39 +45,39 @@ export function useTouchToggle(open, setOpen, rootRef, { enabled = true, fallbac
45
45
  const openRef = useLatestRef(open);
46
46
  const setOpenRef = useLatestRef(setOpen);
47
47
  /** 이 트리거 위 톡 누름 때의 열림(null 이면 톡 누름이 아니었다) */
48
- const 누를때열림 = useRef(null);
49
- const 뒤집은시각 = useRef(0);
48
+ const openOnPress = useRef(null);
49
+ const toggledAt = useRef(0);
50
50
  useEffect(() => {
51
51
  if (!enabled || typeof document === 'undefined')
52
52
  return;
53
- const 누름 = (ev) => {
53
+ const onPress = (ev) => {
54
54
  const pe = ev;
55
55
  const t = ev.target;
56
56
  const root = rootRef.current;
57
- const 안 = !!(t && root && root.contains(t));
57
+ const inside = !!(t && root && root.contains(t));
58
58
  if (pe.pointerType !== 'touch') {
59
- if (안)
60
- 누를때열림.current = null;
59
+ if (inside)
60
+ openOnPress.current = null;
61
61
  return;
62
62
  }
63
- if (안) {
64
- 누를때열림.current = openRef.current;
63
+ if (inside) {
64
+ openOnPress.current = openRef.current;
65
65
  return;
66
66
  }
67
- 누를때열림.current = null;
67
+ openOnPress.current = null;
68
68
  if (openRef.current && !(t && t.closest?.('.tooltip-content')))
69
69
  setOpenRef.current(false, root);
70
70
  };
71
- document.addEventListener('pointerdown', 누름, true);
72
- return () => document.removeEventListener('pointerdown', 누름, true);
71
+ document.addEventListener('pointerdown', onPress, true);
72
+ return () => document.removeEventListener('pointerdown', onPress, true);
73
73
  }, [enabled, rootRef, openRef, setOpenRef]);
74
74
  const touchPress = useCallback((el) => {
75
- const 전 = 누를때열림.current;
76
- if (전 === null)
75
+ const before = openOnPress.current;
76
+ if (before === null)
77
77
  return false;
78
- 누를때열림.current = null;
79
- 뒤집은시각.current = Date.now();
80
- setOpenRef.current(!전, el ?? rootRef.current);
78
+ openOnPress.current = null;
79
+ toggledAt.current = Date.now();
80
+ setOpenRef.current(!before, el ?? rootRef.current);
81
81
  return true;
82
82
  }, [rootRef, setOpenRef]);
83
83
  /* 막힌 native 단추 — 포인터 이벤트가 안 오면 감싼 요소에서 톡(touchstart→touchend)을 받아 자리로 가린다 */
@@ -88,27 +88,27 @@ export function useTouchToggle(open, setOpen, rootRef, { enabled = true, fallbac
88
88
  const parent = root?.parentElement;
89
89
  if (!root || !parent)
90
90
  return;
91
- let 시작때열림 = null;
92
- const 안에있나 = (e) => {
91
+ let openAtStart = null;
92
+ const isInside = (e) => {
93
93
  const p = e.changedTouches?.[0];
94
94
  if (!p)
95
95
  return false;
96
96
  const r = root.getBoundingClientRect();
97
97
  return p.clientX >= r.left && p.clientX <= r.right && p.clientY >= r.top && p.clientY <= r.bottom;
98
98
  };
99
- const 시작 = (e) => { 시작때열림 = 안에있나(e) ? openRef.current : null; };
100
- const 끝 = (e) => {
101
- const 전 = 시작때열림;
102
- 시작때열림 = null;
103
- if (전 === null || Date.now() - 뒤집은시각.current < 700 || !안에있나(e))
99
+ const onStart = (e) => { openAtStart = isInside(e) ? openRef.current : null; };
100
+ const onEnd = (e) => {
101
+ const before = openAtStart;
102
+ openAtStart = null;
103
+ if (before === null || Date.now() - toggledAt.current < 700 || !isInside(e))
104
104
  return;
105
- 누를때열림.current = null;
106
- 뒤집은시각.current = Date.now();
107
- setOpenRef.current(!전, root);
105
+ openOnPress.current = null;
106
+ toggledAt.current = Date.now();
107
+ setOpenRef.current(!before, root);
108
108
  };
109
- parent.addEventListener('touchstart', 시작, { passive: true });
110
- parent.addEventListener('touchend', 끝);
111
- return () => { parent.removeEventListener('touchstart', 시작); parent.removeEventListener('touchend', 끝); };
109
+ parent.addEventListener('touchstart', onStart, { passive: true });
110
+ parent.addEventListener('touchend', onEnd);
111
+ return () => { parent.removeEventListener('touchstart', onStart); parent.removeEventListener('touchend', onEnd); };
112
112
  }, [enabled, fallbackOnParent, rootRef, openRef, setOpenRef]);
113
113
  return { touchPress };
114
114
  }
@@ -10,15 +10,15 @@ import { pushEsc } from './escStack';
10
10
  * 트리거 조상 가운데 가장 높은 z-index 바로 위(없으면 `--z-menu`)로 둔다. 그 층들은 모두 알림·토스트 아래다.
11
11
  */
12
12
  export function anchoredMenuZIndex(trigger) {
13
- let 가장높은 = 0;
13
+ let tallest = 0;
14
14
  if (trigger && typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {
15
15
  for (let el = trigger; el && el !== document.body && el !== document.documentElement; el = el.parentElement) {
16
16
  const z = parseInt(window.getComputedStyle(el).zIndex, 10);
17
- if (!Number.isNaN(z) && z > 가장높은)
18
- 가장높은 = z;
17
+ if (!Number.isNaN(z) && z > tallest)
18
+ tallest = z;
19
19
  }
20
20
  }
21
- return 가장높은 > 0 ? `max(var(--z-menu, 1500), ${가장높은 + 1})` : 'var(--z-menu, 1500)';
21
+ return tallest > 0 ? `max(var(--z-menu, 1500), ${tallest + 1})` : 'var(--z-menu, 1500)';
22
22
  }
23
23
  /**
24
24
  * 트리거 요소에 fixed 포지션으로 앵커링되는 메뉴/드롭다운/팝오버의 공용 로직.
@@ -53,28 +53,28 @@ export function useAnchoredMenu({ computeStyle, onScroll = 'close', onResize = '
53
53
  (약 40px)이 오르내린다. 높이가 바뀔 때마다 다시 센다.
54
54
  **높이가 달라졌을 때만** 다시 센다 — 안 그러면 셈이 자리를 바꾸고 자리가 다시
55
55
  셈을 불러 끝없이 돈다. */
56
- let 잰높이 = menuRef.current?.offsetHeight ?? 0;
57
- if (잰높이 > 0)
56
+ let measuredHeight = menuRef.current?.offsetHeight ?? 0;
57
+ if (measuredHeight > 0)
58
58
  recalc();
59
59
  /* **오가는 것을 끊는다.** 높이가 달라질 때만 세는 것으로는 두 꼴을 번갈아 오가는 것을
60
60
  못 막는다(A 로 자리를 잡으면 높이가 B 가 되고, B 로 잡으면 다시 A 가 되는 꼴).
61
61
  한 번 여는 동안 몇 번까지만 센다 — 제대로 된 다시 재기는 한두 번이면 끝난다. */
62
- let 센횟수 = 0;
63
- let 판관찰 = null;
62
+ let measureCount = 0;
63
+ let panelObserver = null;
64
64
  if (typeof ResizeObserver !== 'undefined' && menuRef.current) {
65
- 판관찰 = new ResizeObserver(() => {
65
+ panelObserver = new ResizeObserver(() => {
66
66
  const h = menuRef.current?.offsetHeight ?? 0;
67
- if (h === 잰높이)
67
+ if (h === measuredHeight)
68
68
  return;
69
- 잰높이 = h;
70
- if (++센횟수 > 4) {
71
- 판관찰?.disconnect();
72
- 판관찰 = null;
69
+ measuredHeight = h;
70
+ if (++measureCount > 4) {
71
+ panelObserver?.disconnect();
72
+ panelObserver = null;
73
73
  return;
74
74
  }
75
75
  recalc();
76
76
  });
77
- 판관찰.observe(menuRef.current);
77
+ panelObserver.observe(menuRef.current);
78
78
  }
79
79
  const handleOutsideClick = (e) => {
80
80
  const target = e.target;
@@ -94,21 +94,21 @@ export function useAnchoredMenu({ computeStyle, onScroll = 'close', onResize = '
94
94
  capture 로** 받는다 — `DatePicker` 가 `onScroll: 'reposition'` 을 주므로 달력이 열린 동안 굴리면
95
95
  틀마다 여러 번 돌았다. **닫는 길('close')은 안 미룬다** — 미루면 앵커에서 떨어진 판이 한 틀 더
96
96
  허공에 남는다(Tooltip 과 같은 가름). 미루는 것은 *자리* 뿐이다. */
97
- let 자리예약 = null;
98
- const 자리다시 = () => {
97
+ let rafId = null;
98
+ const reposition = () => {
99
99
  if (typeof requestAnimationFrame === 'undefined') {
100
100
  recalc();
101
101
  return;
102
102
  }
103
- if (자리예약 !== null)
103
+ if (rafId !== null)
104
104
  return;
105
- 자리예약 = requestAnimationFrame(() => { 자리예약 = null; recalc(); });
105
+ rafId = requestAnimationFrame(() => { rafId = null; recalc(); });
106
106
  };
107
107
  const handleScroll = (e) => {
108
108
  if (menuRef.current?.contains(e.target))
109
109
  return;
110
110
  if (onScroll === 'reposition')
111
- 자리다시();
111
+ reposition();
112
112
  else if (onScroll === 'close')
113
113
  setOpen(false);
114
114
  };
@@ -116,17 +116,17 @@ export function useAnchoredMenu({ computeStyle, onScroll = 'close', onResize = '
116
116
  window.addEventListener('scroll', handleScroll, true);
117
117
  const handleResize = () => {
118
118
  if (onResize === 'reposition')
119
- 자리다시();
119
+ reposition();
120
120
  else if (onResize === 'close')
121
121
  setOpen(false);
122
122
  };
123
123
  /* 회전(orientationchange)도 함께 — iOS Safari 는 회전 직후 resize 가 늦거나 옛 크기로 오는 일이 있어 회전 뒤 한 번 더 잰다 */
124
- let 회전타이머 = null;
124
+ let rotateTimer = null;
125
125
  const handleOrientation = () => {
126
126
  handleResize();
127
- if (회전타이머)
128
- clearTimeout(회전타이머);
129
- 회전타이머 = setTimeout(() => { 회전타이머 = null; if (onResize === 'reposition')
127
+ if (rotateTimer)
128
+ clearTimeout(rotateTimer);
129
+ rotateTimer = setTimeout(() => { rotateTimer = null; if (onResize === 'reposition')
130
130
  recalc(); }, 250);
131
131
  };
132
132
  if (onResize !== 'ignore') {
@@ -134,9 +134,9 @@ export function useAnchoredMenu({ computeStyle, onScroll = 'close', onResize = '
134
134
  window.addEventListener('orientationchange', handleOrientation);
135
135
  }
136
136
  return () => {
137
- if (자리예약 !== null)
138
- cancelAnimationFrame(자리예약);
139
- 판관찰?.disconnect();
137
+ if (rafId !== null)
138
+ cancelAnimationFrame(rafId);
139
+ panelObserver?.disconnect();
140
140
  document.removeEventListener('mousedown', handleOutsideClick);
141
141
  document.removeEventListener('pointerdown', handleOutsideClick);
142
142
  if (onScroll !== 'ignore')
@@ -145,8 +145,8 @@ export function useAnchoredMenu({ computeStyle, onScroll = 'close', onResize = '
145
145
  window.removeEventListener('resize', handleResize);
146
146
  window.removeEventListener('orientationchange', handleOrientation);
147
147
  }
148
- if (회전타이머)
149
- clearTimeout(회전타이머);
148
+ if (rotateTimer)
149
+ clearTimeout(rotateTimer);
150
150
  };
151
151
  }, [open, recalc, onScroll, onResize, ignoreOutsideClickSelector]);
152
152
  /* ESC 로 닫기 — **공유 ESC 스택**에 올린다(Popup 과 같은 스택). 전에는 document 에서 따로 들어,
@@ -34,25 +34,25 @@ export function useDailyTrainingSelection(userId) {
34
34
  const [hasData, setHasData] = useState(false);
35
35
  /* 오늘(KST) — 자정을 넘기면 바뀌어 아래 선택을 **새 날짜 시드로** 다시 계산하게 한다. 전에는 userId 만 봐서 화면을 열어 둔 채
36
36
  자정을 넘기면 어제 시드의 세트가 '오늘' 로 보이는데 완료 기록에는 새 날짜가 찍혔다. */
37
- const [오늘, set오늘] = useState(() => getTodayKst());
37
+ const [today, setToday] = useState(() => getTodayKst());
38
38
  useEffect(() => {
39
- const 다음자정까지 = () => {
40
- const 지금 = Date.now();
41
- const kst = new Date(지금 + 9 * 3600_000);
42
- const 자정 = Date.UTC(kst.getUTCFullYear(), kst.getUTCMonth(), kst.getUTCDate() + 1) - 9 * 3600_000;
43
- return Math.max(1000, 자정 - 지금 + 1000);
39
+ const msUntilMidnight = () => {
40
+ const now = Date.now();
41
+ const kst = new Date(now + 9 * 3600_000);
42
+ const midnight = Date.UTC(kst.getUTCFullYear(), kst.getUTCMonth(), kst.getUTCDate() + 1) - 9 * 3600_000;
43
+ return Math.max(1000, midnight - now + 1000);
44
44
  };
45
- let t = setTimeout(function 넘김() { set오늘(getTodayKst()); t = setTimeout(넘김, 다음자정까지()); }, 다음자정까지());
45
+ let t = setTimeout(function tick() { setToday(getTodayKst()); t = setTimeout(tick, msUntilMidnight()); }, msUntilMidnight());
46
46
  return () => clearTimeout(t);
47
47
  }, []);
48
48
  useEffect(() => {
49
49
  let cancelled = false;
50
- let 떠남 = false;
51
- let 적용됨 = false;
50
+ let left = false;
51
+ let applied = false;
52
52
  let timer;
53
53
  let attempts = 0;
54
- let 받는중 = null;
55
- let 다음받기 = 0; // 이 attempts 에 이르면 자료 받기를 다시 청한다(2초 = 8틱마다)
54
+ let fetching = null;
55
+ let nextFetchAt = 0; // 이 attempts 에 이르면 자료 받기를 다시 청한다(2초 = 8틱마다)
56
56
  const MAX_ATTEMPTS = 40; // 250ms * 40 = 10초 후 폴링 중단(풀이 영영 비는 경우 무한루프 방지)
57
57
  const stop = () => { if (timer !== undefined) {
58
58
  clearInterval(timer);
@@ -66,9 +66,9 @@ export function useDailyTrainingSelection(userId) {
66
66
  }
67
67
  catch { }
68
68
  const apply = (sel) => {
69
- if (떠남)
69
+ if (left)
70
70
  return;
71
- 적용됨 = true;
71
+ applied = true;
72
72
  todayRef.current = sel.today;
73
73
  yesterdayRef.current = sel.yesterday;
74
74
  setLoaded(true);
@@ -92,19 +92,19 @@ export function useDailyTrainingSelection(userId) {
92
92
  배포(형식 오류)에서 10초에 850KB 자료를 40번 받았다. 그 사이 폴링은 선택만 다시 계산한다. */
93
93
  /* **받는 중이면 이 틱은 건너뛴다** — 받기는 3초까지 기다리는데 250ms 틱이 그 사이 attempts 가 안 오른 채 들어와
94
94
  ensureTrainingDataLoaded 를 매번 새로 불러, 끝나지 않은 받기마다 3초 타이머가 열두 개씩 쌓였다. 진행 중인 약속을 하나만 둔다. */
95
- if (받는중) {
95
+ if (fetching) {
96
96
  if (++attempts >= MAX_ATTEMPTS)
97
- 포기();
97
+ giveUp();
98
98
  return;
99
99
  } // 건너뛴 틱도 10초 시한에 센다
100
- if (attempts >= 다음받기) {
101
- 다음받기 = attempts + 8;
102
- 받는중 = ensureTrainingDataLoaded(3000).catch(() => false);
100
+ if (attempts >= nextFetchAt) {
101
+ nextFetchAt = attempts + 8;
102
+ fetching = ensureTrainingDataLoaded(3000).catch(() => false);
103
103
  try {
104
- await 받는중;
104
+ await fetching;
105
105
  }
106
106
  finally {
107
- 받는중 = null;
107
+ fetching = null;
108
108
  }
109
109
  if (cancelled)
110
110
  return;
@@ -118,35 +118,35 @@ export function useDailyTrainingSelection(userId) {
118
118
  // 포기한다. hasData 를 반드시 false 로 되돌린다 — userId 가 바뀌어 다시 돌 때,
119
119
  // 앞 사람 것이 성공했다면 true 로 남아 있어 "자료가 있다" 고 거짓말하게 된다.
120
120
  if (++attempts >= MAX_ATTEMPTS)
121
- 포기();
121
+ giveUp();
122
122
  };
123
- function 포기() { setLoaded(true); setHasData(false); cancelled = true; stop(); }
123
+ function giveUp() { setLoaded(true); setHasData(false); cancelled = true; stop(); }
124
124
  /* 포기한 뒤(10초)라도 자료가 도착하면 다시 계산한다 — 느린 회선에서 13초 걸려 받은 회원은 새로고침 전까지 "자료를 못 찾음" 에 남았다 */
125
- const 자료도착 = () => {
126
- if (떠남 || 적용됨)
125
+ const onData = () => {
126
+ if (left || applied)
127
127
  return;
128
128
  const sel = resolveSelection(forceRefresh, undefined, userId);
129
129
  if (hasItems(sel.today))
130
130
  apply(sel);
131
131
  };
132
132
  try {
133
- window.addEventListener('speechear.data.updated', 자료도착);
133
+ window.addEventListener('speechear.data.updated', onData);
134
134
  }
135
135
  catch { }
136
136
  tryFill();
137
137
  timer = setInterval(tryFill, 250);
138
138
  return () => {
139
139
  cancelled = true;
140
- 떠남 = true;
140
+ left = true;
141
141
  stop();
142
142
  try {
143
- window.removeEventListener('speechear.data.updated', 자료도착);
143
+ window.removeEventListener('speechear.data.updated', onData);
144
144
  }
145
145
  catch { }
146
146
  };
147
147
  // 오늘 은 날짜가 바뀌었다는 신호 — resolveSelection 이 안에서 오늘 날짜를 읽는다
148
148
  // eslint-disable-next-line react-hooks/exhaustive-deps
149
- }, [userId, 오늘]);
149
+ }, [userId, today]);
150
150
  const getTrainingPerType = useCallback((typeKey) => {
151
151
  const src = showYesterday ? yesterdayRef.current : todayRef.current;
152
152
  const fallback = showYesterday ? todayRef.current : yesterdayRef.current;
@@ -26,8 +26,8 @@ export const DocumentTitleSuppressContext = createContext(false);
26
26
  /* 제목을 건 화면들 — 나중에 건 것이 위. 떠날 때 **남은 맨 위 제목**으로 돌린다.
27
27
  전에는 저마다 건 순간의 제목(prev)을 기억했다가 되돌려, A 가 떠 있는 동안 B 가 붙고 A 가 나중에 떠나면
28
28
  A 의 prev 로 돌아가 B 의 제목이 지워졌다(말귀 CustomersPage 가 겪고 우회해 둠). */
29
- const 걸린제목 = [];
30
- let 처음제목 = null;
29
+ const titleStack = [];
30
+ let originalTitle = null;
31
31
  /**
32
32
  * 현재 화면의 제목을 `document.title`에 반영한다 — `'<제목> · <앱 이름>'`.
33
33
  *
@@ -47,16 +47,16 @@ export function useDocumentTitle(title) {
47
47
  return;
48
48
  if (typeof title !== 'string' || !title)
49
49
  return;
50
- if (걸린제목.length === 0)
51
- 처음제목 = document.title;
52
- const 칸 = { text: BASE_TITLE ? `${title}${SEP}${BASE_TITLE}` : title };
53
- 걸린제목.push(칸);
54
- document.title = 칸.text;
50
+ if (titleStack.length === 0)
51
+ originalTitle = document.title;
52
+ const entry = { text: BASE_TITLE ? `${title}${SEP}${BASE_TITLE}` : title };
53
+ titleStack.push(entry);
54
+ document.title = entry.text;
55
55
  return () => {
56
- const i = 걸린제목.lastIndexOf(칸);
56
+ const i = titleStack.lastIndexOf(entry);
57
57
  if (i >= 0)
58
- 걸린제목.splice(i, 1);
59
- document.title = 걸린제목.length ? 걸린제목[걸린제목.length - 1].text : (처음제목 ?? document.title);
58
+ titleStack.splice(i, 1);
59
+ document.title = titleStack.length ? titleStack[titleStack.length - 1].text : (originalTitle ?? document.title);
60
60
  };
61
61
  }, [title, suppressed]);
62
62
  }
@@ -43,13 +43,13 @@ export function useEditableList(emptyItem, initialList = []) {
43
43
  /* 편집이 **취소로** 끝나면 그 사이 건너뛴 목록을 받는다 — 전에는 initialList 가 바뀔 때만 돌아, 편집 중 들어온 서버 목록이
44
44
  취소한 뒤에도 끝내 안 보였다(방문 예정을 고치다 취소하면 그 사이 다른 방문이 빠진 채). commitEdit 으로 끝난 때는
45
45
  방금 커밋한 편집을 옛 목록으로 덮지 않도록 한 번 넘긴다(다음 목록이 오면 그때 받는다). */
46
- const 커밋으로끝남Ref = useRef(false);
46
+ const endedByCommitRef = useRef(false);
47
47
  const prevInitialRef = useRef(null);
48
48
  useEffect(() => {
49
49
  if (isEditing)
50
50
  return;
51
- if (커밋으로끝남Ref.current) {
52
- 커밋으로끝남Ref.current = false;
51
+ if (endedByCommitRef.current) {
52
+ endedByCommitRef.current = false;
53
53
  return;
54
54
  }
55
55
  const prev = prevInitialRef.current;
@@ -80,7 +80,7 @@ export function useEditableList(emptyItem, initialList = []) {
80
80
  setSelected(updated);
81
81
  // 편집 중이었을 때만 표시한다(편집이 아닌데 표시하면 다음 목록 한 번을 헛되이 건너뛴다). 같은 값을 넣는 표시라 두 번 불려도 같다
82
82
  setIsEditing(prev => { if (prev)
83
- 커밋으로끝남Ref.current = true; return false; });
83
+ endedByCommitRef.current = true; return false; });
84
84
  }, []);
85
85
  const remove = useCallback((item) => {
86
86
  setList(prev => prev.filter(v => !isSameEditableItem(v, item)));
@@ -40,8 +40,8 @@ export function useFocusTrap(panelRef, enabled = true) {
40
40
  const active = document.activeElement;
41
41
  /* 포커스가 **판 자체**(tabIndex=-1 — 본문 글자를 누르면 판이 받는다)에 있으면 첫 칸·끝 칸 어느 쪽도 아니라
42
42
  되감지 않아, Shift+Tab 한 번에 뒤 화면으로 나갔다. 판 자체면 Shift+Tab 은 끝 칸으로 보낸다. */
43
- const 판자체 = active === panel;
44
- if (e.shiftKey && (active === first || 판자체)) {
43
+ const onPanelItself = active === panel;
44
+ if (e.shiftKey && (active === first || onPanelItself)) {
45
45
  e.preventDefault();
46
46
  last.focus();
47
47
  }
@@ -5,7 +5,7 @@ export default function useFontScale({ storageKey = 'fontScale',
5
5
  // 기본값을 중앙화: Training/Tooltip 텍스트 박스 공통값
6
6
  baseFont = 20, minScale = 0.8, maxScale = 1.4, step = 0.1, lineHeightRatio = 1.4, } = {}) {
7
7
  const clamp = (v) => Math.max(minScale, Math.min(maxScale, Number(v)));
8
- const 읽기 = (key) => {
8
+ const read = (key) => {
9
9
  try {
10
10
  const raw = localStorage.getItem(key);
11
11
  const parsed = raw ? Number(raw) : 1;
@@ -21,13 +21,13 @@ baseFont = 20, minScale = 0.8, maxScale = 1.4, step = 0.1, lineHeightRatio = 1.4
21
21
  return clamp(1);
22
22
  }
23
23
  };
24
- const [scale, setScaleRaw] = useState(() => 읽기(storageKey));
24
+ const [scale, setScaleRaw] = useState(() => read(storageKey));
25
25
  /* 열쇠가 바뀌면 **그 열쇠의 저장값** 으로 — 전에는 앞 열쇠의 값을 들고 있다가 아래 쓰기가 새 열쇠의 저장값을 덮었다.
26
26
  렌더 중에 맞추므로 쓰기 effect 는 새 값으로만 돈다. */
27
- const [읽은열쇠, set읽은열쇠] = useState(storageKey);
28
- if (읽은열쇠 !== storageKey) {
29
- set읽은열쇠(storageKey);
30
- setScaleRaw(읽기(storageKey));
27
+ const [loadedKey, setLoadedKey] = useState(storageKey);
28
+ if (loadedKey !== storageKey) {
29
+ setLoadedKey(storageKey);
30
+ setScaleRaw(read(storageKey));
31
31
  }
32
32
  useEffect(() => {
33
33
  try {