gnui 1.2.17 → 1.2.19

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 +736 -96
  2. package/dist/js/gnui.js +736 -96
  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 +736 -96
  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
@@ -16924,7 +16924,10 @@ class GNCoreEventManager {
16924
16924
  // 이벤트 삭제
16925
16925
  delete this._eventMap[uid];
16926
16926
  }
16927
- dispatch(uid, name, params) {
16927
+ /**
16928
+ * lifeCycle 핸들러 실행
16929
+ */
16930
+ cyclepatch(uid, name, params) {
16928
16931
  const _events = this._getEvent(uid, name);
16929
16932
  if (_events.length) {
16930
16933
  _events.forEach((_event) => {
@@ -16933,6 +16936,28 @@ class GNCoreEventManager {
16933
16936
  });
16934
16937
  }
16935
16938
  }
16939
+ /**
16940
+ * 등록된 이벤트 핸들러 실행
16941
+ *
16942
+ * - sync/async 핸들러를 모두 지원한다.
16943
+ * - 모든 핸들러를 순차 실행한 뒤, 하나라도 `false` 를 반환한 경우 `true`(cancelled)를 반환한다.
16944
+ * (첫 false 에서 중단하지 않고, 나머지 핸들러도 계속 실행한다)
16945
+ */
16946
+ async dispatch(uid, name, params) {
16947
+ const _events = this._getEvent(uid, name);
16948
+ let cancelled = false;
16949
+ if (_events.length) {
16950
+ for (const _event of _events) {
16951
+ const _target = _event.target || this;
16952
+ // sync/async 둘 다 지원: Promise.resolve 로 감싸서 await
16953
+ const result = await Promise.resolve(params ? _event.handler.call(_target, ...params) : _event.handler.call(_target));
16954
+ if (result === false) {
16955
+ cancelled = true;
16956
+ }
16957
+ }
16958
+ }
16959
+ return cancelled;
16960
+ }
16936
16961
  _getEvent(uid, name) {
16937
16962
  // parameters에 해당하는 이벤트 반환
16938
16963
  return this._eventMap[uid]
@@ -17017,7 +17042,7 @@ class GNUIState {
17017
17042
  });
17018
17043
  return _findComponent;
17019
17044
  }
17020
- // 등록된 컴포넌트 제거
17045
+ // 등록된 컴포넌트 제거 (selector 기반)
17021
17046
  _removeComponent(selector) {
17022
17047
  if (!selector) {
17023
17048
  return;
@@ -17026,10 +17051,30 @@ class GNUIState {
17026
17051
  Object.values(this._componentMap).forEach(n => {
17027
17052
  // 동일한 selector 인지 비교해서 동일한 component의 selector이면 제거 처리
17028
17053
  if (isEquals(n.selector, _selector)) {
17029
- delete this._componentMap[_selector._uid];
17054
+ // componentMap에서 완전히 제거
17055
+ delete this._componentMap[n.uid];
17056
+ // 컴포넌트 내부 참조도 제거 (메모리 누수 방지)
17057
+ if (n.component) {
17058
+ n.component = null;
17059
+ }
17060
+ n.selector = null;
17030
17061
  }
17031
17062
  });
17032
17063
  }
17064
+ // 등록된 컴포넌트 제거 (uid 기반 - 더 효율적)
17065
+ _removeComponentByUid(uid) {
17066
+ if (!uid || !this._componentMap[uid]) {
17067
+ return;
17068
+ }
17069
+ // 컴포넌트 내부 참조 제거 (메모리 누수 방지)
17070
+ const componentInfo = this._componentMap[uid];
17071
+ if (componentInfo.component) {
17072
+ componentInfo.component = null;
17073
+ }
17074
+ componentInfo.selector = null;
17075
+ // componentMap에서 완전히 제거
17076
+ delete this._componentMap[uid];
17077
+ }
17033
17078
  // 컴포넌트 life cycle에 따른 eventManager dispatch
17034
17079
  _detectedCycle(uid, name) {
17035
17080
  // component 마지막 status 업데이트
@@ -17039,7 +17084,7 @@ class GNUIState {
17039
17084
  // event manager를 이용해 해당 uid 이벤트 dispatch
17040
17085
  const eventManager = GNCoreEventManager.getInstance();
17041
17086
  // 호출 후
17042
- eventManager.dispatch(uid, name, '');
17087
+ eventManager.cyclepatch(uid, name, '');
17043
17088
  // 이벤트 해제 - life cycle 은 컴포넌트 별로 한번씩만 존재하므로..
17044
17089
  eventManager.remove(uid, name);
17045
17090
  }
@@ -17053,6 +17098,7 @@ class GNUIState {
17053
17098
  function _removedNode(removed) {
17054
17099
  Array.prototype.forEach.call(removed, (rm) => {
17055
17100
  // 삭제노드 연관 컴포넌트 (ex. tooltip) 삭제
17101
+ var _a;
17056
17102
  const dependents = findAll('[data-gnui]', rm);
17057
17103
  each(dependents, (dependent) => {
17058
17104
  if (isElement$2(dependent)) {
@@ -17065,12 +17111,19 @@ class GNUIState {
17065
17111
  if (isElement$2(rm) && attr(rm, 'data-gnui')) {
17066
17112
  remove($('#' + attr(rm, 'data-gnui')));
17067
17113
  }
17114
+ const childComponents = $$('[data-gn-uid]', rm);
17115
+ each(childComponents, (childComponent) => {
17116
+ if (isElement$2(childComponent)) {
17117
+ const findComponent = closerThis._getComponent($(childComponent));
17118
+ if (findComponent && findComponent._uid && findComponent.$el && findComponent.$name !== 'modal') {
17119
+ // $destroy 내부에서 removeAll과 _removeComponentByUid를 처리하므로 직접 호출만
17120
+ findComponent.$destroy(findComponent, false);
17121
+ }
17122
+ }
17123
+ });
17068
17124
  const findComponent = closerThis._getComponent($(rm));
17069
- if (findComponent && findComponent._uid && !findComponent.$el.parentNode && findComponent.$name !== 'modal') {
17070
- // state manager 에서 component 삭제
17071
- closerThis._removeComponent(rm);
17072
- // event manager 에서 unbind
17073
- GNCoreEventManager.getInstance().removeAll(findComponent._uid);
17125
+ if (findComponent && findComponent._uid && !((_a = findComponent.$el) === null || _a === void 0 ? void 0 : _a.parentNode) && findComponent.$name !== 'modal') {
17126
+ findComponent.$destroy(findComponent, false);
17074
17127
  }
17075
17128
  });
17076
17129
  }
@@ -17226,6 +17279,7 @@ class GNCoreInstance {
17226
17279
  if (tmpRole) {
17227
17280
  attr(this.$el, 'role', tmpRole);
17228
17281
  }
17282
+ attr(this.$el, 'data-gn-uid', this._uid);
17229
17283
  // inherit selector class
17230
17284
  selector.className && addClass(this.$el, selector.className);
17231
17285
  }
@@ -17265,25 +17319,48 @@ class GNCoreInstance {
17265
17319
  }
17266
17320
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
17267
17321
  $update(element = this.$el, e) { }
17268
- $event(component, name, ...params) {
17322
+ /**
17323
+ * 컴포넌트 이벤트 디스패치 헬퍼
17324
+ *
17325
+ * - sync/async 핸들러 모두 지원한다.
17326
+ * - 하나 이상의 핸들러에서 `false`를 반환하면 `true`(cancelled) 를 반환한다.
17327
+ * - 단순 알림용(fire-and-forget) 이벤트는 반환값/await 없이 호출해도 된다.
17328
+ */
17329
+ async $event(component, name, ...params) {
17269
17330
  const eventManager = GNCoreEventManager.getInstance();
17270
- eventManager.dispatch(component._uid, name, params);
17331
+ return eventManager.dispatch(component._uid, name, params);
17271
17332
  }
17272
17333
  $destroy(component = this, removeEl = true) {
17334
+ var _a;
17273
17335
  const stateManager = GNUIState.getInstance();
17274
17336
  // state manager 를 통해 destroy 상태 dispatch
17275
17337
  stateManager._detectedCycle(component._uid, 'destroy');
17276
- // remove Component in state manager
17277
- stateManager._removeComponent(component.$selector);
17278
17338
  // remove DOM (by removeEl)
17279
17339
  if (removeEl) {
17280
17340
  style(component.$el, 'display', 'none');
17281
17341
  remove(component.$el);
17282
17342
  }
17343
+ if (((_a = component.$options) === null || _a === void 0 ? void 0 : _a._destroy) && isFunction(component.$options._destroy)) {
17344
+ component.$options._destroy();
17345
+ }
17283
17346
  // state manager 를 통해 destroy 상태 dispatch
17284
17347
  stateManager._detectedCycle(component._uid, 'destroyed');
17285
- // event manager 에서 등록 해제가 가장 마지막..
17348
+ // event manager 에서 등록 해제
17286
17349
  GNCoreEventManager.getInstance().removeAll(component._uid);
17350
+ // state manager에서 component 제거 (uid 기반으로 효율적 제거)
17351
+ stateManager._removeComponentByUid(component._uid);
17352
+ // 메모리 누수 방지: component의 모든 hasOwnProperty 제거
17353
+ // config, events, methods, _hidden 등 동적으로 추가된 속성 포함
17354
+ Object.keys(component).forEach((key) => {
17355
+ try {
17356
+ component[key] = null;
17357
+ delete component[key];
17358
+ }
17359
+ catch (e) {
17360
+ // readonly 속성 등 삭제 불가능한 경우 무시
17361
+ }
17362
+ });
17363
+ component = null;
17287
17364
  }
17288
17365
  }
17289
17366
 
@@ -33484,8 +33561,19 @@ class Dropdown extends GNCoreInstance {
33484
33561
  });
33485
33562
  if (this.$options.value) {
33486
33563
  if (this.$options.multiple) {
33487
- const values = this.$options.value.split(',');
33488
- this.$options.value = this.$options.flatData.filter((opt) => values.includes(opt.value) && opt.text);
33564
+ // multiple 모드에서 다양한 타입의 value를 문자열 배열로 변환
33565
+ // 지원 타입: 문자열(쉼표 구분), 객체 배열, 단일 객체, 문자열 배열
33566
+ const values = typeof this.$options.value === 'string'
33567
+ ? this.$options.value.split(',') // 케이스 1: 'item1,item2,item3'
33568
+ : Array.isArray(this.$options.value)
33569
+ ? 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']
33570
+ : typeof this.$options.value === 'object' && this.$options.value !== null && 'value' in this.$options.value
33571
+ ? [String(this.$options.value.value)] // 케이스 2: {value:'item1', text:'항목1'}
33572
+ : [String(this.$options.value)]; // 기타: 숫자 등
33573
+ this.$options.value = this.$options.flatData.filter((opt) => {
33574
+ const optValue = typeof opt.value === 'string' ? opt.value : String(opt.value);
33575
+ return values.includes(optValue) && opt.text;
33576
+ });
33489
33577
  }
33490
33578
  else {
33491
33579
  this.$options.value = this.$options.flatData.find((opt) => opt.value + '' === this.$options.value + '' && opt.text);
@@ -37000,26 +37088,8 @@ class DataGrid extends GNCoreInstance {
37000
37088
  }
37001
37089
  this.$event(this, 'onSort', column);
37002
37090
  },
37003
- renderHeader: (columns) => {
37004
- this.$options.hasOrder &&
37005
- !this.$options.readonly &&
37006
- columns.push({
37007
- label: this.$options.textSets.orderLabel,
37008
- key: 'btnOrder',
37009
- style: {
37010
- width: '50px'
37011
- }
37012
- });
37013
- this.$options.hasDelete &&
37014
- !this.$options.readonly &&
37015
- columns.push({
37016
- label: this.$options.textSets.deleteLabel,
37017
- key: 'btnDelete',
37018
- style: {
37019
- width: '30px'
37020
- }
37021
- });
37022
- this._setColumnsTemplate();
37091
+ renderHeader: (columns, isReset = false) => {
37092
+ this._setColumnsTemplate(isReset);
37023
37093
  return (createElement$1("div", { className: "gn-datagrid-header-row", style: {
37024
37094
  'grid-template-columns': this._columnsTemplate.join(' ')
37025
37095
  } },
@@ -37058,6 +37128,10 @@ class DataGrid extends GNCoreInstance {
37058
37128
  column.draggable && (this.$options.headers ? idx < this.$options.headers.length - 1 : true) && createElement$1("span", { className: "is-handle", "data-index": idx })));
37059
37129
  },
37060
37130
  renderBody: (data, columns) => {
37131
+ // 헤더가 숨겨진 경우에도 body 렌더 전에 템플릿 폭을 준비한다
37132
+ if (!this._columnsTemplate || !this._columnsTemplate.length) {
37133
+ this._setColumnsTemplate();
37134
+ }
37061
37135
  rowIdx$1 = 0;
37062
37136
  return (createElement$1("div", { className: "gn-datagrid-body", style: {
37063
37137
  maxHeight: this.$options.bodyHeight ? this.$options.bodyHeight : 'auto'
@@ -37071,7 +37145,6 @@ class DataGrid extends GNCoreInstance {
37071
37145
  });
37072
37146
  },
37073
37147
  renderRow: (row, columns, depth = 0, hasChild, isOpened, isCheck = false) => {
37074
- row._depth = depth;
37075
37148
  const _index = rowIdx$1++;
37076
37149
  if (row.isChecked) {
37077
37150
  isCheck = true;
@@ -37205,8 +37278,9 @@ class DataGrid extends GNCoreInstance {
37205
37278
  e.stopPropagation();
37206
37279
  toggler = parents(e.currentTarget, '.gn-datagrid-body-row');
37207
37280
  }
37208
- const children = nextUntil(toggler, '.gn-datagrid-body-row[data-depth="' + row._depth + '"]').filter((x) => {
37209
- return x.dataset.depth > row._depth;
37281
+ const rowDepth = Number(attr(toggler, 'data-depth')) || 0;
37282
+ const children = nextUntil(toggler, '.gn-datagrid-body-row[data-depth="' + rowDepth + '"]').filter((x) => {
37283
+ return Number(attr(x, 'data-depth')) > rowDepth;
37210
37284
  });
37211
37285
  type = type ? type : hasClass(toggler, 'is-collapsed') ? 'expand' : 'collapse';
37212
37286
  if (type === 'collapse') {
@@ -37222,7 +37296,7 @@ class DataGrid extends GNCoreInstance {
37222
37296
  //show childs
37223
37297
  removeClass(toggler, 'is-collapsed');
37224
37298
  removeClass(children.filter((x) => {
37225
- return x.dataset.depth == row._depth + 1;
37299
+ return Number(attr(x, 'data-depth')) == rowDepth + 1;
37226
37300
  }), 'is-hidden');
37227
37301
  this.$event(this, 'onToggle', 'expanded', row, index$1(toggler));
37228
37302
  }
@@ -37280,13 +37354,15 @@ class DataGrid extends GNCoreInstance {
37280
37354
  e.stopPropagation();
37281
37355
  const checker = parents(e.currentTarget, '.gn-datagrid-body-row');
37282
37356
  const checkerState = e.target.checked;
37283
- find('.is-allChecker', this.$el).checked = false;
37357
+ const allChecker = find('.is-allChecker', this.$el);
37358
+ allChecker && (allChecker.checked = false);
37359
+ const rowDepth = Number(attr(checker, 'data-depth')) || 0;
37284
37360
  // 1. row에 자식노드가 있는지 확인한다.
37285
37361
  if (this.$options.checkCapturing && row[this.$options.childField] && row[this.$options.childField].length) {
37286
37362
  // 2. 자식노드가 있는경우 자식 체크박스도 함께 토글한다.
37287
- nextUntil(checker, '.gn-datagrid-body-row[data-depth="' + row._depth + '"]')
37363
+ nextUntil(checker, '.gn-datagrid-body-row[data-depth="' + rowDepth + '"]')
37288
37364
  .filter((x) => {
37289
- return x.dataset.depth > row._depth;
37365
+ return Number(attr(x, 'data-depth')) > rowDepth;
37290
37366
  })
37291
37367
  .forEach((x) => {
37292
37368
  const _checker = find('.is-rowChecker', x);
@@ -37296,17 +37372,17 @@ class DataGrid extends GNCoreInstance {
37296
37372
  });
37297
37373
  }
37298
37374
  // 3. 체크 해제인 경우만 부모노드가 있는지 확인한다.
37299
- if (this.$options.checkCapturing && row._depth > 0 && !checkerState) {
37375
+ if (this.$options.checkCapturing && rowDepth > 0 && !checkerState) {
37300
37376
  // 4. 부모노드가 체크되어 있는지 확인한다
37301
37377
  const exeDepth = [];
37302
37378
  prevUntil(checker, '.gn-datagrid-body-row[data-depth="0"]')
37303
37379
  .filter((x) => {
37304
- const _thisDepth = x.dataset.depth;
37380
+ const _thisDepth = attr(x, 'data-depth');
37305
37381
  if (exeDepth.includes(_thisDepth)) {
37306
37382
  return false;
37307
37383
  }
37308
37384
  exeDepth.push(_thisDepth);
37309
- return _thisDepth < row._depth;
37385
+ return Number(_thisDepth) < rowDepth;
37310
37386
  })
37311
37387
  .forEach((x) => {
37312
37388
  const _checker = find('.is-rowChecker', x);
@@ -37322,9 +37398,21 @@ class DataGrid extends GNCoreInstance {
37322
37398
  if (hasCheck === undefined) {
37323
37399
  hasCheck = this.$options.hasCheck;
37324
37400
  }
37325
- this.$options.headers = headers;
37401
+ const prevHeaders = [...(this.$options.headers || [])];
37402
+ // _prepareHeaders가 배열을 변경하므로 비교용(prevHeaders)과 가공용(baseHeaders)을 분리한다
37403
+ const baseHeaders = headers ? [...headers] : [...prevHeaders];
37404
+ const preparedHeaders = this._prepareHeaders(baseHeaders);
37405
+ // 헤더 배열의 내용(길이, key)이 바뀐 경우에만 DOM 기반 폭 계산을 리셋한다
37406
+ 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); });
37326
37407
  this.$options.hasCheck = hasCheck;
37327
- this.$template.reRender(find('.gn-datagrid-header-row', this.$el), this._hidden.renderHeader(this.$options.headers));
37408
+ const headerRow = find('.gn-datagrid-header-row', this.$el);
37409
+ // 헤더가 없을 때도 컬럼 수/폭 변경을 반영하기 위해 템플릿을 갱신한다
37410
+ if (this.$options.hasHeader && headerRow) {
37411
+ this.$template.reRender(headerRow, this._hidden.renderHeader(preparedHeaders, isReset));
37412
+ }
37413
+ else {
37414
+ this._setColumnsTemplate(isReset);
37415
+ }
37328
37416
  this._hidden.resetData(data ? arrClone(data) : this.$options.data);
37329
37417
  this.$render(this.$options);
37330
37418
  isFunction(resolve) && resolve();
@@ -37339,6 +37427,10 @@ class DataGrid extends GNCoreInstance {
37339
37427
  });
37340
37428
  },
37341
37429
  awaitData: (data) => {
37430
+ // asyncData 콜백 실행 중 컴포넌트가 destroy된 경우 안전하게 종료
37431
+ if (!this.$options || !this._hidden) {
37432
+ return;
37433
+ }
37342
37434
  if (this.$options.asyncData && this.$options.paginator && !this._paginator) {
37343
37435
  this._paginator = new Pagination('pagination', find('.gn-datagrid-footer', this.$el), {
37344
37436
  total: this.$options.paginator.total || 0,
@@ -37363,7 +37455,8 @@ class DataGrid extends GNCoreInstance {
37363
37455
  this._fixCellStyleOnDraggable();
37364
37456
  // 체크박스가 있는경우 전체 체크항목을 해제해준다
37365
37457
  if (this.$options.hasCheck) {
37366
- find('.is-allChecker', this.$el).checked = false;
37458
+ const allChecker = find('.is-allChecker', this.$el);
37459
+ allChecker && (allChecker.checked = false);
37367
37460
  }
37368
37461
  isFunction(resolve) && resolve();
37369
37462
  });
@@ -37398,8 +37491,18 @@ class DataGrid extends GNCoreInstance {
37398
37491
  stopRowSelectEvent: (e) => {
37399
37492
  e.stopPropagation();
37400
37493
  },
37401
- deleteRow: (index) => {
37402
- this.$options.data = this.$options.data.filter((_data, idx) => index !== idx);
37494
+ deleteRow: async (index) => {
37495
+ var _a;
37496
+ const confirmMessage = (_a = this.$options.textSets) === null || _a === void 0 ? void 0 : _a.deleteConfirmMessage;
37497
+ if (confirmMessage && !window.confirm(confirmMessage)) {
37498
+ return;
37499
+ }
37500
+ const removedData = this._hidden.findData(index);
37501
+ const cancelled = await this.$event(this, 'onDelete', removedData, index);
37502
+ if (cancelled) {
37503
+ return;
37504
+ }
37505
+ this._hidden.deleteData(index);
37403
37506
  this._hidden.resetData(this.$options.data);
37404
37507
  this.$event(this, 'onChange', this.$options.data);
37405
37508
  },
@@ -37434,22 +37537,38 @@ class DataGrid extends GNCoreInstance {
37434
37537
  col.offHover && col.offHover.call(this, row, col, index, e);
37435
37538
  },
37436
37539
  findData: (index) => {
37437
- let deter = 0, indexData = null;
37438
- const findIndex = (datas, index) => {
37439
- return datas.some((data) => {
37440
- if (index === deter) {
37441
- indexData = data;
37540
+ return this._hidden.walkByIndex(index, 'find');
37541
+ },
37542
+ deleteData: (index) => {
37543
+ return this._hidden.walkByIndex(index, 'delete');
37544
+ },
37545
+ walkByIndex: (index, action) => {
37546
+ let deter = 0;
37547
+ let result = null;
37548
+ const findIndex = (datas) => {
37549
+ for (let i = 0; i < datas.length; i++) {
37550
+ if (deter === index) {
37551
+ switch (action) {
37552
+ case 'find':
37553
+ result = datas[i];
37554
+ break;
37555
+ case 'delete':
37556
+ result = datas.splice(i, 1)[0];
37557
+ break;
37558
+ }
37442
37559
  return true;
37443
37560
  }
37444
- ++deter;
37445
- if (isArray$1(data[this.$options.childField]) && data[this.$options.childField].length) {
37446
- return findIndex(data[this.$options.childField], index);
37561
+ deter++;
37562
+ const children = datas[i][this.$options.childField];
37563
+ if (Array.isArray(children) && children.length) {
37564
+ if (findIndex(children))
37565
+ return true;
37447
37566
  }
37448
- return false;
37449
- });
37567
+ }
37568
+ return false;
37450
37569
  };
37451
- findIndex(this.$options.data, index);
37452
- return indexData;
37570
+ findIndex(this.$options.data);
37571
+ return result;
37453
37572
  },
37454
37573
  getChecked: () => {
37455
37574
  return findAll('.is-rowChecker', this.$el)
@@ -37536,11 +37655,13 @@ class DataGrid extends GNCoreInstance {
37536
37655
  hasOrder: false,
37537
37656
  hasDelete: false,
37538
37657
  isEllipsis: false,
37658
+ hasHeader: true,
37539
37659
  data: [],
37540
37660
  textSets: {
37541
37661
  noData: 'No records available.',
37542
37662
  orderLabel: '',
37543
- deleteLabel: ''
37663
+ deleteLabel: '',
37664
+ deleteConfirmMessage: ''
37544
37665
  },
37545
37666
  childField: 'child',
37546
37667
  checkCapturing: true,
@@ -37558,7 +37679,8 @@ class DataGrid extends GNCoreInstance {
37558
37679
  onCheck: true,
37559
37680
  onDoubleClick: true,
37560
37681
  onChange: true,
37561
- onDragEnd: true
37682
+ onDragEnd: true,
37683
+ onDelete: true
37562
37684
  };
37563
37685
  this.methods = {
37564
37686
  reRender(options) {
@@ -37618,18 +37740,21 @@ class DataGrid extends GNCoreInstance {
37618
37740
  this.$selector = this.$selector;
37619
37741
  this.$init(this, options);
37620
37742
  }
37621
- _setColumnsTemplate() {
37743
+ _setColumnsTemplate(isReset = false) {
37622
37744
  // header cell의 각 넓이를 배열로 가져온다
37623
37745
  // ! 모든 컬럼의 넓이가 지정된 경우 이동(btnOrder), 삭제(btnDelete) 컬럼을 제외한 마지막 컬럼은 1fr로 고정
37624
37746
  const _isfixedAllWidth = this.$options.headers.every((header) => { var _a; return ((_a = header.style) === null || _a === void 0 ? void 0 : _a.width) !== undefined; });
37625
37747
  const _fixedTemplateColumn = this.$options.headers.findLast((header) => !this._isSystemAddedColumn(header.key) && !header.isHidden);
37748
+ // 헤더 DOM이 없거나 모든 일반 컬럼이 숨겨진 경우에도 안전하게 비교할 수 있도록 key만 옵셔널하게 캐싱한다
37749
+ const _fixedTemplateKey = _fixedTemplateColumn === null || _fixedTemplateColumn === void 0 ? void 0 : _fixedTemplateColumn.key;
37626
37750
  const columns = findAll('.gn-datagrid-header-cell', this.$el);
37627
- if (this.$el && columns.length) {
37751
+ // isReset이면 기존 DOM 폭을 재사용하지 않고 헤더 정의로 재계산
37752
+ if (this.$el && columns.length && !isReset) {
37628
37753
  this._columnsTemplate = findAll('.gn-datagrid-header-cell', this.$el).map((header, idx) => {
37629
37754
  if (this.$options.headers[idx].isHidden) {
37630
37755
  return '';
37631
37756
  }
37632
- else if (_isfixedAllWidth && this.$options.headers[idx].key === _fixedTemplateColumn.key) {
37757
+ else if (_isfixedAllWidth && _fixedTemplateKey && this.$options.headers[idx].key === _fixedTemplateKey) {
37633
37758
  return '1fr';
37634
37759
  }
37635
37760
  else {
@@ -37643,7 +37768,7 @@ class DataGrid extends GNCoreInstance {
37643
37768
  if (header.isHidden) {
37644
37769
  return '';
37645
37770
  }
37646
- else if (_isfixedAllWidth && header.key === _fixedTemplateColumn.key) {
37771
+ else if (_isfixedAllWidth && _fixedTemplateKey && header.key === _fixedTemplateKey) {
37647
37772
  return '1fr';
37648
37773
  }
37649
37774
  else {
@@ -37664,18 +37789,45 @@ class DataGrid extends GNCoreInstance {
37664
37789
  _isSystemAddedColumn(key) {
37665
37790
  return ['btnOrder', 'btnDelete'].includes(key);
37666
37791
  }
37792
+ // 옵션에 따른 추가 해더 구성
37793
+ _prepareHeaders(headers = []) {
37794
+ const hasOrderColumn = headers.some((header) => header.key === 'btnOrder');
37795
+ const hasDeleteColumn = headers.some((header) => header.key === 'btnDelete');
37796
+ if (this.$options.hasOrder && !this.$options.readonly && !hasOrderColumn) {
37797
+ headers.push({
37798
+ label: this.$options.textSets.orderLabel,
37799
+ key: 'btnOrder',
37800
+ style: {
37801
+ width: '50px'
37802
+ }
37803
+ });
37804
+ }
37805
+ if (this.$options.hasDelete && !this.$options.readonly && !hasDeleteColumn) {
37806
+ headers.push({
37807
+ label: this.$options.textSets.deleteLabel,
37808
+ key: 'btnDelete',
37809
+ style: {
37810
+ width: '30px'
37811
+ }
37812
+ });
37813
+ }
37814
+ this.$options.headers = headers;
37815
+ return headers;
37816
+ }
37667
37817
  template(config) {
37668
37818
  const styles = {};
37819
+ const headers = this._prepareHeaders(config.headers);
37669
37820
  return (createElement$1("div", { id: this._uid, className: 'gn-datagrid' +
37670
37821
  (config.style ? ' is-' + config.style : '') +
37671
37822
  (config.isEllipsis ? ' is-ellipsis' : '') +
37672
37823
  (config.bodyHeight ? ' has-fixed-body' : '') +
37673
37824
  (config.fixHeader ? ' has-fixed-header' : '') +
37674
37825
  (config.fixFooter ? ' has-fixed-footer' : '') +
37826
+ (!config.hasHeader ? ' is-headless' : '') +
37675
37827
  (config.data.some((d) => isArray$1(d[this.$options.childField])) ? ' has-left-padding' : '') +
37676
37828
  (config.disabled ? ' is-disabled' : ''), style: styles },
37677
- createElement$1("div", { className: "gn-datagrid-header" }, this._hidden.renderHeader(config.headers)),
37678
- 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)),
37829
+ config.hasHeader && createElement$1("div", { className: "gn-datagrid-header" }, this._hidden.renderHeader(headers)),
37830
+ 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)),
37679
37831
  config.paginator /* 페이지네이터 옵션 확인 */ && createElement$1("div", { className: "gn-datagrid-footer" })));
37680
37832
  }
37681
37833
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -37692,18 +37844,20 @@ class DataGrid extends GNCoreInstance {
37692
37844
  }
37693
37845
  }
37694
37846
  completed() {
37695
- if (this.$options.fixHeader) {
37847
+ if (this.$options.fixHeader && this.$options.hasHeader) {
37696
37848
  const body = find('.gn-datagrid-contents', this.$el);
37697
37849
  const header = find('.gn-datagrid-header', this.$el);
37698
- const _offset = offset(header);
37699
- this.$options.bodyTopMargin = _offset.height ? _offset.height - 1 + 'px' : '2.4rem';
37700
- css$1(body, 'margin-top', this.$options.bodyTopMargin);
37850
+ if (header) {
37851
+ const _offset = offset(header);
37852
+ this.$options.bodyTopMargin = _offset.height ? _offset.height - 1 + 'px' : '2.4rem';
37853
+ css$1(body, 'margin-top', this.$options.bodyTopMargin);
37854
+ }
37701
37855
  if (this.$options.paginator) {
37702
37856
  this.$options.bodyBottomMargin = '2.4rem';
37703
37857
  css$1(body, 'margin-bottom', this.$options.bodyBottomMargin);
37704
37858
  }
37705
37859
  }
37706
- if (this.$options.fixHeader || this.$options.bodyHeight) {
37860
+ if ((this.$options.fixHeader && this.$options.hasHeader) || this.$options.bodyHeight) {
37707
37861
  this._hidden.setBlankHeader();
37708
37862
  on(window, 'resize', this._hidden.setBlankHeader);
37709
37863
  }
@@ -39048,7 +39202,9 @@ class MenuButton extends GNCoreInstance {
39048
39202
  super(name, selector, options);
39049
39203
  this._hidden = {
39050
39204
  open: () => {
39051
- addClass(this.$el, 'is-open');
39205
+ if (!this.$options.disabled) {
39206
+ addClass(this.$el, 'is-open');
39207
+ }
39052
39208
  },
39053
39209
  close: () => {
39054
39210
  removeClass(this.$el, 'is-open');
@@ -39060,6 +39216,62 @@ class MenuButton extends GNCoreInstance {
39060
39216
  changeText: (buttonText) => {
39061
39217
  this.$options.textSets.buttonText = buttonText;
39062
39218
  html(find('.menuButton-text', this.$el), buttonText);
39219
+ },
39220
+ disable: () => {
39221
+ this.$options.disabled = true;
39222
+ const buttonEl = find('button', this.$el);
39223
+ if (buttonEl) {
39224
+ attr(buttonEl, 'disabled', true);
39225
+ }
39226
+ addClass(this.$el, 'is-disabled');
39227
+ this._hidden.close();
39228
+ },
39229
+ enable: () => {
39230
+ this.$options.disabled = false;
39231
+ const buttonEl = find('button', this.$el);
39232
+ if (buttonEl) {
39233
+ removeAttr(buttonEl, 'disabled');
39234
+ }
39235
+ removeClass(this.$el, 'is-disabled');
39236
+ },
39237
+ renderMenus: (menus, depth = 0, parentPath = '') => {
39238
+ return (createElement$1("ul", { className: depth > 0 ? 'menuButton-submenu' : '' }, menus.map((menu, index) => {
39239
+ const hasChild = menu.child && isArray$1(menu.child) && menu.child.length > 0;
39240
+ const hasHtml = !!menu.html;
39241
+ // html이 있으면 innerHTML이 모든 자식 요소를 덮어쓰므로 서브메뉴를 렌더링하지 않음
39242
+ const canRenderChild = hasChild && depth < 2 && !hasHtml; // 최대 2단계까지만 허용
39243
+ const isDisabled = menu.disabled === true;
39244
+ const isActived = menu.actived === true;
39245
+ // 부모 경로를 포함한 고유한 ID 생성
39246
+ const currentPath = parentPath ? `${parentPath}-${index}` : `${index}`;
39247
+ const uniqueId = `${this._uid}-${currentPath}`;
39248
+ return (createElement$1("li", { id: uniqueId, className: 'menuButton-menu' +
39249
+ (this.$options.align ? ' has-text-' + this.$options.align : '') +
39250
+ (canRenderChild ? ' has-submenu' : '') +
39251
+ (depth > 0 ? ' is-submenu-item' : '') +
39252
+ (isDisabled ? ' is-disabled' : '') +
39253
+ (isActived ? ' is-actived' : ''), "on-click": (e) => {
39254
+ // disabled 상태이거나 자식 메뉴가 있는 경우 클릭 이벤트 처리하지 않음
39255
+ if (isDisabled) {
39256
+ e.stopPropagation();
39257
+ e.preventDefault();
39258
+ return;
39259
+ }
39260
+ // 자식 메뉴가 없는 경우에만 select 이벤트 발생
39261
+ if (!canRenderChild) {
39262
+ e.stopPropagation();
39263
+ this._hidden.select.call(this, menu, e);
39264
+ }
39265
+ }, innerHTML: hasHtml ? menu.html : '' },
39266
+ hasHtml ? ('') : (createElement$1("span", { className: "menuButton-menu-content" },
39267
+ createElement$1("span", { className: "menuButton-menu-text" }, menu.text),
39268
+ canRenderChild && (createElement$1("span", { className: "menuButton-menu-arrow" },
39269
+ createElement$1("i", { className: "fas fa-caret-right" }))))),
39270
+ canRenderChild && this._hidden.renderMenus.call(this, menu.child, depth + 1, currentPath)));
39271
+ })));
39272
+ },
39273
+ renderSub: (data) => {
39274
+ 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));
39063
39275
  }
39064
39276
  };
39065
39277
  this.config = {
@@ -39079,6 +39291,19 @@ class MenuButton extends GNCoreInstance {
39079
39291
  },
39080
39292
  buttonText(text) {
39081
39293
  this._hidden.changeText(text);
39294
+ },
39295
+ reRender(data) {
39296
+ this.$options.data = data;
39297
+ const menuMenusEl = find('.menuButton-menus > div', this.$el);
39298
+ if (menuMenusEl && this.$template) {
39299
+ this.$template.reRender(menuMenusEl, this._hidden.renderSub.call(this, data));
39300
+ }
39301
+ },
39302
+ disabled() {
39303
+ this._hidden.disable();
39304
+ },
39305
+ enabled() {
39306
+ this._hidden.enable();
39082
39307
  }
39083
39308
  };
39084
39309
  this.$selector = this.$selector;
@@ -39089,23 +39314,19 @@ class MenuButton extends GNCoreInstance {
39089
39314
  if (config.width) {
39090
39315
  styles.width = getUnit('width', config.width);
39091
39316
  }
39092
- const renderMenus = (menus) => {
39093
- 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) => {
39094
- this._hidden.select.call(this, menu, e);
39095
- }, innerHTML: menu.html ? menu.html : '' }, menu.html ? '' : menu.text)))));
39096
- };
39097
- const renderSub = (data) => {
39098
- return createElement$1("div", null, isArray$1(data) && data.length && isArray$1(data[0]) ? data.map((menus) => renderMenus(menus)) : renderMenus(data));
39099
- };
39100
- 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 },
39101
- createElement$1("button", { type: "button", className: config.align ? 'has-text-' + config.align : '', "on-click": this._hidden.open },
39317
+ return (createElement$1("div", { id: this._uid, className: 'gn-menuButton' +
39318
+ (config.color ? ' is-' + config.color : '') +
39319
+ (config.style ? ' is-' + config.style : '') +
39320
+ (config.size ? ' is-' + config.size : '') +
39321
+ (config.disabled ? ' is-disabled' : ''), style: styles },
39322
+ createElement$1("button", { type: "button", className: config.align ? 'has-text-' + config.align : '', disabled: config.disabled, "on-click": this._hidden.open },
39102
39323
  config.icon && (createElement$1("span", { className: 'gn-icon is-' + (config.size === 'large' ? 'medium' : config.size === 'medium' ? 'normal' : 'small') },
39103
39324
  createElement$1("i", { className: 'fas fa-' + config.icon }),
39104
39325
  ' ')),
39105
39326
  createElement$1("span", { className: "gn-icon is-small menuButton-icon" },
39106
39327
  createElement$1("i", { className: "fas fa-caret-down" })),
39107
39328
  createElement$1("span", { className: "menuButton-text" }, config.textSets.buttonText)),
39108
- createElement$1("div", { className: "menuButton-menus" }, renderSub(config.data))));
39329
+ createElement$1("div", { className: "menuButton-menus" }, this._hidden.renderSub.call(this, config.data))));
39109
39330
  }
39110
39331
  completed() {
39111
39332
  // 해당 컴포넌트 외 클릭 시 menu panel 숨김
@@ -39316,6 +39537,7 @@ class MultiTextArea extends GNCoreInstance {
39316
39537
  }
39317
39538
  }
39318
39539
 
39540
+ library$1.add(icons$1, icons);
39319
39541
  class Picklist extends GNCoreInstance {
39320
39542
  constructor(name, selector, options = {}) {
39321
39543
  super(name, selector, options);
@@ -39457,17 +39679,26 @@ class Picklist extends GNCoreInstance {
39457
39679
  },
39458
39680
  renderSub: (item) => {
39459
39681
  const items = this.$options.data[item] || [];
39460
- 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', [
39461
- {
39462
- value: option.value,
39463
- text: option.text
39464
- }
39465
- ]) },
39466
- createElement$1("span", { className: "dropdown-text" }, option.text))))));
39682
+ return (createElement$1("ul", null, items.map((option, index) => {
39683
+ var _a;
39684
+ 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
39685
+ ? null
39686
+ : this._hidden.move.bind(this, item === 'source' ? 'add' : 'remove', [
39687
+ {
39688
+ value: option.value,
39689
+ text: option.text,
39690
+ html: (_a = option.html) !== null && _a !== void 0 ? _a : null
39691
+ }
39692
+ ]) },
39693
+ createElement$1("span", { className: "dropdown-text", innerHTML: option.html ? option.html : '' }, option.html ? ('') : option.icon ? (createElement$1("span", null,
39694
+ createElement$1("span", { className: 'gn-icon' + (this.$options.size ? ' is-' + this.$options.size : '') },
39695
+ createElement$1("i", { className: (this.isBrandIcon(option.icon) ? 'fab' : 'fa') + ` fa-${option.icon}` })),
39696
+ escapeEntity(option.text))) : (escapeEntity(option.text)))));
39697
+ })));
39467
39698
  },
39468
39699
  getSelection: (target) => {
39469
39700
  return findAll('.is-active', this.$options.delegates[target]).map((select) => {
39470
- return { value: attr(select, 'data-value'), text: text$1(select) };
39701
+ return this.$options.data[target].find((option) => option.value === attr(select, 'data-value'));
39471
39702
  });
39472
39703
  },
39473
39704
  disable: () => {
@@ -39627,6 +39858,11 @@ class Picklist extends GNCoreInstance {
39627
39858
  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'))));
39628
39859
  }
39629
39860
  }
39861
+ isBrandIcon(iconName) {
39862
+ const iconLookup = { prefix: 'fab', iconName: iconName };
39863
+ const iconDefinition = findIconDefinition$1(iconLookup);
39864
+ return iconDefinition !== undefined;
39865
+ }
39630
39866
  }
39631
39867
 
39632
39868
  class Progressbar extends GNCoreInstance {
@@ -39785,6 +40021,409 @@ class SelectButton extends GNCoreInstance {
39785
40021
  }
39786
40022
  }
39787
40023
 
40024
+ const CLASS_DRAGGING = 'is-dragging';
40025
+ const CLASS_GROUP_DRAGGING = 'is-group-dragging';
40026
+ const CLASS_DRAG_OVER_TOP = 'is-drag-over-top';
40027
+ const CLASS_DRAG_OVER_BOTTOM = 'is-drag-over-bottom';
40028
+ const DEFAULT_NO_DATA = 'No records available.';
40029
+ const DRAG_STATE_CLASSES = [CLASS_DRAGGING, CLASS_GROUP_DRAGGING, CLASS_DRAG_OVER_TOP, CLASS_DRAG_OVER_BOTTOM];
40030
+ class SortableList extends GNCoreInstance {
40031
+ constructor(name, selector, options = {}) {
40032
+ super(name, selector, options);
40033
+ this._dragging = null;
40034
+ this._selection = new Set();
40035
+ this.DRAG_IMAGE_OFFSET_X = 0;
40036
+ this.DRAG_IMAGE_OFFSET_Y = 0;
40037
+ this.DRAG_OVER_THRESHOLD_RATIO = 0.5;
40038
+ this._hidden = {
40039
+ toggle: (e) => {
40040
+ if (this.$options.disabled) {
40041
+ return;
40042
+ }
40043
+ const value = attr(e.currentTarget, 'data-value');
40044
+ const hasItem = this.$options.data.some((option) => option.value === value);
40045
+ if (!value || !hasItem) {
40046
+ return;
40047
+ }
40048
+ if (this._selection.has(value)) {
40049
+ this._selection.delete(value);
40050
+ removeClass(e.currentTarget, 'is-active');
40051
+ }
40052
+ else {
40053
+ this._selection.add(value);
40054
+ addClass(e.currentTarget, 'is-active');
40055
+ }
40056
+ },
40057
+ sort: (dir) => {
40058
+ const items = this.$options.data || [];
40059
+ const selected = this._hidden.getSelection();
40060
+ if (!selected.length || selected.length === items.length) {
40061
+ return;
40062
+ }
40063
+ const selectedValues = new Set(selected.map((item) => item.value));
40064
+ // dir: up/down → 이동, up-all/down-all → 맨 위/맨 아래로 보내기
40065
+ if (dir.indexOf('all') > -1) {
40066
+ this.$options.data = items.slice().sort((a, b) => {
40067
+ const _sort = dir === 'up-all' ? -1 : 1;
40068
+ const aSel = selectedValues.has(a.value);
40069
+ const bSel = selectedValues.has(b.value);
40070
+ if (aSel && bSel) {
40071
+ return 0;
40072
+ }
40073
+ else if (aSel) {
40074
+ return _sort;
40075
+ }
40076
+ else if (bSel) {
40077
+ return _sort * -1;
40078
+ }
40079
+ return 0;
40080
+ });
40081
+ }
40082
+ else {
40083
+ if (dir === 'up') {
40084
+ const reordered = items.slice();
40085
+ // 한 번에 한 칸씩만 올려 상대 순서를 유지한다
40086
+ for (let i = 1; i < reordered.length; i++) {
40087
+ if (!selectedValues.has(reordered[i].value) || selectedValues.has(reordered[i - 1].value)) {
40088
+ continue;
40089
+ }
40090
+ const temp = reordered[i - 1];
40091
+ reordered[i - 1] = reordered[i];
40092
+ reordered[i] = temp;
40093
+ }
40094
+ this.$options.data = reordered;
40095
+ }
40096
+ else {
40097
+ let reordered = [];
40098
+ let itemsToMoveDown = [];
40099
+ items.forEach((option) => {
40100
+ // reordered: 최종 순서를 쌓는 버퍼, itemsToMoveDown: 아래로 밀어야 할 선택 항목 임시 저장
40101
+ // 비선택 항목은 흐름대로 push, 선택 항목은 dir에 따라 위/아래로 밀어 넣음
40102
+ if (!selectedValues.has(option.value)) {
40103
+ reordered.push(option);
40104
+ if (itemsToMoveDown.length) {
40105
+ reordered = reordered.concat(itemsToMoveDown);
40106
+ itemsToMoveDown = [];
40107
+ }
40108
+ }
40109
+ else if (dir === 'down') {
40110
+ itemsToMoveDown.push(option);
40111
+ }
40112
+ });
40113
+ if (itemsToMoveDown.length) {
40114
+ reordered = reordered.concat(itemsToMoveDown);
40115
+ itemsToMoveDown = [];
40116
+ }
40117
+ this.$options.data = reordered.slice();
40118
+ }
40119
+ }
40120
+ this._hidden.reRender();
40121
+ this._hidden.updateControls();
40122
+ this.$event(this, 'onChange', this.$options.data);
40123
+ },
40124
+ renderSub: () => {
40125
+ var _a, _b;
40126
+ const items = this.$options.data || [];
40127
+ const hasCols = items.some((item) => isArray$1(item.cols) && item.cols.length);
40128
+ if (!items.length) {
40129
+ return (createElement$1("ul", { className: "sortablelist-rows" },
40130
+ 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)));
40131
+ }
40132
+ return (createElement$1("ul", { className: 'sortablelist-rows' + (hasCols ? ' is-cols' : '') }, items.map((option, index) => {
40133
+ var _a;
40134
+ 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)))));
40135
+ })));
40136
+ },
40137
+ getSelection: () => {
40138
+ return this.$options.data.filter((option) => this._selection.has(option.value));
40139
+ },
40140
+ reRender: () => {
40141
+ const listContainer = find('ul', this.$options.delegates.list);
40142
+ listContainer && this.$template.reRender(listContainer, this._hidden.renderSub());
40143
+ },
40144
+ syncSelection: () => {
40145
+ const values = new Set(this.$options.data.map((item) => item.value));
40146
+ this._selection.forEach(val => {
40147
+ if (!values.has(val)) {
40148
+ this._selection.delete(val);
40149
+ }
40150
+ });
40151
+ },
40152
+ hydrateSelection: (items) => {
40153
+ this._selection.clear();
40154
+ items.forEach((item) => {
40155
+ if (item.selected) {
40156
+ this._selection.add(item.value);
40157
+ }
40158
+ });
40159
+ },
40160
+ validateData: (items) => {
40161
+ if (!isArray$1(items)) {
40162
+ throw new TypeError('Invalid SortableList data: data must be an array');
40163
+ }
40164
+ const seen = new Set();
40165
+ items.forEach((item) => {
40166
+ if (!item || typeof item.value !== 'string') {
40167
+ throw new TypeError('Invalid SortableList data: value must be string');
40168
+ }
40169
+ if (item.value === '') {
40170
+ throw new TypeError('Invalid SortableList data: value cannot be empty');
40171
+ }
40172
+ if (seen.has(item.value)) {
40173
+ throw new TypeError(`Invalid SortableList data: duplicate value '${item.value}'`);
40174
+ }
40175
+ seen.add(item.value);
40176
+ });
40177
+ },
40178
+ updateControls: () => {
40179
+ const hasData = (this.$options.data || []).length > 0;
40180
+ const disableButtons = this.$options.disabled || !hasData;
40181
+ if (disableButtons) {
40182
+ attr(findAll('button', this.$el), 'disabled', true);
40183
+ }
40184
+ else {
40185
+ removeAttr(findAll('button', this.$el), 'disabled');
40186
+ }
40187
+ if (!hasData) {
40188
+ addClass(this.$el, 'is-empty');
40189
+ }
40190
+ else {
40191
+ removeClass(this.$el, 'is-empty');
40192
+ }
40193
+ },
40194
+ clearDragState: () => {
40195
+ var _a;
40196
+ DRAG_STATE_CLASSES.forEach(className => {
40197
+ removeClass(findAll(`.${className}`, this.$el), className);
40198
+ });
40199
+ if ((_a = this._dragging) === null || _a === void 0 ? void 0 : _a.preview) {
40200
+ this._dragging.preview.remove();
40201
+ }
40202
+ this._dragging = null;
40203
+ },
40204
+ dragStart: (e) => {
40205
+ if (this.$options.disabled || !this.$options.draggable) {
40206
+ return;
40207
+ }
40208
+ // 기존 드래그 상태 정리 (이전 드래그가 중단된 경우 대비)
40209
+ this._hidden.clearDragState();
40210
+ const value = attr(e.currentTarget, 'data-value');
40211
+ if (!value) {
40212
+ return;
40213
+ }
40214
+ const selected = this._hidden.getSelection().map((item) => item.value);
40215
+ const isGroup = selected.length > 1 && selected.includes(value);
40216
+ const dragValues = isGroup ? selected : [value];
40217
+ this._dragging = { values: dragValues, isGroup: isGroup, preview: null };
40218
+ if (e.dataTransfer) {
40219
+ e.dataTransfer.setData('text/plain', value);
40220
+ e.dataTransfer.effectAllowed = 'move';
40221
+ if (isGroup) {
40222
+ const preview = document.createElement('div');
40223
+ preview.className = 'sortablelist-drag-preview';
40224
+ preview.setAttribute('data-count', String(dragValues.length));
40225
+ const list = document.createElement('div');
40226
+ list.className = 'sortablelist-drag-preview-list';
40227
+ dragValues.forEach((dragValue) => {
40228
+ const itemEl = find(`[data-value="${CSS.escape(dragValue)}"]`, this.$options.delegates.list);
40229
+ if (!(itemEl instanceof HTMLElement)) {
40230
+ return;
40231
+ }
40232
+ const clone = itemEl.cloneNode(true);
40233
+ removeClass(clone, CLASS_DRAGGING);
40234
+ removeClass(clone, CLASS_GROUP_DRAGGING);
40235
+ removeClass(clone, CLASS_DRAG_OVER_TOP);
40236
+ removeClass(clone, CLASS_DRAG_OVER_BOTTOM);
40237
+ addClass(clone, 'is-ghost');
40238
+ list.appendChild(clone);
40239
+ });
40240
+ preview.appendChild(list);
40241
+ document.body.appendChild(preview);
40242
+ e.dataTransfer.setDragImage(preview, this.DRAG_IMAGE_OFFSET_X, this.DRAG_IMAGE_OFFSET_Y);
40243
+ this._dragging.preview = preview;
40244
+ }
40245
+ }
40246
+ findAll('.dropdown-item', this.$options.delegates.list).forEach((node) => {
40247
+ const itemValue = attr(node, 'data-value');
40248
+ if (dragValues.includes(itemValue)) {
40249
+ addClass(node, CLASS_DRAGGING);
40250
+ if (isGroup) {
40251
+ addClass(node, CLASS_GROUP_DRAGGING);
40252
+ }
40253
+ }
40254
+ });
40255
+ },
40256
+ dragOver: (e) => {
40257
+ if (!this._dragging || this.$options.disabled || !this.$options.draggable) {
40258
+ return;
40259
+ }
40260
+ e.preventDefault();
40261
+ if (!(e.currentTarget instanceof HTMLElement)) {
40262
+ return;
40263
+ }
40264
+ const target = e.currentTarget;
40265
+ const value = attr(target, 'data-value');
40266
+ if (this._dragging.values.includes(value)) {
40267
+ return;
40268
+ }
40269
+ removeClass(target, CLASS_DRAG_OVER_TOP);
40270
+ removeClass(target, CLASS_DRAG_OVER_BOTTOM);
40271
+ const rect = target.getBoundingClientRect();
40272
+ const midpoint = rect.top + rect.height * this.DRAG_OVER_THRESHOLD_RATIO;
40273
+ if (e.clientY > midpoint) {
40274
+ addClass(target, CLASS_DRAG_OVER_BOTTOM);
40275
+ }
40276
+ else {
40277
+ addClass(target, CLASS_DRAG_OVER_TOP);
40278
+ }
40279
+ },
40280
+ dragLeave: (e) => {
40281
+ if (e.currentTarget instanceof HTMLElement) {
40282
+ removeClass(e.currentTarget, 'is-drag-over-top');
40283
+ removeClass(e.currentTarget, 'is-drag-over-bottom');
40284
+ }
40285
+ },
40286
+ drop: (e) => {
40287
+ if (!this._dragging || this.$options.disabled || !this.$options.draggable) {
40288
+ return;
40289
+ }
40290
+ e.preventDefault();
40291
+ if (!(e.currentTarget instanceof HTMLElement)) {
40292
+ return;
40293
+ }
40294
+ const targetEl = e.currentTarget;
40295
+ const targetValue = attr(targetEl, 'data-value');
40296
+ const dragValues = this._dragging.values;
40297
+ if (!targetValue || dragValues.includes(targetValue)) {
40298
+ this._hidden.clearDragState();
40299
+ return;
40300
+ }
40301
+ const items = this.$options.data;
40302
+ const dragItems = items.filter((item) => dragValues.includes(item.value));
40303
+ const remain = items.filter((item) => !dragValues.includes(item.value));
40304
+ const targetIndex = remain.findIndex((item) => item.value === targetValue);
40305
+ const rect = targetEl.getBoundingClientRect();
40306
+ const midpoint = rect.top + rect.height * this.DRAG_OVER_THRESHOLD_RATIO;
40307
+ const insertAfter = e.clientY > midpoint;
40308
+ const insertIndex = targetIndex === -1 ? remain.length : insertAfter ? targetIndex + 1 : targetIndex;
40309
+ remain.splice(insertIndex, 0, ...dragItems);
40310
+ this.$options.data = remain;
40311
+ this._hidden.reRender();
40312
+ this._hidden.updateControls();
40313
+ this.$event(this, 'onChange', this.$options.data);
40314
+ this._hidden.clearDragState();
40315
+ },
40316
+ dragEnd: () => {
40317
+ this._hidden.clearDragState();
40318
+ },
40319
+ setData: (data) => {
40320
+ // 드래그 중일 경우 상태 정리 (DOM 요소가 제거되면 dragend 이벤트가 발생하지 않을 수 있음)
40321
+ this._hidden.clearDragState();
40322
+ this._hidden.validateData(data);
40323
+ this.$options.data = data;
40324
+ this._hidden.hydrateSelection(data);
40325
+ this._hidden.syncSelection();
40326
+ this._hidden.reRender();
40327
+ this._hidden.updateControls();
40328
+ this.$event(this, 'onChange', this.$options.data);
40329
+ },
40330
+ disable: () => {
40331
+ // 드래그 중일 경우 상태 정리
40332
+ this._hidden.clearDragState();
40333
+ this.$options.disabled = true;
40334
+ addClass(this.$el, 'is-disabled');
40335
+ this._hidden.reRender();
40336
+ this._hidden.updateControls();
40337
+ },
40338
+ enable: () => {
40339
+ // 드래그 중일 경우 상태 정리
40340
+ this._hidden.clearDragState();
40341
+ this.$options.disabled = false;
40342
+ removeClass(this.$el, 'is-disabled');
40343
+ this._hidden.reRender();
40344
+ this._hidden.updateControls();
40345
+ }
40346
+ };
40347
+ this.config = {
40348
+ name: this.$selector.name || this._uid,
40349
+ data: [],
40350
+ delegates: {
40351
+ list: '.sortablelist-items'
40352
+ },
40353
+ buttonPosition: 'left',
40354
+ draggable: false,
40355
+ textSets: {
40356
+ noData: DEFAULT_NO_DATA
40357
+ },
40358
+ height: 150
40359
+ };
40360
+ this.events = {
40361
+ onChange: true
40362
+ };
40363
+ this.methods = {
40364
+ getData() {
40365
+ return this.$options.data;
40366
+ },
40367
+ setData(data) {
40368
+ const next = isArray$1(data) ? data : data && 'data' in data ? data.data : null;
40369
+ if (!isArray$1(next)) {
40370
+ throw new TypeError('Invalid SortableList data: data must be an array');
40371
+ }
40372
+ this._hidden.setData(next);
40373
+ },
40374
+ disable() {
40375
+ this._hidden.disable();
40376
+ },
40377
+ enable() {
40378
+ this._hidden.enable();
40379
+ }
40380
+ };
40381
+ this.$selector = this.$selector;
40382
+ this.$init(this, options);
40383
+ }
40384
+ template(config) {
40385
+ const styles = {};
40386
+ if (config.width) {
40387
+ styles.width = getUnit('width', config.width);
40388
+ }
40389
+ const listStyles = {};
40390
+ if (config.height) {
40391
+ const heightValue = getUnit('height', config.height);
40392
+ styles.height = heightValue;
40393
+ listStyles.height = heightValue;
40394
+ listStyles.maxHeight = heightValue;
40395
+ }
40396
+ const controlClass = 'sortablelist-controls gn-control is-small has-arrange is-vertical is-center' + (config.buttonPosition === 'right' ? ' is-right' : '');
40397
+ const buttonDisabled = config.disabled || (config.data || []).length === 0;
40398
+ return (createElement$1("div", { id: this._uid, className: 'gn-sortablelist' + (config.disabled ? ' is-disabled' : ''), style: styles },
40399
+ createElement$1("div", { className: controlClass },
40400
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'up-all'), disabled: buttonDisabled },
40401
+ createElement$1("span", { className: "gn-icon" },
40402
+ createElement$1("i", { className: "fa fa-light fa-angle-double-up" }))),
40403
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'up'), disabled: buttonDisabled },
40404
+ createElement$1("span", { className: "gn-icon" },
40405
+ createElement$1("i", { className: "fa fa-light fa-angle-up" }))),
40406
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'down'), disabled: buttonDisabled },
40407
+ createElement$1("span", { className: "gn-icon" },
40408
+ createElement$1("i", { className: "fa fa-light fa-angle-down" }))),
40409
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'down-all'), disabled: buttonDisabled },
40410
+ createElement$1("span", { className: "gn-icon" },
40411
+ createElement$1("i", { className: "fa fa-light fa-angle-double-down" })))),
40412
+ createElement$1("div", { className: "gn-dropdown is-opened sortablelist-items" },
40413
+ createElement$1("div", { className: "dropdown-items", style: listStyles }, this._hidden.renderSub()))));
40414
+ }
40415
+ beforeMount() {
40416
+ this._hidden.validateData(this.$options.data);
40417
+ this._hidden.hydrateSelection(this.$options.data);
40418
+ }
40419
+ completed() {
40420
+ this._hidden.updateControls();
40421
+ }
40422
+ destroyed() {
40423
+ this._hidden.clearDragState();
40424
+ }
40425
+ }
40426
+
39788
40427
  class Splitter extends GNCoreInstance {
39789
40428
  constructor(name, selector, options = {}) {
39790
40429
  super(name, selector, options);
@@ -40432,6 +41071,7 @@ var gnUIComp = {
40432
41071
  picklist: Picklist,
40433
41072
  progressbar: Progressbar,
40434
41073
  selectbutton: SelectButton,
41074
+ sortablelist: SortableList,
40435
41075
  splitter: Splitter,
40436
41076
  switch: Switch,
40437
41077
  syntaxinput: SyntaxInput,