@sellmate/design-system 1.0.75 → 1.0.76

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 (173) hide show
  1. package/dist/cjs/design-system.cjs.js +1 -1
  2. package/dist/cjs/index.cjs.js +5 -0
  3. package/dist/cjs/loader.cjs.js +1 -1
  4. package/dist/cjs/sd-button_4.cjs.entry.js +2 -2
  5. package/dist/cjs/sd-ghost-button.cjs.entry.js +10 -5
  6. package/dist/cjs/sd-modal-container.cjs.entry.js +76 -68
  7. package/dist/cjs/sd-pagination_5.cjs.entry.js +889 -0
  8. package/dist/cjs/sd-radio-button.cjs.entry.js +6 -1
  9. package/dist/cjs/sd-select-v2-list-item_4.cjs.entry.js +65 -5
  10. package/dist/cjs/sd-switch.cjs.entry.js +1 -1
  11. package/dist/cjs/sd-table.cjs.entry.js +167 -20
  12. package/dist/cjs/sd-tabs.cjs.entry.js +1 -1
  13. package/dist/cjs/sd-tag.cjs.entry.js +2 -2
  14. package/dist/cjs/sd-td.cjs.entry.js +53 -1
  15. package/dist/cjs/sd-text-link.cjs.entry.js +3 -3
  16. package/dist/cjs/sd-textarea.cjs.entry.js +1 -1
  17. package/dist/cjs/sd-toast-container.cjs.entry.js +1 -1
  18. package/dist/cjs/sd-toast.cjs.entry.js +2 -2
  19. package/dist/cjs/sd-toggle.cjs.entry.js +1 -1
  20. package/dist/collection/components/sd-ghost-button/sd-ghost-button.js +10 -5
  21. package/dist/collection/components/sd-modal-container/sd-modal-container.js +77 -71
  22. package/dist/collection/components/sd-radio-button/sd-radio-button.js +6 -1
  23. package/dist/collection/components/sd-select-v2/sd-select-v2-listbox/sd-select-v2-listbox.js +103 -3
  24. package/dist/collection/components/sd-select-v2/sd-select-v2-trigger/sd-select-v2-trigger.js +2 -2
  25. package/dist/collection/components/sd-select-v2/sd-select-v2.js +82 -4
  26. package/dist/collection/components/sd-switch/sd-switch.js +1 -1
  27. package/dist/collection/components/sd-table/sd-table.css +1 -1
  28. package/dist/collection/components/sd-table/sd-table.js +170 -21
  29. package/dist/collection/components/sd-table/sd-tbody/sd-tbody.js +7 -2
  30. package/dist/collection/components/sd-table/sd-td/sd-td.js +91 -1
  31. package/dist/collection/components/sd-table/sd-thead/sd-thead.js +9 -4
  32. package/dist/collection/components/sd-table/sd-tr/sd-tr.css +8 -0
  33. package/dist/collection/components/sd-table/sd-tr/sd-tr.js +62 -12
  34. package/dist/collection/components/sd-tabs/sd-tabs.js +1 -1
  35. package/dist/collection/components/sd-tag/sd-tag.js +2 -2
  36. package/dist/collection/components/sd-text-link/sd-text-link.js +3 -3
  37. package/dist/collection/components/sd-textarea/sd-textarea.js +1 -1
  38. package/dist/collection/components/sd-toast/sd-toast.js +2 -2
  39. package/dist/collection/components/sd-toast-container/sd-toast-container.js +1 -1
  40. package/dist/collection/components/sd-toggle/sd-toggle.js +1 -1
  41. package/dist/collection/components/sd-tooltip/sd-tooltip.js +2 -2
  42. package/dist/collection/utils/modal.js +5 -0
  43. package/dist/components/index.js +1 -1
  44. package/dist/components/{p-BALOEavB.js → p-6AvsuYqF.js} +1 -1
  45. package/dist/components/{p-CTwEbxRN.js → p-6PsyRF61.js} +1 -1
  46. package/dist/components/{p-DEBakAhm.js → p-7DKZPPev.js} +1 -1
  47. package/dist/components/p-BBD_1E3n.js +1 -0
  48. package/dist/components/p-BQvugXhH.js +1 -0
  49. package/dist/components/p-BRfPoWUn.js +1 -0
  50. package/dist/components/{p-CHFGWh0m.js → p-C-BOe23n.js} +1 -1
  51. package/dist/components/p-C7h8lwnU.js +1 -0
  52. package/dist/components/{p-SDBnyM8D.js → p-CUg9NH6y.js} +1 -1
  53. package/dist/components/{p-C3dI7f7C.js → p-CgMyz4NQ.js} +1 -1
  54. package/dist/components/p-Csfj4h1A.js +1 -0
  55. package/dist/components/{p-Bp0B8tcl.js → p-DAC3TaZV.js} +1 -1
  56. package/dist/components/p-DfOYYI9m.js +1 -0
  57. package/dist/components/{p-H-9uoufd.js → p-d4UB2UF7.js} +1 -1
  58. package/dist/components/p-eEC3ITv0.js +1 -0
  59. package/dist/components/{p-CWEeXx2E.js → p-nVHDJc9g.js} +1 -1
  60. package/dist/components/{p-D8fG9Yt7.js → p-rnbt1m4L.js} +1 -1
  61. package/dist/components/sd-action-modal.js +1 -1
  62. package/dist/components/sd-barcode-input.js +1 -1
  63. package/dist/components/sd-chip.js +1 -1
  64. package/dist/components/sd-confirm-modal.js +1 -1
  65. package/dist/components/sd-date-picker-calendar.js +1 -1
  66. package/dist/components/sd-date-picker.js +1 -1
  67. package/dist/components/sd-date-range-picker-calendar.js +1 -1
  68. package/dist/components/sd-date-range-picker.js +1 -1
  69. package/dist/components/sd-field.js +1 -1
  70. package/dist/components/sd-file-picker.js +1 -1
  71. package/dist/components/sd-ghost-button.js +1 -1
  72. package/dist/components/sd-guide.js +1 -1
  73. package/dist/components/sd-input.js +1 -1
  74. package/dist/components/sd-modal-container.js +1 -1
  75. package/dist/components/sd-number-input.js +1 -1
  76. package/dist/components/sd-popover.js +1 -1
  77. package/dist/components/sd-radio-button.js +1 -1
  78. package/dist/components/sd-select-dropdown.js +1 -1
  79. package/dist/components/sd-select-group.js +1 -1
  80. package/dist/components/sd-select-multiple-group.js +1 -1
  81. package/dist/components/sd-select-multiple.js +1 -1
  82. package/dist/components/sd-select-search-input.js +1 -1
  83. package/dist/components/sd-select-v2-listbox.js +1 -1
  84. package/dist/components/sd-select-v2-trigger.js +1 -1
  85. package/dist/components/sd-select-v2.js +1 -1
  86. package/dist/components/sd-select.js +1 -1
  87. package/dist/components/sd-switch.js +1 -1
  88. package/dist/components/sd-table.js +1 -1
  89. package/dist/components/sd-tabs.js +1 -1
  90. package/dist/components/sd-tag.js +1 -1
  91. package/dist/components/sd-tbody.js +1 -1
  92. package/dist/components/sd-td.js +1 -1
  93. package/dist/components/sd-text-link.js +1 -1
  94. package/dist/components/sd-textarea.js +1 -1
  95. package/dist/components/sd-thead.js +1 -1
  96. package/dist/components/sd-toast-container.js +1 -1
  97. package/dist/components/sd-toast.js +1 -1
  98. package/dist/components/sd-toggle.js +1 -1
  99. package/dist/components/sd-tooltip.js +1 -1
  100. package/dist/components/sd-tr.js +1 -1
  101. package/dist/design-system/design-system.esm.js +1 -1
  102. package/dist/design-system/index.esm.js +1 -1
  103. package/dist/design-system/p-0e1b27cc.entry.js +1 -0
  104. package/dist/design-system/p-11029f6e.entry.js +1 -0
  105. package/dist/design-system/{p-cc62c180.entry.js → p-140b40ab.entry.js} +1 -1
  106. package/dist/design-system/p-34f7345b.entry.js +1 -0
  107. package/dist/design-system/p-363c9451.entry.js +1 -0
  108. package/dist/design-system/{p-fdcfaa7c.entry.js → p-506f2b68.entry.js} +1 -1
  109. package/dist/design-system/{p-8200b5f2.entry.js → p-531a6a82.entry.js} +1 -1
  110. package/dist/design-system/p-55b65a41.entry.js +1 -0
  111. package/dist/design-system/{p-d1dfa0e1.entry.js → p-68d0d67e.entry.js} +1 -1
  112. package/dist/design-system/p-7fe3a466.entry.js +1 -0
  113. package/dist/design-system/{p-05a1c092.entry.js → p-9466cd93.entry.js} +1 -1
  114. package/dist/design-system/{p-33bec0e3.entry.js → p-b683f2fe.entry.js} +1 -1
  115. package/dist/design-system/p-c521e731.entry.js +1 -0
  116. package/dist/design-system/{p-16a15368.entry.js → p-c9eb70f5.entry.js} +1 -1
  117. package/dist/design-system/p-d1846df9.entry.js +1 -0
  118. package/dist/design-system/{p-2d154fe0.entry.js → p-fdb52620.entry.js} +1 -1
  119. package/dist/esm/design-system.js +1 -1
  120. package/dist/esm/index.js +5 -0
  121. package/dist/esm/loader.js +1 -1
  122. package/dist/esm/sd-button_4.entry.js +2 -2
  123. package/dist/esm/sd-ghost-button.entry.js +10 -5
  124. package/dist/esm/sd-modal-container.entry.js +76 -68
  125. package/dist/esm/sd-pagination_5.entry.js +883 -0
  126. package/dist/esm/sd-radio-button.entry.js +6 -1
  127. package/dist/esm/sd-select-v2-list-item_4.entry.js +65 -5
  128. package/dist/esm/sd-switch.entry.js +1 -1
  129. package/dist/esm/sd-table.entry.js +168 -21
  130. package/dist/esm/sd-tabs.entry.js +1 -1
  131. package/dist/esm/sd-tag.entry.js +2 -2
  132. package/dist/esm/sd-td.entry.js +53 -1
  133. package/dist/esm/sd-text-link.entry.js +3 -3
  134. package/dist/esm/sd-textarea.entry.js +1 -1
  135. package/dist/esm/sd-toast-container.entry.js +1 -1
  136. package/dist/esm/sd-toast.entry.js +2 -2
  137. package/dist/esm/sd-toggle.entry.js +1 -1
  138. package/dist/types/components/sd-ghost-button/sd-ghost-button.d.ts +1 -0
  139. package/dist/types/components/sd-modal-container/sd-modal-container.config.d.ts +1 -1
  140. package/dist/types/components/sd-modal-container/sd-modal-container.d.ts +6 -4
  141. package/dist/types/components/sd-radio-button/sd-radio-button.d.ts +1 -0
  142. package/dist/types/components/sd-select-v2/sd-select-v2-listbox/sd-select-v2-listbox.d.ts +9 -0
  143. package/dist/types/components/sd-select-v2/sd-select-v2.d.ts +4 -0
  144. package/dist/types/components/sd-table/sd-table.d.ts +17 -0
  145. package/dist/types/components/sd-table/sd-td/sd-td.d.ts +8 -0
  146. package/dist/types/components/sd-table/sd-tr/sd-tr.d.ts +4 -0
  147. package/dist/types/components.d.ts +52 -0
  148. package/hydrate/index.js +481 -141
  149. package/hydrate/index.mjs +481 -141
  150. package/package.json +1 -1
  151. package/dist/cjs/sd-pagination_2.cjs.entry.js +0 -427
  152. package/dist/cjs/sd-tbody.cjs.entry.js +0 -66
  153. package/dist/cjs/sd-thead.cjs.entry.js +0 -179
  154. package/dist/cjs/sd-tr.cjs.entry.js +0 -171
  155. package/dist/components/p-Bbs5Ws0k.js +0 -1
  156. package/dist/components/p-CgL8_FSD.js +0 -1
  157. package/dist/components/p-DuMkBStM.js +0 -1
  158. package/dist/components/p-vQDL-PZ8.js +0 -1
  159. package/dist/design-system/p-380198bc.entry.js +0 -1
  160. package/dist/design-system/p-6b537e2f.entry.js +0 -1
  161. package/dist/design-system/p-6e90fb80.entry.js +0 -1
  162. package/dist/design-system/p-7b77c65c.entry.js +0 -1
  163. package/dist/design-system/p-8f88bd67.entry.js +0 -1
  164. package/dist/design-system/p-ba5fea6f.entry.js +0 -1
  165. package/dist/design-system/p-be54d6bd.entry.js +0 -1
  166. package/dist/design-system/p-c3379a6e.entry.js +0 -1
  167. package/dist/design-system/p-dc07d618.entry.js +0 -1
  168. package/dist/design-system/p-ef09409c.entry.js +0 -1
  169. package/dist/design-system/p-f8237991.entry.js +0 -1
  170. package/dist/esm/sd-pagination_2.entry.js +0 -424
  171. package/dist/esm/sd-tbody.entry.js +0 -64
  172. package/dist/esm/sd-thead.entry.js +0 -177
  173. package/dist/esm/sd-tr.entry.js +0 -169
@@ -40,7 +40,7 @@ export class SdSwitch {
40
40
  '--sd-switch-line-height': `${SWITCH_TYPOGRAPHY.lineHeight}px`,
41
41
  '--sd-switch-text-decoration': SWITCH_TYPOGRAPHY.textDecoration,
42
42
  };
43
- return (h("label", { key: 'ac6a35d06ddd04bc94369014ff586766dd745bbb', "aria-label": this.label || 'switch', class: this.switchClasses, style: cssVars }, h("input", { key: '9ebc810e93c9dfa3bf7407f279dadbada579196e', type: "checkbox", checked: this.value, disabled: this.disabled, onInput: this.handleChange }), h("div", { key: '7add76be99645d6434e95305f6318fac7da0de57', class: "sd-switch__track" }, h("div", { key: 'ed90778aa4dcd6f0853029579c36cfdb8640afcf', class: "sd-switch__knob" })), this.label && h("span", { key: '70e3ddf819e77da4c693a4853ddb7f392381964e', class: "sd-switch__label" }, this.label)));
43
+ return (h("label", { key: '469c012285d3c8a33792a460e74d8566c384efe8', "aria-label": this.label || 'switch', class: this.switchClasses, style: cssVars }, h("input", { key: '9678e3325339a47e3e2d81ce3cd752c86ed0f906', type: "checkbox", checked: this.value, disabled: this.disabled, onInput: this.handleChange }), h("div", { key: '47348914869f5215957a652cfcf3a11807a0216f', class: "sd-switch__track" }, h("div", { key: '0676260c42e6b79acec710f0f9ba72f01a3a7c18', class: "sd-switch__knob" })), this.label && h("span", { key: 'b92597092795bff38d2acf0cff76f9c381435438', class: "sd-switch__label" }, this.label)));
44
44
  }
45
45
  static get is() { return "sd-switch"; }
46
46
  static get originalStyleUrls() {
@@ -11,7 +11,7 @@ sd-table *,
11
11
  }
12
12
 
13
13
  .sd-table__container {
14
- height: var(--table-height, auto);
14
+ height: var(--table-height, 100%);
15
15
  width: var(--table-width, 100%);
16
16
  max-width: 100%;
17
17
  min-width: 0;
@@ -1,4 +1,4 @@
1
- import { h, Host, readTask, } from "@stencil/core";
1
+ import { h, Host, readTask, forceUpdate, } from "@stencil/core";
2
2
  import { nanoid } from "nanoid";
3
3
  import { TABLE_ID_ATTR, } from "./constants";
4
4
  export class SdTable {
@@ -47,11 +47,17 @@ export class SdTable {
47
47
  scrolledRight = false;
48
48
  rowCount = 0;
49
49
  loadingScrollTop = 0;
50
+ // light DOM에 sd-thead / sd-tbody 자식이 없으면 sd-table이 직접 렌더해야 함을 알리는 플래그.
51
+ // componentWillLoad에서 한 번 결정되며, 이후 동적 토글은 지원하지 않는다.
52
+ autoThead = false;
53
+ autoTbody = false;
50
54
  vsStart = 0;
51
55
  vsEnd = 0;
52
56
  lastReachEndNotifiedRowCount = -1;
53
57
  scrollContainer = null;
54
58
  onScroll;
59
+ // 키: `${rowKey}::${field}` → { rowspan, colspan }
60
+ spanRegistry = new Map();
55
61
  toFiniteNumber(value, fallback) {
56
62
  const n = typeof value === 'number' ? value : Number(value);
57
63
  return Number.isFinite(n) ? n : fallback;
@@ -130,9 +136,16 @@ export class SdTable {
130
136
  this.innerRowsPerPage = newVal.rowsPerPage;
131
137
  }
132
138
  }
139
+ detectChildren() {
140
+ const hasThead = !!this.el.querySelector(':scope > sd-thead');
141
+ const hasTbody = !!this.el.querySelector(':scope > sd-tbody');
142
+ this.autoThead = !hasThead;
143
+ this.autoTbody = !hasTbody;
144
+ }
133
145
  componentWillLoad() {
134
146
  this.syncTableIdAttribute();
135
147
  this.handleNoDataLabelChange(this.noDataLabel);
148
+ this.detectChildren();
136
149
  this.innerSelected = new Set(this.selected || []);
137
150
  this.columnWidths = (this.columns || []).map(c => parseInt(c.width || '120', 10));
138
151
  if (this.pagination?.page) {
@@ -155,6 +168,11 @@ export class SdTable {
155
168
  el.getTableIdSync = () => this.getResolvedTableId();
156
169
  el.getVirtualScrollConfigSync = this.getVirtualScrollConfigSync.bind(this);
157
170
  el.calculateVisibleRange = this.calculateVisibleRange.bind(this);
171
+ el.registerSpanSync = this.registerSpanSync.bind(this);
172
+ el.unregisterSpanSync = this.unregisterSpanSync.bind(this);
173
+ el.getSpanSync = this.getSpanSync.bind(this);
174
+ el.isCoveredSync = this.isCoveredSync.bind(this);
175
+ el.hasRowspanSync = this.hasRowspanSync.bind(this);
158
176
  if (Array.isArray(this.rows)) {
159
177
  this.rowCount = this.rows.length;
160
178
  this.pushRowsToChildren(this.rows);
@@ -203,11 +221,22 @@ export class SdTable {
203
221
  this.scrollContainer.removeEventListener('scroll', this.onScroll);
204
222
  }
205
223
  }
224
+ // light DOM(manual mode 자식)과 shadow DOM(autoThead/autoTbody fallback) 양쪽 모두에서 자식을 찾는다.
225
+ queryChildEl(selector) {
226
+ return (this.el.querySelector(selector) ??
227
+ this.el.shadowRoot?.querySelector(selector) ??
228
+ null);
229
+ }
230
+ queryAllTr() {
231
+ const light = Array.from(this.el.querySelectorAll('sd-tr'));
232
+ const shadow = Array.from(this.el.shadowRoot?.querySelectorAll('sd-tr') ?? []);
233
+ return [...light, ...shadow];
234
+ }
206
235
  pushRowsToChildren(rows) {
207
- const tbody = this.el.querySelector('sd-tbody');
236
+ const tbody = this.queryChildEl('sd-tbody');
208
237
  if (tbody)
209
238
  tbody.rows = rows;
210
- const thead = this.el.querySelector('sd-thead');
239
+ const thead = this.queryChildEl('sd-thead');
211
240
  if (thead)
212
241
  thead.rows = rows;
213
242
  }
@@ -218,16 +247,14 @@ export class SdTable {
218
247
  this.refreshChildrenConfig();
219
248
  };
220
249
  refreshChildrenSelection() {
221
- const thead = this.el.querySelector('sd-thead');
222
- const rows = this.el.querySelectorAll('sd-tr');
250
+ const thead = this.queryChildEl('sd-thead');
223
251
  thead?.refreshSelection?.();
224
- rows.forEach(tr => tr?.refreshSelection?.());
252
+ this.queryAllTr().forEach(tr => tr?.refreshSelection?.());
225
253
  }
226
254
  refreshChildrenConfig() {
227
- const thead = this.el.querySelector('sd-thead');
228
- const rows = this.el.querySelectorAll('sd-tr');
255
+ const thead = this.queryChildEl('sd-thead');
229
256
  thead?.refreshConfig?.();
230
- rows.forEach(tr => tr?.refreshConfig?.());
257
+ this.queryAllTr().forEach(tr => tr?.refreshConfig?.());
231
258
  }
232
259
  maybeEmitVirtualReachEnd(start, end) {
233
260
  const threshold = Math.max(1, this.virtualEndThreshold);
@@ -259,7 +286,7 @@ export class SdTable {
259
286
  this.vsEnd = end;
260
287
  const topHeight = start * this.rowHeight;
261
288
  const bottomHeight = Math.max(0, (this.rowCount - end) * this.rowHeight);
262
- const tbody = this.el.querySelector('sd-tbody');
289
+ const tbody = this.queryChildEl('sd-tbody');
263
290
  tbody?.setSpacersSync?.(topHeight, bottomHeight);
264
291
  if (rangeChanged) {
265
292
  this.sdVirtualUpdate.emit({
@@ -351,8 +378,7 @@ export class SdTable {
351
378
  this.updateRowsVisibility();
352
379
  }
353
380
  updateRowsVisibility() {
354
- const rows = this.el.querySelectorAll('sd-tr');
355
- rows.forEach(tr => tr?.updateVisibility?.());
381
+ this.queryAllTr().forEach(tr => tr?.updateVisibility?.());
356
382
  }
357
383
  changeRowsPerPage(perPage) {
358
384
  const changedRowsPerPage = perPage ? Number(perPage) : 0;
@@ -389,10 +415,9 @@ export class SdTable {
389
415
  const delta = moveEvent.clientX - startX;
390
416
  const newWidth = Math.min(Math.max(startWidth + (reversed ? -delta : delta), minWidth), maxWidth);
391
417
  this.columnWidths = this.columnWidths.map((width, idx) => (idx === index ? newWidth : width));
392
- const thead = this.el.querySelector('sd-thead');
393
- const rows = this.el.querySelectorAll('sd-tr');
418
+ const thead = this.queryChildEl('sd-thead');
394
419
  thead?.setColumnWidths?.(this.columnWidths);
395
- rows.forEach(tr => tr?.setColumnWidths?.(this.columnWidths));
420
+ this.queryAllTr().forEach(tr => tr?.setColumnWidths?.(this.columnWidths));
396
421
  const stickyRightCount = this.stickyColumn?.right || 0;
397
422
  const visibleColCount = this.columns.filter(c => c.visible !== false).length;
398
423
  const isRightStickyEdgeResizer = stickyRightCount > 0 && index === visibleColCount - stickyRightCount;
@@ -429,6 +454,112 @@ export class SdTable {
429
454
  async getStickyStyle(colIdx) {
430
455
  return this.getStickyStyleSync(colIdx);
431
456
  }
457
+ // ─── rowspan / colspan registry ─────────────────────────────────
458
+ // sd-td가 mount/unmount 시 자기 (rowKey, field)와 span을 등록한다.
459
+ // sd-tr는 render마다 isCoveredSync로 자신의 셀 위치가 다른 셀의 span에
460
+ // 덮였는지 판정해 <td>를 그릴지 결정한다.
461
+ spanKey(rowKey, field) {
462
+ return `${rowKey}::${field}`;
463
+ }
464
+ // span 등록은 sd-td의 lifecycle에서 비동기적으로 일어나므로,
465
+ // 등록/해제 직후 형제 sd-tr들이 새 레지스트리 상태로 다시 그려져야
466
+ // 덮인 셀이 사라지거나 다시 나타난다.
467
+ // forceUpdate는 React 래퍼 환경에서 prop 동기화 사이클과 부딪혀 누락되는
468
+ // 경우가 있어, sd-tr의 @State (spansVersion)을 통해 재렌더를 강제한다.
469
+ requestAllTrUpdate() {
470
+ this.queryAllTr().forEach(tr => {
471
+ const trAny = tr;
472
+ if (typeof trAny.bumpSpansVersion === 'function') {
473
+ trAny.bumpSpansVersion();
474
+ }
475
+ else {
476
+ forceUpdate(tr);
477
+ }
478
+ });
479
+ }
480
+ registerSpanSync(rowKey, field, rowspan, colspan) {
481
+ if (rowKey == null || !field)
482
+ return;
483
+ const safeRowspan = Math.max(1, Math.floor(rowspan || 1));
484
+ const safeColspan = Math.max(1, Math.floor(colspan || 1));
485
+ const key = this.spanKey(rowKey, field);
486
+ const prev = this.spanRegistry.get(key);
487
+ if (safeRowspan === 1 && safeColspan === 1) {
488
+ if (!prev)
489
+ return;
490
+ this.spanRegistry.delete(key);
491
+ this.requestAllTrUpdate();
492
+ return;
493
+ }
494
+ if (prev && prev.rowspan === safeRowspan && prev.colspan === safeColspan)
495
+ return;
496
+ this.spanRegistry.set(key, { rowspan: safeRowspan, colspan: safeColspan });
497
+ this.requestAllTrUpdate();
498
+ }
499
+ unregisterSpanSync(rowKey, field) {
500
+ if (rowKey == null || !field)
501
+ return;
502
+ const key = this.spanKey(rowKey, field);
503
+ if (!this.spanRegistry.has(key))
504
+ return;
505
+ this.spanRegistry.delete(key);
506
+ this.requestAllTrUpdate();
507
+ }
508
+ getSpanSync(rowKey, field) {
509
+ return this.spanRegistry.get(this.spanKey(rowKey, field));
510
+ }
511
+ // 레지스트리에 rowspan>1 항목이 하나라도 있으면 true.
512
+ // hover 동작을 끌지 결정하는 데 사용 — colspan만 있는 경우는 그대로 hover 유지.
513
+ hasRowspanSync() {
514
+ for (const span of this.spanRegistry.values()) {
515
+ if (span.rowspan > 1)
516
+ return true;
517
+ }
518
+ return false;
519
+ }
520
+ isCoveredSync(rowKey, colIdx, columns) {
521
+ if (this.spanRegistry.size === 0)
522
+ return false;
523
+ const visibleCols = columns.filter(c => c.visible !== false);
524
+ // 1. 같은 행 왼쪽 스캔 — colspan으로 이 위치를 덮는 셀이 있는가
525
+ for (let i = 0; i < colIdx; i++) {
526
+ const c = visibleCols[i];
527
+ if (!c)
528
+ continue;
529
+ const field = typeof c.field === 'string' ? c.field : c.name;
530
+ const span = this.spanRegistry.get(this.spanKey(rowKey, field));
531
+ if (!span)
532
+ continue;
533
+ if (i + span.colspan > colIdx)
534
+ return true;
535
+ }
536
+ // 2. 위쪽 행 스캔 — 숫자 변환 가능한 rowKey만 rowspan 평가
537
+ const myRowIdx = Number(rowKey);
538
+ if (!Number.isFinite(myRowIdx))
539
+ return false;
540
+ for (const [key, span] of this.spanRegistry) {
541
+ if (span.rowspan <= 1)
542
+ continue;
543
+ const sepIdx = key.indexOf('::');
544
+ if (sepIdx < 0)
545
+ continue;
546
+ const otherRowKey = key.slice(0, sepIdx);
547
+ const otherField = key.slice(sepIdx + 2);
548
+ const otherRowIdx = Number(otherRowKey);
549
+ if (!Number.isFinite(otherRowIdx))
550
+ continue;
551
+ if (otherRowIdx >= myRowIdx)
552
+ continue;
553
+ if (otherRowIdx + span.rowspan <= myRowIdx)
554
+ continue;
555
+ const otherColIdx = visibleCols.findIndex(c => (typeof c.field === 'string' ? c.field : c.name) === otherField);
556
+ if (otherColIdx < 0)
557
+ continue;
558
+ if (otherColIdx <= colIdx && otherColIdx + span.colspan > colIdx)
559
+ return true;
560
+ }
561
+ return false;
562
+ }
432
563
  setRowCountSync(count) {
433
564
  const safeCount = Math.max(0, Math.floor(this.toFiniteNumber(count, 0)));
434
565
  if (safeCount !== this.rowCount) {
@@ -476,6 +607,22 @@ export class SdTable {
476
607
  return null;
477
608
  return { from: this.vsStart, to: this.vsEnd };
478
609
  }
610
+ // autoTbody fallback에서 sd-table이 직접 sd-tr을 만들어내는 경로.
611
+ // 가상 스크롤은 사용자가 직접 SdTbody+SdTr을 작성해야 하므로 빈 배열을 반환한다.
612
+ renderAutoRows() {
613
+ if (this.useVirtualScroll)
614
+ return null;
615
+ const allRows = this.rows ?? [];
616
+ const pageInfo = this.getPaginationInfoSync();
617
+ const startIdx = pageInfo?.startIndex ?? 0;
618
+ const displayed = pageInfo
619
+ ? allRows.slice(pageInfo.startIndex, pageInfo.endIndex)
620
+ : allRows;
621
+ return displayed.map((row, i) => {
622
+ const absoluteIdx = startIdx + i;
623
+ return (h("sd-tr", { key: absoluteIdx, "row-key": String(absoluteIdx), row: row }));
624
+ });
625
+ }
479
626
  get tableClasses() {
480
627
  return [
481
628
  'sd-table',
@@ -495,24 +642,24 @@ export class SdTable {
495
642
  }
496
643
  render() {
497
644
  const resolvedTableId = this.getResolvedTableId();
498
- return (h(Host, { key: '0b90643721a90b7bb59a6c31f6edeb313849b973' }, h("div", { key: 'e55fe4f476dbea4888a168861e7e6d6951d7d489', class: "sd-table__container", style: {
645
+ return (h(Host, { key: 'd73cd690ad11ce92af37b6f32374f6f891c5b677' }, h("div", { key: 'f51d23212885ad8121b9a4e895fb854f1e142bc4', class: "sd-table__container", style: {
499
646
  '--table-width': this.width,
500
647
  '--table-height': this.height,
501
648
  '--table-container-height': `calc(${this.height || '100%'} - ${this.pagination && this.rowCount > 0 && !this.useVirtualScroll ? 48 : 0}px)`,
502
- } }, h("div", { key: 'de18a900037a7b2f619dd22981c30ed76f5a8111', class: {
649
+ } }, h("div", { key: '84b1ba7b2220ff55304b9c19e59304ca2257cff0', class: {
503
650
  'sd-table__clip': true,
504
651
  'sd-table__clip--has-pagination': !!(this.pagination &&
505
652
  this.pagination.rowsPerPage > 0 &&
506
653
  this.rowCount > 0 &&
507
654
  !this.useVirtualScroll),
508
- } }, h("div", { key: '028e0982d415033adaf420ee6b953241c8f3dbec', class: {
655
+ } }, h("div", { key: 'c901eba67eae29515bf0b3edcc6632b2aacf0f80', class: {
509
656
  'sd-table__wrapper': true,
510
657
  'sd-table__wrapper--loading': this.isLoading,
511
658
  'sd-table__wrapper--no-data': this.rowCount === 0 && !this.isLoading,
512
- } }, this.isLoading && (h("div", { key: '8c9110c9c2c26cddbbed8e1f2782ecfaafd3679c', class: "sd-table__loading", style: { top: `${this.loadingScrollTop}px` } }, h("sd-circle-progress", { key: '51b61f91b0ecebf63fe3e65b94012963f0375e5c', indeterminate: true }))), this.rowCount === 0 && !this.isLoading && (h("div", { key: '0a7a45049014869b8164b081a251218d3c814e87', class: "sd-table__no-data" }, h("slot", { key: 'd4b6219e5dcaaa22be3360f7f0f05a8b32533890', name: "no-data" }, h("span", { key: 'c76848aad8a1d3967f3cd239090e97266e46ab3e' }, this.resolvedNoDataLabel)))), h("table", { key: '2b560d112402549c7043b337d944b0de88c3ba0e', class: this.tableClasses }, h("slot", { key: '1c4583148df6a585f389c1bed0426fa4a3cb4899', name: `${resolvedTableId}-head`, onSlotchange: this.handleStructureSlotChange }), h("slot", { key: 'a5d983106de61c829f9cbeb541d1908fee48468c', name: `${resolvedTableId}-body`, onSlotchange: this.handleStructureSlotChange })))), this.pagination &&
659
+ } }, this.isLoading && (h("div", { key: '35a237d0203b2479dbdb77ac42c918a9375bdfd3', class: "sd-table__loading", style: { top: `${this.loadingScrollTop}px` } }, h("sd-circle-progress", { key: '21a29981c1cdbd679f46abd2e7de7794c9ebbca9', indeterminate: true }))), this.rowCount === 0 && !this.isLoading && (h("div", { key: '07ad28bc6e7556cfe229fc1e952410f388424a7f', class: "sd-table__no-data" }, h("slot", { key: 'bf21e60f5b86614587b704bea2965b9467c7f467', name: "no-data" }, h("span", { key: 'f3d012d12e9189545b0cef52250502d46cb9a764' }, this.resolvedNoDataLabel)))), h("table", { key: '655d3dc017c6445ec454faef0e5e9837b7ee0013', class: this.tableClasses }, this.autoThead ? (h("slot", { name: `${resolvedTableId}-head`, onSlotchange: this.handleStructureSlotChange }, h("sd-thead", { rows: this.rows ?? [] }))) : (h("slot", { name: `${resolvedTableId}-head`, onSlotchange: this.handleStructureSlotChange })), this.autoTbody ? (h("slot", { name: `${resolvedTableId}-body`, onSlotchange: this.handleStructureSlotChange }, h("sd-tbody", { rows: this.rows ?? [] }, this.renderAutoRows()))) : (h("slot", { name: `${resolvedTableId}-body`, onSlotchange: this.handleStructureSlotChange }))))), this.pagination &&
513
660
  this.pagination.rowsPerPage > 0 &&
514
661
  this.rowCount > 0 &&
515
- !this.useVirtualScroll && (h("div", { key: '004355d84e2cfc2fd38a9d4811f14fd66dc5c21b', class: "sd-table__pagination" }, h("sd-pagination", { key: '463586c7e57782a7989962f73cb3d4e8c04f5bce', currentPage: !this.useInternalPagination ? this.pagination.page : this.currentPage, lastPage: !this.useInternalPagination ? this.pagination.lastPage : this.lastPageNumber, onSdPageChange: (e) => this.changePage(e.detail) }), this.useRowsPerPageSelect && (h("sd-select-v2", { key: '1f26de447da63ef0c4a24ffb49666a0687db9991', value: this.useInternalPagination
662
+ !this.useVirtualScroll && (h("div", { key: '7ab0b30a0c0e0a197b0f79c6b07ef5614d5d2879', class: "sd-table__pagination" }, h("sd-pagination", { key: '71d44bba5a82f4d8f7c067e525db15fe9a36c305', currentPage: !this.useInternalPagination ? this.pagination.page : this.currentPage, lastPage: !this.useInternalPagination ? this.pagination.lastPage : this.lastPageNumber, onSdPageChange: (e) => this.changePage(e.detail) }), this.useRowsPerPageSelect && (h("sd-select-v2", { key: 'a9ab33347db0714da188f658a2ca1902502db690', value: this.useInternalPagination
516
663
  ? this.innerRowsPerPage
517
664
  : this.pagination.rowsPerPage, options: this.rowsPerPageOption, width: "128px", emitValue: true, onSdUpdate: e => {
518
665
  if (!this.isRowsPerPageValue(e.detail))
@@ -992,7 +1139,9 @@ export class SdTable {
992
1139
  "scrolledLeft": {},
993
1140
  "scrolledRight": {},
994
1141
  "rowCount": {},
995
- "loadingScrollTop": {}
1142
+ "loadingScrollTop": {},
1143
+ "autoThead": {},
1144
+ "autoTbody": {}
996
1145
  };
997
1146
  }
998
1147
  static get events() {
@@ -28,7 +28,12 @@ export class SdTbody {
28
28
  this.syncTableContext();
29
29
  }
30
30
  syncTableContext() {
31
- const table = this.el.closest('sd-table');
31
+ // sd-table shadow:true이므로 fallback content로 렌더되면 closest가 경계를 못 넘는다.
32
+ // 그 경우 getRootNode().host(=sd-table)로 폴백한다.
33
+ const closest = this.el.closest('sd-table');
34
+ const root = this.el.getRootNode();
35
+ const fromShadow = root instanceof ShadowRoot ? root.host : null;
36
+ const table = closest ?? fromShadow;
32
37
  this.tableEl = table;
33
38
  const fromMethod = table?.getTableIdSync?.();
34
39
  const fromAttr = table?.getAttribute(TABLE_ID_ATTR);
@@ -41,7 +46,7 @@ export class SdTbody {
41
46
  }
42
47
  render() {
43
48
  const hasRows = this.rows.length > 0;
44
- return (h(Host, { key: 'e4c64dbf97185bbc6eaec4b883c65dc8ebd34ece', slot: `${this.tableId}-body` }, h("tbody", { key: 'b2fb886684a5a6055d3af659bba75dd9bc801b73', class: { 'tbody': true, 'tbody--empty': !hasRows } }, hasRows ? ([
49
+ return (h(Host, { key: 'bc9fbd4f08f4d77da60b083dceef4e24e2fb5532', slot: `${this.tableId}-body` }, h("tbody", { key: '81ef875cbc39f988021a211ede716a98d3ea30cd', class: { 'tbody': true, 'tbody--empty': !hasRows } }, hasRows ? ([
45
50
  this.topSpacerHeight > 0 && (h("tr", { key: "spacer-top", class: "tbody__spacer", style: { height: `${this.topSpacerHeight}px`, display: 'block' } })),
46
51
  h("slot", null),
47
52
  this.bottomSpacerHeight > 0 && (h("tr", { key: "spacer-bottom", class: "tbody__spacer", style: { height: `${this.bottomSpacerHeight}px`, display: 'block' } })),
@@ -5,14 +5,22 @@ export class SdTd {
5
5
  field;
6
6
  rowKey;
7
7
  align;
8
+ rowspan;
9
+ colspan;
8
10
  handleFieldChange() {
9
11
  this.syncSlotName();
12
+ this.syncSpanRegistration();
10
13
  }
11
14
  handleRowKeyChange() {
12
15
  this.syncSlotName();
16
+ this.syncSpanRegistration();
17
+ }
18
+ handleSpanChange() {
19
+ this.syncSpanRegistration();
13
20
  }
14
21
  componentWillLoad() {
15
22
  this.syncSlotName();
23
+ this.syncSpanRegistration();
16
24
  // slot 타이밍 엇갈림 대응: 부모 sd-tr forceUpdate로 슬롯 재매칭
17
25
  const parentTr = this.el.parentElement;
18
26
  if (parentTr?.tagName?.toLowerCase() === 'sd-tr') {
@@ -21,6 +29,44 @@ export class SdTd {
21
29
  }
22
30
  componentDidLoad() {
23
31
  this.syncSlotName();
32
+ this.syncSpanRegistration();
33
+ }
34
+ // React StrictMode에서는 disconnect/reconnect 사이클이 일어나면서
35
+ // 동일 인스턴스의 componentWillLoad는 더 이상 호출되지 않는다.
36
+ // 재연결 시점에도 등록 상태를 복구해야 rowspan/colspan이 유지된다.
37
+ connectedCallback() {
38
+ this.syncSpanRegistration();
39
+ }
40
+ disconnectedCallback() {
41
+ const table = this.findTable();
42
+ if (table?.unregisterSpanSync && this.field && this.rowKey != null) {
43
+ table.unregisterSpanSync(String(this.rowKey), this.field);
44
+ this.requestParentTrUpdate();
45
+ }
46
+ }
47
+ findTable() {
48
+ return this.el.closest('sd-table');
49
+ }
50
+ requestParentTrUpdate() {
51
+ const parentTr = this.el.parentElement;
52
+ if (parentTr?.tagName?.toLowerCase() !== 'sd-tr')
53
+ return;
54
+ const trAny = parentTr;
55
+ if (typeof trAny.bumpSpansVersion === 'function') {
56
+ trAny.bumpSpansVersion();
57
+ }
58
+ else {
59
+ forceUpdate(parentTr);
60
+ }
61
+ }
62
+ syncSpanRegistration() {
63
+ const table = this.findTable();
64
+ if (!table?.registerSpanSync || !this.field || this.rowKey == null)
65
+ return;
66
+ const rs = Math.max(1, Math.floor(Number(this.rowspan) || 1));
67
+ const cs = Math.max(1, Math.floor(Number(this.colspan) || 1));
68
+ table.registerSpanSync(String(this.rowKey), this.field, rs, cs);
69
+ this.requestParentTrUpdate();
24
70
  }
25
71
  syncSlotName() {
26
72
  const table = this.el.closest('sd-table');
@@ -34,7 +80,7 @@ export class SdTd {
34
80
  }
35
81
  }
36
82
  render() {
37
- return (h(Host, { key: '672c967273dac405ed4a47fa5939463265075681', class: { [`align-${this.align}`]: Boolean(this.align) } }, h("slot", { key: 'c15e572fdf4a8c68fff8b69b586dfbf9f01dce1b' })));
83
+ return (h(Host, { key: 'da9ce2edb986d4b3cf1a6e5f59030009f1288250', class: { [`align-${this.align}`]: Boolean(this.align) } }, h("slot", { key: '8514071bd38c4f5b1997ae7239b8585a25f97ce0' })));
38
84
  }
39
85
  static get is() { return "sd-td"; }
40
86
  static get originalStyleUrls() {
@@ -105,6 +151,44 @@ export class SdTd {
105
151
  "setter": false,
106
152
  "reflect": false,
107
153
  "attribute": "align"
154
+ },
155
+ "rowspan": {
156
+ "type": "number",
157
+ "mutable": false,
158
+ "complexType": {
159
+ "original": "number",
160
+ "resolved": "number | undefined",
161
+ "references": {}
162
+ },
163
+ "required": false,
164
+ "optional": true,
165
+ "docs": {
166
+ "tags": [],
167
+ "text": ""
168
+ },
169
+ "getter": false,
170
+ "setter": false,
171
+ "reflect": false,
172
+ "attribute": "rowspan"
173
+ },
174
+ "colspan": {
175
+ "type": "number",
176
+ "mutable": false,
177
+ "complexType": {
178
+ "original": "number",
179
+ "resolved": "number | undefined",
180
+ "references": {}
181
+ },
182
+ "required": false,
183
+ "optional": true,
184
+ "docs": {
185
+ "tags": [],
186
+ "text": ""
187
+ },
188
+ "getter": false,
189
+ "setter": false,
190
+ "reflect": false,
191
+ "attribute": "colspan"
108
192
  }
109
193
  };
110
194
  }
@@ -116,6 +200,12 @@ export class SdTd {
116
200
  }, {
117
201
  "propName": "rowKey",
118
202
  "methodName": "handleRowKeyChange"
203
+ }, {
204
+ "propName": "rowspan",
205
+ "methodName": "handleSpanChange"
206
+ }, {
207
+ "propName": "colspan",
208
+ "methodName": "handleSpanChange"
119
209
  }];
120
210
  }
121
211
  }
@@ -38,7 +38,12 @@ export class SdThead {
38
38
  this.resolveConfig();
39
39
  }
40
40
  syncTableContext() {
41
- const table = this.el.closest('sd-table');
41
+ // sd-table shadow:true이므로 fallback content로 렌더되면 closest가 경계를 못 넘는다.
42
+ // 그 경우 getRootNode().host(=sd-table)로 폴백한다.
43
+ const closest = this.el.closest('sd-table');
44
+ const root = this.el.getRootNode();
45
+ const fromShadow = root instanceof ShadowRoot ? root.host : null;
46
+ const table = closest ?? fromShadow;
42
47
  this.tableEl = table;
43
48
  const fromMethod = table?.getTableIdSync?.();
44
49
  const fromAttr = table?.getAttribute(TABLE_ID_ATTR);
@@ -128,16 +133,16 @@ export class SdThead {
128
133
  const stickyLeftCols = this.visibleColumns.slice(0, stickyLeftCount);
129
134
  const middleCols = this.visibleColumns.slice(stickyLeftCount, this.visibleColumns.length - stickyRightCount);
130
135
  const stickyRightCols = this.visibleColumns.slice(this.visibleColumns.length - stickyRightCount);
131
- return (h(Host, { key: '2358499ef65d88005d03b1246604c0fd8eefdde1', slot: `${this.tableId}-head` }, h("thead", { key: '49bb43a19aeb83f3a60367b26dbb6535ebcec743', class: {
136
+ return (h(Host, { key: '5aa1e38311b542d1a0b05b55abc7ef41927508bd', slot: `${this.tableId}-head` }, h("thead", { key: '6d17753988ecd02d900d8a3bd7f2115b534b68a7', class: {
132
137
  'thead': true,
133
138
  'thead--sticky': this._stickyHeader,
134
- } }, h("tr", { key: 'cd6388d79e9a4a5d9edbbda05601abbbefcb1ff4', class: "tr" }, this._selectable && (h("th", { key: 'fa09e82368d45e9fb1d32838ef44e609c698609d', class: {
139
+ } }, h("tr", { key: '9cc729603e126da2f9c8923fe29ece10c7750bb4', class: "tr" }, this._selectable && (h("th", { key: '37b2bcfa77eaf4c44465684c49ae84d566acc18b', class: {
135
140
  'th': true,
136
141
  'th--selected': true,
137
142
  'sticky-left': true,
138
143
  'sticky-left-edge': stickyLeftCount === 0,
139
144
  'is-scrolled-left': stickyLeftCount === 0 && this._scrolledLeft,
140
- }, style: { '--sticky-left-offset': '0px' } }, h("sd-checkbox", { key: '5ab4861aecfac9495101363d5bd809d077c7dbc3', value: this.getIsAllChecked(), disabled: !safeRows.length, onSdUpdate: (e) => this.handleSelectAll(e.detail) }))), stickyLeftCols.map((col, idx) => (h("th", { key: col.name, class: {
145
+ }, style: { '--sticky-left-offset': '0px' } }, h("sd-checkbox", { key: '4284894821640ee1bdbd12b1759efb01e6c06488', value: this.getIsAllChecked(), disabled: !safeRows.length, onSdUpdate: (e) => this.handleSelectAll(e.detail) }))), stickyLeftCols.map((col, idx) => (h("th", { key: col.name, class: {
141
146
  'th': true,
142
147
  [`${col.thClass}`]: Boolean(col.thClass),
143
148
  'sticky-left': true,
@@ -11,6 +11,9 @@ sd-tr * {
11
11
  .tr:hover .td {
12
12
  background-color: #F9F9F9;
13
13
  }
14
+ .tr--no-hover:hover .td {
15
+ background-color: white;
16
+ }
14
17
 
15
18
  .td {
16
19
  display: table-cell;
@@ -108,4 +111,9 @@ sd-tr * {
108
111
  .tr:hover .td.sticky-left,
109
112
  .tr:hover .td.sticky-right {
110
113
  background-color: #F9F9F9;
114
+ }
115
+
116
+ .tr--no-hover:hover .td.sticky-left,
117
+ .tr--no-hover:hover .td.sticky-right {
118
+ background-color: white;
111
119
  }