ninegrid2 6.315.0 → 6.317.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.
@@ -11036,6 +11036,18 @@ class ninegrid {
11036
11036
  static nvl = (v, v2) => {
11037
11037
  return ninegrid.isNvl(v) ? v2 : v;
11038
11038
  };
11039
+
11040
+ static isEllipsis = (element) => {
11041
+ const clone = element.cloneNode(true);
11042
+ clone.style.width = "auto";
11043
+ clone.style.visibility = "hidden";
11044
+ document.body.appendChild(clone);
11045
+
11046
+ const isTruncated = clone.scrollWidth > element.clientWidth;
11047
+ document.body.removeChild(clone);
11048
+
11049
+ return isTruncated;
11050
+ }
11039
11051
 
11040
11052
  static oppositeColor = (bgColor, lightColor, darkColor) => {
11041
11053
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
@@ -17824,581 +17836,190 @@ class ngFields
17824
17836
  };
17825
17837
  }
17826
17838
 
17827
- /**
17828
- * button.filterOptions = [{
17829
- * colnm: "userId",
17830
- * data: [],
17831
- * }]
17832
- * button.
17833
- */
17834
- class ngFiltering
17839
+ class ngFoot extends HTMLElement
17835
17840
  {
17836
17841
  #owner;
17837
- #isFiltering;
17838
-
17839
- constructor(owner) {
17840
- this.#owner = owner;
17841
- this.#isFiltering = false;
17842
-
17843
- const filterPanel = document.createElement("ng-filter-panel"); // ✅ 필터 패널 생성
17844
- filterPanel.style.display = "none"; // ✅ 숨김 처리
17845
- this.#owner.shadowRoot.appendChild(filterPanel); // ✅ Shadow DOM 내부에 추가
17846
- }
17847
-
17848
- initialize = () => {
17849
- /**
17850
- * 1. grid attr == filter : on();
17851
- */
17852
- this.#isFiltering = false;
17853
- this.#owner.data.clearFilter();
17854
- };
17855
-
17856
- isFiltering = () => {
17857
- return this.#isFiltering;
17858
- };
17859
17842
 
17860
- on = (v) => {
17843
+ constructor () {
17844
+ super();
17861
17845
 
17862
- this.#isFiltering = true;
17863
- const lastRowIndex = this.#getLastRowIndex();
17864
-
17865
- this.#owner.body.querySelectorAll(".ng-table thead th, .ng-table thead td").forEach((td) => {
17866
- const rowIndex = td.closest("tr")?.sectionRowIndex;
17867
- if (rowIndex !== undefined && rowIndex + td.rowSpan - 1 === lastRowIndex) {
17868
- const options = Array.from(this.#owner.activeTmpl.querySelectorAll(`[data-col="${td.dataset.col}"]`))
17869
- .map(el => el.dataset.bind ? { colnm: el.dataset.bind, data: [] } : null)
17870
- .filter(Boolean); // ✅ `null` 제거
17871
-
17872
- if (options.length > 0) {
17873
- td.querySelector("ng-filter-button")?.remove();
17874
-
17875
- const filterButton = document.createElement("ng-filter-button");
17876
- td.appendChild(filterButton);
17877
- filterButton.filterOptions = options;
17878
- }
17879
- }
17880
- });
17881
- };
17882
-
17883
- off = () => {
17884
- this.#isFiltering = false;
17885
-
17886
- this.#owner.body.querySelectorAll(".ng-table ng-filter-button").forEach((el) => {
17887
- el.remove(); // ✅ 요소 삭제
17888
- });
17889
- };
17890
-
17891
- /**
17892
- clear = () => {
17893
- console.log(this.#owner.dataManager.rawRecords);
17894
- this.#owner.dataManager.rawRecords.map(item => { item.__ng.filtered = false; });
17895
-
17896
-
17897
- this.#records = this.#owner.dataManager.rawRecords.filter(item => { return !item.__ng.deleted && !item.__ng.filtered; } );
17898
- return this.#records;
17846
+ this.attachShadow({ mode: 'open' });
17899
17847
  }
17900
17848
 
17901
- refresh = () => {
17902
- this.#records = this.#owner.dataManager.rawRecords.filter(item => { return !item.__ng.deleted && !item.__ng.filtered; } );
17903
- //this.#records.forEach((o,i) => { o.__ng.rowidx = i; });
17904
- //this.getValidData().forEach((o,i) => { o.__ng._[ninegrid.ROW.ORDER] = i + 1; });
17905
- this.#resetOrder();
17906
- return this.#records;
17907
- } */
17908
-
17909
-
17910
- /**
17911
- * oFilter : {
17912
- * col1 : [],
17913
- * col2 : []
17914
- * }
17915
- */
17916
- set = (oFilter) => {
17917
-
17918
- this.on();
17919
-
17920
- // ✅ JSON 변환 (배열 → 객체)
17921
- let jsonFilter = Array.isArray(oFilter)
17922
- ? Object.fromEntries(Object.keys(oFilter[0]).map(key => [key, [...new Set(oFilter.map(item => item[key]))]]))
17923
- : oFilter;
17924
-
17925
- this.#owner.data.clearFilter();
17926
-
17927
- Object.entries(jsonFilter).forEach(([key, arr]) => {
17928
- const idx = this.#owner.fields.indexOf(key);
17929
-
17930
- // ✅ 숫자 판별 및 변환
17931
- if (this.#owner.data.getValidData().some(o => typeof o.v[idx] === "number")) {
17932
- arr = arr.map(Number);
17933
- }
17934
-
17935
- // ✅ 필터 적용
17936
- this.#owner.data.getValidData()
17937
- .filter(m => arr.nineBinarySearch(m.v[idx] || '') < 0)
17938
- .forEach(m => { m.__ng.filtered = true; });
17939
- });
17940
-
17941
-
17942
- this.#owner.data.refreshFilter();
17943
-
17944
- // ✅ 필터 버튼 초기화
17945
- this.#owner.shadowRoot.querySelectorAll("ng-filter-button").forEach(el => {
17946
- el.filterOptions.forEach(opt => { opt.data = []; });
17947
- });
17948
-
17949
- Object.entries(jsonFilter).forEach(([key, arr]) => {
17950
- this.#owner.shadowRoot.querySelectorAll("ng-filter-button").forEach(el => {
17951
-
17952
- let options = el.filterOptions;
17953
-
17954
- options.forEach(opt => {
17955
- if (opt.colnm === key) opt.data = arr;
17956
- });
17957
-
17958
- el.filterOptions = options;
17959
- });
17960
- });
17849
+ connectedCallback() {
17961
17850
 
17851
+ this.#owner = this.getRootNode().host;//this.closest("nine-grid");
17962
17852
 
17963
- this.#owner.scrollTo_V1(0);
17853
+ this.shadowRoot.innerHTML = `
17854
+ <style>
17855
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngFoot.css";
17856
+ ${ninegrid.getCustomPath(this,"ngFoot.css")}
17857
+ </style>
17858
+
17859
+ <ng-layout></ng-layout>
17860
+ <ng-paging></ng-paging>
17861
+ `;
17964
17862
 
17965
- this.#owner.paging.reset();
17966
17863
  };
17967
-
17968
- #getLastRowIndex = (v = "thead") =>
17969
- [...this.#owner.body.querySelectorAll(`.ng-table ${v}`)]
17970
- .reduce((maxIndex, el) => Math.max(el.rows.length - 1, maxIndex), 0);
17971
-
17972
17864
  }
17973
17865
 
17974
- class ngFilterButton extends HTMLElement
17866
+ class ngLayout extends HTMLElement
17975
17867
  {
17976
17868
  #owner;
17977
- #filterOptions;
17869
+ #tables;
17870
+ #wrap;
17871
+ #at;
17978
17872
 
17979
17873
  constructor () {
17980
17874
  super();
17875
+
17876
+ this.#tables = [];
17877
+ this.#at = 0;
17981
17878
  }
17982
-
17879
+
17983
17880
  connectedCallback() {
17984
- this.#owner = this.getRootNode().host;//this.closest("nine-grid");
17881
+ this.#owner = this.getRootNode().host.getRootNode().host;//this.closest("nine-grid");
17882
+
17883
+ this.innerHTML =`<div class="ng-wrap"></div>`;
17884
+ this.#wrap = this.querySelector(".ng-wrap");
17885
+ };
17886
+
17887
+ add = (arr) => {
17985
17888
 
17986
- this.removeEventListener("click", this.#onClick);
17987
- this.addEventListener("click", this.#onClick);
17889
+ this.#tables.push(...arr);
17890
+
17891
+ var i = 0;
17892
+ for (const tbl of this.#tables) {
17893
+ i++;
17894
+ const caption = tbl.querySelector("caption");
17895
+ const sheetName = caption ? caption.innerHTML : `Sheet${i}`;
17896
+
17897
+ $(this.#wrap).append(`<button class="ng-button" value="${i-1}" title="${sheetName.replaceAll('"', "'")}">${sheetName}</button>`);
17898
+ }
17899
+
17900
+ if (this.#tables.length <= 1) $(this.#wrap).hide();
17901
+
17902
+ //$("button", this.#wrap).eq(0).addClass("ng-active");
17903
+ ninegrid.j.querySelectorAll(this.#wrap.querySelector("button")).addClass("ng-active");
17904
+
17905
+ $("button", this.#wrap).on("click", this.#onClick);
17988
17906
  };
17989
17907
 
17990
- get filterOptions() {
17991
- return this.#filterOptions;
17908
+ get tables() {
17909
+ return this.#tables;
17910
+ };
17911
+ get currentTable() {
17912
+ return this.#tables[this.#at];
17992
17913
  };
17993
- set filterOptions(v) {
17994
- this.#filterOptions = v;
17995
- this.classList.toggle('filtered', this.#filterOptions.some(item => item.data.length > 0));
17996
- }
17997
17914
 
17998
- #onClick = (e) => {
17999
- e.preventDefault();
18000
- e.stopPropagation();
18001
-
18002
- const panel = this.#owner.shadowRoot.querySelector("ng-filter-panel");
18003
- const col = this.closest("th,td")?.dataset.col; // ✅ `?.` 활용하여 안전한 접근
18004
-
18005
- panel.col === col ? panel.close() : panel.open(this);
18006
- };
18007
- }
17915
+ resize = () => {
17916
+ const overflow = $(this.#wrap).width() > $(this).width();
18008
17917
 
18009
- class ngFilterPanel extends HTMLElement
18010
- {
18011
- #owner;
18012
- #button;
18013
- #timer;
17918
+ $(this.#wrap).css({
17919
+ "width": overflow ? "100%" : "unset",
17920
+ "justify-content": overflow ? "space-evenly" : "unset",
17921
+ });
17922
+ };
18014
17923
 
18015
- constructor () {
18016
- super();
18017
- this.attachShadow({ mode: 'open' });
18018
- }
18019
-
18020
- connectedCallback() {
18021
-
18022
- this.#owner = this.getRootNode().host;
17924
+ #onClick = e => {
17925
+
17926
+ if (ninegrid.j.querySelectorAll(e.currentTarget).hasClass("ng-active")) return;
17927
+
17928
+ const oldIndex = this.#at;
17929
+
17930
+ this.#at = parseInt(e.currentTarget.value);
17931
+
17932
+ this.#owner.changeRayout(this.#at);
17933
+
17934
+ ninegrid.j.querySelectorAll("button", this.#wrap).removeClass("ng-active");
17935
+ ninegrid.j.querySelectorAll(e.currentTarget).addClass("ng-active");
17936
+
17937
+ var customEvent = new CustomEvent(ninegrid.EVENT.LAYOUT_CHANGED, { bubbles: true, detail: {} });
17938
+ customEvent.oldIndex = oldIndex;
17939
+ customEvent.newIndex = this.#at;
17940
+
17941
+ this.#owner.dispatchEvent(customEvent);
17942
+ };
17943
+ }
18023
17944
 
18024
- const cssPath = this.getRootNode().host.closest("nine-grid").getAttribute("css-path") || "";
17945
+ customElements.define("ng-layout", ngLayout);
17946
+ customElements.define("ng-foot", ngFoot);
18025
17947
 
18026
- this.shadowRoot.innerHTML = `
18027
- <style>
18028
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngFilterPanel.css";
18029
- ${ninegrid.getCustomPath(this,"ngFilterPanel.css")}
18030
- </style>
18031
-
18032
- <div class="head">
18033
- <input type="text">
18034
- </div>
18035
- <nine-grid class="simple filter" css-path="${cssPath}" select-type="row" auto-fit-col="true" col-indicator-type="collapse">
18036
- <table style="display: none;">
18037
- <colgroup>
18038
- <col width="180" />
18039
- <col width="30" />
18040
- </colgroup>
18041
- <tbody>
18042
- <tr style="height: 24px;">
18043
- <td data-bind="LVL" data-expr="data.DATA2">
18044
- <ng-tree-item />
18045
- </td>
18046
- <td data-bind="CHK">
18047
- <ng-checkbox update-row-state="false" visible="data.LVL!=1" selected-border-color="white" />
18048
- </td>
18049
- </tr>
18050
- </tbody>
18051
- </table>
18052
- </nine-grid>
18053
-
18054
- <div>
18055
- <label><input type="checkbox" checked>Select All</label>
18056
- <button id="btnOk">OK</button>
18057
- <button id="btnCancel">Cancel</button>
18058
- </div>
18059
- `;
18060
-
18061
- this.shadowRoot.querySelector("input[type=text]").addEventListener("input", this.#onInput);
18062
- this.shadowRoot.querySelector("input[type=checkbox]").addEventListener("change", this.#onSelectAll);
18063
- this.shadowRoot.querySelector("#btnOk").addEventListener("click", this.#onOk);
18064
- this.shadowRoot.querySelector("#btnCancel").addEventListener("click", this.#onCancel);
18065
- };
18066
-
18067
- #onOk = (e) => {
18068
-
18069
- const grd = this.shadowRoot.querySelector("nine-grid");
18070
-
18071
- this.classList.add("loading");
18072
-
18073
- setTimeout(() => {
18074
- const [LVL_IDX, CHK_IDX, COLNM_IDX, DATA_IDX] = ["LVL", "CHK", "COLNM", "DATA"].map(k => grd.fields.indexOf(k));
18075
- const checked = grd.data.getValidDataNF().filter(m => m.v[LVL_IDX] === 2 && m.v[CHK_IDX] === "Y");
18076
- const filterOptions = checked.length > 0 ? [...new Set(checked.map(m => m.v[COLNM_IDX]))].map(v => ({
18077
- colnm: v,
18078
- data: [...new Set(grd.data.getValidData().filter(m => m.v[CHK_IDX] === "Y" && m.v[COLNM_IDX] === v).map(m => m.v[DATA_IDX] || ''))].sort()
18079
- })) : [];
18080
-
18081
-
18082
- this.#button.filterOptions = filterOptions;
18083
-
18084
- const oParam = Object.fromEntries(
18085
- [...this.#owner.body.querySelectorAll("ng-filter-button")]
18086
- .flatMap(el => el.filterOptions)
18087
- .filter(opt => opt.data.length > 0)
18088
- .map(opt => [opt.colnm, opt.data])
18089
- );
18090
-
18091
- this.#owner.filtering.set(oParam);
18092
-
18093
- this.classList.remove("loading");
18094
- this.style.display = 'none';
18095
- });
18096
- };
18097
-
18098
-
18099
-
18100
- #onCancel = (e) => {
18101
- this.style.display = 'none';
18102
- };
18103
-
18104
- #onSelectAll = (e) => {
18105
- const grd = this.shadowRoot.querySelector("nine-grid");
18106
- const idx = grd.fields.indexOf("CHK");
18107
- const isChecked = e.target.checked; // ✅ jQuery 없이 `checked` 값 가져오기
18108
-
18109
- grd.data.getValidData().forEach(m => {
18110
- m.v[idx] = isChecked ? "Y" : "N"; // ✅ `Y` 또는 `N` 값 설정
18111
- });
18112
-
18113
- grd.refreshData();
18114
- };
18115
-
18116
-
18117
-
18118
- #onInput = (e) => {
18119
- const grd = this.shadowRoot.querySelector("nine-grid");
18120
-
18121
- grd.classList.add("loading");
18122
-
18123
- const data = grd.dataManager.rawRecords;
18124
- data.forEach(m => { m.__ng.filtered = false; });
18125
-
18126
- const v = e.target.value.toLowerCase(); // ✅ jQuery 없이 값 가져오기
18127
- const [LVL_IDX, DATA_IDX] = ["LVL", "DATA"].map(field => grd.fields.indexOf(field));
18128
-
18129
- data.forEach(m => {
18130
- m.__ng.filtered = m.v[LVL_IDX] === 2 && !String(m.v[DATA_IDX] || "").toLowerCase().includes(v);
18131
- });
18132
-
18133
- grd.data.resetRecords();
18134
-
18135
- clearTimeout(this.#timer);
18136
- this.#timer = setTimeout(() => {
18137
- grd.dataManager.viewRecords.reset();
18138
- grd.dataManager.viewRecords.touch();
18139
- }, 200);
18140
- };
18141
-
18142
- close = () => {
18143
- this.col = null;
18144
- this.style.display = 'none';
18145
- };
18146
-
18147
- open = (filterButton) => {
18148
-
18149
- /** 위치 */
18150
- const cell = filterButton.closest("th,td");
18151
-
18152
- const { left: btnLeft } = filterButton.getBoundingClientRect();
18153
- const { left: ownerLeft, width: ownerWidth, top: ownerTop } = this.#owner.getBoundingClientRect();
18154
- const { top: cellTop, height: cellHeight } = cell.getBoundingClientRect();
18155
- const { width: targetWidth } = this.getBoundingClientRect();
18156
-
18157
- let l = Math.max(0, btnLeft - ownerLeft);
18158
- l = Math.min(l, ownerWidth - targetWidth - 5);
18159
-
18160
- const t = cellTop + cellHeight - ownerTop;
18161
-
18162
- Object.assign(this.style, { left: `${l}px`, top: `${t}px`, display: "flex" });
18163
- this.classList.add("loading");
18164
-
18165
- this.shadowRoot.querySelector("input[type=text]").value = "";
18166
-
18167
- setTimeout(() => {
18168
- const data = this.#owner.data.getValidDataNF();
18169
- const col = cell.dataset.col;
18170
- this.col = col;
18171
- this.#button = filterButton;
18172
-
18173
- const ds = filterButton.filterOptions.map((opt, i) => {
18174
- const groupLabel = `<span class="group">${cell.textContent}${filterButton.filterOptions.length > 1 ? ` #${i + 1} (${opt.colnm})` : ""}</span>`;
18175
-
18176
- const cellEl = this.#owner.activeTmpl.querySelector(`[data-col="${col}"][data-bind="${opt.colnm}"]`);
18177
- const expr = cellEl?.getAttribute("data-expr");
18178
- const exprFunc = expr ? this.#owner.exprFunction(expr) : null;
18179
-
18180
- const data2 = data.map(rowData => {
18181
- const idx = this.#owner.fields.indexOf(opt.colnm);
18182
- return expr ? { v: rowData.v[idx], v2: exprFunc(this.#owner.data.conv(rowData), rowData.__ng.rowidx, this.#owner.data) } : { v: rowData.v[idx], v2: rowData.v[idx] };
18183
- });
18184
-
18185
- return [
18186
- { LVL: 1, CHK: "N", DATA2: groupLabel },
18187
- ...[...new Set(data2.map(JSON.stringify))]
18188
- .map(JSON.parse)
18189
- .sort((a, b) => (a.v2 || "") > (b.v2 || "") ? 1 : (a.v2 || "") < (b.v2 || "") ? -1 : 0)
18190
- .map(o => ({
18191
- LVL: 2,
18192
- DATA: o.v,
18193
- DATA2: o.v2 || ` <span class="empty">(empty)</span> ${o.v}`,
18194
- COLNM: opt.colnm,
18195
- CHK: opt.data.length === 0 || opt.data.nineBinarySearch(o.v || "") >= 0 ? "Y" : "N",
18196
- }))
18197
- ];
18198
- }).flat();
18199
-
18200
- const grd = this.shadowRoot.querySelector("nine-grid");
18201
- grd.fields.add(["DATA","DATA2","COLNM"]);
18202
- grd.data.set(ds);
18203
-
18204
- // ✅ 데이터 필터링 및 체크 상태 결정
18205
- const checkbox = this.shadowRoot.querySelector("input[type=checkbox]");
18206
- checkbox.checked = grd.data.getValidData().every(item => !(item.LVL === 2 && item.CHK !== "Y"));
18207
-
18208
- this.shadowRoot.querySelector("input").focus();
18209
- this.classList.remove("loading");
18210
- });
18211
- };
18212
- }
18213
-
18214
-
18215
- customElements.define("ng-filter-button", ngFilterButton);
18216
- customElements.define("ng-filter-panel", ngFilterPanel);
18217
-
18218
- class ngFoot extends HTMLElement
18219
- {
18220
- #owner;
18221
-
18222
- constructor () {
18223
- super();
18224
-
18225
- this.attachShadow({ mode: 'open' });
18226
- }
18227
-
18228
- connectedCallback() {
18229
-
18230
- this.#owner = this.getRootNode().host;//this.closest("nine-grid");
18231
-
18232
- this.shadowRoot.innerHTML = `
18233
- <style>
18234
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngFoot.css";
18235
- ${ninegrid.getCustomPath(this,"ngFoot.css")}
18236
- </style>
18237
-
18238
- <ng-layout></ng-layout>
18239
- <ng-paging></ng-paging>
18240
- `;
18241
-
18242
- };
18243
- }
18244
-
18245
- class ngLayout extends HTMLElement
18246
- {
18247
- #owner;
18248
- #tables;
18249
- #wrap;
18250
- #at;
18251
-
18252
- constructor () {
18253
- super();
18254
-
18255
- this.#tables = [];
18256
- this.#at = 0;
18257
- }
18258
-
18259
- connectedCallback() {
18260
- this.#owner = this.getRootNode().host.getRootNode().host;//this.closest("nine-grid");
18261
-
18262
- this.innerHTML =`<div class="ng-wrap"></div>`;
18263
- this.#wrap = this.querySelector(".ng-wrap");
18264
- };
18265
-
18266
- add = (arr) => {
18267
-
18268
- this.#tables.push(...arr);
18269
-
18270
- var i = 0;
18271
- for (const tbl of this.#tables) {
18272
- i++;
18273
- const caption = tbl.querySelector("caption");
18274
- const sheetName = caption ? caption.innerHTML : `Sheet${i}`;
18275
-
18276
- $(this.#wrap).append(`<button class="ng-button" value="${i-1}" title="${sheetName.replaceAll('"', "'")}">${sheetName}</button>`);
18277
- }
18278
-
18279
- if (this.#tables.length <= 1) $(this.#wrap).hide();
18280
-
18281
- //$("button", this.#wrap).eq(0).addClass("ng-active");
18282
- ninegrid.j.querySelectorAll(this.#wrap.querySelector("button")).addClass("ng-active");
18283
-
18284
- $("button", this.#wrap).on("click", this.#onClick);
18285
- };
18286
-
18287
- get tables() {
18288
- return this.#tables;
18289
- };
18290
- get currentTable() {
18291
- return this.#tables[this.#at];
18292
- };
18293
-
18294
- resize = () => {
18295
- const overflow = $(this.#wrap).width() > $(this).width();
18296
-
18297
- $(this.#wrap).css({
18298
- "width": overflow ? "100%" : "unset",
18299
- "justify-content": overflow ? "space-evenly" : "unset",
18300
- });
18301
- };
18302
-
18303
- #onClick = e => {
18304
-
18305
- if (ninegrid.j.querySelectorAll(e.currentTarget).hasClass("ng-active")) return;
18306
-
18307
- const oldIndex = this.#at;
18308
-
18309
- this.#at = parseInt(e.currentTarget.value);
18310
-
18311
- this.#owner.changeRayout(this.#at);
18312
-
18313
- ninegrid.j.querySelectorAll("button", this.#wrap).removeClass("ng-active");
18314
- ninegrid.j.querySelectorAll(e.currentTarget).addClass("ng-active");
18315
-
18316
- var customEvent = new CustomEvent(ninegrid.EVENT.LAYOUT_CHANGED, { bubbles: true, detail: {} });
18317
- customEvent.oldIndex = oldIndex;
18318
- customEvent.newIndex = this.#at;
18319
-
18320
- this.#owner.dispatchEvent(customEvent);
18321
- };
18322
- }
18323
-
18324
- customElements.define("ng-layout", ngLayout);
18325
- customElements.define("ng-foot", ngFoot);
18326
-
18327
- class ngHead extends HTMLElement
18328
- {
18329
- #owner;
18330
-
18331
- constructor () {
18332
- super();
18333
-
18334
- this.attachShadow({ mode: 'open' });
18335
- }
18336
-
18337
- connectedCallback() {
18338
-
18339
- this.#owner = this.getRootNode().host;
18340
-
18341
- if (ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) return;
18342
-
18343
- this.shadowRoot.innerHTML = `
18344
- <style>
18345
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngHead.css";
18346
- ${ninegrid.getCustomPath(this,"ngHead.css")}
18347
- </style>
18348
-
18349
- <ng-menu></ng-menu>
18350
- <ng-title></ng-title>
18351
- <ng-custom></ng-custom>
18352
- `;
18353
- };
18354
- }
18355
-
18356
- class ngTitle extends HTMLElement
18357
- {
18358
- #owner;
18359
-
18360
- constructor () {
18361
- super();
18362
- }
18363
-
18364
- connectedCallback() {
18365
- if (!this.getRootNode().host) return;
18366
-
18367
- this.#owner = this.getRootNode().host.getRootNode().host;
18368
-
18369
- this.innerHTML = `<span>${this.#owner.getAttribute("caption")}</span>`;
18370
-
18371
- $("span", this).on("dblclick", e => {
18372
- const link = '<font color="green">https://www.ninegrid.net</font>';
18373
- e.currentTarget.innerHTML = e.currentTarget.innerHTML == link ? `<span>${this.#owner.getAttribute("caption")}</span>` : link;
18374
- });
18375
- };
18376
- }
18377
-
18378
- class ngCustom extends HTMLElement
18379
- {
18380
- #owner;
18381
-
18382
- constructor () {
18383
- super();
18384
- }
18385
-
18386
- connectedCallback() {
18387
- if (!this.getRootNode().host) return;
18388
-
18389
- this.#owner = this.getRootNode().host.getRootNode().host;
18390
-
18391
- this.innerHTML = `<div></div>`;
18392
- };
18393
-
18394
- add = (element) => {
18395
- this.querySelector("div:first-child").append(element);
18396
- };
18397
- }
18398
-
18399
- customElements.define("ng-title", ngTitle);
18400
- customElements.define("ng-custom", ngCustom);
18401
- customElements.define("ng-head", ngHead);
17948
+ class ngHead extends HTMLElement
17949
+ {
17950
+ #owner;
17951
+
17952
+ constructor () {
17953
+ super();
17954
+
17955
+ this.attachShadow({ mode: 'open' });
17956
+ }
17957
+
17958
+ connectedCallback() {
17959
+
17960
+ this.#owner = this.getRootNode().host;
17961
+
17962
+ if (ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) return;
17963
+
17964
+ this.shadowRoot.innerHTML = `
17965
+ <style>
17966
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngHead.css";
17967
+ ${ninegrid.getCustomPath(this,"ngHead.css")}
17968
+ </style>
17969
+
17970
+ <ng-menu></ng-menu>
17971
+ <ng-title></ng-title>
17972
+ <ng-custom></ng-custom>
17973
+ `;
17974
+ };
17975
+ }
17976
+
17977
+ class ngTitle extends HTMLElement
17978
+ {
17979
+ #owner;
17980
+
17981
+ constructor () {
17982
+ super();
17983
+ }
17984
+
17985
+ connectedCallback() {
17986
+ if (!this.getRootNode().host) return;
17987
+
17988
+ this.#owner = this.getRootNode().host.getRootNode().host;
17989
+
17990
+ this.innerHTML = `<span>${this.#owner.getAttribute("caption")}</span>`;
17991
+
17992
+ $("span", this).on("dblclick", e => {
17993
+ const link = '<font color="green">https://www.ninegrid.net</font>';
17994
+ e.currentTarget.innerHTML = e.currentTarget.innerHTML == link ? `<span>${this.#owner.getAttribute("caption")}</span>` : link;
17995
+ });
17996
+ };
17997
+ }
17998
+
17999
+ class ngCustom extends HTMLElement
18000
+ {
18001
+ #owner;
18002
+
18003
+ constructor () {
18004
+ super();
18005
+ }
18006
+
18007
+ connectedCallback() {
18008
+ if (!this.getRootNode().host) return;
18009
+
18010
+ this.#owner = this.getRootNode().host.getRootNode().host;
18011
+
18012
+ this.innerHTML = `<div></div>`;
18013
+ };
18014
+
18015
+ add = (element) => {
18016
+ this.querySelector("div:first-child").append(element);
18017
+ };
18018
+ }
18019
+
18020
+ customElements.define("ng-title", ngTitle);
18021
+ customElements.define("ng-custom", ngCustom);
18022
+ customElements.define("ng-head", ngHead);
18402
18023
 
18403
18024
  class ngIcon extends HTMLElement
18404
18025
  {
@@ -23464,596 +23085,987 @@ class ngTreeItem extends ngCellEx
23464
23085
 
23465
23086
  customElements.define("ng-tree-item", ngTreeItem);
23466
23087
 
23467
- //import { ngData } from "./ngData.js";
23088
+ //import { ngData } from "./ngData.js";
23089
+
23090
+ class ngView
23091
+ {
23092
+ #owner;
23093
+ #rawIndex;
23094
+ #height;
23095
+ #ing;
23096
+ #timers = [];
23097
+
23098
+ constructor(owner) {
23099
+ this.#owner = owner;
23100
+
23101
+ this.#ing = false;
23102
+
23103
+ this.init();
23104
+ }
23105
+
23106
+ /**
23107
+ * changelayout
23108
+ * resize
23109
+ */
23110
+ init = () => {
23111
+
23112
+ //if (this.#ing) return;
23113
+
23114
+ this.#height = {
23115
+ body : $(".ng-container-body", this.#owner.body).height() || 0,
23116
+ thead : $(".ng-container-body thead", this.#owner.body).height() || 0,
23117
+ tfoot : $(".ng-container-body tfoot", this.#owner.body).height() || 0,
23118
+ };
23119
+
23120
+ if (this.#ing) return;
23121
+
23122
+ $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(this.#height.body);
23123
+
23124
+ this.#ing = true;
23125
+ };
23126
+
23127
+ redraw = () => {
23128
+
23129
+ this.#owner.touchEvent.scrollTop();
23130
+
23131
+ this.init();
23132
+
23133
+ if (!this.#owner.dataManager) return;
23134
+
23135
+ this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23136
+
23137
+ this.#resetTR3();
23138
+
23139
+ this.#r();
23140
+
23141
+ this.#owner.body.querySelector("ng-vscrollbar").refresh();
23142
+ };
23143
+
23144
+ redrawV2 = () => {
23145
+
23146
+ this.#owner.touchEvent.scrollTop();
23147
+
23148
+ this.init();
23149
+
23150
+ this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23151
+
23152
+ this.#resetTR3V2();
23153
+
23154
+ this.#r();
23155
+ this.#owner.body.querySelector("ng-vscrollbar").refreshV2();
23156
+ };
23157
+
23158
+ redrawV3 = () => {
23159
+
23160
+ this.init();
23161
+
23162
+ this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23163
+
23164
+ //this.#resetTR3V2();
23165
+
23166
+ this.#r();
23167
+ //this.#owner.body.querySelector("ng-vscrollbar").refreshV2();
23168
+ };
23169
+
23170
+
23171
+ #getPinHeight = () => {
23172
+ var h = 0;
23173
+
23174
+ $(".ng-container-body tbody.fixed tr", this.#owner.body).each((i, tr) => {
23175
+ if (tr.data && tr.data.__ng) {
23176
+ h += this.#owner.matrix.getHeight(tr.data.__ng.height[parseInt(i % this.#owner.template.length)]);
23177
+ }
23178
+ });
23179
+
23180
+ return h;
23181
+ }
23182
+
23183
+ #getTrHeight = () => {
23184
+ var h = this.#getPinHeight();
23185
+
23186
+ const startRow = parseInt(this.#rawIndex * this.#owner.template.length);
23187
+
23188
+ $(".ng-container-body tbody.bindable tr:not(.nodata)", this.#owner.body).each((i, tr) => {
23189
+ h += this.#owner.matrix.getHeight(startRow + i);
23190
+ });
23191
+
23192
+ return h;
23193
+ }
23194
+
23195
+ #getRowCount = () => {
23196
+
23197
+ const displayRowCount = parseInt(this.#owner.getAttribute("display-row-count"));
23198
+
23199
+ if (this.#owner.closest("dialog") && ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) return 1;
23200
+ if ([ninegrid.PAGINGTYPE.CLIENT,ninegrid.PAGINGTYPE.SERVER].includes(this.#owner.paging.type)) return this.#owner.paging.linesPerPage;
23201
+ if (!isNaN(displayRowCount)) {
23202
+
23203
+ /**
23204
+ * 데이타가 적을 경우 rawIndex = 0;
23205
+ * 마지막 페이지 인경우 rawIndex 계산
23206
+ */
23207
+ if (this.#rawIndex + displayRowCount > this.#owner.data.count()) {
23208
+ this.#rawIndex = (displayRowCount >= this.#owner.data.count()) ? 0 : this.#owner.data.count() - displayRowCount;
23209
+ this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23210
+
23211
+ }
23212
+
23213
+
23214
+ return displayRowCount;
23215
+ }
23216
+
23217
+ const totalHeight = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getPinHeight();
23218
+
23219
+ var rowCount = 0;
23220
+ var h = 0;
23221
+ while (h < totalHeight) {
23222
+ h += this.getRowHeight(this.#rawIndex + rowCount++);
23223
+ }
23224
+
23225
+ if (this.#rawIndex + rowCount > this.#owner.data.count()) {
23226
+
23227
+ rowCount = 0;
23228
+ h = 0;
23229
+ for (var i = this.#owner.data.count() - 1; i >= 0 && h < totalHeight; i--) {
23230
+ h += this.getRowHeight(i);
23231
+ rowCount++;
23232
+ }
23233
+
23234
+ if (h > totalHeight) rowCount --;
23235
+
23236
+ this.#rawIndex = this.#owner.data.count() - rowCount;
23237
+
23238
+ this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23239
+ }
23240
+
23241
+
23242
+ return rowCount;
23243
+ };
23244
+
23245
+ #createTR = () => {
23246
+
23247
+ // 1. 스크린에 보여질 TR 갯수 계산
23248
+ var rowCount = this.#getRowCount();
23249
+
23250
+ // 0. TR 생성여부 판단
23251
+ //if (!this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:not(.nodata)") || this.isLastPage) {
23252
+ // 2. TR 생성
23253
+ var startRow = $(".ng-container-body tbody.bindable tr", this.#owner.body).not('tr.nodata').length / (this.#owner.template.length || 1);
23254
+
23255
+
23256
+ const leftTmpl = this.#owner.template.clone();
23257
+ const rightTmpl = leftTmpl.clone();//this.#owner.template.clone();
23258
+ const bodyTmpl = leftTmpl.clone();//this.#owner.template.clone();
23259
+
23260
+ leftTmpl .find("th,td").not("[fixed=left]").remove();
23261
+ rightTmpl .find("th,td").not("[fixed=right]").remove();
23262
+ bodyTmpl .find("th[fixed=left],td[fixed=left],th[fixed=right],td[fixed=right]").remove();
23263
+
23264
+ const leftNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-left"));
23265
+ const rightNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-right"));
23266
+ const bodyNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-body"));
23267
+
23268
+ for (var i = startRow; i < rowCount; i++) {
23269
+
23270
+
23271
+ leftTmpl .each((i,tr) => { leftNoData .before(tr.outerHTML); });
23272
+ rightTmpl .each((i,tr) => { rightNoData .before(tr.outerHTML); });
23273
+ bodyTmpl .each((i,tr) => { bodyNoData .before(tr.outerHTML); });
23274
+ }
23275
+ //}
23276
+
23277
+
23278
+
23279
+ // 3. 불필요 TR 제거
23280
+ /**
23281
+ rowCount = Math.min(rowCount, this.#owner.data.count());// + ($(".ng-container-body tbody.fixed tr", this.#owner.body).length / this.#owner.template.length);
23282
+ $(".ng-container tbody.bindable tr", this.#owner.body).not(".nodata").each((i,tr) => {
23283
+ if (parseInt(tr.sectionRowIndex / this.#owner.template.length) >= rowCount) {
23284
+ $(tr).remove();
23285
+ }
23286
+ }); */
23287
+
23288
+ return;
23289
+
23290
+ // 4. row resize
23291
+ var trsBody = $(".ng-container-body tbody.bindable tr:not(.nodata)", this.#owner.body);
23292
+
23293
+ if (trsBody.length > 0) {
23294
+ var startRow = parseInt(this.#rawIndex * this.#owner.template.length);
23295
+ }
23296
+ };
23297
+
23298
+ #removeGarbageTR = () => {
23299
+
23300
+ var rowCount = Math.min(this.#getRowCount(), this.#owner.data.count());// + this.pin.count();
23301
+
23302
+
23303
+ $(".ng-container tbody.bindable tr:not(.nodata)", this.#owner.body).each((i,tr) => {
23304
+ if (parseInt(tr.sectionRowIndex / this.#owner.template.length) >= rowCount) {
23305
+ $(tr).remove();
23306
+ }
23307
+ });
23308
+
23309
+ this.#owner.body.querySelectorAll(".ng-container tbody.bindable tr:not(.nodata)").forEach(tr => {
23310
+ const row = this.#rawIndex + parseInt(tr.sectionRowIndex / this.#owner.template.length);
23311
+ if (row >= this.#owner.data.count()) {
23312
+ $(tr).remove();
23313
+ }
23314
+ });
23315
+ };
23316
+
23317
+ #resetTR3 = () => {
23318
+
23319
+ // 1. TR 생성
23320
+ this.#createTR();
23321
+
23322
+ this.#removeGarbageTR();
23323
+
23324
+ // 5. nodata resize
23325
+
23326
+ var h = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getTrHeight();
23327
+ $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23328
+
23329
+ /**
23330
+ this.#owner.body.querySelector(".ng-container-body .ng-table").style.top = 0;
23331
+ this.#owner.body.querySelector(".ng-container-left .ng-table").style.top = 0;
23332
+ this.#owner.body.querySelector(".ng-container-right .ng-table").style.top = 0;
23333
+ */
23334
+
23335
+ //$('.ng-container .ng-table', this.#owner.body).offset({top:$('.ng-container .ng-table', this.#owner.body).offset().top});
23336
+ //$('.ng-container .ng-table', this.#owner.body).offset({top:$('.ng-container .ng-table', this.#owner.body).offset().top});
23337
+ //this.isLastPage = h > 0 ? true : false;
23338
+
23339
+
23340
+ if (this.#owner.closest("dialog") && ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) {
23341
+ $("tr.nodata", this.#owner.body).height(0);
23342
+
23343
+ var h = 47;//this.#getTrHeight();
23344
+ $('.ng-container-body tbody.bindable tr', this.#owner.body).not('.nodata').each((i,tr) => {
23345
+ h += $(tr).height();
23346
+ });
23347
+
23348
+ $(this.#owner.closest("dialog")).height(h);
23349
+ }
23350
+ };
23351
+
23352
+ #resetTR3V2 = () => {
23353
+
23354
+ // 1. TR 생성
23355
+ //this.#owner.isLastPage = rows[rows.length-1] == this.#owner.data.count() - 1 ? true : false;//this.#isLastPage();
23356
+
23357
+ //if (this.#owner.isLastPage) {
23358
+ //if (this.#isLastPage) {
23359
+
23360
+ //if (this.#owner.isLastPage) {
23361
+ this.#createTR();
23362
+ //}
23363
+ /**
23364
+ if ($(".ng-container-body tbody.bindable tr.nodata", this.#owner.body).height() > 0) {
23365
+ this.#createTR();
23366
+ } */
23367
+
23368
+ this.#removeGarbageTR();
23369
+
23370
+
23371
+ // 5. nodata resize
23372
+ /**
23373
+ if (this.#owner.isFirstPage || this.#owner.isLastPage) {
23374
+ var h = this.#owner.body.querySelector("ng-vscrollbar").bodyHeight - $(".ng-container-body .ng-table", this.#owner.body).height() + $('.ng-container-body .ng-table tr.nodata', this.#owner.body).height();
23375
+ $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23376
+ }*/
23377
+ //$(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(700);
23378
+
23379
+ if (this.#owner.isLastPage) ;
23380
+
23381
+
23382
+ //this.isLastPage = h > 0 ? true : false;
23383
+
23384
+
23385
+ //this.nodata = h > 0 ? true : false;
23386
+
23387
+ // 6. 마지막 TR tabindex
23388
+ };
23389
+
23390
+ #refreshData = () => {
23391
+
23392
+ for (const v of this.#timers) {
23393
+ clearTimeout(v);
23394
+ }
23395
+ this.#timers = [];
23396
+
23397
+ this.#timers.push(setTimeout(() => {
23398
+
23399
+ this.#ing = false;
23400
+
23401
+ // 4. row resize
23402
+ var trsBody = $(".ng-container-body tbody.bindable tr", this.#owner.body).not(".nodata");
23403
+ var trsLeft = $(".ng-container-left tbody.bindable tr", this.#owner.body).not(".nodata");
23404
+ var trsRight= $(".ng-container-right tbody.bindable tr", this.#owner.body).not(".nodata");
23405
+
23406
+ if (trsBody.length > 0) {
23407
+ var startRow = parseInt(trsBody[0].dataset.row * this.#owner.template.length);
23408
+
23409
+ trsBody.each((i, tr) => {
23410
+ var h = this.#owner.matrix.getHeight(startRow + i);// || 32;
23411
+
23412
+ $("canvas.chart", tr).css({ "height" : h-4 });
23413
+ $("canvas.chart", trsLeft.eq(i)).css({ "height" : h-4 });
23414
+ $("canvas.chart", trsRight.eq(i)).css({ "height" : h-4 });
23415
+
23416
+ $(tr).css({ "height" : h }).height(h);
23417
+ trsLeft.eq(i).css({ "height" : h }).height(h);
23418
+ trsRight.eq(i).css({ "height" : h }).height(h);
23419
+ });
23420
+ }
23421
+
23422
+ this.#owner.cell.refresh();
23423
+
23424
+ //row resize
23425
+ $('ng-row-indicator', this.#owner.body).each((i,el) => {
23426
+ el.refresh();
23427
+ });
23428
+
23429
+ //5. nodata resize
23430
+ var h = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getTrHeight();
23431
+ $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23432
+
23433
+ //this.isLastPage = h > 0 ? true : false;
23434
+
23435
+
23436
+ this.#owner.refreshBindRows();
23437
+
23438
+ this.#owner.refreshData();
23439
+
23440
+ this.#owner.paging.totalCount = this.#owner.data.countNF();
23441
+
23442
+ this.#owner.body.querySelector("ng-vscrollbar").refresh();
23468
23443
 
23469
- class ngView
23470
- {
23471
- #owner;
23472
- #rawIndex;
23473
- #height;
23474
- #ing;
23475
- #timers = [];
23476
-
23477
- constructor(owner) {
23478
- this.#owner = owner;
23479
-
23480
- this.#ing = false;
23444
+ ninegrid.j.querySelectorAll(".ng-table tbody tr", this.#owner.body).removeClass("hover");
23445
+ ninegrid.j.querySelectorAll(this.#owner).removeClass("loading");
23446
+ ninegrid.j.querySelectorAll("ng-filter-panel").removeClass("loading");
23447
+
23448
+ /**
23449
+ this.#owner.body.querySelector(".ng-container-left").style.position = "sticky";
23450
+ this.#owner.body.querySelector(".ng-container-right").style.position = "sticky";
23451
+ this.#owner.body.querySelector(".ng-container-body").style.position = "sticky";
23452
+ this.#owner.body.querySelector(".ng-container-left").style.top = "0px";
23453
+ this.#owner.body.querySelector(".ng-container-right").style.top = "0px";
23454
+ this.#owner.body.querySelector(".ng-container-body").style.top = "0px";
23455
+
23456
+ setTimeout(() => {
23457
+ this.#owner.body.querySelector(".ng-container-left").style.position = "relative";
23458
+ this.#owner.body.querySelector(".ng-container-right").style.position = "relative";
23459
+ this.#owner.body.querySelector(".ng-container-body").style.position = "relative";
23460
+ this.#owner.body.querySelector(".ng-container-left").style.top = "0px";
23461
+ this.#owner.body.querySelector(".ng-container-right").style.top = "0px";
23462
+ this.#owner.body.querySelector(".ng-container-body").style.top = "0px";
23463
+ }, 1000); */
23464
+
23465
+ }, 300));
23481
23466
 
23482
- this.init();
23467
+ //if (!this.timers) this.timers = [];
23468
+ this.#timers.push(setTimeout(() => {
23469
+ this.#owner.refreshDataV2();
23470
+ }));
23483
23471
  }
23484
23472
 
23485
- /**
23486
- * changelayout
23487
- * resize
23488
- */
23489
- init = () => {
23490
-
23491
- //if (this.#ing) return;
23492
-
23493
- this.#height = {
23494
- body : $(".ng-container-body", this.#owner.body).height() || 0,
23495
- thead : $(".ng-container-body thead", this.#owner.body).height() || 0,
23496
- tfoot : $(".ng-container-body tfoot", this.#owner.body).height() || 0,
23497
- };
23498
-
23499
- if (this.#ing) return;
23473
+ #r = () => {
23474
+
23475
+ var rows = [];
23476
+ this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)").forEach(tr => {
23477
+ rows.push(this.#rawIndex + parseInt(tr.sectionRowIndex / this.#owner.template.length));
23478
+ });
23500
23479
 
23501
- $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(this.#height.body);
23480
+ rows = [...new Set(rows)];
23502
23481
 
23503
- this.#ing = true;
23504
- };
23505
-
23506
- redraw = () => {
23482
+ this.#owner.body.querySelectorAll(".ng-container tbody.bindable tr:not(.nodata)").forEach(tr => {
23483
+ const index = parseInt(tr.sectionRowIndex / this.#owner.template.length);
23507
23484
 
23508
- this.#owner.touchEvent.scrollTop();
23509
-
23510
- this.init();
23485
+ tr.dataset.row = rows[index];
23486
+ tr.dataset.matrixRow = parseInt(rows[index] * this.#owner.template.length) + parseInt(tr.sectionRowIndex % this.#owner.template.length);
23487
+
23488
+ for (const cell of tr.cells) {
23489
+ cell.dataset.row = tr.dataset.row;
23490
+ cell.dataset.matrixRow = tr.dataset.matrixRow;
23491
+ }
23492
+ });
23511
23493
 
23512
- if (!this.#owner.dataManager) return;
23513
23494
 
23514
- this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23495
+ rows.forEach(row => {
23496
+ const data = this.#owner.data.get(row);
23515
23497
 
23516
- this.#resetTR3();
23498
+ this.#owner.body.querySelectorAll(`.ng-container tbody.bindable tr[data-row="${row}"]`).forEach(tr => {
23499
+ tr.data = data;
23500
+ });
23501
+ });
23517
23502
 
23518
- this.#r();
23503
+ this.#rawIndex = parseInt(this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:first-of-type").getAttribute("data-row"));
23504
+
23505
+ this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23519
23506
 
23520
- this.#owner.body.querySelector("ng-vscrollbar").refresh();
23521
- };
23522
-
23523
- redrawV2 = () => {
23524
23507
 
23525
- this.#owner.touchEvent.scrollTop();
23526
-
23527
- this.init();
23508
+ this.#refreshData();
23528
23509
 
23529
- this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23510
+ //this.#owner.isFirstPage = this.#rawIndex == this.#owner.cell.firstRow() ? true : false;
23511
+ this.#owner.isFirstPage = this.#rawIndex == 0 ? true : false;
23512
+ //this.#owner.isLastPage = this.isLastPage;
23530
23513
 
23531
- this.#resetTR3V2();
23532
23514
 
23533
- this.#r();
23534
- this.#owner.body.querySelector("ng-vscrollbar").refreshV2();
23515
+ this.#owner.isLastPage = rows[rows.length-1] == this.#owner.data.count() - 1 ? true : false;//this.#isLastPage();
23516
+ //this.#owner.lastHidden = (parseInt($(".ng-container-body", this.#owner.body).height() - ($(".ng-container-body tfoot", this.#owner.body).height() || 0) + 2) >= t + h) ? false : true;
23517
+
23518
+ //this.#owner.body.querySelector("ng-vscrollbar").refresh();
23535
23519
  };
23536
23520
 
23537
- redrawV3 = () => {
23538
-
23539
- this.init();
23540
-
23541
- this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23542
-
23543
- //this.#resetTR3V2();
23521
+ getVisibleFirstRow = () => {
23522
+ var tr = this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:first-child");
23523
+ return parseInt(tr.dataset.row);
23524
+ };
23544
23525
 
23545
- this.#r();
23546
- //this.#owner.body.querySelector("ng-vscrollbar").refreshV2();
23526
+ getVisibleLastRow = () => {
23527
+ var trs = this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)");
23528
+ return (trs.length > 0) ? parseInt(trs[trs.length - 1].dataset.row) : -1;
23547
23529
  };
23548
-
23549
23530
 
23550
- #getPinHeight = () => {
23551
- var h = 0;
23531
+ getRowHeight = (rowIndex) => {
23532
+ let h = 0;
23552
23533
 
23553
- $(".ng-container-body tbody.fixed tr", this.#owner.body).each((i, tr) => {
23554
- if (tr.data && tr.data.__ng) {
23555
- h += this.#owner.matrix.getHeight(tr.data.__ng.height[parseInt(i % this.#owner.template.length)]);
23556
- }
23557
- });
23534
+ for (var i = 0; i < this.#owner.template.length; i++) {
23535
+ h += this.#owner.matrix.getHeight(parseInt(rowIndex * this.#owner.template.length + i));
23536
+ }
23558
23537
 
23559
23538
  return h;
23560
- }
23539
+ };
23561
23540
 
23562
- #getTrHeight = () => {
23563
- var h = this.#getPinHeight();
23564
-
23565
- const startRow = parseInt(this.#rawIndex * this.#owner.template.length);
23566
-
23567
- $(".ng-container-body tbody.bindable tr:not(.nodata)", this.#owner.body).each((i, tr) => {
23568
- h += this.#owner.matrix.getHeight(startRow + i);
23569
- });
23541
+ getTranslateY = () => {
23542
+ const tbody = this.#owner.body.querySelector(".ng-container-body tbody.bindable");
23543
+ return (!tbody || !tbody.style || !tbody.style.transform) ? 0 : parseFloat(tbody.style.transform.replace("translateY(", "").replace(")", ""));
23544
+ };
23545
+
23546
+ getTotalSpace = () => {
23570
23547
 
23571
- return h;
23572
- }
23548
+ return $(this.#owner.body.querySelector(".ng-container-body")).height()
23549
+ - ($(this.#owner.body.querySelector(".ng-container-body thead")).height() || 0)
23550
+ - ($(this.#owner.body.querySelector(".ng-container-body tfoot")).height() || 0)
23551
+ - ($(this.#owner.body.querySelector(".ng-container-body tbody.fixed")).height() || 0);
23552
+ //- this.getTranslateY();
23553
+ };
23573
23554
 
23574
- #getRowCount = () => {
23555
+ fitPage = () => {
23556
+ const lastRow = this.getVisibleLastRow();
23557
+ const totalSpace = this.getTotalSpace();
23575
23558
 
23576
- const displayRowCount = parseInt(this.#owner.getAttribute("display-row-count"));
23577
-
23578
- if (this.#owner.closest("dialog") && ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) return 1;
23579
- if ([ninegrid.PAGINGTYPE.CLIENT,ninegrid.PAGINGTYPE.SERVER].includes(this.#owner.paging.type)) return this.#owner.paging.linesPerPage;
23580
- if (!isNaN(displayRowCount)) {
23581
-
23582
- /**
23583
- * 데이타가 적을 경우 rawIndex = 0;
23584
- * 마지막 페이지 인경우 rawIndex 계산
23585
- */
23586
- if (this.#rawIndex + displayRowCount > this.#owner.data.count()) {
23587
- this.#rawIndex = (displayRowCount >= this.#owner.data.count()) ? 0 : this.#owner.data.count() - displayRowCount;
23588
- this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23589
-
23559
+ /**
23560
+ * 마지막 페이지 정리
23561
+ */
23562
+ if (lastRow == this.#owner.data.count() - 1) {
23563
+ var h = 0;
23564
+ for (var i = lastRow; i >= 0; i--) {
23565
+ h += this.getRowHeight(i);
23566
+
23567
+ if (h > totalSpace) {
23568
+ this.#owner.scrollTo(i + 1);
23569
+ break;
23570
+ }
23590
23571
  }
23591
-
23592
-
23593
- return displayRowCount;
23594
- }
23595
-
23596
- const totalHeight = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getPinHeight();
23597
-
23598
- var rowCount = 0;
23599
- var h = 0;
23600
- while (h < totalHeight) {
23601
- h += this.getRowHeight(this.#rawIndex + rowCount++);
23602
23572
  }
23573
+ }
23574
+ /**
23575
+ #isLastPage = () => {
23576
+ const arr = this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)");
23577
+ if (arr.length <= 0) return null;
23603
23578
 
23604
- if (this.#rawIndex + rowCount > this.#owner.data.count()) {
23605
-
23606
- rowCount = 0;
23607
- h = 0;
23608
- for (var i = this.#owner.data.count() - 1; i >= 0 && h < totalHeight; i--) {
23609
- h += this.getRowHeight(i);
23610
- rowCount++;
23611
- }
23612
-
23613
- if (h > totalHeight) rowCount --;
23579
+ return arr[arr.length - 1].dataset.row == this.#owner.data.count() - 1 ? true : false;
23580
+ }*/
23581
+
23582
+
23583
+ /**
23584
+ * grid.selectCell(row, colnm, subrow) => scrollTo
23585
+ * grid.selectCol(fromColNm, toColNm)
23586
+ * grid.selectRow(row) => scrollTo
23587
+ * grid.selectArea(row1, col1, row2, col2) => scrollTo
23588
+ *
23589
+ * grid.clearSelect()
23590
+ *
23591
+ * grid.selection.currentCell;
23592
+ * grid.selection.currentRow;
23593
+ * grid.selection.currentSubRow;
23594
+ * grid.selection.currentCol;
23595
+ * grid.selection.firstRow
23596
+ * grid.selection.firstCol
23597
+ * grid.selection.lastRow
23598
+ * grid.selection.lastCol
23599
+ */
23600
+
23601
+ selectArea = (oRow1, oCol1, oRow2, oCol2) => {
23614
23602
 
23615
- this.#rawIndex = this.#owner.data.count() - rowCount;
23616
-
23617
- this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23618
- }
23603
+ const row1 = this.#getRowIndex(oRow1);
23604
+ const row2 = this.#getRowIndex(oRow2);
23605
+ const col1 = this.#getColIndex(oCol1);
23606
+ const col2 = this.#getColIndex(oCol2);
23607
+ const matrixRow1= parseInt(row1 * this.#owner.template.length);
23608
+ const matrixRow2= parseInt(row2 * this.#owner.template.length) + this.#owner.template.length - 1;
23619
23609
 
23620
-
23621
- return rowCount;
23610
+ this.#owner.row.at = row1;
23611
+
23612
+ (row1 >= 0 && row2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, col1, matrixRow2, col2) : this.clearSelection();
23622
23613
  };
23623
23614
 
23624
- #createTR = () => {
23625
-
23626
- // 1. 스크린에 보여질 TR 갯수 계산
23627
- var rowCount = this.#getRowCount();
23628
-
23629
- // 0. TR 생성여부 판단
23630
- //if (!this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:not(.nodata)") || this.isLastPage) {
23631
- // 2. TR 생성
23632
- var startRow = $(".ng-container-body tbody.bindable tr", this.#owner.body).not('tr.nodata').length / (this.#owner.template.length || 1);
23633
-
23634
-
23635
- const leftTmpl = this.#owner.template.clone();
23636
- const rightTmpl = leftTmpl.clone();//this.#owner.template.clone();
23637
- const bodyTmpl = leftTmpl.clone();//this.#owner.template.clone();
23638
-
23639
- leftTmpl .find("th,td").not("[fixed=left]").remove();
23640
- rightTmpl .find("th,td").not("[fixed=right]").remove();
23641
- bodyTmpl .find("th[fixed=left],td[fixed=left],th[fixed=right],td[fixed=right]").remove();
23642
-
23643
- const leftNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-left"));
23644
- const rightNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-right"));
23645
- const bodyNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-body"));
23615
+ selectRow = (oRow1, oRow2) => {
23646
23616
 
23647
- for (var i = startRow; i < rowCount; i++) {
23648
-
23649
-
23650
- leftTmpl .each((i,tr) => { leftNoData .before(tr.outerHTML); });
23651
- rightTmpl .each((i,tr) => { rightNoData .before(tr.outerHTML); });
23652
- bodyTmpl .each((i,tr) => { bodyNoData .before(tr.outerHTML); });
23653
- }
23654
- //}
23617
+ const row1 = this.#getRowIndex(oRow1);
23618
+ const row2 = ninegrid.isNull(oRow2) ? row1 : this.#getRowIndex(oRow2);
23619
+ const matrixRow1= parseInt(row1 * this.#owner.template.length);
23620
+ const matrixRow2= parseInt(row2 * this.#owner.template.length) + this.#owner.template.length - 1;
23655
23621
 
23656
-
23622
+ this.#owner.row.at = row1;
23657
23623
 
23658
- // 3. 불필요 TR 제거
23659
- /**
23660
- rowCount = Math.min(rowCount, this.#owner.data.count());// + ($(".ng-container-body tbody.fixed tr", this.#owner.body).length / this.#owner.template.length);
23661
- $(".ng-container tbody.bindable tr", this.#owner.body).not(".nodata").each((i,tr) => {
23662
- if (parseInt(tr.sectionRowIndex / this.#owner.template.length) >= rowCount) {
23663
- $(tr).remove();
23664
- }
23665
- }); */
23624
+ (row1 >= 0 && row2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, this.#owner.firstCol, matrixRow2, this.#owner.lastCol) : this.clearSelection();
23625
+ };
23626
+
23627
+ selectCol = (oCol1, oCol2) => {
23628
+ const col1 = this.#getColIndex(oCol1);
23629
+ const col2 = ninegrid.isNull(oCol2) ? col1 : this.#getColIndex(oCol2);
23630
+ const matrixRow1= 0;
23631
+ const matrixRow2= parseInt(this.#owner.data.count() * this.#owner.template.length - 1);
23632
+
23633
+ (col1 >= 0 && col2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, col1, matrixRow2, col2) : this.clearSelection();
23634
+ };
23635
+
23636
+ selectCell = (oRow, oCol, subrow) => {
23666
23637
 
23667
- return;
23638
+ const row = this.#getRowIndex(oRow);
23639
+ const col = this.#getColIndex(oCol);
23640
+ const matrixRow = parseInt(row * this.#owner.template.length) + ninegrid.nvl(subrow, 0);
23668
23641
 
23669
- // 4. row resize
23670
- var trsBody = $(".ng-container-body tbody.bindable tr:not(.nodata)", this.#owner.body);
23642
+ this.#owner.row.at = row;
23671
23643
 
23672
- if (trsBody.length > 0) {
23673
- var startRow = parseInt(this.#rawIndex * this.#owner.template.length);
23644
+ (row >= 0) ? this.#owner.selection.selectArea(matrixRow, col, matrixRow, col) : this.clearSelection();
23645
+ };
23646
+
23647
+ #getRowIndex = (oRow) => {
23648
+ if (typeof oRow === "number") {
23649
+ return oRow;
23650
+ }
23651
+ else if (typeof oRow === "function") {
23652
+ return this.#owner.data.findIndex(oRow);
23653
+ }
23654
+ else {
23655
+ throw `invalid ${oRow}`;
23674
23656
  }
23675
23657
  };
23676
23658
 
23677
- #removeGarbageTR = () => {
23678
-
23679
- var rowCount = Math.min(this.#getRowCount(), this.#owner.data.count());// + this.pin.count();
23659
+ #getColIndex = (oCol) => {
23660
+ if (typeof oCol === "number") {
23661
+ return oCol;
23662
+ }
23663
+ else if (typeof oCol === "string" && this.#owner.fields.includes(oCol)) {
23664
+ return parseInt($(`[data-bind="${oCol}"]`, this.#owner.template)[0].dataset.col);
23665
+ }
23666
+ else {
23667
+ throw `invalid ${oCol}`;
23668
+ }
23669
+ };
23670
+
23671
+
23672
+
23673
+ moveTo = (at) => {
23674
+ this.#owner.scrollTo(at);
23675
+ }
23676
+ }
23680
23677
 
23681
-
23682
- $(".ng-container tbody.bindable tr:not(.nodata)", this.#owner.body).each((i,tr) => {
23683
- if (parseInt(tr.sectionRowIndex / this.#owner.template.length) >= rowCount) {
23684
- $(tr).remove();
23685
- }
23686
- });
23678
+ /**
23679
+ * button.filterOptions = [{
23680
+ * colnm: "userId",
23681
+ * data: [],
23682
+ * }]
23683
+ * button.
23684
+ */
23685
+ class ngFiltering
23686
+ {
23687
+ #owner;
23688
+ #isFiltering;
23689
+
23690
+ constructor(owner) {
23691
+ this.#owner = owner;
23692
+ this.#isFiltering = false;
23687
23693
 
23688
- this.#owner.body.querySelectorAll(".ng-container tbody.bindable tr:not(.nodata)").forEach(tr => {
23689
- const row = this.#rawIndex + parseInt(tr.sectionRowIndex / this.#owner.template.length);
23690
- if (row >= this.#owner.data.count()) {
23691
- $(tr).remove();
23692
- }
23693
- });
23694
+ const filterPanel = document.createElement("ng-filter-panel"); // ✅ 필터 패널 생성
23695
+ filterPanel.style.display = "none"; // 숨김 처리
23696
+ this.#owner.shadowRoot.appendChild(filterPanel); // ✅ Shadow DOM 내부에 추가
23697
+ }
23698
+
23699
+ initialize = () => {
23700
+ /**
23701
+ * 1. grid attr == filter : on();
23702
+ */
23703
+ this.#isFiltering = false;
23704
+ this.#owner.data.clearFilter();
23694
23705
  };
23695
23706
 
23696
- #resetTR3 = () => {
23697
-
23698
- // 1. TR 생성
23699
- this.#createTR();
23707
+ isFiltering = () => {
23708
+ return this.#isFiltering;
23709
+ };
23710
+
23711
+ on = (v) => {
23712
+
23713
+ this.#isFiltering = true;
23714
+ const lastRowIndex = this.#getLastRowIndex();
23700
23715
 
23701
- this.#removeGarbageTR();
23716
+ this.#owner.body.querySelectorAll(".ng-table thead th, .ng-table thead td").forEach((td) => {
23717
+ const rowIndex = td.closest("tr")?.sectionRowIndex;
23718
+ if (rowIndex !== undefined && rowIndex + td.rowSpan - 1 === lastRowIndex) {
23719
+ const options = Array.from(this.#owner.activeTmpl.querySelectorAll(`[data-col="${td.dataset.col}"]`))
23720
+ .map(el => el.dataset.bind ? { colnm: el.dataset.bind, data: [] } : null)
23721
+ .filter(Boolean); // ✅ `null` 제거
23702
23722
 
23703
- // 5. nodata resize
23723
+ if (options.length > 0) {
23724
+ td.querySelector("ng-filter-button")?.remove();
23704
23725
 
23705
- var h = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getTrHeight();
23706
- $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23707
-
23708
- /**
23709
- this.#owner.body.querySelector(".ng-container-body .ng-table").style.top = 0;
23710
- this.#owner.body.querySelector(".ng-container-left .ng-table").style.top = 0;
23711
- this.#owner.body.querySelector(".ng-container-right .ng-table").style.top = 0;
23712
- */
23713
-
23714
- //$('.ng-container .ng-table', this.#owner.body).offset({top:$('.ng-container .ng-table', this.#owner.body).offset().top});
23715
- //$('.ng-container .ng-table', this.#owner.body).offset({top:$('.ng-container .ng-table', this.#owner.body).offset().top});
23716
- //this.isLastPage = h > 0 ? true : false;
23726
+ const filterButton = document.createElement("ng-filter-button");
23727
+ td.appendChild(filterButton);
23728
+ filterButton.filterOptions = options;
23729
+ }
23730
+ }
23731
+ });
23732
+ };
23717
23733
 
23718
-
23719
- if (this.#owner.closest("dialog") && ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) {
23720
- $("tr.nodata", this.#owner.body).height(0);
23721
-
23722
- var h = 47;//this.#getTrHeight();
23723
- $('.ng-container-body tbody.bindable tr', this.#owner.body).not('.nodata').each((i,tr) => {
23724
- h += $(tr).height();
23725
- });
23734
+ off = () => {
23735
+ this.#isFiltering = false;
23726
23736
 
23727
- $(this.#owner.closest("dialog")).height(h);
23728
- }
23737
+ this.#owner.body.querySelectorAll(".ng-table ng-filter-button").forEach((el) => {
23738
+ el.remove(); // ✅ 요소 삭제
23739
+ });
23729
23740
  };
23730
23741
 
23731
- #resetTR3V2 = () => {
23732
-
23733
- // 1. TR 생성
23734
- //this.#owner.isLastPage = rows[rows.length-1] == this.#owner.data.count() - 1 ? true : false;//this.#isLastPage();
23735
-
23736
- //if (this.#owner.isLastPage) {
23737
- //if (this.#isLastPage) {
23742
+ /**
23743
+ clear = () => {
23744
+ console.log(this.#owner.dataManager.rawRecords);
23745
+ this.#owner.dataManager.rawRecords.map(item => { item.__ng.filtered = false; });
23738
23746
 
23739
- //if (this.#owner.isLastPage) {
23740
- this.#createTR();
23741
- //}
23742
- /**
23743
- if ($(".ng-container-body tbody.bindable tr.nodata", this.#owner.body).height() > 0) {
23744
- this.#createTR();
23745
- } */
23746
23747
 
23747
- this.#removeGarbageTR();
23748
+ this.#records = this.#owner.dataManager.rawRecords.filter(item => { return !item.__ng.deleted && !item.__ng.filtered; } );
23749
+ return this.#records;
23750
+ }
23751
+
23752
+ refresh = () => {
23753
+ this.#records = this.#owner.dataManager.rawRecords.filter(item => { return !item.__ng.deleted && !item.__ng.filtered; } );
23754
+ //this.#records.forEach((o,i) => { o.__ng.rowidx = i; });
23755
+ //this.getValidData().forEach((o,i) => { o.__ng._[ninegrid.ROW.ORDER] = i + 1; });
23756
+ this.#resetOrder();
23757
+ return this.#records;
23758
+ } */
23759
+
23760
+
23761
+ /**
23762
+ * oFilter : {
23763
+ * col1 : [],
23764
+ * col2 : []
23765
+ * }
23766
+ */
23767
+ set = (oFilter) => {
23748
23768
 
23769
+ this.on();
23770
+
23771
+ // ✅ JSON 변환 (배열 → 객체)
23772
+ let jsonFilter = Array.isArray(oFilter)
23773
+ ? Object.fromEntries(Object.keys(oFilter[0]).map(key => [key, [...new Set(oFilter.map(item => item[key]))]]))
23774
+ : oFilter;
23749
23775
 
23750
- // 5. nodata resize
23751
- /**
23752
- if (this.#owner.isFirstPage || this.#owner.isLastPage) {
23753
- var h = this.#owner.body.querySelector("ng-vscrollbar").bodyHeight - $(".ng-container-body .ng-table", this.#owner.body).height() + $('.ng-container-body .ng-table tr.nodata', this.#owner.body).height();
23754
- $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23755
- }*/
23756
- //$(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(700);
23757
-
23758
- if (this.#owner.isLastPage) ;
23759
-
23776
+ this.#owner.data.clearFilter();
23760
23777
 
23761
- //this.isLastPage = h > 0 ? true : false;
23778
+ Object.entries(jsonFilter).forEach(([key, arr]) => {
23779
+ const idx = this.#owner.fields.indexOf(key);
23780
+
23781
+ // ✅ 숫자 판별 및 변환
23782
+ if (this.#owner.data.getValidData().some(o => typeof o.v[idx] === "number")) {
23783
+ arr = arr.map(Number);
23784
+ }
23762
23785
 
23786
+ // ✅ 필터 적용
23787
+ this.#owner.data.getValidData()
23788
+ .filter(m => arr.nineBinarySearch(m.v[idx] || '') < 0)
23789
+ .forEach(m => { m.__ng.filtered = true; });
23790
+ });
23763
23791
 
23764
- //this.nodata = h > 0 ? true : false;
23765
23792
 
23766
- // 6. 마지막 TR tabindex
23767
- };
23768
-
23769
- #refreshData = () => {
23793
+ this.#owner.data.refreshFilter();
23770
23794
 
23771
- for (const v of this.#timers) {
23772
- clearTimeout(v);
23773
- }
23774
- this.#timers = [];
23775
-
23776
- this.#timers.push(setTimeout(() => {
23777
-
23778
- this.#ing = false;
23779
-
23780
- // 4. row resize
23781
- var trsBody = $(".ng-container-body tbody.bindable tr", this.#owner.body).not(".nodata");
23782
- var trsLeft = $(".ng-container-left tbody.bindable tr", this.#owner.body).not(".nodata");
23783
- var trsRight= $(".ng-container-right tbody.bindable tr", this.#owner.body).not(".nodata");
23784
-
23785
- if (trsBody.length > 0) {
23786
- var startRow = parseInt(trsBody[0].dataset.row * this.#owner.template.length);
23787
-
23788
- trsBody.each((i, tr) => {
23789
- var h = this.#owner.matrix.getHeight(startRow + i);// || 32;
23795
+ // 필터 버튼 초기화
23796
+ this.#owner.shadowRoot.querySelectorAll("ng-filter-button").forEach(el => {
23797
+ el.filterOptions.forEach(opt => { opt.data = []; });
23798
+ });
23799
+
23800
+ Object.entries(jsonFilter).forEach(([key, arr]) => {
23801
+ this.#owner.shadowRoot.querySelectorAll("ng-filter-button").forEach(el => {
23790
23802
 
23791
- $("canvas.chart", tr).css({ "height" : h-4 });
23792
- $("canvas.chart", trsLeft.eq(i)).css({ "height" : h-4 });
23793
- $("canvas.chart", trsRight.eq(i)).css({ "height" : h-4 });
23803
+ let options = el.filterOptions;
23794
23804
 
23795
- $(tr).css({ "height" : h }).height(h);
23796
- trsLeft.eq(i).css({ "height" : h }).height(h);
23797
- trsRight.eq(i).css({ "height" : h }).height(h);
23805
+ options.forEach(opt => {
23806
+ if (opt.colnm === key) opt.data = arr;
23798
23807
  });
23799
- }
23800
-
23801
- this.#owner.cell.refresh();
23802
-
23803
- //row resize
23804
- $('ng-row-indicator', this.#owner.body).each((i,el) => {
23805
- el.refresh();
23806
- });
23807
-
23808
- //5. nodata resize
23809
- var h = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getTrHeight();
23810
- $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23811
-
23812
- //this.isLastPage = h > 0 ? true : false;
23813
23808
 
23814
-
23815
- this.#owner.refreshBindRows();
23809
+ el.filterOptions = options;
23810
+ });
23811
+ });
23812
+
23813
+
23814
+ this.#owner.scrollTo_V1(0);
23815
+
23816
+ this.#owner.paging.reset();
23817
+ };
23818
+
23819
+ #getLastRowIndex = (v = "thead") =>
23820
+ [...this.#owner.body.querySelectorAll(`.ng-table ${v}`)]
23821
+ .reduce((maxIndex, el) => Math.max(el.rows.length - 1, maxIndex), 0);
23816
23822
 
23817
- this.#owner.refreshData();
23818
-
23819
- this.#owner.paging.totalCount = this.#owner.data.countNF();
23820
-
23821
- this.#owner.body.querySelector("ng-vscrollbar").refresh();
23823
+ }
23822
23824
 
23823
- ninegrid.j.querySelectorAll(".ng-table tbody tr", this.#owner.body).removeClass("hover");
23824
- ninegrid.j.querySelectorAll(this.#owner).removeClass("loading");
23825
- ninegrid.j.querySelectorAll("ng-filter-panel").removeClass("loading");
23826
-
23827
- /**
23828
- this.#owner.body.querySelector(".ng-container-left").style.position = "sticky";
23829
- this.#owner.body.querySelector(".ng-container-right").style.position = "sticky";
23830
- this.#owner.body.querySelector(".ng-container-body").style.position = "sticky";
23831
- this.#owner.body.querySelector(".ng-container-left").style.top = "0px";
23832
- this.#owner.body.querySelector(".ng-container-right").style.top = "0px";
23833
- this.#owner.body.querySelector(".ng-container-body").style.top = "0px";
23834
-
23835
- setTimeout(() => {
23836
- this.#owner.body.querySelector(".ng-container-left").style.position = "relative";
23837
- this.#owner.body.querySelector(".ng-container-right").style.position = "relative";
23838
- this.#owner.body.querySelector(".ng-container-body").style.position = "relative";
23839
- this.#owner.body.querySelector(".ng-container-left").style.top = "0px";
23840
- this.#owner.body.querySelector(".ng-container-right").style.top = "0px";
23841
- this.#owner.body.querySelector(".ng-container-body").style.top = "0px";
23842
- }, 1000); */
23825
+ class ngFilterButton extends HTMLElement
23826
+ {
23827
+ #owner;
23828
+ #filterOptions;
23829
+
23830
+ constructor () {
23831
+ super();
23832
+ }
23843
23833
 
23844
- }, 300));
23834
+ connectedCallback() {
23835
+ this.#owner = this.getRootNode().host;//this.closest("nine-grid");
23845
23836
 
23846
- //if (!this.timers) this.timers = [];
23847
- this.#timers.push(setTimeout(() => {
23848
- this.#owner.refreshDataV2();
23849
- }));
23837
+ this.removeEventListener("click", this.#onClick);
23838
+ this.addEventListener("click", this.#onClick);
23839
+ };
23840
+
23841
+ get filterOptions() {
23842
+ return this.#filterOptions;
23843
+ };
23844
+ set filterOptions(v) {
23845
+ this.#filterOptions = v;
23846
+ this.classList.toggle('filtered', this.#filterOptions.some(item => item.data.length > 0));
23850
23847
  }
23851
23848
 
23852
- #r = () => {
23849
+ #onClick = (e) => {
23850
+ e.preventDefault();
23851
+ e.stopPropagation();
23853
23852
 
23854
- var rows = [];
23855
- this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)").forEach(tr => {
23856
- rows.push(this.#rawIndex + parseInt(tr.sectionRowIndex / this.#owner.template.length));
23857
- });
23858
-
23859
- rows = [...new Set(rows)];
23860
-
23861
- this.#owner.body.querySelectorAll(".ng-container tbody.bindable tr:not(.nodata)").forEach(tr => {
23862
- const index = parseInt(tr.sectionRowIndex / this.#owner.template.length);
23853
+ const panel = this.#owner.shadowRoot.querySelector("ng-filter-panel");
23854
+ const col = this.closest("th,td")?.dataset.col; // ✅ `?.` 활용하여 안전한 접근
23863
23855
 
23864
- tr.dataset.row = rows[index];
23865
- tr.dataset.matrixRow = parseInt(rows[index] * this.#owner.template.length) + parseInt(tr.sectionRowIndex % this.#owner.template.length);
23866
-
23867
- for (const cell of tr.cells) {
23868
- cell.dataset.row = tr.dataset.row;
23869
- cell.dataset.matrixRow = tr.dataset.matrixRow;
23870
- }
23871
- });
23872
-
23856
+ panel.col === col ? panel.close() : panel.open(this);
23857
+ };
23858
+ }
23873
23859
 
23874
- rows.forEach(row => {
23875
- const data = this.#owner.data.get(row);
23860
+ class ngFilterPanel extends HTMLElement
23861
+ {
23862
+ #owner;
23863
+ #button;
23864
+ #timer;
23865
+
23866
+ constructor () {
23867
+ super();
23868
+ this.attachShadow({ mode: 'open' });
23869
+ }
23876
23870
 
23877
- this.#owner.body.querySelectorAll(`.ng-container tbody.bindable tr[data-row="${row}"]`).forEach(tr => {
23878
- tr.data = data;
23879
- });
23880
- });
23881
-
23882
- this.#rawIndex = parseInt(this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:first-of-type").getAttribute("data-row"));
23883
-
23884
- this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23871
+ connectedCallback() {
23885
23872
 
23873
+ this.#owner = this.getRootNode().host;
23886
23874
 
23887
- this.#refreshData();
23875
+ const cssPath = this.getRootNode().host.closest("nine-grid").getAttribute("css-path") || "";
23876
+
23877
+ this.shadowRoot.innerHTML = `
23878
+ <style>
23879
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngFilterPanel.css";
23880
+ ${ninegrid.getCustomPath(this,"ngFilterPanel.css")}
23881
+ </style>
23882
+
23883
+ <div class="head">
23884
+ <input type="text">
23885
+ </div>
23886
+ <nine-grid class="simple filter" css-path="${cssPath}" select-type="row" auto-fit-col="true" col-indicator-type="collapse">
23887
+ <table style="display: none;">
23888
+ <colgroup>
23889
+ <col width="180" />
23890
+ <col width="30" />
23891
+ </colgroup>
23892
+ <tbody>
23893
+ <tr style="height: 24px;">
23894
+ <td data-bind="LVL" data-expr="data.DATA2">
23895
+ <ng-tree-item />
23896
+ </td>
23897
+ <td data-bind="CHK">
23898
+ <ng-checkbox update-row-state="false" visible="data.LVL!=1" selected-border-color="white" />
23899
+ </td>
23900
+ </tr>
23901
+ </tbody>
23902
+ </table>
23903
+ </nine-grid>
23904
+
23905
+ <div>
23906
+ <label><input type="checkbox" checked>Select All</label>
23907
+ <button id="btnOk">OK</button>
23908
+ <button id="btnCancel">Cancel</button>
23909
+ </div>
23910
+ `;
23888
23911
 
23889
- //this.#owner.isFirstPage = this.#rawIndex == this.#owner.cell.firstRow() ? true : false;
23890
- this.#owner.isFirstPage = this.#rawIndex == 0 ? true : false;
23891
- //this.#owner.isLastPage = this.isLastPage;
23912
+ this.shadowRoot.querySelector("input[type=text]").addEventListener("input", this.#onInput);
23913
+ this.shadowRoot.querySelector("input[type=checkbox]").addEventListener("change", this.#onSelectAll);
23914
+ this.shadowRoot.querySelector("#btnOk").addEventListener("click", this.#onOk);
23915
+ this.shadowRoot.querySelector("#btnCancel").addEventListener("click", this.#onCancel);
23916
+ };
23917
+
23918
+ #onOk = (e) => {
23919
+
23920
+ const grd = this.shadowRoot.querySelector("nine-grid");
23921
+
23922
+ this.classList.add("loading");
23892
23923
 
23924
+ setTimeout(() => {
23925
+ const [LVL_IDX, CHK_IDX, COLNM_IDX, DATA_IDX] = ["LVL", "CHK", "COLNM", "DATA"].map(k => grd.fields.indexOf(k));
23926
+ const checked = grd.data.getValidDataNF().filter(m => m.v[LVL_IDX] === 2 && m.v[CHK_IDX] === "Y");
23927
+ const filterOptions = checked.length > 0 ? [...new Set(checked.map(m => m.v[COLNM_IDX]))].map(v => ({
23928
+ colnm: v,
23929
+ data: [...new Set(grd.data.getValidData().filter(m => m.v[CHK_IDX] === "Y" && m.v[COLNM_IDX] === v).map(m => m.v[DATA_IDX] || ''))].sort()
23930
+ })) : [];
23931
+
23932
+
23933
+ this.#button.filterOptions = filterOptions;
23893
23934
 
23894
- this.#owner.isLastPage = rows[rows.length-1] == this.#owner.data.count() - 1 ? true : false;//this.#isLastPage();
23895
- //this.#owner.lastHidden = (parseInt($(".ng-container-body", this.#owner.body).height() - ($(".ng-container-body tfoot", this.#owner.body).height() || 0) + 2) >= t + h) ? false : true;
23935
+ const oParam = Object.fromEntries(
23936
+ [...this.#owner.body.querySelectorAll("ng-filter-button")]
23937
+ .flatMap(el => el.filterOptions)
23938
+ .filter(opt => opt.data.length > 0)
23939
+ .map(opt => [opt.colnm, opt.data])
23940
+ );
23941
+
23942
+ this.#owner.filtering.set(oParam);
23896
23943
 
23897
- //this.#owner.body.querySelector("ng-vscrollbar").refresh();
23898
- };
23899
-
23900
- getVisibleFirstRow = () => {
23901
- var tr = this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:first-child");
23902
- return parseInt(tr.dataset.row);
23944
+ this.classList.remove("loading");
23945
+ this.style.display = 'none';
23946
+ });
23903
23947
  };
23904
23948
 
23905
- getVisibleLastRow = () => {
23906
- var trs = this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)");
23907
- return (trs.length > 0) ? parseInt(trs[trs.length - 1].dataset.row) : -1;
23908
- };
23909
23949
 
23910
- getRowHeight = (rowIndex) => {
23911
- let h = 0;
23912
-
23913
- for (var i = 0; i < this.#owner.template.length; i++) {
23914
- h += this.#owner.matrix.getHeight(parseInt(rowIndex * this.#owner.template.length + i));
23915
- }
23916
-
23917
- return h;
23918
- };
23919
23950
 
23920
- getTranslateY = () => {
23921
- const tbody = this.#owner.body.querySelector(".ng-container-body tbody.bindable");
23922
- return (!tbody || !tbody.style || !tbody.style.transform) ? 0 : parseFloat(tbody.style.transform.replace("translateY(", "").replace(")", ""));
23951
+ #onCancel = (e) => {
23952
+ this.style.display = 'none';
23923
23953
  };
23924
-
23925
- getTotalSpace = () => {
23926
-
23927
- return $(this.#owner.body.querySelector(".ng-container-body")).height()
23928
- - ($(this.#owner.body.querySelector(".ng-container-body thead")).height() || 0)
23929
- - ($(this.#owner.body.querySelector(".ng-container-body tfoot")).height() || 0)
23930
- - ($(this.#owner.body.querySelector(".ng-container-body tbody.fixed")).height() || 0);
23931
- //- this.getTranslateY();
23954
+
23955
+ #onSelectAll = (e) => {
23956
+ const grd = this.shadowRoot.querySelector("nine-grid");
23957
+ const idx = grd.fields.indexOf("CHK");
23958
+ const isChecked = e.target.checked; // jQuery 없이 `checked` 값 가져오기
23959
+
23960
+ grd.data.getValidData().forEach(m => {
23961
+ m.v[idx] = isChecked ? "Y" : "N"; // ✅ `Y` 또는 `N` 값 설정
23962
+ });
23963
+
23964
+ grd.refreshData();
23932
23965
  };
23933
-
23934
- fitPage = () => {
23935
- const lastRow = this.getVisibleLastRow();
23936
- const totalSpace = this.getTotalSpace();
23937
-
23938
- /**
23939
- * 마지막 페이지 정리
23940
- */
23941
- if (lastRow == this.#owner.data.count() - 1) {
23942
- var h = 0;
23943
- for (var i = lastRow; i >= 0; i--) {
23944
- h += this.getRowHeight(i);
23945
-
23946
- if (h > totalSpace) {
23947
- this.#owner.scrollTo(i + 1);
23948
- break;
23949
- }
23950
- }
23951
- }
23952
- }
23953
- /**
23954
- #isLastPage = () => {
23955
- const arr = this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)");
23956
- if (arr.length <= 0) return null;
23957
23966
 
23958
- return arr[arr.length - 1].dataset.row == this.#owner.data.count() - 1 ? true : false;
23959
- }*/
23960
-
23961
23967
 
23962
- /**
23963
- * grid.selectCell(row, colnm, subrow) => scrollTo
23964
- * grid.selectCol(fromColNm, toColNm)
23965
- * grid.selectRow(row) => scrollTo
23966
- * grid.selectArea(row1, col1, row2, col2) => scrollTo
23967
- *
23968
- * grid.clearSelect()
23969
- *
23970
- * grid.selection.currentCell;
23971
- * grid.selection.currentRow;
23972
- * grid.selection.currentSubRow;
23973
- * grid.selection.currentCol;
23974
- * grid.selection.firstRow
23975
- * grid.selection.firstCol
23976
- * grid.selection.lastRow
23977
- * grid.selection.lastCol
23978
- */
23979
23968
 
23980
- selectArea = (oRow1, oCol1, oRow2, oCol2) => {
23981
-
23982
- const row1 = this.#getRowIndex(oRow1);
23983
- const row2 = this.#getRowIndex(oRow2);
23984
- const col1 = this.#getColIndex(oCol1);
23985
- const col2 = this.#getColIndex(oCol2);
23986
- const matrixRow1= parseInt(row1 * this.#owner.template.length);
23987
- const matrixRow2= parseInt(row2 * this.#owner.template.length) + this.#owner.template.length - 1;
23988
-
23989
- this.#owner.row.at = row1;
23969
+ #onInput = (e) => {
23970
+ const grd = this.shadowRoot.querySelector("nine-grid");
23990
23971
 
23991
- (row1 >= 0 && row2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, col1, matrixRow2, col2) : this.clearSelection();
23992
- };
23993
-
23994
- selectRow = (oRow1, oRow2) => {
23995
-
23996
- const row1 = this.#getRowIndex(oRow1);
23997
- const row2 = ninegrid.isNull(oRow2) ? row1 : this.#getRowIndex(oRow2);
23998
- const matrixRow1= parseInt(row1 * this.#owner.template.length);
23999
- const matrixRow2= parseInt(row2 * this.#owner.template.length) + this.#owner.template.length - 1;
23972
+ grd.classList.add("loading");
24000
23973
 
24001
- this.#owner.row.at = row1;
23974
+ const data = grd.dataManager.rawRecords;
23975
+ data.forEach(m => { m.__ng.filtered = false; });
24002
23976
 
24003
- (row1 >= 0 && row2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, this.#owner.firstCol, matrixRow2, this.#owner.lastCol) : this.clearSelection();
24004
- };
24005
-
24006
- selectCol = (oCol1, oCol2) => {
24007
- const col1 = this.#getColIndex(oCol1);
24008
- const col2 = ninegrid.isNull(oCol2) ? col1 : this.#getColIndex(oCol2);
24009
- const matrixRow1= 0;
24010
- const matrixRow2= parseInt(this.#owner.data.count() * this.#owner.template.length - 1);
23977
+ const v = e.target.value.toLowerCase(); // jQuery 없이 값 가져오기
23978
+ const [LVL_IDX, DATA_IDX] = ["LVL", "DATA"].map(field => grd.fields.indexOf(field));
24011
23979
 
24012
- (col1 >= 0 && col2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, col1, matrixRow2, col2) : this.clearSelection();
24013
- };
24014
-
24015
- selectCell = (oRow, oCol, subrow) => {
24016
-
24017
- const row = this.#getRowIndex(oRow);
24018
- const col = this.#getColIndex(oCol);
24019
- const matrixRow = parseInt(row * this.#owner.template.length) + ninegrid.nvl(subrow, 0);
24020
-
24021
- this.#owner.row.at = row;
23980
+ data.forEach(m => {
23981
+ m.__ng.filtered = m.v[LVL_IDX] === 2 && !String(m.v[DATA_IDX] || "").toLowerCase().includes(v);
23982
+ });
23983
+
23984
+ grd.data.resetRecords();
24022
23985
 
24023
- (row >= 0) ? this.#owner.selection.selectArea(matrixRow, col, matrixRow, col) : this.clearSelection();
23986
+ clearTimeout(this.#timer);
23987
+ this.#timer = setTimeout(() => {
23988
+ grd.dataManager.viewRecords.reset();
23989
+ grd.dataManager.viewRecords.touch();
23990
+ }, 200);
24024
23991
  };
24025
23992
 
24026
- #getRowIndex = (oRow) => {
24027
- if (typeof oRow === "number") {
24028
- return oRow;
24029
- }
24030
- else if (typeof oRow === "function") {
24031
- return this.#owner.data.findIndex(oRow);
24032
- }
24033
- else {
24034
- throw `invalid ${oRow}`;
24035
- }
23993
+ close = () => {
23994
+ this.col = null;
23995
+ this.style.display = 'none';
24036
23996
  };
24037
-
24038
- #getColIndex = (oCol) => {
24039
- if (typeof oCol === "number") {
24040
- return oCol;
24041
- }
24042
- else if (typeof oCol === "string" && this.#owner.fields.includes(oCol)) {
24043
- return parseInt($(`[data-bind="${oCol}"]`, this.#owner.template)[0].dataset.col);
24044
- }
24045
- else {
24046
- throw `invalid ${oCol}`;
24047
- }
23997
+
23998
+ open = (filterButton) => {
23999
+
24000
+ /** 위치 */
24001
+ const cell = filterButton.closest("th,td");
24002
+
24003
+ const { left: btnLeft } = filterButton.getBoundingClientRect();
24004
+ const { left: ownerLeft, width: ownerWidth, top: ownerTop } = this.#owner.getBoundingClientRect();
24005
+ const { top: cellTop, height: cellHeight } = cell.getBoundingClientRect();
24006
+ const { width: targetWidth } = this.getBoundingClientRect();
24007
+
24008
+ let l = Math.max(0, btnLeft - ownerLeft);
24009
+ l = Math.min(l, ownerWidth - targetWidth - 5);
24010
+
24011
+ const t = cellTop + cellHeight - ownerTop;
24012
+
24013
+ Object.assign(this.style, { left: `${l}px`, top: `${t}px`, display: "flex" });
24014
+ this.classList.add("loading");
24015
+
24016
+ this.shadowRoot.querySelector("input[type=text]").value = "";
24017
+
24018
+ setTimeout(() => {
24019
+ const data = this.#owner.data.getValidDataNF();
24020
+ const col = cell.dataset.col;
24021
+ this.col = col;
24022
+ this.#button = filterButton;
24023
+
24024
+ const ds = filterButton.filterOptions.map((opt, i) => {
24025
+ const groupLabel = `<span class="group">${cell.textContent}${filterButton.filterOptions.length > 1 ? ` #${i + 1} (${opt.colnm})` : ""}</span>`;
24026
+
24027
+ const cellEl = this.#owner.activeTmpl.querySelector(`[data-col="${col}"][data-bind="${opt.colnm}"]`);
24028
+ const expr = cellEl?.getAttribute("data-expr");
24029
+ const exprFunc = expr ? this.#owner.exprFunction(expr) : null;
24030
+
24031
+ const data2 = data.map(rowData => {
24032
+ const idx = this.#owner.fields.indexOf(opt.colnm);
24033
+ return expr ? { v: rowData.v[idx], v2: exprFunc(this.#owner.data.conv(rowData), rowData.__ng.rowidx, this.#owner.data) } : { v: rowData.v[idx], v2: rowData.v[idx] };
24034
+ });
24035
+
24036
+ return [
24037
+ { LVL: 1, CHK: "N", DATA2: groupLabel },
24038
+ ...[...new Set(data2.map(JSON.stringify))]
24039
+ .map(JSON.parse)
24040
+ .sort((a, b) => (a.v2 || "") > (b.v2 || "") ? 1 : (a.v2 || "") < (b.v2 || "") ? -1 : 0)
24041
+ .map(o => ({
24042
+ LVL: 2,
24043
+ DATA: o.v,
24044
+ DATA2: o.v2 || ` <span class="empty">(empty)</span> ${o.v}`,
24045
+ COLNM: opt.colnm,
24046
+ CHK: opt.data.length === 0 || opt.data.nineBinarySearch(o.v || "") >= 0 ? "Y" : "N",
24047
+ }))
24048
+ ];
24049
+ }).flat();
24050
+
24051
+ const grd = this.shadowRoot.querySelector("nine-grid");
24052
+ grd.fields.add(["DATA","DATA2","COLNM"]);
24053
+ grd.data.set(ds);
24054
+
24055
+ // ✅ 데이터 필터링 및 체크 상태 결정
24056
+ const checkbox = this.shadowRoot.querySelector("input[type=checkbox]");
24057
+ checkbox.checked = grd.data.getValidData().every(item => !(item.LVL === 2 && item.CHK !== "Y"));
24058
+
24059
+ this.shadowRoot.querySelector("input").focus();
24060
+ this.classList.remove("loading");
24061
+ });
24048
24062
  };
24049
-
24050
-
24051
-
24052
- moveTo = (at) => {
24053
- this.#owner.scrollTo(at);
24054
- }
24055
24063
  }
24056
24064
 
24065
+
24066
+ customElements.define("ng-filter-button", ngFilterButton);
24067
+ customElements.define("ng-filter-panel", ngFilterPanel);
24068
+
24057
24069
  class ninegridContainer extends HTMLElement
24058
24070
  {
24059
24071
  #colResizer;
@@ -27305,9 +27317,9 @@ class aiMessage extends HTMLElement
27305
27317
  </style>
27306
27318
 
27307
27319
  <div class="chat-message">
27308
- <div title="${this.#message}" class="message">
27320
+ <span title="${this.#message}" class="message">
27309
27321
  ${this.#message}
27310
- </div>
27322
+ </span>
27311
27323
  <span class="more" style="position:relative;"><a href="#">more</a></span>
27312
27324
  </div>
27313
27325
 
@@ -27330,6 +27342,8 @@ class aiMessage extends HTMLElement
27330
27342
  set data(v) {
27331
27343
  console.log(v); }
27332
27344
 
27345
+
27346
+
27333
27347
  #init = () => {
27334
27348
 
27335
27349
  const html = `
@@ -27366,21 +27380,7 @@ class aiMessage extends HTMLElement
27366
27380
  alert(this.#message);
27367
27381
  });
27368
27382
 
27369
- setTimeout(() => {
27370
- const elMessage = this.shadowRoot.querySelector(".message");
27371
- const elMore = this.shadowRoot.querySelector(".more");
27372
-
27373
- console.log(elMessage.scrollWidth, elMessage.clientWidth);
27374
-
27375
- elMore.style.display = "flex";
27376
- //elMore.style.position = "absolute";
27377
-
27378
- console.log(elMessage.scrollWidth, elMessage.clientWidth);
27379
-
27380
- //elMore.style.display = (elMessage.scrollWidth > elMessage.clientWidth) ? "flex" : "none";
27381
- if (elMessage.scrollWidth > elMessage.clientWidth) ;
27382
- }, 100);
27383
-
27383
+ this.shadowRoot.querySelector(".more").style.display = (ninegrid.isEllipsis(this.shadowRoot.querySelector(".message"))) ? "flex" : "none";
27384
27384
  };
27385
27385
  }
27386
27386