gnui 1.2.17 → 1.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/js/gnui.esm.js +724 -92
  2. package/dist/js/gnui.js +724 -92
  3. package/dist/js/gnui.min.js +6 -6
  4. package/dist/styles/default.css +1019 -108
  5. package/dist/styles/gpi.css +1019 -108
  6. package/dist/styles/green24.css +1230 -289
  7. package/dist/styles/insights.css +1019 -108
  8. package/dist/styles/nac.css +969 -58
  9. package/dist/styles/ztnac.css +1206 -265
  10. package/package.json +1 -1
  11. package/styleguide/assets/components.js +216 -9
  12. package/styleguide/assets/js/gnui.js +724 -92
  13. package/styleguide/assets/js/gnui.min.js +6 -6
  14. package/styleguide/assets/styles/default.css +1019 -108
  15. package/styleguide/assets/styles/gpi.css +1019 -108
  16. package/styleguide/assets/styles/green24.css +1230 -289
  17. package/styleguide/assets/styles/insights.css +1019 -108
  18. package/styleguide/assets/styles/nac.css +969 -58
  19. package/styleguide/assets/styles/ztnac.css +1206 -265
  20. package/styleguide/category/COLOR/index.html +2 -2
  21. package/styleguide/category/COMPONENT/Alert(js)/index.html +2 -2
  22. package/styleguide/category/COMPONENT/Bignumber/index.html +2 -2
  23. package/styleguide/category/COMPONENT/Breadcrumb/index.html +2 -2
  24. package/styleguide/category/COMPONENT/Calendar(js)/index.html +2 -2
  25. package/styleguide/category/COMPONENT/Card/index.html +2 -2
  26. package/styleguide/category/COMPONENT/Chart(js)/index.html +2 -2
  27. package/styleguide/category/COMPONENT/Datagrid(js)/index.html +136 -9
  28. package/styleguide/category/COMPONENT/Datalist(js)/index.html +2 -2
  29. package/styleguide/category/COMPONENT/Growl(js)/index.html +2 -2
  30. package/styleguide/category/COMPONENT/JsonView(js)/index.html +2 -2
  31. package/styleguide/category/COMPONENT/Loader(js)/index.html +21 -4
  32. package/styleguide/category/COMPONENT/MenuButton(js)/index.html +74 -6
  33. package/styleguide/category/COMPONENT/Message(js)/index.html +2 -2
  34. package/styleguide/category/COMPONENT/Modal(js)/index.html +2 -2
  35. package/styleguide/category/COMPONENT/Pagination(js)/index.html +2 -2
  36. package/styleguide/category/COMPONENT/Panel/index.html +2 -2
  37. package/styleguide/category/COMPONENT/Progressbar(js)/index.html +2 -2
  38. package/styleguide/category/COMPONENT/Tab(js)/index.html +2 -2
  39. package/styleguide/category/COMPONENT/Tagcloud(js)/index.html +2 -2
  40. package/styleguide/category/COMPONENT/Tooltip(js)/index.html +2 -2
  41. package/styleguide/category/COMPONENT/Tree(js)/index.html +2 -2
  42. package/styleguide/category/CONTROLS/Button(js)/index.html +2 -2
  43. package/styleguide/category/CONTROLS/Checkbox/index.html +2 -2
  44. package/styleguide/category/CONTROLS/Colorpicker(js)/index.html +2 -2
  45. package/styleguide/category/CONTROLS/Datepicker(js)/index.html +2 -2
  46. package/styleguide/category/CONTROLS/Dropdown(js)/index.html +2 -2
  47. package/styleguide/category/CONTROLS/File/index.html +2 -2
  48. package/styleguide/category/CONTROLS/Form/Control/index.html +2 -2
  49. package/styleguide/category/CONTROLS/Form/Field/index.html +2 -2
  50. package/styleguide/category/CONTROLS/Form/Plain/index.html +2 -2
  51. package/styleguide/category/CONTROLS/Input/index.html +2 -2
  52. package/styleguide/category/CONTROLS/MultiText(js)/index.html +2 -2
  53. package/styleguide/category/CONTROLS/Picklist(js)/index.html +28 -18
  54. package/styleguide/category/CONTROLS/Radio/index.html +2 -2
  55. package/styleguide/category/CONTROLS/Select/index.html +2 -2
  56. package/styleguide/category/CONTROLS/SelectButton(js)/index.html +2 -2
  57. package/styleguide/category/CONTROLS/Slider/index.html +2 -2
  58. package/styleguide/category/CONTROLS/SortableList(js)/index.html +487 -0
  59. package/styleguide/category/CONTROLS/Switch(js)/index.html +2 -2
  60. package/styleguide/category/CONTROLS/SyntaxInput(js)/index.html +2 -2
  61. package/styleguide/category/CONTROLS/Textarea/index.html +2 -2
  62. package/styleguide/category/CONTROLS/Time(js)/index.html +2 -2
  63. package/styleguide/category/ELEMENTS/Box/index.html +2 -2
  64. package/styleguide/category/ELEMENTS/Icon/index.html +2 -2
  65. package/styleguide/category/ELEMENTS/Image/index.html +2 -2
  66. package/styleguide/category/ELEMENTS/List/index.html +2 -2
  67. package/styleguide/category/ELEMENTS/Table/index.html +2 -2
  68. package/styleguide/category/ELEMENTS/Tag/index.html +2 -2
  69. package/styleguide/category/ELEMENTS/Title/index.html +2 -2
  70. package/styleguide/category/LAYOUT/Container/index.html +2 -2
  71. package/styleguide/category/LAYOUT/Grid/index.html +2 -2
  72. package/styleguide/category/LAYOUT/Splitter(js)/index.html +2 -2
  73. package/styleguide/category/UTILITY/index.html +2 -2
  74. package/styleguide/category/Utils/index.html +2 -2
  75. package/styleguide/color.html +2 -2
  76. package/styleguide/index.html +2 -2
  77. package/styleguide/tag/javascript/index.html +608 -31
  78. package/styleguide/tag/v.0.1.0/index.html +608 -31
package/dist/js/gnui.js CHANGED
@@ -16930,7 +16930,10 @@
16930
16930
  // 이벤트 삭제
16931
16931
  delete this._eventMap[uid];
16932
16932
  }
16933
- dispatch(uid, name, params) {
16933
+ /**
16934
+ * lifeCycle 핸들러 실행
16935
+ */
16936
+ cyclepatch(uid, name, params) {
16934
16937
  const _events = this._getEvent(uid, name);
16935
16938
  if (_events.length) {
16936
16939
  _events.forEach((_event) => {
@@ -16939,6 +16942,28 @@
16939
16942
  });
16940
16943
  }
16941
16944
  }
16945
+ /**
16946
+ * 등록된 이벤트 핸들러 실행
16947
+ *
16948
+ * - sync/async 핸들러를 모두 지원한다.
16949
+ * - 모든 핸들러를 순차 실행한 뒤, 하나라도 `false` 를 반환한 경우 `true`(cancelled)를 반환한다.
16950
+ * (첫 false 에서 중단하지 않고, 나머지 핸들러도 계속 실행한다)
16951
+ */
16952
+ async dispatch(uid, name, params) {
16953
+ const _events = this._getEvent(uid, name);
16954
+ let cancelled = false;
16955
+ if (_events.length) {
16956
+ for (const _event of _events) {
16957
+ const _target = _event.target || this;
16958
+ // sync/async 둘 다 지원: Promise.resolve 로 감싸서 await
16959
+ const result = await Promise.resolve(params ? _event.handler.call(_target, ...params) : _event.handler.call(_target));
16960
+ if (result === false) {
16961
+ cancelled = true;
16962
+ }
16963
+ }
16964
+ }
16965
+ return cancelled;
16966
+ }
16942
16967
  _getEvent(uid, name) {
16943
16968
  // parameters에 해당하는 이벤트 반환
16944
16969
  return this._eventMap[uid]
@@ -17023,7 +17048,7 @@
17023
17048
  });
17024
17049
  return _findComponent;
17025
17050
  }
17026
- // 등록된 컴포넌트 제거
17051
+ // 등록된 컴포넌트 제거 (selector 기반)
17027
17052
  _removeComponent(selector) {
17028
17053
  if (!selector) {
17029
17054
  return;
@@ -17032,10 +17057,30 @@
17032
17057
  Object.values(this._componentMap).forEach(n => {
17033
17058
  // 동일한 selector 인지 비교해서 동일한 component의 selector이면 제거 처리
17034
17059
  if (isEquals(n.selector, _selector)) {
17035
- delete this._componentMap[_selector._uid];
17060
+ // componentMap에서 완전히 제거
17061
+ delete this._componentMap[n.uid];
17062
+ // 컴포넌트 내부 참조도 제거 (메모리 누수 방지)
17063
+ if (n.component) {
17064
+ n.component = null;
17065
+ }
17066
+ n.selector = null;
17036
17067
  }
17037
17068
  });
17038
17069
  }
17070
+ // 등록된 컴포넌트 제거 (uid 기반 - 더 효율적)
17071
+ _removeComponentByUid(uid) {
17072
+ if (!uid || !this._componentMap[uid]) {
17073
+ return;
17074
+ }
17075
+ // 컴포넌트 내부 참조 제거 (메모리 누수 방지)
17076
+ const componentInfo = this._componentMap[uid];
17077
+ if (componentInfo.component) {
17078
+ componentInfo.component = null;
17079
+ }
17080
+ componentInfo.selector = null;
17081
+ // componentMap에서 완전히 제거
17082
+ delete this._componentMap[uid];
17083
+ }
17039
17084
  // 컴포넌트 life cycle에 따른 eventManager dispatch
17040
17085
  _detectedCycle(uid, name) {
17041
17086
  // component 마지막 status 업데이트
@@ -17045,7 +17090,7 @@
17045
17090
  // event manager를 이용해 해당 uid 이벤트 dispatch
17046
17091
  const eventManager = GNCoreEventManager.getInstance();
17047
17092
  // 호출 후
17048
- eventManager.dispatch(uid, name, '');
17093
+ eventManager.cyclepatch(uid, name, '');
17049
17094
  // 이벤트 해제 - life cycle 은 컴포넌트 별로 한번씩만 존재하므로..
17050
17095
  eventManager.remove(uid, name);
17051
17096
  }
@@ -17059,6 +17104,7 @@
17059
17104
  function _removedNode(removed) {
17060
17105
  Array.prototype.forEach.call(removed, (rm) => {
17061
17106
  // 삭제노드 연관 컴포넌트 (ex. tooltip) 삭제
17107
+ var _a;
17062
17108
  const dependents = findAll('[data-gnui]', rm);
17063
17109
  each(dependents, (dependent) => {
17064
17110
  if (isElement$2(dependent)) {
@@ -17072,7 +17118,7 @@
17072
17118
  remove($('#' + attr(rm, 'data-gnui')));
17073
17119
  }
17074
17120
  const findComponent = closerThis._getComponent($(rm));
17075
- if (findComponent && findComponent._uid && !findComponent.$el.parentNode && findComponent.$name !== 'modal') {
17121
+ if (findComponent && findComponent._uid && !((_a = findComponent.$el) === null || _a === void 0 ? void 0 : _a.parentNode) && findComponent.$name !== 'modal') {
17076
17122
  // state manager 에서 component 삭제
17077
17123
  closerThis._removeComponent(rm);
17078
17124
  // event manager 에서 unbind
@@ -17271,25 +17317,48 @@
17271
17317
  }
17272
17318
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
17273
17319
  $update(element = this.$el, e) { }
17274
- $event(component, name, ...params) {
17320
+ /**
17321
+ * 컴포넌트 이벤트 디스패치 헬퍼
17322
+ *
17323
+ * - sync/async 핸들러 모두 지원한다.
17324
+ * - 하나 이상의 핸들러에서 `false`를 반환하면 `true`(cancelled) 를 반환한다.
17325
+ * - 단순 알림용(fire-and-forget) 이벤트는 반환값/await 없이 호출해도 된다.
17326
+ */
17327
+ async $event(component, name, ...params) {
17275
17328
  const eventManager = GNCoreEventManager.getInstance();
17276
- eventManager.dispatch(component._uid, name, params);
17329
+ return eventManager.dispatch(component._uid, name, params);
17277
17330
  }
17278
17331
  $destroy(component = this, removeEl = true) {
17332
+ var _a;
17279
17333
  const stateManager = GNUIState.getInstance();
17280
17334
  // state manager 를 통해 destroy 상태 dispatch
17281
17335
  stateManager._detectedCycle(component._uid, 'destroy');
17282
- // remove Component in state manager
17283
- stateManager._removeComponent(component.$selector);
17284
17336
  // remove DOM (by removeEl)
17285
17337
  if (removeEl) {
17286
17338
  style(component.$el, 'display', 'none');
17287
17339
  remove(component.$el);
17288
17340
  }
17341
+ if (((_a = component.$options) === null || _a === void 0 ? void 0 : _a._destroy) && isFunction(component.$options._destroy)) {
17342
+ component.$options._destroy();
17343
+ }
17289
17344
  // state manager 를 통해 destroy 상태 dispatch
17290
17345
  stateManager._detectedCycle(component._uid, 'destroyed');
17291
- // event manager 에서 등록 해제가 가장 마지막..
17346
+ // event manager 에서 등록 해제
17292
17347
  GNCoreEventManager.getInstance().removeAll(component._uid);
17348
+ // state manager에서 component 제거 (uid 기반으로 효율적 제거)
17349
+ stateManager._removeComponentByUid(component._uid);
17350
+ // 메모리 누수 방지: component의 모든 hasOwnProperty 제거
17351
+ // config, events, methods, _hidden 등 동적으로 추가된 속성 포함
17352
+ Object.keys(component).forEach((key) => {
17353
+ try {
17354
+ component[key] = null;
17355
+ delete component[key];
17356
+ }
17357
+ catch (e) {
17358
+ // readonly 속성 등 삭제 불가능한 경우 무시
17359
+ }
17360
+ });
17361
+ component = null;
17293
17362
  }
17294
17363
  }
17295
17364
 
@@ -33490,8 +33559,19 @@
33490
33559
  });
33491
33560
  if (this.$options.value) {
33492
33561
  if (this.$options.multiple) {
33493
- const values = this.$options.value.split(',');
33494
- this.$options.value = this.$options.flatData.filter((opt) => values.includes(opt.value) && opt.text);
33562
+ // multiple 모드에서 다양한 타입의 value를 문자열 배열로 변환
33563
+ // 지원 타입: 문자열(쉼표 구분), 객체 배열, 단일 객체, 문자열 배열
33564
+ const values = typeof this.$options.value === 'string'
33565
+ ? this.$options.value.split(',') // 케이스 1: 'item1,item2,item3'
33566
+ : Array.isArray(this.$options.value)
33567
+ ? this.$options.value.map((v) => (typeof v === 'object' && v !== null && 'value' in v ? String(v.value) : String(v))) // 케이스 3: [{value:'item1', text:'항목1'}, ...] 또는 ['item1', 'item2']
33568
+ : typeof this.$options.value === 'object' && this.$options.value !== null && 'value' in this.$options.value
33569
+ ? [String(this.$options.value.value)] // 케이스 2: {value:'item1', text:'항목1'}
33570
+ : [String(this.$options.value)]; // 기타: 숫자 등
33571
+ this.$options.value = this.$options.flatData.filter((opt) => {
33572
+ const optValue = typeof opt.value === 'string' ? opt.value : String(opt.value);
33573
+ return values.includes(optValue) && opt.text;
33574
+ });
33495
33575
  }
33496
33576
  else {
33497
33577
  this.$options.value = this.$options.flatData.find((opt) => opt.value + '' === this.$options.value + '' && opt.text);
@@ -37006,26 +37086,8 @@
37006
37086
  }
37007
37087
  this.$event(this, 'onSort', column);
37008
37088
  },
37009
- renderHeader: (columns) => {
37010
- this.$options.hasOrder &&
37011
- !this.$options.readonly &&
37012
- columns.push({
37013
- label: this.$options.textSets.orderLabel,
37014
- key: 'btnOrder',
37015
- style: {
37016
- width: '50px'
37017
- }
37018
- });
37019
- this.$options.hasDelete &&
37020
- !this.$options.readonly &&
37021
- columns.push({
37022
- label: this.$options.textSets.deleteLabel,
37023
- key: 'btnDelete',
37024
- style: {
37025
- width: '30px'
37026
- }
37027
- });
37028
- this._setColumnsTemplate();
37089
+ renderHeader: (columns, isReset = false) => {
37090
+ this._setColumnsTemplate(isReset);
37029
37091
  return (createElement$1("div", { className: "gn-datagrid-header-row", style: {
37030
37092
  'grid-template-columns': this._columnsTemplate.join(' ')
37031
37093
  } },
@@ -37064,6 +37126,10 @@
37064
37126
  column.draggable && (this.$options.headers ? idx < this.$options.headers.length - 1 : true) && createElement$1("span", { className: "is-handle", "data-index": idx })));
37065
37127
  },
37066
37128
  renderBody: (data, columns) => {
37129
+ // 헤더가 숨겨진 경우에도 body 렌더 전에 템플릿 폭을 준비한다
37130
+ if (!this._columnsTemplate || !this._columnsTemplate.length) {
37131
+ this._setColumnsTemplate();
37132
+ }
37067
37133
  rowIdx$1 = 0;
37068
37134
  return (createElement$1("div", { className: "gn-datagrid-body", style: {
37069
37135
  maxHeight: this.$options.bodyHeight ? this.$options.bodyHeight : 'auto'
@@ -37077,7 +37143,6 @@
37077
37143
  });
37078
37144
  },
37079
37145
  renderRow: (row, columns, depth = 0, hasChild, isOpened, isCheck = false) => {
37080
- row._depth = depth;
37081
37146
  const _index = rowIdx$1++;
37082
37147
  if (row.isChecked) {
37083
37148
  isCheck = true;
@@ -37211,8 +37276,9 @@
37211
37276
  e.stopPropagation();
37212
37277
  toggler = parents(e.currentTarget, '.gn-datagrid-body-row');
37213
37278
  }
37214
- const children = nextUntil(toggler, '.gn-datagrid-body-row[data-depth="' + row._depth + '"]').filter((x) => {
37215
- return x.dataset.depth > row._depth;
37279
+ const rowDepth = Number(attr(toggler, 'data-depth')) || 0;
37280
+ const children = nextUntil(toggler, '.gn-datagrid-body-row[data-depth="' + rowDepth + '"]').filter((x) => {
37281
+ return Number(attr(x, 'data-depth')) > rowDepth;
37216
37282
  });
37217
37283
  type = type ? type : hasClass(toggler, 'is-collapsed') ? 'expand' : 'collapse';
37218
37284
  if (type === 'collapse') {
@@ -37228,7 +37294,7 @@
37228
37294
  //show childs
37229
37295
  removeClass(toggler, 'is-collapsed');
37230
37296
  removeClass(children.filter((x) => {
37231
- return x.dataset.depth == row._depth + 1;
37297
+ return Number(attr(x, 'data-depth')) == rowDepth + 1;
37232
37298
  }), 'is-hidden');
37233
37299
  this.$event(this, 'onToggle', 'expanded', row, index$1(toggler));
37234
37300
  }
@@ -37286,13 +37352,15 @@
37286
37352
  e.stopPropagation();
37287
37353
  const checker = parents(e.currentTarget, '.gn-datagrid-body-row');
37288
37354
  const checkerState = e.target.checked;
37289
- find('.is-allChecker', this.$el).checked = false;
37355
+ const allChecker = find('.is-allChecker', this.$el);
37356
+ allChecker && (allChecker.checked = false);
37357
+ const rowDepth = Number(attr(checker, 'data-depth')) || 0;
37290
37358
  // 1. row에 자식노드가 있는지 확인한다.
37291
37359
  if (this.$options.checkCapturing && row[this.$options.childField] && row[this.$options.childField].length) {
37292
37360
  // 2. 자식노드가 있는경우 자식 체크박스도 함께 토글한다.
37293
- nextUntil(checker, '.gn-datagrid-body-row[data-depth="' + row._depth + '"]')
37361
+ nextUntil(checker, '.gn-datagrid-body-row[data-depth="' + rowDepth + '"]')
37294
37362
  .filter((x) => {
37295
- return x.dataset.depth > row._depth;
37363
+ return Number(attr(x, 'data-depth')) > rowDepth;
37296
37364
  })
37297
37365
  .forEach((x) => {
37298
37366
  const _checker = find('.is-rowChecker', x);
@@ -37302,17 +37370,17 @@
37302
37370
  });
37303
37371
  }
37304
37372
  // 3. 체크 해제인 경우만 부모노드가 있는지 확인한다.
37305
- if (this.$options.checkCapturing && row._depth > 0 && !checkerState) {
37373
+ if (this.$options.checkCapturing && rowDepth > 0 && !checkerState) {
37306
37374
  // 4. 부모노드가 체크되어 있는지 확인한다
37307
37375
  const exeDepth = [];
37308
37376
  prevUntil(checker, '.gn-datagrid-body-row[data-depth="0"]')
37309
37377
  .filter((x) => {
37310
- const _thisDepth = x.dataset.depth;
37378
+ const _thisDepth = attr(x, 'data-depth');
37311
37379
  if (exeDepth.includes(_thisDepth)) {
37312
37380
  return false;
37313
37381
  }
37314
37382
  exeDepth.push(_thisDepth);
37315
- return _thisDepth < row._depth;
37383
+ return Number(_thisDepth) < rowDepth;
37316
37384
  })
37317
37385
  .forEach((x) => {
37318
37386
  const _checker = find('.is-rowChecker', x);
@@ -37328,9 +37396,21 @@
37328
37396
  if (hasCheck === undefined) {
37329
37397
  hasCheck = this.$options.hasCheck;
37330
37398
  }
37331
- this.$options.headers = headers;
37399
+ const prevHeaders = [...(this.$options.headers || [])];
37400
+ // _prepareHeaders가 배열을 변경하므로 비교용(prevHeaders)과 가공용(baseHeaders)을 분리한다
37401
+ const baseHeaders = headers ? [...headers] : [...prevHeaders];
37402
+ const preparedHeaders = this._prepareHeaders(baseHeaders);
37403
+ // 헤더 배열의 내용(길이, key)이 바뀐 경우에만 DOM 기반 폭 계산을 리셋한다
37404
+ const isReset = prevHeaders.length !== preparedHeaders.length || prevHeaders.some((header, idx) => { var _a; return header.key !== ((_a = preparedHeaders[idx]) === null || _a === void 0 ? void 0 : _a.key); });
37332
37405
  this.$options.hasCheck = hasCheck;
37333
- this.$template.reRender(find('.gn-datagrid-header-row', this.$el), this._hidden.renderHeader(this.$options.headers));
37406
+ const headerRow = find('.gn-datagrid-header-row', this.$el);
37407
+ // 헤더가 없을 때도 컬럼 수/폭 변경을 반영하기 위해 템플릿을 갱신한다
37408
+ if (this.$options.hasHeader && headerRow) {
37409
+ this.$template.reRender(headerRow, this._hidden.renderHeader(preparedHeaders, isReset));
37410
+ }
37411
+ else {
37412
+ this._setColumnsTemplate(isReset);
37413
+ }
37334
37414
  this._hidden.resetData(data ? arrClone(data) : this.$options.data);
37335
37415
  this.$render(this.$options);
37336
37416
  isFunction(resolve) && resolve();
@@ -37345,6 +37425,10 @@
37345
37425
  });
37346
37426
  },
37347
37427
  awaitData: (data) => {
37428
+ // asyncData 콜백 실행 중 컴포넌트가 destroy된 경우 안전하게 종료
37429
+ if (!this.$options || !this._hidden) {
37430
+ return;
37431
+ }
37348
37432
  if (this.$options.asyncData && this.$options.paginator && !this._paginator) {
37349
37433
  this._paginator = new Pagination('pagination', find('.gn-datagrid-footer', this.$el), {
37350
37434
  total: this.$options.paginator.total || 0,
@@ -37369,7 +37453,8 @@
37369
37453
  this._fixCellStyleOnDraggable();
37370
37454
  // 체크박스가 있는경우 전체 체크항목을 해제해준다
37371
37455
  if (this.$options.hasCheck) {
37372
- find('.is-allChecker', this.$el).checked = false;
37456
+ const allChecker = find('.is-allChecker', this.$el);
37457
+ allChecker && (allChecker.checked = false);
37373
37458
  }
37374
37459
  isFunction(resolve) && resolve();
37375
37460
  });
@@ -37404,8 +37489,18 @@
37404
37489
  stopRowSelectEvent: (e) => {
37405
37490
  e.stopPropagation();
37406
37491
  },
37407
- deleteRow: (index) => {
37408
- this.$options.data = this.$options.data.filter((_data, idx) => index !== idx);
37492
+ deleteRow: async (index) => {
37493
+ var _a;
37494
+ const confirmMessage = (_a = this.$options.textSets) === null || _a === void 0 ? void 0 : _a.deleteConfirmMessage;
37495
+ if (confirmMessage && !window.confirm(confirmMessage)) {
37496
+ return;
37497
+ }
37498
+ const removedData = this._hidden.findData(index);
37499
+ const cancelled = await this.$event(this, 'onDelete', removedData, index);
37500
+ if (cancelled) {
37501
+ return;
37502
+ }
37503
+ this._hidden.deleteData(index);
37409
37504
  this._hidden.resetData(this.$options.data);
37410
37505
  this.$event(this, 'onChange', this.$options.data);
37411
37506
  },
@@ -37440,22 +37535,38 @@
37440
37535
  col.offHover && col.offHover.call(this, row, col, index, e);
37441
37536
  },
37442
37537
  findData: (index) => {
37443
- let deter = 0, indexData = null;
37444
- const findIndex = (datas, index) => {
37445
- return datas.some((data) => {
37446
- if (index === deter) {
37447
- indexData = data;
37538
+ return this._hidden.walkByIndex(index, 'find');
37539
+ },
37540
+ deleteData: (index) => {
37541
+ return this._hidden.walkByIndex(index, 'delete');
37542
+ },
37543
+ walkByIndex: (index, action) => {
37544
+ let deter = 0;
37545
+ let result = null;
37546
+ const findIndex = (datas) => {
37547
+ for (let i = 0; i < datas.length; i++) {
37548
+ if (deter === index) {
37549
+ switch (action) {
37550
+ case 'find':
37551
+ result = datas[i];
37552
+ break;
37553
+ case 'delete':
37554
+ result = datas.splice(i, 1)[0];
37555
+ break;
37556
+ }
37448
37557
  return true;
37449
37558
  }
37450
- ++deter;
37451
- if (isArray$1(data[this.$options.childField]) && data[this.$options.childField].length) {
37452
- return findIndex(data[this.$options.childField], index);
37559
+ deter++;
37560
+ const children = datas[i][this.$options.childField];
37561
+ if (Array.isArray(children) && children.length) {
37562
+ if (findIndex(children))
37563
+ return true;
37453
37564
  }
37454
- return false;
37455
- });
37565
+ }
37566
+ return false;
37456
37567
  };
37457
- findIndex(this.$options.data, index);
37458
- return indexData;
37568
+ findIndex(this.$options.data);
37569
+ return result;
37459
37570
  },
37460
37571
  getChecked: () => {
37461
37572
  return findAll('.is-rowChecker', this.$el)
@@ -37542,11 +37653,13 @@
37542
37653
  hasOrder: false,
37543
37654
  hasDelete: false,
37544
37655
  isEllipsis: false,
37656
+ hasHeader: true,
37545
37657
  data: [],
37546
37658
  textSets: {
37547
37659
  noData: 'No records available.',
37548
37660
  orderLabel: '',
37549
- deleteLabel: ''
37661
+ deleteLabel: '',
37662
+ deleteConfirmMessage: ''
37550
37663
  },
37551
37664
  childField: 'child',
37552
37665
  checkCapturing: true,
@@ -37564,7 +37677,8 @@
37564
37677
  onCheck: true,
37565
37678
  onDoubleClick: true,
37566
37679
  onChange: true,
37567
- onDragEnd: true
37680
+ onDragEnd: true,
37681
+ onDelete: true
37568
37682
  };
37569
37683
  this.methods = {
37570
37684
  reRender(options) {
@@ -37624,18 +37738,21 @@
37624
37738
  this.$selector = this.$selector;
37625
37739
  this.$init(this, options);
37626
37740
  }
37627
- _setColumnsTemplate() {
37741
+ _setColumnsTemplate(isReset = false) {
37628
37742
  // header cell의 각 넓이를 배열로 가져온다
37629
37743
  // ! 모든 컬럼의 넓이가 지정된 경우 이동(btnOrder), 삭제(btnDelete) 컬럼을 제외한 마지막 컬럼은 1fr로 고정
37630
37744
  const _isfixedAllWidth = this.$options.headers.every((header) => { var _a; return ((_a = header.style) === null || _a === void 0 ? void 0 : _a.width) !== undefined; });
37631
37745
  const _fixedTemplateColumn = this.$options.headers.findLast((header) => !this._isSystemAddedColumn(header.key) && !header.isHidden);
37746
+ // 헤더 DOM이 없거나 모든 일반 컬럼이 숨겨진 경우에도 안전하게 비교할 수 있도록 key만 옵셔널하게 캐싱한다
37747
+ const _fixedTemplateKey = _fixedTemplateColumn === null || _fixedTemplateColumn === void 0 ? void 0 : _fixedTemplateColumn.key;
37632
37748
  const columns = findAll('.gn-datagrid-header-cell', this.$el);
37633
- if (this.$el && columns.length) {
37749
+ // isReset이면 기존 DOM 폭을 재사용하지 않고 헤더 정의로 재계산
37750
+ if (this.$el && columns.length && !isReset) {
37634
37751
  this._columnsTemplate = findAll('.gn-datagrid-header-cell', this.$el).map((header, idx) => {
37635
37752
  if (this.$options.headers[idx].isHidden) {
37636
37753
  return '';
37637
37754
  }
37638
- else if (_isfixedAllWidth && this.$options.headers[idx].key === _fixedTemplateColumn.key) {
37755
+ else if (_isfixedAllWidth && _fixedTemplateKey && this.$options.headers[idx].key === _fixedTemplateKey) {
37639
37756
  return '1fr';
37640
37757
  }
37641
37758
  else {
@@ -37649,7 +37766,7 @@
37649
37766
  if (header.isHidden) {
37650
37767
  return '';
37651
37768
  }
37652
- else if (_isfixedAllWidth && header.key === _fixedTemplateColumn.key) {
37769
+ else if (_isfixedAllWidth && _fixedTemplateKey && header.key === _fixedTemplateKey) {
37653
37770
  return '1fr';
37654
37771
  }
37655
37772
  else {
@@ -37670,18 +37787,45 @@
37670
37787
  _isSystemAddedColumn(key) {
37671
37788
  return ['btnOrder', 'btnDelete'].includes(key);
37672
37789
  }
37790
+ // 옵션에 따른 추가 해더 구성
37791
+ _prepareHeaders(headers = []) {
37792
+ const hasOrderColumn = headers.some((header) => header.key === 'btnOrder');
37793
+ const hasDeleteColumn = headers.some((header) => header.key === 'btnDelete');
37794
+ if (this.$options.hasOrder && !this.$options.readonly && !hasOrderColumn) {
37795
+ headers.push({
37796
+ label: this.$options.textSets.orderLabel,
37797
+ key: 'btnOrder',
37798
+ style: {
37799
+ width: '50px'
37800
+ }
37801
+ });
37802
+ }
37803
+ if (this.$options.hasDelete && !this.$options.readonly && !hasDeleteColumn) {
37804
+ headers.push({
37805
+ label: this.$options.textSets.deleteLabel,
37806
+ key: 'btnDelete',
37807
+ style: {
37808
+ width: '30px'
37809
+ }
37810
+ });
37811
+ }
37812
+ this.$options.headers = headers;
37813
+ return headers;
37814
+ }
37673
37815
  template(config) {
37674
37816
  const styles = {};
37817
+ const headers = this._prepareHeaders(config.headers);
37675
37818
  return (createElement$1("div", { id: this._uid, className: 'gn-datagrid' +
37676
37819
  (config.style ? ' is-' + config.style : '') +
37677
37820
  (config.isEllipsis ? ' is-ellipsis' : '') +
37678
37821
  (config.bodyHeight ? ' has-fixed-body' : '') +
37679
37822
  (config.fixHeader ? ' has-fixed-header' : '') +
37680
37823
  (config.fixFooter ? ' has-fixed-footer' : '') +
37824
+ (!config.hasHeader ? ' is-headless' : '') +
37681
37825
  (config.data.some((d) => isArray$1(d[this.$options.childField])) ? ' has-left-padding' : '') +
37682
37826
  (config.disabled ? ' is-disabled' : ''), style: styles },
37683
- createElement$1("div", { className: "gn-datagrid-header" }, this._hidden.renderHeader(config.headers)),
37684
- createElement$1("div", { className: "gn-datagrid-contents", style: { marginTop: this.$options.bodyTopMargin ? this.$options.bodyTopMargin : '0', marginBottom: this.$options.bodyBottomMargin ? this.$options.bodyBottomMargin : '0' } }, this._hidden.renderBody(arrClone(config.data), config.headers)),
37827
+ config.hasHeader && createElement$1("div", { className: "gn-datagrid-header" }, this._hidden.renderHeader(headers)),
37828
+ createElement$1("div", { className: "gn-datagrid-contents", style: { marginTop: this.$options.bodyTopMargin ? this.$options.bodyTopMargin : '0', marginBottom: this.$options.bodyBottomMargin ? this.$options.bodyBottomMargin : '0' } }, this._hidden.renderBody(arrClone(config.data), headers)),
37685
37829
  config.paginator /* 페이지네이터 옵션 확인 */ && createElement$1("div", { className: "gn-datagrid-footer" })));
37686
37830
  }
37687
37831
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -37698,18 +37842,20 @@
37698
37842
  }
37699
37843
  }
37700
37844
  completed() {
37701
- if (this.$options.fixHeader) {
37845
+ if (this.$options.fixHeader && this.$options.hasHeader) {
37702
37846
  const body = find('.gn-datagrid-contents', this.$el);
37703
37847
  const header = find('.gn-datagrid-header', this.$el);
37704
- const _offset = offset(header);
37705
- this.$options.bodyTopMargin = _offset.height ? _offset.height - 1 + 'px' : '2.4rem';
37706
- css$1(body, 'margin-top', this.$options.bodyTopMargin);
37848
+ if (header) {
37849
+ const _offset = offset(header);
37850
+ this.$options.bodyTopMargin = _offset.height ? _offset.height - 1 + 'px' : '2.4rem';
37851
+ css$1(body, 'margin-top', this.$options.bodyTopMargin);
37852
+ }
37707
37853
  if (this.$options.paginator) {
37708
37854
  this.$options.bodyBottomMargin = '2.4rem';
37709
37855
  css$1(body, 'margin-bottom', this.$options.bodyBottomMargin);
37710
37856
  }
37711
37857
  }
37712
- if (this.$options.fixHeader || this.$options.bodyHeight) {
37858
+ if ((this.$options.fixHeader && this.$options.hasHeader) || this.$options.bodyHeight) {
37713
37859
  this._hidden.setBlankHeader();
37714
37860
  on(window, 'resize', this._hidden.setBlankHeader);
37715
37861
  }
@@ -39054,7 +39200,9 @@
39054
39200
  super(name, selector, options);
39055
39201
  this._hidden = {
39056
39202
  open: () => {
39057
- addClass(this.$el, 'is-open');
39203
+ if (!this.$options.disabled) {
39204
+ addClass(this.$el, 'is-open');
39205
+ }
39058
39206
  },
39059
39207
  close: () => {
39060
39208
  removeClass(this.$el, 'is-open');
@@ -39066,6 +39214,62 @@
39066
39214
  changeText: (buttonText) => {
39067
39215
  this.$options.textSets.buttonText = buttonText;
39068
39216
  html(find('.menuButton-text', this.$el), buttonText);
39217
+ },
39218
+ disable: () => {
39219
+ this.$options.disabled = true;
39220
+ const buttonEl = find('button', this.$el);
39221
+ if (buttonEl) {
39222
+ attr(buttonEl, 'disabled', true);
39223
+ }
39224
+ addClass(this.$el, 'is-disabled');
39225
+ this._hidden.close();
39226
+ },
39227
+ enable: () => {
39228
+ this.$options.disabled = false;
39229
+ const buttonEl = find('button', this.$el);
39230
+ if (buttonEl) {
39231
+ removeAttr(buttonEl, 'disabled');
39232
+ }
39233
+ removeClass(this.$el, 'is-disabled');
39234
+ },
39235
+ renderMenus: (menus, depth = 0, parentPath = '') => {
39236
+ return (createElement$1("ul", { className: depth > 0 ? 'menuButton-submenu' : '' }, menus.map((menu, index) => {
39237
+ const hasChild = menu.child && isArray$1(menu.child) && menu.child.length > 0;
39238
+ const hasHtml = !!menu.html;
39239
+ // html이 있으면 innerHTML이 모든 자식 요소를 덮어쓰므로 서브메뉴를 렌더링하지 않음
39240
+ const canRenderChild = hasChild && depth < 2 && !hasHtml; // 최대 2단계까지만 허용
39241
+ const isDisabled = menu.disabled === true;
39242
+ const isActived = menu.actived === true;
39243
+ // 부모 경로를 포함한 고유한 ID 생성
39244
+ const currentPath = parentPath ? `${parentPath}-${index}` : `${index}`;
39245
+ const uniqueId = `${this._uid}-${currentPath}`;
39246
+ return (createElement$1("li", { id: uniqueId, className: 'menuButton-menu' +
39247
+ (this.$options.align ? ' has-text-' + this.$options.align : '') +
39248
+ (canRenderChild ? ' has-submenu' : '') +
39249
+ (depth > 0 ? ' is-submenu-item' : '') +
39250
+ (isDisabled ? ' is-disabled' : '') +
39251
+ (isActived ? ' is-actived' : ''), "on-click": (e) => {
39252
+ // disabled 상태이거나 자식 메뉴가 있는 경우 클릭 이벤트 처리하지 않음
39253
+ if (isDisabled) {
39254
+ e.stopPropagation();
39255
+ e.preventDefault();
39256
+ return;
39257
+ }
39258
+ // 자식 메뉴가 없는 경우에만 select 이벤트 발생
39259
+ if (!canRenderChild) {
39260
+ e.stopPropagation();
39261
+ this._hidden.select.call(this, menu, e);
39262
+ }
39263
+ }, innerHTML: hasHtml ? menu.html : '' },
39264
+ hasHtml ? ('') : (createElement$1("span", { className: "menuButton-menu-content" },
39265
+ createElement$1("span", { className: "menuButton-menu-text" }, menu.text),
39266
+ canRenderChild && (createElement$1("span", { className: "menuButton-menu-arrow" },
39267
+ createElement$1("i", { className: "fas fa-caret-right" }))))),
39268
+ canRenderChild && this._hidden.renderMenus.call(this, menu.child, depth + 1, currentPath)));
39269
+ })));
39270
+ },
39271
+ renderSub: (data) => {
39272
+ return createElement$1("div", null, isArray$1(data) && data.length && isArray$1(data[0]) ? data.map((menus) => this._hidden.renderMenus.call(this, menus)) : this._hidden.renderMenus.call(this, data));
39069
39273
  }
39070
39274
  };
39071
39275
  this.config = {
@@ -39085,6 +39289,19 @@
39085
39289
  },
39086
39290
  buttonText(text) {
39087
39291
  this._hidden.changeText(text);
39292
+ },
39293
+ reRender(data) {
39294
+ this.$options.data = data;
39295
+ const menuMenusEl = find('.menuButton-menus > div', this.$el);
39296
+ if (menuMenusEl && this.$template) {
39297
+ this.$template.reRender(menuMenusEl, this._hidden.renderSub.call(this, data));
39298
+ }
39299
+ },
39300
+ disabled() {
39301
+ this._hidden.disable();
39302
+ },
39303
+ enabled() {
39304
+ this._hidden.enable();
39088
39305
  }
39089
39306
  };
39090
39307
  this.$selector = this.$selector;
@@ -39095,23 +39312,19 @@
39095
39312
  if (config.width) {
39096
39313
  styles.width = getUnit('width', config.width);
39097
39314
  }
39098
- const renderMenus = (menus) => {
39099
- return (createElement$1("ul", null, menus.map((menu, index) => (createElement$1("li", { id: this._uid + '-' + index, className: 'menuButton-menu' + (config.align ? ' has-text-' + config.align : ''), "on-click": (e) => {
39100
- this._hidden.select.call(this, menu, e);
39101
- }, innerHTML: menu.html ? menu.html : '' }, menu.html ? '' : menu.text)))));
39102
- };
39103
- const renderSub = (data) => {
39104
- return createElement$1("div", null, isArray$1(data) && data.length && isArray$1(data[0]) ? data.map((menus) => renderMenus(menus)) : renderMenus(data));
39105
- };
39106
- return (createElement$1("div", { id: this._uid, className: 'gn-menuButton' + (config.color ? ' is-' + config.color : '') + (config.style ? ' is-' + config.style : '') + (config.size ? ' is-' + config.size : ''), style: styles },
39107
- createElement$1("button", { type: "button", className: config.align ? 'has-text-' + config.align : '', "on-click": this._hidden.open },
39315
+ return (createElement$1("div", { id: this._uid, className: 'gn-menuButton' +
39316
+ (config.color ? ' is-' + config.color : '') +
39317
+ (config.style ? ' is-' + config.style : '') +
39318
+ (config.size ? ' is-' + config.size : '') +
39319
+ (config.disabled ? ' is-disabled' : ''), style: styles },
39320
+ createElement$1("button", { type: "button", className: config.align ? 'has-text-' + config.align : '', disabled: config.disabled, "on-click": this._hidden.open },
39108
39321
  config.icon && (createElement$1("span", { className: 'gn-icon is-' + (config.size === 'large' ? 'medium' : config.size === 'medium' ? 'normal' : 'small') },
39109
39322
  createElement$1("i", { className: 'fas fa-' + config.icon }),
39110
39323
  ' ')),
39111
39324
  createElement$1("span", { className: "gn-icon is-small menuButton-icon" },
39112
39325
  createElement$1("i", { className: "fas fa-caret-down" })),
39113
39326
  createElement$1("span", { className: "menuButton-text" }, config.textSets.buttonText)),
39114
- createElement$1("div", { className: "menuButton-menus" }, renderSub(config.data))));
39327
+ createElement$1("div", { className: "menuButton-menus" }, this._hidden.renderSub.call(this, config.data))));
39115
39328
  }
39116
39329
  completed() {
39117
39330
  // 해당 컴포넌트 외 클릭 시 menu panel 숨김
@@ -39322,6 +39535,7 @@
39322
39535
  }
39323
39536
  }
39324
39537
 
39538
+ library$1.add(icons$1, icons);
39325
39539
  class Picklist extends GNCoreInstance {
39326
39540
  constructor(name, selector, options = {}) {
39327
39541
  super(name, selector, options);
@@ -39463,17 +39677,26 @@
39463
39677
  },
39464
39678
  renderSub: (item) => {
39465
39679
  const items = this.$options.data[item] || [];
39466
- return (createElement$1("ul", null, items.map((option, index) => (createElement$1("li", { id: this._uid + '_opt_' + index, className: 'dropdown-item' + (option.selected ? ' is-active' : ''), "data-value": option.value, "on-click": this._hidden.toggle.bind(this), "on-dblclick": this._hidden.move.bind(this, item === 'source' ? 'add' : 'remove', [
39467
- {
39468
- value: option.value,
39469
- text: option.text
39470
- }
39471
- ]) },
39472
- createElement$1("span", { className: "dropdown-text" }, option.text))))));
39680
+ return (createElement$1("ul", null, items.map((option, index) => {
39681
+ var _a;
39682
+ return (createElement$1("li", { id: this._uid + '_opt_' + index, className: 'dropdown-item' + (option.selected ? ' is-active' : ''), "data-value": option.value, "on-click": !option.text ? null : this._hidden.toggle.bind(this), "on-dblclick": !option.text
39683
+ ? null
39684
+ : this._hidden.move.bind(this, item === 'source' ? 'add' : 'remove', [
39685
+ {
39686
+ value: option.value,
39687
+ text: option.text,
39688
+ html: (_a = option.html) !== null && _a !== void 0 ? _a : null
39689
+ }
39690
+ ]) },
39691
+ createElement$1("span", { className: "dropdown-text", innerHTML: option.html ? option.html : '' }, option.html ? ('') : option.icon ? (createElement$1("span", null,
39692
+ createElement$1("span", { className: 'gn-icon' + (this.$options.size ? ' is-' + this.$options.size : '') },
39693
+ createElement$1("i", { className: (this.isBrandIcon(option.icon) ? 'fab' : 'fa') + ` fa-${option.icon}` })),
39694
+ escapeEntity(option.text))) : (escapeEntity(option.text)))));
39695
+ })));
39473
39696
  },
39474
39697
  getSelection: (target) => {
39475
39698
  return findAll('.is-active', this.$options.delegates[target]).map((select) => {
39476
- return { value: attr(select, 'data-value'), text: text$1(select) };
39699
+ return this.$options.data[target].find((option) => option.value === attr(select, 'data-value'));
39477
39700
  });
39478
39701
  },
39479
39702
  disable: () => {
@@ -39633,6 +39856,11 @@
39633
39856
  style(find('.picklist-target .dropdown-items', this.$el), 'height', getUnit('height', getNumber(this.$options.height) - getNumber(style(find('.picklist-target .picklist-caption', this.$el), 'height'))));
39634
39857
  }
39635
39858
  }
39859
+ isBrandIcon(iconName) {
39860
+ const iconLookup = { prefix: 'fab', iconName: iconName };
39861
+ const iconDefinition = findIconDefinition$1(iconLookup);
39862
+ return iconDefinition !== undefined;
39863
+ }
39636
39864
  }
39637
39865
 
39638
39866
  class Progressbar extends GNCoreInstance {
@@ -39791,6 +40019,409 @@
39791
40019
  }
39792
40020
  }
39793
40021
 
40022
+ const CLASS_DRAGGING = 'is-dragging';
40023
+ const CLASS_GROUP_DRAGGING = 'is-group-dragging';
40024
+ const CLASS_DRAG_OVER_TOP = 'is-drag-over-top';
40025
+ const CLASS_DRAG_OVER_BOTTOM = 'is-drag-over-bottom';
40026
+ const DEFAULT_NO_DATA = 'No records available.';
40027
+ const DRAG_STATE_CLASSES = [CLASS_DRAGGING, CLASS_GROUP_DRAGGING, CLASS_DRAG_OVER_TOP, CLASS_DRAG_OVER_BOTTOM];
40028
+ class SortableList extends GNCoreInstance {
40029
+ constructor(name, selector, options = {}) {
40030
+ super(name, selector, options);
40031
+ this._dragging = null;
40032
+ this._selection = new Set();
40033
+ this.DRAG_IMAGE_OFFSET_X = 0;
40034
+ this.DRAG_IMAGE_OFFSET_Y = 0;
40035
+ this.DRAG_OVER_THRESHOLD_RATIO = 0.5;
40036
+ this._hidden = {
40037
+ toggle: (e) => {
40038
+ if (this.$options.disabled) {
40039
+ return;
40040
+ }
40041
+ const value = attr(e.currentTarget, 'data-value');
40042
+ const hasItem = this.$options.data.some((option) => option.value === value);
40043
+ if (!value || !hasItem) {
40044
+ return;
40045
+ }
40046
+ if (this._selection.has(value)) {
40047
+ this._selection.delete(value);
40048
+ removeClass(e.currentTarget, 'is-active');
40049
+ }
40050
+ else {
40051
+ this._selection.add(value);
40052
+ addClass(e.currentTarget, 'is-active');
40053
+ }
40054
+ },
40055
+ sort: (dir) => {
40056
+ const items = this.$options.data || [];
40057
+ const selected = this._hidden.getSelection();
40058
+ if (!selected.length || selected.length === items.length) {
40059
+ return;
40060
+ }
40061
+ const selectedValues = new Set(selected.map((item) => item.value));
40062
+ // dir: up/down → 이동, up-all/down-all → 맨 위/맨 아래로 보내기
40063
+ if (dir.indexOf('all') > -1) {
40064
+ this.$options.data = items.slice().sort((a, b) => {
40065
+ const _sort = dir === 'up-all' ? -1 : 1;
40066
+ const aSel = selectedValues.has(a.value);
40067
+ const bSel = selectedValues.has(b.value);
40068
+ if (aSel && bSel) {
40069
+ return 0;
40070
+ }
40071
+ else if (aSel) {
40072
+ return _sort;
40073
+ }
40074
+ else if (bSel) {
40075
+ return _sort * -1;
40076
+ }
40077
+ return 0;
40078
+ });
40079
+ }
40080
+ else {
40081
+ if (dir === 'up') {
40082
+ const reordered = items.slice();
40083
+ // 한 번에 한 칸씩만 올려 상대 순서를 유지한다
40084
+ for (let i = 1; i < reordered.length; i++) {
40085
+ if (!selectedValues.has(reordered[i].value) || selectedValues.has(reordered[i - 1].value)) {
40086
+ continue;
40087
+ }
40088
+ const temp = reordered[i - 1];
40089
+ reordered[i - 1] = reordered[i];
40090
+ reordered[i] = temp;
40091
+ }
40092
+ this.$options.data = reordered;
40093
+ }
40094
+ else {
40095
+ let reordered = [];
40096
+ let itemsToMoveDown = [];
40097
+ items.forEach((option) => {
40098
+ // reordered: 최종 순서를 쌓는 버퍼, itemsToMoveDown: 아래로 밀어야 할 선택 항목 임시 저장
40099
+ // 비선택 항목은 흐름대로 push, 선택 항목은 dir에 따라 위/아래로 밀어 넣음
40100
+ if (!selectedValues.has(option.value)) {
40101
+ reordered.push(option);
40102
+ if (itemsToMoveDown.length) {
40103
+ reordered = reordered.concat(itemsToMoveDown);
40104
+ itemsToMoveDown = [];
40105
+ }
40106
+ }
40107
+ else if (dir === 'down') {
40108
+ itemsToMoveDown.push(option);
40109
+ }
40110
+ });
40111
+ if (itemsToMoveDown.length) {
40112
+ reordered = reordered.concat(itemsToMoveDown);
40113
+ itemsToMoveDown = [];
40114
+ }
40115
+ this.$options.data = reordered.slice();
40116
+ }
40117
+ }
40118
+ this._hidden.reRender();
40119
+ this._hidden.updateControls();
40120
+ this.$event(this, 'onChange', this.$options.data);
40121
+ },
40122
+ renderSub: () => {
40123
+ var _a, _b;
40124
+ const items = this.$options.data || [];
40125
+ const hasCols = items.some((item) => isArray$1(item.cols) && item.cols.length);
40126
+ if (!items.length) {
40127
+ return (createElement$1("ul", { className: "sortablelist-rows" },
40128
+ createElement$1("li", { className: "dropdown-item is-empty" }, (_b = (_a = this.$options.textSets) === null || _a === void 0 ? void 0 : _a.noData) !== null && _b !== void 0 ? _b : DEFAULT_NO_DATA)));
40129
+ }
40130
+ return (createElement$1("ul", { className: 'sortablelist-rows' + (hasCols ? ' is-cols' : '') }, items.map((option, index) => {
40131
+ var _a;
40132
+ return (createElement$1("li", { id: this._uid + '_opt_' + index, className: 'dropdown-item' + (this._selection.has(option.value) ? ' is-active' : ''), "data-value": option.value, draggable: this.$options.draggable && !this.$options.disabled ? true : null, "on-click": this._hidden.toggle.bind(this), "on-dragstart": this.$options.draggable ? this._hidden.dragStart.bind(this) : null, "on-dragover": this.$options.draggable ? this._hidden.dragOver.bind(this) : null, "on-dragleave": this.$options.draggable ? this._hidden.dragLeave.bind(this) : null, "on-drop": this.$options.draggable ? this._hidden.drop.bind(this) : null, "on-dragend": this.$options.draggable ? this._hidden.dragEnd.bind(this) : null }, hasCols && isArray$1(option.cols) && option.cols.length ? (createElement$1("div", { className: "sortablelist-cols" }, option.cols.map((col, colIndex) => (createElement$1("span", { className: "sortablelist-col", "data-col": colIndex }, escapeEntity(col)))))) : (createElement$1("span", { className: "dropdown-text" }, escapeEntity((_a = option.text) !== null && _a !== void 0 ? _a : option.value)))));
40133
+ })));
40134
+ },
40135
+ getSelection: () => {
40136
+ return this.$options.data.filter((option) => this._selection.has(option.value));
40137
+ },
40138
+ reRender: () => {
40139
+ const listContainer = find('ul', this.$options.delegates.list);
40140
+ listContainer && this.$template.reRender(listContainer, this._hidden.renderSub());
40141
+ },
40142
+ syncSelection: () => {
40143
+ const values = new Set(this.$options.data.map((item) => item.value));
40144
+ this._selection.forEach(val => {
40145
+ if (!values.has(val)) {
40146
+ this._selection.delete(val);
40147
+ }
40148
+ });
40149
+ },
40150
+ hydrateSelection: (items) => {
40151
+ this._selection.clear();
40152
+ items.forEach((item) => {
40153
+ if (item.selected) {
40154
+ this._selection.add(item.value);
40155
+ }
40156
+ });
40157
+ },
40158
+ validateData: (items) => {
40159
+ if (!isArray$1(items)) {
40160
+ throw new TypeError('Invalid SortableList data: data must be an array');
40161
+ }
40162
+ const seen = new Set();
40163
+ items.forEach((item) => {
40164
+ if (!item || typeof item.value !== 'string') {
40165
+ throw new TypeError('Invalid SortableList data: value must be string');
40166
+ }
40167
+ if (item.value === '') {
40168
+ throw new TypeError('Invalid SortableList data: value cannot be empty');
40169
+ }
40170
+ if (seen.has(item.value)) {
40171
+ throw new TypeError(`Invalid SortableList data: duplicate value '${item.value}'`);
40172
+ }
40173
+ seen.add(item.value);
40174
+ });
40175
+ },
40176
+ updateControls: () => {
40177
+ const hasData = (this.$options.data || []).length > 0;
40178
+ const disableButtons = this.$options.disabled || !hasData;
40179
+ if (disableButtons) {
40180
+ attr(findAll('button', this.$el), 'disabled', true);
40181
+ }
40182
+ else {
40183
+ removeAttr(findAll('button', this.$el), 'disabled');
40184
+ }
40185
+ if (!hasData) {
40186
+ addClass(this.$el, 'is-empty');
40187
+ }
40188
+ else {
40189
+ removeClass(this.$el, 'is-empty');
40190
+ }
40191
+ },
40192
+ clearDragState: () => {
40193
+ var _a;
40194
+ DRAG_STATE_CLASSES.forEach(className => {
40195
+ removeClass(findAll(`.${className}`, this.$el), className);
40196
+ });
40197
+ if ((_a = this._dragging) === null || _a === void 0 ? void 0 : _a.preview) {
40198
+ this._dragging.preview.remove();
40199
+ }
40200
+ this._dragging = null;
40201
+ },
40202
+ dragStart: (e) => {
40203
+ if (this.$options.disabled || !this.$options.draggable) {
40204
+ return;
40205
+ }
40206
+ // 기존 드래그 상태 정리 (이전 드래그가 중단된 경우 대비)
40207
+ this._hidden.clearDragState();
40208
+ const value = attr(e.currentTarget, 'data-value');
40209
+ if (!value) {
40210
+ return;
40211
+ }
40212
+ const selected = this._hidden.getSelection().map((item) => item.value);
40213
+ const isGroup = selected.length > 1 && selected.includes(value);
40214
+ const dragValues = isGroup ? selected : [value];
40215
+ this._dragging = { values: dragValues, isGroup: isGroup, preview: null };
40216
+ if (e.dataTransfer) {
40217
+ e.dataTransfer.setData('text/plain', value);
40218
+ e.dataTransfer.effectAllowed = 'move';
40219
+ if (isGroup) {
40220
+ const preview = document.createElement('div');
40221
+ preview.className = 'sortablelist-drag-preview';
40222
+ preview.setAttribute('data-count', String(dragValues.length));
40223
+ const list = document.createElement('div');
40224
+ list.className = 'sortablelist-drag-preview-list';
40225
+ dragValues.forEach((dragValue) => {
40226
+ const itemEl = find(`[data-value="${CSS.escape(dragValue)}"]`, this.$options.delegates.list);
40227
+ if (!(itemEl instanceof HTMLElement)) {
40228
+ return;
40229
+ }
40230
+ const clone = itemEl.cloneNode(true);
40231
+ removeClass(clone, CLASS_DRAGGING);
40232
+ removeClass(clone, CLASS_GROUP_DRAGGING);
40233
+ removeClass(clone, CLASS_DRAG_OVER_TOP);
40234
+ removeClass(clone, CLASS_DRAG_OVER_BOTTOM);
40235
+ addClass(clone, 'is-ghost');
40236
+ list.appendChild(clone);
40237
+ });
40238
+ preview.appendChild(list);
40239
+ document.body.appendChild(preview);
40240
+ e.dataTransfer.setDragImage(preview, this.DRAG_IMAGE_OFFSET_X, this.DRAG_IMAGE_OFFSET_Y);
40241
+ this._dragging.preview = preview;
40242
+ }
40243
+ }
40244
+ findAll('.dropdown-item', this.$options.delegates.list).forEach((node) => {
40245
+ const itemValue = attr(node, 'data-value');
40246
+ if (dragValues.includes(itemValue)) {
40247
+ addClass(node, CLASS_DRAGGING);
40248
+ if (isGroup) {
40249
+ addClass(node, CLASS_GROUP_DRAGGING);
40250
+ }
40251
+ }
40252
+ });
40253
+ },
40254
+ dragOver: (e) => {
40255
+ if (!this._dragging || this.$options.disabled || !this.$options.draggable) {
40256
+ return;
40257
+ }
40258
+ e.preventDefault();
40259
+ if (!(e.currentTarget instanceof HTMLElement)) {
40260
+ return;
40261
+ }
40262
+ const target = e.currentTarget;
40263
+ const value = attr(target, 'data-value');
40264
+ if (this._dragging.values.includes(value)) {
40265
+ return;
40266
+ }
40267
+ removeClass(target, CLASS_DRAG_OVER_TOP);
40268
+ removeClass(target, CLASS_DRAG_OVER_BOTTOM);
40269
+ const rect = target.getBoundingClientRect();
40270
+ const midpoint = rect.top + rect.height * this.DRAG_OVER_THRESHOLD_RATIO;
40271
+ if (e.clientY > midpoint) {
40272
+ addClass(target, CLASS_DRAG_OVER_BOTTOM);
40273
+ }
40274
+ else {
40275
+ addClass(target, CLASS_DRAG_OVER_TOP);
40276
+ }
40277
+ },
40278
+ dragLeave: (e) => {
40279
+ if (e.currentTarget instanceof HTMLElement) {
40280
+ removeClass(e.currentTarget, 'is-drag-over-top');
40281
+ removeClass(e.currentTarget, 'is-drag-over-bottom');
40282
+ }
40283
+ },
40284
+ drop: (e) => {
40285
+ if (!this._dragging || this.$options.disabled || !this.$options.draggable) {
40286
+ return;
40287
+ }
40288
+ e.preventDefault();
40289
+ if (!(e.currentTarget instanceof HTMLElement)) {
40290
+ return;
40291
+ }
40292
+ const targetEl = e.currentTarget;
40293
+ const targetValue = attr(targetEl, 'data-value');
40294
+ const dragValues = this._dragging.values;
40295
+ if (!targetValue || dragValues.includes(targetValue)) {
40296
+ this._hidden.clearDragState();
40297
+ return;
40298
+ }
40299
+ const items = this.$options.data;
40300
+ const dragItems = items.filter((item) => dragValues.includes(item.value));
40301
+ const remain = items.filter((item) => !dragValues.includes(item.value));
40302
+ const targetIndex = remain.findIndex((item) => item.value === targetValue);
40303
+ const rect = targetEl.getBoundingClientRect();
40304
+ const midpoint = rect.top + rect.height * this.DRAG_OVER_THRESHOLD_RATIO;
40305
+ const insertAfter = e.clientY > midpoint;
40306
+ const insertIndex = targetIndex === -1 ? remain.length : insertAfter ? targetIndex + 1 : targetIndex;
40307
+ remain.splice(insertIndex, 0, ...dragItems);
40308
+ this.$options.data = remain;
40309
+ this._hidden.reRender();
40310
+ this._hidden.updateControls();
40311
+ this.$event(this, 'onChange', this.$options.data);
40312
+ this._hidden.clearDragState();
40313
+ },
40314
+ dragEnd: () => {
40315
+ this._hidden.clearDragState();
40316
+ },
40317
+ setData: (data) => {
40318
+ // 드래그 중일 경우 상태 정리 (DOM 요소가 제거되면 dragend 이벤트가 발생하지 않을 수 있음)
40319
+ this._hidden.clearDragState();
40320
+ this._hidden.validateData(data);
40321
+ this.$options.data = data;
40322
+ this._hidden.hydrateSelection(data);
40323
+ this._hidden.syncSelection();
40324
+ this._hidden.reRender();
40325
+ this._hidden.updateControls();
40326
+ this.$event(this, 'onChange', this.$options.data);
40327
+ },
40328
+ disable: () => {
40329
+ // 드래그 중일 경우 상태 정리
40330
+ this._hidden.clearDragState();
40331
+ this.$options.disabled = true;
40332
+ addClass(this.$el, 'is-disabled');
40333
+ this._hidden.reRender();
40334
+ this._hidden.updateControls();
40335
+ },
40336
+ enable: () => {
40337
+ // 드래그 중일 경우 상태 정리
40338
+ this._hidden.clearDragState();
40339
+ this.$options.disabled = false;
40340
+ removeClass(this.$el, 'is-disabled');
40341
+ this._hidden.reRender();
40342
+ this._hidden.updateControls();
40343
+ }
40344
+ };
40345
+ this.config = {
40346
+ name: this.$selector.name || this._uid,
40347
+ data: [],
40348
+ delegates: {
40349
+ list: '.sortablelist-items'
40350
+ },
40351
+ buttonPosition: 'left',
40352
+ draggable: false,
40353
+ textSets: {
40354
+ noData: DEFAULT_NO_DATA
40355
+ },
40356
+ height: 150
40357
+ };
40358
+ this.events = {
40359
+ onChange: true
40360
+ };
40361
+ this.methods = {
40362
+ getData() {
40363
+ return this.$options.data;
40364
+ },
40365
+ setData(data) {
40366
+ const next = isArray$1(data) ? data : data && 'data' in data ? data.data : null;
40367
+ if (!isArray$1(next)) {
40368
+ throw new TypeError('Invalid SortableList data: data must be an array');
40369
+ }
40370
+ this._hidden.setData(next);
40371
+ },
40372
+ disable() {
40373
+ this._hidden.disable();
40374
+ },
40375
+ enable() {
40376
+ this._hidden.enable();
40377
+ }
40378
+ };
40379
+ this.$selector = this.$selector;
40380
+ this.$init(this, options);
40381
+ }
40382
+ template(config) {
40383
+ const styles = {};
40384
+ if (config.width) {
40385
+ styles.width = getUnit('width', config.width);
40386
+ }
40387
+ const listStyles = {};
40388
+ if (config.height) {
40389
+ const heightValue = getUnit('height', config.height);
40390
+ styles.height = heightValue;
40391
+ listStyles.height = heightValue;
40392
+ listStyles.maxHeight = heightValue;
40393
+ }
40394
+ const controlClass = 'sortablelist-controls gn-control is-small has-arrange is-vertical is-center' + (config.buttonPosition === 'right' ? ' is-right' : '');
40395
+ const buttonDisabled = config.disabled || (config.data || []).length === 0;
40396
+ return (createElement$1("div", { id: this._uid, className: 'gn-sortablelist' + (config.disabled ? ' is-disabled' : ''), style: styles },
40397
+ createElement$1("div", { className: controlClass },
40398
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'up-all'), disabled: buttonDisabled },
40399
+ createElement$1("span", { className: "gn-icon" },
40400
+ createElement$1("i", { className: "fa fa-light fa-angle-double-up" }))),
40401
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'up'), disabled: buttonDisabled },
40402
+ createElement$1("span", { className: "gn-icon" },
40403
+ createElement$1("i", { className: "fa fa-light fa-angle-up" }))),
40404
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'down'), disabled: buttonDisabled },
40405
+ createElement$1("span", { className: "gn-icon" },
40406
+ createElement$1("i", { className: "fa fa-light fa-angle-down" }))),
40407
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'down-all'), disabled: buttonDisabled },
40408
+ createElement$1("span", { className: "gn-icon" },
40409
+ createElement$1("i", { className: "fa fa-light fa-angle-double-down" })))),
40410
+ createElement$1("div", { className: "gn-dropdown is-opened sortablelist-items" },
40411
+ createElement$1("div", { className: "dropdown-items", style: listStyles }, this._hidden.renderSub()))));
40412
+ }
40413
+ beforeMount() {
40414
+ this._hidden.validateData(this.$options.data);
40415
+ this._hidden.hydrateSelection(this.$options.data);
40416
+ }
40417
+ completed() {
40418
+ this._hidden.updateControls();
40419
+ }
40420
+ destroyed() {
40421
+ this._hidden.clearDragState();
40422
+ }
40423
+ }
40424
+
39794
40425
  class Splitter extends GNCoreInstance {
39795
40426
  constructor(name, selector, options = {}) {
39796
40427
  super(name, selector, options);
@@ -40438,6 +41069,7 @@
40438
41069
  picklist: Picklist,
40439
41070
  progressbar: Progressbar,
40440
41071
  selectbutton: SelectButton,
41072
+ sortablelist: SortableList,
40441
41073
  splitter: Splitter,
40442
41074
  switch: Switch,
40443
41075
  syntaxinput: SyntaxInput,