@operato/input 9.0.9 → 9.0.13

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,24 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ### [9.0.13](https://github.com/hatiolab/operato/compare/v9.0.12...v9.0.13) (2025-07-27)
7
+
8
+
9
+ ### :bug: Bug Fix
10
+
11
+ * custom functions for ox-input-formula ([42e36d1](https://github.com/hatiolab/operato/commit/42e36d10a6198ddd0e736cdc1cf2d7863749c68c))
12
+
13
+
14
+
15
+ ### [9.0.10](https://github.com/hatiolab/operato/compare/v9.0.9...v9.0.10) (2025-07-23)
16
+
17
+
18
+ ### :bug: Bug Fix
19
+
20
+ * ox-grist-editor-formula ([f0e165e](https://github.com/hatiolab/operato/commit/f0e165e06a29dc6a4cf3adf85d20cc1e7b36ce4e))
21
+
22
+
23
+
6
24
  ### [9.0.9](https://github.com/hatiolab/operato/compare/v9.0.8...v9.0.9) (2025-07-23)
7
25
 
8
26
 
@@ -8,7 +8,7 @@ import { OxFormField } from '@operato/input';
8
8
  type FormulaVariable = {
9
9
  name: string;
10
10
  description?: string;
11
- type?: 'number' | 'string' | 'date' | 'metric' | 'kpi';
11
+ type?: string;
12
12
  unit?: string;
13
13
  help?: string;
14
14
  example?: string;
@@ -31,16 +31,22 @@ type FormulaFunction = {
31
31
  * <ox-input-formula
32
32
  * .value=${formulaValue}
33
33
  * .availableVariables=${variables}
34
+ * .availableFunctions=${functions}
35
+ * .includeDefaultFunctions=${true}
34
36
  * ></ox-input-formula>
35
37
  */
36
38
  export declare class KpiFormulaEditor extends OxFormField {
37
39
  static styles: import("lit").CSSResult[];
38
40
  value: string;
39
41
  availableVariables: FormulaVariable[];
42
+ availableFunctions: FormulaFunction[];
43
+ includeDefaultFunctions: boolean;
40
44
  private errorMessage;
41
45
  private isValid;
42
46
  editor: HTMLTextAreaElement;
43
47
  private _changingNow;
48
+ private readonly _defaultFunctions;
49
+ get functions(): FormulaFunction[];
44
50
  getValue(): any;
45
51
  setValue(value: any): void;
46
52
  validate(): boolean;
@@ -49,8 +55,6 @@ export declare class KpiFormulaEditor extends OxFormField {
49
55
  blur(): void;
50
56
  reset(): void;
51
57
  private readonly operators;
52
- private readonly functions;
53
- private _normalizeVariableName;
54
58
  firstUpdated(): void;
55
59
  render(): import("lit-html").TemplateResult<1>;
56
60
  updated(changes: any): void;
@@ -16,6 +16,8 @@ import { OxFormField } from '@operato/input';
16
16
  * <ox-input-formula
17
17
  * .value=${formulaValue}
18
18
  * .availableVariables=${variables}
19
+ * .availableFunctions=${functions}
20
+ * .includeDefaultFunctions=${true}
19
21
  * ></ox-input-formula>
20
22
  */
21
23
  let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
@@ -23,28 +25,13 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
23
25
  super(...arguments);
24
26
  this.value = '';
25
27
  this.availableVariables = [];
28
+ this.availableFunctions = [];
29
+ this.includeDefaultFunctions = true;
26
30
  this.errorMessage = '';
27
31
  this.isValid = true;
28
32
  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
33
  // 기본 함수들 (도움말 정보 포함)
47
- this.functions = [
34
+ this._defaultFunctions = [
48
35
  {
49
36
  name: 'SUM()',
50
37
  description: '합계',
@@ -152,6 +139,23 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
152
139
  ]
153
140
  }
154
141
  ];
142
+ // 기본 연산자들
143
+ this.operators = [
144
+ { symbol: '+', description: '더하기' },
145
+ { symbol: '-', description: '빼기' },
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: 'AND' },
157
+ { symbol: '||', description: 'OR' }
158
+ ];
155
159
  }
156
160
  static { this.styles = [
157
161
  css `
@@ -487,6 +491,29 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
487
491
  }
488
492
  `
489
493
  ]; }
494
+ // 사용 가능한 함수 목록을 가져오는 getter
495
+ get functions() {
496
+ let resultFunctions = [];
497
+ // 기본 함수 포함 여부 확인
498
+ if (this.includeDefaultFunctions) {
499
+ resultFunctions = [...this._defaultFunctions];
500
+ }
501
+ // 사용자 정의 함수들 추가
502
+ if (this.availableFunctions && this.availableFunctions.length > 0) {
503
+ for (const customFunc of this.availableFunctions) {
504
+ const existingIndex = resultFunctions.findIndex(f => f.name === customFunc.name);
505
+ if (existingIndex >= 0) {
506
+ // 기존 함수를 사용자 정의 함수로 교체
507
+ resultFunctions[existingIndex] = customFunc;
508
+ }
509
+ else {
510
+ // 새로운 함수 추가
511
+ resultFunctions.push(customFunc);
512
+ }
513
+ }
514
+ }
515
+ return resultFunctions;
516
+ }
490
517
  getValue() {
491
518
  return this.value;
492
519
  }
@@ -513,11 +540,6 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
513
540
  this.isValid = true;
514
541
  this._updateValue();
515
542
  }
516
- // 변수명 정규화 헬퍼 메서드
517
- _normalizeVariableName(name) {
518
- // 공백을 언더스코어로 변경
519
- return name.replace(/\s+/g, '_');
520
- }
521
543
  firstUpdated() {
522
544
  this.addEventListener('change', this._onChange.bind(this));
523
545
  }
@@ -563,7 +585,7 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
563
585
  <div class="variables-grid">
564
586
  ${this.availableVariables.map(variable => html `
565
587
  <div class="variable-item" @click=${() => this._insertVariable(variable.name)}>
566
- <div class="variable-name">[${this._normalizeVariableName(variable.name)}]</div>
588
+ <div class="variable-name">[${variable.name}]</div>
567
589
  ${variable.description ? html ` <div class="variable-description">${variable.description}</div> ` : ''}
568
590
  ${variable.type ? html ` <div class="variable-type">${variable.type}</div> ` : ''}
569
591
  ${variable.help
@@ -591,33 +613,38 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
591
613
  `)}
592
614
  </div>
593
615
 
594
- <div class="functions-container">
595
- <div class="functions-header middle-align">
596
- <md-icon>functions</md-icon>
597
- <ox-i18n msgid="label.functions"></ox-i18n>
598
- </div>
599
-
600
- <div class="functions-grid">
601
- ${this.functions.map(func => html `
602
- <div class="function-button" @click=${() => this._insertFunction(func.template)}>
603
- <div style="font-weight: 500;">${func.name}</div>
604
- <div style="font-size: 10px; color: var(--md-sys-color-on-surface-variant);">${func.description}</div>
605
- ${func.help
616
+ ${this.functions.length > 0
606
617
  ? html `
607
- <md-icon
608
- class="help-icon"
609
- @click=${(e) => this._showFunctionHelp(e, func)}
610
- style="position: absolute; top: 4px; right: 4px; font-size: 12px;"
611
- >
612
- help
613
- </md-icon>
614
- `
615
- : ''}
618
+ <div class="functions-container">
619
+ <div class="functions-header middle-align">
620
+ <md-icon>functions</md-icon>
621
+ <ox-i18n msgid="label.functions"></ox-i18n>
616
622
  </div>
617
- `)}
618
- </div>
619
- </div>
620
623
 
624
+ <div class="functions-grid">
625
+ ${this.functions.map(func => html `
626
+ <div class="function-button" @click=${() => this._insertFunction(func.template)}>
627
+ <div style="font-weight: 500;">${func.name}</div>
628
+ <div style="font-size: 10px; color: var(--md-sys-color-on-surface-variant);">
629
+ ${func.description}
630
+ </div>
631
+ ${func.help
632
+ ? html `
633
+ <md-icon
634
+ class="help-icon"
635
+ @click=${(e) => this._showFunctionHelp(e, func)}
636
+ style="position: absolute; top: 4px; right: 4px; font-size: 12px;"
637
+ >
638
+ help
639
+ </md-icon>
640
+ `
641
+ : ''}
642
+ </div>
643
+ `)}
644
+ </div>
645
+ </div>
646
+ `
647
+ : ''}
621
648
  ${this.value
622
649
  ? html `
623
650
  <div class="preview-container">
@@ -655,9 +682,8 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
655
682
  const start = this.editor.selectionStart;
656
683
  const end = this.editor.selectionEnd;
657
684
  const currentValue = this.editor.value;
658
- // 변수명 정규화 (공백을 언더스코어로 변경)
659
- const normalizedName = this._normalizeVariableName(variableName);
660
- const insertText = `[${normalizedName}]`;
685
+ // 변수명을 [변수명] 형태로만 감싸서 삽입
686
+ const insertText = `[${variableName}]`;
661
687
  const newValue = currentValue.substring(0, start) + insertText + currentValue.substring(end);
662
688
  this.editor.value = newValue;
663
689
  this.value = newValue;
@@ -915,9 +941,8 @@ let KpiFormulaEditor = class KpiFormulaEditor extends OxFormField {
915
941
  const variableRefs = formula.match(/\[([^\]]+)\]/g) || [];
916
942
  for (const ref of variableRefs) {
917
943
  const varName = ref.slice(1, -1); // [변수명]에서 변수명 추출
918
- const normalizedVarName = this._normalizeVariableName(varName);
919
- const availableVarNames = this.availableVariables.map(v => this._normalizeVariableName(v.name));
920
- if (!availableVarNames.includes(normalizedVarName)) {
944
+ const availableVarNames = this.availableVariables.map(v => v.name);
945
+ if (!availableVarNames.includes(varName)) {
921
946
  this.errorMessage = `알 수 없는 변수: ${varName}`;
922
947
  this.isValid = false;
923
948
  return;
@@ -954,6 +979,12 @@ __decorate([
954
979
  __decorate([
955
980
  property({ type: Array })
956
981
  ], KpiFormulaEditor.prototype, "availableVariables", void 0);
982
+ __decorate([
983
+ property({ type: Array })
984
+ ], KpiFormulaEditor.prototype, "availableFunctions", void 0);
985
+ __decorate([
986
+ property({ type: Boolean })
987
+ ], KpiFormulaEditor.prototype, "includeDefaultFunctions", void 0);
957
988
  __decorate([
958
989
  state()
959
990
  ], KpiFormulaEditor.prototype, "errorMessage", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"ox-input-formula.js","sourceRoot":"","sources":["../../src/ox-input-formula.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport '@material/web/icon/icon.js'\nimport '@material/web/button/elevated-button.js'\n\nimport '@operato/i18n/ox-i18n.js'\nimport { css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { OxFormField } from '@operato/input'\n\ntype FormulaVariable = {\n name: string\n description?: string\n type?: 'number' | 'string' | 'date' | 'metric' | 'kpi'\n unit?: string\n help?: string\n example?: string\n}\n\ntype FormulaFunction = {\n name: string\n description: string\n template: string\n syntax: string\n parameters: string[]\n returnType: string\n help?: string\n examples?: string[]\n}\n\n/**\n * Formula editor component for KPI calculations\n *\n * Example:\n *\n * <ox-input-formula\n * .value=${formulaValue}\n * .availableVariables=${variables}\n * ></ox-input-formula>\n */\n@customElement('ox-input-formula')\nexport class KpiFormulaEditor extends OxFormField {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n margin-bottom: var(--spacing-large);\n gap: var(--spacing-medium);\n }\n\n .formula-container {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small);\n }\n\n .formula-input-container {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small);\n }\n\n .formula-textarea {\n min-height: 120px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 4px;\n padding: var(--spacing-medium);\n font-family: 'Courier New', monospace;\n font-size: 14px;\n line-height: 1.4;\n resize: vertical;\n background-color: #f8f9fa;\n font-size: 1.3rem;\n }\n\n .formula-textarea:focus {\n outline: none;\n border-color: var(--md-sys-color-primary);\n background-color: white;\n }\n\n .variables-container {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small);\n }\n\n .variables-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-small);\n font-weight: 500;\n color: var(--md-sys-color-on-surface);\n }\n\n .variables-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: var(--spacing-small);\n max-height: 200px;\n overflow-y: auto;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 4px;\n padding: var(--spacing-small);\n background-color: #f8f9fa;\n }\n\n .variable-item {\n display: flex;\n flex-direction: column;\n padding: var(--spacing-small);\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 4px;\n background-color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n position: relative;\n }\n\n .variable-item:hover {\n border-color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-primary-container);\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n\n .variable-name {\n font-weight: 500;\n color: var(--md-sys-color-primary);\n font-size: 12px;\n font-family: 'Courier New', monospace;\n }\n\n .variable-description {\n font-size: 11px;\n color: var(--md-sys-color-on-surface-variant);\n margin-top: 2px;\n }\n\n .variable-type {\n font-size: 10px;\n color: var(--md-sys-color-outline);\n margin-top: 2px;\n text-transform: uppercase;\n }\n\n .help-icon {\n position: absolute;\n top: 4px;\n right: 4px;\n font-size: 14px;\n color: var(--md-sys-color-outline);\n cursor: pointer;\n padding: 2px;\n border-radius: 2px;\n transition: all 0.2s ease;\n }\n\n .help-icon:hover {\n color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-primary-container);\n }\n\n .operators-container {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small);\n margin-top: var(--spacing-small);\n }\n\n .operator-button {\n padding: var(--spacing-small) var(--spacing-medium);\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 4px;\n background-color: white;\n cursor: pointer;\n font-family: 'Courier New', monospace;\n font-size: 12px;\n transition: all 0.2s ease;\n }\n\n .operator-button:hover {\n background-color: var(--md-sys-color-primary-container);\n border-color: var(--md-sys-color-primary);\n }\n\n .functions-container {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small);\n margin-top: var(--spacing-small);\n }\n\n .functions-header {\n font-weight: 500;\n color: var(--md-sys-color-on-surface);\n }\n\n .functions-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: var(--spacing-small);\n }\n\n .function-button {\n padding: var(--spacing-small);\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 4px;\n background-color: white;\n cursor: pointer;\n font-family: 'Courier New', monospace;\n font-size: 11px;\n text-align: left;\n transition: all 0.2s ease;\n position: relative;\n }\n\n .function-button:hover {\n background-color: var(--md-sys-color-secondary-container);\n border-color: var(--md-sys-color-secondary);\n }\n\n .preview-container {\n margin-top: var(--spacing-medium);\n padding: var(--spacing-medium);\n background-color: #f8f9fa;\n border-radius: 4px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n .preview-title {\n font-weight: 500;\n margin-bottom: var(--spacing-small);\n color: var(--md-sys-color-on-surface);\n }\n\n .preview-formula {\n font-family: 'Courier New', monospace;\n background-color: white;\n padding: var(--spacing-small);\n border-radius: 4px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n white-space: pre-wrap;\n word-break: break-all;\n }\n\n .error-message {\n color: var(--md-sys-color-error);\n font-size: 12px;\n margin-top: var(--spacing-small);\n }\n\n .info-message {\n color: var(--md-sys-color-on-surface-variant);\n font-size: 12px;\n margin-top: var(--spacing-small);\n }\n\n /* 도움말 팝업 스타일 */\n .help-popup {\n max-width: 500px;\n max-height: 400px;\n overflow-y: auto;\n }\n\n .help-title {\n font-size: 16px;\n font-weight: 600;\n margin-bottom: var(--spacing-medium);\n color: var(--md-sys-color-primary);\n }\n\n .help-section {\n margin-bottom: var(--spacing-medium);\n }\n\n .help-section-title {\n font-weight: 500;\n margin-bottom: var(--spacing-small);\n color: var(--md-sys-color-on-surface);\n }\n\n .help-content {\n font-size: 14px;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface);\n }\n\n .help-syntax {\n background-color: #f8f9fa;\n padding: var(--spacing-small);\n border-radius: 4px;\n font-family: 'Courier New', monospace;\n font-size: 12px;\n margin: var(--spacing-small) 0;\n border-left: 3px solid var(--md-sys-color-primary);\n }\n\n .help-example {\n background-color: #f0f8ff;\n padding: var(--spacing-small);\n border-radius: 4px;\n font-family: 'Courier New', monospace;\n font-size: 12px;\n margin: var(--spacing-small) 0;\n border-left: 3px solid var(--md-sys-color-secondary);\n }\n\n .help-parameters {\n margin: var(--spacing-small) 0;\n }\n\n .help-parameter {\n display: flex;\n justify-content: space-between;\n padding: 2px 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n .help-parameter-name {\n font-weight: 500;\n font-family: 'Courier New', monospace;\n }\n\n .help-parameter-desc {\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* Dialog specific styles */\n .help-dialog {\n border: none;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);\n padding: 0;\n max-width: 500px;\n max-height: 80vh;\n overflow-y: auto;\n background-color: white;\n }\n\n .help-dialog::backdrop {\n background-color: rgba(0, 0, 0, 0.5);\n }\n\n .dialog-actions {\n display: flex;\n justify-content: flex-end;\n gap: var(--spacing-small);\n margin-top: var(--spacing-medium);\n padding: var(--spacing-medium);\n border-top: 1px solid var(--md-sys-color-outline);\n }\n\n .close-btn {\n padding: var(--spacing-small) var(--spacing-medium);\n border: 1px solid var(--md-sys-color-outline);\n border-radius: 4px;\n background-color: white;\n cursor: pointer;\n font-size: 14px;\n transition: all 0.2s ease;\n }\n\n .close-btn:hover {\n background-color: var(--md-sys-color-secondary-container);\n border-color: var(--md-sys-color-secondary);\n }\n\n .middle-align {\n display: flex;\n align-items: center;\n }\n `\n ]\n\n @property({ type: String }) value: string = ''\n @property({ type: Array }) availableVariables: FormulaVariable[] = []\n\n @state() private errorMessage: string = ''\n @state() private isValid: boolean = true\n\n @query('.formula-textarea') editor!: HTMLTextAreaElement\n\n private _changingNow: boolean = false\n\n getValue(): any {\n return this.value\n }\n\n setValue(value: any): void {\n this.value = value\n this._validateFormula()\n }\n\n validate(): boolean {\n this._validateFormula()\n return this.isValid\n }\n\n getErrorMessage(): string {\n return this.errorMessage\n }\n\n focus(): void {\n this.editor.focus()\n }\n\n blur(): void {\n this.editor.blur()\n }\n\n reset(): void {\n this.value = ''\n this.errorMessage = ''\n this.isValid = true\n this._updateValue()\n }\n\n // 기본 연산자들\n private readonly operators = [\n { symbol: '+', description: '더하기' },\n { symbol: '-', description: '빼기' },\n { symbol: '*', description: '곱하기' },\n { symbol: '/', description: '나누기' },\n { symbol: '(', description: '여는 괄호' },\n { symbol: ')', description: '닫는 괄호' },\n { symbol: '=', description: '같음' },\n { symbol: '>', description: '보다 큼' },\n { symbol: '<', description: '보다 작음' },\n { symbol: '>=', description: '보다 크거나 같음' },\n { symbol: '<=', description: '보다 작거나 같음' },\n { symbol: '!=', description: '다름' },\n { symbol: '&&', description: 'AND' },\n { symbol: '||', description: 'OR' }\n ]\n\n // 기본 함수들 (도움말 정보 포함)\n private readonly functions: FormulaFunction[] = [\n {\n name: 'SUM()',\n description: '합계',\n template: 'SUM({expression})',\n syntax: 'SUM(expression)',\n parameters: ['expression - 합계를 계산할 값 또는 변수'],\n returnType: 'number',\n help: '주어진 값들의 합계를 계산합니다. 숫자 배열이나 변수들을 인자로 받습니다.',\n examples: ['SUM(metric1, metric2, metric3)', 'SUM(production_data)', 'SUM(100, 200, 300)']\n },\n {\n name: 'AVG()',\n description: '평균',\n template: 'AVG({expression})',\n syntax: 'AVG(expression)',\n parameters: ['expression - 평균을 계산할 값 또는 변수'],\n returnType: 'number',\n help: '주어진 값들의 평균을 계산합니다. 숫자 배열이나 변수들을 인자로 받습니다.',\n examples: ['AVG(quality_scores)', 'AVG(metric1, metric2, metric3)', 'AVG(10, 20, 30)']\n },\n {\n name: 'MAX()',\n description: '최대값',\n template: 'MAX({expression})',\n syntax: 'MAX(expression)',\n parameters: ['expression - 최대값을 찾을 값 또는 변수'],\n returnType: 'number',\n help: '주어진 값들 중 최대값을 반환합니다.',\n examples: ['MAX(performance_data)', 'MAX(metric1, metric2, metric3)', 'MAX(100, 200, 300)']\n },\n {\n name: 'MIN()',\n description: '최소값',\n template: 'MIN({expression})',\n syntax: 'MIN(expression)',\n parameters: ['expression - 최소값을 찾을 값 또는 변수'],\n returnType: 'number',\n help: '주어진 값들 중 최소값을 반환합니다.',\n examples: ['MIN(quality_scores)', 'MIN(metric1, metric2, metric3)', 'MIN(10, 20, 30)']\n },\n {\n name: 'COUNT()',\n description: '개수',\n template: 'COUNT({expression})',\n syntax: 'COUNT(expression)',\n parameters: ['expression - 개수를 셀 값 또는 변수'],\n returnType: 'number',\n help: '주어진 값들의 개수를 반환합니다.',\n examples: ['COUNT(active_projects)', 'COUNT(metric1, metric2, metric3)', 'COUNT(1, 2, 3, 4, 5)']\n },\n {\n name: 'ROUND()',\n description: '반올림',\n template: 'ROUND({expression}, 2)',\n syntax: 'ROUND(expression, decimals)',\n parameters: ['expression - 반올림할 값', 'decimals - 소수점 자릿수 (기본값: 0)'],\n returnType: 'number',\n help: '주어진 값을 지정된 소수점 자릿수로 반올림합니다.',\n examples: ['ROUND(3.14159, 2) → 3.14', 'ROUND(metric1, 0)', 'ROUND(AVG(scores), 1)']\n },\n {\n name: 'ABS()',\n description: '절대값',\n template: 'ABS({expression})',\n syntax: 'ABS(expression)',\n parameters: ['expression - 절대값을 구할 값'],\n returnType: 'number',\n help: '주어진 값의 절대값을 반환합니다.',\n examples: ['ABS(-10) → 10', 'ABS(metric1 - target)', 'ABS(deviation)']\n },\n {\n name: 'IF()',\n description: '조건문',\n template: 'IF({condition}, {true_value}, {false_value})',\n syntax: 'IF(condition, true_value, false_value)',\n parameters: [\n 'condition - 조건식 (true/false)',\n 'true_value - 조건이 참일 때 반환할 값',\n 'false_value - 조건이 거짓일 때 반환할 값'\n ],\n returnType: 'any',\n help: '조건에 따라 다른 값을 반환합니다. 조건이 참이면 true_value를, 거짓이면 false_value를 반환합니다.',\n examples: [\n 'IF(score > 80, \"우수\", \"보통\")',\n 'IF(metric1 > target, 100, 0)',\n 'IF(quality_score >= 90, \"A등급\", IF(quality_score >= 80, \"B등급\", \"C등급\"))'\n ]\n },\n {\n name: 'CASE()',\n description: '케이스문',\n template: 'CASE {expression} WHEN {value1} THEN {result1} ELSE {default} END',\n syntax: 'CASE expression WHEN value1 THEN result1 [WHEN value2 THEN result2]... ELSE default END',\n parameters: [\n 'expression - 비교할 값',\n 'value1, value2... - 비교할 값들',\n 'result1, result2... - 해당 값일 때 반환할 결과들',\n 'default - 기본값'\n ],\n returnType: 'any',\n help: '여러 조건에 따라 다른 값을 반환합니다. IF문의 확장된 형태입니다.',\n examples: [\n 'CASE grade WHEN \"A\" THEN 100 WHEN \"B\" THEN 80 WHEN \"C\" THEN 60 ELSE 0 END',\n 'CASE score WHEN 90 THEN \"우수\" WHEN 80 THEN \"양호\" WHEN 70 THEN \"보통\" ELSE \"미흡\" END'\n ]\n }\n ]\n\n // 변수명 정규화 헬퍼 메서드\n private _normalizeVariableName(name: string): string {\n // 공백을 언더스코어로 변경\n return name.replace(/\\s+/g, '_')\n }\n\n firstUpdated() {\n this.addEventListener('change', this._onChange.bind(this))\n }\n\n render() {\n return html`\n <div class=\"formula-container\">\n <div class=\"formula-input-container\">\n <label class=\"variables-header\">\n <ox-i18n msgid=\"label.formula\"></ox-i18n>\n </label>\n\n <textarea\n class=\"formula-textarea\"\n .value=${this.value}\n @input=${this._onFormulaInput.bind(this)}\n placeholder=\"수식을 입력하세요. 예: SUM(metric1) + AVG(metric2) * 0.5\"\n ></textarea>\n\n ${this.errorMessage\n ? html`\n <div class=\"error-message middle-align\">\n <md-icon>error</md-icon>\n ${this.errorMessage}\n </div>\n `\n : ''}\n ${!this.errorMessage && this.value\n ? html`\n <div class=\"info-message middle-align\">\n <md-icon>info</md-icon>\n 수식이 유효합니다.\n </div>\n `\n : ''}\n </div>\n\n <div class=\"variables-container\">\n <div class=\"variables-header middle-align\">\n <md-icon>variables</md-icon>\n <ox-i18n msgid=\"label.available-variables\"></ox-i18n>\n </div>\n\n <div class=\"variables-grid\">\n ${this.availableVariables.map(\n variable => html`\n <div class=\"variable-item\" @click=${() => this._insertVariable(variable.name)}>\n <div class=\"variable-name\">[${this._normalizeVariableName(variable.name)}]</div>\n ${variable.description ? html` <div class=\"variable-description\">${variable.description}</div> ` : ''}\n ${variable.type ? html` <div class=\"variable-type\">${variable.type}</div> ` : ''}\n ${variable.help\n ? html`\n <md-icon class=\"help-icon\" @click=${(e: Event) => this._showVariableHelp(e, variable)}>\n help\n </md-icon>\n `\n : ''}\n </div>\n `\n )}\n </div>\n </div>\n\n <div class=\"operators-container\">\n <div class=\"variables-header middle-align\">\n <md-icon>calculate</md-icon>\n <ox-i18n msgid=\"label.operators\"></ox-i18n>\n </div>\n\n ${this.operators.map(\n op => html`\n <button class=\"operator-button\" @click=${() => this._insertOperator(op.symbol)} title=\"${op.description}\">\n ${op.symbol}\n </button>\n `\n )}\n </div>\n\n <div class=\"functions-container\">\n <div class=\"functions-header middle-align\">\n <md-icon>functions</md-icon>\n <ox-i18n msgid=\"label.functions\"></ox-i18n>\n </div>\n\n <div class=\"functions-grid\">\n ${this.functions.map(\n func => html`\n <div class=\"function-button\" @click=${() => this._insertFunction(func.template)}>\n <div style=\"font-weight: 500;\">${func.name}</div>\n <div style=\"font-size: 10px; color: var(--md-sys-color-on-surface-variant);\">${func.description}</div>\n ${func.help\n ? html`\n <md-icon\n class=\"help-icon\"\n @click=${(e: Event) => this._showFunctionHelp(e, func)}\n style=\"position: absolute; top: 4px; right: 4px; font-size: 12px;\"\n >\n help\n </md-icon>\n `\n : ''}\n </div>\n `\n )}\n </div>\n </div>\n\n ${this.value\n ? html`\n <div class=\"preview-container\">\n <div class=\"preview-title middle-align\">\n <md-icon>preview</md-icon>\n <ox-i18n msgid=\"label.formula-preview\"></ox-i18n>\n </div>\n <div class=\"preview-formula\">${this.value}</div>\n </div>\n `\n : ''}\n </div>\n `\n }\n\n updated(changes: any) {\n if (changes.has('value')) {\n this._validateFormula()\n }\n }\n\n _onChange(e: Event) {\n if (this._changingNow) {\n return\n }\n\n this._changingNow = true\n this._updateValue()\n this._changingNow = false\n }\n\n _onFormulaInput(e: Event) {\n const textarea = e.target as HTMLTextAreaElement\n this.value = textarea.value\n this._validateFormula()\n this._updateValue()\n }\n\n _insertVariable(variableName: string) {\n const start = this.editor.selectionStart\n const end = this.editor.selectionEnd\n const currentValue = this.editor.value\n\n // 변수명 정규화 (공백을 언더스코어로 변경)\n const normalizedName = this._normalizeVariableName(variableName)\n const insertText = `[${normalizedName}]`\n const newValue = currentValue.substring(0, start) + insertText + currentValue.substring(end)\n this.editor.value = newValue\n this.value = newValue\n\n // 커서 위치 조정\n const newCursorPos = start + insertText.length\n this.editor.setSelectionRange(newCursorPos, newCursorPos)\n this.editor.focus()\n\n this._validateFormula()\n this._updateValue()\n }\n\n _insertOperator(operator: string) {\n const start = this.editor.selectionStart\n const end = this.editor.selectionEnd\n const currentValue = this.editor.value\n\n // 연산자 앞뒤에 공백 추가\n const insertText = ` ${operator} `\n const newValue = currentValue.substring(0, start) + insertText + currentValue.substring(end)\n this.editor.value = newValue\n this.value = newValue\n\n // 커서 위치 조정\n const newCursorPos = start + insertText.length\n this.editor.setSelectionRange(newCursorPos, newCursorPos)\n this.editor.focus()\n\n this._validateFormula()\n this._updateValue()\n }\n\n _insertFunction(template: string) {\n const start = this.editor.selectionStart\n const end = this.editor.selectionEnd\n const currentValue = this.editor.value\n\n // 선택된 텍스트가 있으면 그것을 expression으로 사용\n const selectedText = currentValue.substring(start, end)\n const insertText = template.replace('{expression}', selectedText || 'expression')\n\n const newValue = currentValue.substring(0, start) + insertText + currentValue.substring(end)\n this.editor.value = newValue\n this.value = newValue\n\n // 커서 위치 조정 (expression 부분에 포커스)\n const expressionStart = start + insertText.indexOf('expression')\n const expressionEnd = expressionStart + 'expression'.length\n this.editor.setSelectionRange(expressionStart, expressionEnd)\n this.editor.focus()\n\n this._validateFormula()\n this._updateValue()\n }\n\n _showVariableHelp(e: Event, variable: FormulaVariable) {\n e.stopPropagation()\n\n // 간단한 테스트 버전\n const message = `\n변수: ${variable.name}\n설명: ${variable.description || '없음'}\n타입: ${variable.type || '없음'}\n단위: ${variable.unit || '없음'}\n예시: ${variable.example || '없음'}\n `.trim()\n\n console.log('Variable help:', message)\n\n // dialog 지원 여부 확인\n if (typeof HTMLDialogElement !== 'undefined') {\n const dialog = document.createElement('dialog')\n dialog.className = 'help-dialog'\n\n // HTML 문자열로 직접 작성\n dialog.innerHTML = `\n <div class=\"help-popup\">\n <div class=\"help-title\">${variable.name}</div>\n \n ${\n variable.description\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">설명</div>\n <div class=\"help-content\">${variable.description}</div>\n </div>\n `\n : ''\n }\n \n ${\n variable.type\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">타입</div>\n <div class=\"help-content\">${variable.type}</div>\n </div>\n `\n : ''\n }\n \n ${\n variable.unit\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">단위</div>\n <div class=\"help-content\">${variable.unit}</div>\n </div>\n `\n : ''\n }\n \n ${\n variable.help\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">상세 설명</div>\n <div class=\"help-content\">${variable.help}</div>\n </div>\n `\n : ''\n }\n \n ${\n variable.example\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">사용 예시</div>\n <div class=\"help-example\">${variable.example}</div>\n </div>\n `\n : ''\n }\n </div>\n \n <div class=\"dialog-actions\">\n <button class=\"close-btn\" onclick=\"this.closest('dialog').close()\">닫기</button>\n </div>\n `\n\n document.body.appendChild(dialog)\n dialog.showModal()\n\n // 외부 클릭으로 닫기\n dialog.addEventListener('click', event => {\n const rect = dialog.getBoundingClientRect()\n const isInDialog =\n event.clientX >= rect.left &&\n event.clientX <= rect.right &&\n event.clientY >= rect.top &&\n event.clientY <= rect.bottom\n\n if (!isInDialog) {\n dialog.close()\n }\n })\n\n dialog.addEventListener('close', () => {\n if (document.body.contains(dialog)) {\n document.body.removeChild(dialog)\n }\n })\n\n // ESC 키로 닫기\n const handleEsc = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n dialog.close()\n document.removeEventListener('keydown', handleEsc)\n }\n }\n document.addEventListener('keydown', handleEsc)\n } else {\n // dialog를 지원하지 않는 브라우저에서는 alert 사용\n alert(message)\n }\n }\n\n _showFunctionHelp(e: Event, func: FormulaFunction) {\n e.stopPropagation()\n\n const dialog = document.createElement('dialog')\n dialog.className = 'help-dialog'\n\n // HTML 문자열로 직접 작성\n dialog.innerHTML = `\n <div class=\"help-popup\">\n <div class=\"help-title\">${func.name}</div>\n\n <div class=\"help-section\">\n <div class=\"help-section-title\">설명</div>\n <div class=\"help-content\">${func.description}</div>\n </div>\n\n <div class=\"help-section\">\n <div class=\"help-section-title\">구문</div>\n <div class=\"help-syntax\">${func.syntax}</div>\n </div>\n\n <div class=\"help-section\">\n <div class=\"help-section-title\">매개변수</div>\n <div class=\"help-parameters\">\n ${func.parameters\n .map(\n param => `\n <div class=\"help-parameter\">\n <span class=\"help-parameter-desc\">${param}</span>\n </div>\n `\n )\n .join('')}\n </div>\n </div>\n\n <div class=\"help-section\">\n <div class=\"help-section-title\">반환 타입</div>\n <div class=\"help-content\">${func.returnType}</div>\n </div>\n\n ${\n func.help\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">상세 설명</div>\n <div class=\"help-content\">${func.help}</div>\n </div>\n `\n : ''\n }\n \n ${\n func.examples && func.examples.length > 0\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">사용 예시</div>\n ${func.examples\n .map(\n example => `\n <div class=\"help-example\">${example}</div>\n `\n )\n .join('')}\n </div>\n `\n : ''\n }\n </div>\n \n <div class=\"dialog-actions\">\n <button class=\"close-btn\" onclick=\"this.closest('dialog').close()\">닫기</button>\n </div>\n `\n\n document.body.appendChild(dialog)\n dialog.showModal()\n\n // 외부 클릭으로 닫기\n dialog.addEventListener('click', event => {\n const rect = dialog.getBoundingClientRect()\n const isInDialog =\n event.clientX >= rect.left &&\n event.clientX <= rect.right &&\n event.clientY >= rect.top &&\n event.clientY <= rect.bottom\n\n if (!isInDialog) {\n dialog.close()\n }\n })\n\n dialog.addEventListener('close', () => {\n if (document.body.contains(dialog)) {\n document.body.removeChild(dialog)\n }\n })\n\n // ESC 키로 닫기\n const handleEsc = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n dialog.close()\n document.removeEventListener('keydown', handleEsc)\n }\n }\n document.addEventListener('keydown', handleEsc)\n }\n\n _validateFormula() {\n // 기본적인 수식 유효성 검사\n const formula = this.value.trim()\n\n if (!formula) {\n this.errorMessage = ''\n this.isValid = true\n return\n }\n\n // 괄호 균형 검사\n const openBrackets = (formula.match(/\\(/g) || []).length\n const closeBrackets = (formula.match(/\\)/g) || []).length\n\n if (openBrackets !== closeBrackets) {\n this.errorMessage = '괄호가 균형을 이루지 않습니다.'\n this.isValid = false\n return\n }\n\n // 변수 참조 검증 ([변수명] 형태)\n const variableRefs = formula.match(/\\[([^\\]]+)\\]/g) || []\n for (const ref of variableRefs) {\n const varName = ref.slice(1, -1) // [변수명]에서 변수명 추출\n const normalizedVarName = this._normalizeVariableName(varName)\n const availableVarNames = this.availableVariables.map(v => this._normalizeVariableName(v.name))\n\n if (!availableVarNames.includes(normalizedVarName)) {\n this.errorMessage = `알 수 없는 변수: ${varName}`\n this.isValid = false\n return\n }\n }\n\n // 기본 문법 검사\n const invalidPatterns = [\n /[+\\-*/]{2,}/, // 연속된 연산자\n /[+\\-*/]\\s*$/, // 끝에 연산자\n /^\\s*[+\\-*/]/, // 시작에 연산자\n /\\(\\s*\\)/ // 빈 괄호\n ]\n\n for (const pattern of invalidPatterns) {\n if (pattern.test(formula)) {\n this.errorMessage = '수식 문법이 올바르지 않습니다.'\n this.isValid = false\n return\n }\n }\n\n this.errorMessage = ''\n this.isValid = true\n }\n\n _updateValue() {\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: this.value\n })\n )\n }\n}\n"]}
1
+ {"version":3,"file":"ox-input-formula.js","sourceRoot":"","sources":["../../src/ox-input-formula.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport '@material/web/icon/icon.js'\nimport '@material/web/button/elevated-button.js'\n\nimport '@operato/i18n/ox-i18n.js'\nimport { css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { OxFormField } from '@operato/input'\n\ntype FormulaVariable = {\n name: string\n description?: string\n type?: string\n unit?: string\n help?: string\n example?: string\n}\n\ntype FormulaFunction = {\n name: string\n description: string\n template: string\n syntax: string\n parameters: string[]\n returnType: string\n help?: string\n examples?: string[]\n}\n\n/**\n * Formula editor component for KPI calculations\n *\n * Example:\n *\n * <ox-input-formula\n * .value=${formulaValue}\n * .availableVariables=${variables}\n * .availableFunctions=${functions}\n * .includeDefaultFunctions=${true}\n * ></ox-input-formula>\n */\n@customElement('ox-input-formula')\nexport class KpiFormulaEditor extends OxFormField {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n margin-bottom: var(--spacing-large);\n gap: var(--spacing-medium);\n }\n\n .formula-container {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small);\n }\n\n .formula-input-container {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small);\n }\n\n .formula-textarea {\n min-height: 120px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 4px;\n padding: var(--spacing-medium);\n font-family: 'Courier New', monospace;\n font-size: 14px;\n line-height: 1.4;\n resize: vertical;\n background-color: #f8f9fa;\n font-size: 1.3rem;\n }\n\n .formula-textarea:focus {\n outline: none;\n border-color: var(--md-sys-color-primary);\n background-color: white;\n }\n\n .variables-container {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small);\n }\n\n .variables-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-small);\n font-weight: 500;\n color: var(--md-sys-color-on-surface);\n }\n\n .variables-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: var(--spacing-small);\n max-height: 200px;\n overflow-y: auto;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 4px;\n padding: var(--spacing-small);\n background-color: #f8f9fa;\n }\n\n .variable-item {\n display: flex;\n flex-direction: column;\n padding: var(--spacing-small);\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 4px;\n background-color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n position: relative;\n }\n\n .variable-item:hover {\n border-color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-primary-container);\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n\n .variable-name {\n font-weight: 500;\n color: var(--md-sys-color-primary);\n font-size: 12px;\n font-family: 'Courier New', monospace;\n }\n\n .variable-description {\n font-size: 11px;\n color: var(--md-sys-color-on-surface-variant);\n margin-top: 2px;\n }\n\n .variable-type {\n font-size: 10px;\n color: var(--md-sys-color-outline);\n margin-top: 2px;\n text-transform: uppercase;\n }\n\n .help-icon {\n position: absolute;\n top: 4px;\n right: 4px;\n font-size: 14px;\n color: var(--md-sys-color-outline);\n cursor: pointer;\n padding: 2px;\n border-radius: 2px;\n transition: all 0.2s ease;\n }\n\n .help-icon:hover {\n color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-primary-container);\n }\n\n .operators-container {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small);\n margin-top: var(--spacing-small);\n }\n\n .operator-button {\n padding: var(--spacing-small) var(--spacing-medium);\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 4px;\n background-color: white;\n cursor: pointer;\n font-family: 'Courier New', monospace;\n font-size: 12px;\n transition: all 0.2s ease;\n }\n\n .operator-button:hover {\n background-color: var(--md-sys-color-primary-container);\n border-color: var(--md-sys-color-primary);\n }\n\n .functions-container {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small);\n margin-top: var(--spacing-small);\n }\n\n .functions-header {\n font-weight: 500;\n color: var(--md-sys-color-on-surface);\n }\n\n .functions-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: var(--spacing-small);\n }\n\n .function-button {\n padding: var(--spacing-small);\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 4px;\n background-color: white;\n cursor: pointer;\n font-family: 'Courier New', monospace;\n font-size: 11px;\n text-align: left;\n transition: all 0.2s ease;\n position: relative;\n }\n\n .function-button:hover {\n background-color: var(--md-sys-color-secondary-container);\n border-color: var(--md-sys-color-secondary);\n }\n\n .preview-container {\n margin-top: var(--spacing-medium);\n padding: var(--spacing-medium);\n background-color: #f8f9fa;\n border-radius: 4px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n .preview-title {\n font-weight: 500;\n margin-bottom: var(--spacing-small);\n color: var(--md-sys-color-on-surface);\n }\n\n .preview-formula {\n font-family: 'Courier New', monospace;\n background-color: white;\n padding: var(--spacing-small);\n border-radius: 4px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n white-space: pre-wrap;\n word-break: break-all;\n }\n\n .error-message {\n color: var(--md-sys-color-error);\n font-size: 12px;\n margin-top: var(--spacing-small);\n }\n\n .info-message {\n color: var(--md-sys-color-on-surface-variant);\n font-size: 12px;\n margin-top: var(--spacing-small);\n }\n\n /* 도움말 팝업 스타일 */\n .help-popup {\n max-width: 500px;\n max-height: 400px;\n overflow-y: auto;\n }\n\n .help-title {\n font-size: 16px;\n font-weight: 600;\n margin-bottom: var(--spacing-medium);\n color: var(--md-sys-color-primary);\n }\n\n .help-section {\n margin-bottom: var(--spacing-medium);\n }\n\n .help-section-title {\n font-weight: 500;\n margin-bottom: var(--spacing-small);\n color: var(--md-sys-color-on-surface);\n }\n\n .help-content {\n font-size: 14px;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface);\n }\n\n .help-syntax {\n background-color: #f8f9fa;\n padding: var(--spacing-small);\n border-radius: 4px;\n font-family: 'Courier New', monospace;\n font-size: 12px;\n margin: var(--spacing-small) 0;\n border-left: 3px solid var(--md-sys-color-primary);\n }\n\n .help-example {\n background-color: #f0f8ff;\n padding: var(--spacing-small);\n border-radius: 4px;\n font-family: 'Courier New', monospace;\n font-size: 12px;\n margin: var(--spacing-small) 0;\n border-left: 3px solid var(--md-sys-color-secondary);\n }\n\n .help-parameters {\n margin: var(--spacing-small) 0;\n }\n\n .help-parameter {\n display: flex;\n justify-content: space-between;\n padding: 2px 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n .help-parameter-name {\n font-weight: 500;\n font-family: 'Courier New', monospace;\n }\n\n .help-parameter-desc {\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* Dialog specific styles */\n .help-dialog {\n border: none;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);\n padding: 0;\n max-width: 500px;\n max-height: 80vh;\n overflow-y: auto;\n background-color: white;\n }\n\n .help-dialog::backdrop {\n background-color: rgba(0, 0, 0, 0.5);\n }\n\n .dialog-actions {\n display: flex;\n justify-content: flex-end;\n gap: var(--spacing-small);\n margin-top: var(--spacing-medium);\n padding: var(--spacing-medium);\n border-top: 1px solid var(--md-sys-color-outline);\n }\n\n .close-btn {\n padding: var(--spacing-small) var(--spacing-medium);\n border: 1px solid var(--md-sys-color-outline);\n border-radius: 4px;\n background-color: white;\n cursor: pointer;\n font-size: 14px;\n transition: all 0.2s ease;\n }\n\n .close-btn:hover {\n background-color: var(--md-sys-color-secondary-container);\n border-color: var(--md-sys-color-secondary);\n }\n\n .middle-align {\n display: flex;\n align-items: center;\n }\n `\n ]\n\n @property({ type: String }) value: string = ''\n @property({ type: Array }) availableVariables: FormulaVariable[] = []\n @property({ type: Array }) availableFunctions: FormulaFunction[] = []\n @property({ type: Boolean }) includeDefaultFunctions: boolean = true\n\n @state() private errorMessage: string = ''\n @state() private isValid: boolean = true\n\n @query('.formula-textarea') editor!: HTMLTextAreaElement\n\n private _changingNow: boolean = false\n\n // 기본 함수들 (도움말 정보 포함)\n private readonly _defaultFunctions: FormulaFunction[] = [\n {\n name: 'SUM()',\n description: '합계',\n template: 'SUM({expression})',\n syntax: 'SUM(expression)',\n parameters: ['expression - 합계를 계산할 값 또는 변수'],\n returnType: 'number',\n help: '주어진 값들의 합계를 계산합니다. 숫자 배열이나 변수들을 인자로 받습니다.',\n examples: ['SUM(metric1, metric2, metric3)', 'SUM(production_data)', 'SUM(100, 200, 300)']\n },\n {\n name: 'AVG()',\n description: '평균',\n template: 'AVG({expression})',\n syntax: 'AVG(expression)',\n parameters: ['expression - 평균을 계산할 값 또는 변수'],\n returnType: 'number',\n help: '주어진 값들의 평균을 계산합니다. 숫자 배열이나 변수들을 인자로 받습니다.',\n examples: ['AVG(quality_scores)', 'AVG(metric1, metric2, metric3)', 'AVG(10, 20, 30)']\n },\n {\n name: 'MAX()',\n description: '최대값',\n template: 'MAX({expression})',\n syntax: 'MAX(expression)',\n parameters: ['expression - 최대값을 찾을 값 또는 변수'],\n returnType: 'number',\n help: '주어진 값들 중 최대값을 반환합니다.',\n examples: ['MAX(performance_data)', 'MAX(metric1, metric2, metric3)', 'MAX(100, 200, 300)']\n },\n {\n name: 'MIN()',\n description: '최소값',\n template: 'MIN({expression})',\n syntax: 'MIN(expression)',\n parameters: ['expression - 최소값을 찾을 값 또는 변수'],\n returnType: 'number',\n help: '주어진 값들 중 최소값을 반환합니다.',\n examples: ['MIN(quality_scores)', 'MIN(metric1, metric2, metric3)', 'MIN(10, 20, 30)']\n },\n {\n name: 'COUNT()',\n description: '개수',\n template: 'COUNT({expression})',\n syntax: 'COUNT(expression)',\n parameters: ['expression - 개수를 셀 값 또는 변수'],\n returnType: 'number',\n help: '주어진 값들의 개수를 반환합니다.',\n examples: ['COUNT(active_projects)', 'COUNT(metric1, metric2, metric3)', 'COUNT(1, 2, 3, 4, 5)']\n },\n {\n name: 'ROUND()',\n description: '반올림',\n template: 'ROUND({expression}, 2)',\n syntax: 'ROUND(expression, decimals)',\n parameters: ['expression - 반올림할 값', 'decimals - 소수점 자릿수 (기본값: 0)'],\n returnType: 'number',\n help: '주어진 값을 지정된 소수점 자릿수로 반올림합니다.',\n examples: ['ROUND(3.14159, 2) → 3.14', 'ROUND(metric1, 0)', 'ROUND(AVG(scores), 1)']\n },\n {\n name: 'ABS()',\n description: '절대값',\n template: 'ABS({expression})',\n syntax: 'ABS(expression)',\n parameters: ['expression - 절대값을 구할 값'],\n returnType: 'number',\n help: '주어진 값의 절대값을 반환합니다.',\n examples: ['ABS(-10) → 10', 'ABS(metric1 - target)', 'ABS(deviation)']\n },\n {\n name: 'IF()',\n description: '조건문',\n template: 'IF({condition}, {true_value}, {false_value})',\n syntax: 'IF(condition, true_value, false_value)',\n parameters: [\n 'condition - 조건식 (true/false)',\n 'true_value - 조건이 참일 때 반환할 값',\n 'false_value - 조건이 거짓일 때 반환할 값'\n ],\n returnType: 'any',\n help: '조건에 따라 다른 값을 반환합니다. 조건이 참이면 true_value를, 거짓이면 false_value를 반환합니다.',\n examples: [\n 'IF(score > 80, \"우수\", \"보통\")',\n 'IF(metric1 > target, 100, 0)',\n 'IF(quality_score >= 90, \"A등급\", IF(quality_score >= 80, \"B등급\", \"C등급\"))'\n ]\n },\n {\n name: 'CASE()',\n description: '케이스문',\n template: 'CASE {expression} WHEN {value1} THEN {result1} ELSE {default} END',\n syntax: 'CASE expression WHEN value1 THEN result1 [WHEN value2 THEN result2]... ELSE default END',\n parameters: [\n 'expression - 비교할 값',\n 'value1, value2... - 비교할 값들',\n 'result1, result2... - 해당 값일 때 반환할 결과들',\n 'default - 기본값'\n ],\n returnType: 'any',\n help: '여러 조건에 따라 다른 값을 반환합니다. IF문의 확장된 형태입니다.',\n examples: [\n 'CASE grade WHEN \"A\" THEN 100 WHEN \"B\" THEN 80 WHEN \"C\" THEN 60 ELSE 0 END',\n 'CASE score WHEN 90 THEN \"우수\" WHEN 80 THEN \"양호\" WHEN 70 THEN \"보통\" ELSE \"미흡\" END'\n ]\n }\n ]\n\n // 사용 가능한 함수 목록을 가져오는 getter\n get functions(): FormulaFunction[] {\n let resultFunctions: FormulaFunction[] = []\n\n // 기본 함수 포함 여부 확인\n if (this.includeDefaultFunctions) {\n resultFunctions = [...this._defaultFunctions]\n }\n\n // 사용자 정의 함수들 추가\n if (this.availableFunctions && this.availableFunctions.length > 0) {\n for (const customFunc of this.availableFunctions) {\n const existingIndex = resultFunctions.findIndex(f => f.name === customFunc.name)\n if (existingIndex >= 0) {\n // 기존 함수를 사용자 정의 함수로 교체\n resultFunctions[existingIndex] = customFunc\n } else {\n // 새로운 함수 추가\n resultFunctions.push(customFunc)\n }\n }\n }\n\n return resultFunctions\n }\n\n getValue(): any {\n return this.value\n }\n\n setValue(value: any): void {\n this.value = value\n this._validateFormula()\n }\n\n validate(): boolean {\n this._validateFormula()\n return this.isValid\n }\n\n getErrorMessage(): string {\n return this.errorMessage\n }\n\n focus(): void {\n this.editor.focus()\n }\n\n blur(): void {\n this.editor.blur()\n }\n\n reset(): void {\n this.value = ''\n this.errorMessage = ''\n this.isValid = true\n this._updateValue()\n }\n\n // 기본 연산자들\n private readonly operators = [\n { symbol: '+', description: '더하기' },\n { symbol: '-', description: '빼기' },\n { symbol: '*', description: '곱하기' },\n { symbol: '/', description: '나누기' },\n { symbol: '(', description: '여는 괄호' },\n { symbol: ')', description: '닫는 괄호' },\n { symbol: '=', description: '같음' },\n { symbol: '>', description: '보다 큼' },\n { symbol: '<', description: '보다 작음' },\n { symbol: '>=', description: '보다 크거나 같음' },\n { symbol: '<=', description: '보다 작거나 같음' },\n { symbol: '!=', description: '다름' },\n { symbol: '&&', description: 'AND' },\n { symbol: '||', description: 'OR' }\n ]\n\n firstUpdated() {\n this.addEventListener('change', this._onChange.bind(this))\n }\n\n render() {\n return html`\n <div class=\"formula-container\">\n <div class=\"formula-input-container\">\n <label class=\"variables-header\">\n <ox-i18n msgid=\"label.formula\"></ox-i18n>\n </label>\n\n <textarea\n class=\"formula-textarea\"\n .value=${this.value}\n @input=${this._onFormulaInput.bind(this)}\n placeholder=\"수식을 입력하세요. 예: SUM(metric1) + AVG(metric2) * 0.5\"\n ></textarea>\n\n ${this.errorMessage\n ? html`\n <div class=\"error-message middle-align\">\n <md-icon>error</md-icon>\n ${this.errorMessage}\n </div>\n `\n : ''}\n ${!this.errorMessage && this.value\n ? html`\n <div class=\"info-message middle-align\">\n <md-icon>info</md-icon>\n 수식이 유효합니다.\n </div>\n `\n : ''}\n </div>\n\n <div class=\"variables-container\">\n <div class=\"variables-header middle-align\">\n <md-icon>variables</md-icon>\n <ox-i18n msgid=\"label.available-variables\"></ox-i18n>\n </div>\n\n <div class=\"variables-grid\">\n ${this.availableVariables.map(\n variable => html`\n <div class=\"variable-item\" @click=${() => this._insertVariable(variable.name)}>\n <div class=\"variable-name\">[${variable.name}]</div>\n ${variable.description ? html` <div class=\"variable-description\">${variable.description}</div> ` : ''}\n ${variable.type ? html` <div class=\"variable-type\">${variable.type}</div> ` : ''}\n ${variable.help\n ? html`\n <md-icon class=\"help-icon\" @click=${(e: Event) => this._showVariableHelp(e, variable)}>\n help\n </md-icon>\n `\n : ''}\n </div>\n `\n )}\n </div>\n </div>\n\n <div class=\"operators-container\">\n <div class=\"variables-header middle-align\">\n <md-icon>calculate</md-icon>\n <ox-i18n msgid=\"label.operators\"></ox-i18n>\n </div>\n\n ${this.operators.map(\n op => html`\n <button class=\"operator-button\" @click=${() => this._insertOperator(op.symbol)} title=\"${op.description}\">\n ${op.symbol}\n </button>\n `\n )}\n </div>\n\n ${this.functions.length > 0\n ? html`\n <div class=\"functions-container\">\n <div class=\"functions-header middle-align\">\n <md-icon>functions</md-icon>\n <ox-i18n msgid=\"label.functions\"></ox-i18n>\n </div>\n\n <div class=\"functions-grid\">\n ${this.functions.map(\n func => html`\n <div class=\"function-button\" @click=${() => this._insertFunction(func.template)}>\n <div style=\"font-weight: 500;\">${func.name}</div>\n <div style=\"font-size: 10px; color: var(--md-sys-color-on-surface-variant);\">\n ${func.description}\n </div>\n ${func.help\n ? html`\n <md-icon\n class=\"help-icon\"\n @click=${(e: Event) => this._showFunctionHelp(e, func)}\n style=\"position: absolute; top: 4px; right: 4px; font-size: 12px;\"\n >\n help\n </md-icon>\n `\n : ''}\n </div>\n `\n )}\n </div>\n </div>\n `\n : ''}\n ${this.value\n ? html`\n <div class=\"preview-container\">\n <div class=\"preview-title middle-align\">\n <md-icon>preview</md-icon>\n <ox-i18n msgid=\"label.formula-preview\"></ox-i18n>\n </div>\n <div class=\"preview-formula\">${this.value}</div>\n </div>\n `\n : ''}\n </div>\n `\n }\n\n updated(changes: any) {\n if (changes.has('value')) {\n this._validateFormula()\n }\n }\n\n _onChange(e: Event) {\n if (this._changingNow) {\n return\n }\n\n this._changingNow = true\n this._updateValue()\n this._changingNow = false\n }\n\n _onFormulaInput(e: Event) {\n const textarea = e.target as HTMLTextAreaElement\n this.value = textarea.value\n this._validateFormula()\n this._updateValue()\n }\n\n _insertVariable(variableName: string) {\n const start = this.editor.selectionStart\n const end = this.editor.selectionEnd\n const currentValue = this.editor.value\n\n // 변수명을 [변수명] 형태로만 감싸서 삽입\n const insertText = `[${variableName}]`\n const newValue = currentValue.substring(0, start) + insertText + currentValue.substring(end)\n this.editor.value = newValue\n this.value = newValue\n\n // 커서 위치 조정\n const newCursorPos = start + insertText.length\n this.editor.setSelectionRange(newCursorPos, newCursorPos)\n this.editor.focus()\n\n this._validateFormula()\n this._updateValue()\n }\n\n _insertOperator(operator: string) {\n const start = this.editor.selectionStart\n const end = this.editor.selectionEnd\n const currentValue = this.editor.value\n\n // 연산자 앞뒤에 공백 추가\n const insertText = ` ${operator} `\n const newValue = currentValue.substring(0, start) + insertText + currentValue.substring(end)\n this.editor.value = newValue\n this.value = newValue\n\n // 커서 위치 조정\n const newCursorPos = start + insertText.length\n this.editor.setSelectionRange(newCursorPos, newCursorPos)\n this.editor.focus()\n\n this._validateFormula()\n this._updateValue()\n }\n\n _insertFunction(template: string) {\n const start = this.editor.selectionStart\n const end = this.editor.selectionEnd\n const currentValue = this.editor.value\n\n // 선택된 텍스트가 있으면 그것을 expression으로 사용\n const selectedText = currentValue.substring(start, end)\n const insertText = template.replace('{expression}', selectedText || 'expression')\n\n const newValue = currentValue.substring(0, start) + insertText + currentValue.substring(end)\n this.editor.value = newValue\n this.value = newValue\n\n // 커서 위치 조정 (expression 부분에 포커스)\n const expressionStart = start + insertText.indexOf('expression')\n const expressionEnd = expressionStart + 'expression'.length\n this.editor.setSelectionRange(expressionStart, expressionEnd)\n this.editor.focus()\n\n this._validateFormula()\n this._updateValue()\n }\n\n _showVariableHelp(e: Event, variable: FormulaVariable) {\n e.stopPropagation()\n\n // 간단한 테스트 버전\n const message = `\n변수: ${variable.name}\n설명: ${variable.description || '없음'}\n타입: ${variable.type || '없음'}\n단위: ${variable.unit || '없음'}\n예시: ${variable.example || '없음'}\n `.trim()\n\n console.log('Variable help:', message)\n\n // dialog 지원 여부 확인\n if (typeof HTMLDialogElement !== 'undefined') {\n const dialog = document.createElement('dialog')\n dialog.className = 'help-dialog'\n\n // HTML 문자열로 직접 작성\n dialog.innerHTML = `\n <div class=\"help-popup\">\n <div class=\"help-title\">${variable.name}</div>\n \n ${\n variable.description\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">설명</div>\n <div class=\"help-content\">${variable.description}</div>\n </div>\n `\n : ''\n }\n \n ${\n variable.type\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">타입</div>\n <div class=\"help-content\">${variable.type}</div>\n </div>\n `\n : ''\n }\n \n ${\n variable.unit\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">단위</div>\n <div class=\"help-content\">${variable.unit}</div>\n </div>\n `\n : ''\n }\n \n ${\n variable.help\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">상세 설명</div>\n <div class=\"help-content\">${variable.help}</div>\n </div>\n `\n : ''\n }\n \n ${\n variable.example\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">사용 예시</div>\n <div class=\"help-example\">${variable.example}</div>\n </div>\n `\n : ''\n }\n </div>\n \n <div class=\"dialog-actions\">\n <button class=\"close-btn\" onclick=\"this.closest('dialog').close()\">닫기</button>\n </div>\n `\n\n document.body.appendChild(dialog)\n dialog.showModal()\n\n // 외부 클릭으로 닫기\n dialog.addEventListener('click', event => {\n const rect = dialog.getBoundingClientRect()\n const isInDialog =\n event.clientX >= rect.left &&\n event.clientX <= rect.right &&\n event.clientY >= rect.top &&\n event.clientY <= rect.bottom\n\n if (!isInDialog) {\n dialog.close()\n }\n })\n\n dialog.addEventListener('close', () => {\n if (document.body.contains(dialog)) {\n document.body.removeChild(dialog)\n }\n })\n\n // ESC 키로 닫기\n const handleEsc = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n dialog.close()\n document.removeEventListener('keydown', handleEsc)\n }\n }\n document.addEventListener('keydown', handleEsc)\n } else {\n // dialog를 지원하지 않는 브라우저에서는 alert 사용\n alert(message)\n }\n }\n\n _showFunctionHelp(e: Event, func: FormulaFunction) {\n e.stopPropagation()\n\n const dialog = document.createElement('dialog')\n dialog.className = 'help-dialog'\n\n // HTML 문자열로 직접 작성\n dialog.innerHTML = `\n <div class=\"help-popup\">\n <div class=\"help-title\">${func.name}</div>\n\n <div class=\"help-section\">\n <div class=\"help-section-title\">설명</div>\n <div class=\"help-content\">${func.description}</div>\n </div>\n\n <div class=\"help-section\">\n <div class=\"help-section-title\">구문</div>\n <div class=\"help-syntax\">${func.syntax}</div>\n </div>\n\n <div class=\"help-section\">\n <div class=\"help-section-title\">매개변수</div>\n <div class=\"help-parameters\">\n ${func.parameters\n .map(\n param => `\n <div class=\"help-parameter\">\n <span class=\"help-parameter-desc\">${param}</span>\n </div>\n `\n )\n .join('')}\n </div>\n </div>\n\n <div class=\"help-section\">\n <div class=\"help-section-title\">반환 타입</div>\n <div class=\"help-content\">${func.returnType}</div>\n </div>\n\n ${\n func.help\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">상세 설명</div>\n <div class=\"help-content\">${func.help}</div>\n </div>\n `\n : ''\n }\n \n ${\n func.examples && func.examples.length > 0\n ? `\n <div class=\"help-section\">\n <div class=\"help-section-title\">사용 예시</div>\n ${func.examples\n .map(\n example => `\n <div class=\"help-example\">${example}</div>\n `\n )\n .join('')}\n </div>\n `\n : ''\n }\n </div>\n \n <div class=\"dialog-actions\">\n <button class=\"close-btn\" onclick=\"this.closest('dialog').close()\">닫기</button>\n </div>\n `\n\n document.body.appendChild(dialog)\n dialog.showModal()\n\n // 외부 클릭으로 닫기\n dialog.addEventListener('click', event => {\n const rect = dialog.getBoundingClientRect()\n const isInDialog =\n event.clientX >= rect.left &&\n event.clientX <= rect.right &&\n event.clientY >= rect.top &&\n event.clientY <= rect.bottom\n\n if (!isInDialog) {\n dialog.close()\n }\n })\n\n dialog.addEventListener('close', () => {\n if (document.body.contains(dialog)) {\n document.body.removeChild(dialog)\n }\n })\n\n // ESC 키로 닫기\n const handleEsc = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n dialog.close()\n document.removeEventListener('keydown', handleEsc)\n }\n }\n document.addEventListener('keydown', handleEsc)\n }\n\n _validateFormula() {\n // 기본적인 수식 유효성 검사\n const formula = this.value.trim()\n\n if (!formula) {\n this.errorMessage = ''\n this.isValid = true\n return\n }\n\n // 괄호 균형 검사\n const openBrackets = (formula.match(/\\(/g) || []).length\n const closeBrackets = (formula.match(/\\)/g) || []).length\n\n if (openBrackets !== closeBrackets) {\n this.errorMessage = '괄호가 균형을 이루지 않습니다.'\n this.isValid = false\n return\n }\n\n // 변수 참조 검증 ([변수명] 형태)\n const variableRefs = formula.match(/\\[([^\\]]+)\\]/g) || []\n for (const ref of variableRefs) {\n const varName = ref.slice(1, -1) // [변수명]에서 변수명 추출\n const availableVarNames = this.availableVariables.map(v => v.name)\n if (!availableVarNames.includes(varName)) {\n this.errorMessage = `알 수 없는 변수: ${varName}`\n this.isValid = false\n return\n }\n }\n\n // 기본 문법 검사\n const invalidPatterns = [\n /[+\\-*/]{2,}/, // 연속된 연산자\n /[+\\-*/]\\s*$/, // 끝에 연산자\n /^\\s*[+\\-*/]/, // 시작에 연산자\n /\\(\\s*\\)/ // 빈 괄호\n ]\n\n for (const pattern of invalidPatterns) {\n if (pattern.test(formula)) {\n this.errorMessage = '수식 문법이 올바르지 않습니다.'\n this.isValid = false\n return\n }\n }\n\n this.errorMessage = ''\n this.isValid = true\n }\n\n _updateValue() {\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: this.value\n })\n )\n }\n}\n"]}