smarthr-ui 96.1.3 → 97.0.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 (146) hide show
  1. package/lib/components/Combobox/helper.cjs +29 -13
  2. package/lib/components/Combobox/helper.cjs.map +1 -1
  3. package/lib/components/Combobox/helper.d.ts +1 -1
  4. package/lib/components/Combobox/helper.js +29 -13
  5. package/lib/components/Combobox/helper.js.map +1 -1
  6. package/lib/components/Combobox/useOptions.cjs +21 -17
  7. package/lib/components/Combobox/useOptions.cjs.map +1 -1
  8. package/lib/components/Combobox/useOptions.js +21 -17
  9. package/lib/components/Combobox/useOptions.js.map +1 -1
  10. package/lib/components/Dialog/DialogContentInner.cjs +8 -1
  11. package/lib/components/Dialog/DialogContentInner.cjs.map +1 -1
  12. package/lib/components/Dialog/DialogContentInner.js +9 -2
  13. package/lib/components/Dialog/DialogContentInner.js.map +1 -1
  14. package/lib/components/Dialog/ModelessDialog/ModelessDialog.cjs +13 -11
  15. package/lib/components/Dialog/ModelessDialog/ModelessDialog.cjs.map +1 -1
  16. package/lib/components/Dialog/ModelessDialog/ModelessDialog.js +13 -11
  17. package/lib/components/Dialog/ModelessDialog/ModelessDialog.js.map +1 -1
  18. package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.cjs +5 -2
  19. package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.cjs.map +1 -1
  20. package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.js +5 -2
  21. package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.js.map +1 -1
  22. package/lib/components/Disclosure/DisclosureTrigger.cjs +39 -18
  23. package/lib/components/Disclosure/DisclosureTrigger.cjs.map +1 -1
  24. package/lib/components/Disclosure/DisclosureTrigger.js +40 -19
  25. package/lib/components/Disclosure/DisclosureTrigger.js.map +1 -1
  26. package/lib/components/Disclosure/useDisclosure.cjs +8 -14
  27. package/lib/components/Disclosure/useDisclosure.cjs.map +1 -1
  28. package/lib/components/Disclosure/useDisclosure.js +8 -14
  29. package/lib/components/Disclosure/useDisclosure.js.map +1 -1
  30. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs +4 -2
  31. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs.map +1 -1
  32. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js +4 -2
  33. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js.map +1 -1
  34. package/lib/components/Dropdown/DropdownTrigger.cjs +3 -0
  35. package/lib/components/Dropdown/DropdownTrigger.cjs.map +1 -1
  36. package/lib/components/Dropdown/DropdownTrigger.js +3 -0
  37. package/lib/components/Dropdown/DropdownTrigger.js.map +1 -1
  38. package/lib/components/InputFile/InputFileMultiplyAppendable.cjs +12 -17
  39. package/lib/components/InputFile/InputFileMultiplyAppendable.cjs.map +1 -1
  40. package/lib/components/InputFile/InputFileMultiplyAppendable.js +12 -17
  41. package/lib/components/InputFile/InputFileMultiplyAppendable.js.map +1 -1
  42. package/lib/components/LineClamp/LineClamp.cjs +14 -3
  43. package/lib/components/LineClamp/LineClamp.cjs.map +1 -1
  44. package/lib/components/LineClamp/LineClamp.js +14 -3
  45. package/lib/components/LineClamp/LineClamp.js.map +1 -1
  46. package/lib/components/Select/Select.cjs +11 -11
  47. package/lib/components/Select/Select.cjs.map +1 -1
  48. package/lib/components/Select/Select.js +12 -12
  49. package/lib/components/Select/Select.js.map +1 -1
  50. package/lib/components/TabBar/TabItem.cjs +11 -9
  51. package/lib/components/TabBar/TabItem.cjs.map +1 -1
  52. package/lib/components/TabBar/TabItem.d.ts +2 -2
  53. package/lib/components/TabBar/TabItem.js +13 -11
  54. package/lib/components/TabBar/TabItem.js.map +1 -1
  55. package/lib/hooks/useHandleEscape.cjs +4 -4
  56. package/lib/hooks/useHandleEscape.cjs.map +1 -1
  57. package/lib/hooks/useHandleEscape.d.ts +1 -1
  58. package/lib/hooks/useHandleEscape.js +4 -4
  59. package/lib/hooks/useHandleEscape.js.map +1 -1
  60. package/lib/intl/Localizer.d.ts +3 -3
  61. package/lib/intl/locales/en_us.json.cjs +133 -0
  62. package/lib/intl/locales/en_us.json.cjs.map +1 -0
  63. package/lib/intl/locales/en_us.json.js +131 -0
  64. package/lib/intl/locales/en_us.json.js.map +1 -0
  65. package/lib/intl/locales/id_id.json.cjs +133 -0
  66. package/lib/intl/locales/id_id.json.cjs.map +1 -0
  67. package/lib/intl/locales/id_id.json.js +131 -0
  68. package/lib/intl/locales/id_id.json.js.map +1 -0
  69. package/lib/intl/locales/index.cjs +24 -18
  70. package/lib/intl/locales/index.cjs.map +1 -1
  71. package/lib/intl/locales/index.d.ts +1135 -1136
  72. package/lib/intl/locales/index.js +25 -19
  73. package/lib/intl/locales/index.js.map +1 -1
  74. package/lib/intl/locales/ja.json.cjs +133 -0
  75. package/lib/intl/locales/ja.json.cjs.map +1 -0
  76. package/lib/intl/locales/ja.json.js +131 -0
  77. package/lib/intl/locales/ja.json.js.map +1 -0
  78. package/lib/intl/locales/ja_easy.json.cjs +133 -0
  79. package/lib/intl/locales/ja_easy.json.cjs.map +1 -0
  80. package/lib/intl/locales/ja_easy.json.js +131 -0
  81. package/lib/intl/locales/ja_easy.json.js.map +1 -0
  82. package/lib/intl/locales/ko_kr.json.cjs +133 -0
  83. package/lib/intl/locales/ko_kr.json.cjs.map +1 -0
  84. package/lib/intl/locales/ko_kr.json.js +131 -0
  85. package/lib/intl/locales/ko_kr.json.js.map +1 -0
  86. package/lib/intl/locales/pt_br.json.cjs +133 -0
  87. package/lib/intl/locales/pt_br.json.cjs.map +1 -0
  88. package/lib/intl/locales/pt_br.json.js +131 -0
  89. package/lib/intl/locales/pt_br.json.js.map +1 -0
  90. package/lib/intl/locales/vi_vn.json.cjs +133 -0
  91. package/lib/intl/locales/vi_vn.json.cjs.map +1 -0
  92. package/lib/intl/locales/vi_vn.json.js +131 -0
  93. package/lib/intl/locales/vi_vn.json.js.map +1 -0
  94. package/lib/intl/locales/zh_hans_cn.json.cjs +133 -0
  95. package/lib/intl/locales/zh_hans_cn.json.cjs.map +1 -0
  96. package/lib/intl/locales/zh_hans_cn.json.js +131 -0
  97. package/lib/intl/locales/zh_hans_cn.json.js.map +1 -0
  98. package/lib/intl/locales/zh_hant_tw.json.cjs +133 -0
  99. package/lib/intl/locales/zh_hant_tw.json.cjs.map +1 -0
  100. package/lib/intl/locales/zh_hant_tw.json.js +131 -0
  101. package/lib/intl/locales/zh_hant_tw.json.js.map +1 -0
  102. package/lib/intl/useIntl.cjs.map +1 -1
  103. package/lib/intl/useIntl.d.ts +3 -3
  104. package/lib/intl/useIntl.js.map +1 -1
  105. package/metadata.json +1 -1
  106. package/package.json +2 -1
  107. package/lib/intl/locales/en_us.cjs +0 -135
  108. package/lib/intl/locales/en_us.cjs.map +0 -1
  109. package/lib/intl/locales/en_us.d.ts +0 -128
  110. package/lib/intl/locales/en_us.js +0 -133
  111. package/lib/intl/locales/en_us.js.map +0 -1
  112. package/lib/intl/locales/id_id.cjs +0 -135
  113. package/lib/intl/locales/id_id.cjs.map +0 -1
  114. package/lib/intl/locales/id_id.d.ts +0 -128
  115. package/lib/intl/locales/id_id.js +0 -133
  116. package/lib/intl/locales/id_id.js.map +0 -1
  117. package/lib/intl/locales/ja_easy.cjs +0 -135
  118. package/lib/intl/locales/ja_easy.cjs.map +0 -1
  119. package/lib/intl/locales/ja_easy.d.ts +0 -128
  120. package/lib/intl/locales/ja_easy.js +0 -133
  121. package/lib/intl/locales/ja_easy.js.map +0 -1
  122. package/lib/intl/locales/ko_kr.cjs +0 -135
  123. package/lib/intl/locales/ko_kr.cjs.map +0 -1
  124. package/lib/intl/locales/ko_kr.d.ts +0 -128
  125. package/lib/intl/locales/ko_kr.js +0 -133
  126. package/lib/intl/locales/ko_kr.js.map +0 -1
  127. package/lib/intl/locales/pt_br.cjs +0 -135
  128. package/lib/intl/locales/pt_br.cjs.map +0 -1
  129. package/lib/intl/locales/pt_br.d.ts +0 -128
  130. package/lib/intl/locales/pt_br.js +0 -133
  131. package/lib/intl/locales/pt_br.js.map +0 -1
  132. package/lib/intl/locales/vi_vn.cjs +0 -135
  133. package/lib/intl/locales/vi_vn.cjs.map +0 -1
  134. package/lib/intl/locales/vi_vn.d.ts +0 -128
  135. package/lib/intl/locales/vi_vn.js +0 -133
  136. package/lib/intl/locales/vi_vn.js.map +0 -1
  137. package/lib/intl/locales/zh_hans_cn.cjs +0 -135
  138. package/lib/intl/locales/zh_hans_cn.cjs.map +0 -1
  139. package/lib/intl/locales/zh_hans_cn.d.ts +0 -128
  140. package/lib/intl/locales/zh_hans_cn.js +0 -133
  141. package/lib/intl/locales/zh_hans_cn.js.map +0 -1
  142. package/lib/intl/locales/zh_hant_tw.cjs +0 -135
  143. package/lib/intl/locales/zh_hant_tw.cjs.map +0 -1
  144. package/lib/intl/locales/zh_hant_tw.d.ts +0 -128
  145. package/lib/intl/locales/zh_hant_tw.js +0 -133
  146. package/lib/intl/locales/zh_hant_tw.js.map +0 -1
@@ -2,19 +2,35 @@
2
2
 
3
3
  var index = require('../../_virtual/index3.cjs');
4
4
 
5
- function convertMatchableString(original) {
6
- return (original
7
- .replace(/\s/g, ' ')
8
- .replace(/’/g, "'")
9
- .replace(/[”“]/g, '"')
10
- .replace(/`/g, '`')
11
- .replace(/¥/g, '¥')
12
- .replace(/−/g, '-')
13
- .replace(/〜/g, '~')
14
- // unicode [!] から [}] の間に定義されている英数・記号を半角に変換
15
- .replace(/[!-}]/g, (str) => String.fromCharCode(str.charCodeAt(0) - 0xfee0))
16
- .toLowerCase());
17
- }
5
+ const CHAR_MAP = {
6
+ '\u2019': "'", // ’
7
+ '\u201C': '"', // “
8
+ '\u201D': '"', // ”
9
+ '`': '`',
10
+ '¥': '¥',
11
+ '−': '-',
12
+ '〜': '~',
13
+ };
14
+ // 変換対象:空白・特殊な全角記号(マップ変換用)および Unicode [!][}] の全角英数記号
15
+ // ※ [](全角マイナス)は正規表現の範囲指定ハイフンと誤認されないよう、エスケープなしで安全な位置に配置
16
+ const NORMALIZE_PATTERN = /[\s’“”`¥−〜\uFF01-\uFF5D]/g;
17
+ const WHITESPACE_PATTERN = /\s/;
18
+ const normalizeChar = (match) => {
19
+ // 空白文字の正規化
20
+ if (WHITESPACE_PATTERN.test(match))
21
+ return ' ';
22
+ // マッピングテーブルでの変換
23
+ const mappedMatch = CHAR_MAP[match];
24
+ if (mappedMatch !== undefined)
25
+ return mappedMatch;
26
+ // 全角英数記号の半角化
27
+ const code = match.charCodeAt(0);
28
+ if (code >= 0xff01 && code <= 0xff5d) {
29
+ return String.fromCharCode(code - 0xfee0);
30
+ }
31
+ return match;
32
+ };
33
+ const convertMatchableString = (original) => original.replace(NORMALIZE_PATTERN, normalizeChar).toLowerCase();
18
34
  function areItemsEqual(a, b) {
19
35
  return a.value === b.value && index(a.label) === index(b.label);
20
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"helper.cjs","sources":["../../../src/components/Combobox/helper.ts"],"sourcesContent":["import innerText from 'react-innertext'\n\nimport type { ComboboxItem } from './types'\n\nexport function convertMatchableString(original: string) {\n return (\n original\n .replace(/\\s/g, ' ')\n .replace(/’/g, \"'\")\n .replace(/[”“]/g, '\"')\n .replace(/`/g, '`')\n .replace(/¥/g, '¥')\n .replace(/−/g, '-')\n .replace(/〜/g, '~')\n // unicode [!] から [] の間に定義されている英数・記号を半角に変換\n .replace(/[!-}]/g, (str) => String.fromCharCode(str.charCodeAt(0) - 0xfee0))\n .toLowerCase()\n )\n}\n\nexport function areItemsEqual<T>(a: ComboboxItem<T>, b: ComboboxItem<T>) {\n return a.value === b.value && innerText(a.label) === innerText(b.label)\n}\n"],"names":["innerText"],"mappings":";;;;AAIM,SAAU,sBAAsB,CAAC,QAAgB,EAAA;AACrD,IAAA,QACE;AACG,SAAA,OAAO,CAAC,KAAK,EAAE,GAAG;AAClB,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;AACjB,SAAA,OAAO,CAAC,OAAO,EAAE,GAAG;AACpB,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;AACjB,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;AACjB,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;AACjB,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;;SAEjB,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;SAC1E,WAAW,EAAE;AAEpB;AAEM,SAAU,aAAa,CAAI,CAAkB,EAAE,CAAkB,EAAA;IACrE,OAAO,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,IAAIA,KAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAKA,KAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACzE;;;;;"}
1
+ {"version":3,"file":"helper.cjs","sources":["../../../src/components/Combobox/helper.ts"],"sourcesContent":["import innerText from 'react-innertext'\n\nimport type { ComboboxItem } from './types'\n\nconst CHAR_MAP: Record<string, string> = {\n '\\u2019': \"'\", // ’\n '\\u201C': '\"', // “\n '\\u201D': '\"', // ”\n '`': '`',\n '¥': '¥',\n '−': '-',\n '〜': '~',\n}\n\n// 変換対象:空白・特殊な全角記号(マップ変換用)および Unicode [!]〜[}] の全角英数記号\n// [](全角マイナス)は正規表現の範囲指定ハイフンと誤認されないよう、エスケープなしで安全な位置に配置\nconst NORMALIZE_PATTERN = /[\\s’“”`¥−〜\\uFF01-\\uFF5D]/g\nconst WHITESPACE_PATTERN = /\\s/\n\nconst normalizeChar = (match: string): string => {\n // 空白文字の正規化\n if (WHITESPACE_PATTERN.test(match)) return ' '\n\n // マッピングテーブルでの変換\n const mappedMatch = CHAR_MAP[match]\n if (mappedMatch !== undefined) return mappedMatch\n\n // 全角英数記号の半角化\n const code = match.charCodeAt(0)\n if (code >= 0xff01 && code <= 0xff5d) {\n return String.fromCharCode(code - 0xfee0)\n }\n\n return match\n}\n\nexport const convertMatchableString = (original: string) =>\n original.replace(NORMALIZE_PATTERN, normalizeChar).toLowerCase()\n\nexport function areItemsEqual<T>(a: ComboboxItem<T>, b: ComboboxItem<T>) {\n return a.value === b.value && innerText(a.label) === innerText(b.label)\n}\n"],"names":["innerText"],"mappings":";;;;AAIA,MAAM,QAAQ,GAA2B;IACvC,QAAQ,EAAE,GAAG;IACb,QAAQ,EAAE,GAAG;IACb,QAAQ,EAAE,GAAG;AACb,IAAA,GAAG,EAAE,GAAG;AACR,IAAA,GAAG,EAAE,GAAG;AACR,IAAA,GAAG,EAAE,GAAG;AACR,IAAA,GAAG,EAAE,GAAG;CACT;AAED;AACA;AACA,MAAM,iBAAiB,GAAG,2BAA2B;AACrD,MAAM,kBAAkB,GAAG,IAAI;AAE/B,MAAM,aAAa,GAAG,CAAC,KAAa,KAAY;;AAE9C,IAAA,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,GAAG;;AAG9C,IAAA,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC;IACnC,IAAI,WAAW,KAAK,SAAS;AAAE,QAAA,OAAO,WAAW;;IAGjD,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IAChC,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE;QACpC,OAAO,MAAM,CAAC,YAAY,CAAC,IAAI,GAAG,MAAM,CAAC;IAC3C;AAEA,IAAA,OAAO,KAAK;AACd,CAAC;MAEY,sBAAsB,GAAG,CAAC,QAAgB,KACrD,QAAQ,CAAC,OAAO,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC,WAAW;AAE1D,SAAU,aAAa,CAAI,CAAkB,EAAE,CAAkB,EAAA;IACrE,OAAO,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,IAAIA,KAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAKA,KAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACzE;;;;;"}
@@ -1,3 +1,3 @@
1
1
  import type { ComboboxItem } from './types';
2
- export declare function convertMatchableString(original: string): string;
2
+ export declare const convertMatchableString: (original: string) => string;
3
3
  export declare function areItemsEqual<T>(a: ComboboxItem<T>, b: ComboboxItem<T>): boolean;
@@ -1,18 +1,34 @@
1
1
  import innerText from '../../_virtual/index3.js';
2
2
 
3
- function convertMatchableString(original) {
4
- return (original
5
- .replace(/\s/g, ' ')
6
- .replace(/’/g, "'")
7
- .replace(/[”“]/g, '"')
8
- .replace(/`/g, '`')
9
- .replace(/¥/g, '¥')
10
- .replace(/−/g, '-')
11
- .replace(/〜/g, '~')
12
- // unicode [!] から [}] の間に定義されている英数・記号を半角に変換
13
- .replace(/[!-}]/g, (str) => String.fromCharCode(str.charCodeAt(0) - 0xfee0))
14
- .toLowerCase());
15
- }
3
+ const CHAR_MAP = {
4
+ '\u2019': "'", // ’
5
+ '\u201C': '"', // “
6
+ '\u201D': '"', // ”
7
+ '`': '`',
8
+ '¥': '¥',
9
+ '−': '-',
10
+ '〜': '~',
11
+ };
12
+ // 変換対象:空白・特殊な全角記号(マップ変換用)および Unicode [!][}] の全角英数記号
13
+ // ※ [](全角マイナス)は正規表現の範囲指定ハイフンと誤認されないよう、エスケープなしで安全な位置に配置
14
+ const NORMALIZE_PATTERN = /[\s’“”`¥−〜\uFF01-\uFF5D]/g;
15
+ const WHITESPACE_PATTERN = /\s/;
16
+ const normalizeChar = (match) => {
17
+ // 空白文字の正規化
18
+ if (WHITESPACE_PATTERN.test(match))
19
+ return ' ';
20
+ // マッピングテーブルでの変換
21
+ const mappedMatch = CHAR_MAP[match];
22
+ if (mappedMatch !== undefined)
23
+ return mappedMatch;
24
+ // 全角英数記号の半角化
25
+ const code = match.charCodeAt(0);
26
+ if (code >= 0xff01 && code <= 0xff5d) {
27
+ return String.fromCharCode(code - 0xfee0);
28
+ }
29
+ return match;
30
+ };
31
+ const convertMatchableString = (original) => original.replace(NORMALIZE_PATTERN, normalizeChar).toLowerCase();
16
32
  function areItemsEqual(a, b) {
17
33
  return a.value === b.value && innerText(a.label) === innerText(b.label);
18
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","sources":["../../../src/components/Combobox/helper.ts"],"sourcesContent":["import innerText from 'react-innertext'\n\nimport type { ComboboxItem } from './types'\n\nexport function convertMatchableString(original: string) {\n return (\n original\n .replace(/\\s/g, ' ')\n .replace(/’/g, \"'\")\n .replace(/[”“]/g, '\"')\n .replace(/`/g, '`')\n .replace(/¥/g, '¥')\n .replace(/−/g, '-')\n .replace(/〜/g, '~')\n // unicode [!] から [] の間に定義されている英数・記号を半角に変換\n .replace(/[!-}]/g, (str) => String.fromCharCode(str.charCodeAt(0) - 0xfee0))\n .toLowerCase()\n )\n}\n\nexport function areItemsEqual<T>(a: ComboboxItem<T>, b: ComboboxItem<T>) {\n return a.value === b.value && innerText(a.label) === innerText(b.label)\n}\n"],"names":[],"mappings":";;AAIM,SAAU,sBAAsB,CAAC,QAAgB,EAAA;AACrD,IAAA,QACE;AACG,SAAA,OAAO,CAAC,KAAK,EAAE,GAAG;AAClB,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;AACjB,SAAA,OAAO,CAAC,OAAO,EAAE,GAAG;AACpB,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;AACjB,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;AACjB,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;AACjB,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;;SAEjB,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;SAC1E,WAAW,EAAE;AAEpB;AAEM,SAAU,aAAa,CAAI,CAAkB,EAAE,CAAkB,EAAA;IACrE,OAAO,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACzE;;;;"}
1
+ {"version":3,"file":"helper.js","sources":["../../../src/components/Combobox/helper.ts"],"sourcesContent":["import innerText from 'react-innertext'\n\nimport type { ComboboxItem } from './types'\n\nconst CHAR_MAP: Record<string, string> = {\n '\\u2019': \"'\", // ’\n '\\u201C': '\"', // “\n '\\u201D': '\"', // ”\n '`': '`',\n '¥': '¥',\n '−': '-',\n '〜': '~',\n}\n\n// 変換対象:空白・特殊な全角記号(マップ変換用)および Unicode [!]〜[}] の全角英数記号\n// [](全角マイナス)は正規表現の範囲指定ハイフンと誤認されないよう、エスケープなしで安全な位置に配置\nconst NORMALIZE_PATTERN = /[\\s’“”`¥−〜\\uFF01-\\uFF5D]/g\nconst WHITESPACE_PATTERN = /\\s/\n\nconst normalizeChar = (match: string): string => {\n // 空白文字の正規化\n if (WHITESPACE_PATTERN.test(match)) return ' '\n\n // マッピングテーブルでの変換\n const mappedMatch = CHAR_MAP[match]\n if (mappedMatch !== undefined) return mappedMatch\n\n // 全角英数記号の半角化\n const code = match.charCodeAt(0)\n if (code >= 0xff01 && code <= 0xff5d) {\n return String.fromCharCode(code - 0xfee0)\n }\n\n return match\n}\n\nexport const convertMatchableString = (original: string) =>\n original.replace(NORMALIZE_PATTERN, normalizeChar).toLowerCase()\n\nexport function areItemsEqual<T>(a: ComboboxItem<T>, b: ComboboxItem<T>) {\n return a.value === b.value && innerText(a.label) === innerText(b.label)\n}\n"],"names":[],"mappings":";;AAIA,MAAM,QAAQ,GAA2B;IACvC,QAAQ,EAAE,GAAG;IACb,QAAQ,EAAE,GAAG;IACb,QAAQ,EAAE,GAAG;AACb,IAAA,GAAG,EAAE,GAAG;AACR,IAAA,GAAG,EAAE,GAAG;AACR,IAAA,GAAG,EAAE,GAAG;AACR,IAAA,GAAG,EAAE,GAAG;CACT;AAED;AACA;AACA,MAAM,iBAAiB,GAAG,2BAA2B;AACrD,MAAM,kBAAkB,GAAG,IAAI;AAE/B,MAAM,aAAa,GAAG,CAAC,KAAa,KAAY;;AAE9C,IAAA,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,GAAG;;AAG9C,IAAA,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC;IACnC,IAAI,WAAW,KAAK,SAAS;AAAE,QAAA,OAAO,WAAW;;IAGjD,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IAChC,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE;QACpC,OAAO,MAAM,CAAC,YAAY,CAAC,IAAI,GAAG,MAAM,CAAC;IAC3C;AAEA,IAAA,OAAO,KAAK;AACd,CAAC;MAEY,sBAAsB,GAAG,CAAC,QAAgB,KACrD,QAAQ,CAAC,OAAO,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC,WAAW;AAE1D,SAAU,aAAa,CAAI,CAAkB,EAAE,CAAkB,EAAA;IACrE,OAAO,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACzE;;;;"}
@@ -16,27 +16,31 @@ const useMultiOptions = ({ selected, isItemSelected = defaultIsItemSelected, ...
16
16
  function useOptions({ items, creatable, inputValue = '', isFilteringDisabled = false }, isSelected) {
17
17
  const newItemId = React.useId();
18
18
  const optionIdPrefix = React.useId();
19
- const existedOptions = React.useMemo(() => items.map((item, i) => ({
20
- id: `${optionIdPrefix}-${i}`,
21
- selected: isSelected(item),
22
- isNew: false,
23
- item,
24
- })), [isSelected, items, optionIdPrefix]);
25
- const addingOption = React.useMemo(() => creatable && inputValue && items.every((item) => item.label !== inputValue)
26
- ? {
27
- id: newItemId,
28
- isNew: true,
29
- selected: false,
30
- item: { label: inputValue, value: inputValue },
31
- }
32
- : null, [inputValue, items, creatable, newItemId]);
33
- const allOptions = React.useMemo(() => (addingOption ? [addingOption, ...existedOptions] : existedOptions), [existedOptions, addingOption]);
34
19
  const options = React.useMemo(() => {
20
+ const existedOptions = items.map((item, i) => ({
21
+ id: `${optionIdPrefix}-${i}`,
22
+ selected: isSelected(item),
23
+ isNew: false,
24
+ item,
25
+ }));
26
+ const allOptions = creatable && inputValue && items.every((item) => item.label !== inputValue)
27
+ ? [
28
+ {
29
+ id: newItemId,
30
+ isNew: true,
31
+ selected: false,
32
+ item: { label: inputValue, value: inputValue },
33
+ },
34
+ ...existedOptions,
35
+ ]
36
+ : existedOptions;
35
37
  if (isFilteringDisabled || !inputValue) {
36
38
  return allOptions;
37
39
  }
38
- return allOptions.filter(({ item: { label } }) => components_Combobox_helper.convertMatchableString(index(label)).includes(components_Combobox_helper.convertMatchableString(inputValue)));
39
- }, [allOptions, inputValue, isFilteringDisabled]);
40
+ const convertedInputtedValue = components_Combobox_helper.convertMatchableString(inputValue);
41
+ return allOptions.filter(({ item: { label } }) => components_Combobox_helper.convertMatchableString(index(label)).includes(convertedInputtedValue));
42
+ // TODO: itemsの安定化方法を検討中
43
+ }, [isSelected, items, optionIdPrefix, inputValue, creatable, newItemId, isFilteringDisabled]);
40
44
  return {
41
45
  options,
42
46
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useOptions.cjs","sources":["../../../src/components/Combobox/useOptions.ts"],"sourcesContent":["import { useCallback, useId, useMemo } from 'react'\nimport innerText from 'react-innertext'\n\nimport { areItemsEqual, convertMatchableString } from './helper'\n\nimport type { ComboboxItem, ComboboxOption } from './types'\n\nconst defaultIsItemSelected = <T>(\n targetItem: ComboboxItem<T>,\n selectedItems: Array<ComboboxItem<T>>,\n) => selectedItems.some((item) => areItemsEqual(item, targetItem))\n\ntype BaseUseOptionsProps<T> = {\n items: Array<ComboboxItem<T>>\n creatable?: boolean\n inputValue?: string\n isFilteringDisabled?: boolean\n}\n\nexport const useSingleOptions = <T>({\n selected,\n ...rest\n}: BaseUseOptionsProps<T> & {\n selected: ComboboxItem<T> | null\n}) => {\n const isSelected = useCallback(\n (item: ComboboxItem<T>) => selected !== null && areItemsEqual(selected, item),\n [selected],\n )\n\n return useOptions<T>(rest, isSelected)\n}\n\nexport const useMultiOptions = <T>({\n selected,\n isItemSelected = defaultIsItemSelected,\n ...rest\n}: BaseUseOptionsProps<T> & {\n selected: Array<ComboboxItem<T>>\n isItemSelected?: (targetItem: ComboboxItem<T>, selectedItems: Array<ComboboxItem<T>>) => boolean\n}) => {\n const isSelected = useCallback(\n (item: ComboboxItem<T>) => isItemSelected(item, selected),\n [selected, isItemSelected],\n )\n\n return useOptions<T>(rest, isSelected)\n}\n\nfunction useOptions<T>(\n { items, creatable, inputValue = '', isFilteringDisabled = false }: BaseUseOptionsProps<T>,\n isSelected: (item: ComboboxItem<T>) => boolean,\n) {\n const newItemId = useId()\n const optionIdPrefix = useId()\n\n const existedOptions: Array<ComboboxOption<T>> = useMemo(\n () =>\n items.map((item, i) => ({\n id: `${optionIdPrefix}-${i}`,\n selected: isSelected(item),\n isNew: false,\n item,\n })),\n [isSelected, items, optionIdPrefix],\n )\n const addingOption: ComboboxOption<T> | null = useMemo(\n () =>\n creatable && inputValue && items.every((item) => item.label !== inputValue)\n ? {\n id: newItemId,\n isNew: true,\n selected: false,\n item: { label: inputValue, value: inputValue },\n }\n : null,\n [inputValue, items, creatable, newItemId],\n )\n\n const allOptions: Array<ComboboxOption<T>> = useMemo(\n () => (addingOption ? [addingOption, ...existedOptions] : existedOptions),\n [existedOptions, addingOption],\n )\n\n const options = useMemo(() => {\n if (isFilteringDisabled || !inputValue) {\n return allOptions\n }\n\n return allOptions.filter(({ item: { label } }) =>\n convertMatchableString(innerText(label)).includes(convertMatchableString(inputValue)),\n )\n }, [allOptions, inputValue, isFilteringDisabled])\n\n return {\n options,\n }\n}\n"],"names":["areItemsEqual","useCallback","useId","useMemo","convertMatchableString","innerText"],"mappings":";;;;;;AAOA,MAAM,qBAAqB,GAAG,CAC5B,UAA2B,EAC3B,aAAqC,KAClC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,KAAKA,wCAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAS3D,MAAM,gBAAgB,GAAG,CAAI,EAClC,QAAQ,EACR,GAAG,IAAI,EAGR,KAAI;IACH,MAAM,UAAU,GAAGC,iBAAW,CAC5B,CAAC,IAAqB,KAAK,QAAQ,KAAK,IAAI,IAAID,wCAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,EAC7E,CAAC,QAAQ,CAAC,CACX;AAED,IAAA,OAAO,UAAU,CAAI,IAAI,EAAE,UAAU,CAAC;AACxC;AAEO,MAAM,eAAe,GAAG,CAAI,EACjC,QAAQ,EACR,cAAc,GAAG,qBAAqB,EACtC,GAAG,IAAI,EAIR,KAAI;IACH,MAAM,UAAU,GAAGC,iBAAW,CAC5B,CAAC,IAAqB,KAAK,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,EACzD,CAAC,QAAQ,EAAE,cAAc,CAAC,CAC3B;AAED,IAAA,OAAO,UAAU,CAAI,IAAI,EAAE,UAAU,CAAC;AACxC;AAEA,SAAS,UAAU,CACjB,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,GAAG,EAAE,EAAE,mBAAmB,GAAG,KAAK,EAA0B,EAC1F,UAA8C,EAAA;AAE9C,IAAA,MAAM,SAAS,GAAGC,WAAK,EAAE;AACzB,IAAA,MAAM,cAAc,GAAGA,WAAK,EAAE;AAE9B,IAAA,MAAM,cAAc,GAA6BC,aAAO,CACtD,MACE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM;AACtB,QAAA,EAAE,EAAE,CAAA,EAAG,cAAc,CAAA,CAAA,EAAI,CAAC,CAAA,CAAE;AAC5B,QAAA,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC;AAC1B,QAAA,KAAK,EAAE,KAAK;QACZ,IAAI;KACL,CAAC,CAAC,EACL,CAAC,UAAU,EAAE,KAAK,EAAE,cAAc,CAAC,CACpC;IACD,MAAM,YAAY,GAA6BA,aAAO,CACpD,MACE,SAAS,IAAI,UAAU,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,KAAK,UAAU;AACxE,UAAE;AACE,YAAA,EAAE,EAAE,SAAS;AACb,YAAA,KAAK,EAAE,IAAI;AACX,YAAA,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;AAC/C;AACH,UAAE,IAAI,EACV,CAAC,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,CAAC,CAC1C;AAED,IAAA,MAAM,UAAU,GAA6BA,aAAO,CAClD,OAAO,YAAY,GAAG,CAAC,YAAY,EAAE,GAAG,cAAc,CAAC,GAAG,cAAc,CAAC,EACzE,CAAC,cAAc,EAAE,YAAY,CAAC,CAC/B;AAED,IAAA,MAAM,OAAO,GAAGA,aAAO,CAAC,MAAK;AAC3B,QAAA,IAAI,mBAAmB,IAAI,CAAC,UAAU,EAAE;AACtC,YAAA,OAAO,UAAU;QACnB;AAEA,QAAA,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,KAC3CC,iDAAsB,CAACC,KAAS,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAACD,iDAAsB,CAAC,UAAU,CAAC,CAAC,CACtF;IACH,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC;IAEjD,OAAO;QACL,OAAO;KACR;AACH;;;;;"}
1
+ {"version":3,"file":"useOptions.cjs","sources":["../../../src/components/Combobox/useOptions.ts"],"sourcesContent":["import { useCallback, useId, useMemo } from 'react'\nimport innerText from 'react-innertext'\n\nimport { areItemsEqual, convertMatchableString } from './helper'\n\nimport type { ComboboxItem, ComboboxOption } from './types'\n\nconst defaultIsItemSelected = <T>(\n targetItem: ComboboxItem<T>,\n selectedItems: Array<ComboboxItem<T>>,\n) => selectedItems.some((item) => areItemsEqual(item, targetItem))\n\ntype BaseUseOptionsProps<T> = {\n items: Array<ComboboxItem<T>>\n creatable?: boolean\n inputValue?: string\n isFilteringDisabled?: boolean\n}\n\nexport const useSingleOptions = <T>({\n selected,\n ...rest\n}: BaseUseOptionsProps<T> & {\n selected: ComboboxItem<T> | null\n}) => {\n const isSelected = useCallback(\n (item: ComboboxItem<T>) => selected !== null && areItemsEqual(selected, item),\n [selected],\n )\n\n return useOptions<T>(rest, isSelected)\n}\n\nexport const useMultiOptions = <T>({\n selected,\n isItemSelected = defaultIsItemSelected,\n ...rest\n}: BaseUseOptionsProps<T> & {\n selected: Array<ComboboxItem<T>>\n isItemSelected?: (targetItem: ComboboxItem<T>, selectedItems: Array<ComboboxItem<T>>) => boolean\n}) => {\n const isSelected = useCallback(\n (item: ComboboxItem<T>) => isItemSelected(item, selected),\n [selected, isItemSelected],\n )\n\n return useOptions<T>(rest, isSelected)\n}\n\nfunction useOptions<T>(\n { items, creatable, inputValue = '', isFilteringDisabled = false }: BaseUseOptionsProps<T>,\n isSelected: (item: ComboboxItem<T>) => boolean,\n) {\n const newItemId = useId()\n const optionIdPrefix = useId()\n\n const options = useMemo(() => {\n const existedOptions: Array<ComboboxOption<T>> = items.map((item, i) => ({\n id: `${optionIdPrefix}-${i}`,\n selected: isSelected(item),\n isNew: false,\n item,\n }))\n\n const allOptions =\n creatable && inputValue && items.every((item) => item.label !== inputValue)\n ? [\n {\n id: newItemId,\n isNew: true,\n selected: false,\n item: { label: inputValue, value: inputValue },\n } as ComboboxOption<T>,\n ...existedOptions,\n ]\n : existedOptions\n\n if (isFilteringDisabled || !inputValue) {\n return allOptions\n }\n\n const convertedInputtedValue = convertMatchableString(inputValue)\n\n return allOptions.filter(({ item: { label } }) =>\n convertMatchableString(innerText(label)).includes(convertedInputtedValue),\n )\n // TODO: itemsの安定化方法を検討中\n }, [isSelected, items, optionIdPrefix, inputValue, creatable, newItemId, isFilteringDisabled])\n\n return {\n options,\n }\n}\n"],"names":["areItemsEqual","useCallback","useId","useMemo","convertMatchableString","innerText"],"mappings":";;;;;;AAOA,MAAM,qBAAqB,GAAG,CAC5B,UAA2B,EAC3B,aAAqC,KAClC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,KAAKA,wCAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAS3D,MAAM,gBAAgB,GAAG,CAAI,EAClC,QAAQ,EACR,GAAG,IAAI,EAGR,KAAI;IACH,MAAM,UAAU,GAAGC,iBAAW,CAC5B,CAAC,IAAqB,KAAK,QAAQ,KAAK,IAAI,IAAID,wCAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,EAC7E,CAAC,QAAQ,CAAC,CACX;AAED,IAAA,OAAO,UAAU,CAAI,IAAI,EAAE,UAAU,CAAC;AACxC;AAEO,MAAM,eAAe,GAAG,CAAI,EACjC,QAAQ,EACR,cAAc,GAAG,qBAAqB,EACtC,GAAG,IAAI,EAIR,KAAI;IACH,MAAM,UAAU,GAAGC,iBAAW,CAC5B,CAAC,IAAqB,KAAK,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,EACzD,CAAC,QAAQ,EAAE,cAAc,CAAC,CAC3B;AAED,IAAA,OAAO,UAAU,CAAI,IAAI,EAAE,UAAU,CAAC;AACxC;AAEA,SAAS,UAAU,CACjB,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,GAAG,EAAE,EAAE,mBAAmB,GAAG,KAAK,EAA0B,EAC1F,UAA8C,EAAA;AAE9C,IAAA,MAAM,SAAS,GAAGC,WAAK,EAAE;AACzB,IAAA,MAAM,cAAc,GAAGA,WAAK,EAAE;AAE9B,IAAA,MAAM,OAAO,GAAGC,aAAO,CAAC,MAAK;AAC3B,QAAA,MAAM,cAAc,GAA6B,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM;AACvE,YAAA,EAAE,EAAE,CAAA,EAAG,cAAc,CAAA,CAAA,EAAI,CAAC,CAAA,CAAE;AAC5B,YAAA,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC;AAC1B,YAAA,KAAK,EAAE,KAAK;YACZ,IAAI;AACL,SAAA,CAAC,CAAC;QAEH,MAAM,UAAU,GACd,SAAS,IAAI,UAAU,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,KAAK,UAAU;AACxE,cAAE;AACE,gBAAA;AACE,oBAAA,EAAE,EAAE,SAAS;AACb,oBAAA,KAAK,EAAE,IAAI;AACX,oBAAA,QAAQ,EAAE,KAAK;oBACf,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;AAC1B,iBAAA;AACtB,gBAAA,GAAG,cAAc;AAClB;cACD,cAAc;AAEpB,QAAA,IAAI,mBAAmB,IAAI,CAAC,UAAU,EAAE;AACtC,YAAA,OAAO,UAAU;QACnB;AAEA,QAAA,MAAM,sBAAsB,GAAGC,iDAAsB,CAAC,UAAU,CAAC;AAEjE,QAAA,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,KAC3CA,iDAAsB,CAACC,KAAS,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAC1E;;AAEH,IAAA,CAAC,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAE9F,OAAO;QACL,OAAO;KACR;AACH;;;;;"}
@@ -14,27 +14,31 @@ const useMultiOptions = ({ selected, isItemSelected = defaultIsItemSelected, ...
14
14
  function useOptions({ items, creatable, inputValue = '', isFilteringDisabled = false }, isSelected) {
15
15
  const newItemId = useId();
16
16
  const optionIdPrefix = useId();
17
- const existedOptions = useMemo(() => items.map((item, i) => ({
18
- id: `${optionIdPrefix}-${i}`,
19
- selected: isSelected(item),
20
- isNew: false,
21
- item,
22
- })), [isSelected, items, optionIdPrefix]);
23
- const addingOption = useMemo(() => creatable && inputValue && items.every((item) => item.label !== inputValue)
24
- ? {
25
- id: newItemId,
26
- isNew: true,
27
- selected: false,
28
- item: { label: inputValue, value: inputValue },
29
- }
30
- : null, [inputValue, items, creatable, newItemId]);
31
- const allOptions = useMemo(() => (addingOption ? [addingOption, ...existedOptions] : existedOptions), [existedOptions, addingOption]);
32
17
  const options = useMemo(() => {
18
+ const existedOptions = items.map((item, i) => ({
19
+ id: `${optionIdPrefix}-${i}`,
20
+ selected: isSelected(item),
21
+ isNew: false,
22
+ item,
23
+ }));
24
+ const allOptions = creatable && inputValue && items.every((item) => item.label !== inputValue)
25
+ ? [
26
+ {
27
+ id: newItemId,
28
+ isNew: true,
29
+ selected: false,
30
+ item: { label: inputValue, value: inputValue },
31
+ },
32
+ ...existedOptions,
33
+ ]
34
+ : existedOptions;
33
35
  if (isFilteringDisabled || !inputValue) {
34
36
  return allOptions;
35
37
  }
36
- return allOptions.filter(({ item: { label } }) => convertMatchableString(innerText(label)).includes(convertMatchableString(inputValue)));
37
- }, [allOptions, inputValue, isFilteringDisabled]);
38
+ const convertedInputtedValue = convertMatchableString(inputValue);
39
+ return allOptions.filter(({ item: { label } }) => convertMatchableString(innerText(label)).includes(convertedInputtedValue));
40
+ // TODO: itemsの安定化方法を検討中
41
+ }, [isSelected, items, optionIdPrefix, inputValue, creatable, newItemId, isFilteringDisabled]);
38
42
  return {
39
43
  options,
40
44
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useOptions.js","sources":["../../../src/components/Combobox/useOptions.ts"],"sourcesContent":["import { useCallback, useId, useMemo } from 'react'\nimport innerText from 'react-innertext'\n\nimport { areItemsEqual, convertMatchableString } from './helper'\n\nimport type { ComboboxItem, ComboboxOption } from './types'\n\nconst defaultIsItemSelected = <T>(\n targetItem: ComboboxItem<T>,\n selectedItems: Array<ComboboxItem<T>>,\n) => selectedItems.some((item) => areItemsEqual(item, targetItem))\n\ntype BaseUseOptionsProps<T> = {\n items: Array<ComboboxItem<T>>\n creatable?: boolean\n inputValue?: string\n isFilteringDisabled?: boolean\n}\n\nexport const useSingleOptions = <T>({\n selected,\n ...rest\n}: BaseUseOptionsProps<T> & {\n selected: ComboboxItem<T> | null\n}) => {\n const isSelected = useCallback(\n (item: ComboboxItem<T>) => selected !== null && areItemsEqual(selected, item),\n [selected],\n )\n\n return useOptions<T>(rest, isSelected)\n}\n\nexport const useMultiOptions = <T>({\n selected,\n isItemSelected = defaultIsItemSelected,\n ...rest\n}: BaseUseOptionsProps<T> & {\n selected: Array<ComboboxItem<T>>\n isItemSelected?: (targetItem: ComboboxItem<T>, selectedItems: Array<ComboboxItem<T>>) => boolean\n}) => {\n const isSelected = useCallback(\n (item: ComboboxItem<T>) => isItemSelected(item, selected),\n [selected, isItemSelected],\n )\n\n return useOptions<T>(rest, isSelected)\n}\n\nfunction useOptions<T>(\n { items, creatable, inputValue = '', isFilteringDisabled = false }: BaseUseOptionsProps<T>,\n isSelected: (item: ComboboxItem<T>) => boolean,\n) {\n const newItemId = useId()\n const optionIdPrefix = useId()\n\n const existedOptions: Array<ComboboxOption<T>> = useMemo(\n () =>\n items.map((item, i) => ({\n id: `${optionIdPrefix}-${i}`,\n selected: isSelected(item),\n isNew: false,\n item,\n })),\n [isSelected, items, optionIdPrefix],\n )\n const addingOption: ComboboxOption<T> | null = useMemo(\n () =>\n creatable && inputValue && items.every((item) => item.label !== inputValue)\n ? {\n id: newItemId,\n isNew: true,\n selected: false,\n item: { label: inputValue, value: inputValue },\n }\n : null,\n [inputValue, items, creatable, newItemId],\n )\n\n const allOptions: Array<ComboboxOption<T>> = useMemo(\n () => (addingOption ? [addingOption, ...existedOptions] : existedOptions),\n [existedOptions, addingOption],\n )\n\n const options = useMemo(() => {\n if (isFilteringDisabled || !inputValue) {\n return allOptions\n }\n\n return allOptions.filter(({ item: { label } }) =>\n convertMatchableString(innerText(label)).includes(convertMatchableString(inputValue)),\n )\n }, [allOptions, inputValue, isFilteringDisabled])\n\n return {\n options,\n }\n}\n"],"names":[],"mappings":";;;;AAOA,MAAM,qBAAqB,GAAG,CAC5B,UAA2B,EAC3B,aAAqC,KAClC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAS3D,MAAM,gBAAgB,GAAG,CAAI,EAClC,QAAQ,EACR,GAAG,IAAI,EAGR,KAAI;IACH,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,IAAqB,KAAK,QAAQ,KAAK,IAAI,IAAI,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,EAC7E,CAAC,QAAQ,CAAC,CACX;AAED,IAAA,OAAO,UAAU,CAAI,IAAI,EAAE,UAAU,CAAC;AACxC;AAEO,MAAM,eAAe,GAAG,CAAI,EACjC,QAAQ,EACR,cAAc,GAAG,qBAAqB,EACtC,GAAG,IAAI,EAIR,KAAI;IACH,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,IAAqB,KAAK,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,EACzD,CAAC,QAAQ,EAAE,cAAc,CAAC,CAC3B;AAED,IAAA,OAAO,UAAU,CAAI,IAAI,EAAE,UAAU,CAAC;AACxC;AAEA,SAAS,UAAU,CACjB,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,GAAG,EAAE,EAAE,mBAAmB,GAAG,KAAK,EAA0B,EAC1F,UAA8C,EAAA;AAE9C,IAAA,MAAM,SAAS,GAAG,KAAK,EAAE;AACzB,IAAA,MAAM,cAAc,GAAG,KAAK,EAAE;AAE9B,IAAA,MAAM,cAAc,GAA6B,OAAO,CACtD,MACE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM;AACtB,QAAA,EAAE,EAAE,CAAA,EAAG,cAAc,CAAA,CAAA,EAAI,CAAC,CAAA,CAAE;AAC5B,QAAA,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC;AAC1B,QAAA,KAAK,EAAE,KAAK;QACZ,IAAI;KACL,CAAC,CAAC,EACL,CAAC,UAAU,EAAE,KAAK,EAAE,cAAc,CAAC,CACpC;IACD,MAAM,YAAY,GAA6B,OAAO,CACpD,MACE,SAAS,IAAI,UAAU,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,KAAK,UAAU;AACxE,UAAE;AACE,YAAA,EAAE,EAAE,SAAS;AACb,YAAA,KAAK,EAAE,IAAI;AACX,YAAA,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;AAC/C;AACH,UAAE,IAAI,EACV,CAAC,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,CAAC,CAC1C;AAED,IAAA,MAAM,UAAU,GAA6B,OAAO,CAClD,OAAO,YAAY,GAAG,CAAC,YAAY,EAAE,GAAG,cAAc,CAAC,GAAG,cAAc,CAAC,EACzE,CAAC,cAAc,EAAE,YAAY,CAAC,CAC/B;AAED,IAAA,MAAM,OAAO,GAAG,OAAO,CAAC,MAAK;AAC3B,QAAA,IAAI,mBAAmB,IAAI,CAAC,UAAU,EAAE;AACtC,YAAA,OAAO,UAAU;QACnB;AAEA,QAAA,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,KAC3C,sBAAsB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC,CACtF;IACH,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC;IAEjD,OAAO;QACL,OAAO;KACR;AACH;;;;"}
1
+ {"version":3,"file":"useOptions.js","sources":["../../../src/components/Combobox/useOptions.ts"],"sourcesContent":["import { useCallback, useId, useMemo } from 'react'\nimport innerText from 'react-innertext'\n\nimport { areItemsEqual, convertMatchableString } from './helper'\n\nimport type { ComboboxItem, ComboboxOption } from './types'\n\nconst defaultIsItemSelected = <T>(\n targetItem: ComboboxItem<T>,\n selectedItems: Array<ComboboxItem<T>>,\n) => selectedItems.some((item) => areItemsEqual(item, targetItem))\n\ntype BaseUseOptionsProps<T> = {\n items: Array<ComboboxItem<T>>\n creatable?: boolean\n inputValue?: string\n isFilteringDisabled?: boolean\n}\n\nexport const useSingleOptions = <T>({\n selected,\n ...rest\n}: BaseUseOptionsProps<T> & {\n selected: ComboboxItem<T> | null\n}) => {\n const isSelected = useCallback(\n (item: ComboboxItem<T>) => selected !== null && areItemsEqual(selected, item),\n [selected],\n )\n\n return useOptions<T>(rest, isSelected)\n}\n\nexport const useMultiOptions = <T>({\n selected,\n isItemSelected = defaultIsItemSelected,\n ...rest\n}: BaseUseOptionsProps<T> & {\n selected: Array<ComboboxItem<T>>\n isItemSelected?: (targetItem: ComboboxItem<T>, selectedItems: Array<ComboboxItem<T>>) => boolean\n}) => {\n const isSelected = useCallback(\n (item: ComboboxItem<T>) => isItemSelected(item, selected),\n [selected, isItemSelected],\n )\n\n return useOptions<T>(rest, isSelected)\n}\n\nfunction useOptions<T>(\n { items, creatable, inputValue = '', isFilteringDisabled = false }: BaseUseOptionsProps<T>,\n isSelected: (item: ComboboxItem<T>) => boolean,\n) {\n const newItemId = useId()\n const optionIdPrefix = useId()\n\n const options = useMemo(() => {\n const existedOptions: Array<ComboboxOption<T>> = items.map((item, i) => ({\n id: `${optionIdPrefix}-${i}`,\n selected: isSelected(item),\n isNew: false,\n item,\n }))\n\n const allOptions =\n creatable && inputValue && items.every((item) => item.label !== inputValue)\n ? [\n {\n id: newItemId,\n isNew: true,\n selected: false,\n item: { label: inputValue, value: inputValue },\n } as ComboboxOption<T>,\n ...existedOptions,\n ]\n : existedOptions\n\n if (isFilteringDisabled || !inputValue) {\n return allOptions\n }\n\n const convertedInputtedValue = convertMatchableString(inputValue)\n\n return allOptions.filter(({ item: { label } }) =>\n convertMatchableString(innerText(label)).includes(convertedInputtedValue),\n )\n // TODO: itemsの安定化方法を検討中\n }, [isSelected, items, optionIdPrefix, inputValue, creatable, newItemId, isFilteringDisabled])\n\n return {\n options,\n }\n}\n"],"names":[],"mappings":";;;;AAOA,MAAM,qBAAqB,GAAG,CAC5B,UAA2B,EAC3B,aAAqC,KAClC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAS3D,MAAM,gBAAgB,GAAG,CAAI,EAClC,QAAQ,EACR,GAAG,IAAI,EAGR,KAAI;IACH,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,IAAqB,KAAK,QAAQ,KAAK,IAAI,IAAI,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,EAC7E,CAAC,QAAQ,CAAC,CACX;AAED,IAAA,OAAO,UAAU,CAAI,IAAI,EAAE,UAAU,CAAC;AACxC;AAEO,MAAM,eAAe,GAAG,CAAI,EACjC,QAAQ,EACR,cAAc,GAAG,qBAAqB,EACtC,GAAG,IAAI,EAIR,KAAI;IACH,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,IAAqB,KAAK,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,EACzD,CAAC,QAAQ,EAAE,cAAc,CAAC,CAC3B;AAED,IAAA,OAAO,UAAU,CAAI,IAAI,EAAE,UAAU,CAAC;AACxC;AAEA,SAAS,UAAU,CACjB,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,GAAG,EAAE,EAAE,mBAAmB,GAAG,KAAK,EAA0B,EAC1F,UAA8C,EAAA;AAE9C,IAAA,MAAM,SAAS,GAAG,KAAK,EAAE;AACzB,IAAA,MAAM,cAAc,GAAG,KAAK,EAAE;AAE9B,IAAA,MAAM,OAAO,GAAG,OAAO,CAAC,MAAK;AAC3B,QAAA,MAAM,cAAc,GAA6B,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM;AACvE,YAAA,EAAE,EAAE,CAAA,EAAG,cAAc,CAAA,CAAA,EAAI,CAAC,CAAA,CAAE;AAC5B,YAAA,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC;AAC1B,YAAA,KAAK,EAAE,KAAK;YACZ,IAAI;AACL,SAAA,CAAC,CAAC;QAEH,MAAM,UAAU,GACd,SAAS,IAAI,UAAU,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,KAAK,UAAU;AACxE,cAAE;AACE,gBAAA;AACE,oBAAA,EAAE,EAAE,SAAS;AACb,oBAAA,KAAK,EAAE,IAAI;AACX,oBAAA,QAAQ,EAAE,KAAK;oBACf,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;AAC1B,iBAAA;AACtB,gBAAA,GAAG,cAAc;AAClB;cACD,cAAc;AAEpB,QAAA,IAAI,mBAAmB,IAAI,CAAC,UAAU,EAAE;AACtC,YAAA,OAAO,UAAU;QACnB;AAEA,QAAA,MAAM,sBAAsB,GAAG,sBAAsB,CAAC,UAAU,CAAC;AAEjE,QAAA,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,KAC3C,sBAAsB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAC1E;;AAEH,IAAA,CAAC,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAE9F,OAAO;QACL,OAAO;KACR;AACH;;;;"}
@@ -57,7 +57,14 @@ const DialogContentInner = ({ onClickOverlay, onPressEscape, isOpen, id, width,
57
57
  return actualWidth ? { width: actualWidth } : undefined;
58
58
  }, [width, size]);
59
59
  const innerRef = React.useRef(null);
60
- hooks_useHandleEscape.useHandleEscape(React.useMemo(() => (onPressEscape && isOpen ? onPressEscape : undefined), [isOpen, onPressEscape]));
60
+ // 外部propsのonPressEscapeをrefに保存
61
+ const onPressEscapeRef = React.useRef(onPressEscape);
62
+ onPressEscapeRef.current = onPressEscape;
63
+ // stableなcallbackを作成
64
+ const memoizedOnPressEscape = React.useCallback(() => {
65
+ onPressEscapeRef.current?.();
66
+ }, []);
67
+ hooks_useHandleEscape.useHandleEscape(isOpen ? memoizedOnPressEscape : undefined);
61
68
  components_Dialog_useBodyScrollLock.useBodyScrollLock(isOpen);
62
69
  return (jsxRuntime.jsx(components_Dialog_DialogOverlap.DialogOverlap, { isOpen: isOpen, children: jsxRuntime.jsxs("div", { id: id, className: classNames.layout, style: style, children: [jsxRuntime.jsx(Overlay, { isOpen: isOpen, onClickOverlay: onClickOverlay, className: classNames.background }), jsxRuntime.jsx("div", { ...rest, ref: innerRef, role: "dialog", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-modal": "true", className: classNames.inner, children: jsxRuntime.jsx(components_Dialog_FocusTrap.FocusTrap, { firstFocusTarget: firstFocusTarget, ref: focusTrapRef, children: children }) })] }) }));
63
70
  };
@@ -1 +1 @@
1
- {"version":3,"file":"DialogContentInner.cjs","sources":["../../../src/components/Dialog/DialogContentInner.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type PropsWithChildren,\n type RefObject,\n memo,\n useMemo,\n useRef,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useHandleEscape } from '../../hooks/useHandleEscape'\nimport { dialogSize } from '../../tailwind'\n\nimport { DialogOverlap } from './DialogOverlap'\nimport { FocusTrap, type FocusTrapRef } from './FocusTrap'\nimport { useBodyScrollLock } from './useBodyScrollLock'\n\nimport type { DialogSize } from './types'\n\nexport type DialogContentInnerProps = PropsWithChildren<{\n /**\n * オーバーレイをクリックした時に発火するコールバック関数\n */\n onClickOverlay?: () => void\n /**\n * エスケープキーを押下した時に発火するコールバック関数\n */\n onPressEscape?: () => void\n /**\n * ダイアログを開いているかどうか\n */\n isOpen: boolean\n /**\n * @deprecated ダイアログの幅を指定する場合は、`width` ではなく `size` を使用してください。\n * ダイアログの幅\n */\n width?: string | number\n /**\n * ダイアログの大きさ\n */\n size?: DialogSize\n /**\n * ダイアログの `id`\n * TODO 使われてなさそうなので確認\n */\n id?: string\n /**\n * ダイアログを開いた時にフォーカスする対象\n */\n firstFocusTarget?: RefObject<HTMLElement>\n /**\n * ダイアログの `aria-label`\n */\n ariaLabel?: string\n /**\n * ダイアログの `aria-labelledby`\n */\n ariaLabelledby?: string\n /**\n * ダイアログトップのフォーカストラップへの ref\n */\n focusTrapRef?: RefObject<FocusTrapRef>\n}>\ntype Props = DialogContentInnerProps & Omit<ComponentProps<'div'>, keyof DialogContentInnerProps>\n\nconst classNameGenerator = tv({\n slots: {\n layout: ['smarthr-ui-Dialog-wrapper', 'shr-max-w-[calc(100dvw-theme(spacing.1))]'],\n inner: [\n 'smarthr-ui-Dialog',\n 'shr-border-shorthand shr-relative shr-z-1 shr-rounded-m shr-bg-white shr-shadow-layer-3',\n 'contrast-more:shr-border-high-contrast',\n ],\n background: ['smarthr-ui-Dialog-background', 'shr-absolute shr-inset-0 shr-bg-scrim'],\n },\n variants: {\n size: {\n XS: { layout: dialogSize.XS },\n S: { layout: dialogSize.S },\n M: { layout: dialogSize.M },\n L: { layout: dialogSize.L },\n XL: { layout: dialogSize.XL },\n XXL: { layout: dialogSize.XXL },\n FULL: { layout: dialogSize.FULL },\n },\n },\n})\n\nexport const DialogContentInner: FC<Props> = ({\n onClickOverlay,\n onPressEscape,\n isOpen,\n id,\n width,\n size,\n firstFocusTarget,\n ariaLabel,\n ariaLabelledby,\n children,\n className,\n focusTrapRef,\n ...rest\n}) => {\n const classNames = useMemo(() => {\n const { layout, inner, background } = classNameGenerator()\n\n return {\n layout: layout({ size }),\n inner: inner({ className }),\n background: background(),\n }\n }, [size, className])\n const style = useMemo(() => {\n // width は deprecated なので、size が指定されている場合は width を無視する\n const actualWidth = size ? undefined : typeof width === 'number' ? `${width}px` : width\n\n return actualWidth ? { width: actualWidth } : undefined\n }, [width, size])\n\n const innerRef = useRef<HTMLDivElement>(null)\n\n useHandleEscape(\n useMemo(() => (onPressEscape && isOpen ? onPressEscape : undefined), [isOpen, onPressEscape]),\n )\n\n useBodyScrollLock(isOpen)\n\n return (\n <DialogOverlap isOpen={isOpen}>\n <div id={id} className={classNames.layout} style={style}>\n <Overlay\n isOpen={isOpen}\n onClickOverlay={onClickOverlay}\n className={classNames.background}\n />\n <div\n {...rest}\n ref={innerRef}\n role=\"dialog\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n aria-modal=\"true\"\n className={classNames.inner}\n >\n <FocusTrap firstFocusTarget={firstFocusTarget} ref={focusTrapRef}>\n {children}\n </FocusTrap>\n </div>\n </div>\n </DialogOverlap>\n )\n}\n\nconst Overlay = memo<Pick<Props, 'onClickOverlay' | 'isOpen'> & { className: string }>(\n ({ onClickOverlay, isOpen, className }) => {\n const onClick = useMemo(\n () => (onClickOverlay && isOpen ? onClickOverlay : undefined),\n [isOpen, onClickOverlay],\n )\n\n // eslint-disable-next-line smarthr/best-practice-for-interactive-element\n return <div onClick={onClick} className={className} role=\"presentation\" />\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoEA;AACE;AACE;AACA;;;;AAIC;AACD;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACF;AACF;AAEM;AAeL;;;AAII;AACA;;;AAGJ;AACA;;;AAIE;AACF;AAEA;;;AAQA;AAwBF;AAEA;;;AAQI;AACF;;"}
1
+ {"version":3,"file":"DialogContentInner.cjs","sources":["../../../src/components/Dialog/DialogContentInner.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type PropsWithChildren,\n type RefObject,\n memo,\n useCallback,\n useMemo,\n useRef,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useHandleEscape } from '../../hooks/useHandleEscape'\nimport { dialogSize } from '../../tailwind'\n\nimport { DialogOverlap } from './DialogOverlap'\nimport { FocusTrap, type FocusTrapRef } from './FocusTrap'\nimport { useBodyScrollLock } from './useBodyScrollLock'\n\nimport type { DialogSize } from './types'\n\nexport type DialogContentInnerProps = PropsWithChildren<{\n /**\n * オーバーレイをクリックした時に発火するコールバック関数\n */\n onClickOverlay?: () => void\n /**\n * エスケープキーを押下した時に発火するコールバック関数\n */\n onPressEscape?: () => void\n /**\n * ダイアログを開いているかどうか\n */\n isOpen: boolean\n /**\n * @deprecated ダイアログの幅を指定する場合は、`width` ではなく `size` を使用してください。\n * ダイアログの幅\n */\n width?: string | number\n /**\n * ダイアログの大きさ\n */\n size?: DialogSize\n /**\n * ダイアログの `id`\n * TODO 使われてなさそうなので確認\n */\n id?: string\n /**\n * ダイアログを開いた時にフォーカスする対象\n */\n firstFocusTarget?: RefObject<HTMLElement>\n /**\n * ダイアログの `aria-label`\n */\n ariaLabel?: string\n /**\n * ダイアログの `aria-labelledby`\n */\n ariaLabelledby?: string\n /**\n * ダイアログトップのフォーカストラップへの ref\n */\n focusTrapRef?: RefObject<FocusTrapRef>\n}>\ntype Props = DialogContentInnerProps & Omit<ComponentProps<'div'>, keyof DialogContentInnerProps>\n\nconst classNameGenerator = tv({\n slots: {\n layout: ['smarthr-ui-Dialog-wrapper', 'shr-max-w-[calc(100dvw-theme(spacing.1))]'],\n inner: [\n 'smarthr-ui-Dialog',\n 'shr-border-shorthand shr-relative shr-z-1 shr-rounded-m shr-bg-white shr-shadow-layer-3',\n 'contrast-more:shr-border-high-contrast',\n ],\n background: ['smarthr-ui-Dialog-background', 'shr-absolute shr-inset-0 shr-bg-scrim'],\n },\n variants: {\n size: {\n XS: { layout: dialogSize.XS },\n S: { layout: dialogSize.S },\n M: { layout: dialogSize.M },\n L: { layout: dialogSize.L },\n XL: { layout: dialogSize.XL },\n XXL: { layout: dialogSize.XXL },\n FULL: { layout: dialogSize.FULL },\n },\n },\n})\n\nexport const DialogContentInner: FC<Props> = ({\n onClickOverlay,\n onPressEscape,\n isOpen,\n id,\n width,\n size,\n firstFocusTarget,\n ariaLabel,\n ariaLabelledby,\n children,\n className,\n focusTrapRef,\n ...rest\n}) => {\n const classNames = useMemo(() => {\n const { layout, inner, background } = classNameGenerator()\n\n return {\n layout: layout({ size }),\n inner: inner({ className }),\n background: background(),\n }\n }, [size, className])\n const style = useMemo(() => {\n // width は deprecated なので、size が指定されている場合は width を無視する\n const actualWidth = size ? undefined : typeof width === 'number' ? `${width}px` : width\n\n return actualWidth ? { width: actualWidth } : undefined\n }, [width, size])\n\n const innerRef = useRef<HTMLDivElement>(null)\n\n // 外部propsのonPressEscapeをrefに保存\n const onPressEscapeRef = useRef(onPressEscape)\n onPressEscapeRef.current = onPressEscape\n\n // stableなcallbackを作成\n const memoizedOnPressEscape = useCallback(() => {\n onPressEscapeRef.current?.()\n }, [])\n\n useHandleEscape(isOpen ? memoizedOnPressEscape : undefined)\n\n useBodyScrollLock(isOpen)\n\n return (\n <DialogOverlap isOpen={isOpen}>\n <div id={id} className={classNames.layout} style={style}>\n <Overlay\n isOpen={isOpen}\n onClickOverlay={onClickOverlay}\n className={classNames.background}\n />\n <div\n {...rest}\n ref={innerRef}\n role=\"dialog\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n aria-modal=\"true\"\n className={classNames.inner}\n >\n <FocusTrap firstFocusTarget={firstFocusTarget} ref={focusTrapRef}>\n {children}\n </FocusTrap>\n </div>\n </div>\n </DialogOverlap>\n )\n}\n\nconst Overlay = memo<Pick<Props, 'onClickOverlay' | 'isOpen'> & { className: string }>(\n ({ onClickOverlay, isOpen, className }) => {\n const onClick = useMemo(\n () => (onClickOverlay && isOpen ? onClickOverlay : undefined),\n [isOpen, onClickOverlay],\n )\n\n // eslint-disable-next-line smarthr/best-practice-for-interactive-element\n return <div onClick={onClick} className={className} role=\"presentation\" />\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqEA;AACE;AACE;AACA;;;;AAIC;AACD;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACF;AACF;AAEM;AAeL;;;AAII;AACA;;;AAGJ;AACA;;;AAIE;AACF;AAEA;;AAGA;AACA;;AAGA;AACE;;;;AAOF;AAwBF;AAEA;;;AAQI;AACF;;"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from 'react/jsx-runtime';
3
- import { memo, useMemo, useRef } from 'react';
3
+ import { memo, useMemo, useRef, useCallback } from 'react';
4
4
  import { tv as ce } from './../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.js';
5
5
  import { useHandleEscape } from '../../hooks/useHandleEscape.js';
6
6
  import { dialogSize } from '../../tailwind/dialogSize.js';
@@ -55,7 +55,14 @@ const DialogContentInner = ({ onClickOverlay, onPressEscape, isOpen, id, width,
55
55
  return actualWidth ? { width: actualWidth } : undefined;
56
56
  }, [width, size]);
57
57
  const innerRef = useRef(null);
58
- useHandleEscape(useMemo(() => (onPressEscape && isOpen ? onPressEscape : undefined), [isOpen, onPressEscape]));
58
+ // 外部propsのonPressEscapeをrefに保存
59
+ const onPressEscapeRef = useRef(onPressEscape);
60
+ onPressEscapeRef.current = onPressEscape;
61
+ // stableなcallbackを作成
62
+ const memoizedOnPressEscape = useCallback(() => {
63
+ onPressEscapeRef.current?.();
64
+ }, []);
65
+ useHandleEscape(isOpen ? memoizedOnPressEscape : undefined);
59
66
  useBodyScrollLock(isOpen);
60
67
  return (jsx(DialogOverlap, { isOpen: isOpen, children: jsxs("div", { id: id, className: classNames.layout, style: style, children: [jsx(Overlay, { isOpen: isOpen, onClickOverlay: onClickOverlay, className: classNames.background }), jsx("div", { ...rest, ref: innerRef, role: "dialog", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-modal": "true", className: classNames.inner, children: jsx(FocusTrap, { firstFocusTarget: firstFocusTarget, ref: focusTrapRef, children: children }) })] }) }));
61
68
  };
@@ -1 +1 @@
1
- {"version":3,"file":"DialogContentInner.js","sources":["../../../src/components/Dialog/DialogContentInner.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type PropsWithChildren,\n type RefObject,\n memo,\n useMemo,\n useRef,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useHandleEscape } from '../../hooks/useHandleEscape'\nimport { dialogSize } from '../../tailwind'\n\nimport { DialogOverlap } from './DialogOverlap'\nimport { FocusTrap, type FocusTrapRef } from './FocusTrap'\nimport { useBodyScrollLock } from './useBodyScrollLock'\n\nimport type { DialogSize } from './types'\n\nexport type DialogContentInnerProps = PropsWithChildren<{\n /**\n * オーバーレイをクリックした時に発火するコールバック関数\n */\n onClickOverlay?: () => void\n /**\n * エスケープキーを押下した時に発火するコールバック関数\n */\n onPressEscape?: () => void\n /**\n * ダイアログを開いているかどうか\n */\n isOpen: boolean\n /**\n * @deprecated ダイアログの幅を指定する場合は、`width` ではなく `size` を使用してください。\n * ダイアログの幅\n */\n width?: string | number\n /**\n * ダイアログの大きさ\n */\n size?: DialogSize\n /**\n * ダイアログの `id`\n * TODO 使われてなさそうなので確認\n */\n id?: string\n /**\n * ダイアログを開いた時にフォーカスする対象\n */\n firstFocusTarget?: RefObject<HTMLElement>\n /**\n * ダイアログの `aria-label`\n */\n ariaLabel?: string\n /**\n * ダイアログの `aria-labelledby`\n */\n ariaLabelledby?: string\n /**\n * ダイアログトップのフォーカストラップへの ref\n */\n focusTrapRef?: RefObject<FocusTrapRef>\n}>\ntype Props = DialogContentInnerProps & Omit<ComponentProps<'div'>, keyof DialogContentInnerProps>\n\nconst classNameGenerator = tv({\n slots: {\n layout: ['smarthr-ui-Dialog-wrapper', 'shr-max-w-[calc(100dvw-theme(spacing.1))]'],\n inner: [\n 'smarthr-ui-Dialog',\n 'shr-border-shorthand shr-relative shr-z-1 shr-rounded-m shr-bg-white shr-shadow-layer-3',\n 'contrast-more:shr-border-high-contrast',\n ],\n background: ['smarthr-ui-Dialog-background', 'shr-absolute shr-inset-0 shr-bg-scrim'],\n },\n variants: {\n size: {\n XS: { layout: dialogSize.XS },\n S: { layout: dialogSize.S },\n M: { layout: dialogSize.M },\n L: { layout: dialogSize.L },\n XL: { layout: dialogSize.XL },\n XXL: { layout: dialogSize.XXL },\n FULL: { layout: dialogSize.FULL },\n },\n },\n})\n\nexport const DialogContentInner: FC<Props> = ({\n onClickOverlay,\n onPressEscape,\n isOpen,\n id,\n width,\n size,\n firstFocusTarget,\n ariaLabel,\n ariaLabelledby,\n children,\n className,\n focusTrapRef,\n ...rest\n}) => {\n const classNames = useMemo(() => {\n const { layout, inner, background } = classNameGenerator()\n\n return {\n layout: layout({ size }),\n inner: inner({ className }),\n background: background(),\n }\n }, [size, className])\n const style = useMemo(() => {\n // width は deprecated なので、size が指定されている場合は width を無視する\n const actualWidth = size ? undefined : typeof width === 'number' ? `${width}px` : width\n\n return actualWidth ? { width: actualWidth } : undefined\n }, [width, size])\n\n const innerRef = useRef<HTMLDivElement>(null)\n\n useHandleEscape(\n useMemo(() => (onPressEscape && isOpen ? onPressEscape : undefined), [isOpen, onPressEscape]),\n )\n\n useBodyScrollLock(isOpen)\n\n return (\n <DialogOverlap isOpen={isOpen}>\n <div id={id} className={classNames.layout} style={style}>\n <Overlay\n isOpen={isOpen}\n onClickOverlay={onClickOverlay}\n className={classNames.background}\n />\n <div\n {...rest}\n ref={innerRef}\n role=\"dialog\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n aria-modal=\"true\"\n className={classNames.inner}\n >\n <FocusTrap firstFocusTarget={firstFocusTarget} ref={focusTrapRef}>\n {children}\n </FocusTrap>\n </div>\n </div>\n </DialogOverlap>\n )\n}\n\nconst Overlay = memo<Pick<Props, 'onClickOverlay' | 'isOpen'> & { className: string }>(\n ({ onClickOverlay, isOpen, className }) => {\n const onClick = useMemo(\n () => (onClickOverlay && isOpen ? onClickOverlay : undefined),\n [isOpen, onClickOverlay],\n )\n\n // eslint-disable-next-line smarthr/best-practice-for-interactive-element\n return <div onClick={onClick} className={className} role=\"presentation\" />\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAoEA;AACE;AACE;AACA;;;;AAIC;AACD;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACF;AACF;AAEM;AAeL;;;AAII;AACA;;;AAGJ;AACA;;;AAIE;AACF;AAEA;;;AAQA;AAwBF;AAEA;;;AAQI;AACF;;"}
1
+ {"version":3,"file":"DialogContentInner.js","sources":["../../../src/components/Dialog/DialogContentInner.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type PropsWithChildren,\n type RefObject,\n memo,\n useCallback,\n useMemo,\n useRef,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useHandleEscape } from '../../hooks/useHandleEscape'\nimport { dialogSize } from '../../tailwind'\n\nimport { DialogOverlap } from './DialogOverlap'\nimport { FocusTrap, type FocusTrapRef } from './FocusTrap'\nimport { useBodyScrollLock } from './useBodyScrollLock'\n\nimport type { DialogSize } from './types'\n\nexport type DialogContentInnerProps = PropsWithChildren<{\n /**\n * オーバーレイをクリックした時に発火するコールバック関数\n */\n onClickOverlay?: () => void\n /**\n * エスケープキーを押下した時に発火するコールバック関数\n */\n onPressEscape?: () => void\n /**\n * ダイアログを開いているかどうか\n */\n isOpen: boolean\n /**\n * @deprecated ダイアログの幅を指定する場合は、`width` ではなく `size` を使用してください。\n * ダイアログの幅\n */\n width?: string | number\n /**\n * ダイアログの大きさ\n */\n size?: DialogSize\n /**\n * ダイアログの `id`\n * TODO 使われてなさそうなので確認\n */\n id?: string\n /**\n * ダイアログを開いた時にフォーカスする対象\n */\n firstFocusTarget?: RefObject<HTMLElement>\n /**\n * ダイアログの `aria-label`\n */\n ariaLabel?: string\n /**\n * ダイアログの `aria-labelledby`\n */\n ariaLabelledby?: string\n /**\n * ダイアログトップのフォーカストラップへの ref\n */\n focusTrapRef?: RefObject<FocusTrapRef>\n}>\ntype Props = DialogContentInnerProps & Omit<ComponentProps<'div'>, keyof DialogContentInnerProps>\n\nconst classNameGenerator = tv({\n slots: {\n layout: ['smarthr-ui-Dialog-wrapper', 'shr-max-w-[calc(100dvw-theme(spacing.1))]'],\n inner: [\n 'smarthr-ui-Dialog',\n 'shr-border-shorthand shr-relative shr-z-1 shr-rounded-m shr-bg-white shr-shadow-layer-3',\n 'contrast-more:shr-border-high-contrast',\n ],\n background: ['smarthr-ui-Dialog-background', 'shr-absolute shr-inset-0 shr-bg-scrim'],\n },\n variants: {\n size: {\n XS: { layout: dialogSize.XS },\n S: { layout: dialogSize.S },\n M: { layout: dialogSize.M },\n L: { layout: dialogSize.L },\n XL: { layout: dialogSize.XL },\n XXL: { layout: dialogSize.XXL },\n FULL: { layout: dialogSize.FULL },\n },\n },\n})\n\nexport const DialogContentInner: FC<Props> = ({\n onClickOverlay,\n onPressEscape,\n isOpen,\n id,\n width,\n size,\n firstFocusTarget,\n ariaLabel,\n ariaLabelledby,\n children,\n className,\n focusTrapRef,\n ...rest\n}) => {\n const classNames = useMemo(() => {\n const { layout, inner, background } = classNameGenerator()\n\n return {\n layout: layout({ size }),\n inner: inner({ className }),\n background: background(),\n }\n }, [size, className])\n const style = useMemo(() => {\n // width は deprecated なので、size が指定されている場合は width を無視する\n const actualWidth = size ? undefined : typeof width === 'number' ? `${width}px` : width\n\n return actualWidth ? { width: actualWidth } : undefined\n }, [width, size])\n\n const innerRef = useRef<HTMLDivElement>(null)\n\n // 外部propsのonPressEscapeをrefに保存\n const onPressEscapeRef = useRef(onPressEscape)\n onPressEscapeRef.current = onPressEscape\n\n // stableなcallbackを作成\n const memoizedOnPressEscape = useCallback(() => {\n onPressEscapeRef.current?.()\n }, [])\n\n useHandleEscape(isOpen ? memoizedOnPressEscape : undefined)\n\n useBodyScrollLock(isOpen)\n\n return (\n <DialogOverlap isOpen={isOpen}>\n <div id={id} className={classNames.layout} style={style}>\n <Overlay\n isOpen={isOpen}\n onClickOverlay={onClickOverlay}\n className={classNames.background}\n />\n <div\n {...rest}\n ref={innerRef}\n role=\"dialog\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n aria-modal=\"true\"\n className={classNames.inner}\n >\n <FocusTrap firstFocusTarget={firstFocusTarget} ref={focusTrapRef}>\n {children}\n </FocusTrap>\n </div>\n </div>\n </DialogOverlap>\n )\n}\n\nconst Overlay = memo<Pick<Props, 'onClickOverlay' | 'isOpen'> & { className: string }>(\n ({ onClickOverlay, isOpen, className }) => {\n const onClick = useMemo(\n () => (onClickOverlay && isOpen ? onClickOverlay : undefined),\n [isOpen, onClickOverlay],\n )\n\n // eslint-disable-next-line smarthr/best-practice-for-interactive-element\n return <div onClick={onClick} className={className} role=\"presentation\" />\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAqEA;AACE;AACE;AACA;;;;AAIC;AACD;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACF;AACF;AAEM;AAeL;;;AAII;AACA;;;AAGJ;AACA;;;AAIE;AACF;AAEA;;AAGA;AACA;;AAGA;AACE;;;;AAOF;AAwBF;AAEA;;;AAQI;AACF;;"}
@@ -118,6 +118,9 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
118
118
  });
119
119
  debounceLiveRegionText(txt);
120
120
  }, [localize, wrapperPosition, debounceLiveRegionText]);
121
+ // 外部propsをrefに保存
122
+ const unstableRef = React.useRef({ isOpen, onClickClose, onPressEscape });
123
+ unstableRef.current = { isOpen, onClickClose, onPressEscape };
121
124
  const positionStyle = React.useMemo(() => ({
122
125
  top: centering.top ?? top,
123
126
  left: centering.left ?? left,
@@ -127,7 +130,7 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
127
130
  height,
128
131
  }), [centering, top, left, right, bottom, width, height, size]);
129
132
  const handleArrowKey = React.useCallback((e) => {
130
- if (!isOpen || document.activeElement !== e.currentTarget) {
133
+ if (!unstableRef.current.isOpen || document.activeElement !== e.currentTarget) {
131
134
  return;
132
135
  }
133
136
  const movingDistance = 20;
@@ -161,7 +164,7 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
161
164
  e.preventDefault();
162
165
  break;
163
166
  }
164
- }, [isOpen]);
167
+ }, []);
165
168
  React.useEffect(() => {
166
169
  if (wrapperRef.current instanceof Element) {
167
170
  setWrapperPosition(wrapperRef.current.getBoundingClientRect());
@@ -202,15 +205,14 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
202
205
  }, [isOpen]);
203
206
  const actualOnClickClose = React.useCallback((e) => {
204
207
  lastFocusElementRef.current?.focus();
205
- onClickClose?.(e);
206
- }, [onClickClose]);
207
- const actualOnPressEscape = React.useMemo(() => onPressEscape
208
- ? () => {
209
- lastFocusElementRef.current?.focus();
210
- onPressEscape();
211
- }
212
- : undefined, [onPressEscape]);
213
- hooks_useHandleEscape.useHandleEscape(React.useMemo(() => (actualOnPressEscape && isOpen ? actualOnPressEscape : undefined), [isOpen, actualOnPressEscape]));
208
+ unstableRef.current.onClickClose?.(e);
209
+ }, []);
210
+ // stableなcallbackを作成
211
+ const memoizedOnPressEscape = React.useCallback(() => {
212
+ lastFocusElementRef.current?.focus();
213
+ unstableRef.current.onPressEscape?.();
214
+ }, []);
215
+ hooks_useHandleEscape.useHandleEscape(isOpen ? memoizedOnPressEscape : undefined);
214
216
  React.useEffect(() => {
215
217
  const focusHandler = (e) => {
216
218
  // e.target(現在フォーカスがあたっている要素)がModeless dialog外の要素であれば、lastFocusElementRefに代入する
@@ -1 +1 @@
1
- {"version":3,"file":"ModelessDialog.cjs","sources":["../../../../src/components/Dialog/ModelessDialog/ModelessDialog.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type PropsWithChildren,\n type ReactNode,\n type RefObject,\n memo,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport Draggable from 'react-draggable'\nimport { type VariantProps, tv } from 'tailwind-variants'\n\nimport { useHandleEscape } from '../../../hooks/useHandleEscape'\nimport { useIntl } from '../../../intl'\nimport { debounce } from '../../../libs/debounce'\nimport { dialogSize } from '../../../tailwind'\nimport { Base, type BaseElementProps } from '../../Base'\nimport { Button } from '../../Button'\nimport { Heading } from '../../Heading'\nimport { FaGripIcon, FaXmarkIcon } from '../../Icon'\nimport { DialogBody, type Props as DialogBodyProps } from '../DialogBody'\nimport { DialogOverlap } from '../DialogOverlap'\nimport { useDialogPortal } from '../useDialogPortal'\n\nimport type { DialogSize } from '../types'\n\ntype AbstractProps = PropsWithChildren<{\n /**\n * ダイアログのタイトルの内容\n */\n heading: ReactNode\n /**\n * ダイアログのフッタ部分の内容\n */\n footer?: ReactNode\n /**\n * ダイアログが開かれているかどうかの真偽値\n */\n isOpen: boolean\n /**\n * 閉じるボタンを押下したときのハンドラ\n */\n onClickClose?: (e: MouseEvent<HTMLButtonElement>) => void\n /**\n * ダイアログが開いている状態で Escape キーを押下したときのハンドラ\n */\n onPressEscape?: () => void\n /**\n * @deprecated ダイアログの幅を指定する場合は、`width` ではなく `size` を使用してください。\n * ダイアログの幅\n */\n width?: string | number\n /**\n * ダイアログの大きさ\n */\n size?: DialogSize\n /**\n * ダイアログの高さ\n */\n height?: string | number\n /**\n * ダイアログを開いたときの初期 top 位置\n */\n top?: string | number\n /**\n * ダイアログを開いたときの初期 left 位置\n */\n left?: string | number\n /**\n * ダイアログを開いたときの初期 right 位置\n */\n right?: string | number\n /**\n * ダイアログを開いたときの初期 bottom 位置\n */\n bottom?: string | number\n /**\n * ポータルの container となる DOM 要素を追加する親要素\n */\n portalParent?: HTMLElement | RefObject<HTMLElement>\n}>\ntype Props = AbstractProps &\n Omit<DialogBodyProps, keyof AbstractProps> &\n Omit<BaseElementProps, keyof AbstractProps> &\n Omit<VariantProps<typeof classNameGenerator>, keyof AbstractProps>\n\nconst classNameGenerator = tv({\n slots: {\n overlap: 'shr-inset-[unset]',\n wrapper: 'smarthr-ui-ModelessDialog shr-fixed shr-flex shr-flex-col',\n headerEl: [\n 'smarthr-ui-ModelessDialog-header shr-border-b-shorthand shr-relative shr-flex shr-cursor-move shr-items-center shr-rounded-tl-l shr-rounded-tr-l shr-pe-1 shr-ps-1.5',\n 'hover:shr-bg-white-darken',\n /* DialogHandlerにフォーカスが当たっているときは、headerもフォーカス状態のスタイルにする。 */\n 'has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-focus-indicator has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-bg-white-darken has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-transition-colors has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-duration-100 has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-ease-in-out',\n ],\n dialogHandler: [\n 'smarthr-ui-ModelessDialog-handle shr-absolute shr-inset-x-0 shr-bottom-0 shr-top-[2px] shr-m-auto shr-flex shr-justify-center shr-rounded-tl-s shr-rounded-tr-s shr-border-none shr-text-grey shr-transition-colors shr-duration-100 shr-ease-in-out',\n 'focus-visible:shr-focus-indicator--none shr-cursor-[inherit] shr-bg-[unset]',\n ],\n headingEl: ['shr-my-1 shr-me-1'],\n closeButtonLayout: [\n 'shr-relative' /* DialogHandlerの上に出すためにスタッキングコンテキストを生成 */,\n 'shr-ml-auto shr-shrink-0',\n ],\n footerEl: 'smarthr-ui-ModelessDialog-footer shr-border-t-shorthand',\n },\n variants: {\n size: {\n XS: { wrapper: dialogSize.XS },\n S: { wrapper: dialogSize.S },\n M: { wrapper: dialogSize.M },\n L: { wrapper: dialogSize.L },\n XL: { wrapper: dialogSize.XL },\n XXL: { wrapper: dialogSize.XXL },\n FULL: { wrapper: dialogSize.FULL },\n },\n resizable: {\n true: {\n wrapper: 'shr-resize shr-overflow-auto',\n },\n false: {},\n },\n },\n})\n\nexport const ModelessDialog: FC<Props> = ({\n heading,\n children,\n contentBgColor,\n contentPadding,\n footer,\n isOpen,\n onPressEscape,\n resizable = false,\n width,\n size,\n height,\n top,\n left,\n right,\n bottom,\n portalParent,\n className,\n id,\n onClickClose,\n ...rest\n}) => {\n const labelId = useId()\n const lastFocusElementRef = useRef<HTMLElement | null>(null)\n const { createPortal } = useDialogPortal(portalParent, id)\n const { localize } = useIntl()\n\n const classNames = useMemo(() => {\n const { overlap, wrapper, headerEl, headingEl, dialogHandler, closeButtonLayout, footerEl } =\n classNameGenerator()\n\n return {\n overlap: overlap({ className }),\n wrapper: wrapper({ size, resizable }),\n header: headerEl(),\n heading: headingEl(),\n dialogHandler: dialogHandler(),\n closeButtonLayout: closeButtonLayout(),\n footer: footerEl(),\n }\n }, [className, size, resizable])\n\n const wrapperRef = useRef<HTMLDivElement>(null)\n const focusTargetRef = useRef<HTMLDivElement>(null)\n\n const [wrapperPosition, setWrapperPosition] = useState<DOMRect | undefined>(undefined)\n const [debouncedLiveRegionText, setDebouncedLiveRegionText] = useState<string>('')\n const [centering, setCentering] = useState<{\n top?: number\n left?: number\n }>({})\n const [position, setPosition] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n })\n const [draggableBounds, setDraggableBounds] =\n useState<ComponentProps<typeof Draggable>['bounds']>()\n const debounceLiveRegionText = useMemo(() => debounce(setDebouncedLiveRegionText, 600), [])\n\n useEffect(() => {\n if (!wrapperPosition) {\n setDebouncedLiveRegionText('')\n return\n }\n\n const txt = localize(\n {\n id: 'smarthr-ui/ModelessDialog/dialogHandlerLiveRegionText',\n defaultText: '上から{top}px、左から{left}px',\n },\n {\n top: Math.trunc(wrapperPosition.top).toString(),\n left: Math.trunc(wrapperPosition.left).toString(),\n },\n )\n\n debounceLiveRegionText(txt)\n }, [localize, wrapperPosition, debounceLiveRegionText])\n\n const positionStyle = useMemo(\n () => ({\n top: centering.top ?? top,\n left: centering.left ?? left,\n right,\n bottom,\n width: size ? undefined : width,\n height,\n }),\n [centering, top, left, right, bottom, width, height, size],\n )\n\n const handleArrowKey = useCallback(\n (e: KeyboardEvent) => {\n if (!isOpen || document.activeElement !== e.currentTarget) {\n return\n }\n\n const movingDistance = 20\n\n switch (e.key) {\n case 'ArrowUp':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y - movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowDown':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y + movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowLeft':\n setPosition((prev) => ({\n x: prev.x - movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n case 'ArrowRight':\n setPosition((prev) => ({\n x: prev.x + movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n }\n },\n [isOpen],\n )\n\n useEffect(() => {\n if (wrapperRef.current instanceof Element) {\n setWrapperPosition(wrapperRef.current.getBoundingClientRect())\n }\n }, [position])\n\n useEffect(() => {\n // 中央寄せの座標計算を行う\n if (!wrapperRef.current || !isOpen) {\n return\n }\n\n const isXCenter = left === undefined && right === undefined\n const isYCenter = top === undefined && bottom === undefined\n\n if (isXCenter || isYCenter) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setCentering({\n top: isYCenter ? window.innerHeight / 2 - rect.height / 2 : undefined,\n left: isXCenter ? window.innerWidth / 2 - rect.width / 2 : undefined,\n })\n }\n }, [bottom, isOpen, left, right, top])\n\n useEffect(() => {\n if (!isOpen) return\n\n if (centering.top) {\n setDraggableBounds({ top: centering.top * -1 })\n\n return\n }\n\n if (wrapperRef.current) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setDraggableBounds({ top: rect.top * -1 })\n }\n }, [isOpen, centering.top])\n\n useEffect(() => {\n if (isOpen) {\n setPosition({ x: 0, y: 0 })\n focusTargetRef.current?.focus()\n }\n }, [isOpen])\n\n const actualOnClickClose = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => {\n lastFocusElementRef.current?.focus()\n onClickClose?.(e)\n },\n [onClickClose],\n )\n\n const actualOnPressEscape = useMemo(\n () =>\n onPressEscape\n ? () => {\n lastFocusElementRef.current?.focus()\n onPressEscape()\n }\n : undefined,\n [onPressEscape],\n )\n\n useHandleEscape(\n useMemo(\n () => (actualOnPressEscape && isOpen ? actualOnPressEscape : undefined),\n [isOpen, actualOnPressEscape],\n ),\n )\n\n useEffect(() => {\n const focusHandler = (e: FocusEvent) => {\n // e.target(現在フォーカスがあたっている要素)がModeless dialog外の要素であれば、lastFocusElementRefに代入する\n if (e.target instanceof HTMLElement && !wrapperRef?.current?.contains(e.target)) {\n lastFocusElementRef.current = e.target\n }\n }\n\n document.addEventListener('focus', focusHandler, true)\n\n return () => document.removeEventListener('focus', focusHandler, true)\n }, [])\n\n const onDragStart = useCallback((_: any, data: { x: number; y: number }) => setPosition(data), [])\n const onDrag = useCallback((_: any, data: { deltaX: number; deltaY: number }) => {\n setPosition((prev) => ({\n x: prev.x + data.deltaX,\n y: prev.y + data.deltaY,\n }))\n }, [])\n\n return createPortal(\n <DialogOverlap isOpen={isOpen} className={classNames.overlap} as=\"section\">\n <Draggable\n handle=\".smarthr-ui-ModelessDialog-handle\"\n onStart={onDragStart}\n onDrag={onDrag}\n position={position}\n bounds={draggableBounds}\n nodeRef={wrapperRef}\n >\n <Base\n {...rest}\n ref={wrapperRef}\n role=\"dialog\"\n aria-labelledby={labelId}\n radius=\"m\"\n layer={3}\n overflow=\"auto\"\n className={classNames.wrapper}\n style={positionStyle}\n >\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex -- dummy element for focus management. */}\n <div tabIndex={-1} ref={focusTargetRef} />\n <div className={classNames.header}>\n <Handler onArrowKeyDown={handleArrowKey} className={classNames.dialogHandler} />\n <div id={labelId} className={classNames.heading}>\n {/* eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content */}\n <Heading>{heading}</Heading>\n </div>\n <CloseButton onClick={actualOnClickClose} className={classNames.closeButtonLayout} />\n </div>\n <DialogBody\n contentBgColor={contentBgColor}\n contentPadding={contentPadding}\n className=\"smarthr-ui-ModelessDialog-content shr-overscroll-contain\"\n >\n {children}\n </DialogBody>\n {footer && <div className={classNames.footer}>{footer}</div>}\n <LiveRegion regionText={debouncedLiveRegionText} />\n </Base>\n </Draggable>\n </DialogOverlap>,\n )\n}\n\nconst Handler = memo<{\n className: string\n onArrowKeyDown: (e: KeyboardEvent) => void\n}>(({ onArrowKeyDown: onDelegateKeyDown, ...rest }) => {\n const { localize } = useIntl()\n const accessibleDefaultTexts = useMemo(\n () => ({\n dialogHandlerAriaRoleDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaRoleDescription',\n defaultText: 'ドラッグ可能',\n }),\n dialogHandlerDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerDescription',\n defaultText: '矢印キーを押して上下左右に移動できます',\n }),\n dialogHandlerAriaLabel: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaLabel',\n defaultText: 'ダイアログの位置',\n }),\n }),\n [localize],\n )\n\n return (\n <>\n <button\n {...rest}\n type=\"button\"\n aria-label={accessibleDefaultTexts.dialogHandlerAriaLabel}\n aria-roledescription={accessibleDefaultTexts.dialogHandlerAriaRoleDescription}\n aria-describedby=\"handler-description\"\n onKeyDown={onDelegateKeyDown}\n >\n <FaGripIcon />\n </button>\n <div className=\"shr-hidden\" id=\"handler-description\">\n {accessibleDefaultTexts.dialogHandlerDescription}\n </div>\n </>\n )\n})\n\nconst LiveRegion = ({ regionText }: { regionText: string | undefined }) => (\n <div\n role=\"status\"\n className=\"shr-fixed -shr-m-px shr-h-px shr-w-px shr-overflow-hidden shr-whitespace-nowrap\"\n >\n {regionText}\n </div>\n)\n\nconst CloseButton = memo<{\n className: string\n onClick: (e: MouseEvent<HTMLButtonElement>) => void\n}>(({ onClick, className }) => {\n const { localize } = useIntl()\n const closeButtonIconAlt = localize({\n id: 'smarthr-ui/ModelessDialog/closeButtonIconAlt',\n defaultText: '閉じる',\n })\n\n return (\n <div className={className}>\n <Button\n type=\"button\"\n size=\"S\"\n onClick={onClick}\n className=\"smarthr-ui-ModelessDialog-closeButton\"\n >\n <FaXmarkIcon alt={closeButtonIconAlt} />\n </Button>\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+FA;AACE;AACE;AACA;AACA;;;;;AAKC;AACD;;;AAGC;;AAED;AACE;;AAED;AACD;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACE;AACD;AACD;AACD;AACF;AACF;;AAwBC;AACA;;AAEA;AAEA;AACE;;AAIE;;;;;;;;;AAUJ;AACA;;;;AAQA;AACE;AACA;AACD;;AAGD;;;;;;;AAUM;AACA;;;;AAKD;;;AAML;AAEI;AACA;;;;;AAKD;AAIH;;;;;AAQI;AACE;AACE;;AAEE;AACD;;;AAGH;AACE;;AAEE;AACD;;;AAGH;AACE;AACE;;AAED;;;AAGH;AACE;AACE;;AAED;;;;AAIP;;AAKA;;;AAGF;;;;;;;;AAWE;;AAGE;AACE;AACA;AACD;;AAEL;;AAGE;;AAEA;AACE;;;AAKF;;AAGE;;;;;;AAOA;;AAEJ;AAEA;AAEI;AACA;AACF;AAIF;;AAIU;AACA;;AAEJ;;;AAYJ;;AAEE;AACE;;AAEJ;;AAIA;;AAGF;;AAEE;AACE;AACA;AACD;;;AA+CL;AAEA;AAIE;AACA;;AAGM;AACA;;;AAGA;AACA;;;AAGA;AACA;;AAEH;AAIH;AAiBF;AAEA;AASA;AAIE;;AAEE;AACA;AACD;AAED;AAYF;;"}
1
+ {"version":3,"file":"ModelessDialog.cjs","sources":["../../../../src/components/Dialog/ModelessDialog/ModelessDialog.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type PropsWithChildren,\n type ReactNode,\n type RefObject,\n memo,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport Draggable from 'react-draggable'\nimport { type VariantProps, tv } from 'tailwind-variants'\n\nimport { useHandleEscape } from '../../../hooks/useHandleEscape'\nimport { useIntl } from '../../../intl'\nimport { debounce } from '../../../libs/debounce'\nimport { dialogSize } from '../../../tailwind'\nimport { Base, type BaseElementProps } from '../../Base'\nimport { Button } from '../../Button'\nimport { Heading } from '../../Heading'\nimport { FaGripIcon, FaXmarkIcon } from '../../Icon'\nimport { DialogBody, type Props as DialogBodyProps } from '../DialogBody'\nimport { DialogOverlap } from '../DialogOverlap'\nimport { useDialogPortal } from '../useDialogPortal'\n\nimport type { DialogSize } from '../types'\n\ntype AbstractProps = PropsWithChildren<{\n /**\n * ダイアログのタイトルの内容\n */\n heading: ReactNode\n /**\n * ダイアログのフッタ部分の内容\n */\n footer?: ReactNode\n /**\n * ダイアログが開かれているかどうかの真偽値\n */\n isOpen: boolean\n /**\n * 閉じるボタンを押下したときのハンドラ\n */\n onClickClose?: (e: MouseEvent<HTMLButtonElement>) => void\n /**\n * ダイアログが開いている状態で Escape キーを押下したときのハンドラ\n */\n onPressEscape?: () => void\n /**\n * @deprecated ダイアログの幅を指定する場合は、`width` ではなく `size` を使用してください。\n * ダイアログの幅\n */\n width?: string | number\n /**\n * ダイアログの大きさ\n */\n size?: DialogSize\n /**\n * ダイアログの高さ\n */\n height?: string | number\n /**\n * ダイアログを開いたときの初期 top 位置\n */\n top?: string | number\n /**\n * ダイアログを開いたときの初期 left 位置\n */\n left?: string | number\n /**\n * ダイアログを開いたときの初期 right 位置\n */\n right?: string | number\n /**\n * ダイアログを開いたときの初期 bottom 位置\n */\n bottom?: string | number\n /**\n * ポータルの container となる DOM 要素を追加する親要素\n */\n portalParent?: HTMLElement | RefObject<HTMLElement>\n}>\ntype Props = AbstractProps &\n Omit<DialogBodyProps, keyof AbstractProps> &\n Omit<BaseElementProps, keyof AbstractProps> &\n Omit<VariantProps<typeof classNameGenerator>, keyof AbstractProps>\n\nconst classNameGenerator = tv({\n slots: {\n overlap: 'shr-inset-[unset]',\n wrapper: 'smarthr-ui-ModelessDialog shr-fixed shr-flex shr-flex-col',\n headerEl: [\n 'smarthr-ui-ModelessDialog-header shr-border-b-shorthand shr-relative shr-flex shr-cursor-move shr-items-center shr-rounded-tl-l shr-rounded-tr-l shr-pe-1 shr-ps-1.5',\n 'hover:shr-bg-white-darken',\n /* DialogHandlerにフォーカスが当たっているときは、headerもフォーカス状態のスタイルにする。 */\n 'has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-focus-indicator has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-bg-white-darken has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-transition-colors has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-duration-100 has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-ease-in-out',\n ],\n dialogHandler: [\n 'smarthr-ui-ModelessDialog-handle shr-absolute shr-inset-x-0 shr-bottom-0 shr-top-[2px] shr-m-auto shr-flex shr-justify-center shr-rounded-tl-s shr-rounded-tr-s shr-border-none shr-text-grey shr-transition-colors shr-duration-100 shr-ease-in-out',\n 'focus-visible:shr-focus-indicator--none shr-cursor-[inherit] shr-bg-[unset]',\n ],\n headingEl: ['shr-my-1 shr-me-1'],\n closeButtonLayout: [\n 'shr-relative' /* DialogHandlerの上に出すためにスタッキングコンテキストを生成 */,\n 'shr-ml-auto shr-shrink-0',\n ],\n footerEl: 'smarthr-ui-ModelessDialog-footer shr-border-t-shorthand',\n },\n variants: {\n size: {\n XS: { wrapper: dialogSize.XS },\n S: { wrapper: dialogSize.S },\n M: { wrapper: dialogSize.M },\n L: { wrapper: dialogSize.L },\n XL: { wrapper: dialogSize.XL },\n XXL: { wrapper: dialogSize.XXL },\n FULL: { wrapper: dialogSize.FULL },\n },\n resizable: {\n true: {\n wrapper: 'shr-resize shr-overflow-auto',\n },\n false: {},\n },\n },\n})\n\nexport const ModelessDialog: FC<Props> = ({\n heading,\n children,\n contentBgColor,\n contentPadding,\n footer,\n isOpen,\n onPressEscape,\n resizable = false,\n width,\n size,\n height,\n top,\n left,\n right,\n bottom,\n portalParent,\n className,\n id,\n onClickClose,\n ...rest\n}) => {\n const labelId = useId()\n const lastFocusElementRef = useRef<HTMLElement | null>(null)\n const { createPortal } = useDialogPortal(portalParent, id)\n const { localize } = useIntl()\n\n const classNames = useMemo(() => {\n const { overlap, wrapper, headerEl, headingEl, dialogHandler, closeButtonLayout, footerEl } =\n classNameGenerator()\n\n return {\n overlap: overlap({ className }),\n wrapper: wrapper({ size, resizable }),\n header: headerEl(),\n heading: headingEl(),\n dialogHandler: dialogHandler(),\n closeButtonLayout: closeButtonLayout(),\n footer: footerEl(),\n }\n }, [className, size, resizable])\n\n const wrapperRef = useRef<HTMLDivElement>(null)\n const focusTargetRef = useRef<HTMLDivElement>(null)\n\n const [wrapperPosition, setWrapperPosition] = useState<DOMRect | undefined>(undefined)\n const [debouncedLiveRegionText, setDebouncedLiveRegionText] = useState<string>('')\n const [centering, setCentering] = useState<{\n top?: number\n left?: number\n }>({})\n const [position, setPosition] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n })\n const [draggableBounds, setDraggableBounds] =\n useState<ComponentProps<typeof Draggable>['bounds']>()\n const debounceLiveRegionText = useMemo(() => debounce(setDebouncedLiveRegionText, 600), [])\n\n useEffect(() => {\n if (!wrapperPosition) {\n setDebouncedLiveRegionText('')\n return\n }\n\n const txt = localize(\n {\n id: 'smarthr-ui/ModelessDialog/dialogHandlerLiveRegionText',\n defaultText: '上から{top}px、左から{left}px',\n },\n {\n top: Math.trunc(wrapperPosition.top).toString(),\n left: Math.trunc(wrapperPosition.left).toString(),\n },\n )\n\n debounceLiveRegionText(txt)\n }, [localize, wrapperPosition, debounceLiveRegionText])\n\n // 外部propsをrefに保存\n const unstableRef = useRef({ isOpen, onClickClose, onPressEscape })\n unstableRef.current = { isOpen, onClickClose, onPressEscape }\n\n const positionStyle = useMemo(\n () => ({\n top: centering.top ?? top,\n left: centering.left ?? left,\n right,\n bottom,\n width: size ? undefined : width,\n height,\n }),\n [centering, top, left, right, bottom, width, height, size],\n )\n\n const handleArrowKey = useCallback((e: KeyboardEvent) => {\n if (!unstableRef.current.isOpen || document.activeElement !== e.currentTarget) {\n return\n }\n\n const movingDistance = 20\n\n switch (e.key) {\n case 'ArrowUp':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y - movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowDown':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y + movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowLeft':\n setPosition((prev) => ({\n x: prev.x - movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n case 'ArrowRight':\n setPosition((prev) => ({\n x: prev.x + movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n }\n }, [])\n\n useEffect(() => {\n if (wrapperRef.current instanceof Element) {\n setWrapperPosition(wrapperRef.current.getBoundingClientRect())\n }\n }, [position])\n\n useEffect(() => {\n // 中央寄せの座標計算を行う\n if (!wrapperRef.current || !isOpen) {\n return\n }\n\n const isXCenter = left === undefined && right === undefined\n const isYCenter = top === undefined && bottom === undefined\n\n if (isXCenter || isYCenter) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setCentering({\n top: isYCenter ? window.innerHeight / 2 - rect.height / 2 : undefined,\n left: isXCenter ? window.innerWidth / 2 - rect.width / 2 : undefined,\n })\n }\n }, [bottom, isOpen, left, right, top])\n\n useEffect(() => {\n if (!isOpen) return\n\n if (centering.top) {\n setDraggableBounds({ top: centering.top * -1 })\n\n return\n }\n\n if (wrapperRef.current) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setDraggableBounds({ top: rect.top * -1 })\n }\n }, [isOpen, centering.top])\n\n useEffect(() => {\n if (isOpen) {\n setPosition({ x: 0, y: 0 })\n focusTargetRef.current?.focus()\n }\n }, [isOpen])\n\n const actualOnClickClose = useCallback((e: MouseEvent<HTMLButtonElement>) => {\n lastFocusElementRef.current?.focus()\n unstableRef.current.onClickClose?.(e)\n }, [])\n\n // stableなcallbackを作成\n const memoizedOnPressEscape = useCallback(() => {\n lastFocusElementRef.current?.focus()\n unstableRef.current.onPressEscape?.()\n }, [])\n\n useHandleEscape(isOpen ? memoizedOnPressEscape : undefined)\n\n useEffect(() => {\n const focusHandler = (e: FocusEvent) => {\n // e.target(現在フォーカスがあたっている要素)がModeless dialog外の要素であれば、lastFocusElementRefに代入する\n if (e.target instanceof HTMLElement && !wrapperRef?.current?.contains(e.target)) {\n lastFocusElementRef.current = e.target\n }\n }\n\n document.addEventListener('focus', focusHandler, true)\n\n return () => document.removeEventListener('focus', focusHandler, true)\n }, [])\n\n const onDragStart = useCallback((_: any, data: { x: number; y: number }) => setPosition(data), [])\n const onDrag = useCallback((_: any, data: { deltaX: number; deltaY: number }) => {\n setPosition((prev) => ({\n x: prev.x + data.deltaX,\n y: prev.y + data.deltaY,\n }))\n }, [])\n\n return createPortal(\n <DialogOverlap isOpen={isOpen} className={classNames.overlap} as=\"section\">\n <Draggable\n handle=\".smarthr-ui-ModelessDialog-handle\"\n onStart={onDragStart}\n onDrag={onDrag}\n position={position}\n bounds={draggableBounds}\n nodeRef={wrapperRef}\n >\n <Base\n {...rest}\n ref={wrapperRef}\n role=\"dialog\"\n aria-labelledby={labelId}\n radius=\"m\"\n layer={3}\n overflow=\"auto\"\n className={classNames.wrapper}\n style={positionStyle}\n >\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex -- dummy element for focus management. */}\n <div tabIndex={-1} ref={focusTargetRef} />\n <div className={classNames.header}>\n <Handler onArrowKeyDown={handleArrowKey} className={classNames.dialogHandler} />\n <div id={labelId} className={classNames.heading}>\n {/* eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content */}\n <Heading>{heading}</Heading>\n </div>\n <CloseButton onClick={actualOnClickClose} className={classNames.closeButtonLayout} />\n </div>\n <DialogBody\n contentBgColor={contentBgColor}\n contentPadding={contentPadding}\n className=\"smarthr-ui-ModelessDialog-content shr-overscroll-contain\"\n >\n {children}\n </DialogBody>\n {footer && <div className={classNames.footer}>{footer}</div>}\n <LiveRegion regionText={debouncedLiveRegionText} />\n </Base>\n </Draggable>\n </DialogOverlap>,\n )\n}\n\nconst Handler = memo<{\n className: string\n onArrowKeyDown: (e: KeyboardEvent) => void\n}>(({ onArrowKeyDown: onDelegateKeyDown, ...rest }) => {\n const { localize } = useIntl()\n const accessibleDefaultTexts = useMemo(\n () => ({\n dialogHandlerAriaRoleDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaRoleDescription',\n defaultText: 'ドラッグ可能',\n }),\n dialogHandlerDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerDescription',\n defaultText: '矢印キーを押して上下左右に移動できます',\n }),\n dialogHandlerAriaLabel: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaLabel',\n defaultText: 'ダイアログの位置',\n }),\n }),\n [localize],\n )\n\n return (\n <>\n <button\n {...rest}\n type=\"button\"\n aria-label={accessibleDefaultTexts.dialogHandlerAriaLabel}\n aria-roledescription={accessibleDefaultTexts.dialogHandlerAriaRoleDescription}\n aria-describedby=\"handler-description\"\n onKeyDown={onDelegateKeyDown}\n >\n <FaGripIcon />\n </button>\n <div className=\"shr-hidden\" id=\"handler-description\">\n {accessibleDefaultTexts.dialogHandlerDescription}\n </div>\n </>\n )\n})\n\nconst LiveRegion = ({ regionText }: { regionText: string | undefined }) => (\n <div\n role=\"status\"\n className=\"shr-fixed -shr-m-px shr-h-px shr-w-px shr-overflow-hidden shr-whitespace-nowrap\"\n >\n {regionText}\n </div>\n)\n\nconst CloseButton = memo<{\n className: string\n onClick: (e: MouseEvent<HTMLButtonElement>) => void\n}>(({ onClick, className }) => {\n const { localize } = useIntl()\n const closeButtonIconAlt = localize({\n id: 'smarthr-ui/ModelessDialog/closeButtonIconAlt',\n defaultText: '閉じる',\n })\n\n return (\n <div className={className}>\n <Button\n type=\"button\"\n size=\"S\"\n onClick={onClick}\n className=\"smarthr-ui-ModelessDialog-closeButton\"\n >\n <FaXmarkIcon alt={closeButtonIconAlt} />\n </Button>\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+FA;AACE;AACE;AACA;AACA;;;;;AAKC;AACD;;;AAGC;;AAED;AACE;;AAED;AACD;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACE;AACD;AACD;AACD;AACF;AACF;;AAwBC;AACA;;AAEA;AAEA;AACE;;AAIE;;;;;;;;;AAUJ;AACA;;;;AAQA;AACE;AACA;AACD;;AAGD;;;;;;;AAUM;AACA;;;;AAKD;;;;AAOL;;AAGA;AAEI;AACA;;;;;AAKD;AAIH;AACE;;;;AAMA;AACE;AACE;;AAEE;AACD;;;AAGH;AACE;;AAEE;AACD;;;AAGH;AACE;AACE;;AAED;;;AAGH;AACE;AACE;;AAED;;;;;;AAOL;;;AAGF;;;;;;;;AAWE;;AAGE;AACE;AACA;AACD;;AAEL;;AAGE;;AAEA;AACE;;;AAKF;;AAGE;;;;;;AAOA;;AAEJ;AAEA;AACE;;;;AAKF;AACE;AACA;;;;AAMA;;AAEE;AACE;;AAEJ;;AAIA;;AAGF;;AAEE;AACE;AACA;AACD;;;AA+CL;AAEA;AAIE;AACA;;AAGM;AACA;;;AAGA;AACA;;;AAGA;AACA;;AAEH;AAIH;AAiBF;AAEA;AASA;AAIE;;AAEE;AACA;AACD;AAED;AAYF;;"}
@@ -116,6 +116,9 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
116
116
  });
117
117
  debounceLiveRegionText(txt);
118
118
  }, [localize, wrapperPosition, debounceLiveRegionText]);
119
+ // 外部propsをrefに保存
120
+ const unstableRef = useRef({ isOpen, onClickClose, onPressEscape });
121
+ unstableRef.current = { isOpen, onClickClose, onPressEscape };
119
122
  const positionStyle = useMemo(() => ({
120
123
  top: centering.top ?? top,
121
124
  left: centering.left ?? left,
@@ -125,7 +128,7 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
125
128
  height,
126
129
  }), [centering, top, left, right, bottom, width, height, size]);
127
130
  const handleArrowKey = useCallback((e) => {
128
- if (!isOpen || document.activeElement !== e.currentTarget) {
131
+ if (!unstableRef.current.isOpen || document.activeElement !== e.currentTarget) {
129
132
  return;
130
133
  }
131
134
  const movingDistance = 20;
@@ -159,7 +162,7 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
159
162
  e.preventDefault();
160
163
  break;
161
164
  }
162
- }, [isOpen]);
165
+ }, []);
163
166
  useEffect(() => {
164
167
  if (wrapperRef.current instanceof Element) {
165
168
  setWrapperPosition(wrapperRef.current.getBoundingClientRect());
@@ -200,15 +203,14 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
200
203
  }, [isOpen]);
201
204
  const actualOnClickClose = useCallback((e) => {
202
205
  lastFocusElementRef.current?.focus();
203
- onClickClose?.(e);
204
- }, [onClickClose]);
205
- const actualOnPressEscape = useMemo(() => onPressEscape
206
- ? () => {
207
- lastFocusElementRef.current?.focus();
208
- onPressEscape();
209
- }
210
- : undefined, [onPressEscape]);
211
- useHandleEscape(useMemo(() => (actualOnPressEscape && isOpen ? actualOnPressEscape : undefined), [isOpen, actualOnPressEscape]));
206
+ unstableRef.current.onClickClose?.(e);
207
+ }, []);
208
+ // stableなcallbackを作成
209
+ const memoizedOnPressEscape = useCallback(() => {
210
+ lastFocusElementRef.current?.focus();
211
+ unstableRef.current.onPressEscape?.();
212
+ }, []);
213
+ useHandleEscape(isOpen ? memoizedOnPressEscape : undefined);
212
214
  useEffect(() => {
213
215
  const focusHandler = (e) => {
214
216
  // e.target(現在フォーカスがあたっている要素)がModeless dialog外の要素であれば、lastFocusElementRefに代入する