@operato/input 9.0.10 → 9.0.14

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.
@@ -4,10 +4,12 @@
4
4
  import { __decorate } from "tslib";
5
5
  import '@material/web/icon/icon.js';
6
6
  import '@material/web/button/elevated-button.js';
7
+ import './ox-input-formula-help-popup.js';
7
8
  import '@operato/i18n/ox-i18n.js';
8
9
  import { css, html } from 'lit';
9
10
  import { customElement, property, query, state } from 'lit/decorators.js';
10
11
  import { OxFormField } from '@operato/input';
12
+ import { ScrollbarStyles } from '@operato/styles';
11
13
  /**
12
14
  * Formula editor component for KPI calculations
13
15
  *
@@ -16,6 +18,8 @@ import { OxFormField } from '@operato/input';
16
18
  * <ox-input-formula
17
19
  * .value=${formulaValue}
18
20
  * .availableVariables=${variables}
21
+ * .availableFunctions=${functions}
22
+ * .includeDefaultFunctions=${true}
19
23
  * ></ox-input-formula>
20
24
  */
21
25
  let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
@@ -23,28 +27,13 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
23
27
  super(...arguments);
24
28
  this.value = '';
25
29
  this.availableVariables = [];
30
+ this.availableFunctions = [];
31
+ this.includeDefaultFunctions = true;
26
32
  this.errorMessage = '';
27
33
  this.isValid = true;
28
34
  this._changingNow = false;
29
- // 기본 연산자들
30
- this.operators = [
31
- { symbol: '+', description: '더하기' },
32
- { symbol: '-', description: '빼기' },
33
- { symbol: '*', description: '곱하기' },
34
- { symbol: '/', description: '나누기' },
35
- { symbol: '(', description: '여는 괄호' },
36
- { symbol: ')', description: '닫는 괄호' },
37
- { symbol: '=', description: '같음' },
38
- { symbol: '>', description: '보다 큼' },
39
- { symbol: '<', description: '보다 작음' },
40
- { symbol: '>=', description: '보다 크거나 같음' },
41
- { symbol: '<=', description: '보다 작거나 같음' },
42
- { symbol: '!=', description: '다름' },
43
- { symbol: '&&', description: 'AND' },
44
- { symbol: '||', description: 'OR' }
45
- ];
46
35
  // 기본 함수들 (도움말 정보 포함)
47
- this.functions = [
36
+ this._defaultFunctions = [
48
37
  {
49
38
  name: 'SUM()',
50
39
  description: '합계',
@@ -152,8 +141,26 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
152
141
  ]
153
142
  }
154
143
  ];
144
+ // 기본 연산자들
145
+ this.operators = [
146
+ { symbol: '+', description: '더하기' },
147
+ { symbol: '-', description: '빼기' },
148
+ { symbol: '*', description: '곱하기' },
149
+ { symbol: '/', description: '나누기' },
150
+ { symbol: '(', description: '여는 괄호' },
151
+ { symbol: ')', description: '닫는 괄호' },
152
+ { symbol: '=', description: '같음' },
153
+ { symbol: '>', description: '보다 큼' },
154
+ { symbol: '<', description: '보다 작음' },
155
+ { symbol: '>=', description: '보다 크거나 같음' },
156
+ { symbol: '<=', description: '보다 작거나 같음' },
157
+ { symbol: '!=', description: '다름' },
158
+ { symbol: '&&', description: 'AND' },
159
+ { symbol: '||', description: 'OR' }
160
+ ];
155
161
  }
156
162
  static { this.styles = [
163
+ ScrollbarStyles,
157
164
  css `
158
165
  :host {
159
166
  display: flex;
@@ -376,69 +383,80 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
376
383
  max-width: 500px;
377
384
  max-height: 400px;
378
385
  overflow-y: auto;
386
+ background: white;
387
+ border-radius: 8px;
388
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
379
389
  }
380
390
 
381
391
  .help-title {
382
- font-size: 16px;
392
+ font-size: 20px;
383
393
  font-weight: 600;
384
- margin-bottom: var(--spacing-medium);
394
+ margin-bottom: 16px;
385
395
  color: var(--md-sys-color-primary);
396
+ padding: 20px 20px 16px 20px;
397
+ background: #f8f9fa;
398
+ border-bottom: 1px solid #e0e0e0;
399
+ border-radius: 8px 8px 0 0;
386
400
  }
387
401
 
388
402
  .help-section {
389
- margin-bottom: var(--spacing-medium);
403
+ margin-bottom: 16px;
404
+ padding: 0 20px;
390
405
  }
391
406
 
392
407
  .help-section-title {
393
- font-weight: 500;
394
- margin-bottom: var(--spacing-small);
395
- color: var(--md-sys-color-on-surface);
408
+ font-weight: 600;
409
+ margin-bottom: 8px;
410
+ color: #333;
411
+ font-size: 14px;
412
+ border-bottom: 1px solid #e0e0e0;
413
+ padding-bottom: 4px;
396
414
  }
397
415
 
398
416
  .help-content {
399
417
  font-size: 14px;
400
- line-height: 1.5;
401
- color: var(--md-sys-color-on-surface);
418
+ line-height: 1.6;
419
+ color: #555;
420
+ padding: 4px 0;
402
421
  }
403
422
 
404
423
  .help-syntax {
405
- background-color: #f8f9fa;
406
- padding: var(--spacing-small);
424
+ background: #f5f5f5;
425
+ border: 1px solid #e0e0e0;
407
426
  border-radius: 4px;
427
+ padding: 12px;
408
428
  font-family: 'Courier New', monospace;
409
- font-size: 12px;
410
- margin: var(--spacing-small) 0;
411
- border-left: 3px solid var(--md-sys-color-primary);
429
+ font-size: 13px;
430
+ color: #333;
431
+ margin: 8px 0;
412
432
  }
413
433
 
414
434
  .help-example {
415
- background-color: #f0f8ff;
416
- padding: var(--spacing-small);
435
+ background: #f0f8ff;
436
+ border: 1px solid #c8e6c9;
417
437
  border-radius: 4px;
438
+ padding: 12px;
418
439
  font-family: 'Courier New', monospace;
419
- font-size: 12px;
420
- margin: var(--spacing-small) 0;
421
- border-left: 3px solid var(--md-sys-color-secondary);
440
+ font-size: 13px;
441
+ color: #2e7d32;
442
+ margin: 8px 0;
422
443
  }
423
444
 
424
445
  .help-parameters {
425
- margin: var(--spacing-small) 0;
446
+ margin: 8px 0;
426
447
  }
427
448
 
428
449
  .help-parameter {
429
- display: flex;
430
- justify-content: space-between;
431
- padding: 2px 0;
432
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
433
- }
434
-
435
- .help-parameter-name {
436
- font-weight: 500;
437
- font-family: 'Courier New', monospace;
450
+ background: #f8f9fa;
451
+ border-left: 3px solid var(--md-sys-color-primary);
452
+ border-radius: 4px;
453
+ padding: 8px 12px;
454
+ margin-bottom: 4px;
455
+ font-size: 13px;
438
456
  }
439
457
 
440
458
  .help-parameter-desc {
441
- color: var(--md-sys-color-on-surface-variant);
459
+ color: #555;
442
460
  }
443
461
 
444
462
  /* Dialog specific styles */
@@ -460,25 +478,27 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
460
478
  .dialog-actions {
461
479
  display: flex;
462
480
  justify-content: flex-end;
463
- gap: var(--spacing-small);
464
- margin-top: var(--spacing-medium);
465
- padding: var(--spacing-medium);
466
- border-top: 1px solid var(--md-sys-color-outline);
481
+ padding: 16px 20px;
482
+ border-top: 1px solid #e0e0e0;
483
+ background: #f8f9fa;
484
+ border-radius: 0 0 8px 8px;
467
485
  }
468
486
 
469
487
  .close-btn {
470
- padding: var(--spacing-small) var(--spacing-medium);
471
- border: 1px solid var(--md-sys-color-outline);
488
+ padding: 8px 16px;
489
+ border: 1px solid var(--md-sys-color-primary);
472
490
  border-radius: 4px;
473
- background-color: white;
491
+ background-color: var(--md-sys-color-primary);
492
+ color: white;
474
493
  cursor: pointer;
475
494
  font-size: 14px;
495
+ font-weight: 500;
476
496
  transition: all 0.2s ease;
477
497
  }
478
498
 
479
499
  .close-btn:hover {
480
- background-color: var(--md-sys-color-secondary-container);
481
- border-color: var(--md-sys-color-secondary);
500
+ background-color: #1565c0;
501
+ border-color: #1565c0;
482
502
  }
483
503
 
484
504
  .middle-align {
@@ -487,6 +507,29 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
487
507
  }
488
508
  `
489
509
  ]; }
510
+ // 사용 가능한 함수 목록을 가져오는 getter
511
+ get functions() {
512
+ let resultFunctions = [];
513
+ // 기본 함수 포함 여부 확인
514
+ if (this.includeDefaultFunctions) {
515
+ resultFunctions = [...this._defaultFunctions];
516
+ }
517
+ // 사용자 정의 함수들 추가
518
+ if (this.availableFunctions && this.availableFunctions.length > 0) {
519
+ for (const customFunc of this.availableFunctions) {
520
+ const existingIndex = resultFunctions.findIndex(f => f.name === customFunc.name);
521
+ if (existingIndex >= 0) {
522
+ // 기존 함수를 사용자 정의 함수로 교체
523
+ resultFunctions[existingIndex] = customFunc;
524
+ }
525
+ else {
526
+ // 새로운 함수 추가
527
+ resultFunctions.push(customFunc);
528
+ }
529
+ }
530
+ }
531
+ return resultFunctions;
532
+ }
490
533
  getValue() {
491
534
  return this.value;
492
535
  }
@@ -513,8 +556,6 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
513
556
  this.isValid = true;
514
557
  this._updateValue();
515
558
  }
516
- // 변수명 정규화 헬퍼 메서드
517
- // _normalizeVariableName 메서드 완전히 삭제
518
559
  firstUpdated() {
519
560
  this.addEventListener('change', this._onChange.bind(this));
520
561
  }
@@ -588,33 +629,38 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
588
629
  `)}
589
630
  </div>
590
631
 
591
- <div class="functions-container">
592
- <div class="functions-header middle-align">
593
- <md-icon>functions</md-icon>
594
- <ox-i18n msgid="label.functions"></ox-i18n>
595
- </div>
596
-
597
- <div class="functions-grid">
598
- ${this.functions.map(func => html `
599
- <div class="function-button" @click=${() => this._insertFunction(func.template)}>
600
- <div style="font-weight: 500;">${func.name}</div>
601
- <div style="font-size: 10px; color: var(--md-sys-color-on-surface-variant);">${func.description}</div>
602
- ${func.help
632
+ ${this.functions.length > 0
603
633
  ? html `
604
- <md-icon
605
- class="help-icon"
606
- @click=${(e) => this._showFunctionHelp(e, func)}
607
- style="position: absolute; top: 4px; right: 4px; font-size: 12px;"
608
- >
609
- help
610
- </md-icon>
611
- `
612
- : ''}
634
+ <div class="functions-container">
635
+ <div class="functions-header middle-align">
636
+ <md-icon>functions</md-icon>
637
+ <ox-i18n msgid="label.functions"></ox-i18n>
613
638
  </div>
614
- `)}
615
- </div>
616
- </div>
617
639
 
640
+ <div class="functions-grid">
641
+ ${this.functions.map(func => html `
642
+ <div class="function-button" @click=${() => this._insertFunction(func.template)}>
643
+ <div style="font-weight: 500;">${func.name}</div>
644
+ <div style="font-size: 10px; color: var(--md-sys-color-on-surface-variant);">
645
+ ${func.description}
646
+ </div>
647
+ ${func.help
648
+ ? html `
649
+ <md-icon
650
+ class="help-icon"
651
+ @click=${(e) => this._showFunctionHelp(e, func)}
652
+ style="position: absolute; top: 4px; right: 4px; font-size: 12px;"
653
+ >
654
+ help
655
+ </md-icon>
656
+ `
657
+ : ''}
658
+ </div>
659
+ `)}
660
+ </div>
661
+ </div>
662
+ `
663
+ : ''}
618
664
  ${this.value
619
665
  ? html `
620
666
  <div class="preview-container">
@@ -700,170 +746,110 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
700
746
  }
701
747
  _showVariableHelp(e, variable) {
702
748
  e.stopPropagation();
703
- // 간단한 테스트 버전
704
- const message = `
705
- 변수: ${variable.name}
706
- 설명: ${variable.description || '없음'}
707
- 타입: ${variable.type || '없음'}
708
- 단위: ${variable.unit || '없음'}
709
- 예시: ${variable.example || '없음'}
710
- `.trim();
711
- console.log('Variable help:', message);
712
- // dialog 지원 여부 확인
713
- if (typeof HTMLDialogElement !== 'undefined') {
714
- const dialog = document.createElement('dialog');
715
- dialog.className = 'help-dialog';
716
- // HTML 문자열로 직접 작성
717
- dialog.innerHTML = `
718
- <div class="help-popup">
719
- <div class="help-title">${variable.name}</div>
720
-
721
- ${variable.description
722
- ? `
723
- <div class="help-section">
724
- <div class="help-section-title">설명</div>
725
- <div class="help-content">${variable.description}</div>
726
- </div>
727
- `
728
- : ''}
729
-
730
- ${variable.type
731
- ? `
732
- <div class="help-section">
733
- <div class="help-section-title">타입</div>
734
- <div class="help-content">${variable.type}</div>
735
- </div>
736
- `
737
- : ''}
738
-
739
- ${variable.unit
740
- ? `
741
- <div class="help-section">
742
- <div class="help-section-title">단위</div>
743
- <div class="help-content">${variable.unit}</div>
744
- </div>
745
- `
746
- : ''}
747
-
748
- ${variable.help
749
- ? `
750
- <div class="help-section">
751
- <div class="help-section-title">상세 설명</div>
752
- <div class="help-content">${variable.help}</div>
753
- </div>
754
- `
755
- : ''}
756
-
757
- ${variable.example
758
- ? `
759
- <div class="help-section">
760
- <div class="help-section-title">사용 예시</div>
761
- <div class="help-example">${variable.example}</div>
762
- </div>
763
- `
764
- : ''}
765
- </div>
766
-
767
- <div class="dialog-actions">
768
- <button class="close-btn" onclick="this.closest('dialog').close()">닫기</button>
769
- </div>
770
- `;
771
- document.body.appendChild(dialog);
772
- dialog.showModal();
773
- // 외부 클릭으로 닫기
774
- dialog.addEventListener('click', event => {
775
- const rect = dialog.getBoundingClientRect();
776
- const isInDialog = event.clientX >= rect.left &&
777
- event.clientX <= rect.right &&
778
- event.clientY >= rect.top &&
779
- event.clientY <= rect.bottom;
780
- if (!isInDialog) {
781
- dialog.close();
782
- }
749
+ const sections = [];
750
+ if (variable.description) {
751
+ sections.push({
752
+ title: '설명',
753
+ content: variable.description,
754
+ type: 'text'
783
755
  });
784
- dialog.addEventListener('close', () => {
785
- if (document.body.contains(dialog)) {
786
- document.body.removeChild(dialog);
787
- }
756
+ }
757
+ if (variable.type) {
758
+ sections.push({
759
+ title: '타입',
760
+ content: variable.type,
761
+ type: 'text'
762
+ });
763
+ }
764
+ if (variable.unit) {
765
+ sections.push({
766
+ title: '단위',
767
+ content: variable.unit,
768
+ type: 'text'
769
+ });
770
+ }
771
+ if (variable.help) {
772
+ sections.push({
773
+ title: '상세 설명',
774
+ content: variable.help,
775
+ type: 'text'
788
776
  });
789
- // ESC 키로 닫기
790
- const handleEsc = (e) => {
791
- if (e.key === 'Escape') {
792
- dialog.close();
793
- document.removeEventListener('keydown', handleEsc);
794
- }
795
- };
796
- document.addEventListener('keydown', handleEsc);
797
777
  }
798
- else {
799
- // dialog를 지원하지 않는 브라우저에서는 alert 사용
800
- alert(message);
778
+ if (variable.example) {
779
+ sections.push({
780
+ title: '사용 예시',
781
+ content: variable.example,
782
+ type: 'example'
783
+ });
801
784
  }
785
+ const helpContent = {
786
+ title: variable.name,
787
+ sections
788
+ };
789
+ this._showHelpPopup(helpContent);
802
790
  }
803
791
  _showFunctionHelp(e, func) {
804
792
  e.stopPropagation();
793
+ const sections = [
794
+ {
795
+ title: '설명',
796
+ content: func.description,
797
+ type: 'text'
798
+ },
799
+ {
800
+ title: '구문',
801
+ content: func.syntax,
802
+ type: 'code'
803
+ },
804
+ {
805
+ title: '매개변수',
806
+ content: func.parameters.join('\n'),
807
+ type: 'parameter'
808
+ },
809
+ {
810
+ title: '반환 타입',
811
+ content: func.returnType,
812
+ type: 'text'
813
+ }
814
+ ];
815
+ if (func.help) {
816
+ sections.push({
817
+ title: '상세 설명',
818
+ content: func.help,
819
+ type: 'text'
820
+ });
821
+ }
822
+ if (func.examples && func.examples.length > 0) {
823
+ sections.push({
824
+ title: '사용 예시',
825
+ content: func.examples.join('\n'),
826
+ type: 'example'
827
+ });
828
+ }
829
+ const helpContent = {
830
+ title: func.name,
831
+ sections
832
+ };
833
+ this._showHelpPopup(helpContent);
834
+ }
835
+ _showHelpPopup(content) {
805
836
  const dialog = document.createElement('dialog');
806
- dialog.className = 'help-dialog';
807
- // HTML 문자열로 직접 작성
808
- dialog.innerHTML = `
809
- <div class="help-popup">
810
- <div class="help-title">${func.name}</div>
811
-
812
- <div class="help-section">
813
- <div class="help-section-title">설명</div>
814
- <div class="help-content">${func.description}</div>
815
- </div>
816
-
817
- <div class="help-section">
818
- <div class="help-section-title">구문</div>
819
- <div class="help-syntax">${func.syntax}</div>
820
- </div>
821
-
822
- <div class="help-section">
823
- <div class="help-section-title">매개변수</div>
824
- <div class="help-parameters">
825
- ${func.parameters
826
- .map(param => `
827
- <div class="help-parameter">
828
- <span class="help-parameter-desc">${param}</span>
829
- </div>
830
- `)
831
- .join('')}
832
- </div>
833
- </div>
834
-
835
- <div class="help-section">
836
- <div class="help-section-title">반환 타입</div>
837
- <div class="help-content">${func.returnType}</div>
838
- </div>
839
-
840
- ${func.help
841
- ? `
842
- <div class="help-section">
843
- <div class="help-section-title">상세 설명</div>
844
- <div class="help-content">${func.help}</div>
845
- </div>
846
- `
847
- : ''}
848
-
849
- ${func.examples && func.examples.length > 0
850
- ? `
851
- <div class="help-section">
852
- <div class="help-section-title">사용 예시</div>
853
- ${func.examples
854
- .map(example => `
855
- <div class="help-example">${example}</div>
856
- `)
857
- .join('')}
858
- </div>
859
- `
860
- : ''}
861
- </div>
862
-
863
- <div class="dialog-actions">
864
- <button class="close-btn" onclick="this.closest('dialog').close()">닫기</button>
865
- </div>
837
+ dialog.style.cssText = `
838
+ border: none;
839
+ border-radius: 8px;
840
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
841
+ padding: 0;
842
+ max-width: 500px;
843
+ max-height: 80vh;
844
+ overflow-y: auto;
845
+ background-color: white;
866
846
  `;
847
+ const helpPopup = document.createElement('ox-input-formula-help-popup');
848
+ helpPopup.content = content;
849
+ helpPopup.onClose = () => {
850
+ dialog.close();
851
+ };
852
+ dialog.appendChild(helpPopup);
867
853
  document.body.appendChild(dialog);
868
854
  dialog.showModal();
869
855
  // 외부 클릭으로 닫기
@@ -949,6 +935,12 @@ __decorate([
949
935
  __decorate([
950
936
  property({ type: Array })
951
937
  ], KpiFormulaEditor.prototype, "availableVariables", void 0);
938
+ __decorate([
939
+ property({ type: Array })
940
+ ], KpiFormulaEditor.prototype, "availableFunctions", void 0);
941
+ __decorate([
942
+ property({ type: Boolean })
943
+ ], KpiFormulaEditor.prototype, "includeDefaultFunctions", void 0);
952
944
  __decorate([
953
945
  state()
954
946
  ], KpiFormulaEditor.prototype, "errorMessage", void 0);