open-grid 1.0.8 → 1.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 (56) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/OpenGrid-5flQwc3W.js +8434 -0
  3. package/dist/OpenGrid-DahxRY7C.cjs +92 -0
  4. package/dist/open-grid-base.css +61 -0
  5. package/dist/open-grid-react.cjs +1 -1
  6. package/dist/open-grid-react.js +1 -1
  7. package/dist/open-grid-themes.css +96 -1
  8. package/dist/open-grid-vue.cjs +1 -1
  9. package/dist/open-grid-vue.js +1 -1
  10. package/dist/open-grid.cjs +1 -1
  11. package/dist/open-grid.js +2 -2
  12. package/dist/types/core/CellEditManager.d.ts +4 -0
  13. package/dist/types/core/CellEventHandler.d.ts +6 -0
  14. package/dist/types/core/ChartManager.d.ts +58 -0
  15. package/dist/types/core/DataLayer.d.ts +17 -0
  16. package/dist/types/core/DetailManager.d.ts +72 -0
  17. package/dist/types/core/FlatRowModel.d.ts +56 -0
  18. package/dist/types/core/GridRenderer.d.ts +33 -1
  19. package/dist/types/core/GroupTreeManager.d.ts +13 -0
  20. package/dist/types/core/KeyboardManager.d.ts +19 -0
  21. package/dist/types/core/OpenGrid.d.ts +109 -1
  22. package/dist/types/core/RangeSelectionManager.d.ts +95 -0
  23. package/dist/types/core/SortFilterManager.d.ts +2 -0
  24. package/dist/types/core/chart/CanvasAdapter.d.ts +52 -0
  25. package/dist/types/core/chart/DataExtractor.d.ts +49 -0
  26. package/dist/types/core/chart/a11y.d.ts +21 -0
  27. package/dist/types/core/chart/downsample.d.ts +25 -0
  28. package/dist/types/core/chart/hittest.d.ts +20 -0
  29. package/dist/types/core/chart/palette.d.ts +56 -0
  30. package/dist/types/core/chart/scales.d.ts +24 -0
  31. package/dist/types/core/chart/types.d.ts +181 -0
  32. package/dist/types/core/detail/DetailGlyph.d.ts +48 -0
  33. package/dist/types/core/detail/DetailSplice.d.ts +67 -0
  34. package/dist/types/core/detail/DetailState.d.ts +67 -0
  35. package/dist/types/core/detail/SubgridCache.d.ts +73 -0
  36. package/dist/types/core/detail/index.d.ts +14 -0
  37. package/dist/types/core/formula/FormulaEvaluator.d.ts +15 -0
  38. package/dist/types/core/formula/FormulaGraph.d.ts +43 -0
  39. package/dist/types/core/formula/FormulaParser.d.ts +6 -0
  40. package/dist/types/core/formula/FormulaStore.d.ts +17 -0
  41. package/dist/types/core/formula/RecalcCoordinator.d.ts +85 -0
  42. package/dist/types/core/formula/normalizeRefs.d.ts +15 -0
  43. package/dist/types/core/formula/numericLiteral.d.ts +7 -0
  44. package/dist/types/core/formula/serializeFormula.d.ts +6 -0
  45. package/dist/types/core/formula/types.d.ts +104 -0
  46. package/dist/types/core/range/ClipboardCodec.d.ts +30 -0
  47. package/dist/types/core/range/FillEngine.d.ts +63 -0
  48. package/dist/types/core/range/RangeModel.d.ts +53 -0
  49. package/dist/types/core/range/RangeQuery.d.ts +16 -0
  50. package/dist/types/core/range/types.d.ts +47 -0
  51. package/dist/types/core/renderers/CellRenderer.d.ts +2 -0
  52. package/dist/types/core/types.d.ts +242 -0
  53. package/dist/types/index.d.ts +2 -1
  54. package/package.json +1 -1
  55. package/dist/OpenGrid-CZRcxruq.cjs +0 -90
  56. package/dist/OpenGrid-Cjv7Os5a.js +0 -4871
@@ -26,6 +26,11 @@
26
26
  --og-row-selected-bg: #bbdefb;
27
27
  --og-row-selected-color:#0d47a1;
28
28
 
29
+ /* F1: 범위 선택 + 채우기 핸들(11_design_F1_v2.md §6.1) */
30
+ --og-range-bg: rgba(25, 118, 210, 0.12);
31
+ --og-range-border: #1976d2;
32
+ --og-fill-handle-bg: #1976d2;
33
+
29
34
  /* 상태 행 */
30
35
  --og-row-added-bg: #e8f5e9;
31
36
  --og-row-added-color: #1b5e20;
@@ -323,6 +328,29 @@
323
328
  }
324
329
  .og-cell.og-editing { padding: 0; }
325
330
 
331
+ /* ── F1: 범위 선택 + 채우기 핸들(11_design_F1_v2.md §4) ─────────────────
332
+ 면 하이라이트(og-range-selected)의 배경은 GridRenderer 가 인라인 --og-range-bg 로
333
+ 직접 지정한다(호스트 CSS 격리, QA-3). 여기서는 클래스 자체의 시각 보강만 최소로 둔다. */
334
+ .og-cell.og-range-selected { position: relative; }
335
+
336
+ /* 연속 테두리·핸들·채우기 프리뷰는 bodyWrap 자식 오버레이(RangeSelectionManager) 가
337
+ 인라인 스타일로 배치한다(C6). 아래는 로드베어링 아닌 보조 규칙만. */
338
+ .og-range-fill-handle {
339
+ width: 10px; height: 10px;
340
+ margin-left: -5px; margin-top: -5px;
341
+ border-radius: 1px;
342
+ box-shadow: 0 0 0 1px #fff;
343
+ }
344
+ @media (pointer: coarse) {
345
+ /* C8.4: 터치 히트박스 ≥44×44px — 시각 크기는 유지, 히트영역만 확대 */
346
+ .og-range-fill-handle {
347
+ width: 44px; height: 44px;
348
+ margin-left: -22px; margin-top: -22px;
349
+ background-clip: content-box;
350
+ padding: 17px;
351
+ }
352
+ }
353
+
326
354
  /* 인라인 에디터 입력 */
327
355
  .og-cell input.og-cell-input,
328
356
  .og-cell select.og-cell-select,
@@ -1629,6 +1657,39 @@
1629
1657
  opacity: 0.32;
1630
1658
  }
1631
1659
 
1660
+ /* F2(11_design_F2_v2.md §11, HANMS-16): 디테일 패널 인트로 — 높이 애니는 정수슬롯 제약상
1661
+ 불가하나(MVP), 패널 등장 자체는 opacity+translateY 로 손맛을 준다. load-bearing 레이아웃
1662
+ 속성은 GridRenderer 가 인라인으로 고정하므로(NFR-3) 이 클래스는 순수 연출용. */
1663
+ .og-detail-panel-intro {
1664
+ animation: og-detail-in 140ms ease-out;
1665
+ }
1666
+ @keyframes og-detail-in {
1667
+ from { opacity: 0; transform: translateY(6px); }
1668
+ to { opacity: 1; transform: translateY(0); }
1669
+ }
1670
+ @media (prefers-reduced-motion: reduce) {
1671
+ .og-detail-panel-intro { animation: none; }
1672
+ }
1673
+ .og-detail-expander {
1674
+ border-radius: 3px;
1675
+ color: var(--og-primary, #1976d2);
1676
+ font-size: 13px;
1677
+ }
1678
+ .og-detail-expander:hover { background: rgba(25, 118, 210, 0.08); }
1679
+ .og-detail-expander:focus-visible { outline: 2px solid var(--og-primary, #1976d2); outline-offset: 1px; }
1680
+ @media (pointer: coarse) {
1681
+ /* C8.4 / DetailGlyph.ts:24(DETAIL_EXPANDER_MIN_HIT_TARGET_PX=44): expander 터치 히트박스 ≥44×44px —
1682
+ 시각 글리프 크기(버튼 22px)는 유지, 히트 영역만 투명 패딩으로 확대(F1 채우기 핸들과 동일 기법).
1683
+ 부모 셀(og-col-detail-toggle, 폭 28px)의 overflow:hidden을 이 컬럼에 한해 해제해 확대된 히트영역이
1684
+ 잘리지 않게 하고, flex-shrink:0으로 좁은 컬럼에 눌려 패딩이 줄어들지 않게 고정한다. */
1685
+ .og-cell.og-col-detail-toggle { overflow: visible; }
1686
+ .og-detail-expander {
1687
+ flex-shrink: 0;
1688
+ padding: 11px; /* 22px(min-width/height) + 11px×2 = 44px */
1689
+ background-clip: content-box;
1690
+ }
1691
+ }
1692
+
1632
1693
  /* 고정 컬럼 그림자: 레이어드 섀도우로 자연스러운 경계 */
1633
1694
  .og-frozen-last {
1634
1695
  box-shadow: 4px 0 10px -2px rgba(0,0,0,0.12),
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const g=require("react/jsx-runtime"),r=require("react"),D=require("./OpenGrid-CZRcxruq.cjs");function M({data:e,columns:t,height:u=400,width:s="100%",editable:l=!1,sortable:O=!0,filterable:o=!0,rowNumber:x=!1,checkColumn:y=!1,stateColumn:R=!1,draggable:G=!1,frozenColumns:j=0,theme:f="default",options:P,style:W,className:z,onReady:c,onDataChange:b,onCellClick:q,onRowClick:S,onEditEnd:d,onSortChange:v,onFilterChange:E,onRowDrop:T}){const i=r.useRef(null),n=r.useRef(null),A={height:typeof u=="number"?`${u}px`:u,width:typeof s=="number"?`${s}px`:s,display:"block",boxSizing:"border-box",...W};return r.useEffect(()=>{if(!i.current)return;const B={columns:t,height:"100%",width:"100%",editable:l,sortable:O,filterable:o,rowNumber:x,checkColumn:y,stateColumn:R,draggable:G,frozenColumns:j,theme:f,...P,onReady:p=>{n.current=p,e!=null&&e.length&&p.setData(e),c==null||c(p)},...b&&{onDataChange:b},...q&&{onCellClick:q},...S&&{onRowClick:S},...d&&{onEditEnd:d},...v&&{onSortChange:v},...E&&{onFilterChange:E},...T&&{onRowDrop:T}},$=new D.OpenGrid(i.current,B);return n.current=$,()=>{$.destroy(),n.current=null}},[t,l,O,o,x,y,R,G,j]),r.useEffect(()=>{n.current&&e&&n.current.setData(e)},[e]),r.useEffect(()=>{n.current&&n.current.setTheme(f)},[f]),g.jsx("div",{ref:i,style:A,className:z})}const H=r.forwardRef((e,t)=>{r.useRef(null);const u=r.useRef(null);return r.useEffect(()=>{u.current&&t&&(typeof t=="function"?t(u.current):t.current=u.current)}),g.jsx(M,{...e})});H.displayName="OpenGrid";exports.OpenGrid=M;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const g=require("react/jsx-runtime"),r=require("react"),D=require("./OpenGrid-DahxRY7C.cjs");function M({data:e,columns:t,height:u=400,width:s="100%",editable:l=!1,sortable:O=!0,filterable:o=!0,rowNumber:x=!1,checkColumn:y=!1,stateColumn:R=!1,draggable:G=!1,frozenColumns:j=0,theme:f="default",options:P,style:W,className:z,onReady:c,onDataChange:b,onCellClick:q,onRowClick:S,onEditEnd:d,onSortChange:v,onFilterChange:E,onRowDrop:T}){const i=r.useRef(null),n=r.useRef(null),A={height:typeof u=="number"?`${u}px`:u,width:typeof s=="number"?`${s}px`:s,display:"block",boxSizing:"border-box",...W};return r.useEffect(()=>{if(!i.current)return;const B={columns:t,height:"100%",width:"100%",editable:l,sortable:O,filterable:o,rowNumber:x,checkColumn:y,stateColumn:R,draggable:G,frozenColumns:j,theme:f,...P,onReady:p=>{n.current=p,e!=null&&e.length&&p.setData(e),c==null||c(p)},...b&&{onDataChange:b},...q&&{onCellClick:q},...S&&{onRowClick:S},...d&&{onEditEnd:d},...v&&{onSortChange:v},...E&&{onFilterChange:E},...T&&{onRowDrop:T}},$=new D.OpenGrid(i.current,B);return n.current=$,()=>{$.destroy(),n.current=null}},[t,l,O,o,x,y,R,G,j]),r.useEffect(()=>{n.current&&e&&n.current.setData(e)},[e]),r.useEffect(()=>{n.current&&n.current.setTheme(f)},[f]),g.jsx("div",{ref:i,style:A,className:z})}const H=r.forwardRef((e,t)=>{r.useRef(null);const u=r.useRef(null);return r.useEffect(()=>{u.current&&t&&(typeof t=="function"?t(u.current):t.current=u.current)}),g.jsx(M,{...e})});H.displayName="OpenGrid";exports.OpenGrid=M;
2
2
  //# sourceMappingURL=open-grid-react.cjs.map
@@ -1,6 +1,6 @@
1
1
  import { jsx as A } from "react/jsx-runtime";
2
2
  import K, { useRef as f, useEffect as u } from "react";
3
- import { O as L } from "./OpenGrid-Cjv7Os5a.js";
3
+ import { O as L } from "./OpenGrid-5flQwc3W.js";
4
4
  function M({
5
5
  data: r,
6
6
  columns: e,
@@ -1,5 +1,5 @@
1
1
  /* ============================================================
2
- OPEN_GRID — 12가지 테마 정의
2
+ OPEN_GRID — 15가지 테마 정의
3
3
  사용법: <div data-og-theme="ocean"> 또는 grid.setTheme('ocean')
4
4
  ============================================================ */
5
5
 
@@ -628,3 +628,98 @@
628
628
  --og-filter-bg: #ffffff;
629
629
  --og-filter-color: #0f172a;
630
630
  }
631
+
632
+ /* ──────────────────────────────────────────────────────────
633
+ 15. stitch (스티치 — 리넨/크라프트지 + 자수실 핸드크래프트)
634
+ 천에 박음질한 느낌: 대시(점선) 보더를 실땀처럼 사용하고,
635
+ 본문 CSS 변수(--og-row-bg 등)에 아주 옅은 능직(綾織) 그라디언트를
636
+ 중첩해 리넨 결을 표현한다(별도 이미지 없이 순수 CSS gradient).
637
+ ────────────────────────────────────────────────────────── */
638
+ [data-og-theme="stitch"] {
639
+ /* 내부 헬퍼: 능직(리넨) 텍스처 — 공식 변수 계약 밖의 private 변수.
640
+ 8px 타일, 4~5% 불투명도로 배경색 위에 아주 은은하게만 겹친다. */
641
+ --og-stitch-weave:
642
+ linear-gradient(45deg, rgba(124,98,64,0.05) 25%, transparent 25%, transparent 75%, rgba(124,98,64,0.05) 75%) 0 0/8px 8px,
643
+ linear-gradient(-45deg, rgba(124,98,64,0.05) 25%, transparent 25%, transparent 75%, rgba(124,98,64,0.05) 75%) 4px 4px/8px 8px;
644
+
645
+ --og-primary: #a8342a; /* 자수 레드 (embroidery red) */
646
+ --og-primary-light: #f3e0d3;
647
+ --og-primary-dark: #7c2620;
648
+ --og-header-bg: var(--og-stitch-weave), #f1e8d4;
649
+ --og-header-color: #3e2f22;
650
+ --og-header-hover-bg: #e4d6ba;
651
+ --og-header-sort-color: #a8342a;
652
+ --og-row-bg: var(--og-stitch-weave), #faf5e9;
653
+ --og-row-alt-bg: var(--og-stitch-weave), #efe6d2;
654
+ --og-row-hover-bg: #ece0c4;
655
+ --og-row-color: #3e2f22;
656
+ --og-row-selected-bg: #34415e; /* 인디고 실 */
657
+ --og-row-selected-color: #fbf4e4;
658
+ --og-row-added-bg: #e3ead4; /* 모스그린 실 */
659
+ --og-row-added-color: #3b5a2e;
660
+ --og-row-edited-bg: #f5e7c4; /* 머스터드 실 */
661
+ --og-row-edited-color: #7a5209;
662
+ --og-row-removed-bg: #f0dad6; /* 러스트 실 */
663
+ --og-row-removed-color: #6e2a22;
664
+ --og-group-bg: #eadfc0;
665
+ --og-group-color: #3e2f22;
666
+ --og-group-border: #c9a354;
667
+ --og-tree-toggle-color: #34415e;
668
+ --og-tree-line-color: #d8c7a0;
669
+ --og-tree-folder-color: #b9822b; /* 열림: 머스터드 */
670
+ --og-tree-folder-closed-color: #7c6b4e; /* 닫힘: 무광 타우프 */
671
+ --og-tree-file-color: #8a7b63;
672
+ --og-tree-hover-bg: rgba(168,52,42,0.07);
673
+ --og-tree-indent-guide: #e4d7b8;
674
+ --og-merge-bg: #f6ecc9;
675
+ --og-merge-border: #b9822b;
676
+ --og-border-color: #c7b48c; /* 실땀(스티치) 색 — 대시 보더에 사용 */
677
+ --og-focus-border: #a8342a;
678
+ --og-pagination-bg: #f1e8d4;
679
+ --og-pagination-btn-bg: #faf5e9;
680
+ --og-pagination-btn-color:#3e2f22;
681
+ --og-input-bg: #faf5e9;
682
+ --og-input-color: #3e2f22;
683
+ --og-input-border: #a8342a;
684
+ --og-filter-bg: #f1e8d4;
685
+ --og-filter-color: #3e2f22;
686
+ --og-spinner-track: rgba(62,47,34,0.15);
687
+ }
688
+ /* 핵심 라인만 절제해서 대시(점선) 처리 — 셀 세로줄은 과해지므로 손대지 않는다.
689
+ 각 규칙은 base.css 원본 선언이 이미 해당 변만 명시적으로 지정해둔 곳만 override
690
+ 하므로(예: .og-cell은 border-right만 선언되어 있어 대상에서 제외) 다른 테마의
691
+ 보더 렌더링에는 영향이 없다.
692
+
693
+ ⚠ 셀렉터 특정성 주의 (실브라우저 버그 수정, 2026-07-03):
694
+ OpenGrid는 data-og-theme 속성을 그리드 루트 .og-container 자기 자신에 붙인다
695
+ (OpenGrid.ts _mount/setTheme). 따라서:
696
+ - 컨테이너 자신: `[data-og-theme="stitch"] .og-container`(자손 결합자)는
697
+ "속성을 가진 조상의 자손"을 찾으므로 속성이 걸린 요소 자기 자신에는 매치되지
698
+ 않는다 → 컴파운드 셀렉터 `.og-container[data-og-theme="stitch"]`(공백 없음)로
699
+ 교체.
700
+ - 자손 요소(.og-header 등)는 구조상 base.css의 단일 클래스 셀렉터(특정성 0,1,0)
701
+ 보다 여기 셀렉터가 항상 높아야 한다. `.og-container[data-og-theme="stitch"] .og-x`
702
+ (특정성 0,3,0)로 접두를 강화한다.
703
+ - !important가 필요한 대상 (컨테이너/헤더/헤더셀/그룹행): "host isolation" 설계
704
+ (GridRenderer.ts:81 `_header.style.cssText`의 border-bottom, :271 헤더셀
705
+ `_style()`의 borderBottom, OpenGrid.ts:393 `_container.style.border`)로 인해
706
+ 이 요소들은 border를 **인라인 style 속성**으로 직접 갖는다. 인라인 스타일은
707
+ 외부 스타일시트 규칙보다 항상 우선하므로(특정성을 아무리 올려도 소용없음),
708
+ 여기서는 !important 없이는 절대 이길 수 없다 — 실측(Playwright computed
709
+ style)으로 확인. og-group-row는 추가로 base.css 1603행에 이미
710
+ `border-bottom: ... !important` 선언(og-group-row 3중 정의 중 마지막)이
711
+ 있어 어차피 !important가 필요했다.
712
+ - .og-row 는 인라인 border가 없고(base.css 클래스 선언만 있음) 이미 특정성
713
+ 우위만으로 정상 동작함을 실측 확인 → !important 불필요, 최소 적용 유지.
714
+ - .og-footer 는 현재 코어 렌더링 코드에 이 클래스로 생성되는 실 DOM 요소가
715
+ 없어(FooterManager 는 og-footer-bar 사용) 실브라우저 검증 대상에서 제외
716
+ 되었으나, base.css 선언과의 정합성을 위해 셀렉터는 유지한다(인라인 style
717
+ 충돌 사례가 없으므로 !important 불필요). */
718
+ .og-container[data-og-theme="stitch"] { border-style: dashed !important; }
719
+ .og-container[data-og-theme="stitch"] .og-header { border-bottom-style: dashed !important; }
720
+ .og-container[data-og-theme="stitch"] .og-header-cell { border-bottom-style: dashed !important; }
721
+ .og-container[data-og-theme="stitch"] .og-row { border-bottom-style: dashed; }
722
+ .og-container[data-og-theme="stitch"] .og-group-row { border-bottom-style: dashed !important; }
723
+ .og-container[data-og-theme="stitch"] .og-footer { border-top-style: dashed; }
724
+ [data-og-theme="stitch"] .og-empty-message { color: #6b5d46; }
725
+ [data-og-theme="stitch"] .og-loading-mask { background: rgba(250,245,233,0.80); }
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("vue"),p=require("./OpenGrid-CZRcxruq.cjs"),h=a.defineComponent({__name:"OpenGrid",props:{data:{default:()=>[]},columns:{},height:{default:400},width:{default:"100%"},editable:{type:Boolean,default:!1},sortable:{type:Boolean,default:!0},filterable:{type:Boolean,default:!0},rowNumber:{type:Boolean,default:!1},checkColumn:{type:Boolean,default:!1},stateColumn:{type:Boolean,default:!1},draggable:{type:Boolean,default:!1},frozenColumns:{default:0},theme:{default:"default"},options:{}},emits:["update:data","ready","cell-click","row-click","edit-end","sort-change","filter-change","row-check"],setup(r,{expose:c,emit:u}){const e=r,n=u,d=a.ref(),o=a.shallowRef(null);let i=null;const f=a.computed(()=>({height:typeof e.height=="number"?`${e.height}px`:e.height,width:typeof e.width=="number"?`${e.width}px`:e.width}));return a.onMounted(()=>{if(!d.value)return;const l={columns:e.columns,height:"100%",width:"100%",editable:e.editable,sortable:e.sortable,filterable:e.filterable,rowNumber:e.rowNumber,checkColumn:e.checkColumn,stateColumn:e.stateColumn,draggable:e.draggable,frozenColumns:e.frozenColumns,theme:e.theme,...e.options,onReady:t=>{var s;(s=e.data)!=null&&s.length&&t.setData(e.data),n("ready",t)},onCellClick:t=>n("cell-click",t),onRowClick:t=>n("row-click",t),onEditEnd:t=>n("edit-end",t),onSortChange:t=>n("sort-change",t),onFilterChange:t=>n("filter-change",t),onDataChange:t=>{i=t,n("update:data",t)}};o.value=new p.OpenGrid(d.value,l)}),a.watch(()=>e.data,l=>{!o.value||!l||l!==i&&o.value.setData(l)},{deep:!1}),a.watch(()=>e.theme,l=>{o.value&&l&&o.value.setTheme(l)}),a.watch(()=>e.columns,l=>{o.value&&o.value.applyColumns(l)},{deep:!1}),a.onUnmounted(()=>{var l;(l=o.value)==null||l.destroy(),o.value=null}),c({grid:o}),(l,t)=>(a.openBlock(),a.createElementBlock("div",{ref_key:"containerRef",ref:d,class:"og-vue-wrapper",style:a.normalizeStyle(f.value)},null,4))}}),m=(r,c)=>{const u=r.__vccOpts||r;for(const[e,n]of c)u[e]=n;return u},g=m(h,[["__scopeId","data-v-0a49d4fc"]]);exports.OpenGrid=g;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("vue"),p=require("./OpenGrid-DahxRY7C.cjs"),h=a.defineComponent({__name:"OpenGrid",props:{data:{default:()=>[]},columns:{},height:{default:400},width:{default:"100%"},editable:{type:Boolean,default:!1},sortable:{type:Boolean,default:!0},filterable:{type:Boolean,default:!0},rowNumber:{type:Boolean,default:!1},checkColumn:{type:Boolean,default:!1},stateColumn:{type:Boolean,default:!1},draggable:{type:Boolean,default:!1},frozenColumns:{default:0},theme:{default:"default"},options:{}},emits:["update:data","ready","cell-click","row-click","edit-end","sort-change","filter-change","row-check"],setup(r,{expose:c,emit:u}){const e=r,n=u,d=a.ref(),o=a.shallowRef(null);let i=null;const f=a.computed(()=>({height:typeof e.height=="number"?`${e.height}px`:e.height,width:typeof e.width=="number"?`${e.width}px`:e.width}));return a.onMounted(()=>{if(!d.value)return;const l={columns:e.columns,height:"100%",width:"100%",editable:e.editable,sortable:e.sortable,filterable:e.filterable,rowNumber:e.rowNumber,checkColumn:e.checkColumn,stateColumn:e.stateColumn,draggable:e.draggable,frozenColumns:e.frozenColumns,theme:e.theme,...e.options,onReady:t=>{var s;(s=e.data)!=null&&s.length&&t.setData(e.data),n("ready",t)},onCellClick:t=>n("cell-click",t),onRowClick:t=>n("row-click",t),onEditEnd:t=>n("edit-end",t),onSortChange:t=>n("sort-change",t),onFilterChange:t=>n("filter-change",t),onDataChange:t=>{i=t,n("update:data",t)}};o.value=new p.OpenGrid(d.value,l)}),a.watch(()=>e.data,l=>{!o.value||!l||l!==i&&o.value.setData(l)},{deep:!1}),a.watch(()=>e.theme,l=>{o.value&&l&&o.value.setTheme(l)}),a.watch(()=>e.columns,l=>{o.value&&o.value.applyColumns(l)},{deep:!1}),a.onUnmounted(()=>{var l;(l=o.value)==null||l.destroy(),o.value=null}),c({grid:o}),(l,t)=>(a.openBlock(),a.createElementBlock("div",{ref_key:"containerRef",ref:d,class:"og-vue-wrapper",style:a.normalizeStyle(f.value)},null,4))}}),m=(r,c)=>{const u=r.__vccOpts||r;for(const[e,n]of c)u[e]=n;return u},g=m(h,[["__scopeId","data-v-0a49d4fc"]]);exports.OpenGrid=g;
2
2
  //# sourceMappingURL=open-grid-vue.cjs.map
@@ -1,5 +1,5 @@
1
1
  import { defineComponent as p, ref as m, shallowRef as h, computed as g, onMounted as y, watch as d, onUnmounted as b, openBlock as v, createElementBlock as C, normalizeStyle as k } from "vue";
2
- import { O as w } from "./OpenGrid-Cjv7Os5a.js";
2
+ import { O as w } from "./OpenGrid-5flQwc3W.js";
3
3
  const _ = /* @__PURE__ */ p({
4
4
  __name: "OpenGrid",
5
5
  props: {
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const A=require("./OpenGrid-CZRcxruq.cjs");class C{constructor(t,e,o,i={}){this._left=t,this._right=e;const r=document.createElement("div");r.className="og-shuttle",r.style.cssText=`display:flex;gap:6px;align-items:center;justify-content:center;flex-direction:${i.layout==="horizontal"?"row":"column"};`;const c=(m,f,u)=>{const a=document.createElement("button");return a.type="button",a.className="og-shuttle-btn",a.textContent=m,a.title=f,a.style.cssText="min-width:34px;height:30px;padding:0 8px;border:1px solid #bbb;border-radius:7px;background:#fff;cursor:pointer;font-size:14px;color:#444;line-height:1;box-shadow:0 1px 2px rgba(0,0,0,0.06);",a.addEventListener("mouseover",()=>{a.style.background="#f0f6ff",a.style.borderColor="#1976d2"}),a.addEventListener("mouseout",()=>{a.style.background="#fff",a.style.borderColor="#bbb"}),a.addEventListener("click",u),a},_=i.labels??{};r.appendChild(c(_.toRight??"▶","체크한 행을 오른쪽 그리드로 이동",()=>{this._left.moveCheckedTo(this._right)})),r.appendChild(c(_.toLeft??"◀","체크한 행을 왼쪽 그리드로 이동",()=>{this._right.moveCheckedTo(this._left)})),i.includeAll&&(r.appendChild(c(_.allRight??"⏩","왼쪽 전체를 오른쪽으로 이동",()=>{this._moveAll(this._left,this._right)})),r.appendChild(c(_.allLeft??"⏪","오른쪽 전체를 왼쪽으로 이동",()=>{this._moveAll(this._right,this._left)}))),o.appendChild(r),this._el=r}_moveAll(t,e){const o=t.getData().length;o>0&&t.moveRowsTo(e,Array.from({length:o},(i,r)=>r))}destroy(){this._el.remove()}}function T(b,t,e,o){return new C(b,t,e,o)}class v{constructor(t,e){this._data=[],this._roots=[],this._expandedKeys=new Set,this._selectedId=null,this._container=typeof t=="string"?document.querySelector(t):t,this._opts={nodeWidth:160,nodeHeight:72,levelGap:52,siblingGap:20,expandOnLoad:!0,onNodeClick:()=>{},...e},this._container.classList.add("og-orgchart")}setData(t){this._data=t;const{idField:e,parentIdField:o,expandOnLoad:i}=this._opts;i&&this._expandedKeys.size===0&&t.forEach(r=>this._expandedKeys.add(r[e])),this._roots=A.buildTree(t,{idField:e,parentIdField:o},this._expandedKeys),this._render()}setTheme(t){this._container.setAttribute("data-og-theme",t)}expandAll(){const t=e=>{for(const o of e)this._expandedKeys.add(o._treeId),o.children.length&&t(o.children)};t(this._roots),this._rebuild()}collapseAll(){this._expandedKeys.clear(),this._rebuild()}_toggle(t){this._expandedKeys.has(t)?this._expandedKeys.delete(t):this._expandedKeys.add(t),this._rebuild()}_rebuild(){const{idField:t,parentIdField:e}=this._opts;this._roots=A.buildTree(this._data,{idField:t,parentIdField:e},this._expandedKeys),this._render()}_calcLayout(){const{nodeWidth:t,nodeHeight:e,levelGap:o,siblingGap:i}=this._opts,r=new Map;let c=0;const _=u=>{const a=u._depth*(e+o),p=u._expanded?u.children:[];if(!p.length){const g=c;return c+=t+i,r.set(u._treeId,{x:g,y:a}),{minX:g,maxX:g}}let n=1/0,l=-1/0;for(const g of p){const{minX:s,maxX:h}=_(g);s<n&&(n=s),h>l&&(l=h)}const d=n+(l-n+t)/2-t/2;return r.set(u._treeId,{x:d,y:a}),{minX:n,maxX:l}};for(const u of this._roots)_(u);let m=0,f=0;for(const{x:u,y:a}of r.values())u+t>m&&(m=u+t),a+e>f&&(f=a+e);return{layout:r,totalW:m+i,totalH:f+o+16}}_line(t,e,o,i,r){const c=document.createElementNS("http://www.w3.org/2000/svg","line");c.setAttribute("x1",String(e)),c.setAttribute("y1",String(o)),c.setAttribute("x2",String(i)),c.setAttribute("y2",String(r)),c.setAttribute("class","og-orgchart-line"),t.appendChild(c)}_render(){const{nodeWidth:t,nodeHeight:e,levelGap:o,columns:i}=this._opts,{layout:r,totalW:c,totalH:_}=this._calcLayout();this._container.innerHTML="";const m=document.createElement("div");m.className="og-orgchart-wrap",m.style.cssText=`width:${c}px;height:${_}px;`;const f=document.createElementNS("http://www.w3.org/2000/svg","svg");f.setAttribute("width",String(c)),f.setAttribute("height",String(_)),f.style.cssText="position:absolute;top:0;left:0;pointer-events:none;overflow:visible;";const u=p=>{for(const n of p){if(!n._expanded||!n.children.length)continue;const l=r.get(n._treeId),d=l.x+t/2,g=l.y+e,s=g+o/2,h=n.children;if(this._line(f,d,g,d,s),h.length>1){const x=r.get(h[0]._treeId),y=r.get(h[h.length-1]._treeId);this._line(f,x.x+t/2,s,y.x+t/2,s)}for(const x of h){const y=r.get(x._treeId),E=y.x+t/2;this._line(f,E,s,E,y.y)}u(h)}};u(this._roots),m.appendChild(f);const a=p=>{for(const n of p){const l=r.get(n._treeId);if(!l)continue;const d=document.createElement("div");d.className="og-orgchart-node",n._hasChildren&&d.classList.add("og-orgchart-node--branch"),n._expanded&&d.classList.add("og-orgchart-node--expanded"),this._selectedId===n._treeId&&d.classList.add("og-orgchart-node--selected"),d.style.cssText=`left:${l.x}px;top:${l.y}px;width:${t}px;height:${e}px;`;const g=document.createElement("div");g.className="og-orgchart-node-content";for(const s of i){const h=n.data[s.field],x=document.createElement("div");if(x.className="og-orgchart-col"+(s.className?" "+s.className:""),s.style){const y=typeof s.style=="function"?s.style(h,n.data):s.style;x.setAttribute("style",y)}if(s.renderer){const y=s.renderer(h,n.data);typeof y=="string"?x.innerHTML=y:x.appendChild(y)}else x.textContent=h??"";g.appendChild(x)}if(d.appendChild(g),n._hasChildren){const s=document.createElement("button");s.type="button",s.className="og-orgchart-toggle",s.setAttribute("aria-expanded",n._expanded?"true":"false"),s.setAttribute("aria-label",n._expanded?"접기":"펼치기");const h=document.createElement("i");h.setAttribute("aria-hidden","true"),h.className=n._expanded?"bi bi-dash-circle":"bi bi-plus-circle",s.appendChild(h),s.addEventListener("click",x=>{x.stopPropagation(),this._toggle(n._treeId)}),d.appendChild(s)}d.addEventListener("click",()=>{this._selectedId=n._treeId,this._opts.onNodeClick(n._treeId,n.data),this._container.querySelectorAll(".og-orgchart-node--selected").forEach(s=>s.classList.remove("og-orgchart-node--selected")),d.classList.add("og-orgchart-node--selected")}),m.appendChild(d),n._expanded&&n.children.length&&a(n.children)}};a(this._roots),this._container.appendChild(m)}}class N{static parse(t,e={}){var p,n;const{fieldMap:o={},trim:i=!0}=e,c=new DOMParser().parseFromString(t.trim(),"text/xml"),_=c.querySelector("parsererror");if(_)throw new Error(`XML 파싱 오류: ${(p=_.textContent)==null?void 0:p.trim()}`);const m=c.documentElement;let f=e.rowTag;if(!f){const l=e.rootTag?c.querySelector(e.rootTag):m;f=((n=l==null?void 0:l.children[0])==null?void 0:n.tagName)??"row"}const u=c.getElementsByTagName(f),a=[];for(let l=0;l<u.length;l++){const d=u[l],g={};for(const s of Array.from(d.attributes)){const h=o[s.name]??s.name;g[h]=i?s.value.trim():s.value}for(const s of Array.from(d.children)){const h=o[s.tagName]??s.tagName,x=s.textContent??"";g[h]=i?x.trim():x}a.push(g)}return a}static stringify(t,e={}){const{rootTag:o="rows",rowTag:i="row",mode:r="element",fieldMap:c={},declaration:_=!0,indent:m=2,nullAs:f="",excludeFields:u=[]}=e,a=" ".repeat(m),p=[];_&&p.push('<?xml version="1.0" encoding="UTF-8"?>'),p.push(`<${o}>`);for(const n of t){const l=Object.entries(n).filter(([d])=>!u.includes(d));if(r==="attribute"){const d=l.map(([g,s])=>{const h=c[g]??g,x=s==null?f:String(s);return`${h}="${this._escAttr(x)}"`}).join(" ");p.push(`${a}<${i}${d?" "+d:""} />`)}else{p.push(`${a}<${i}>`);for(const[d,g]of l){const s=c[d]??d,h=g==null?f:String(g);p.push(`${a}${a}<${s}>${this._escText(h)}</${s}>`)}p.push(`${a}</${i}>`)}}return p.push(`</${o}>`),p.join(`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const A=require("./OpenGrid-DahxRY7C.cjs");class C{constructor(t,e,o,i={}){this._left=t,this._right=e;const r=document.createElement("div");r.className="og-shuttle",r.style.cssText=`display:flex;gap:6px;align-items:center;justify-content:center;flex-direction:${i.layout==="horizontal"?"row":"column"};`;const c=(m,f,u)=>{const a=document.createElement("button");return a.type="button",a.className="og-shuttle-btn",a.textContent=m,a.title=f,a.style.cssText="min-width:34px;height:30px;padding:0 8px;border:1px solid #bbb;border-radius:7px;background:#fff;cursor:pointer;font-size:14px;color:#444;line-height:1;box-shadow:0 1px 2px rgba(0,0,0,0.06);",a.addEventListener("mouseover",()=>{a.style.background="#f0f6ff",a.style.borderColor="#1976d2"}),a.addEventListener("mouseout",()=>{a.style.background="#fff",a.style.borderColor="#bbb"}),a.addEventListener("click",u),a},_=i.labels??{};r.appendChild(c(_.toRight??"▶","체크한 행을 오른쪽 그리드로 이동",()=>{this._left.moveCheckedTo(this._right)})),r.appendChild(c(_.toLeft??"◀","체크한 행을 왼쪽 그리드로 이동",()=>{this._right.moveCheckedTo(this._left)})),i.includeAll&&(r.appendChild(c(_.allRight??"⏩","왼쪽 전체를 오른쪽으로 이동",()=>{this._moveAll(this._left,this._right)})),r.appendChild(c(_.allLeft??"⏪","오른쪽 전체를 왼쪽으로 이동",()=>{this._moveAll(this._right,this._left)}))),o.appendChild(r),this._el=r}_moveAll(t,e){const o=t.getData().length;o>0&&t.moveRowsTo(e,Array.from({length:o},(i,r)=>r))}destroy(){this._el.remove()}}function T(b,t,e,o){return new C(b,t,e,o)}class v{constructor(t,e){this._data=[],this._roots=[],this._expandedKeys=new Set,this._selectedId=null,this._container=typeof t=="string"?document.querySelector(t):t,this._opts={nodeWidth:160,nodeHeight:72,levelGap:52,siblingGap:20,expandOnLoad:!0,onNodeClick:()=>{},...e},this._container.classList.add("og-orgchart")}setData(t){this._data=t;const{idField:e,parentIdField:o,expandOnLoad:i}=this._opts;i&&this._expandedKeys.size===0&&t.forEach(r=>this._expandedKeys.add(r[e])),this._roots=A.buildTree(t,{idField:e,parentIdField:o},this._expandedKeys),this._render()}setTheme(t){this._container.setAttribute("data-og-theme",t)}expandAll(){const t=e=>{for(const o of e)this._expandedKeys.add(o._treeId),o.children.length&&t(o.children)};t(this._roots),this._rebuild()}collapseAll(){this._expandedKeys.clear(),this._rebuild()}_toggle(t){this._expandedKeys.has(t)?this._expandedKeys.delete(t):this._expandedKeys.add(t),this._rebuild()}_rebuild(){const{idField:t,parentIdField:e}=this._opts;this._roots=A.buildTree(this._data,{idField:t,parentIdField:e},this._expandedKeys),this._render()}_calcLayout(){const{nodeWidth:t,nodeHeight:e,levelGap:o,siblingGap:i}=this._opts,r=new Map;let c=0;const _=u=>{const a=u._depth*(e+o),p=u._expanded?u.children:[];if(!p.length){const g=c;return c+=t+i,r.set(u._treeId,{x:g,y:a}),{minX:g,maxX:g}}let n=1/0,l=-1/0;for(const g of p){const{minX:s,maxX:h}=_(g);s<n&&(n=s),h>l&&(l=h)}const d=n+(l-n+t)/2-t/2;return r.set(u._treeId,{x:d,y:a}),{minX:n,maxX:l}};for(const u of this._roots)_(u);let m=0,f=0;for(const{x:u,y:a}of r.values())u+t>m&&(m=u+t),a+e>f&&(f=a+e);return{layout:r,totalW:m+i,totalH:f+o+16}}_line(t,e,o,i,r){const c=document.createElementNS("http://www.w3.org/2000/svg","line");c.setAttribute("x1",String(e)),c.setAttribute("y1",String(o)),c.setAttribute("x2",String(i)),c.setAttribute("y2",String(r)),c.setAttribute("class","og-orgchart-line"),t.appendChild(c)}_render(){const{nodeWidth:t,nodeHeight:e,levelGap:o,columns:i}=this._opts,{layout:r,totalW:c,totalH:_}=this._calcLayout();this._container.innerHTML="";const m=document.createElement("div");m.className="og-orgchart-wrap",m.style.cssText=`width:${c}px;height:${_}px;`;const f=document.createElementNS("http://www.w3.org/2000/svg","svg");f.setAttribute("width",String(c)),f.setAttribute("height",String(_)),f.style.cssText="position:absolute;top:0;left:0;pointer-events:none;overflow:visible;";const u=p=>{for(const n of p){if(!n._expanded||!n.children.length)continue;const l=r.get(n._treeId),d=l.x+t/2,g=l.y+e,s=g+o/2,h=n.children;if(this._line(f,d,g,d,s),h.length>1){const x=r.get(h[0]._treeId),y=r.get(h[h.length-1]._treeId);this._line(f,x.x+t/2,s,y.x+t/2,s)}for(const x of h){const y=r.get(x._treeId),E=y.x+t/2;this._line(f,E,s,E,y.y)}u(h)}};u(this._roots),m.appendChild(f);const a=p=>{for(const n of p){const l=r.get(n._treeId);if(!l)continue;const d=document.createElement("div");d.className="og-orgchart-node",n._hasChildren&&d.classList.add("og-orgchart-node--branch"),n._expanded&&d.classList.add("og-orgchart-node--expanded"),this._selectedId===n._treeId&&d.classList.add("og-orgchart-node--selected"),d.style.cssText=`left:${l.x}px;top:${l.y}px;width:${t}px;height:${e}px;`;const g=document.createElement("div");g.className="og-orgchart-node-content";for(const s of i){const h=n.data[s.field],x=document.createElement("div");if(x.className="og-orgchart-col"+(s.className?" "+s.className:""),s.style){const y=typeof s.style=="function"?s.style(h,n.data):s.style;x.setAttribute("style",y)}if(s.renderer){const y=s.renderer(h,n.data);typeof y=="string"?x.innerHTML=y:x.appendChild(y)}else x.textContent=h??"";g.appendChild(x)}if(d.appendChild(g),n._hasChildren){const s=document.createElement("button");s.type="button",s.className="og-orgchart-toggle",s.setAttribute("aria-expanded",n._expanded?"true":"false"),s.setAttribute("aria-label",n._expanded?"접기":"펼치기");const h=document.createElement("i");h.setAttribute("aria-hidden","true"),h.className=n._expanded?"bi bi-dash-circle":"bi bi-plus-circle",s.appendChild(h),s.addEventListener("click",x=>{x.stopPropagation(),this._toggle(n._treeId)}),d.appendChild(s)}d.addEventListener("click",()=>{this._selectedId=n._treeId,this._opts.onNodeClick(n._treeId,n.data),this._container.querySelectorAll(".og-orgchart-node--selected").forEach(s=>s.classList.remove("og-orgchart-node--selected")),d.classList.add("og-orgchart-node--selected")}),m.appendChild(d),n._expanded&&n.children.length&&a(n.children)}};a(this._roots),this._container.appendChild(m)}}class N{static parse(t,e={}){var p,n;const{fieldMap:o={},trim:i=!0}=e,c=new DOMParser().parseFromString(t.trim(),"text/xml"),_=c.querySelector("parsererror");if(_)throw new Error(`XML 파싱 오류: ${(p=_.textContent)==null?void 0:p.trim()}`);const m=c.documentElement;let f=e.rowTag;if(!f){const l=e.rootTag?c.querySelector(e.rootTag):m;f=((n=l==null?void 0:l.children[0])==null?void 0:n.tagName)??"row"}const u=c.getElementsByTagName(f),a=[];for(let l=0;l<u.length;l++){const d=u[l],g={};for(const s of Array.from(d.attributes)){const h=o[s.name]??s.name;g[h]=i?s.value.trim():s.value}for(const s of Array.from(d.children)){const h=o[s.tagName]??s.tagName,x=s.textContent??"";g[h]=i?x.trim():x}a.push(g)}return a}static stringify(t,e={}){const{rootTag:o="rows",rowTag:i="row",mode:r="element",fieldMap:c={},declaration:_=!0,indent:m=2,nullAs:f="",excludeFields:u=[]}=e,a=" ".repeat(m),p=[];_&&p.push('<?xml version="1.0" encoding="UTF-8"?>'),p.push(`<${o}>`);for(const n of t){const l=Object.entries(n).filter(([d])=>!u.includes(d));if(r==="attribute"){const d=l.map(([g,s])=>{const h=c[g]??g,x=s==null?f:String(s);return`${h}="${this._escAttr(x)}"`}).join(" ");p.push(`${a}<${i}${d?" "+d:""} />`)}else{p.push(`${a}<${i}>`);for(const[d,g]of l){const s=c[d]??d,h=g==null?f:String(g);p.push(`${a}${a}<${s}>${this._escText(h)}</${s}>`)}p.push(`${a}</${i}>`)}}return p.push(`</${o}>`),p.join(`
2
2
  `)}static parseSap(t){var m,f,u,a;const o=new DOMParser().parseFromString(t.trim(),"text/xml"),i={header:{},items:[],returns:[],raw:o},r=o.getElementsByTagName("DOCUMENTHEADER")[0];if(r)for(const p of Array.from(r.children))i.header[p.tagName]=((m=p.textContent)==null?void 0:m.trim())??"";const c=o.getElementsByTagName("RETURN");for(const p of Array.from(c)){const n={};for(const l of Array.from(p.children))n[l.tagName]=((f=l.textContent)==null?void 0:f.trim())??"";i.returns.push(n)}const _=["ACCOUNTGL","ACCOUNTRECEIVABLE","ACCOUNTPAYABLE","ITEMS"];for(const p of _){const n=o.getElementsByTagName(p)[0];if(!n)continue;const l=n.getElementsByTagName("ITEM"),d=l.length>0?Array.from(l):[n];for(const g of d){const s={};for(const h of Array.from(g.children))s[h.tagName]=((u=h.textContent)==null?void 0:u.trim())??"";i.items.push(s)}break}if(i.items.length===0){const p=o.documentElement,n=Array.from(p.children).filter(l=>l.hasAttribute("SEGMENT"));for(const l of n){if(l.tagName==="EDI_DC40")continue;const d={};for(const g of Array.from(l.children))d[g.tagName]=((a=g.textContent)==null?void 0:a.trim())??"";Object.keys(d).length>0&&i.items.push(d)}}return i}static stringifySap(t){const e=['<?xml version="1.0" encoding="UTF-8"?>',"<BAPI_CALL>"];if(t.BAPI_FUNCTION&&e.push(` <FUNCTION>${this._escText(t.BAPI_FUNCTION)}</FUNCTION>`),t.DOCUMENTHEADER&&typeof t.DOCUMENTHEADER=="object"){e.push(" <DOCUMENTHEADER>");for(const[i,r]of Object.entries(t.DOCUMENTHEADER))r!=null&&r!==""&&e.push(` <${i}>${this._escText(String(r))}</${i}>`);e.push(" </DOCUMENTHEADER>")}const o=Object.keys(t).find(i=>Array.isArray(t[i])&&!i.startsWith("_"));if(o){e.push(` <${o}>`);for(const i of t[o]){e.push(" <ITEM>");for(const[r,c]of Object.entries(i))c!=null&&c!==""&&!r.startsWith("_")&&e.push(` <${r}>${this._escText(String(c))}</${r}>`);e.push(" </ITEM>")}e.push(` </${o}>`)}return e.push("</BAPI_CALL>"),e.join(`
3
3
  `)}static stringifySapBatch(t){const e=['<?xml version="1.0" encoding="UTF-8"?>',`<BAPI_BATCH total="${t.documents.length}">`];return t.documents.forEach((o,i)=>{e.push(` <BAPI_CALL seq="${i+1}">`);const r=this.stringifySap(o).split(`
4
4
  `).filter(c=>!c.startsWith("<?xml")).map(c=>" "+c).join(`
package/dist/open-grid.js CHANGED
@@ -1,5 +1,5 @@
1
- import { b as E } from "./OpenGrid-Cjv7Os5a.js";
2
- import { O as L } from "./OpenGrid-Cjv7Os5a.js";
1
+ import { b as E } from "./OpenGrid-5flQwc3W.js";
2
+ import { O as L } from "./OpenGrid-5flQwc3W.js";
3
3
  class C {
4
4
  constructor(t, e, r, i = {}) {
5
5
  this._left = t, this._right = e;
@@ -15,6 +15,10 @@ export interface CellEditDeps<T extends Record<string, any>> {
15
15
  writeCell: (ri: number, field: string, value: any) => void;
16
16
  scrollToRow: (ri: number) => void;
17
17
  getVisibleLeaves: () => ColumnDef<T>[];
18
+ hasCellFormula?: (ri: number, field: string) => boolean;
19
+ getCellFormula?: (ri: number, field: string) => string | null;
20
+ setCellFormula?: (ri: number, field: string, formula: string) => void;
21
+ clearCellFormula?: (ri: number, field: string) => void;
18
22
  }
19
23
  export declare class CellEditManager<T extends Record<string, any> = any> {
20
24
  private _activeEditor;
@@ -12,6 +12,12 @@ export interface CellEventDeps<T extends Record<string, any>> {
12
12
  writeCell: (ri: number, field: string, value: any) => void;
13
13
  doRender: () => void;
14
14
  getContainer: () => HTMLElement;
15
+ /** 'cells' 모드 클릭/Shift+클릭 위임 — RangeSelectionManager.handleClick 로 연결 */
16
+ onCellsClick?: (ri: number, ci: number, shiftKey: boolean) => void;
17
+ /** 범위 드래그 선택 상태머신(§3.1) — RangeSelectionManager.handleCellMouseDown/Move/Up 로 연결 */
18
+ rangeMouseDown?: (ri: number, ci: number, e: MouseEvent) => void;
19
+ rangeMouseMove?: (ri: number, ci: number, e: MouseEvent) => void;
20
+ rangeMouseUp?: (ri: number, ci: number, e: MouseEvent) => void;
15
21
  }
16
22
  export declare class CellEventHandler<T extends Record<string, any> = any> {
17
23
  private _d;
@@ -0,0 +1,58 @@
1
+ import { CellRange } from './types.js';
2
+ import { FlatRowModel } from './FlatRowModel.js';
3
+ import { ChartConfig, ChartInstance } from './chart/types.js';
4
+ import { ChartColumnRef } from './chart/DataExtractor.js';
5
+ export interface ChartManagerDeps {
6
+ getContainer(): HTMLElement;
7
+ getOptions(): any;
8
+ getAllRows(): Array<Record<string, any>>;
9
+ getSelectedRows(): Array<Record<string, any>>;
10
+ getCheckedRows(): Array<Record<string, any>>;
11
+ /** ColumnLayout.visibleLeaves 투영(숨김 제외, C0.4). */
12
+ getVisibleColumns(): ChartColumnRef[];
13
+ getFlatModel(): FlatRowModel;
14
+ getRowById(rowId: string): Record<string, any> | undefined;
15
+ /** F1 seam(C4). 없으면 range 소스는 selection 으로 강등(§7). */
16
+ getActiveRange?(): CellRange | null;
17
+ /** 그리드 이벤트 구독/해제(EventEmitter). */
18
+ on(ev: string, cb: (...a: any[]) => void): void;
19
+ off(ev: string, cb: (...a: any[]) => void): void;
20
+ emit(ev: string, ...args: any[]): void;
21
+ announce(msg: string): void;
22
+ }
23
+ export type { ChartInstance } from './chart/types.js';
24
+ export declare class ChartManager {
25
+ private _d;
26
+ private _charts;
27
+ constructor(deps: ChartManagerDeps);
28
+ createChart(config: ChartConfig): ChartInstance;
29
+ /**
30
+ * config.size 가 명시되면 그대로 쓴다. 없으면 host 의 실측 clientWidth(실브라우저에서
31
+ * max-width:100% 로 축소된 실제 폭)를 우선하고, 측정 불가(jsdom=0)면 480×300 기본값으로
32
+ * 폴백해 기존 유닛 테스트 동작을 보존한다. height 는 host 콘텐츠에 따라 달라지는 값이 아니라
33
+ * 패널 배치 시점에 확정되는 값이라 clientWidth 처럼 실측하지 않는다.
34
+ */
35
+ private _resolveRenderSize;
36
+ private _attachResizeObserver;
37
+ getCharts(): ChartInstance[];
38
+ destroyCharts(): void;
39
+ private _snapshotRange;
40
+ private _extract;
41
+ private _extractDeps;
42
+ private _buildSpec;
43
+ private _readVar;
44
+ private _snapshotTheme;
45
+ private _buildPanel;
46
+ private _renderBadges;
47
+ private _subscribeLive;
48
+ private _refresh;
49
+ private _resolveAdapter;
50
+ private _onPointClick;
51
+ /** rec 당 1회만 생성해 캐시한다(코드리뷰 Minor: 매번 새 객체면 === 식별이 불안정). */
52
+ private _makeInstance;
53
+ private _bindOptionCallbacks;
54
+ private _on;
55
+ private _emit;
56
+ private _destroyById;
57
+ private _destroy;
58
+ }
@@ -7,6 +7,12 @@ export declare class DataLayer<T extends Record<string, any> = any> {
7
7
  private _idField;
8
8
  private _displayIndexes;
9
9
  private _idMap;
10
+ /**
11
+ * rowId → DataLayer 원본(_data) 인덱스 조회.
12
+ * Phase 0 인프라(FlatRowModel.ts, C0.3 FlatRowRef.dataIndex) 해소 전용 — _idMap 을
13
+ * 직접 노출하지 않고 단건 조회만 허용한다. 없으면 undefined(삭제된 행 등).
14
+ */
15
+ getDataIndexByRowId(rowId: string): number | undefined;
10
16
  private _findQuery;
11
17
  private _findFields;
12
18
  private _getStrategy;
@@ -26,6 +32,17 @@ export declare class DataLayer<T extends Record<string, any> = any> {
26
32
  updateCell(rowIndex: number, field: string, value: any): boolean;
27
33
  getRowByIndex(rowIndex: number): T | undefined;
28
34
  getCellValue(rowIndex: number, field: string): any;
35
+ /** rowId 가 아직 존재(soft-delete 제외)하는지(F3-R28 삭제 무효화 판정용). */
36
+ hasRow(rowId: string): boolean;
37
+ /** rowId → 원본 행(soft-delete 된 행은 undefined, F3-R28). */
38
+ getRowById(rowId: string): T | undefined;
39
+ getCellValueByRowId(rowId: string, field: string): any;
40
+ /**
41
+ * 수식 재계산 결과 기록(§4.2/C2.2) — 일반 updateCell 과 달리 meta.state 를 건드리지
42
+ * 않는다(edited 오염 방지) 및 dataChange 를 발화하지 않는다(호출부가 배치 종료 후
43
+ * formulaRecalc 1회로 표면화할 책임을 진다).
44
+ */
45
+ setComputedValueByRowId(rowId: string, field: string, value: any): void;
29
46
  /** 수정된 행만 반환 (추가/삭제 제외) */
30
47
  getEditedRows(): T[];
31
48
  /** 수정된 행만 반환 (하위 호환용 — 신규 코드는 getEditedRows() 사용) */
@@ -0,0 +1,72 @@
1
+ import { VirtualScroll } from './VirtualScroll.js';
2
+ import { FlatRowModel } from './FlatRowModel.js';
3
+ /** expandRow/collapseRow/toggleRow/isRowExpanded/getDetailInstance 공통 인자(§6.2, C0.2). */
4
+ export type DetailRowRef = number | {
5
+ id: string;
6
+ };
7
+ export interface DetailManagerDeps<T extends Record<string, any> = any> {
8
+ /** this._options — masterDetail.* 를 이 안에서 읽는다(옵션 자체는 항상 최신값 참조). */
9
+ getOptions: () => any;
10
+ /** Phase 0 baseline. registerSplice 로 detail splice 를 등록하고, count()/resolveFlatRow 로
11
+ * rowIndex↔rowId 를 해소한다(F2 는 이 모델의 "소유자"가 아니라 "등록자" — C0.3 정정). */
12
+ getFlatModel: () => FlatRowModel;
13
+ getVs: () => VirtualScroll | null;
14
+ /** DataLayer 가 부여한 stable id 필드값(OpenGrid 의 ROW_ID_FIELD). */
15
+ getRowId: (row: T) => string;
16
+ /** stable rowId → 현재 행(삭제됐으면 undefined). DataLayer.getRowById 위임. */
17
+ getRowById: (rowId: string) => T | undefined;
18
+ /** group/tree rebuild 와 동일한 관용구 — 전체(0..n-1) 재렌더. */
19
+ doRenderFull: (n: number) => void;
20
+ emit: (ev: string, payload: any) => void;
21
+ announce: (msg: string) => void;
22
+ /** 이 그리드 인스턴스의 중첩 깊이(0=최상위, 부모가 자식 생성 시 depth+1 주입). */
23
+ getDepth: () => number;
24
+ /** masterDetail.renderer 의 DetailRenderApi.grid 에 실어줄 인스턴스. */
25
+ getGridInstance: () => any;
26
+ /** masterDetail.subgridOptions(§5 ②) 소비 — 순환 import 회피 위해 OpenGrid.ts 가 주입. */
27
+ createSubgrid?: (host: HTMLElement, subgridOptions: any, depth: number) => any;
28
+ }
29
+ export declare class DetailManager<T extends Record<string, any> = any> {
30
+ private _d;
31
+ private _state;
32
+ private _cache;
33
+ /** rowId → 영속 host div(mount-once, renderBody teardown 생존 — §5 핵심 통찰). */
34
+ private _hosts;
35
+ /** FR-9(HANMS-08): collapse 직전 패널 내부에 포커스가 있었는지 rowId 로 기억. */
36
+ private _focusPendingRestore;
37
+ constructor(deps: DetailManagerDeps<T>);
38
+ private _mdOpts;
39
+ /** masterDetail.enabled 여부(옵션 자체 — 펼침 개수와 무관). */
40
+ get enabled(): boolean;
41
+ /** §3.2 v2: "isActive" = 기능 on + 실제 펼침 ≥1(FlatRowModel 합성/렌더 분기 판단용). */
42
+ get isActive(): boolean;
43
+ get maxDepth(): number;
44
+ private _resolveRowId;
45
+ isExpandedId(rowId: string): boolean;
46
+ isRowExpanded(ref: DetailRowRef): boolean;
47
+ expandRow(ref: DetailRowRef): void;
48
+ collapseRow(ref: DetailRowRef): void;
49
+ toggleRow(ref: DetailRowRef): void;
50
+ collapseAllDetails(): void;
51
+ getDetailInstance<D = any>(ref: DetailRowRef): D | undefined;
52
+ /** FR-11 공개 계약 — 실제로는 리사이즈 이후 발생하는 통상 재렌더가 panel width 를 최신
53
+ * totalColWidth 로 이미 재계산하므로(§4.3 GridRenderer 가 매 렌더 폭을 새로 그린다), 여기선
54
+ * 강제 재렌더 1회로 계약을 만족시킨다(중복 상태 없이 단일 진실원 유지). */
55
+ resyncPanelWidths(): void;
56
+ /** GridRenderer 가 detailHead 를 그릴 때 호출 — 영속 host 를 최초 1회만 만들고 이후 재사용. */
57
+ getPanelHost(rowId: string): HTMLElement;
58
+ private _buildInstance;
59
+ /** §5(4) skip-rebuild(FR-8/NFR-2, MCCONNELL-04 → Phase1 승격): renderBody teardown 직전
60
+ * 호출된다. 편집 중인 host 는 detach 자체를 회피(document.body 로 hoist, 연결 유지 → blur
61
+ * 없음) 하고, 그 외는 정상 detach(연결 끊음, 참조는 Map 이 쥐고 있어 파괴 아님). */
62
+ onBeforeTeardown(): void;
63
+ isEditing(rowId: string): boolean;
64
+ /** collapse 직후 렌더가 끝난 뒤 호출 — FR-9: 패널 내부에 포커스가 있었으면 해당 마스터 행의
65
+ * expander 로 복원한다. GridRenderer 가 expander 엘리먼트를 렌더한 다음 프레임에 호출. */
66
+ consumePendingFocusRestore(): string | null;
67
+ private _releaseInstance;
68
+ private _splice;
69
+ private _afterToggle;
70
+ private _rebuildAndRender;
71
+ destroy(): void;
72
+ }
@@ -0,0 +1,56 @@
1
+ import { DataLayer } from './DataLayer.js';
2
+ /** flat 한 행이 무엇을 가리키는지 판별하는 참조 정보(C0.3). */
3
+ export interface FlatRowRef {
4
+ kind: 'data' | 'group' | 'tree' | 'detailHead' | 'detailFiller';
5
+ /** kind==='data'|'tree' 일 때 stable id(DataLayer 가 부여한 rowId 필드값). */
6
+ rowId?: string;
7
+ /** kind==='data' 일 때 DataLayer 원본(_data) 인덱스. */
8
+ dataIndex?: number;
9
+ /** 정렬/필터 반영된 논리 순서. plain 모드(backing 미교체)에서만 flatIndex 와 1:1 대응. */
10
+ displayIndex?: number;
11
+ }
12
+ /** flat 배열을 구성하는 원소(실데이터 행 T, GroupRow, 또는 TreeNode) — 내부 전용. */
13
+ type FlatItem = any;
14
+ /** group/tree 등이 baseline backing 을 통째로 교체할 때 쓰는 provider. null = plain 으로 복귀. */
15
+ export type FlatBackingProvider = (() => FlatItem[]) | null;
16
+ /** (Phase 0: 등록 지점만 제공, 소비자 없음) 합성 후단에서 flat 배열을 변형하는 훅 — F2 detail splice 용. */
17
+ export type FlatSpliceFn = (flat: FlatItem[]) => FlatItem[];
18
+ export interface FlatRowModelDeps {
19
+ getDataLayer: () => DataLayer<any>;
20
+ /** DataLayer 가 각 행에 부여하는 안정 rowId 필드명(OpenGrid 의 ROW_ID_FIELD, 기본 '_ogRowId'). */
21
+ rowIdField: string;
22
+ }
23
+ export declare class FlatRowModel {
24
+ private _d;
25
+ /** group/tree 가 등록한 backing override. null 이면 plain(DataLayer.getData()). */
26
+ private _backing;
27
+ /** F2 detail splice 등록 슬롯(Phase 0: 항상 비어 있음). */
28
+ private _splices;
29
+ private _revIndex;
30
+ private _revIndexSrc;
31
+ constructor(deps: FlatRowModelDeps);
32
+ /**
33
+ * group/tree 등 baseline backing 을 통째로 교체하는 등록점.
34
+ * provider=null 이면 plain(DataLayer 표시 순서)으로 복귀.
35
+ */
36
+ setBacking(provider: FlatBackingProvider): void;
37
+ /**
38
+ * (Phase 0: 등록 지점만 제공, 미소비) F2 DetailManager 가 합성 후단에 detail
39
+ * head/filler pseudo-row 를 끼워 넣을 변환기를 등록한다. 등록 순서대로 순차 적용.
40
+ */
41
+ registerSplice(fn: FlatSpliceFn): void;
42
+ count(): number;
43
+ /**
44
+ * 합성 완료된 flat 배열 원본을 노출한다(F2 렌더 배선 소비 — GridRenderer.renderBody 가 이 배열을
45
+ * `groupFlatRows` 자리에 그대로 넘겨받아 detail head/filler 를 렌더한다). 호출측은 결과를
46
+ * 변경하지 않는다(내부 backing/splice 산출물을 그대로 반환).
47
+ */
48
+ getFlatArray(): FlatItem[];
49
+ resolveFlatRow(flatIndex: number): FlatRowRef;
50
+ flatIndexOfRowId(rowId: string): number;
51
+ rowIdOfFlat(flatIndex: number): string | null;
52
+ private _flat;
53
+ private _invalidate;
54
+ private _ensureRevIndex;
55
+ }
56
+ export {};
@@ -20,6 +20,31 @@ export interface RendererCallbacks {
20
20
  onColDrop: (toIndex: number) => void;
21
21
  getColDragIdx: () => number | null;
22
22
  getDisplayText?: (rowIndex: number, field: string) => string | null;
23
+ getFormulaMeta?: (rowIndex: number, field: string) => {
24
+ src: string;
25
+ error: string | null;
26
+ approx: boolean;
27
+ } | null;
28
+ }
29
+ /**
30
+ * F2(11_design_F2_v2.md §4/§5, C6/C10): renderBody 가 detail head/filler 분기·expander 셀을
31
+ * 그릴 때 소비하는 배선 콜백 묶음. OpenGrid._buildDetailRenderContext() 가 매 렌더 새로 만들어
32
+ * 넘긴다(옵션/상태는 항상 DetailManager 최신값). masterDetail.enabled 가 아니면 undefined.
33
+ */
34
+ export interface DetailRenderContext {
35
+ toggleMode: 'expander-col' | 'first-cell';
36
+ ariaLabel: string;
37
+ getRowId: (row: any) => string;
38
+ isExpanded: (rowId: string) => boolean;
39
+ onToggle: (rowIndex: number, rowId: string) => void;
40
+ getGlyph: (expanded: boolean) => {
41
+ glyph: string;
42
+ ariaLabel: string;
43
+ title: string;
44
+ };
45
+ getPanelHost: (rowId: string) => HTMLElement;
46
+ /** renderBody 매 호출 시작(teardown 직전) — 편집중 host hoist + 나머지 detach(§5). */
47
+ onBeforeTeardown: () => void;
23
48
  }
24
49
  export declare class GridRenderer {
25
50
  private _root;
@@ -35,8 +60,15 @@ export declare class GridRenderer {
35
60
  /** 현재 렌더된 헤더 영역의 실제 높이(px). 헤더 줄바꿈으로 늘어난 높이를 레이아웃에 반영할 때 사용. */
36
61
  getHeaderHeight(): number;
37
62
  renderHeader(headerRows: any[][], leaves: ColumnDef[], widths: number[], sortList: SortItem[], opts: any): void;
38
- renderBody(startIndex: number, endIndex: number, data: DataLayer<any>, leaves: ColumnDef[], widths: number[], opts: any, offsetY: number, totalHeight: number, selectedRows: Set<number>, checkedRows: Set<number>, groupFlatRows?: Array<any> | null, onGroupToggle?: (key: string) => void, onTreeToggle?: (nodeId: any) => void, extraOpts?: Record<string, any>, mergeEngine?: MergeEngine): void;
63
+ renderBody(startIndex: number, endIndex: number, data: DataLayer<any>, leaves: ColumnDef[], widths: number[], opts: any, offsetY: number, totalHeight: number, selectedRows: Set<number>, checkedRows: Set<number>, groupFlatRows?: Array<any> | null, onGroupToggle?: (key: string) => void, onTreeToggle?: (nodeId: any) => void, extraOpts?: Record<string, any>, mergeEngine?: MergeEngine, detailApi?: DetailRenderContext): void;
39
64
  getCellEl(rowIndex: number, colIndex: number): HTMLElement | undefined;
65
+ /**
66
+ * F2(§4.3 C6): full-width 디테일 패널을 body-absolute 로 그린다(병합 마스터셀 선례
67
+ * `:783-806` 재사용 — flex flow 밖). host 는 detailApi.getPanelHost() 가 반환하는 영속
68
+ * div(mount-once, §5) — 이 함수는 그 host 를 새 패널 wrapper 에 appendChild 할 뿐이며,
69
+ * DOM 이동만 발생하므로(파괴 아님) 재렌더/스크롤 왕복에도 인스턴스 상태가 생존한다.
70
+ */
71
+ private _appendDetailPanel;
40
72
  destroy(): void;
41
73
  }
42
74
  export declare function _el(tag: string, className?: string): HTMLElement;
@@ -11,6 +11,19 @@ export interface GroupTreeDeps<T extends Record<string, any>> {
11
11
  doRender: () => void;
12
12
  /** Phase 2: OverrideKernel.getStrategy 주입(슬롯 groupKeyFn 도달용). */
13
13
  getStrategy?: <F extends Function>(slot: string, fallback: F) => F;
14
+ /**
15
+ * Phase 0(C0.3): FlatRowModel 의 baseline backing 등록점. group/tree 진입 시 자기
16
+ * flat 배열(`_groupFlatRows`/`_treeFlatRows`)로 교체하고, 해제 시 null(plain 복귀)로 되돌린다.
17
+ */
18
+ setFlatBacking: (provider: (() => Array<any>) | null) => void;
19
+ /**
20
+ * F2(11_design_F2_v2.md §3.2 R3 "flat 소유 경합" 해소): setFlatBacking 직후 이 값을 조회해
21
+ * VirtualScroll.setTotalRows/doRenderFull 에 넘긴다. FlatRowModel.count() 는 방금 교체한
22
+ * backing 위에 DetailManager 가 등록한 detail splice 까지 합성한 "최종" 총 행수이므로,
23
+ * GroupTreeManager 는 자기 배열 길이(`_groupFlatRows.length` 등)를 직접 쓰지 않는다
24
+ * (detail 활성 시 그 값은 실제보다 작아 스크롤/렌더 범위가 잘린다).
25
+ */
26
+ getFlatCount: () => number;
14
27
  }
15
28
  export declare class GroupTreeManager<T extends Record<string, any> = any> {
16
29
  private _groupFields;
@@ -2,6 +2,18 @@ import { DataLayer } from './DataLayer.js';
2
2
  import { ColumnLayout } from './ColumnLayout.js';
3
3
  import { CellEditManager } from './CellEditManager.js';
4
4
  import { RowManager } from './RowManager.js';
5
+ /** F1 범위 선택 배선 훅(M-3/M-4/M-5) — RangeSelectionManager 가 그대로 구현한다. */
6
+ export interface RangeKeyboardHooks {
7
+ isEnabled(): boolean;
8
+ hasSelection(): boolean;
9
+ extendFocus(dir: 'up' | 'down' | 'left' | 'right'): void;
10
+ ctrlFill(axis: 'down' | 'right'): void;
11
+ clear(): void;
12
+ /** 범위 없으면 null(호출측이 기존 focusCell/selectedRows 경로로 폴백) */
13
+ copyText(): string | null;
14
+ /** true = 배치 경유로 처리함, false = 범위 없음(폴백) */
15
+ pasteText(text: string): boolean;
16
+ }
5
17
  export interface KeyboardDeps<T extends Record<string, any>> {
6
18
  getEditMgr: () => CellEditManager<T>;
7
19
  getRowMgr: () => RowManager<T>;
@@ -15,6 +27,13 @@ export interface KeyboardDeps<T extends Record<string, any>> {
15
27
  emit: (event: string, ...args: any[]) => void;
16
28
  visRange: () => [number, number];
17
29
  handleCellKeyEvt: (eventName: 'cellKeyDown' | 'cellKeyUp' | 'cellKeyPress', e: KeyboardEvent) => void;
30
+ /** M-4: 붙여넣기 쓰기 퍼널을 writeCell(배치)로 교체(CON-2, 의도적 동작 변경) */
31
+ writeCells?: (patches: Array<{
32
+ rowIndex: number;
33
+ field: string;
34
+ value: any;
35
+ }>) => number;
36
+ getRangeHooks?: () => RangeKeyboardHooks | null;
18
37
  }
19
38
  export declare class KeyboardManager<T extends Record<string, any> = any> {
20
39
  private _d;