gnui 1.2.16 → 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 +772 -111
  2. package/dist/js/gnui.js +772 -111
  3. package/dist/js/gnui.min.js +6 -6
  4. package/dist/styles/default.css +1018 -108
  5. package/dist/styles/gpi.css +1018 -108
  6. package/dist/styles/green24.css +1229 -289
  7. package/dist/styles/insights.css +1018 -108
  8. package/dist/styles/nac.css +1019 -109
  9. package/dist/styles/ztnac.css +1205 -265
  10. package/package.json +2 -2
  11. package/styleguide/assets/components.js +216 -9
  12. package/styleguide/assets/js/gnui.js +772 -111
  13. package/styleguide/assets/js/gnui.min.js +6 -6
  14. package/styleguide/assets/styles/default.css +1018 -108
  15. package/styleguide/assets/styles/gpi.css +1018 -108
  16. package/styleguide/assets/styles/green24.css +1229 -289
  17. package/styleguide/assets/styles/insights.css +1018 -108
  18. package/styleguide/assets/styles/nac.css +1019 -109
  19. package/styleguide/assets/styles/ztnac.css +1205 -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
@@ -14118,15 +14118,15 @@
14118
14118
  if (!obj || !pathString) {
14119
14119
  return "";
14120
14120
  }
14121
- if (isString(pathString)) {
14122
- pathString = pathString.split(".");
14123
- }
14124
- const currentPath = pathString.splice(0, 1)[0];
14125
- if (obj[currentPath] !== void 0) {
14126
- return pathString.length ? findValue(obj[currentPath], pathString) : obj[currentPath];
14127
- } else {
14128
- return "";
14121
+ const keys = isString(pathString) ? pathString.split(".") : pathString;
14122
+ let current = obj;
14123
+ for (const key of keys) {
14124
+ if (current === void 0 || current === null) {
14125
+ return "";
14126
+ }
14127
+ current = current[key];
14129
14128
  }
14129
+ return current !== void 0 ? current : "";
14130
14130
  }
14131
14131
  function findProperty(obj, predicate) {
14132
14132
  let result = [];
@@ -14265,17 +14265,18 @@
14265
14265
  return dmustach.test(result) ? interpolateURL(result, source) : result;
14266
14266
  }
14267
14267
  function interpolateCop(textCondition, data2, parent2, $2) {
14268
- const onlymustach = /^\{{2}[^{}]*\}{2}$/;
14268
+ const onlymustach = /^\{{2}[\w.$\[\]]+\}{2}$/;
14269
14269
  if (onlymustach.test(textCondition)) {
14270
14270
  const path = textCondition.replace("{{", "").replace("}}", "");
14271
14271
  return findValue({ data: data2, parent: parent2, $: $2 }, path);
14272
14272
  }
14273
14273
  const dmustach = new RegExp(/\{{([^{}]*)}}/gm);
14274
+ const context = { data: data2, parent: parent2, $: $2 };
14274
14275
  const result = textCondition.replace(dmustach, (match) => {
14275
14276
  const conditionalOp = match.replace(/\{{|\}}/gm, "");
14276
- return new Function("data", "parent", "$", "return " + conditionalOp)(data2, parent2, $2);
14277
+ return new Function("ctx", "const {data, parent, $} = ctx; return " + conditionalOp)(context);
14277
14278
  });
14278
- return dmustach.test(result) ? this.interpolateCop(result, data2, parent2, $2) : result;
14279
+ return dmustach.test(result) ? interpolateCop(result, context.data, context.parent, context.$) : result;
14279
14280
  }
14280
14281
  function parseBundle(text2, locale) {
14281
14282
  let _value = text2;
@@ -14709,7 +14710,10 @@
14709
14710
  });
14710
14711
  }
14711
14712
  function getArgs(args) {
14712
- return args.reduce((args2, arg) => args2.concat.call(args2, isString(arg) && includes(arg, " ") ? arg.trim().split(" ") : arg), []);
14713
+ return args.reduce(
14714
+ (args2, arg) => args2.concat.call(args2, isString(arg) && includes(arg, " ") ? arg.trim().split(" ") : arg),
14715
+ []
14716
+ );
14713
14717
  }
14714
14718
  var supports = {
14715
14719
  get Multiple() {
@@ -14735,7 +14739,7 @@
14735
14739
  s = s / 100;
14736
14740
  l = l / 100;
14737
14741
  const c = (1 - Math.abs(2 * l - 1)) * s, x = c * (1 - Math.abs(h / 60 % 2 - 1)), m = l - c / 2;
14738
- let r, g, b;
14742
+ let r = 0, g = 0, b = 0;
14739
14743
  switch (true) {
14740
14744
  case (h > -1 && h < 60):
14741
14745
  r = c;
@@ -15014,7 +15018,14 @@
15014
15018
  function dateData(locale) {
15015
15019
  return ["S", "M", "T", "W", "T", "F", "S"];
15016
15020
  }
15017
- function objToDate({ year, month, day, hour, minute, second }) {
15021
+ function objToDate({
15022
+ year,
15023
+ month,
15024
+ day,
15025
+ hour,
15026
+ minute,
15027
+ second
15028
+ }) {
15018
15029
  if (hour === void 0) {
15019
15030
  hour = "00";
15020
15031
  }
@@ -15243,7 +15254,11 @@
15243
15254
  ["left", "top"].forEach((prop) => {
15244
15255
  if (prop in coordinates) {
15245
15256
  const value = css$1(element, prop);
15246
- css$1(element, prop, coordinates[prop] - (currentOffset == null ? void 0 : currentOffset[prop]) + toFloat(pos === "absolute" && value === "auto" ? position(element)[prop] : value));
15257
+ css$1(
15258
+ element,
15259
+ prop,
15260
+ coordinates[prop] - (currentOffset == null ? void 0 : currentOffset[prop]) + toFloat(pos === "absolute" && value === "auto" ? position(element)[prop] : value)
15261
+ );
15247
15262
  }
15248
15263
  });
15249
15264
  }
@@ -15366,7 +15381,10 @@
15366
15381
  }
15367
15382
  function after(ref, element) {
15368
15383
  ref = $(ref);
15369
- return insertNodes(element, (element2) => ref.nextSibling ? before(ref.nextSibling, element2) : append(ref.parentNode, element2));
15384
+ return insertNodes(
15385
+ element,
15386
+ (element2) => ref.nextSibling ? before(ref.nextSibling, element2) : append(ref.parentNode, element2)
15387
+ );
15370
15388
  }
15371
15389
  function insertNodes(element, fn) {
15372
15390
  element = isString(element) ? fragment(element) : element;
@@ -15390,13 +15408,21 @@
15390
15408
  listener = delegate(targets, selector, listener);
15391
15409
  }
15392
15410
  useCapture = useCaptureFilter(useCapture);
15393
- type.split(" ").forEach((type2) => targets.forEach((target) => target.addEventListener(type2, listener, useCapture)));
15411
+ type.split(" ").forEach(
15412
+ (type2) => targets.forEach(
15413
+ (target) => target.addEventListener(type2, listener, useCapture)
15414
+ )
15415
+ );
15394
15416
  return () => off(targets, type, listener, useCapture);
15395
15417
  }
15396
15418
  function off(targets, type, listener, useCapture = false) {
15397
15419
  useCapture = useCaptureFilter(useCapture);
15398
15420
  targets = toEventTargets(targets);
15399
- type.split(" ").forEach((type2) => targets.forEach((target) => target.removeEventListener(type2, listener, useCapture)));
15421
+ type.split(" ").forEach(
15422
+ (type2) => targets.forEach(
15423
+ (target) => target.removeEventListener(type2, listener, useCapture)
15424
+ )
15425
+ );
15400
15426
  }
15401
15427
  function once(...args) {
15402
15428
  const [element, type, selector, listener, useCapture, condition] = getArgs2(args);
@@ -15416,7 +15442,10 @@
15416
15442
  return off2;
15417
15443
  }
15418
15444
  function trigger(targets, event, detail2) {
15419
- return toEventTargets(targets).reduce((notCanceled, target) => notCanceled && target.dispatchEvent(createEvent(event, true, true, detail2)), true);
15445
+ return toEventTargets(targets).reduce(
15446
+ (notCanceled, target) => notCanceled && target.dispatchEvent(createEvent(event, true, true, detail2)),
15447
+ true
15448
+ );
15420
15449
  }
15421
15450
  function createEvent(e, bubbles = true, cancelable = false, detail2) {
15422
15451
  if (isString(e)) {
@@ -16901,7 +16930,10 @@
16901
16930
  // 이벤트 삭제
16902
16931
  delete this._eventMap[uid];
16903
16932
  }
16904
- dispatch(uid, name, params) {
16933
+ /**
16934
+ * lifeCycle 핸들러 실행
16935
+ */
16936
+ cyclepatch(uid, name, params) {
16905
16937
  const _events = this._getEvent(uid, name);
16906
16938
  if (_events.length) {
16907
16939
  _events.forEach((_event) => {
@@ -16910,6 +16942,28 @@
16910
16942
  });
16911
16943
  }
16912
16944
  }
16945
+ /**
16946
+ * 등록된 이벤트 핸들러 실행
16947
+ *
16948
+ * - sync/async 핸들러를 모두 지원한다.
16949
+ * - 모든 핸들러를 순차 실행한 뒤, 하나라도 `false` 를 반환한 경우 `true`(cancelled)를 반환한다.
16950
+ * (첫 false 에서 중단하지 않고, 나머지 핸들러도 계속 실행한다)
16951
+ */
16952
+ async dispatch(uid, name, params) {
16953
+ const _events = this._getEvent(uid, name);
16954
+ let cancelled = false;
16955
+ if (_events.length) {
16956
+ for (const _event of _events) {
16957
+ const _target = _event.target || this;
16958
+ // sync/async 둘 다 지원: Promise.resolve 로 감싸서 await
16959
+ const result = await Promise.resolve(params ? _event.handler.call(_target, ...params) : _event.handler.call(_target));
16960
+ if (result === false) {
16961
+ cancelled = true;
16962
+ }
16963
+ }
16964
+ }
16965
+ return cancelled;
16966
+ }
16913
16967
  _getEvent(uid, name) {
16914
16968
  // parameters에 해당하는 이벤트 반환
16915
16969
  return this._eventMap[uid]
@@ -16994,7 +17048,7 @@
16994
17048
  });
16995
17049
  return _findComponent;
16996
17050
  }
16997
- // 등록된 컴포넌트 제거
17051
+ // 등록된 컴포넌트 제거 (selector 기반)
16998
17052
  _removeComponent(selector) {
16999
17053
  if (!selector) {
17000
17054
  return;
@@ -17003,10 +17057,30 @@
17003
17057
  Object.values(this._componentMap).forEach(n => {
17004
17058
  // 동일한 selector 인지 비교해서 동일한 component의 selector이면 제거 처리
17005
17059
  if (isEquals(n.selector, _selector)) {
17006
- delete this._componentMap[_selector._uid];
17060
+ // componentMap에서 완전히 제거
17061
+ delete this._componentMap[n.uid];
17062
+ // 컴포넌트 내부 참조도 제거 (메모리 누수 방지)
17063
+ if (n.component) {
17064
+ n.component = null;
17065
+ }
17066
+ n.selector = null;
17007
17067
  }
17008
17068
  });
17009
17069
  }
17070
+ // 등록된 컴포넌트 제거 (uid 기반 - 더 효율적)
17071
+ _removeComponentByUid(uid) {
17072
+ if (!uid || !this._componentMap[uid]) {
17073
+ return;
17074
+ }
17075
+ // 컴포넌트 내부 참조 제거 (메모리 누수 방지)
17076
+ const componentInfo = this._componentMap[uid];
17077
+ if (componentInfo.component) {
17078
+ componentInfo.component = null;
17079
+ }
17080
+ componentInfo.selector = null;
17081
+ // componentMap에서 완전히 제거
17082
+ delete this._componentMap[uid];
17083
+ }
17010
17084
  // 컴포넌트 life cycle에 따른 eventManager dispatch
17011
17085
  _detectedCycle(uid, name) {
17012
17086
  // component 마지막 status 업데이트
@@ -17016,7 +17090,7 @@
17016
17090
  // event manager를 이용해 해당 uid 이벤트 dispatch
17017
17091
  const eventManager = GNCoreEventManager.getInstance();
17018
17092
  // 호출 후
17019
- eventManager.dispatch(uid, name, '');
17093
+ eventManager.cyclepatch(uid, name, '');
17020
17094
  // 이벤트 해제 - life cycle 은 컴포넌트 별로 한번씩만 존재하므로..
17021
17095
  eventManager.remove(uid, name);
17022
17096
  }
@@ -17030,6 +17104,7 @@
17030
17104
  function _removedNode(removed) {
17031
17105
  Array.prototype.forEach.call(removed, (rm) => {
17032
17106
  // 삭제노드 연관 컴포넌트 (ex. tooltip) 삭제
17107
+ var _a;
17033
17108
  const dependents = findAll('[data-gnui]', rm);
17034
17109
  each(dependents, (dependent) => {
17035
17110
  if (isElement$2(dependent)) {
@@ -17043,7 +17118,7 @@
17043
17118
  remove($('#' + attr(rm, 'data-gnui')));
17044
17119
  }
17045
17120
  const findComponent = closerThis._getComponent($(rm));
17046
- if (findComponent && findComponent._uid && !findComponent.$el.parentNode && findComponent.$name !== 'modal') {
17121
+ if (findComponent && findComponent._uid && !((_a = findComponent.$el) === null || _a === void 0 ? void 0 : _a.parentNode) && findComponent.$name !== 'modal') {
17047
17122
  // state manager 에서 component 삭제
17048
17123
  closerThis._removeComponent(rm);
17049
17124
  // event manager 에서 unbind
@@ -17242,25 +17317,48 @@
17242
17317
  }
17243
17318
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
17244
17319
  $update(element = this.$el, e) { }
17245
- $event(component, name, ...params) {
17320
+ /**
17321
+ * 컴포넌트 이벤트 디스패치 헬퍼
17322
+ *
17323
+ * - sync/async 핸들러 모두 지원한다.
17324
+ * - 하나 이상의 핸들러에서 `false`를 반환하면 `true`(cancelled) 를 반환한다.
17325
+ * - 단순 알림용(fire-and-forget) 이벤트는 반환값/await 없이 호출해도 된다.
17326
+ */
17327
+ async $event(component, name, ...params) {
17246
17328
  const eventManager = GNCoreEventManager.getInstance();
17247
- eventManager.dispatch(component._uid, name, params);
17329
+ return eventManager.dispatch(component._uid, name, params);
17248
17330
  }
17249
17331
  $destroy(component = this, removeEl = true) {
17332
+ var _a;
17250
17333
  const stateManager = GNUIState.getInstance();
17251
17334
  // state manager 를 통해 destroy 상태 dispatch
17252
17335
  stateManager._detectedCycle(component._uid, 'destroy');
17253
- // remove Component in state manager
17254
- stateManager._removeComponent(component.$selector);
17255
17336
  // remove DOM (by removeEl)
17256
17337
  if (removeEl) {
17257
17338
  style(component.$el, 'display', 'none');
17258
17339
  remove(component.$el);
17259
17340
  }
17341
+ if (((_a = component.$options) === null || _a === void 0 ? void 0 : _a._destroy) && isFunction(component.$options._destroy)) {
17342
+ component.$options._destroy();
17343
+ }
17260
17344
  // state manager 를 통해 destroy 상태 dispatch
17261
17345
  stateManager._detectedCycle(component._uid, 'destroyed');
17262
- // event manager 에서 등록 해제가 가장 마지막..
17346
+ // event manager 에서 등록 해제
17263
17347
  GNCoreEventManager.getInstance().removeAll(component._uid);
17348
+ // state manager에서 component 제거 (uid 기반으로 효율적 제거)
17349
+ stateManager._removeComponentByUid(component._uid);
17350
+ // 메모리 누수 방지: component의 모든 hasOwnProperty 제거
17351
+ // config, events, methods, _hidden 등 동적으로 추가된 속성 포함
17352
+ Object.keys(component).forEach((key) => {
17353
+ try {
17354
+ component[key] = null;
17355
+ delete component[key];
17356
+ }
17357
+ catch (e) {
17358
+ // readonly 속성 등 삭제 불가능한 경우 무시
17359
+ }
17360
+ });
17361
+ component = null;
17264
17362
  }
17265
17363
  }
17266
17364
 
@@ -33461,8 +33559,19 @@
33461
33559
  });
33462
33560
  if (this.$options.value) {
33463
33561
  if (this.$options.multiple) {
33464
- const values = this.$options.value.split(',');
33465
- this.$options.value = this.$options.flatData.filter((opt) => values.includes(opt.value) && opt.text);
33562
+ // multiple 모드에서 다양한 타입의 value를 문자열 배열로 변환
33563
+ // 지원 타입: 문자열(쉼표 구분), 객체 배열, 단일 객체, 문자열 배열
33564
+ const values = typeof this.$options.value === 'string'
33565
+ ? this.$options.value.split(',') // 케이스 1: 'item1,item2,item3'
33566
+ : Array.isArray(this.$options.value)
33567
+ ? this.$options.value.map((v) => (typeof v === 'object' && v !== null && 'value' in v ? String(v.value) : String(v))) // 케이스 3: [{value:'item1', text:'항목1'}, ...] 또는 ['item1', 'item2']
33568
+ : typeof this.$options.value === 'object' && this.$options.value !== null && 'value' in this.$options.value
33569
+ ? [String(this.$options.value.value)] // 케이스 2: {value:'item1', text:'항목1'}
33570
+ : [String(this.$options.value)]; // 기타: 숫자 등
33571
+ this.$options.value = this.$options.flatData.filter((opt) => {
33572
+ const optValue = typeof opt.value === 'string' ? opt.value : String(opt.value);
33573
+ return values.includes(optValue) && opt.text;
33574
+ });
33466
33575
  }
33467
33576
  else {
33468
33577
  this.$options.value = this.$options.flatData.find((opt) => opt.value + '' === this.$options.value + '' && opt.text);
@@ -36977,26 +37086,8 @@
36977
37086
  }
36978
37087
  this.$event(this, 'onSort', column);
36979
37088
  },
36980
- renderHeader: (columns) => {
36981
- this.$options.hasOrder &&
36982
- !this.$options.readonly &&
36983
- columns.push({
36984
- label: this.$options.textSets.orderLabel,
36985
- key: 'btnOrder',
36986
- style: {
36987
- width: '50px'
36988
- }
36989
- });
36990
- this.$options.hasDelete &&
36991
- !this.$options.readonly &&
36992
- columns.push({
36993
- label: this.$options.textSets.deleteLabel,
36994
- key: 'btnDelete',
36995
- style: {
36996
- width: '30px'
36997
- }
36998
- });
36999
- this._setColumnsTemplate();
37089
+ renderHeader: (columns, isReset = false) => {
37090
+ this._setColumnsTemplate(isReset);
37000
37091
  return (createElement$1("div", { className: "gn-datagrid-header-row", style: {
37001
37092
  'grid-template-columns': this._columnsTemplate.join(' ')
37002
37093
  } },
@@ -37035,6 +37126,10 @@
37035
37126
  column.draggable && (this.$options.headers ? idx < this.$options.headers.length - 1 : true) && createElement$1("span", { className: "is-handle", "data-index": idx })));
37036
37127
  },
37037
37128
  renderBody: (data, columns) => {
37129
+ // 헤더가 숨겨진 경우에도 body 렌더 전에 템플릿 폭을 준비한다
37130
+ if (!this._columnsTemplate || !this._columnsTemplate.length) {
37131
+ this._setColumnsTemplate();
37132
+ }
37038
37133
  rowIdx$1 = 0;
37039
37134
  return (createElement$1("div", { className: "gn-datagrid-body", style: {
37040
37135
  maxHeight: this.$options.bodyHeight ? this.$options.bodyHeight : 'auto'
@@ -37048,7 +37143,6 @@
37048
37143
  });
37049
37144
  },
37050
37145
  renderRow: (row, columns, depth = 0, hasChild, isOpened, isCheck = false) => {
37051
- row._depth = depth;
37052
37146
  const _index = rowIdx$1++;
37053
37147
  if (row.isChecked) {
37054
37148
  isCheck = true;
@@ -37182,8 +37276,9 @@
37182
37276
  e.stopPropagation();
37183
37277
  toggler = parents(e.currentTarget, '.gn-datagrid-body-row');
37184
37278
  }
37185
- const children = nextUntil(toggler, '.gn-datagrid-body-row[data-depth="' + row._depth + '"]').filter((x) => {
37186
- return x.dataset.depth > row._depth;
37279
+ const rowDepth = Number(attr(toggler, 'data-depth')) || 0;
37280
+ const children = nextUntil(toggler, '.gn-datagrid-body-row[data-depth="' + rowDepth + '"]').filter((x) => {
37281
+ return Number(attr(x, 'data-depth')) > rowDepth;
37187
37282
  });
37188
37283
  type = type ? type : hasClass(toggler, 'is-collapsed') ? 'expand' : 'collapse';
37189
37284
  if (type === 'collapse') {
@@ -37199,7 +37294,7 @@
37199
37294
  //show childs
37200
37295
  removeClass(toggler, 'is-collapsed');
37201
37296
  removeClass(children.filter((x) => {
37202
- return x.dataset.depth == row._depth + 1;
37297
+ return Number(attr(x, 'data-depth')) == rowDepth + 1;
37203
37298
  }), 'is-hidden');
37204
37299
  this.$event(this, 'onToggle', 'expanded', row, index$1(toggler));
37205
37300
  }
@@ -37257,13 +37352,15 @@
37257
37352
  e.stopPropagation();
37258
37353
  const checker = parents(e.currentTarget, '.gn-datagrid-body-row');
37259
37354
  const checkerState = e.target.checked;
37260
- find('.is-allChecker', this.$el).checked = false;
37355
+ const allChecker = find('.is-allChecker', this.$el);
37356
+ allChecker && (allChecker.checked = false);
37357
+ const rowDepth = Number(attr(checker, 'data-depth')) || 0;
37261
37358
  // 1. row에 자식노드가 있는지 확인한다.
37262
37359
  if (this.$options.checkCapturing && row[this.$options.childField] && row[this.$options.childField].length) {
37263
37360
  // 2. 자식노드가 있는경우 자식 체크박스도 함께 토글한다.
37264
- nextUntil(checker, '.gn-datagrid-body-row[data-depth="' + row._depth + '"]')
37361
+ nextUntil(checker, '.gn-datagrid-body-row[data-depth="' + rowDepth + '"]')
37265
37362
  .filter((x) => {
37266
- return x.dataset.depth > row._depth;
37363
+ return Number(attr(x, 'data-depth')) > rowDepth;
37267
37364
  })
37268
37365
  .forEach((x) => {
37269
37366
  const _checker = find('.is-rowChecker', x);
@@ -37273,17 +37370,17 @@
37273
37370
  });
37274
37371
  }
37275
37372
  // 3. 체크 해제인 경우만 부모노드가 있는지 확인한다.
37276
- if (this.$options.checkCapturing && row._depth > 0 && !checkerState) {
37373
+ if (this.$options.checkCapturing && rowDepth > 0 && !checkerState) {
37277
37374
  // 4. 부모노드가 체크되어 있는지 확인한다
37278
37375
  const exeDepth = [];
37279
37376
  prevUntil(checker, '.gn-datagrid-body-row[data-depth="0"]')
37280
37377
  .filter((x) => {
37281
- const _thisDepth = x.dataset.depth;
37378
+ const _thisDepth = attr(x, 'data-depth');
37282
37379
  if (exeDepth.includes(_thisDepth)) {
37283
37380
  return false;
37284
37381
  }
37285
37382
  exeDepth.push(_thisDepth);
37286
- return _thisDepth < row._depth;
37383
+ return Number(_thisDepth) < rowDepth;
37287
37384
  })
37288
37385
  .forEach((x) => {
37289
37386
  const _checker = find('.is-rowChecker', x);
@@ -37299,9 +37396,21 @@
37299
37396
  if (hasCheck === undefined) {
37300
37397
  hasCheck = this.$options.hasCheck;
37301
37398
  }
37302
- this.$options.headers = headers;
37399
+ const prevHeaders = [...(this.$options.headers || [])];
37400
+ // _prepareHeaders가 배열을 변경하므로 비교용(prevHeaders)과 가공용(baseHeaders)을 분리한다
37401
+ const baseHeaders = headers ? [...headers] : [...prevHeaders];
37402
+ const preparedHeaders = this._prepareHeaders(baseHeaders);
37403
+ // 헤더 배열의 내용(길이, key)이 바뀐 경우에만 DOM 기반 폭 계산을 리셋한다
37404
+ const isReset = prevHeaders.length !== preparedHeaders.length || prevHeaders.some((header, idx) => { var _a; return header.key !== ((_a = preparedHeaders[idx]) === null || _a === void 0 ? void 0 : _a.key); });
37303
37405
  this.$options.hasCheck = hasCheck;
37304
- this.$template.reRender(find('.gn-datagrid-header-row', this.$el), this._hidden.renderHeader(this.$options.headers));
37406
+ const headerRow = find('.gn-datagrid-header-row', this.$el);
37407
+ // 헤더가 없을 때도 컬럼 수/폭 변경을 반영하기 위해 템플릿을 갱신한다
37408
+ if (this.$options.hasHeader && headerRow) {
37409
+ this.$template.reRender(headerRow, this._hidden.renderHeader(preparedHeaders, isReset));
37410
+ }
37411
+ else {
37412
+ this._setColumnsTemplate(isReset);
37413
+ }
37305
37414
  this._hidden.resetData(data ? arrClone(data) : this.$options.data);
37306
37415
  this.$render(this.$options);
37307
37416
  isFunction(resolve) && resolve();
@@ -37316,6 +37425,10 @@
37316
37425
  });
37317
37426
  },
37318
37427
  awaitData: (data) => {
37428
+ // asyncData 콜백 실행 중 컴포넌트가 destroy된 경우 안전하게 종료
37429
+ if (!this.$options || !this._hidden) {
37430
+ return;
37431
+ }
37319
37432
  if (this.$options.asyncData && this.$options.paginator && !this._paginator) {
37320
37433
  this._paginator = new Pagination('pagination', find('.gn-datagrid-footer', this.$el), {
37321
37434
  total: this.$options.paginator.total || 0,
@@ -37340,7 +37453,8 @@
37340
37453
  this._fixCellStyleOnDraggable();
37341
37454
  // 체크박스가 있는경우 전체 체크항목을 해제해준다
37342
37455
  if (this.$options.hasCheck) {
37343
- find('.is-allChecker', this.$el).checked = false;
37456
+ const allChecker = find('.is-allChecker', this.$el);
37457
+ allChecker && (allChecker.checked = false);
37344
37458
  }
37345
37459
  isFunction(resolve) && resolve();
37346
37460
  });
@@ -37375,8 +37489,18 @@
37375
37489
  stopRowSelectEvent: (e) => {
37376
37490
  e.stopPropagation();
37377
37491
  },
37378
- deleteRow: (index) => {
37379
- this.$options.data = this.$options.data.filter((_data, idx) => index !== idx);
37492
+ deleteRow: async (index) => {
37493
+ var _a;
37494
+ const confirmMessage = (_a = this.$options.textSets) === null || _a === void 0 ? void 0 : _a.deleteConfirmMessage;
37495
+ if (confirmMessage && !window.confirm(confirmMessage)) {
37496
+ return;
37497
+ }
37498
+ const removedData = this._hidden.findData(index);
37499
+ const cancelled = await this.$event(this, 'onDelete', removedData, index);
37500
+ if (cancelled) {
37501
+ return;
37502
+ }
37503
+ this._hidden.deleteData(index);
37380
37504
  this._hidden.resetData(this.$options.data);
37381
37505
  this.$event(this, 'onChange', this.$options.data);
37382
37506
  },
@@ -37411,22 +37535,38 @@
37411
37535
  col.offHover && col.offHover.call(this, row, col, index, e);
37412
37536
  },
37413
37537
  findData: (index) => {
37414
- let deter = 0, indexData = null;
37415
- const findIndex = (datas, index) => {
37416
- return datas.some((data) => {
37417
- if (index === deter) {
37418
- indexData = data;
37538
+ return this._hidden.walkByIndex(index, 'find');
37539
+ },
37540
+ deleteData: (index) => {
37541
+ return this._hidden.walkByIndex(index, 'delete');
37542
+ },
37543
+ walkByIndex: (index, action) => {
37544
+ let deter = 0;
37545
+ let result = null;
37546
+ const findIndex = (datas) => {
37547
+ for (let i = 0; i < datas.length; i++) {
37548
+ if (deter === index) {
37549
+ switch (action) {
37550
+ case 'find':
37551
+ result = datas[i];
37552
+ break;
37553
+ case 'delete':
37554
+ result = datas.splice(i, 1)[0];
37555
+ break;
37556
+ }
37419
37557
  return true;
37420
37558
  }
37421
- ++deter;
37422
- if (isArray$1(data[this.$options.childField]) && data[this.$options.childField].length) {
37423
- return findIndex(data[this.$options.childField], index);
37559
+ deter++;
37560
+ const children = datas[i][this.$options.childField];
37561
+ if (Array.isArray(children) && children.length) {
37562
+ if (findIndex(children))
37563
+ return true;
37424
37564
  }
37425
- return false;
37426
- });
37565
+ }
37566
+ return false;
37427
37567
  };
37428
- findIndex(this.$options.data, index);
37429
- return indexData;
37568
+ findIndex(this.$options.data);
37569
+ return result;
37430
37570
  },
37431
37571
  getChecked: () => {
37432
37572
  return findAll('.is-rowChecker', this.$el)
@@ -37513,11 +37653,13 @@
37513
37653
  hasOrder: false,
37514
37654
  hasDelete: false,
37515
37655
  isEllipsis: false,
37656
+ hasHeader: true,
37516
37657
  data: [],
37517
37658
  textSets: {
37518
37659
  noData: 'No records available.',
37519
37660
  orderLabel: '',
37520
- deleteLabel: ''
37661
+ deleteLabel: '',
37662
+ deleteConfirmMessage: ''
37521
37663
  },
37522
37664
  childField: 'child',
37523
37665
  checkCapturing: true,
@@ -37535,7 +37677,8 @@
37535
37677
  onCheck: true,
37536
37678
  onDoubleClick: true,
37537
37679
  onChange: true,
37538
- onDragEnd: true
37680
+ onDragEnd: true,
37681
+ onDelete: true
37539
37682
  };
37540
37683
  this.methods = {
37541
37684
  reRender(options) {
@@ -37595,18 +37738,21 @@
37595
37738
  this.$selector = this.$selector;
37596
37739
  this.$init(this, options);
37597
37740
  }
37598
- _setColumnsTemplate() {
37741
+ _setColumnsTemplate(isReset = false) {
37599
37742
  // header cell의 각 넓이를 배열로 가져온다
37600
37743
  // ! 모든 컬럼의 넓이가 지정된 경우 이동(btnOrder), 삭제(btnDelete) 컬럼을 제외한 마지막 컬럼은 1fr로 고정
37601
37744
  const _isfixedAllWidth = this.$options.headers.every((header) => { var _a; return ((_a = header.style) === null || _a === void 0 ? void 0 : _a.width) !== undefined; });
37602
37745
  const _fixedTemplateColumn = this.$options.headers.findLast((header) => !this._isSystemAddedColumn(header.key) && !header.isHidden);
37746
+ // 헤더 DOM이 없거나 모든 일반 컬럼이 숨겨진 경우에도 안전하게 비교할 수 있도록 key만 옵셔널하게 캐싱한다
37747
+ const _fixedTemplateKey = _fixedTemplateColumn === null || _fixedTemplateColumn === void 0 ? void 0 : _fixedTemplateColumn.key;
37603
37748
  const columns = findAll('.gn-datagrid-header-cell', this.$el);
37604
- if (this.$el && columns.length) {
37749
+ // isReset이면 기존 DOM 폭을 재사용하지 않고 헤더 정의로 재계산
37750
+ if (this.$el && columns.length && !isReset) {
37605
37751
  this._columnsTemplate = findAll('.gn-datagrid-header-cell', this.$el).map((header, idx) => {
37606
37752
  if (this.$options.headers[idx].isHidden) {
37607
37753
  return '';
37608
37754
  }
37609
- else if (_isfixedAllWidth && this.$options.headers[idx].key === _fixedTemplateColumn.key) {
37755
+ else if (_isfixedAllWidth && _fixedTemplateKey && this.$options.headers[idx].key === _fixedTemplateKey) {
37610
37756
  return '1fr';
37611
37757
  }
37612
37758
  else {
@@ -37620,7 +37766,7 @@
37620
37766
  if (header.isHidden) {
37621
37767
  return '';
37622
37768
  }
37623
- else if (_isfixedAllWidth && header.key === _fixedTemplateColumn.key) {
37769
+ else if (_isfixedAllWidth && _fixedTemplateKey && header.key === _fixedTemplateKey) {
37624
37770
  return '1fr';
37625
37771
  }
37626
37772
  else {
@@ -37641,18 +37787,45 @@
37641
37787
  _isSystemAddedColumn(key) {
37642
37788
  return ['btnOrder', 'btnDelete'].includes(key);
37643
37789
  }
37790
+ // 옵션에 따른 추가 해더 구성
37791
+ _prepareHeaders(headers = []) {
37792
+ const hasOrderColumn = headers.some((header) => header.key === 'btnOrder');
37793
+ const hasDeleteColumn = headers.some((header) => header.key === 'btnDelete');
37794
+ if (this.$options.hasOrder && !this.$options.readonly && !hasOrderColumn) {
37795
+ headers.push({
37796
+ label: this.$options.textSets.orderLabel,
37797
+ key: 'btnOrder',
37798
+ style: {
37799
+ width: '50px'
37800
+ }
37801
+ });
37802
+ }
37803
+ if (this.$options.hasDelete && !this.$options.readonly && !hasDeleteColumn) {
37804
+ headers.push({
37805
+ label: this.$options.textSets.deleteLabel,
37806
+ key: 'btnDelete',
37807
+ style: {
37808
+ width: '30px'
37809
+ }
37810
+ });
37811
+ }
37812
+ this.$options.headers = headers;
37813
+ return headers;
37814
+ }
37644
37815
  template(config) {
37645
37816
  const styles = {};
37817
+ const headers = this._prepareHeaders(config.headers);
37646
37818
  return (createElement$1("div", { id: this._uid, className: 'gn-datagrid' +
37647
37819
  (config.style ? ' is-' + config.style : '') +
37648
37820
  (config.isEllipsis ? ' is-ellipsis' : '') +
37649
37821
  (config.bodyHeight ? ' has-fixed-body' : '') +
37650
37822
  (config.fixHeader ? ' has-fixed-header' : '') +
37651
37823
  (config.fixFooter ? ' has-fixed-footer' : '') +
37824
+ (!config.hasHeader ? ' is-headless' : '') +
37652
37825
  (config.data.some((d) => isArray$1(d[this.$options.childField])) ? ' has-left-padding' : '') +
37653
37826
  (config.disabled ? ' is-disabled' : ''), style: styles },
37654
- createElement$1("div", { className: "gn-datagrid-header" }, this._hidden.renderHeader(config.headers)),
37655
- createElement$1("div", { className: "gn-datagrid-contents", style: { marginTop: this.$options.bodyTopMargin ? this.$options.bodyTopMargin : '0', marginBottom: this.$options.bodyBottomMargin ? this.$options.bodyBottomMargin : '0' } }, this._hidden.renderBody(arrClone(config.data), config.headers)),
37827
+ config.hasHeader && createElement$1("div", { className: "gn-datagrid-header" }, this._hidden.renderHeader(headers)),
37828
+ createElement$1("div", { className: "gn-datagrid-contents", style: { marginTop: this.$options.bodyTopMargin ? this.$options.bodyTopMargin : '0', marginBottom: this.$options.bodyBottomMargin ? this.$options.bodyBottomMargin : '0' } }, this._hidden.renderBody(arrClone(config.data), headers)),
37656
37829
  config.paginator /* 페이지네이터 옵션 확인 */ && createElement$1("div", { className: "gn-datagrid-footer" })));
37657
37830
  }
37658
37831
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -37669,18 +37842,20 @@
37669
37842
  }
37670
37843
  }
37671
37844
  completed() {
37672
- if (this.$options.fixHeader) {
37845
+ if (this.$options.fixHeader && this.$options.hasHeader) {
37673
37846
  const body = find('.gn-datagrid-contents', this.$el);
37674
37847
  const header = find('.gn-datagrid-header', this.$el);
37675
- const _offset = offset(header);
37676
- this.$options.bodyTopMargin = _offset.height ? _offset.height - 1 + 'px' : '2.4rem';
37677
- css$1(body, 'margin-top', this.$options.bodyTopMargin);
37848
+ if (header) {
37849
+ const _offset = offset(header);
37850
+ this.$options.bodyTopMargin = _offset.height ? _offset.height - 1 + 'px' : '2.4rem';
37851
+ css$1(body, 'margin-top', this.$options.bodyTopMargin);
37852
+ }
37678
37853
  if (this.$options.paginator) {
37679
37854
  this.$options.bodyBottomMargin = '2.4rem';
37680
37855
  css$1(body, 'margin-bottom', this.$options.bodyBottomMargin);
37681
37856
  }
37682
37857
  }
37683
- if (this.$options.fixHeader || this.$options.bodyHeight) {
37858
+ if ((this.$options.fixHeader && this.$options.hasHeader) || this.$options.bodyHeight) {
37684
37859
  this._hidden.setBlankHeader();
37685
37860
  on(window, 'resize', this._hidden.setBlankHeader);
37686
37861
  }
@@ -39025,7 +39200,9 @@
39025
39200
  super(name, selector, options);
39026
39201
  this._hidden = {
39027
39202
  open: () => {
39028
- addClass(this.$el, 'is-open');
39203
+ if (!this.$options.disabled) {
39204
+ addClass(this.$el, 'is-open');
39205
+ }
39029
39206
  },
39030
39207
  close: () => {
39031
39208
  removeClass(this.$el, 'is-open');
@@ -39037,6 +39214,62 @@
39037
39214
  changeText: (buttonText) => {
39038
39215
  this.$options.textSets.buttonText = buttonText;
39039
39216
  html(find('.menuButton-text', this.$el), buttonText);
39217
+ },
39218
+ disable: () => {
39219
+ this.$options.disabled = true;
39220
+ const buttonEl = find('button', this.$el);
39221
+ if (buttonEl) {
39222
+ attr(buttonEl, 'disabled', true);
39223
+ }
39224
+ addClass(this.$el, 'is-disabled');
39225
+ this._hidden.close();
39226
+ },
39227
+ enable: () => {
39228
+ this.$options.disabled = false;
39229
+ const buttonEl = find('button', this.$el);
39230
+ if (buttonEl) {
39231
+ removeAttr(buttonEl, 'disabled');
39232
+ }
39233
+ removeClass(this.$el, 'is-disabled');
39234
+ },
39235
+ renderMenus: (menus, depth = 0, parentPath = '') => {
39236
+ return (createElement$1("ul", { className: depth > 0 ? 'menuButton-submenu' : '' }, menus.map((menu, index) => {
39237
+ const hasChild = menu.child && isArray$1(menu.child) && menu.child.length > 0;
39238
+ const hasHtml = !!menu.html;
39239
+ // html이 있으면 innerHTML이 모든 자식 요소를 덮어쓰므로 서브메뉴를 렌더링하지 않음
39240
+ const canRenderChild = hasChild && depth < 2 && !hasHtml; // 최대 2단계까지만 허용
39241
+ const isDisabled = menu.disabled === true;
39242
+ const isActived = menu.actived === true;
39243
+ // 부모 경로를 포함한 고유한 ID 생성
39244
+ const currentPath = parentPath ? `${parentPath}-${index}` : `${index}`;
39245
+ const uniqueId = `${this._uid}-${currentPath}`;
39246
+ return (createElement$1("li", { id: uniqueId, className: 'menuButton-menu' +
39247
+ (this.$options.align ? ' has-text-' + this.$options.align : '') +
39248
+ (canRenderChild ? ' has-submenu' : '') +
39249
+ (depth > 0 ? ' is-submenu-item' : '') +
39250
+ (isDisabled ? ' is-disabled' : '') +
39251
+ (isActived ? ' is-actived' : ''), "on-click": (e) => {
39252
+ // disabled 상태이거나 자식 메뉴가 있는 경우 클릭 이벤트 처리하지 않음
39253
+ if (isDisabled) {
39254
+ e.stopPropagation();
39255
+ e.preventDefault();
39256
+ return;
39257
+ }
39258
+ // 자식 메뉴가 없는 경우에만 select 이벤트 발생
39259
+ if (!canRenderChild) {
39260
+ e.stopPropagation();
39261
+ this._hidden.select.call(this, menu, e);
39262
+ }
39263
+ }, innerHTML: hasHtml ? menu.html : '' },
39264
+ hasHtml ? ('') : (createElement$1("span", { className: "menuButton-menu-content" },
39265
+ createElement$1("span", { className: "menuButton-menu-text" }, menu.text),
39266
+ canRenderChild && (createElement$1("span", { className: "menuButton-menu-arrow" },
39267
+ createElement$1("i", { className: "fas fa-caret-right" }))))),
39268
+ canRenderChild && this._hidden.renderMenus.call(this, menu.child, depth + 1, currentPath)));
39269
+ })));
39270
+ },
39271
+ renderSub: (data) => {
39272
+ return createElement$1("div", null, isArray$1(data) && data.length && isArray$1(data[0]) ? data.map((menus) => this._hidden.renderMenus.call(this, menus)) : this._hidden.renderMenus.call(this, data));
39040
39273
  }
39041
39274
  };
39042
39275
  this.config = {
@@ -39056,6 +39289,19 @@
39056
39289
  },
39057
39290
  buttonText(text) {
39058
39291
  this._hidden.changeText(text);
39292
+ },
39293
+ reRender(data) {
39294
+ this.$options.data = data;
39295
+ const menuMenusEl = find('.menuButton-menus > div', this.$el);
39296
+ if (menuMenusEl && this.$template) {
39297
+ this.$template.reRender(menuMenusEl, this._hidden.renderSub.call(this, data));
39298
+ }
39299
+ },
39300
+ disabled() {
39301
+ this._hidden.disable();
39302
+ },
39303
+ enabled() {
39304
+ this._hidden.enable();
39059
39305
  }
39060
39306
  };
39061
39307
  this.$selector = this.$selector;
@@ -39066,23 +39312,19 @@
39066
39312
  if (config.width) {
39067
39313
  styles.width = getUnit('width', config.width);
39068
39314
  }
39069
- const renderMenus = (menus) => {
39070
- 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) => {
39071
- this._hidden.select.call(this, menu, e);
39072
- }, innerHTML: menu.html ? menu.html : '' }, menu.html ? '' : menu.text)))));
39073
- };
39074
- const renderSub = (data) => {
39075
- return createElement$1("div", null, isArray$1(data) && data.length && isArray$1(data[0]) ? data.map((menus) => renderMenus(menus)) : renderMenus(data));
39076
- };
39077
- 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 },
39078
- createElement$1("button", { type: "button", className: config.align ? 'has-text-' + config.align : '', "on-click": this._hidden.open },
39315
+ return (createElement$1("div", { id: this._uid, className: 'gn-menuButton' +
39316
+ (config.color ? ' is-' + config.color : '') +
39317
+ (config.style ? ' is-' + config.style : '') +
39318
+ (config.size ? ' is-' + config.size : '') +
39319
+ (config.disabled ? ' is-disabled' : ''), style: styles },
39320
+ createElement$1("button", { type: "button", className: config.align ? 'has-text-' + config.align : '', disabled: config.disabled, "on-click": this._hidden.open },
39079
39321
  config.icon && (createElement$1("span", { className: 'gn-icon is-' + (config.size === 'large' ? 'medium' : config.size === 'medium' ? 'normal' : 'small') },
39080
39322
  createElement$1("i", { className: 'fas fa-' + config.icon }),
39081
39323
  ' ')),
39082
39324
  createElement$1("span", { className: "gn-icon is-small menuButton-icon" },
39083
39325
  createElement$1("i", { className: "fas fa-caret-down" })),
39084
39326
  createElement$1("span", { className: "menuButton-text" }, config.textSets.buttonText)),
39085
- createElement$1("div", { className: "menuButton-menus" }, renderSub(config.data))));
39327
+ createElement$1("div", { className: "menuButton-menus" }, this._hidden.renderSub.call(this, config.data))));
39086
39328
  }
39087
39329
  completed() {
39088
39330
  // 해당 컴포넌트 외 클릭 시 menu panel 숨김
@@ -39293,6 +39535,7 @@
39293
39535
  }
39294
39536
  }
39295
39537
 
39538
+ library$1.add(icons$1, icons);
39296
39539
  class Picklist extends GNCoreInstance {
39297
39540
  constructor(name, selector, options = {}) {
39298
39541
  super(name, selector, options);
@@ -39434,17 +39677,26 @@
39434
39677
  },
39435
39678
  renderSub: (item) => {
39436
39679
  const items = this.$options.data[item] || [];
39437
- 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', [
39438
- {
39439
- value: option.value,
39440
- text: option.text
39441
- }
39442
- ]) },
39443
- createElement$1("span", { className: "dropdown-text" }, option.text))))));
39680
+ return (createElement$1("ul", null, items.map((option, index) => {
39681
+ var _a;
39682
+ return (createElement$1("li", { id: this._uid + '_opt_' + index, className: 'dropdown-item' + (option.selected ? ' is-active' : ''), "data-value": option.value, "on-click": !option.text ? null : this._hidden.toggle.bind(this), "on-dblclick": !option.text
39683
+ ? null
39684
+ : this._hidden.move.bind(this, item === 'source' ? 'add' : 'remove', [
39685
+ {
39686
+ value: option.value,
39687
+ text: option.text,
39688
+ html: (_a = option.html) !== null && _a !== void 0 ? _a : null
39689
+ }
39690
+ ]) },
39691
+ createElement$1("span", { className: "dropdown-text", innerHTML: option.html ? option.html : '' }, option.html ? ('') : option.icon ? (createElement$1("span", null,
39692
+ createElement$1("span", { className: 'gn-icon' + (this.$options.size ? ' is-' + this.$options.size : '') },
39693
+ createElement$1("i", { className: (this.isBrandIcon(option.icon) ? 'fab' : 'fa') + ` fa-${option.icon}` })),
39694
+ escapeEntity(option.text))) : (escapeEntity(option.text)))));
39695
+ })));
39444
39696
  },
39445
39697
  getSelection: (target) => {
39446
39698
  return findAll('.is-active', this.$options.delegates[target]).map((select) => {
39447
- return { value: attr(select, 'data-value'), text: text$1(select) };
39699
+ return this.$options.data[target].find((option) => option.value === attr(select, 'data-value'));
39448
39700
  });
39449
39701
  },
39450
39702
  disable: () => {
@@ -39604,6 +39856,11 @@
39604
39856
  style(find('.picklist-target .dropdown-items', this.$el), 'height', getUnit('height', getNumber(this.$options.height) - getNumber(style(find('.picklist-target .picklist-caption', this.$el), 'height'))));
39605
39857
  }
39606
39858
  }
39859
+ isBrandIcon(iconName) {
39860
+ const iconLookup = { prefix: 'fab', iconName: iconName };
39861
+ const iconDefinition = findIconDefinition$1(iconLookup);
39862
+ return iconDefinition !== undefined;
39863
+ }
39607
39864
  }
39608
39865
 
39609
39866
  class Progressbar extends GNCoreInstance {
@@ -39762,6 +40019,409 @@
39762
40019
  }
39763
40020
  }
39764
40021
 
40022
+ const CLASS_DRAGGING = 'is-dragging';
40023
+ const CLASS_GROUP_DRAGGING = 'is-group-dragging';
40024
+ const CLASS_DRAG_OVER_TOP = 'is-drag-over-top';
40025
+ const CLASS_DRAG_OVER_BOTTOM = 'is-drag-over-bottom';
40026
+ const DEFAULT_NO_DATA = 'No records available.';
40027
+ const DRAG_STATE_CLASSES = [CLASS_DRAGGING, CLASS_GROUP_DRAGGING, CLASS_DRAG_OVER_TOP, CLASS_DRAG_OVER_BOTTOM];
40028
+ class SortableList extends GNCoreInstance {
40029
+ constructor(name, selector, options = {}) {
40030
+ super(name, selector, options);
40031
+ this._dragging = null;
40032
+ this._selection = new Set();
40033
+ this.DRAG_IMAGE_OFFSET_X = 0;
40034
+ this.DRAG_IMAGE_OFFSET_Y = 0;
40035
+ this.DRAG_OVER_THRESHOLD_RATIO = 0.5;
40036
+ this._hidden = {
40037
+ toggle: (e) => {
40038
+ if (this.$options.disabled) {
40039
+ return;
40040
+ }
40041
+ const value = attr(e.currentTarget, 'data-value');
40042
+ const hasItem = this.$options.data.some((option) => option.value === value);
40043
+ if (!value || !hasItem) {
40044
+ return;
40045
+ }
40046
+ if (this._selection.has(value)) {
40047
+ this._selection.delete(value);
40048
+ removeClass(e.currentTarget, 'is-active');
40049
+ }
40050
+ else {
40051
+ this._selection.add(value);
40052
+ addClass(e.currentTarget, 'is-active');
40053
+ }
40054
+ },
40055
+ sort: (dir) => {
40056
+ const items = this.$options.data || [];
40057
+ const selected = this._hidden.getSelection();
40058
+ if (!selected.length || selected.length === items.length) {
40059
+ return;
40060
+ }
40061
+ const selectedValues = new Set(selected.map((item) => item.value));
40062
+ // dir: up/down → 이동, up-all/down-all → 맨 위/맨 아래로 보내기
40063
+ if (dir.indexOf('all') > -1) {
40064
+ this.$options.data = items.slice().sort((a, b) => {
40065
+ const _sort = dir === 'up-all' ? -1 : 1;
40066
+ const aSel = selectedValues.has(a.value);
40067
+ const bSel = selectedValues.has(b.value);
40068
+ if (aSel && bSel) {
40069
+ return 0;
40070
+ }
40071
+ else if (aSel) {
40072
+ return _sort;
40073
+ }
40074
+ else if (bSel) {
40075
+ return _sort * -1;
40076
+ }
40077
+ return 0;
40078
+ });
40079
+ }
40080
+ else {
40081
+ if (dir === 'up') {
40082
+ const reordered = items.slice();
40083
+ // 한 번에 한 칸씩만 올려 상대 순서를 유지한다
40084
+ for (let i = 1; i < reordered.length; i++) {
40085
+ if (!selectedValues.has(reordered[i].value) || selectedValues.has(reordered[i - 1].value)) {
40086
+ continue;
40087
+ }
40088
+ const temp = reordered[i - 1];
40089
+ reordered[i - 1] = reordered[i];
40090
+ reordered[i] = temp;
40091
+ }
40092
+ this.$options.data = reordered;
40093
+ }
40094
+ else {
40095
+ let reordered = [];
40096
+ let itemsToMoveDown = [];
40097
+ items.forEach((option) => {
40098
+ // reordered: 최종 순서를 쌓는 버퍼, itemsToMoveDown: 아래로 밀어야 할 선택 항목 임시 저장
40099
+ // 비선택 항목은 흐름대로 push, 선택 항목은 dir에 따라 위/아래로 밀어 넣음
40100
+ if (!selectedValues.has(option.value)) {
40101
+ reordered.push(option);
40102
+ if (itemsToMoveDown.length) {
40103
+ reordered = reordered.concat(itemsToMoveDown);
40104
+ itemsToMoveDown = [];
40105
+ }
40106
+ }
40107
+ else if (dir === 'down') {
40108
+ itemsToMoveDown.push(option);
40109
+ }
40110
+ });
40111
+ if (itemsToMoveDown.length) {
40112
+ reordered = reordered.concat(itemsToMoveDown);
40113
+ itemsToMoveDown = [];
40114
+ }
40115
+ this.$options.data = reordered.slice();
40116
+ }
40117
+ }
40118
+ this._hidden.reRender();
40119
+ this._hidden.updateControls();
40120
+ this.$event(this, 'onChange', this.$options.data);
40121
+ },
40122
+ renderSub: () => {
40123
+ var _a, _b;
40124
+ const items = this.$options.data || [];
40125
+ const hasCols = items.some((item) => isArray$1(item.cols) && item.cols.length);
40126
+ if (!items.length) {
40127
+ return (createElement$1("ul", { className: "sortablelist-rows" },
40128
+ createElement$1("li", { className: "dropdown-item is-empty" }, (_b = (_a = this.$options.textSets) === null || _a === void 0 ? void 0 : _a.noData) !== null && _b !== void 0 ? _b : DEFAULT_NO_DATA)));
40129
+ }
40130
+ return (createElement$1("ul", { className: 'sortablelist-rows' + (hasCols ? ' is-cols' : '') }, items.map((option, index) => {
40131
+ var _a;
40132
+ return (createElement$1("li", { id: this._uid + '_opt_' + index, className: 'dropdown-item' + (this._selection.has(option.value) ? ' is-active' : ''), "data-value": option.value, draggable: this.$options.draggable && !this.$options.disabled ? true : null, "on-click": this._hidden.toggle.bind(this), "on-dragstart": this.$options.draggable ? this._hidden.dragStart.bind(this) : null, "on-dragover": this.$options.draggable ? this._hidden.dragOver.bind(this) : null, "on-dragleave": this.$options.draggable ? this._hidden.dragLeave.bind(this) : null, "on-drop": this.$options.draggable ? this._hidden.drop.bind(this) : null, "on-dragend": this.$options.draggable ? this._hidden.dragEnd.bind(this) : null }, hasCols && isArray$1(option.cols) && option.cols.length ? (createElement$1("div", { className: "sortablelist-cols" }, option.cols.map((col, colIndex) => (createElement$1("span", { className: "sortablelist-col", "data-col": colIndex }, escapeEntity(col)))))) : (createElement$1("span", { className: "dropdown-text" }, escapeEntity((_a = option.text) !== null && _a !== void 0 ? _a : option.value)))));
40133
+ })));
40134
+ },
40135
+ getSelection: () => {
40136
+ return this.$options.data.filter((option) => this._selection.has(option.value));
40137
+ },
40138
+ reRender: () => {
40139
+ const listContainer = find('ul', this.$options.delegates.list);
40140
+ listContainer && this.$template.reRender(listContainer, this._hidden.renderSub());
40141
+ },
40142
+ syncSelection: () => {
40143
+ const values = new Set(this.$options.data.map((item) => item.value));
40144
+ this._selection.forEach(val => {
40145
+ if (!values.has(val)) {
40146
+ this._selection.delete(val);
40147
+ }
40148
+ });
40149
+ },
40150
+ hydrateSelection: (items) => {
40151
+ this._selection.clear();
40152
+ items.forEach((item) => {
40153
+ if (item.selected) {
40154
+ this._selection.add(item.value);
40155
+ }
40156
+ });
40157
+ },
40158
+ validateData: (items) => {
40159
+ if (!isArray$1(items)) {
40160
+ throw new TypeError('Invalid SortableList data: data must be an array');
40161
+ }
40162
+ const seen = new Set();
40163
+ items.forEach((item) => {
40164
+ if (!item || typeof item.value !== 'string') {
40165
+ throw new TypeError('Invalid SortableList data: value must be string');
40166
+ }
40167
+ if (item.value === '') {
40168
+ throw new TypeError('Invalid SortableList data: value cannot be empty');
40169
+ }
40170
+ if (seen.has(item.value)) {
40171
+ throw new TypeError(`Invalid SortableList data: duplicate value '${item.value}'`);
40172
+ }
40173
+ seen.add(item.value);
40174
+ });
40175
+ },
40176
+ updateControls: () => {
40177
+ const hasData = (this.$options.data || []).length > 0;
40178
+ const disableButtons = this.$options.disabled || !hasData;
40179
+ if (disableButtons) {
40180
+ attr(findAll('button', this.$el), 'disabled', true);
40181
+ }
40182
+ else {
40183
+ removeAttr(findAll('button', this.$el), 'disabled');
40184
+ }
40185
+ if (!hasData) {
40186
+ addClass(this.$el, 'is-empty');
40187
+ }
40188
+ else {
40189
+ removeClass(this.$el, 'is-empty');
40190
+ }
40191
+ },
40192
+ clearDragState: () => {
40193
+ var _a;
40194
+ DRAG_STATE_CLASSES.forEach(className => {
40195
+ removeClass(findAll(`.${className}`, this.$el), className);
40196
+ });
40197
+ if ((_a = this._dragging) === null || _a === void 0 ? void 0 : _a.preview) {
40198
+ this._dragging.preview.remove();
40199
+ }
40200
+ this._dragging = null;
40201
+ },
40202
+ dragStart: (e) => {
40203
+ if (this.$options.disabled || !this.$options.draggable) {
40204
+ return;
40205
+ }
40206
+ // 기존 드래그 상태 정리 (이전 드래그가 중단된 경우 대비)
40207
+ this._hidden.clearDragState();
40208
+ const value = attr(e.currentTarget, 'data-value');
40209
+ if (!value) {
40210
+ return;
40211
+ }
40212
+ const selected = this._hidden.getSelection().map((item) => item.value);
40213
+ const isGroup = selected.length > 1 && selected.includes(value);
40214
+ const dragValues = isGroup ? selected : [value];
40215
+ this._dragging = { values: dragValues, isGroup: isGroup, preview: null };
40216
+ if (e.dataTransfer) {
40217
+ e.dataTransfer.setData('text/plain', value);
40218
+ e.dataTransfer.effectAllowed = 'move';
40219
+ if (isGroup) {
40220
+ const preview = document.createElement('div');
40221
+ preview.className = 'sortablelist-drag-preview';
40222
+ preview.setAttribute('data-count', String(dragValues.length));
40223
+ const list = document.createElement('div');
40224
+ list.className = 'sortablelist-drag-preview-list';
40225
+ dragValues.forEach((dragValue) => {
40226
+ const itemEl = find(`[data-value="${CSS.escape(dragValue)}"]`, this.$options.delegates.list);
40227
+ if (!(itemEl instanceof HTMLElement)) {
40228
+ return;
40229
+ }
40230
+ const clone = itemEl.cloneNode(true);
40231
+ removeClass(clone, CLASS_DRAGGING);
40232
+ removeClass(clone, CLASS_GROUP_DRAGGING);
40233
+ removeClass(clone, CLASS_DRAG_OVER_TOP);
40234
+ removeClass(clone, CLASS_DRAG_OVER_BOTTOM);
40235
+ addClass(clone, 'is-ghost');
40236
+ list.appendChild(clone);
40237
+ });
40238
+ preview.appendChild(list);
40239
+ document.body.appendChild(preview);
40240
+ e.dataTransfer.setDragImage(preview, this.DRAG_IMAGE_OFFSET_X, this.DRAG_IMAGE_OFFSET_Y);
40241
+ this._dragging.preview = preview;
40242
+ }
40243
+ }
40244
+ findAll('.dropdown-item', this.$options.delegates.list).forEach((node) => {
40245
+ const itemValue = attr(node, 'data-value');
40246
+ if (dragValues.includes(itemValue)) {
40247
+ addClass(node, CLASS_DRAGGING);
40248
+ if (isGroup) {
40249
+ addClass(node, CLASS_GROUP_DRAGGING);
40250
+ }
40251
+ }
40252
+ });
40253
+ },
40254
+ dragOver: (e) => {
40255
+ if (!this._dragging || this.$options.disabled || !this.$options.draggable) {
40256
+ return;
40257
+ }
40258
+ e.preventDefault();
40259
+ if (!(e.currentTarget instanceof HTMLElement)) {
40260
+ return;
40261
+ }
40262
+ const target = e.currentTarget;
40263
+ const value = attr(target, 'data-value');
40264
+ if (this._dragging.values.includes(value)) {
40265
+ return;
40266
+ }
40267
+ removeClass(target, CLASS_DRAG_OVER_TOP);
40268
+ removeClass(target, CLASS_DRAG_OVER_BOTTOM);
40269
+ const rect = target.getBoundingClientRect();
40270
+ const midpoint = rect.top + rect.height * this.DRAG_OVER_THRESHOLD_RATIO;
40271
+ if (e.clientY > midpoint) {
40272
+ addClass(target, CLASS_DRAG_OVER_BOTTOM);
40273
+ }
40274
+ else {
40275
+ addClass(target, CLASS_DRAG_OVER_TOP);
40276
+ }
40277
+ },
40278
+ dragLeave: (e) => {
40279
+ if (e.currentTarget instanceof HTMLElement) {
40280
+ removeClass(e.currentTarget, 'is-drag-over-top');
40281
+ removeClass(e.currentTarget, 'is-drag-over-bottom');
40282
+ }
40283
+ },
40284
+ drop: (e) => {
40285
+ if (!this._dragging || this.$options.disabled || !this.$options.draggable) {
40286
+ return;
40287
+ }
40288
+ e.preventDefault();
40289
+ if (!(e.currentTarget instanceof HTMLElement)) {
40290
+ return;
40291
+ }
40292
+ const targetEl = e.currentTarget;
40293
+ const targetValue = attr(targetEl, 'data-value');
40294
+ const dragValues = this._dragging.values;
40295
+ if (!targetValue || dragValues.includes(targetValue)) {
40296
+ this._hidden.clearDragState();
40297
+ return;
40298
+ }
40299
+ const items = this.$options.data;
40300
+ const dragItems = items.filter((item) => dragValues.includes(item.value));
40301
+ const remain = items.filter((item) => !dragValues.includes(item.value));
40302
+ const targetIndex = remain.findIndex((item) => item.value === targetValue);
40303
+ const rect = targetEl.getBoundingClientRect();
40304
+ const midpoint = rect.top + rect.height * this.DRAG_OVER_THRESHOLD_RATIO;
40305
+ const insertAfter = e.clientY > midpoint;
40306
+ const insertIndex = targetIndex === -1 ? remain.length : insertAfter ? targetIndex + 1 : targetIndex;
40307
+ remain.splice(insertIndex, 0, ...dragItems);
40308
+ this.$options.data = remain;
40309
+ this._hidden.reRender();
40310
+ this._hidden.updateControls();
40311
+ this.$event(this, 'onChange', this.$options.data);
40312
+ this._hidden.clearDragState();
40313
+ },
40314
+ dragEnd: () => {
40315
+ this._hidden.clearDragState();
40316
+ },
40317
+ setData: (data) => {
40318
+ // 드래그 중일 경우 상태 정리 (DOM 요소가 제거되면 dragend 이벤트가 발생하지 않을 수 있음)
40319
+ this._hidden.clearDragState();
40320
+ this._hidden.validateData(data);
40321
+ this.$options.data = data;
40322
+ this._hidden.hydrateSelection(data);
40323
+ this._hidden.syncSelection();
40324
+ this._hidden.reRender();
40325
+ this._hidden.updateControls();
40326
+ this.$event(this, 'onChange', this.$options.data);
40327
+ },
40328
+ disable: () => {
40329
+ // 드래그 중일 경우 상태 정리
40330
+ this._hidden.clearDragState();
40331
+ this.$options.disabled = true;
40332
+ addClass(this.$el, 'is-disabled');
40333
+ this._hidden.reRender();
40334
+ this._hidden.updateControls();
40335
+ },
40336
+ enable: () => {
40337
+ // 드래그 중일 경우 상태 정리
40338
+ this._hidden.clearDragState();
40339
+ this.$options.disabled = false;
40340
+ removeClass(this.$el, 'is-disabled');
40341
+ this._hidden.reRender();
40342
+ this._hidden.updateControls();
40343
+ }
40344
+ };
40345
+ this.config = {
40346
+ name: this.$selector.name || this._uid,
40347
+ data: [],
40348
+ delegates: {
40349
+ list: '.sortablelist-items'
40350
+ },
40351
+ buttonPosition: 'left',
40352
+ draggable: false,
40353
+ textSets: {
40354
+ noData: DEFAULT_NO_DATA
40355
+ },
40356
+ height: 150
40357
+ };
40358
+ this.events = {
40359
+ onChange: true
40360
+ };
40361
+ this.methods = {
40362
+ getData() {
40363
+ return this.$options.data;
40364
+ },
40365
+ setData(data) {
40366
+ const next = isArray$1(data) ? data : data && 'data' in data ? data.data : null;
40367
+ if (!isArray$1(next)) {
40368
+ throw new TypeError('Invalid SortableList data: data must be an array');
40369
+ }
40370
+ this._hidden.setData(next);
40371
+ },
40372
+ disable() {
40373
+ this._hidden.disable();
40374
+ },
40375
+ enable() {
40376
+ this._hidden.enable();
40377
+ }
40378
+ };
40379
+ this.$selector = this.$selector;
40380
+ this.$init(this, options);
40381
+ }
40382
+ template(config) {
40383
+ const styles = {};
40384
+ if (config.width) {
40385
+ styles.width = getUnit('width', config.width);
40386
+ }
40387
+ const listStyles = {};
40388
+ if (config.height) {
40389
+ const heightValue = getUnit('height', config.height);
40390
+ styles.height = heightValue;
40391
+ listStyles.height = heightValue;
40392
+ listStyles.maxHeight = heightValue;
40393
+ }
40394
+ const controlClass = 'sortablelist-controls gn-control is-small has-arrange is-vertical is-center' + (config.buttonPosition === 'right' ? ' is-right' : '');
40395
+ const buttonDisabled = config.disabled || (config.data || []).length === 0;
40396
+ return (createElement$1("div", { id: this._uid, className: 'gn-sortablelist' + (config.disabled ? ' is-disabled' : ''), style: styles },
40397
+ createElement$1("div", { className: controlClass },
40398
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'up-all'), disabled: buttonDisabled },
40399
+ createElement$1("span", { className: "gn-icon" },
40400
+ createElement$1("i", { className: "fa fa-light fa-angle-double-up" }))),
40401
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'up'), disabled: buttonDisabled },
40402
+ createElement$1("span", { className: "gn-icon" },
40403
+ createElement$1("i", { className: "fa fa-light fa-angle-up" }))),
40404
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'down'), disabled: buttonDisabled },
40405
+ createElement$1("span", { className: "gn-icon" },
40406
+ createElement$1("i", { className: "fa fa-light fa-angle-down" }))),
40407
+ createElement$1("button", { type: "button", className: "gn-button is-outline", "on-click": this._hidden.sort.bind(this, 'down-all'), disabled: buttonDisabled },
40408
+ createElement$1("span", { className: "gn-icon" },
40409
+ createElement$1("i", { className: "fa fa-light fa-angle-double-down" })))),
40410
+ createElement$1("div", { className: "gn-dropdown is-opened sortablelist-items" },
40411
+ createElement$1("div", { className: "dropdown-items", style: listStyles }, this._hidden.renderSub()))));
40412
+ }
40413
+ beforeMount() {
40414
+ this._hidden.validateData(this.$options.data);
40415
+ this._hidden.hydrateSelection(this.$options.data);
40416
+ }
40417
+ completed() {
40418
+ this._hidden.updateControls();
40419
+ }
40420
+ destroyed() {
40421
+ this._hidden.clearDragState();
40422
+ }
40423
+ }
40424
+
39765
40425
  class Splitter extends GNCoreInstance {
39766
40426
  constructor(name, selector, options = {}) {
39767
40427
  super(name, selector, options);
@@ -40409,6 +41069,7 @@
40409
41069
  picklist: Picklist,
40410
41070
  progressbar: Progressbar,
40411
41071
  selectbutton: SelectButton,
41072
+ sortablelist: SortableList,
40412
41073
  splitter: Splitter,
40413
41074
  switch: Switch,
40414
41075
  syntaxinput: SyntaxInput,