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