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
@@ -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)) {
@@ -17066,7 +17112,7 @@ class GNUIState {
17066
17112
  remove($('#' + attr(rm, 'data-gnui')));
17067
17113
  }
17068
17114
  const findComponent = closerThis._getComponent($(rm));
17069
- if (findComponent && findComponent._uid && !findComponent.$el.parentNode && findComponent.$name !== 'modal') {
17115
+ if (findComponent && findComponent._uid && !((_a = findComponent.$el) === null || _a === void 0 ? void 0 : _a.parentNode) && findComponent.$name !== 'modal') {
17070
17116
  // state manager 에서 component 삭제
17071
17117
  closerThis._removeComponent(rm);
17072
17118
  // event manager 에서 unbind
@@ -17265,25 +17311,48 @@ class GNCoreInstance {
17265
17311
  }
17266
17312
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
17267
17313
  $update(element = this.$el, e) { }
17268
- $event(component, name, ...params) {
17314
+ /**
17315
+ * 컴포넌트 이벤트 디스패치 헬퍼
17316
+ *
17317
+ * - sync/async 핸들러 모두 지원한다.
17318
+ * - 하나 이상의 핸들러에서 `false`를 반환하면 `true`(cancelled) 를 반환한다.
17319
+ * - 단순 알림용(fire-and-forget) 이벤트는 반환값/await 없이 호출해도 된다.
17320
+ */
17321
+ async $event(component, name, ...params) {
17269
17322
  const eventManager = GNCoreEventManager.getInstance();
17270
- eventManager.dispatch(component._uid, name, params);
17323
+ return eventManager.dispatch(component._uid, name, params);
17271
17324
  }
17272
17325
  $destroy(component = this, removeEl = true) {
17326
+ var _a;
17273
17327
  const stateManager = GNUIState.getInstance();
17274
17328
  // state manager 를 통해 destroy 상태 dispatch
17275
17329
  stateManager._detectedCycle(component._uid, 'destroy');
17276
- // remove Component in state manager
17277
- stateManager._removeComponent(component.$selector);
17278
17330
  // remove DOM (by removeEl)
17279
17331
  if (removeEl) {
17280
17332
  style(component.$el, 'display', 'none');
17281
17333
  remove(component.$el);
17282
17334
  }
17335
+ if (((_a = component.$options) === null || _a === void 0 ? void 0 : _a._destroy) && isFunction(component.$options._destroy)) {
17336
+ component.$options._destroy();
17337
+ }
17283
17338
  // state manager 를 통해 destroy 상태 dispatch
17284
17339
  stateManager._detectedCycle(component._uid, 'destroyed');
17285
- // event manager 에서 등록 해제가 가장 마지막..
17340
+ // event manager 에서 등록 해제
17286
17341
  GNCoreEventManager.getInstance().removeAll(component._uid);
17342
+ // state manager에서 component 제거 (uid 기반으로 효율적 제거)
17343
+ stateManager._removeComponentByUid(component._uid);
17344
+ // 메모리 누수 방지: component의 모든 hasOwnProperty 제거
17345
+ // config, events, methods, _hidden 등 동적으로 추가된 속성 포함
17346
+ Object.keys(component).forEach((key) => {
17347
+ try {
17348
+ component[key] = null;
17349
+ delete component[key];
17350
+ }
17351
+ catch (e) {
17352
+ // readonly 속성 등 삭제 불가능한 경우 무시
17353
+ }
17354
+ });
17355
+ component = null;
17287
17356
  }
17288
17357
  }
17289
17358
 
@@ -33484,8 +33553,19 @@ class Dropdown extends GNCoreInstance {
33484
33553
  });
33485
33554
  if (this.$options.value) {
33486
33555
  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);
33556
+ // multiple 모드에서 다양한 타입의 value를 문자열 배열로 변환
33557
+ // 지원 타입: 문자열(쉼표 구분), 객체 배열, 단일 객체, 문자열 배열
33558
+ const values = typeof this.$options.value === 'string'
33559
+ ? this.$options.value.split(',') // 케이스 1: 'item1,item2,item3'
33560
+ : Array.isArray(this.$options.value)
33561
+ ? 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']
33562
+ : typeof this.$options.value === 'object' && this.$options.value !== null && 'value' in this.$options.value
33563
+ ? [String(this.$options.value.value)] // 케이스 2: {value:'item1', text:'항목1'}
33564
+ : [String(this.$options.value)]; // 기타: 숫자 등
33565
+ this.$options.value = this.$options.flatData.filter((opt) => {
33566
+ const optValue = typeof opt.value === 'string' ? opt.value : String(opt.value);
33567
+ return values.includes(optValue) && opt.text;
33568
+ });
33489
33569
  }
33490
33570
  else {
33491
33571
  this.$options.value = this.$options.flatData.find((opt) => opt.value + '' === this.$options.value + '' && opt.text);
@@ -37000,26 +37080,8 @@ class DataGrid extends GNCoreInstance {
37000
37080
  }
37001
37081
  this.$event(this, 'onSort', column);
37002
37082
  },
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();
37083
+ renderHeader: (columns, isReset = false) => {
37084
+ this._setColumnsTemplate(isReset);
37023
37085
  return (createElement$1("div", { className: "gn-datagrid-header-row", style: {
37024
37086
  'grid-template-columns': this._columnsTemplate.join(' ')
37025
37087
  } },
@@ -37058,6 +37120,10 @@ class DataGrid extends GNCoreInstance {
37058
37120
  column.draggable && (this.$options.headers ? idx < this.$options.headers.length - 1 : true) && createElement$1("span", { className: "is-handle", "data-index": idx })));
37059
37121
  },
37060
37122
  renderBody: (data, columns) => {
37123
+ // 헤더가 숨겨진 경우에도 body 렌더 전에 템플릿 폭을 준비한다
37124
+ if (!this._columnsTemplate || !this._columnsTemplate.length) {
37125
+ this._setColumnsTemplate();
37126
+ }
37061
37127
  rowIdx$1 = 0;
37062
37128
  return (createElement$1("div", { className: "gn-datagrid-body", style: {
37063
37129
  maxHeight: this.$options.bodyHeight ? this.$options.bodyHeight : 'auto'
@@ -37071,7 +37137,6 @@ class DataGrid extends GNCoreInstance {
37071
37137
  });
37072
37138
  },
37073
37139
  renderRow: (row, columns, depth = 0, hasChild, isOpened, isCheck = false) => {
37074
- row._depth = depth;
37075
37140
  const _index = rowIdx$1++;
37076
37141
  if (row.isChecked) {
37077
37142
  isCheck = true;
@@ -37205,8 +37270,9 @@ class DataGrid extends GNCoreInstance {
37205
37270
  e.stopPropagation();
37206
37271
  toggler = parents(e.currentTarget, '.gn-datagrid-body-row');
37207
37272
  }
37208
- const children = nextUntil(toggler, '.gn-datagrid-body-row[data-depth="' + row._depth + '"]').filter((x) => {
37209
- return x.dataset.depth > row._depth;
37273
+ const rowDepth = Number(attr(toggler, 'data-depth')) || 0;
37274
+ const children = nextUntil(toggler, '.gn-datagrid-body-row[data-depth="' + rowDepth + '"]').filter((x) => {
37275
+ return Number(attr(x, 'data-depth')) > rowDepth;
37210
37276
  });
37211
37277
  type = type ? type : hasClass(toggler, 'is-collapsed') ? 'expand' : 'collapse';
37212
37278
  if (type === 'collapse') {
@@ -37222,7 +37288,7 @@ class DataGrid extends GNCoreInstance {
37222
37288
  //show childs
37223
37289
  removeClass(toggler, 'is-collapsed');
37224
37290
  removeClass(children.filter((x) => {
37225
- return x.dataset.depth == row._depth + 1;
37291
+ return Number(attr(x, 'data-depth')) == rowDepth + 1;
37226
37292
  }), 'is-hidden');
37227
37293
  this.$event(this, 'onToggle', 'expanded', row, index$1(toggler));
37228
37294
  }
@@ -37280,13 +37346,15 @@ class DataGrid extends GNCoreInstance {
37280
37346
  e.stopPropagation();
37281
37347
  const checker = parents(e.currentTarget, '.gn-datagrid-body-row');
37282
37348
  const checkerState = e.target.checked;
37283
- find('.is-allChecker', this.$el).checked = false;
37349
+ const allChecker = find('.is-allChecker', this.$el);
37350
+ allChecker && (allChecker.checked = false);
37351
+ const rowDepth = Number(attr(checker, 'data-depth')) || 0;
37284
37352
  // 1. row에 자식노드가 있는지 확인한다.
37285
37353
  if (this.$options.checkCapturing && row[this.$options.childField] && row[this.$options.childField].length) {
37286
37354
  // 2. 자식노드가 있는경우 자식 체크박스도 함께 토글한다.
37287
- nextUntil(checker, '.gn-datagrid-body-row[data-depth="' + row._depth + '"]')
37355
+ nextUntil(checker, '.gn-datagrid-body-row[data-depth="' + rowDepth + '"]')
37288
37356
  .filter((x) => {
37289
- return x.dataset.depth > row._depth;
37357
+ return Number(attr(x, 'data-depth')) > rowDepth;
37290
37358
  })
37291
37359
  .forEach((x) => {
37292
37360
  const _checker = find('.is-rowChecker', x);
@@ -37296,17 +37364,17 @@ class DataGrid extends GNCoreInstance {
37296
37364
  });
37297
37365
  }
37298
37366
  // 3. 체크 해제인 경우만 부모노드가 있는지 확인한다.
37299
- if (this.$options.checkCapturing && row._depth > 0 && !checkerState) {
37367
+ if (this.$options.checkCapturing && rowDepth > 0 && !checkerState) {
37300
37368
  // 4. 부모노드가 체크되어 있는지 확인한다
37301
37369
  const exeDepth = [];
37302
37370
  prevUntil(checker, '.gn-datagrid-body-row[data-depth="0"]')
37303
37371
  .filter((x) => {
37304
- const _thisDepth = x.dataset.depth;
37372
+ const _thisDepth = attr(x, 'data-depth');
37305
37373
  if (exeDepth.includes(_thisDepth)) {
37306
37374
  return false;
37307
37375
  }
37308
37376
  exeDepth.push(_thisDepth);
37309
- return _thisDepth < row._depth;
37377
+ return Number(_thisDepth) < rowDepth;
37310
37378
  })
37311
37379
  .forEach((x) => {
37312
37380
  const _checker = find('.is-rowChecker', x);
@@ -37322,9 +37390,21 @@ class DataGrid extends GNCoreInstance {
37322
37390
  if (hasCheck === undefined) {
37323
37391
  hasCheck = this.$options.hasCheck;
37324
37392
  }
37325
- this.$options.headers = headers;
37393
+ const prevHeaders = [...(this.$options.headers || [])];
37394
+ // _prepareHeaders가 배열을 변경하므로 비교용(prevHeaders)과 가공용(baseHeaders)을 분리한다
37395
+ const baseHeaders = headers ? [...headers] : [...prevHeaders];
37396
+ const preparedHeaders = this._prepareHeaders(baseHeaders);
37397
+ // 헤더 배열의 내용(길이, key)이 바뀐 경우에만 DOM 기반 폭 계산을 리셋한다
37398
+ 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
37399
  this.$options.hasCheck = hasCheck;
37327
- this.$template.reRender(find('.gn-datagrid-header-row', this.$el), this._hidden.renderHeader(this.$options.headers));
37400
+ const headerRow = find('.gn-datagrid-header-row', this.$el);
37401
+ // 헤더가 없을 때도 컬럼 수/폭 변경을 반영하기 위해 템플릿을 갱신한다
37402
+ if (this.$options.hasHeader && headerRow) {
37403
+ this.$template.reRender(headerRow, this._hidden.renderHeader(preparedHeaders, isReset));
37404
+ }
37405
+ else {
37406
+ this._setColumnsTemplate(isReset);
37407
+ }
37328
37408
  this._hidden.resetData(data ? arrClone(data) : this.$options.data);
37329
37409
  this.$render(this.$options);
37330
37410
  isFunction(resolve) && resolve();
@@ -37339,6 +37419,10 @@ class DataGrid extends GNCoreInstance {
37339
37419
  });
37340
37420
  },
37341
37421
  awaitData: (data) => {
37422
+ // asyncData 콜백 실행 중 컴포넌트가 destroy된 경우 안전하게 종료
37423
+ if (!this.$options || !this._hidden) {
37424
+ return;
37425
+ }
37342
37426
  if (this.$options.asyncData && this.$options.paginator && !this._paginator) {
37343
37427
  this._paginator = new Pagination('pagination', find('.gn-datagrid-footer', this.$el), {
37344
37428
  total: this.$options.paginator.total || 0,
@@ -37363,7 +37447,8 @@ class DataGrid extends GNCoreInstance {
37363
37447
  this._fixCellStyleOnDraggable();
37364
37448
  // 체크박스가 있는경우 전체 체크항목을 해제해준다
37365
37449
  if (this.$options.hasCheck) {
37366
- find('.is-allChecker', this.$el).checked = false;
37450
+ const allChecker = find('.is-allChecker', this.$el);
37451
+ allChecker && (allChecker.checked = false);
37367
37452
  }
37368
37453
  isFunction(resolve) && resolve();
37369
37454
  });
@@ -37398,8 +37483,18 @@ class DataGrid extends GNCoreInstance {
37398
37483
  stopRowSelectEvent: (e) => {
37399
37484
  e.stopPropagation();
37400
37485
  },
37401
- deleteRow: (index) => {
37402
- this.$options.data = this.$options.data.filter((_data, idx) => index !== idx);
37486
+ deleteRow: async (index) => {
37487
+ var _a;
37488
+ const confirmMessage = (_a = this.$options.textSets) === null || _a === void 0 ? void 0 : _a.deleteConfirmMessage;
37489
+ if (confirmMessage && !window.confirm(confirmMessage)) {
37490
+ return;
37491
+ }
37492
+ const removedData = this._hidden.findData(index);
37493
+ const cancelled = await this.$event(this, 'onDelete', removedData, index);
37494
+ if (cancelled) {
37495
+ return;
37496
+ }
37497
+ this._hidden.deleteData(index);
37403
37498
  this._hidden.resetData(this.$options.data);
37404
37499
  this.$event(this, 'onChange', this.$options.data);
37405
37500
  },
@@ -37434,22 +37529,38 @@ class DataGrid extends GNCoreInstance {
37434
37529
  col.offHover && col.offHover.call(this, row, col, index, e);
37435
37530
  },
37436
37531
  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;
37532
+ return this._hidden.walkByIndex(index, 'find');
37533
+ },
37534
+ deleteData: (index) => {
37535
+ return this._hidden.walkByIndex(index, 'delete');
37536
+ },
37537
+ walkByIndex: (index, action) => {
37538
+ let deter = 0;
37539
+ let result = null;
37540
+ const findIndex = (datas) => {
37541
+ for (let i = 0; i < datas.length; i++) {
37542
+ if (deter === index) {
37543
+ switch (action) {
37544
+ case 'find':
37545
+ result = datas[i];
37546
+ break;
37547
+ case 'delete':
37548
+ result = datas.splice(i, 1)[0];
37549
+ break;
37550
+ }
37442
37551
  return true;
37443
37552
  }
37444
- ++deter;
37445
- if (isArray$1(data[this.$options.childField]) && data[this.$options.childField].length) {
37446
- return findIndex(data[this.$options.childField], index);
37553
+ deter++;
37554
+ const children = datas[i][this.$options.childField];
37555
+ if (Array.isArray(children) && children.length) {
37556
+ if (findIndex(children))
37557
+ return true;
37447
37558
  }
37448
- return false;
37449
- });
37559
+ }
37560
+ return false;
37450
37561
  };
37451
- findIndex(this.$options.data, index);
37452
- return indexData;
37562
+ findIndex(this.$options.data);
37563
+ return result;
37453
37564
  },
37454
37565
  getChecked: () => {
37455
37566
  return findAll('.is-rowChecker', this.$el)
@@ -37536,11 +37647,13 @@ class DataGrid extends GNCoreInstance {
37536
37647
  hasOrder: false,
37537
37648
  hasDelete: false,
37538
37649
  isEllipsis: false,
37650
+ hasHeader: true,
37539
37651
  data: [],
37540
37652
  textSets: {
37541
37653
  noData: 'No records available.',
37542
37654
  orderLabel: '',
37543
- deleteLabel: ''
37655
+ deleteLabel: '',
37656
+ deleteConfirmMessage: ''
37544
37657
  },
37545
37658
  childField: 'child',
37546
37659
  checkCapturing: true,
@@ -37558,7 +37671,8 @@ class DataGrid extends GNCoreInstance {
37558
37671
  onCheck: true,
37559
37672
  onDoubleClick: true,
37560
37673
  onChange: true,
37561
- onDragEnd: true
37674
+ onDragEnd: true,
37675
+ onDelete: true
37562
37676
  };
37563
37677
  this.methods = {
37564
37678
  reRender(options) {
@@ -37618,18 +37732,21 @@ class DataGrid extends GNCoreInstance {
37618
37732
  this.$selector = this.$selector;
37619
37733
  this.$init(this, options);
37620
37734
  }
37621
- _setColumnsTemplate() {
37735
+ _setColumnsTemplate(isReset = false) {
37622
37736
  // header cell의 각 넓이를 배열로 가져온다
37623
37737
  // ! 모든 컬럼의 넓이가 지정된 경우 이동(btnOrder), 삭제(btnDelete) 컬럼을 제외한 마지막 컬럼은 1fr로 고정
37624
37738
  const _isfixedAllWidth = this.$options.headers.every((header) => { var _a; return ((_a = header.style) === null || _a === void 0 ? void 0 : _a.width) !== undefined; });
37625
37739
  const _fixedTemplateColumn = this.$options.headers.findLast((header) => !this._isSystemAddedColumn(header.key) && !header.isHidden);
37740
+ // 헤더 DOM이 없거나 모든 일반 컬럼이 숨겨진 경우에도 안전하게 비교할 수 있도록 key만 옵셔널하게 캐싱한다
37741
+ const _fixedTemplateKey = _fixedTemplateColumn === null || _fixedTemplateColumn === void 0 ? void 0 : _fixedTemplateColumn.key;
37626
37742
  const columns = findAll('.gn-datagrid-header-cell', this.$el);
37627
- if (this.$el && columns.length) {
37743
+ // isReset이면 기존 DOM 폭을 재사용하지 않고 헤더 정의로 재계산
37744
+ if (this.$el && columns.length && !isReset) {
37628
37745
  this._columnsTemplate = findAll('.gn-datagrid-header-cell', this.$el).map((header, idx) => {
37629
37746
  if (this.$options.headers[idx].isHidden) {
37630
37747
  return '';
37631
37748
  }
37632
- else if (_isfixedAllWidth && this.$options.headers[idx].key === _fixedTemplateColumn.key) {
37749
+ else if (_isfixedAllWidth && _fixedTemplateKey && this.$options.headers[idx].key === _fixedTemplateKey) {
37633
37750
  return '1fr';
37634
37751
  }
37635
37752
  else {
@@ -37643,7 +37760,7 @@ class DataGrid extends GNCoreInstance {
37643
37760
  if (header.isHidden) {
37644
37761
  return '';
37645
37762
  }
37646
- else if (_isfixedAllWidth && header.key === _fixedTemplateColumn.key) {
37763
+ else if (_isfixedAllWidth && _fixedTemplateKey && header.key === _fixedTemplateKey) {
37647
37764
  return '1fr';
37648
37765
  }
37649
37766
  else {
@@ -37664,18 +37781,45 @@ class DataGrid extends GNCoreInstance {
37664
37781
  _isSystemAddedColumn(key) {
37665
37782
  return ['btnOrder', 'btnDelete'].includes(key);
37666
37783
  }
37784
+ // 옵션에 따른 추가 해더 구성
37785
+ _prepareHeaders(headers = []) {
37786
+ const hasOrderColumn = headers.some((header) => header.key === 'btnOrder');
37787
+ const hasDeleteColumn = headers.some((header) => header.key === 'btnDelete');
37788
+ if (this.$options.hasOrder && !this.$options.readonly && !hasOrderColumn) {
37789
+ headers.push({
37790
+ label: this.$options.textSets.orderLabel,
37791
+ key: 'btnOrder',
37792
+ style: {
37793
+ width: '50px'
37794
+ }
37795
+ });
37796
+ }
37797
+ if (this.$options.hasDelete && !this.$options.readonly && !hasDeleteColumn) {
37798
+ headers.push({
37799
+ label: this.$options.textSets.deleteLabel,
37800
+ key: 'btnDelete',
37801
+ style: {
37802
+ width: '30px'
37803
+ }
37804
+ });
37805
+ }
37806
+ this.$options.headers = headers;
37807
+ return headers;
37808
+ }
37667
37809
  template(config) {
37668
37810
  const styles = {};
37811
+ const headers = this._prepareHeaders(config.headers);
37669
37812
  return (createElement$1("div", { id: this._uid, className: 'gn-datagrid' +
37670
37813
  (config.style ? ' is-' + config.style : '') +
37671
37814
  (config.isEllipsis ? ' is-ellipsis' : '') +
37672
37815
  (config.bodyHeight ? ' has-fixed-body' : '') +
37673
37816
  (config.fixHeader ? ' has-fixed-header' : '') +
37674
37817
  (config.fixFooter ? ' has-fixed-footer' : '') +
37818
+ (!config.hasHeader ? ' is-headless' : '') +
37675
37819
  (config.data.some((d) => isArray$1(d[this.$options.childField])) ? ' has-left-padding' : '') +
37676
37820
  (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)),
37821
+ config.hasHeader && createElement$1("div", { className: "gn-datagrid-header" }, this._hidden.renderHeader(headers)),
37822
+ 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
37823
  config.paginator /* 페이지네이터 옵션 확인 */ && createElement$1("div", { className: "gn-datagrid-footer" })));
37680
37824
  }
37681
37825
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -37692,18 +37836,20 @@ class DataGrid extends GNCoreInstance {
37692
37836
  }
37693
37837
  }
37694
37838
  completed() {
37695
- if (this.$options.fixHeader) {
37839
+ if (this.$options.fixHeader && this.$options.hasHeader) {
37696
37840
  const body = find('.gn-datagrid-contents', this.$el);
37697
37841
  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);
37842
+ if (header) {
37843
+ const _offset = offset(header);
37844
+ this.$options.bodyTopMargin = _offset.height ? _offset.height - 1 + 'px' : '2.4rem';
37845
+ css$1(body, 'margin-top', this.$options.bodyTopMargin);
37846
+ }
37701
37847
  if (this.$options.paginator) {
37702
37848
  this.$options.bodyBottomMargin = '2.4rem';
37703
37849
  css$1(body, 'margin-bottom', this.$options.bodyBottomMargin);
37704
37850
  }
37705
37851
  }
37706
- if (this.$options.fixHeader || this.$options.bodyHeight) {
37852
+ if ((this.$options.fixHeader && this.$options.hasHeader) || this.$options.bodyHeight) {
37707
37853
  this._hidden.setBlankHeader();
37708
37854
  on(window, 'resize', this._hidden.setBlankHeader);
37709
37855
  }
@@ -39048,7 +39194,9 @@ class MenuButton extends GNCoreInstance {
39048
39194
  super(name, selector, options);
39049
39195
  this._hidden = {
39050
39196
  open: () => {
39051
- addClass(this.$el, 'is-open');
39197
+ if (!this.$options.disabled) {
39198
+ addClass(this.$el, 'is-open');
39199
+ }
39052
39200
  },
39053
39201
  close: () => {
39054
39202
  removeClass(this.$el, 'is-open');
@@ -39060,6 +39208,62 @@ class MenuButton extends GNCoreInstance {
39060
39208
  changeText: (buttonText) => {
39061
39209
  this.$options.textSets.buttonText = buttonText;
39062
39210
  html(find('.menuButton-text', this.$el), buttonText);
39211
+ },
39212
+ disable: () => {
39213
+ this.$options.disabled = true;
39214
+ const buttonEl = find('button', this.$el);
39215
+ if (buttonEl) {
39216
+ attr(buttonEl, 'disabled', true);
39217
+ }
39218
+ addClass(this.$el, 'is-disabled');
39219
+ this._hidden.close();
39220
+ },
39221
+ enable: () => {
39222
+ this.$options.disabled = false;
39223
+ const buttonEl = find('button', this.$el);
39224
+ if (buttonEl) {
39225
+ removeAttr(buttonEl, 'disabled');
39226
+ }
39227
+ removeClass(this.$el, 'is-disabled');
39228
+ },
39229
+ renderMenus: (menus, depth = 0, parentPath = '') => {
39230
+ return (createElement$1("ul", { className: depth > 0 ? 'menuButton-submenu' : '' }, menus.map((menu, index) => {
39231
+ const hasChild = menu.child && isArray$1(menu.child) && menu.child.length > 0;
39232
+ const hasHtml = !!menu.html;
39233
+ // html이 있으면 innerHTML이 모든 자식 요소를 덮어쓰므로 서브메뉴를 렌더링하지 않음
39234
+ const canRenderChild = hasChild && depth < 2 && !hasHtml; // 최대 2단계까지만 허용
39235
+ const isDisabled = menu.disabled === true;
39236
+ const isActived = menu.actived === true;
39237
+ // 부모 경로를 포함한 고유한 ID 생성
39238
+ const currentPath = parentPath ? `${parentPath}-${index}` : `${index}`;
39239
+ const uniqueId = `${this._uid}-${currentPath}`;
39240
+ return (createElement$1("li", { id: uniqueId, className: 'menuButton-menu' +
39241
+ (this.$options.align ? ' has-text-' + this.$options.align : '') +
39242
+ (canRenderChild ? ' has-submenu' : '') +
39243
+ (depth > 0 ? ' is-submenu-item' : '') +
39244
+ (isDisabled ? ' is-disabled' : '') +
39245
+ (isActived ? ' is-actived' : ''), "on-click": (e) => {
39246
+ // disabled 상태이거나 자식 메뉴가 있는 경우 클릭 이벤트 처리하지 않음
39247
+ if (isDisabled) {
39248
+ e.stopPropagation();
39249
+ e.preventDefault();
39250
+ return;
39251
+ }
39252
+ // 자식 메뉴가 없는 경우에만 select 이벤트 발생
39253
+ if (!canRenderChild) {
39254
+ e.stopPropagation();
39255
+ this._hidden.select.call(this, menu, e);
39256
+ }
39257
+ }, innerHTML: hasHtml ? menu.html : '' },
39258
+ hasHtml ? ('') : (createElement$1("span", { className: "menuButton-menu-content" },
39259
+ createElement$1("span", { className: "menuButton-menu-text" }, menu.text),
39260
+ canRenderChild && (createElement$1("span", { className: "menuButton-menu-arrow" },
39261
+ createElement$1("i", { className: "fas fa-caret-right" }))))),
39262
+ canRenderChild && this._hidden.renderMenus.call(this, menu.child, depth + 1, currentPath)));
39263
+ })));
39264
+ },
39265
+ renderSub: (data) => {
39266
+ 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
39267
  }
39064
39268
  };
39065
39269
  this.config = {
@@ -39079,6 +39283,19 @@ class MenuButton extends GNCoreInstance {
39079
39283
  },
39080
39284
  buttonText(text) {
39081
39285
  this._hidden.changeText(text);
39286
+ },
39287
+ reRender(data) {
39288
+ this.$options.data = data;
39289
+ const menuMenusEl = find('.menuButton-menus > div', this.$el);
39290
+ if (menuMenusEl && this.$template) {
39291
+ this.$template.reRender(menuMenusEl, this._hidden.renderSub.call(this, data));
39292
+ }
39293
+ },
39294
+ disabled() {
39295
+ this._hidden.disable();
39296
+ },
39297
+ enabled() {
39298
+ this._hidden.enable();
39082
39299
  }
39083
39300
  };
39084
39301
  this.$selector = this.$selector;
@@ -39089,23 +39306,19 @@ class MenuButton extends GNCoreInstance {
39089
39306
  if (config.width) {
39090
39307
  styles.width = getUnit('width', config.width);
39091
39308
  }
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 },
39309
+ return (createElement$1("div", { id: this._uid, className: 'gn-menuButton' +
39310
+ (config.color ? ' is-' + config.color : '') +
39311
+ (config.style ? ' is-' + config.style : '') +
39312
+ (config.size ? ' is-' + config.size : '') +
39313
+ (config.disabled ? ' is-disabled' : ''), style: styles },
39314
+ createElement$1("button", { type: "button", className: config.align ? 'has-text-' + config.align : '', disabled: config.disabled, "on-click": this._hidden.open },
39102
39315
  config.icon && (createElement$1("span", { className: 'gn-icon is-' + (config.size === 'large' ? 'medium' : config.size === 'medium' ? 'normal' : 'small') },
39103
39316
  createElement$1("i", { className: 'fas fa-' + config.icon }),
39104
39317
  ' ')),
39105
39318
  createElement$1("span", { className: "gn-icon is-small menuButton-icon" },
39106
39319
  createElement$1("i", { className: "fas fa-caret-down" })),
39107
39320
  createElement$1("span", { className: "menuButton-text" }, config.textSets.buttonText)),
39108
- createElement$1("div", { className: "menuButton-menus" }, renderSub(config.data))));
39321
+ createElement$1("div", { className: "menuButton-menus" }, this._hidden.renderSub.call(this, config.data))));
39109
39322
  }
39110
39323
  completed() {
39111
39324
  // 해당 컴포넌트 외 클릭 시 menu panel 숨김
@@ -39316,6 +39529,7 @@ class MultiTextArea extends GNCoreInstance {
39316
39529
  }
39317
39530
  }
39318
39531
 
39532
+ library$1.add(icons$1, icons);
39319
39533
  class Picklist extends GNCoreInstance {
39320
39534
  constructor(name, selector, options = {}) {
39321
39535
  super(name, selector, options);
@@ -39457,17 +39671,26 @@ class Picklist extends GNCoreInstance {
39457
39671
  },
39458
39672
  renderSub: (item) => {
39459
39673
  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))))));
39674
+ return (createElement$1("ul", null, items.map((option, index) => {
39675
+ var _a;
39676
+ 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
39677
+ ? null
39678
+ : this._hidden.move.bind(this, item === 'source' ? 'add' : 'remove', [
39679
+ {
39680
+ value: option.value,
39681
+ text: option.text,
39682
+ html: (_a = option.html) !== null && _a !== void 0 ? _a : null
39683
+ }
39684
+ ]) },
39685
+ createElement$1("span", { className: "dropdown-text", innerHTML: option.html ? option.html : '' }, option.html ? ('') : option.icon ? (createElement$1("span", null,
39686
+ createElement$1("span", { className: 'gn-icon' + (this.$options.size ? ' is-' + this.$options.size : '') },
39687
+ createElement$1("i", { className: (this.isBrandIcon(option.icon) ? 'fab' : 'fa') + ` fa-${option.icon}` })),
39688
+ escapeEntity(option.text))) : (escapeEntity(option.text)))));
39689
+ })));
39467
39690
  },
39468
39691
  getSelection: (target) => {
39469
39692
  return findAll('.is-active', this.$options.delegates[target]).map((select) => {
39470
- return { value: attr(select, 'data-value'), text: text$1(select) };
39693
+ return this.$options.data[target].find((option) => option.value === attr(select, 'data-value'));
39471
39694
  });
39472
39695
  },
39473
39696
  disable: () => {
@@ -39627,6 +39850,11 @@ class Picklist extends GNCoreInstance {
39627
39850
  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
39851
  }
39629
39852
  }
39853
+ isBrandIcon(iconName) {
39854
+ const iconLookup = { prefix: 'fab', iconName: iconName };
39855
+ const iconDefinition = findIconDefinition$1(iconLookup);
39856
+ return iconDefinition !== undefined;
39857
+ }
39630
39858
  }
39631
39859
 
39632
39860
  class Progressbar extends GNCoreInstance {
@@ -39785,6 +40013,409 @@ class SelectButton extends GNCoreInstance {
39785
40013
  }
39786
40014
  }
39787
40015
 
40016
+ const CLASS_DRAGGING = 'is-dragging';
40017
+ const CLASS_GROUP_DRAGGING = 'is-group-dragging';
40018
+ const CLASS_DRAG_OVER_TOP = 'is-drag-over-top';
40019
+ const CLASS_DRAG_OVER_BOTTOM = 'is-drag-over-bottom';
40020
+ const DEFAULT_NO_DATA = 'No records available.';
40021
+ const DRAG_STATE_CLASSES = [CLASS_DRAGGING, CLASS_GROUP_DRAGGING, CLASS_DRAG_OVER_TOP, CLASS_DRAG_OVER_BOTTOM];
40022
+ class SortableList extends GNCoreInstance {
40023
+ constructor(name, selector, options = {}) {
40024
+ super(name, selector, options);
40025
+ this._dragging = null;
40026
+ this._selection = new Set();
40027
+ this.DRAG_IMAGE_OFFSET_X = 0;
40028
+ this.DRAG_IMAGE_OFFSET_Y = 0;
40029
+ this.DRAG_OVER_THRESHOLD_RATIO = 0.5;
40030
+ this._hidden = {
40031
+ toggle: (e) => {
40032
+ if (this.$options.disabled) {
40033
+ return;
40034
+ }
40035
+ const value = attr(e.currentTarget, 'data-value');
40036
+ const hasItem = this.$options.data.some((option) => option.value === value);
40037
+ if (!value || !hasItem) {
40038
+ return;
40039
+ }
40040
+ if (this._selection.has(value)) {
40041
+ this._selection.delete(value);
40042
+ removeClass(e.currentTarget, 'is-active');
40043
+ }
40044
+ else {
40045
+ this._selection.add(value);
40046
+ addClass(e.currentTarget, 'is-active');
40047
+ }
40048
+ },
40049
+ sort: (dir) => {
40050
+ const items = this.$options.data || [];
40051
+ const selected = this._hidden.getSelection();
40052
+ if (!selected.length || selected.length === items.length) {
40053
+ return;
40054
+ }
40055
+ const selectedValues = new Set(selected.map((item) => item.value));
40056
+ // dir: up/down → 이동, up-all/down-all → 맨 위/맨 아래로 보내기
40057
+ if (dir.indexOf('all') > -1) {
40058
+ this.$options.data = items.slice().sort((a, b) => {
40059
+ const _sort = dir === 'up-all' ? -1 : 1;
40060
+ const aSel = selectedValues.has(a.value);
40061
+ const bSel = selectedValues.has(b.value);
40062
+ if (aSel && bSel) {
40063
+ return 0;
40064
+ }
40065
+ else if (aSel) {
40066
+ return _sort;
40067
+ }
40068
+ else if (bSel) {
40069
+ return _sort * -1;
40070
+ }
40071
+ return 0;
40072
+ });
40073
+ }
40074
+ else {
40075
+ if (dir === 'up') {
40076
+ const reordered = items.slice();
40077
+ // 한 번에 한 칸씩만 올려 상대 순서를 유지한다
40078
+ for (let i = 1; i < reordered.length; i++) {
40079
+ if (!selectedValues.has(reordered[i].value) || selectedValues.has(reordered[i - 1].value)) {
40080
+ continue;
40081
+ }
40082
+ const temp = reordered[i - 1];
40083
+ reordered[i - 1] = reordered[i];
40084
+ reordered[i] = temp;
40085
+ }
40086
+ this.$options.data = reordered;
40087
+ }
40088
+ else {
40089
+ let reordered = [];
40090
+ let itemsToMoveDown = [];
40091
+ items.forEach((option) => {
40092
+ // reordered: 최종 순서를 쌓는 버퍼, itemsToMoveDown: 아래로 밀어야 할 선택 항목 임시 저장
40093
+ // 비선택 항목은 흐름대로 push, 선택 항목은 dir에 따라 위/아래로 밀어 넣음
40094
+ if (!selectedValues.has(option.value)) {
40095
+ reordered.push(option);
40096
+ if (itemsToMoveDown.length) {
40097
+ reordered = reordered.concat(itemsToMoveDown);
40098
+ itemsToMoveDown = [];
40099
+ }
40100
+ }
40101
+ else if (dir === 'down') {
40102
+ itemsToMoveDown.push(option);
40103
+ }
40104
+ });
40105
+ if (itemsToMoveDown.length) {
40106
+ reordered = reordered.concat(itemsToMoveDown);
40107
+ itemsToMoveDown = [];
40108
+ }
40109
+ this.$options.data = reordered.slice();
40110
+ }
40111
+ }
40112
+ this._hidden.reRender();
40113
+ this._hidden.updateControls();
40114
+ this.$event(this, 'onChange', this.$options.data);
40115
+ },
40116
+ renderSub: () => {
40117
+ var _a, _b;
40118
+ const items = this.$options.data || [];
40119
+ const hasCols = items.some((item) => isArray$1(item.cols) && item.cols.length);
40120
+ if (!items.length) {
40121
+ return (createElement$1("ul", { className: "sortablelist-rows" },
40122
+ 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)));
40123
+ }
40124
+ return (createElement$1("ul", { className: 'sortablelist-rows' + (hasCols ? ' is-cols' : '') }, items.map((option, index) => {
40125
+ var _a;
40126
+ 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)))));
40127
+ })));
40128
+ },
40129
+ getSelection: () => {
40130
+ return this.$options.data.filter((option) => this._selection.has(option.value));
40131
+ },
40132
+ reRender: () => {
40133
+ const listContainer = find('ul', this.$options.delegates.list);
40134
+ listContainer && this.$template.reRender(listContainer, this._hidden.renderSub());
40135
+ },
40136
+ syncSelection: () => {
40137
+ const values = new Set(this.$options.data.map((item) => item.value));
40138
+ this._selection.forEach(val => {
40139
+ if (!values.has(val)) {
40140
+ this._selection.delete(val);
40141
+ }
40142
+ });
40143
+ },
40144
+ hydrateSelection: (items) => {
40145
+ this._selection.clear();
40146
+ items.forEach((item) => {
40147
+ if (item.selected) {
40148
+ this._selection.add(item.value);
40149
+ }
40150
+ });
40151
+ },
40152
+ validateData: (items) => {
40153
+ if (!isArray$1(items)) {
40154
+ throw new TypeError('Invalid SortableList data: data must be an array');
40155
+ }
40156
+ const seen = new Set();
40157
+ items.forEach((item) => {
40158
+ if (!item || typeof item.value !== 'string') {
40159
+ throw new TypeError('Invalid SortableList data: value must be string');
40160
+ }
40161
+ if (item.value === '') {
40162
+ throw new TypeError('Invalid SortableList data: value cannot be empty');
40163
+ }
40164
+ if (seen.has(item.value)) {
40165
+ throw new TypeError(`Invalid SortableList data: duplicate value '${item.value}'`);
40166
+ }
40167
+ seen.add(item.value);
40168
+ });
40169
+ },
40170
+ updateControls: () => {
40171
+ const hasData = (this.$options.data || []).length > 0;
40172
+ const disableButtons = this.$options.disabled || !hasData;
40173
+ if (disableButtons) {
40174
+ attr(findAll('button', this.$el), 'disabled', true);
40175
+ }
40176
+ else {
40177
+ removeAttr(findAll('button', this.$el), 'disabled');
40178
+ }
40179
+ if (!hasData) {
40180
+ addClass(this.$el, 'is-empty');
40181
+ }
40182
+ else {
40183
+ removeClass(this.$el, 'is-empty');
40184
+ }
40185
+ },
40186
+ clearDragState: () => {
40187
+ var _a;
40188
+ DRAG_STATE_CLASSES.forEach(className => {
40189
+ removeClass(findAll(`.${className}`, this.$el), className);
40190
+ });
40191
+ if ((_a = this._dragging) === null || _a === void 0 ? void 0 : _a.preview) {
40192
+ this._dragging.preview.remove();
40193
+ }
40194
+ this._dragging = null;
40195
+ },
40196
+ dragStart: (e) => {
40197
+ if (this.$options.disabled || !this.$options.draggable) {
40198
+ return;
40199
+ }
40200
+ // 기존 드래그 상태 정리 (이전 드래그가 중단된 경우 대비)
40201
+ this._hidden.clearDragState();
40202
+ const value = attr(e.currentTarget, 'data-value');
40203
+ if (!value) {
40204
+ return;
40205
+ }
40206
+ const selected = this._hidden.getSelection().map((item) => item.value);
40207
+ const isGroup = selected.length > 1 && selected.includes(value);
40208
+ const dragValues = isGroup ? selected : [value];
40209
+ this._dragging = { values: dragValues, isGroup: isGroup, preview: null };
40210
+ if (e.dataTransfer) {
40211
+ e.dataTransfer.setData('text/plain', value);
40212
+ e.dataTransfer.effectAllowed = 'move';
40213
+ if (isGroup) {
40214
+ const preview = document.createElement('div');
40215
+ preview.className = 'sortablelist-drag-preview';
40216
+ preview.setAttribute('data-count', String(dragValues.length));
40217
+ const list = document.createElement('div');
40218
+ list.className = 'sortablelist-drag-preview-list';
40219
+ dragValues.forEach((dragValue) => {
40220
+ const itemEl = find(`[data-value="${CSS.escape(dragValue)}"]`, this.$options.delegates.list);
40221
+ if (!(itemEl instanceof HTMLElement)) {
40222
+ return;
40223
+ }
40224
+ const clone = itemEl.cloneNode(true);
40225
+ removeClass(clone, CLASS_DRAGGING);
40226
+ removeClass(clone, CLASS_GROUP_DRAGGING);
40227
+ removeClass(clone, CLASS_DRAG_OVER_TOP);
40228
+ removeClass(clone, CLASS_DRAG_OVER_BOTTOM);
40229
+ addClass(clone, 'is-ghost');
40230
+ list.appendChild(clone);
40231
+ });
40232
+ preview.appendChild(list);
40233
+ document.body.appendChild(preview);
40234
+ e.dataTransfer.setDragImage(preview, this.DRAG_IMAGE_OFFSET_X, this.DRAG_IMAGE_OFFSET_Y);
40235
+ this._dragging.preview = preview;
40236
+ }
40237
+ }
40238
+ findAll('.dropdown-item', this.$options.delegates.list).forEach((node) => {
40239
+ const itemValue = attr(node, 'data-value');
40240
+ if (dragValues.includes(itemValue)) {
40241
+ addClass(node, CLASS_DRAGGING);
40242
+ if (isGroup) {
40243
+ addClass(node, CLASS_GROUP_DRAGGING);
40244
+ }
40245
+ }
40246
+ });
40247
+ },
40248
+ dragOver: (e) => {
40249
+ if (!this._dragging || this.$options.disabled || !this.$options.draggable) {
40250
+ return;
40251
+ }
40252
+ e.preventDefault();
40253
+ if (!(e.currentTarget instanceof HTMLElement)) {
40254
+ return;
40255
+ }
40256
+ const target = e.currentTarget;
40257
+ const value = attr(target, 'data-value');
40258
+ if (this._dragging.values.includes(value)) {
40259
+ return;
40260
+ }
40261
+ removeClass(target, CLASS_DRAG_OVER_TOP);
40262
+ removeClass(target, CLASS_DRAG_OVER_BOTTOM);
40263
+ const rect = target.getBoundingClientRect();
40264
+ const midpoint = rect.top + rect.height * this.DRAG_OVER_THRESHOLD_RATIO;
40265
+ if (e.clientY > midpoint) {
40266
+ addClass(target, CLASS_DRAG_OVER_BOTTOM);
40267
+ }
40268
+ else {
40269
+ addClass(target, CLASS_DRAG_OVER_TOP);
40270
+ }
40271
+ },
40272
+ dragLeave: (e) => {
40273
+ if (e.currentTarget instanceof HTMLElement) {
40274
+ removeClass(e.currentTarget, 'is-drag-over-top');
40275
+ removeClass(e.currentTarget, 'is-drag-over-bottom');
40276
+ }
40277
+ },
40278
+ drop: (e) => {
40279
+ if (!this._dragging || this.$options.disabled || !this.$options.draggable) {
40280
+ return;
40281
+ }
40282
+ e.preventDefault();
40283
+ if (!(e.currentTarget instanceof HTMLElement)) {
40284
+ return;
40285
+ }
40286
+ const targetEl = e.currentTarget;
40287
+ const targetValue = attr(targetEl, 'data-value');
40288
+ const dragValues = this._dragging.values;
40289
+ if (!targetValue || dragValues.includes(targetValue)) {
40290
+ this._hidden.clearDragState();
40291
+ return;
40292
+ }
40293
+ const items = this.$options.data;
40294
+ const dragItems = items.filter((item) => dragValues.includes(item.value));
40295
+ const remain = items.filter((item) => !dragValues.includes(item.value));
40296
+ const targetIndex = remain.findIndex((item) => item.value === targetValue);
40297
+ const rect = targetEl.getBoundingClientRect();
40298
+ const midpoint = rect.top + rect.height * this.DRAG_OVER_THRESHOLD_RATIO;
40299
+ const insertAfter = e.clientY > midpoint;
40300
+ const insertIndex = targetIndex === -1 ? remain.length : insertAfter ? targetIndex + 1 : targetIndex;
40301
+ remain.splice(insertIndex, 0, ...dragItems);
40302
+ this.$options.data = remain;
40303
+ this._hidden.reRender();
40304
+ this._hidden.updateControls();
40305
+ this.$event(this, 'onChange', this.$options.data);
40306
+ this._hidden.clearDragState();
40307
+ },
40308
+ dragEnd: () => {
40309
+ this._hidden.clearDragState();
40310
+ },
40311
+ setData: (data) => {
40312
+ // 드래그 중일 경우 상태 정리 (DOM 요소가 제거되면 dragend 이벤트가 발생하지 않을 수 있음)
40313
+ this._hidden.clearDragState();
40314
+ this._hidden.validateData(data);
40315
+ this.$options.data = data;
40316
+ this._hidden.hydrateSelection(data);
40317
+ this._hidden.syncSelection();
40318
+ this._hidden.reRender();
40319
+ this._hidden.updateControls();
40320
+ this.$event(this, 'onChange', this.$options.data);
40321
+ },
40322
+ disable: () => {
40323
+ // 드래그 중일 경우 상태 정리
40324
+ this._hidden.clearDragState();
40325
+ this.$options.disabled = true;
40326
+ addClass(this.$el, 'is-disabled');
40327
+ this._hidden.reRender();
40328
+ this._hidden.updateControls();
40329
+ },
40330
+ enable: () => {
40331
+ // 드래그 중일 경우 상태 정리
40332
+ this._hidden.clearDragState();
40333
+ this.$options.disabled = false;
40334
+ removeClass(this.$el, 'is-disabled');
40335
+ this._hidden.reRender();
40336
+ this._hidden.updateControls();
40337
+ }
40338
+ };
40339
+ this.config = {
40340
+ name: this.$selector.name || this._uid,
40341
+ data: [],
40342
+ delegates: {
40343
+ list: '.sortablelist-items'
40344
+ },
40345
+ buttonPosition: 'left',
40346
+ draggable: false,
40347
+ textSets: {
40348
+ noData: DEFAULT_NO_DATA
40349
+ },
40350
+ height: 150
40351
+ };
40352
+ this.events = {
40353
+ onChange: true
40354
+ };
40355
+ this.methods = {
40356
+ getData() {
40357
+ return this.$options.data;
40358
+ },
40359
+ setData(data) {
40360
+ const next = isArray$1(data) ? data : data && 'data' in data ? data.data : null;
40361
+ if (!isArray$1(next)) {
40362
+ throw new TypeError('Invalid SortableList data: data must be an array');
40363
+ }
40364
+ this._hidden.setData(next);
40365
+ },
40366
+ disable() {
40367
+ this._hidden.disable();
40368
+ },
40369
+ enable() {
40370
+ this._hidden.enable();
40371
+ }
40372
+ };
40373
+ this.$selector = this.$selector;
40374
+ this.$init(this, options);
40375
+ }
40376
+ template(config) {
40377
+ const styles = {};
40378
+ if (config.width) {
40379
+ styles.width = getUnit('width', config.width);
40380
+ }
40381
+ const listStyles = {};
40382
+ if (config.height) {
40383
+ const heightValue = getUnit('height', config.height);
40384
+ styles.height = heightValue;
40385
+ listStyles.height = heightValue;
40386
+ listStyles.maxHeight = heightValue;
40387
+ }
40388
+ const controlClass = 'sortablelist-controls gn-control is-small has-arrange is-vertical is-center' + (config.buttonPosition === 'right' ? ' is-right' : '');
40389
+ const buttonDisabled = config.disabled || (config.data || []).length === 0;
40390
+ return (createElement$1("div", { id: this._uid, className: 'gn-sortablelist' + (config.disabled ? ' is-disabled' : ''), style: styles },
40391
+ createElement$1("div", { className: controlClass },
40392
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'up-all'), disabled: buttonDisabled },
40393
+ createElement$1("span", { className: "gn-icon" },
40394
+ createElement$1("i", { className: "fa fa-light fa-angle-double-up" }))),
40395
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'up'), disabled: buttonDisabled },
40396
+ createElement$1("span", { className: "gn-icon" },
40397
+ createElement$1("i", { className: "fa fa-light fa-angle-up" }))),
40398
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'down'), disabled: buttonDisabled },
40399
+ createElement$1("span", { className: "gn-icon" },
40400
+ createElement$1("i", { className: "fa fa-light fa-angle-down" }))),
40401
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'down-all'), disabled: buttonDisabled },
40402
+ createElement$1("span", { className: "gn-icon" },
40403
+ createElement$1("i", { className: "fa fa-light fa-angle-double-down" })))),
40404
+ createElement$1("div", { className: "gn-dropdown is-opened sortablelist-items" },
40405
+ createElement$1("div", { className: "dropdown-items", style: listStyles }, this._hidden.renderSub()))));
40406
+ }
40407
+ beforeMount() {
40408
+ this._hidden.validateData(this.$options.data);
40409
+ this._hidden.hydrateSelection(this.$options.data);
40410
+ }
40411
+ completed() {
40412
+ this._hidden.updateControls();
40413
+ }
40414
+ destroyed() {
40415
+ this._hidden.clearDragState();
40416
+ }
40417
+ }
40418
+
39788
40419
  class Splitter extends GNCoreInstance {
39789
40420
  constructor(name, selector, options = {}) {
39790
40421
  super(name, selector, options);
@@ -40432,6 +41063,7 @@ var gnUIComp = {
40432
41063
  picklist: Picklist,
40433
41064
  progressbar: Progressbar,
40434
41065
  selectbutton: SelectButton,
41066
+ sortablelist: SortableList,
40435
41067
  splitter: Splitter,
40436
41068
  switch: Switch,
40437
41069
  syntaxinput: SyntaxInput,