sellmate-design-system-react 2.2.0 → 3.1.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 (113) hide show
  1. package/AGENTS.md +661 -0
  2. package/README.md +85 -0
  3. package/dist/components/SActionModal/README.md +41 -0
  4. package/dist/components/SBadge/README.md +25 -0
  5. package/dist/components/SBarcodeInput/README.md +64 -0
  6. package/dist/components/SButton/README.md +49 -0
  7. package/dist/components/SCalendar/README.md +42 -0
  8. package/dist/components/SCallout/README.md +29 -0
  9. package/dist/components/SCallout/SCallout.cjs +76 -1
  10. package/dist/components/SCallout/SCallout.cjs.map +1 -1
  11. package/dist/components/SCallout/SCallout.d.ts +6 -2
  12. package/dist/components/SCallout/SCallout.js +76 -1
  13. package/dist/components/SCallout/SCallout.js.map +1 -1
  14. package/dist/components/SCard/README.md +12 -0
  15. package/dist/components/SCheckbox/README.md +44 -0
  16. package/dist/components/SChip/README.md +57 -0
  17. package/dist/components/SChipInput/README.md +77 -0
  18. package/dist/components/SCircleProgress/README.md +35 -0
  19. package/dist/components/SConfirmModal/README.md +61 -0
  20. package/dist/components/SDatePicker/README.md +62 -0
  21. package/dist/components/SDateRangePicker/README.md +64 -0
  22. package/dist/components/SDivider/README.md +25 -0
  23. package/dist/components/SDraggableItem/README.md +40 -0
  24. package/dist/components/SDraggableItem/SDraggableItem.cjs +3079 -0
  25. package/dist/components/SDraggableItem/SDraggableItem.cjs.map +1 -0
  26. package/dist/components/SDraggableItem/SDraggableItem.d.ts +33 -0
  27. package/dist/components/SDraggableItem/SDraggableItem.js +3077 -0
  28. package/dist/components/SDraggableItem/SDraggableItem.js.map +1 -0
  29. package/dist/components/SDraggableItem/index.d.ts +1 -0
  30. package/dist/components/SDropdownButton/README.md +50 -0
  31. package/dist/components/SExpansionItem/README.md +40 -0
  32. package/dist/components/SExpansionItem/SExpansionItem.cjs +6 -15
  33. package/dist/components/SExpansionItem/SExpansionItem.cjs.map +1 -1
  34. package/dist/components/SExpansionItem/SExpansionItem.js +6 -15
  35. package/dist/components/SExpansionItem/SExpansionItem.js.map +1 -1
  36. package/dist/components/SField/README.md +91 -0
  37. package/dist/components/SFilePicker/README.md +72 -0
  38. package/dist/components/SForm/README.md +31 -0
  39. package/dist/components/SGhostButton/README.md +76 -0
  40. package/dist/components/SGnb/README.md +56 -0
  41. package/dist/components/SGuide/README.md +34 -0
  42. package/dist/components/SIcon/README.md +91 -0
  43. package/dist/components/SInput/README.md +66 -0
  44. package/dist/components/SKeyValueTable/README.md +59 -0
  45. package/dist/components/SLayout/README.md +42 -0
  46. package/dist/components/SLinearProgress/README.md +17 -0
  47. package/dist/components/SList/README.md +14 -0
  48. package/dist/components/SList/SList.cjs +38 -0
  49. package/dist/components/SList/SList.cjs.map +1 -0
  50. package/dist/components/SList/SList.d.ts +10 -0
  51. package/dist/components/SList/SList.js +36 -0
  52. package/dist/components/SList/SList.js.map +1 -0
  53. package/dist/components/SList/index.d.ts +1 -0
  54. package/dist/components/SListItem/README.md +19 -0
  55. package/dist/components/SListItem/SListItem.cjs +83 -0
  56. package/dist/components/SListItem/SListItem.cjs.map +1 -0
  57. package/dist/components/SListItem/SListItem.d.ts +26 -0
  58. package/dist/components/SListItem/SListItem.js +81 -0
  59. package/dist/components/SListItem/SListItem.js.map +1 -0
  60. package/dist/components/SListItem/index.d.ts +1 -0
  61. package/dist/components/SLoadingContainer/README.md +29 -0
  62. package/dist/components/SLoadingModal/README.md +52 -0
  63. package/dist/components/SModal/README.md +192 -0
  64. package/dist/components/SModalContainer/README.md +49 -0
  65. package/dist/components/SNumberInput/README.md +74 -0
  66. package/dist/components/SPage/README.md +25 -0
  67. package/dist/components/SPagination/README.md +40 -0
  68. package/dist/components/SPopover/README.md +54 -0
  69. package/dist/components/SPopup/README.md +38 -0
  70. package/dist/components/SPortal/README.md +51 -0
  71. package/dist/components/SRadio/README.md +57 -0
  72. package/dist/components/SRadioButton/README.md +24 -0
  73. package/dist/components/SScrollArea/README.md +16 -0
  74. package/dist/components/SSectionHeaderCard/README.md +51 -0
  75. package/dist/components/SSectionHeaderCard/SSectionHeaderCard.cjs +3414 -0
  76. package/dist/components/SSectionHeaderCard/SSectionHeaderCard.cjs.map +1 -0
  77. package/dist/components/SSectionHeaderCard/SSectionHeaderCard.d.ts +32 -0
  78. package/dist/components/SSectionHeaderCard/SSectionHeaderCard.js +3392 -0
  79. package/dist/components/SSectionHeaderCard/SSectionHeaderCard.js.map +1 -0
  80. package/dist/components/SSectionHeaderCard/index.d.ts +1 -0
  81. package/dist/components/SSelect/README.md +79 -0
  82. package/dist/components/SStepper/README.md +30 -0
  83. package/dist/components/SSwitch/README.md +24 -0
  84. package/dist/components/STable/README.md +82 -0
  85. package/dist/components/STableBar/README.md +28 -0
  86. package/dist/components/STabs/README.md +37 -0
  87. package/dist/components/STag/README.md +44 -0
  88. package/dist/components/STextLink/README.md +48 -0
  89. package/dist/components/STextarea/README.md +60 -0
  90. package/dist/components/STimePicker/README.md +65 -0
  91. package/dist/components/STimeRangePicker/README.md +63 -0
  92. package/dist/components/SToast/README.md +70 -0
  93. package/dist/components/SToggle/README.md +22 -0
  94. package/dist/components/STooltip/README.md +57 -0
  95. package/dist/index.cjs +815 -419
  96. package/dist/index.cjs.map +1 -1
  97. package/dist/index.d.ts +4 -0
  98. package/dist/index.js +815 -423
  99. package/dist/index.js.map +1 -1
  100. package/dist/llms.txt +3496 -0
  101. package/dist/styles.css +545 -19
  102. package/dist/theme.css +22 -0
  103. package/eslint/index.mjs +66 -0
  104. package/eslint/lib/class-names.mjs +119 -0
  105. package/eslint/lib/table-column.mjs +53 -0
  106. package/eslint/rules/no-arbitrary-class.mjs +91 -0
  107. package/eslint/rules/no-off-scale-spacing.mjs +67 -0
  108. package/eslint/rules/no-raw-html-control.mjs +117 -0
  109. package/eslint/rules/prefer-typo-preset.mjs +73 -0
  110. package/eslint/rules/require-locale-number.mjs +78 -0
  111. package/eslint/rules/table-numeric-align.mjs +93 -0
  112. package/eslint/scale.gen.mjs +16 -0
  113. package/package.json +9 -4
package/README.md CHANGED
@@ -105,6 +105,91 @@ export function Example() {
105
105
 
106
106
  `SButton` 은 텍스트를 **`label`(문자열)로만** 받습니다 — `children` 은 받지 않으며, 아이콘은 `icon`/`rightIcon` 을 씁니다. 색상 prop 은 `color` 입니다(`variant` 아님).
107
107
 
108
+ ## 화면 작성 규칙 (AI 에이전트 포함)
109
+
110
+ 컴포넌트 조합·간격·타이포 등 **화면을 만들 때 지켜야 할 규칙**은 패키지에 함께 배포됩니다.
111
+
112
+ | 파일 | 용도 |
113
+ | --- | --- |
114
+ | `node_modules/sellmate-design-system-react/AGENTS.md` | 사람이 읽는 사용 규칙서 |
115
+ | `node_modules/sellmate-design-system-react/dist/llms.txt` | 규칙 + 토큰 어휘 + 전체 컴포넌트 Props 를 합친 AI 참조용 단일 문서 |
116
+ | `node_modules/sellmate-design-system-react/dist/components/<이름>/README.md` | 컴포넌트별 Props/Events |
117
+
118
+ Claude 등 AI 에이전트를 쓴다면 소비 앱의 `CLAUDE.md` 에 다음 한 줄을 넣어두면 됩니다.
119
+
120
+ ```md
121
+ UI 작업 전 `node_modules/sellmate-design-system-react/dist/llms.txt` 를 먼저 읽을 것.
122
+ ```
123
+
124
+ ## ESLint 설정
125
+
126
+ 위 규칙 중 **정적으로 검출 가능한 것**은 린트로 강제할 수 있습니다. 소비 앱의 `eslint.config.mjs` 에 추가하세요.
127
+
128
+ ```js
129
+ // eslint.config.mjs
130
+ import sellmate from 'sellmate-design-system-react/eslint';
131
+
132
+ export default [
133
+ // ... 기존 설정
134
+ ...sellmate.configs.recommended,
135
+ ];
136
+ ```
137
+
138
+ | 규칙 | 기본 | 검출 대상 |
139
+ | --- | --- | --- |
140
+ | `sellmate/no-raw-html-control` | error | 대응 컴포넌트가 있는 생 HTML (`<button>` `<input>` `<select>` `<textarea>` `<table>` `<form>` `<dialog>` `<hr>` `<details>` `<progress>`), `alert()`/`confirm()` |
141
+ | `sellmate/no-arbitrary-class` | error | **토큰이 있는 속성**(색·타이포·간격·모서리)의 임의 값 — `text-[14px]`, `bg-[#eee]`, `gap-[13px]`, `rounded-[5px]` |
142
+ | `sellmate/no-off-scale-spacing` | error | 스케일 밖 간격 (`gap-13`, `p-15`) — Tailwind v4 에서 **조용히 무시되는** 값이라 눈으로 찾기 어렵다 |
143
+ | `sellmate/table-numeric-align` | error | 숫자 컬럼(금액·수량 등)에 `align: 'right'` 누락 — **`--fix` 로 자동 교정** |
144
+ | `sellmate/require-locale-number` | error | 숫자 컬럼의 `toLocaleString()` 누락 — 세 자리 콤마는 필수 |
145
+ | `sellmate/prefer-typo-preset` | warn | `text-14 font-bold` 같은 낱개 조합 → `typo-*` 프리셋 |
146
+
147
+ `className` 뿐 아니라 `cn()`/`clsx()` 인자, 템플릿 리터럴, 객체 키 안까지 검사합니다.
148
+
149
+ ### 일부러 잡지 않는 것
150
+
151
+ 과검출을 피하려고 다음은 기본 설정에서 **통과**시킵니다.
152
+
153
+ ```tsx
154
+ // 디자인 토큰이 없는 앱 고유 레이아웃 치수 — 정당한 사용
155
+ <div className="w-[280px] max-w-[1200px] grid-cols-[200px_1fr] top-[64px]" />
156
+
157
+ // 토큰을 var() 로 참조하는 형태
158
+ <div className="h-[var(--sys-size-control-md-height)]" />
159
+
160
+ // 시각 요소가 없어 대체 컴포넌트가 없다
161
+ <input type="hidden" name="csrf" />
162
+
163
+ // 로고 SVG·본문 안 시맨틱 목록·DS 밖 컨트롤의 레이블
164
+ <svg viewBox="0 0 24 24" />
165
+ <ul><li>…</li></ul>
166
+ ```
167
+
168
+ 핵심은 **"디자인 토큰을 하드코딩하지 마라"** 이지 "임의 값을 절대 쓰지 마라" 가 아닙니다.
169
+
170
+ ### 더 엄격하게 / 더 느슨하게
171
+
172
+ `configs.strict` 는 전 규칙을 error 로 올리고 `<ul>` `<ol>` `<li>` `<svg>` `<label>` 까지 검사합니다.
173
+
174
+ ```js
175
+ export default [...sellmate.configs.strict];
176
+ ```
177
+
178
+ 규칙별 예외도 줄 수 있습니다.
179
+
180
+ ```js
181
+ {
182
+ rules: {
183
+ // strict 를 쓰되 로고 SVG 는 허용
184
+ 'sellmate/no-raw-html-control': ['error', { strict: true, allow: ['svg'] }],
185
+ // 그림자만 임의 값 허용
186
+ 'sellmate/no-arbitrary-class': ['error', { ignorePrefixes: ['shadow'] }],
187
+ },
188
+ }
189
+ ```
190
+
191
+ 허용 값 스케일은 `theme.css` 에서 **빌드 시 자동 추출**되므로 토큰이 바뀌면 규칙도 따라갑니다.
192
+
108
193
  ## 제공 컴포넌트
109
194
 
110
195
  분류는 `src/index.ts` 의 export 그룹과 동일합니다.
@@ -0,0 +1,41 @@
1
+ # SActionModal
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SActionModal
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `open?` | `boolean` | — | |
12
+ | `persistent?` | `boolean` | — | true면 백드롭·ESC로 안 닫히고 흔들림 (sd-modal-container persistent) |
13
+ | `modalTitle?` | `string` | `''` | |
14
+ | `button?` | `SActionModalButton` | — | 하단 액션 버튼 (주 액션 1개). 의도적으로 단수다 — 보조 버튼(취소·삭제 등)은 `footerLeft` 슬롯에 직접 배치한다. |
15
+ | `footerLeft?` | `ReactNode` | — | footer 좌측 영역 (sd-action-modal 의 bottom-sub-content 슬롯). 보조 버튼이나 안내 문구를 넣는다. 버튼을 하나 더 쓰고 싶을 때 여기에 SButton 을 넣는다. |
16
+ | `width?` | `number \| string` | — | |
17
+ | `height?` | `number \| string` | — | |
18
+ | `children?` | `ReactNode` | — | |
19
+
20
+ #### Events
21
+
22
+ | Event | Type | Description |
23
+ |-------|------|-------------|
24
+ | `onOpenChange` | `(open: boolean) => void` | |
25
+ | `onClose` | `() => void` | 닫기(X) 버튼 클릭 (sdClose) |
26
+
27
+ ## Dependencies
28
+
29
+ ### Depends on
30
+
31
+ - [SButton](../SButton)
32
+ - [SModalContainer](../SModalContainer)
33
+
34
+ ### Graph
35
+
36
+ ```mermaid
37
+ graph TD;
38
+ SActionModal --> SButton
39
+ SActionModal --> SModalContainer
40
+ style SActionModal fill:#f9f,stroke:#333,stroke-width:4px
41
+ ```
@@ -0,0 +1,25 @@
1
+ # SBadge
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SBadge
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `color?` | `SBadgeColor` | `'blue'` | 뱃지 색상 |
12
+
13
+ ## Dependencies
14
+
15
+ ### Used by
16
+
17
+ - [SList](../SList)
18
+
19
+ ### Graph
20
+
21
+ ```mermaid
22
+ graph TD;
23
+ SList --> SBadge
24
+ style SBadge fill:#f9f,stroke:#333,stroke-width:4px
25
+ ```
@@ -0,0 +1,64 @@
1
+ # SBarcodeInput
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SBarcodeInput
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `value?` | `string \| number \| null` | — | |
12
+ | `size?` | `SBarcodeInputSize` | `'sm'` | |
13
+ | `clearable?` | `boolean` | `false` | |
14
+ | `autoFocus?` | `boolean` | — | |
15
+ | `name?` | `string` | — | |
16
+ | `placeholder?` | `string` | `'입력해 주세요.'` | |
17
+ | `disabled?` | `boolean` | `false` | |
18
+ | `readOnly?` | `boolean` | `false` | |
19
+ | `rules?` | `Rule[]` | — | |
20
+ | `status?` | `SFieldStatus` | — | |
21
+ | `focused?` | `boolean` | — | |
22
+ | `hovered?` | `boolean` | — | |
23
+ | `prefix?` | `ReactNode` | — | 입력 앞 슬롯 |
24
+ | `suffix?` | `ReactNode` | — | 입력 뒤 슬롯 |
25
+ | `inputClass?` | `string` | — | |
26
+ | `inputStyle?` | `CSSProperties` | — | |
27
+ | `label?` | `string` | — | |
28
+ | `labelWidth?` | `number \| string` | — | |
29
+ | `icon?` | `SIconName` | — | |
30
+ | `iconColor?` | `SColor` | — | |
31
+ | `labelTooltip?` | `string` | — | |
32
+ | `labelTooltipProps?` | `Partial<STooltipProps>` | — | |
33
+ | `addonLabel?` | `string` | — | |
34
+ | `addonAlign?` | `SFieldAddonAlign` | — | |
35
+ | `hint?` | `string` | — | |
36
+ | `error?` | `boolean` | — | |
37
+ | `errorMessage?` | `string` | — | |
38
+ | `width?` | `number \| string` | — | |
39
+ | `className?` | `string` | — | |
40
+ | `style?` | `CSSProperties` | — | |
41
+
42
+ #### Events
43
+
44
+ | Event | Type | Description |
45
+ |-------|------|-------------|
46
+ | `onValueChange` | `(value: string) => void` | 값 변경 (sdUpdate) |
47
+ | `onFocus` | `() => void` | |
48
+ | `onBlur` | `() => void` | |
49
+
50
+ ## Dependencies
51
+
52
+ ### Depends on
53
+
54
+ - [SField](../SField)
55
+ - [SGhostButton](../SGhostButton)
56
+
57
+ ### Graph
58
+
59
+ ```mermaid
60
+ graph TD;
61
+ SBarcodeInput --> SField
62
+ SBarcodeInput --> SGhostButton
63
+ style SBarcodeInput fill:#f9f,stroke:#333,stroke-width:4px
64
+ ```
@@ -0,0 +1,49 @@
1
+ # SButton
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SButton
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `color?` | `SButtonColor` | `'primary'` | 색상 — sd-button preset의 색 계열 |
12
+ | `outline?` | `boolean` | `false` | 아웃라인(테두리) 스타일. primary·neutral·danger 만 지원 (secondary 는 무시) |
13
+ | `size?` | `SButtonSize` | `'sm'` | 크기 |
14
+ | `icon?` | `SIconName` | — | 레이블 왼쪽 아이콘 |
15
+ | `rightIcon?` | `SIconName` | — | 레이블 오른쪽 아이콘 |
16
+ | `label?` | `string` | — | 버튼 텍스트 (문자열만 — 아이콘은 icon/rightIcon 사용) |
17
+
18
+ ## Dependencies
19
+
20
+ ### Used by
21
+
22
+ - [SActionModal](../SActionModal)
23
+ - [SConfirmModal](../SConfirmModal)
24
+ - [SDropdownButton](../SDropdownButton)
25
+ - [SKeyValueTable](../SKeyValueTable)
26
+ - [SLoadingModal](../SLoadingModal)
27
+ - [SPopover](../SPopover)
28
+ - [SPopup](../SPopup)
29
+ - [SToast](../SToast)
30
+
31
+ ### Depends on
32
+
33
+ - [SIcon](../SIcon)
34
+
35
+ ### Graph
36
+
37
+ ```mermaid
38
+ graph TD;
39
+ SButton --> SIcon
40
+ SActionModal --> SButton
41
+ SConfirmModal --> SButton
42
+ SDropdownButton --> SButton
43
+ SKeyValueTable --> SButton
44
+ SLoadingModal --> SButton
45
+ SPopover --> SButton
46
+ SPopup --> SButton
47
+ SToast --> SButton
48
+ style SButton fill:#f9f,stroke:#333,stroke-width:4px
49
+ ```
@@ -0,0 +1,42 @@
1
+ # SCalendar
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SCalendar
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `value?` | `string \| null` | — | 선택 날짜 (YYYY-MM-DD) |
12
+ | `selectable?` | `[string, string]` | — | 선택 가능 범위 [시작, 종료] (빈 문자열이면 제한 없음) |
13
+ | `events?` | `SCalendarEventGroup[]` | — | 이벤트 그룹 (날짜별 점) |
14
+ | `elevated?` | `boolean` | `false` | 카드 그림자 |
15
+ | `className?` | `string` | — | |
16
+ | `style?` | `CSSProperties` | — | |
17
+
18
+ #### Events
19
+
20
+ | Event | Type | Description |
21
+ |-------|------|-------------|
22
+ | `onValueChange` | `(date: string) => void` | 선택 변경 (sdUpdate) |
23
+ | `onViewChange` | `(v: { year: number; month: number }) => void` | 보이는 연·월 변경 (sdViewChange) |
24
+
25
+ ## Dependencies
26
+
27
+ ### Used by
28
+
29
+ - [SDatePicker](../SDatePicker)
30
+
31
+ ### Depends on
32
+
33
+ - [SGhostButton](../SGhostButton)
34
+
35
+ ### Graph
36
+
37
+ ```mermaid
38
+ graph TD;
39
+ SCalendar --> SGhostButton
40
+ SDatePicker --> SCalendar
41
+ style SCalendar fill:#f9f,stroke:#333,stroke-width:4px
42
+ ```
@@ -0,0 +1,29 @@
1
+ # SCallout
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SCallout
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `type?` | `SCalloutType` | `'default'` | 콜아웃 타입 (색상·아이콘·타이틀 결정) |
12
+ | `message?` | `SCalloutMessage[]` | `[]` | 표시할 메시지 목록 (배열 중첩으로 depth 표현). 각 문자열은 인라인 HTML(`<b>`, `<a>` 등)을 허용하며 sanitizeInlineHtml 로 살균 후 렌더된다. |
13
+ | `children?` | `ReactNode` | — | message 대신 직접 본문을 넣을 때 |
14
+ | `className?` | `string` | — | |
15
+ | `style?` | `CSSProperties` | — | |
16
+
17
+ ## Dependencies
18
+
19
+ ### Depends on
20
+
21
+ - [SIcon](../SIcon)
22
+
23
+ ### Graph
24
+
25
+ ```mermaid
26
+ graph TD;
27
+ SCallout --> SIcon
28
+ style SCallout fill:#f9f,stroke:#333,stroke-width:4px
29
+ ```
@@ -17,6 +17,75 @@ function cn(...inputs) {
17
17
  return twMerge(clsx.clsx(inputs));
18
18
  }
19
19
 
20
+ // src/lib/sanitize-inline-html.ts
21
+ var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
22
+ var ALLOWED_ATTRS_BY_TAG = {
23
+ A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
24
+ };
25
+ var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
26
+ var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
27
+ var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
28
+ "SCRIPT",
29
+ "STYLE",
30
+ "IFRAME",
31
+ "OBJECT",
32
+ "EMBED",
33
+ "META",
34
+ "LINK",
35
+ "BASE",
36
+ "NOSCRIPT",
37
+ // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
38
+ // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
39
+ "SVG",
40
+ "MATH",
41
+ "TEMPLATE"
42
+ ]);
43
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
44
+ var sanitizeNode = (node) => {
45
+ if (node.nodeType === Node.COMMENT_NODE) {
46
+ node.remove();
47
+ return;
48
+ }
49
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
50
+ const element = node;
51
+ const tagName = element.tagName;
52
+ if (DROP_CONTENT_TAGS.has(tagName)) {
53
+ element.remove();
54
+ return;
55
+ }
56
+ Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
57
+ if (ALLOWED_INLINE_TAGS.has(tagName)) {
58
+ if (element.hasAttribute("className")) {
59
+ const val = element.getAttribute("className");
60
+ element.removeAttribute("className");
61
+ if (!element.hasAttribute("class")) element.setAttribute("class", val);
62
+ }
63
+ const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
64
+ Array.from(element.attributes).forEach((attr) => {
65
+ if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
66
+ });
67
+ if (tagName === "A") {
68
+ const href = element.getAttribute("href");
69
+ if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
70
+ if (element.hasAttribute("target")) {
71
+ element.setAttribute("rel", "noopener noreferrer");
72
+ }
73
+ }
74
+ return;
75
+ }
76
+ const parent = element.parentNode;
77
+ if (parent == null) return;
78
+ while (element.firstChild) parent.insertBefore(element.firstChild, element);
79
+ parent.removeChild(element);
80
+ };
81
+ var sanitizeInlineHtml = (value) => {
82
+ if (typeof document === "undefined") return escapeHtml(value);
83
+ const template = document.createElement("template");
84
+ template.innerHTML = value;
85
+ Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
86
+ return template.innerHTML;
87
+ };
88
+
20
89
  // src/lib/color.ts
21
90
  var COLOR_KEYS = [
22
91
  "white",
@@ -2867,7 +2936,13 @@ function renderItems(message, depth = 1) {
2867
2936
  className: cn("flex items-start", isDepth2 && "pl-[32px]"),
2868
2937
  children: [
2869
2938
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-[24px] shrink-0 text-center", "aria-hidden": true, children: isDepth2 ? "\u2022" : "-" }),
2870
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 min-w-0 flex-1 break-words", children: m })
2939
+ /* @__PURE__ */ jsxRuntime.jsx(
2940
+ "p",
2941
+ {
2942
+ className: "m-0 min-w-0 flex-1 break-words",
2943
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(m) }
2944
+ }
2945
+ )
2871
2946
  ]
2872
2947
  },
2873
2948
  `${depth}-${i}-${out.length}`