@masterteam/components 0.0.74 → 0.0.76
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/assets/common.css +1 -1
- package/fesm2022/masterteam-components-color-picker-field.mjs +2 -2
- package/fesm2022/masterteam-components-color-picker-field.mjs.map +1 -1
- package/fesm2022/masterteam-components-date-field.mjs +2 -2
- package/fesm2022/masterteam-components-date-field.mjs.map +1 -1
- package/fesm2022/masterteam-components-dynamic-drawer.mjs +0 -2
- package/fesm2022/masterteam-components-dynamic-drawer.mjs.map +1 -1
- package/fesm2022/masterteam-components-formula.mjs +1126 -0
- package/fesm2022/masterteam-components-formula.mjs.map +1 -0
- package/fesm2022/masterteam-components-module-summary-card.mjs +1 -1
- package/fesm2022/masterteam-components-module-summary-card.mjs.map +1 -1
- package/fesm2022/masterteam-components-page-header.mjs +2 -2
- package/fesm2022/masterteam-components-page-header.mjs.map +1 -1
- package/fesm2022/masterteam-components-page.mjs +2 -2
- package/fesm2022/masterteam-components-page.mjs.map +1 -1
- package/fesm2022/masterteam-components-pick-list-field.mjs +1 -1
- package/fesm2022/masterteam-components-pick-list-field.mjs.map +1 -1
- package/fesm2022/masterteam-components-table.mjs +4 -5
- package/fesm2022/masterteam-components-table.mjs.map +1 -1
- package/fesm2022/masterteam-components-toggle-field.mjs +21 -5
- package/fesm2022/masterteam-components-toggle-field.mjs.map +1 -1
- package/fesm2022/masterteam-components-topbar.mjs +2 -2
- package/fesm2022/masterteam-components-topbar.mjs.map +1 -1
- package/fesm2022/masterteam-components-tree.mjs +1 -1
- package/fesm2022/masterteam-components-tree.mjs.map +1 -1
- package/fesm2022/masterteam-components.mjs +7 -2
- package/fesm2022/masterteam-components.mjs.map +1 -1
- package/package.json +6 -1
- package/types/masterteam-components-formula.d.ts +500 -0
- package/types/masterteam-components-toggle-field.d.ts +8 -1
- package/types/masterteam-components.d.ts +16 -9
|
@@ -0,0 +1,1126 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, signal, computed, effect, forwardRef, Component } from '@angular/core';
|
|
3
|
+
import { CommonModule } from '@angular/common';
|
|
4
|
+
import * as i1$1 from '@angular/forms';
|
|
5
|
+
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
6
|
+
import * as i1 from '@angular/cdk/drag-drop';
|
|
7
|
+
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Flat Formula Token Model
|
|
11
|
+
*
|
|
12
|
+
* The formula is represented as a FLAT array of tokens.
|
|
13
|
+
* This approach provides:
|
|
14
|
+
* - Better performance (no deep component nesting)
|
|
15
|
+
* - Solid drag-drop behavior (functions move as complete units)
|
|
16
|
+
* - Simple serialization (just join token values)
|
|
17
|
+
*
|
|
18
|
+
* STRUCTURE RULES:
|
|
19
|
+
* - fn-open: "IF(" - function name + opening paren as ONE token
|
|
20
|
+
* - fn-close: ")" - closing paren, belongs to a function
|
|
21
|
+
* - fn-separator: "," - argument separator, belongs to a function
|
|
22
|
+
* - Dragging any structural token moves the ENTIRE function
|
|
23
|
+
* - Content tokens (property, operator, literal) can move individually
|
|
24
|
+
*/
|
|
25
|
+
// ============ UTILITY FUNCTIONS ============
|
|
26
|
+
/** Generate unique token ID */
|
|
27
|
+
function generateTokenId() {
|
|
28
|
+
return `tok_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
29
|
+
}
|
|
30
|
+
/** Generate unique function ID */
|
|
31
|
+
function generateFunctionId() {
|
|
32
|
+
return `fn_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
33
|
+
}
|
|
34
|
+
/** Parse function signature like "SUM(values)" or "IF(cond, a, b)" */
|
|
35
|
+
function parseSignature(signature) {
|
|
36
|
+
const match = signature.match(/^(\w+)\((.*)\)$/);
|
|
37
|
+
if (!match)
|
|
38
|
+
return { name: signature, args: [] };
|
|
39
|
+
const name = match[1];
|
|
40
|
+
const argsStr = match[2].trim();
|
|
41
|
+
if (!argsStr)
|
|
42
|
+
return { name, args: [] };
|
|
43
|
+
const args = [];
|
|
44
|
+
let current = '';
|
|
45
|
+
let depth = 0;
|
|
46
|
+
for (const char of argsStr) {
|
|
47
|
+
if (char === '(')
|
|
48
|
+
depth++;
|
|
49
|
+
else if (char === ')')
|
|
50
|
+
depth--;
|
|
51
|
+
else if (char === ',' && depth === 0) {
|
|
52
|
+
args.push(current.trim());
|
|
53
|
+
current = '';
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
current += char;
|
|
57
|
+
}
|
|
58
|
+
if (current.trim())
|
|
59
|
+
args.push(current.trim());
|
|
60
|
+
return { name, args };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create a function's tokens from signature
|
|
64
|
+
* e.g., "IF(condition, trueVal, falseVal)" creates:
|
|
65
|
+
* - fn-open: "IF("
|
|
66
|
+
* - fn-separator: "," (x2)
|
|
67
|
+
* - fn-close: ")"
|
|
68
|
+
*/
|
|
69
|
+
function createFunctionTokens(signature, depth = 0) {
|
|
70
|
+
const parsed = parseSignature(signature);
|
|
71
|
+
const functionId = generateFunctionId();
|
|
72
|
+
const tokens = [];
|
|
73
|
+
// Opening token: "NAME("
|
|
74
|
+
tokens.push({
|
|
75
|
+
id: generateTokenId(),
|
|
76
|
+
type: 'fn-open',
|
|
77
|
+
value: `${parsed.name}(`,
|
|
78
|
+
functionId,
|
|
79
|
+
functionName: parsed.name,
|
|
80
|
+
depth,
|
|
81
|
+
dragBehavior: 'function',
|
|
82
|
+
});
|
|
83
|
+
// Separators for arguments (one less than arg count)
|
|
84
|
+
for (let i = 0; i < parsed.args.length - 1; i++) {
|
|
85
|
+
tokens.push({
|
|
86
|
+
id: generateTokenId(),
|
|
87
|
+
type: 'fn-separator',
|
|
88
|
+
value: ',',
|
|
89
|
+
functionId,
|
|
90
|
+
depth,
|
|
91
|
+
argIndex: i, // Separator AFTER this argument
|
|
92
|
+
dragBehavior: 'function',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Closing token: ")"
|
|
96
|
+
tokens.push({
|
|
97
|
+
id: generateTokenId(),
|
|
98
|
+
type: 'fn-close',
|
|
99
|
+
value: ')',
|
|
100
|
+
functionId,
|
|
101
|
+
depth,
|
|
102
|
+
dragBehavior: 'function',
|
|
103
|
+
});
|
|
104
|
+
return tokens;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a property token
|
|
108
|
+
*/
|
|
109
|
+
function createPropertyToken(propertyKey, propertyType = 'current', depth = 0, functionId, argIndex) {
|
|
110
|
+
const prefix = propertyType === 'current'
|
|
111
|
+
? '@'
|
|
112
|
+
: propertyType === 'children'
|
|
113
|
+
? '*.'
|
|
114
|
+
: '$.';
|
|
115
|
+
return {
|
|
116
|
+
id: generateTokenId(),
|
|
117
|
+
type: 'property',
|
|
118
|
+
value: `${prefix}${propertyKey}`,
|
|
119
|
+
propertyType,
|
|
120
|
+
depth,
|
|
121
|
+
functionId,
|
|
122
|
+
argIndex,
|
|
123
|
+
dragBehavior: 'single',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create an operator token
|
|
128
|
+
*/
|
|
129
|
+
function createOperatorToken(symbol, depth = 0, functionId, argIndex) {
|
|
130
|
+
return {
|
|
131
|
+
id: generateTokenId(),
|
|
132
|
+
type: 'operator',
|
|
133
|
+
value: symbol,
|
|
134
|
+
depth,
|
|
135
|
+
functionId,
|
|
136
|
+
argIndex,
|
|
137
|
+
dragBehavior: 'single',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create a literal token
|
|
142
|
+
*/
|
|
143
|
+
function createLiteralToken(value, depth = 0, functionId, argIndex) {
|
|
144
|
+
let displayValue = String(value);
|
|
145
|
+
let literalType = 'string';
|
|
146
|
+
if (typeof value === 'number') {
|
|
147
|
+
literalType = 'number';
|
|
148
|
+
}
|
|
149
|
+
else if (typeof value === 'boolean') {
|
|
150
|
+
literalType = 'boolean';
|
|
151
|
+
}
|
|
152
|
+
else if (typeof value === 'string' && !value.startsWith('"')) {
|
|
153
|
+
displayValue = `"${value}"`;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
id: generateTokenId(),
|
|
157
|
+
type: 'literal',
|
|
158
|
+
value: displayValue,
|
|
159
|
+
literalType,
|
|
160
|
+
depth,
|
|
161
|
+
functionId,
|
|
162
|
+
argIndex,
|
|
163
|
+
dragBehavior: 'single',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// ============ TOKEN ARRAY OPERATIONS ============
|
|
167
|
+
/**
|
|
168
|
+
* Find the complete range of a function in the token array
|
|
169
|
+
* This includes ALL tokens between fn-open and fn-close (including nested content)
|
|
170
|
+
* Returns [startIndex, endIndex] inclusive
|
|
171
|
+
*/
|
|
172
|
+
function findFunctionRange(tokens, functionId) {
|
|
173
|
+
// Find the fn-open token with this functionId
|
|
174
|
+
const openIndex = tokens.findIndex((t) => t.functionId === functionId && t.type === 'fn-open');
|
|
175
|
+
if (openIndex === -1)
|
|
176
|
+
return null;
|
|
177
|
+
// Now find the matching fn-close by counting depth
|
|
178
|
+
// Start from fn-open and find matching close paren
|
|
179
|
+
let depth = 1;
|
|
180
|
+
for (let i = openIndex + 1; i < tokens.length; i++) {
|
|
181
|
+
const token = tokens[i];
|
|
182
|
+
if (token.type === 'fn-open') {
|
|
183
|
+
depth++;
|
|
184
|
+
}
|
|
185
|
+
else if (token.type === 'fn-close') {
|
|
186
|
+
depth--;
|
|
187
|
+
if (depth === 0) {
|
|
188
|
+
// Found the matching close
|
|
189
|
+
return [openIndex, i];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Fallback: find fn-close with same functionId
|
|
194
|
+
const closeIndex = tokens.findIndex((t) => t.functionId === functionId && t.type === 'fn-close');
|
|
195
|
+
if (closeIndex === -1)
|
|
196
|
+
return null;
|
|
197
|
+
return [openIndex, closeIndex];
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get all tokens belonging to a function (including nested content)
|
|
201
|
+
*/
|
|
202
|
+
function getFunctionTokens(tokens, functionId) {
|
|
203
|
+
const range = findFunctionRange(tokens, functionId);
|
|
204
|
+
if (!range)
|
|
205
|
+
return [];
|
|
206
|
+
return tokens.slice(range[0], range[1] + 1);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Validate if a drop position is allowed
|
|
210
|
+
*/
|
|
211
|
+
function isValidDropPosition(tokens, dropIndex, draggedTokens) {
|
|
212
|
+
// Can always drop at start or end
|
|
213
|
+
if (dropIndex === 0 || dropIndex === tokens.length)
|
|
214
|
+
return true;
|
|
215
|
+
const _prevToken = tokens[dropIndex - 1];
|
|
216
|
+
const _nextToken = tokens[dropIndex];
|
|
217
|
+
// If dragging a function, check if it would create invalid nesting
|
|
218
|
+
const isDraggingFunction = draggedTokens.some((t) => t.type === 'fn-open' || t.type === 'fn-close');
|
|
219
|
+
if (isDraggingFunction) {
|
|
220
|
+
// Don't allow dropping a function that would break structure
|
|
221
|
+
// For now, allow all - we can refine later
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Serialize tokens to formula string
|
|
227
|
+
*/
|
|
228
|
+
function serializeTokens(tokens) {
|
|
229
|
+
const parts = [];
|
|
230
|
+
let lastType = null;
|
|
231
|
+
for (const token of tokens) {
|
|
232
|
+
// Add space between tokens (except after fn-open, before fn-close, after separator)
|
|
233
|
+
if (lastType &&
|
|
234
|
+
lastType !== 'fn-open' &&
|
|
235
|
+
token.type !== 'fn-close' &&
|
|
236
|
+
token.type !== 'fn-separator' &&
|
|
237
|
+
lastType !== 'fn-separator') {
|
|
238
|
+
parts.push(' ');
|
|
239
|
+
}
|
|
240
|
+
parts.push(token.value);
|
|
241
|
+
lastType = token.type;
|
|
242
|
+
}
|
|
243
|
+
return parts.join('');
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Clone a token with new ID
|
|
247
|
+
*/
|
|
248
|
+
function cloneToken(token) {
|
|
249
|
+
return {
|
|
250
|
+
...token,
|
|
251
|
+
id: generateTokenId(),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Clone multiple tokens with new IDs and new function IDs
|
|
256
|
+
*/
|
|
257
|
+
function cloneTokens(tokens) {
|
|
258
|
+
// Map old function IDs to new ones
|
|
259
|
+
const functionIdMap = new Map();
|
|
260
|
+
return tokens.map((token) => {
|
|
261
|
+
const newToken = cloneToken(token);
|
|
262
|
+
// Remap function IDs
|
|
263
|
+
if (token.functionId) {
|
|
264
|
+
if (!functionIdMap.has(token.functionId)) {
|
|
265
|
+
functionIdMap.set(token.functionId, generateFunctionId());
|
|
266
|
+
}
|
|
267
|
+
newToken.functionId = functionIdMap.get(token.functionId);
|
|
268
|
+
}
|
|
269
|
+
return newToken;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Update depth for all tokens (after move)
|
|
274
|
+
*/
|
|
275
|
+
function recalculateDepths(tokens) {
|
|
276
|
+
let currentDepth = 0;
|
|
277
|
+
const result = [];
|
|
278
|
+
for (const token of tokens) {
|
|
279
|
+
if (token.type === 'fn-open') {
|
|
280
|
+
result.push({ ...token, depth: currentDepth });
|
|
281
|
+
currentDepth++;
|
|
282
|
+
}
|
|
283
|
+
else if (token.type === 'fn-close') {
|
|
284
|
+
currentDepth--;
|
|
285
|
+
result.push({ ...token, depth: currentDepth });
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
result.push({ ...token, depth: currentDepth });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Find which argument index a position belongs to within a function
|
|
295
|
+
*/
|
|
296
|
+
function getArgumentIndexAtPosition(tokens, functionId, position) {
|
|
297
|
+
const range = findFunctionRange(tokens, functionId);
|
|
298
|
+
if (!range)
|
|
299
|
+
return 0;
|
|
300
|
+
let argIndex = 0;
|
|
301
|
+
for (let i = range[0] + 1; i < position && i <= range[1]; i++) {
|
|
302
|
+
if (tokens[i].type === 'fn-separator' &&
|
|
303
|
+
tokens[i].functionId === functionId) {
|
|
304
|
+
argIndex++;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return argIndex;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Smart Formula Block - Types and Factory Functions
|
|
312
|
+
* Defines the block structure and creation helpers
|
|
313
|
+
*/
|
|
314
|
+
/** Generate unique block ID */
|
|
315
|
+
function generateSmartBlockId() {
|
|
316
|
+
return `sb_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
317
|
+
}
|
|
318
|
+
/** Create a function block from signature */
|
|
319
|
+
function createFunctionBlock(signature, name) {
|
|
320
|
+
const parsed = parseSignature(signature);
|
|
321
|
+
return {
|
|
322
|
+
id: generateSmartBlockId(),
|
|
323
|
+
type: 'function',
|
|
324
|
+
value: parsed.name,
|
|
325
|
+
functionName: name || parsed.name,
|
|
326
|
+
signature,
|
|
327
|
+
arguments: parsed.args.map((arg) => ({
|
|
328
|
+
id: generateSmartBlockId(),
|
|
329
|
+
name: arg,
|
|
330
|
+
blocks: [],
|
|
331
|
+
placeholder: arg,
|
|
332
|
+
})),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/** Create a property block */
|
|
336
|
+
function createPropertyBlock(propertyKey, propertyType = 'current') {
|
|
337
|
+
const prefix = propertyType === 'current'
|
|
338
|
+
? '@'
|
|
339
|
+
: propertyType === 'children'
|
|
340
|
+
? '*.'
|
|
341
|
+
: '$.';
|
|
342
|
+
return {
|
|
343
|
+
id: generateSmartBlockId(),
|
|
344
|
+
type: 'property',
|
|
345
|
+
value: `${prefix}${propertyKey}`,
|
|
346
|
+
propertyType,
|
|
347
|
+
propertyKey,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/** Create an operator block */
|
|
351
|
+
function createOperatorBlock(symbol) {
|
|
352
|
+
return {
|
|
353
|
+
id: generateSmartBlockId(),
|
|
354
|
+
type: 'operator',
|
|
355
|
+
value: symbol,
|
|
356
|
+
operatorSymbol: symbol,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/** Create a literal block */
|
|
360
|
+
function createLiteralBlock(value) {
|
|
361
|
+
return {
|
|
362
|
+
id: generateSmartBlockId(),
|
|
363
|
+
type: 'literal',
|
|
364
|
+
value: String(value),
|
|
365
|
+
literalValue: value,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/** Deep clone a block with new IDs */
|
|
369
|
+
function cloneBlock(block) {
|
|
370
|
+
const newBlock = { ...block, id: generateSmartBlockId() };
|
|
371
|
+
if (block.arguments) {
|
|
372
|
+
newBlock.arguments = block.arguments.map((arg) => ({
|
|
373
|
+
...arg,
|
|
374
|
+
id: generateSmartBlockId(),
|
|
375
|
+
blocks: arg.blocks.map((b) => cloneBlock(b)),
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
378
|
+
return newBlock;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Validation Result Models
|
|
383
|
+
* API response types for formula validation
|
|
384
|
+
*/
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Formula Block Model - Types for formula functions and operators
|
|
388
|
+
*/
|
|
389
|
+
|
|
390
|
+
// Models index
|
|
391
|
+
|
|
392
|
+
class FormulaEditor {
|
|
393
|
+
// ============ INPUTS ============
|
|
394
|
+
placeholder = input('Click toolbar items to build formula...', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
395
|
+
initialTokens = input([], ...(ngDevMode ? [{ debugName: "initialTokens" }] : []));
|
|
396
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
397
|
+
// ============ OUTPUTS ============
|
|
398
|
+
formulaChange = output();
|
|
399
|
+
tokensChange = output();
|
|
400
|
+
onBlur = output();
|
|
401
|
+
onFocus = output();
|
|
402
|
+
// ============ STATE ============
|
|
403
|
+
tokens = signal([], ...(ngDevMode ? [{ debugName: "tokens" }] : []));
|
|
404
|
+
/** Index range being dragged [start, end] inclusive */
|
|
405
|
+
draggedRange = null;
|
|
406
|
+
/** Is currently dragging */
|
|
407
|
+
isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
|
|
408
|
+
/** Hovered function ID for highlighting */
|
|
409
|
+
hoveredFunctionId = signal(null, ...(ngDevMode ? [{ debugName: "hoveredFunctionId" }] : []));
|
|
410
|
+
/** Is focused */
|
|
411
|
+
isFocused = signal(false, ...(ngDevMode ? [{ debugName: "isFocused" }] : []));
|
|
412
|
+
// ============ CVA ============
|
|
413
|
+
onChange = () => { };
|
|
414
|
+
onTouched = () => { };
|
|
415
|
+
// ============ COMPUTED ============
|
|
416
|
+
/** Line numbers based on token count */
|
|
417
|
+
lines = computed(() => {
|
|
418
|
+
const count = Math.max(1, Math.ceil(this.tokens().length / 8));
|
|
419
|
+
return Array(count).fill(0);
|
|
420
|
+
}, ...(ngDevMode ? [{ debugName: "lines" }] : []));
|
|
421
|
+
/** Serialized formula string */
|
|
422
|
+
formulaString = computed(() => serializeTokens(this.tokens()), ...(ngDevMode ? [{ debugName: "formulaString" }] : []));
|
|
423
|
+
constructor() {
|
|
424
|
+
// Initialize from input
|
|
425
|
+
effect(() => {
|
|
426
|
+
const initial = this.initialTokens();
|
|
427
|
+
if (initial.length > 0) {
|
|
428
|
+
this.tokens.set(initial);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
// ============ CONTROL VALUE ACCESSOR ============
|
|
433
|
+
writeValue(value) {
|
|
434
|
+
if (value === null || value === undefined) {
|
|
435
|
+
this.tokens.set([]);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
// Value should be tokens array
|
|
439
|
+
if (Array.isArray(value)) {
|
|
440
|
+
this.tokens.set(value);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
registerOnChange(fn) {
|
|
444
|
+
this.onChange = fn;
|
|
445
|
+
}
|
|
446
|
+
registerOnTouched(fn) {
|
|
447
|
+
this.onTouched = fn;
|
|
448
|
+
}
|
|
449
|
+
// ============ PUBLIC API ============
|
|
450
|
+
/**
|
|
451
|
+
* Add a block from toolbar (converts SmartBlock to tokens)
|
|
452
|
+
*/
|
|
453
|
+
addBlock(block) {
|
|
454
|
+
if (this.disabled())
|
|
455
|
+
return;
|
|
456
|
+
const newTokens = this.smartBlockToTokens(block);
|
|
457
|
+
this.tokens.update((tokens) => [...tokens, ...cloneTokens(newTokens)]);
|
|
458
|
+
this.emit();
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Add tokens directly
|
|
462
|
+
*/
|
|
463
|
+
addTokens(newTokens) {
|
|
464
|
+
if (this.disabled())
|
|
465
|
+
return;
|
|
466
|
+
this.tokens.update((tokens) => [...tokens, ...cloneTokens(newTokens)]);
|
|
467
|
+
this.emit();
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Clear all tokens
|
|
471
|
+
*/
|
|
472
|
+
clear() {
|
|
473
|
+
if (this.disabled())
|
|
474
|
+
return;
|
|
475
|
+
this.tokens.set([]);
|
|
476
|
+
this.emit();
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get current formula string
|
|
480
|
+
*/
|
|
481
|
+
serialize() {
|
|
482
|
+
return this.formulaString();
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Get current tokens
|
|
486
|
+
*/
|
|
487
|
+
getTokens() {
|
|
488
|
+
return this.tokens();
|
|
489
|
+
}
|
|
490
|
+
// ============ FOCUS HANDLERS ============
|
|
491
|
+
handleFocus() {
|
|
492
|
+
this.isFocused.set(true);
|
|
493
|
+
this.onFocus.emit();
|
|
494
|
+
}
|
|
495
|
+
handleBlur() {
|
|
496
|
+
this.isFocused.set(false);
|
|
497
|
+
this.onTouched();
|
|
498
|
+
this.onBlur.emit();
|
|
499
|
+
}
|
|
500
|
+
// ============ DRAG HANDLERS ============
|
|
501
|
+
/**
|
|
502
|
+
* Handle drag start - determine what tokens to drag
|
|
503
|
+
*/
|
|
504
|
+
onDragStart(event, token, index) {
|
|
505
|
+
if (this.disabled())
|
|
506
|
+
return;
|
|
507
|
+
this.isDragging.set(true);
|
|
508
|
+
if (token.dragBehavior === 'function' && token.functionId) {
|
|
509
|
+
// Drag entire function (all tokens from fn-open to fn-close)
|
|
510
|
+
const range = findFunctionRange(this.tokens(), token.functionId);
|
|
511
|
+
if (range) {
|
|
512
|
+
this.draggedRange = range;
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
// Fallback to single token if range not found
|
|
516
|
+
this.draggedRange = [index, index];
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
// Drag single token
|
|
521
|
+
this.draggedRange = [index, index];
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Handle drop - reorder tokens in the formula
|
|
526
|
+
*/
|
|
527
|
+
onDrop(event) {
|
|
528
|
+
if (this.disabled())
|
|
529
|
+
return;
|
|
530
|
+
this.isDragging.set(false);
|
|
531
|
+
const { previousIndex, currentIndex } = event;
|
|
532
|
+
// No movement
|
|
533
|
+
if (previousIndex === currentIndex) {
|
|
534
|
+
this.resetDragState();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// No drag data (shouldn't happen but be safe)
|
|
538
|
+
if (!this.draggedRange) {
|
|
539
|
+
this.resetDragState();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const [start, end] = this.draggedRange;
|
|
543
|
+
const count = end - start + 1;
|
|
544
|
+
const isSingleToken = start === end;
|
|
545
|
+
// Block dropping inside own range (only for multi-token drag)
|
|
546
|
+
if (!isSingleToken && currentIndex > start && currentIndex <= end) {
|
|
547
|
+
this.resetDragState();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const tokens = [...this.tokens()];
|
|
551
|
+
if (isSingleToken) {
|
|
552
|
+
// Simple single-token move using CDK utility
|
|
553
|
+
moveItemInArray(tokens, previousIndex, currentIndex);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
// Multi-token move (function with all content)
|
|
557
|
+
// 1. Extract the tokens we're moving
|
|
558
|
+
const moving = tokens.splice(start, count);
|
|
559
|
+
// 2. Calculate insert position
|
|
560
|
+
let insertAt;
|
|
561
|
+
if (currentIndex <= start) {
|
|
562
|
+
// Moving left/backward - insert at drop position
|
|
563
|
+
insertAt = currentIndex;
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
// Moving right/forward
|
|
567
|
+
// After removal, adjust for removed count and insert AFTER drop target
|
|
568
|
+
insertAt = currentIndex - count + 1;
|
|
569
|
+
}
|
|
570
|
+
// 3. Clamp and insert
|
|
571
|
+
insertAt = Math.max(0, Math.min(insertAt, tokens.length));
|
|
572
|
+
tokens.splice(insertAt, 0, ...moving);
|
|
573
|
+
}
|
|
574
|
+
// Update state with recalculated depths
|
|
575
|
+
this.tokens.set(recalculateDepths(tokens));
|
|
576
|
+
this.resetDragState();
|
|
577
|
+
this.emit();
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Reset drag state
|
|
581
|
+
*/
|
|
582
|
+
resetDragState() {
|
|
583
|
+
this.draggedRange = null;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Handle drag end
|
|
587
|
+
* NOTE: CDK fires cdkDragEnded BEFORE cdkDropListDropped
|
|
588
|
+
* So we don't reset draggedRange here - onDrop handles it
|
|
589
|
+
*/
|
|
590
|
+
onDragEnd() {
|
|
591
|
+
this.isDragging.set(false);
|
|
592
|
+
}
|
|
593
|
+
// ============ DELETE ============
|
|
594
|
+
/**
|
|
595
|
+
* Remove a token or function
|
|
596
|
+
*/
|
|
597
|
+
removeToken(token, index) {
|
|
598
|
+
if (this.disabled())
|
|
599
|
+
return;
|
|
600
|
+
if (token.dragBehavior === 'function' && token.functionId) {
|
|
601
|
+
// Remove entire function
|
|
602
|
+
const range = findFunctionRange(this.tokens(), token.functionId);
|
|
603
|
+
if (range) {
|
|
604
|
+
this.tokens.update((tokens) => {
|
|
605
|
+
const newTokens = [...tokens];
|
|
606
|
+
newTokens.splice(range[0], range[1] - range[0] + 1);
|
|
607
|
+
return recalculateDepths(newTokens);
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
// Remove single token
|
|
613
|
+
this.tokens.update((tokens) => tokens.filter((_, i) => i !== index));
|
|
614
|
+
}
|
|
615
|
+
this.emit();
|
|
616
|
+
}
|
|
617
|
+
// ============ HOVER EFFECTS ============
|
|
618
|
+
onTokenHover(token) {
|
|
619
|
+
if (token.functionId && token.dragBehavior === 'function') {
|
|
620
|
+
this.hoveredFunctionId.set(token.functionId);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
onTokenLeave() {
|
|
624
|
+
this.hoveredFunctionId.set(null);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Check if token should be highlighted
|
|
628
|
+
* Only structural tokens (fn-open, fn-close, fn-separator) get highlighted
|
|
629
|
+
* Content tokens (property, operator, literal) inside function are NOT highlighted
|
|
630
|
+
*/
|
|
631
|
+
isHighlighted(token) {
|
|
632
|
+
const hoveredId = this.hoveredFunctionId();
|
|
633
|
+
if (!hoveredId)
|
|
634
|
+
return false;
|
|
635
|
+
// Only highlight structural tokens of the function, not content inside
|
|
636
|
+
const isStructural = token.type === 'fn-open' ||
|
|
637
|
+
token.type === 'fn-close' ||
|
|
638
|
+
token.type === 'fn-separator';
|
|
639
|
+
return isStructural && token.functionId === hoveredId;
|
|
640
|
+
}
|
|
641
|
+
// ============ STYLING ============
|
|
642
|
+
/** Base token classes */
|
|
643
|
+
baseTokenClasses = 'inline-flex items-center px-2 py-1 rounded text-sm font-medium cursor-grab select-none whitespace-nowrap active:cursor-grabbing';
|
|
644
|
+
/**
|
|
645
|
+
* Get Tailwind classes for a token
|
|
646
|
+
*/
|
|
647
|
+
getTokenClasses(token) {
|
|
648
|
+
const base = this.baseTokenClasses;
|
|
649
|
+
// Type-based styling using Tailwind
|
|
650
|
+
switch (token.type) {
|
|
651
|
+
case 'fn-open':
|
|
652
|
+
// Function opening: "IF(" - rounded left only
|
|
653
|
+
return `${base} bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 font-semibold hover:bg-slate-300 dark:hover:bg-slate-600 rounded-r-none pr-0.5`;
|
|
654
|
+
case 'fn-close':
|
|
655
|
+
// Function closing: ")" - rounded right only
|
|
656
|
+
return `${base} bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 font-semibold hover:bg-slate-300 dark:hover:bg-slate-600 rounded-l-none pl-0.5`;
|
|
657
|
+
case 'fn-separator':
|
|
658
|
+
// Function separator: ","
|
|
659
|
+
return `${base} bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 font-semibold hover:bg-slate-300 dark:hover:bg-slate-600 rounded-sm px-1`;
|
|
660
|
+
case 'property':
|
|
661
|
+
// Property tokens - amber colors
|
|
662
|
+
return `${base} bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300 font-semibold hover:bg-amber-200 dark:hover:bg-amber-900/50`;
|
|
663
|
+
case 'operator':
|
|
664
|
+
// Operator tokens - sky colors
|
|
665
|
+
return `${base} bg-sky-100 dark:bg-sky-900/30 text-sky-700 dark:text-sky-300 font-bold hover:bg-sky-200 dark:hover:bg-sky-900/50 px-2`;
|
|
666
|
+
case 'literal':
|
|
667
|
+
// Literal tokens - emerald colors
|
|
668
|
+
return `${base} bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300 hover:bg-emerald-200 dark:hover:bg-emerald-900/50`;
|
|
669
|
+
default:
|
|
670
|
+
return base;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// ============ CONVERSION ============
|
|
674
|
+
/**
|
|
675
|
+
* Convert SmartBlock to FormulaTokens
|
|
676
|
+
*/
|
|
677
|
+
smartBlockToTokens(block) {
|
|
678
|
+
switch (block.type) {
|
|
679
|
+
case 'function':
|
|
680
|
+
return createFunctionTokens(block.signature || `${block.functionName}()`, 0);
|
|
681
|
+
case 'property':
|
|
682
|
+
return [
|
|
683
|
+
createPropertyToken(block.propertyKey || block.value, block.propertyType),
|
|
684
|
+
];
|
|
685
|
+
case 'operator':
|
|
686
|
+
return [createOperatorToken(block.operatorSymbol || block.value)];
|
|
687
|
+
case 'literal':
|
|
688
|
+
return [createLiteralToken(block.literalValue ?? block.value)];
|
|
689
|
+
default:
|
|
690
|
+
return [createLiteralToken(block.value)];
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
// ============ EMIT ============
|
|
694
|
+
emit() {
|
|
695
|
+
const formula = this.formulaString();
|
|
696
|
+
const tokens = this.tokens();
|
|
697
|
+
this.tokensChange.emit(tokens);
|
|
698
|
+
this.formulaChange.emit(formula);
|
|
699
|
+
this.onChange(tokens);
|
|
700
|
+
}
|
|
701
|
+
// ============ CDK HELPERS ============
|
|
702
|
+
/**
|
|
703
|
+
* Track function for ngFor
|
|
704
|
+
*/
|
|
705
|
+
trackByToken(_index, token) {
|
|
706
|
+
return token.id;
|
|
707
|
+
}
|
|
708
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormulaEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
709
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FormulaEditor, isStandalone: true, selector: "mt-formula-editor", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, initialTokens: { classPropertyName: "initialTokens", publicName: "initialTokens", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formulaChange: "formulaChange", tokensChange: "tokensChange", onBlur: "onBlur", onFocus: "onFocus" }, providers: [
|
|
710
|
+
{
|
|
711
|
+
provide: NG_VALUE_ACCESSOR,
|
|
712
|
+
useExisting: forwardRef(() => FormulaEditor),
|
|
713
|
+
multi: true,
|
|
714
|
+
},
|
|
715
|
+
], ngImport: i0, template: "<div\r\n class=\"flex border border-surface-200 dark:border-surface-700 rounded-md bg-white dark:bg-surface-800 min-h-20 font-mono text-sm focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/10\"\r\n [class.opacity-60]=\"disabled()\"\r\n [class.pointer-events-none]=\"disabled()\"\r\n (focus)=\"handleFocus()\"\r\n (blur)=\"handleBlur()\"\r\n tabindex=\"0\"\r\n>\r\n <!-- Line Numbers -->\r\n <div\r\n class=\"flex flex-col px-1.5 py-2 bg-surface-50 dark:bg-surface-900 border-r border-surface-200 dark:border-surface-700 rounded-l-md select-none min-w-6 text-right\"\r\n >\r\n @for (line of lines(); track $index) {\r\n <div class=\"text-surface-400 dark:text-surface-500 text-sm leading-6\">\r\n {{ $index + 1 }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Editor Content - Single Drop Zone -->\r\n <div\r\n class=\"flex-1 p-2 flex flex-wrap gap-0.5 items-start content-start min-h-14 cursor-text\"\r\n cdkDropList\r\n cdkDropListOrientation=\"mixed\"\r\n [cdkDropListData]=\"tokens()\"\r\n [cdkDropListDisabled]=\"disabled()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n >\r\n @if (tokens().length === 0) {\r\n <div class=\"text-surface-400 dark:text-surface-500 text-sm italic\">\r\n {{ placeholder() }}\r\n </div>\r\n } @else {\r\n @for (token of tokens(); track token.id; let i = $index) {\r\n <div\r\n class=\"relative inline-flex items-center group\"\r\n cdkDrag\r\n [cdkDragData]=\"token\"\r\n [cdkDragDisabled]=\"disabled()\"\r\n (cdkDragStarted)=\"onDragStart($event, token, i)\"\r\n (cdkDragEnded)=\"onDragEnd()\"\r\n >\r\n <!-- Drag Placeholder (thin cursor line) -->\r\n <div\r\n *cdkDragPlaceholder\r\n class=\"w-2 min-w-2 h-5 bg-primary rounded-sm opacity-80\"\r\n ></div>\r\n\r\n <!-- Token -->\r\n <span\r\n [class]=\"getTokenClasses(token)\"\r\n [class.border-b-2]=\"!isDragging() && isHighlighted(token)\"\r\n [class.border-primary]=\"!isDragging() && isHighlighted(token)\"\r\n (mouseenter)=\"onTokenHover(token)\"\r\n (mouseleave)=\"onTokenLeave()\"\r\n >\r\n {{ token.value }}\r\n </span>\r\n\r\n <!-- Delete Button (shows on hover, hidden during drag) -->\r\n @if (!isDragging() && !disabled()) {\r\n <button\r\n type=\"button\"\r\n class=\"absolute -top-1.5 -right-1.5 w-3.5 h-3.5 flex items-center justify-center bg-red-500 hover:bg-red-600 text-white rounded-full cursor-pointer scale-75 hover:scale-100 opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto z-10\"\r\n (click)=\"removeToken(token, i); $event.stopPropagation()\"\r\n [title]=\"\r\n token.dragBehavior === 'function' ? 'Remove function' : 'Remove'\r\n \"\r\n >\r\n <svg\r\n class=\"h-2.5 w-2.5\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"3\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M6 18L18 6M6 6l12 12\"\r\n />\r\n </svg>\r\n </button>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n</div>\r\n", styles: [".cdk-drag-preview{width:20px!important;height:20px!important;min-width:20px!important;min-height:20px!important;max-width:20px!important;max-height:20px!important;border-radius:4px;opacity:.9;overflow:hidden!important;cursor:grabbing}.cdk-drag-animating{transition:none!important}.cdk-drag-dragging{opacity:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "ngmodule", type: FormsModule }] });
|
|
716
|
+
}
|
|
717
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormulaEditor, decorators: [{
|
|
718
|
+
type: Component,
|
|
719
|
+
args: [{ selector: 'mt-formula-editor', standalone: true, imports: [CommonModule, DragDropModule, FormsModule], providers: [
|
|
720
|
+
{
|
|
721
|
+
provide: NG_VALUE_ACCESSOR,
|
|
722
|
+
useExisting: forwardRef(() => FormulaEditor),
|
|
723
|
+
multi: true,
|
|
724
|
+
},
|
|
725
|
+
], template: "<div\r\n class=\"flex border border-surface-200 dark:border-surface-700 rounded-md bg-white dark:bg-surface-800 min-h-20 font-mono text-sm focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/10\"\r\n [class.opacity-60]=\"disabled()\"\r\n [class.pointer-events-none]=\"disabled()\"\r\n (focus)=\"handleFocus()\"\r\n (blur)=\"handleBlur()\"\r\n tabindex=\"0\"\r\n>\r\n <!-- Line Numbers -->\r\n <div\r\n class=\"flex flex-col px-1.5 py-2 bg-surface-50 dark:bg-surface-900 border-r border-surface-200 dark:border-surface-700 rounded-l-md select-none min-w-6 text-right\"\r\n >\r\n @for (line of lines(); track $index) {\r\n <div class=\"text-surface-400 dark:text-surface-500 text-sm leading-6\">\r\n {{ $index + 1 }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Editor Content - Single Drop Zone -->\r\n <div\r\n class=\"flex-1 p-2 flex flex-wrap gap-0.5 items-start content-start min-h-14 cursor-text\"\r\n cdkDropList\r\n cdkDropListOrientation=\"mixed\"\r\n [cdkDropListData]=\"tokens()\"\r\n [cdkDropListDisabled]=\"disabled()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n >\r\n @if (tokens().length === 0) {\r\n <div class=\"text-surface-400 dark:text-surface-500 text-sm italic\">\r\n {{ placeholder() }}\r\n </div>\r\n } @else {\r\n @for (token of tokens(); track token.id; let i = $index) {\r\n <div\r\n class=\"relative inline-flex items-center group\"\r\n cdkDrag\r\n [cdkDragData]=\"token\"\r\n [cdkDragDisabled]=\"disabled()\"\r\n (cdkDragStarted)=\"onDragStart($event, token, i)\"\r\n (cdkDragEnded)=\"onDragEnd()\"\r\n >\r\n <!-- Drag Placeholder (thin cursor line) -->\r\n <div\r\n *cdkDragPlaceholder\r\n class=\"w-2 min-w-2 h-5 bg-primary rounded-sm opacity-80\"\r\n ></div>\r\n\r\n <!-- Token -->\r\n <span\r\n [class]=\"getTokenClasses(token)\"\r\n [class.border-b-2]=\"!isDragging() && isHighlighted(token)\"\r\n [class.border-primary]=\"!isDragging() && isHighlighted(token)\"\r\n (mouseenter)=\"onTokenHover(token)\"\r\n (mouseleave)=\"onTokenLeave()\"\r\n >\r\n {{ token.value }}\r\n </span>\r\n\r\n <!-- Delete Button (shows on hover, hidden during drag) -->\r\n @if (!isDragging() && !disabled()) {\r\n <button\r\n type=\"button\"\r\n class=\"absolute -top-1.5 -right-1.5 w-3.5 h-3.5 flex items-center justify-center bg-red-500 hover:bg-red-600 text-white rounded-full cursor-pointer scale-75 hover:scale-100 opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto z-10\"\r\n (click)=\"removeToken(token, i); $event.stopPropagation()\"\r\n [title]=\"\r\n token.dragBehavior === 'function' ? 'Remove function' : 'Remove'\r\n \"\r\n >\r\n <svg\r\n class=\"h-2.5 w-2.5\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"3\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M6 18L18 6M6 6l12 12\"\r\n />\r\n </svg>\r\n </button>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n</div>\r\n", styles: [".cdk-drag-preview{width:20px!important;height:20px!important;min-width:20px!important;min-height:20px!important;max-width:20px!important;max-height:20px!important;border-radius:4px;opacity:.9;overflow:hidden!important;cursor:grabbing}.cdk-drag-animating{transition:none!important}.cdk-drag-dragging{opacity:1}\n"] }]
|
|
726
|
+
}], ctorParameters: () => [], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], initialTokens: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialTokens", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], formulaChange: [{ type: i0.Output, args: ["formulaChange"] }], tokensChange: [{ type: i0.Output, args: ["tokensChange"] }], onBlur: [{ type: i0.Output, args: ["onBlur"] }], onFocus: [{ type: i0.Output, args: ["onFocus"] }] } });
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Toolbar Item Component
|
|
730
|
+
* Clickable item in the toolbar (function, property, operator)
|
|
731
|
+
* Items are clicked to insert - no drag from toolbar
|
|
732
|
+
*
|
|
733
|
+
* This is a pure input/output component with no service dependencies.
|
|
734
|
+
*/
|
|
735
|
+
class FormulaToolbarItem {
|
|
736
|
+
/** Item type */
|
|
737
|
+
type = input.required(...(ngDevMode ? [{ debugName: "type" }] : []));
|
|
738
|
+
/** Item value (function name, property key, operator symbol) */
|
|
739
|
+
value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
740
|
+
/** Display value (optional, defaults to value) */
|
|
741
|
+
display = input(...(ngDevMode ? [undefined, { debugName: "display" }] : []));
|
|
742
|
+
/** Description for tooltip */
|
|
743
|
+
description = input('', ...(ngDevMode ? [{ debugName: "description" }] : []));
|
|
744
|
+
/** Function signature (for functions only, e.g., "SUM(values)") */
|
|
745
|
+
signature = input(...(ngDevMode ? [undefined, { debugName: "signature" }] : []));
|
|
746
|
+
/** Property type for properties */
|
|
747
|
+
propertyType = input('current', ...(ngDevMode ? [{ debugName: "propertyType" }] : []));
|
|
748
|
+
/** Insert event - emits SmartBlock */
|
|
749
|
+
onInsert = output();
|
|
750
|
+
/** Computed display value */
|
|
751
|
+
displayValue = computed(() => this.display() ?? this.value(), ...(ngDevMode ? [{ debugName: "displayValue" }] : []));
|
|
752
|
+
/** Build SmartBlock from item data */
|
|
753
|
+
smartBlock = computed(() => {
|
|
754
|
+
switch (this.type()) {
|
|
755
|
+
case 'function':
|
|
756
|
+
return createFunctionBlock(this.signature() || `${this.value()}()`, this.value());
|
|
757
|
+
case 'property':
|
|
758
|
+
return createPropertyBlock(this.value(), this.propertyType());
|
|
759
|
+
case 'operator':
|
|
760
|
+
return createOperatorBlock(this.value());
|
|
761
|
+
default:
|
|
762
|
+
return createOperatorBlock(this.value());
|
|
763
|
+
}
|
|
764
|
+
}, ...(ngDevMode ? [{ debugName: "smartBlock" }] : []));
|
|
765
|
+
/** Item CSS class based on type */
|
|
766
|
+
itemClass = computed(() => {
|
|
767
|
+
switch (this.type()) {
|
|
768
|
+
case 'function':
|
|
769
|
+
return 'bg-slate-200 text-slate-700 hover:bg-slate-300 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600';
|
|
770
|
+
case 'property':
|
|
771
|
+
return 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:hover:bg-amber-900/50';
|
|
772
|
+
case 'operator':
|
|
773
|
+
return 'bg-sky-100 text-sky-700 hover:bg-sky-200 dark:bg-sky-900/30 dark:text-sky-300 dark:hover:bg-sky-900/50';
|
|
774
|
+
default:
|
|
775
|
+
return 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300';
|
|
776
|
+
}
|
|
777
|
+
}, ...(ngDevMode ? [{ debugName: "itemClass" }] : []));
|
|
778
|
+
/** Handle click - emit the SmartBlock */
|
|
779
|
+
handleClick() {
|
|
780
|
+
this.onInsert.emit(this.smartBlock());
|
|
781
|
+
}
|
|
782
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormulaToolbarItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
783
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FormulaToolbarItem, isStandalone: true, selector: "mt-formula-toolbar-item", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, display: { classPropertyName: "display", publicName: "display", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, signature: { classPropertyName: "signature", publicName: "signature", isSignal: true, isRequired: false, transformFunction: null }, propertyType: { classPropertyName: "propertyType", publicName: "propertyType", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onInsert: "onInsert" }, host: { classAttribute: "inline-block" }, ngImport: i0, template: "<button\r\n type=\"button\"\r\n class=\"inline-flex cursor-pointer items-center gap-1 rounded px-2 py-1 text-sm font-medium transition-colors hover:shadow-sm active:scale-95\"\r\n [class]=\"itemClass()\"\r\n [title]=\"description()\"\r\n (click)=\"handleClick()\"\r\n>\r\n @if (type() === \"function\") {\r\n <span class=\"text-sm opacity-60\">\u0192</span>\r\n }\r\n {{ displayValue() }}\r\n</button>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
784
|
+
}
|
|
785
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormulaToolbarItem, decorators: [{
|
|
786
|
+
type: Component,
|
|
787
|
+
args: [{ selector: 'mt-formula-toolbar-item', standalone: true, imports: [CommonModule], host: {
|
|
788
|
+
class: 'inline-block',
|
|
789
|
+
}, template: "<button\r\n type=\"button\"\r\n class=\"inline-flex cursor-pointer items-center gap-1 rounded px-2 py-1 text-sm font-medium transition-colors hover:shadow-sm active:scale-95\"\r\n [class]=\"itemClass()\"\r\n [title]=\"description()\"\r\n (click)=\"handleClick()\"\r\n>\r\n @if (type() === \"function\") {\r\n <span class=\"text-sm opacity-60\">\u0192</span>\r\n }\r\n {{ displayValue() }}\r\n</button>\r\n" }]
|
|
790
|
+
}], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], display: [{ type: i0.Input, args: [{ isSignal: true, alias: "display", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], signature: [{ type: i0.Input, args: [{ isSignal: true, alias: "signature", required: false }] }], propertyType: [{ type: i0.Input, args: [{ isSignal: true, alias: "propertyType", required: false }] }], onInsert: [{ type: i0.Output, args: ["onInsert"] }] } });
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Formula Toolbar Component
|
|
794
|
+
* Vertical tabs toolbar with functions, properties, and operators
|
|
795
|
+
*
|
|
796
|
+
* This is a PURE input/output component with NO service dependencies.
|
|
797
|
+
* All data (functions, operators, properties) must be passed via inputs.
|
|
798
|
+
*/
|
|
799
|
+
class FormulaToolbar {
|
|
800
|
+
// ============ INPUTS ============
|
|
801
|
+
/** Known properties to show in properties tab */
|
|
802
|
+
knownProperties = input([], ...(ngDevMode ? [{ debugName: "knownProperties" }] : []));
|
|
803
|
+
/** Function categories (from API or static data) */
|
|
804
|
+
functionCategories = input([], ...(ngDevMode ? [{ debugName: "functionCategories" }] : []));
|
|
805
|
+
/** Operators list */
|
|
806
|
+
operators = input([], ...(ngDevMode ? [{ debugName: "operators" }] : []));
|
|
807
|
+
/** Initial active tab */
|
|
808
|
+
initialTab = input('functions', ...(ngDevMode ? [{ debugName: "initialTab" }] : []));
|
|
809
|
+
/** Placeholder for search input */
|
|
810
|
+
searchPlaceholder = input('Search...', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : []));
|
|
811
|
+
/** Labels */
|
|
812
|
+
labels = input({}, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
813
|
+
// ============ OUTPUTS ============
|
|
814
|
+
/** Insert event - emits SmartBlock for the editor */
|
|
815
|
+
onBlockInsert = output();
|
|
816
|
+
/** Tab change event */
|
|
817
|
+
onTabChange = output();
|
|
818
|
+
// ============ STATE ============
|
|
819
|
+
/** Active tab */
|
|
820
|
+
activeTab = signal('functions', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
|
|
821
|
+
/** Tab options */
|
|
822
|
+
tabOptions = [
|
|
823
|
+
{ label: 'Functions', value: 'functions' },
|
|
824
|
+
{ label: 'Properties', value: 'properties' },
|
|
825
|
+
{ label: 'Operators', value: 'operators' },
|
|
826
|
+
];
|
|
827
|
+
/** Search query */
|
|
828
|
+
searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
|
|
829
|
+
/** Custom value input */
|
|
830
|
+
customValue = signal('', ...(ngDevMode ? [{ debugName: "customValue" }] : []));
|
|
831
|
+
/** Detected type of custom value */
|
|
832
|
+
customValueType = computed(() => {
|
|
833
|
+
const val = this.customValue().trim();
|
|
834
|
+
if (!val)
|
|
835
|
+
return 'text';
|
|
836
|
+
// Check if it's a number
|
|
837
|
+
if (/^-?\d+(\.\d+)?$/.test(val)) {
|
|
838
|
+
return 'number';
|
|
839
|
+
}
|
|
840
|
+
// Check if it's a quoted string
|
|
841
|
+
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
842
|
+
(val.startsWith("'") && val.endsWith("'"))) {
|
|
843
|
+
return 'string';
|
|
844
|
+
}
|
|
845
|
+
return 'text';
|
|
846
|
+
}, ...(ngDevMode ? [{ debugName: "customValueType" }] : []));
|
|
847
|
+
/** Preview of custom value */
|
|
848
|
+
customValuePreview = computed(() => {
|
|
849
|
+
const val = this.customValue().trim();
|
|
850
|
+
if (!val)
|
|
851
|
+
return '';
|
|
852
|
+
const type = this.customValueType();
|
|
853
|
+
if (type === 'string') {
|
|
854
|
+
// Show the quoted string as-is
|
|
855
|
+
return val;
|
|
856
|
+
}
|
|
857
|
+
if (type === 'number') {
|
|
858
|
+
return val;
|
|
859
|
+
}
|
|
860
|
+
// For unquoted text, show as string preview
|
|
861
|
+
return `"${val}"`;
|
|
862
|
+
}, ...(ngDevMode ? [{ debugName: "customValuePreview" }] : []));
|
|
863
|
+
/** CSS class for custom value type */
|
|
864
|
+
customValueTypeClass = computed(() => {
|
|
865
|
+
switch (this.customValueType()) {
|
|
866
|
+
case 'number':
|
|
867
|
+
return 'bg-rose-100 text-rose-700 hover:bg-rose-200 dark:bg-rose-900/30 dark:text-rose-300 dark:hover:bg-rose-900/50';
|
|
868
|
+
case 'string':
|
|
869
|
+
return 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-300 dark:hover:bg-emerald-900/50';
|
|
870
|
+
default:
|
|
871
|
+
return 'bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600';
|
|
872
|
+
}
|
|
873
|
+
}, ...(ngDevMode ? [{ debugName: "customValueTypeClass" }] : []));
|
|
874
|
+
constructor() {
|
|
875
|
+
// Initialize active tab from input
|
|
876
|
+
effect(() => {
|
|
877
|
+
const initial = this.initialTab();
|
|
878
|
+
if (initial) {
|
|
879
|
+
this.activeTab.set(initial);
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
// ============ COMPUTED ============
|
|
884
|
+
/** Filtered categories based on search */
|
|
885
|
+
filteredCategories = computed(() => {
|
|
886
|
+
const query = this.searchQuery().toLowerCase();
|
|
887
|
+
const categories = this.functionCategories();
|
|
888
|
+
if (!query)
|
|
889
|
+
return categories;
|
|
890
|
+
return categories
|
|
891
|
+
.map((cat) => ({
|
|
892
|
+
...cat,
|
|
893
|
+
functions: cat.functions.filter((fn) => fn.name.toLowerCase().includes(query) ||
|
|
894
|
+
fn.description.toLowerCase().includes(query)),
|
|
895
|
+
}))
|
|
896
|
+
.filter((cat) => cat.functions.length > 0);
|
|
897
|
+
}, ...(ngDevMode ? [{ debugName: "filteredCategories" }] : []));
|
|
898
|
+
/** Filtered properties based on search */
|
|
899
|
+
filteredProperties = computed(() => {
|
|
900
|
+
const query = this.searchQuery().toLowerCase();
|
|
901
|
+
const props = this.knownProperties();
|
|
902
|
+
if (!query)
|
|
903
|
+
return props;
|
|
904
|
+
return props.filter((p) => p.toLowerCase().includes(query));
|
|
905
|
+
}, ...(ngDevMode ? [{ debugName: "filteredProperties" }] : []));
|
|
906
|
+
/** Filtered operators based on search */
|
|
907
|
+
filteredOperators = computed(() => {
|
|
908
|
+
const query = this.searchQuery().toLowerCase();
|
|
909
|
+
const ops = this.operators();
|
|
910
|
+
if (!query)
|
|
911
|
+
return ops;
|
|
912
|
+
return ops.filter((op) => op.symbol.toLowerCase().includes(query) ||
|
|
913
|
+
op.name.toLowerCase().includes(query) ||
|
|
914
|
+
op.description.toLowerCase().includes(query));
|
|
915
|
+
}, ...(ngDevMode ? [{ debugName: "filteredOperators" }] : []));
|
|
916
|
+
/** Count of filtered items per tab */
|
|
917
|
+
filteredFunctionsCount = computed(() => this.filteredCategories().reduce((sum, cat) => sum + cat.functions.length, 0), ...(ngDevMode ? [{ debugName: "filteredFunctionsCount" }] : []));
|
|
918
|
+
filteredPropertiesCount = computed(() => this.filteredProperties().length, ...(ngDevMode ? [{ debugName: "filteredPropertiesCount" }] : []));
|
|
919
|
+
filteredOperatorsCount = computed(() => this.filteredOperators().length, ...(ngDevMode ? [{ debugName: "filteredOperatorsCount" }] : []));
|
|
920
|
+
/** Smart search effect - switches to tab with results */
|
|
921
|
+
smartSearchEffect = effect(() => {
|
|
922
|
+
const query = this.searchQuery();
|
|
923
|
+
if (!query || query.length < 2)
|
|
924
|
+
return;
|
|
925
|
+
const currentTab = this.activeTab();
|
|
926
|
+
const functionsCount = this.filteredFunctionsCount();
|
|
927
|
+
const propertiesCount = this.filteredPropertiesCount();
|
|
928
|
+
const operatorsCount = this.filteredOperatorsCount();
|
|
929
|
+
// Check if current tab has results
|
|
930
|
+
const currentHasResults = (currentTab === 'functions' && functionsCount > 0) ||
|
|
931
|
+
(currentTab === 'properties' && propertiesCount > 0) ||
|
|
932
|
+
(currentTab === 'operators' && operatorsCount > 0);
|
|
933
|
+
// If current tab has no results, switch to first tab with results
|
|
934
|
+
if (!currentHasResults) {
|
|
935
|
+
if (functionsCount > 0) {
|
|
936
|
+
this.activeTab.set('functions');
|
|
937
|
+
}
|
|
938
|
+
else if (propertiesCount > 0) {
|
|
939
|
+
this.activeTab.set('properties');
|
|
940
|
+
}
|
|
941
|
+
else if (operatorsCount > 0) {
|
|
942
|
+
this.activeTab.set('operators');
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}, ...(ngDevMode ? [{ debugName: "smartSearchEffect" }] : []));
|
|
946
|
+
/** Group operators by type */
|
|
947
|
+
operatorGroups = computed(() => {
|
|
948
|
+
const ops = this.filteredOperators();
|
|
949
|
+
const groups = new Map();
|
|
950
|
+
ops.forEach((op) => {
|
|
951
|
+
const type = op.type || 'other';
|
|
952
|
+
if (!groups.has(type)) {
|
|
953
|
+
groups.set(type, []);
|
|
954
|
+
}
|
|
955
|
+
groups.get(type).push(op);
|
|
956
|
+
});
|
|
957
|
+
return Array.from(groups.entries()).map(([type, operators]) => ({
|
|
958
|
+
type: type.charAt(0).toUpperCase() + type.slice(1),
|
|
959
|
+
operators,
|
|
960
|
+
}));
|
|
961
|
+
}, ...(ngDevMode ? [{ debugName: "operatorGroups" }] : []));
|
|
962
|
+
/** Total item count for current tab */
|
|
963
|
+
itemCount = computed(() => {
|
|
964
|
+
switch (this.activeTab()) {
|
|
965
|
+
case 'functions':
|
|
966
|
+
return this.filteredCategories().reduce((sum, cat) => sum + cat.functions.length, 0);
|
|
967
|
+
case 'properties':
|
|
968
|
+
return this.filteredProperties().length;
|
|
969
|
+
case 'operators':
|
|
970
|
+
return this.filteredOperators().length;
|
|
971
|
+
default:
|
|
972
|
+
return 0;
|
|
973
|
+
}
|
|
974
|
+
}, ...(ngDevMode ? [{ debugName: "itemCount" }] : []));
|
|
975
|
+
// ============ COMPUTED LABELS ============
|
|
976
|
+
labelFunctions = computed(() => this.labels()?.functions ?? 'Functions', ...(ngDevMode ? [{ debugName: "labelFunctions" }] : []));
|
|
977
|
+
labelProperties = computed(() => this.labels()?.properties ?? 'Properties', ...(ngDevMode ? [{ debugName: "labelProperties" }] : []));
|
|
978
|
+
labelOperators = computed(() => this.labels()?.operators ?? 'Operators', ...(ngDevMode ? [{ debugName: "labelOperators" }] : []));
|
|
979
|
+
labelNoFunctions = computed(() => this.labels()?.noFunctionsFound ?? 'No functions found', ...(ngDevMode ? [{ debugName: "labelNoFunctions" }] : []));
|
|
980
|
+
labelNoProperties = computed(() => this.labels()?.noPropertiesAvailable ?? 'No properties available', ...(ngDevMode ? [{ debugName: "labelNoProperties" }] : []));
|
|
981
|
+
labelNoOperators = computed(() => this.labels()?.noOperatorsFound ?? 'No operators found', ...(ngDevMode ? [{ debugName: "labelNoOperators" }] : []));
|
|
982
|
+
// ============ METHODS ============
|
|
983
|
+
/** Get count for a specific tab (filtered when searching) */
|
|
984
|
+
getTabCount(tab) {
|
|
985
|
+
const query = this.searchQuery();
|
|
986
|
+
if (query) {
|
|
987
|
+
// When searching, show filtered count
|
|
988
|
+
switch (tab) {
|
|
989
|
+
case 'functions':
|
|
990
|
+
return this.filteredFunctionsCount();
|
|
991
|
+
case 'properties':
|
|
992
|
+
return this.filteredPropertiesCount();
|
|
993
|
+
case 'operators':
|
|
994
|
+
return this.filteredOperatorsCount();
|
|
995
|
+
default:
|
|
996
|
+
return 0;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
else {
|
|
1000
|
+
// When not searching, show total count
|
|
1001
|
+
switch (tab) {
|
|
1002
|
+
case 'functions':
|
|
1003
|
+
return this.functionCategories().reduce((sum, cat) => sum + cat.functions.length, 0);
|
|
1004
|
+
case 'properties':
|
|
1005
|
+
return this.knownProperties().length;
|
|
1006
|
+
case 'operators':
|
|
1007
|
+
return this.operators().length;
|
|
1008
|
+
default:
|
|
1009
|
+
return 0;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
/** Check if a tab has results when searching */
|
|
1014
|
+
tabHasResults(tab) {
|
|
1015
|
+
return this.getTabCount(tab) > 0;
|
|
1016
|
+
}
|
|
1017
|
+
/** Set active tab */
|
|
1018
|
+
setActiveTab(tab) {
|
|
1019
|
+
this.activeTab.set(tab);
|
|
1020
|
+
this.onTabChange.emit(tab);
|
|
1021
|
+
}
|
|
1022
|
+
/** Emit block insert event */
|
|
1023
|
+
insertBlock(block) {
|
|
1024
|
+
this.onBlockInsert.emit(block);
|
|
1025
|
+
}
|
|
1026
|
+
/** Clear search */
|
|
1027
|
+
clearSearch() {
|
|
1028
|
+
this.searchQuery.set('');
|
|
1029
|
+
}
|
|
1030
|
+
/** Insert custom value */
|
|
1031
|
+
insertCustomValue() {
|
|
1032
|
+
const val = this.customValue().trim();
|
|
1033
|
+
if (!val)
|
|
1034
|
+
return;
|
|
1035
|
+
const type = this.customValueType();
|
|
1036
|
+
let literalValue;
|
|
1037
|
+
if (type === 'number') {
|
|
1038
|
+
literalValue = parseFloat(val);
|
|
1039
|
+
}
|
|
1040
|
+
else if (type === 'string') {
|
|
1041
|
+
// Remove quotes for storage, keep the content
|
|
1042
|
+
literalValue = val.slice(1, -1);
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
// Treat as string, add quotes
|
|
1046
|
+
literalValue = val;
|
|
1047
|
+
}
|
|
1048
|
+
const block = createLiteralBlock(literalValue);
|
|
1049
|
+
// For strings, ensure the value includes quotes for display
|
|
1050
|
+
if (type === 'string' || type === 'text') {
|
|
1051
|
+
block.value = type === 'string' ? val : `"${val}"`;
|
|
1052
|
+
}
|
|
1053
|
+
this.onBlockInsert.emit(block);
|
|
1054
|
+
this.clearCustomValue();
|
|
1055
|
+
}
|
|
1056
|
+
/** Clear custom value input */
|
|
1057
|
+
clearCustomValue() {
|
|
1058
|
+
this.customValue.set('');
|
|
1059
|
+
}
|
|
1060
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormulaToolbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1061
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FormulaToolbar, isStandalone: true, selector: "mt-formula-toolbar", inputs: { knownProperties: { classPropertyName: "knownProperties", publicName: "knownProperties", isSignal: true, isRequired: false, transformFunction: null }, functionCategories: { classPropertyName: "functionCategories", publicName: "functionCategories", isSignal: true, isRequired: false, transformFunction: null }, operators: { classPropertyName: "operators", publicName: "operators", isSignal: true, isRequired: false, transformFunction: null }, initialTab: { classPropertyName: "initialTab", publicName: "initialTab", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onBlockInsert: "onBlockInsert", onTabChange: "onTabChange" }, ngImport: i0, template: "<div class=\"flex h-52 flex-col border-b border-slate-200 dark:border-slate-700\">\r\n <!-- Top Bar: Search + Quick Insert -->\r\n <div\r\n class=\"flex items-center gap-2 border-b border-slate-200 px-3 py-2 dark:border-slate-700\"\r\n >\r\n <!-- Search Field -->\r\n <div class=\"relative flex-1\">\r\n <svg\r\n class=\"absolute start-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\r\n />\r\n </svg>\r\n <input\r\n type=\"text\"\r\n class=\"w-full rounded-lg border border-slate-300 bg-white py-1.5 ps-9 pe-8 text-sm text-slate-700 placeholder:text-slate-400 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all duration-200 dark:border-slate-600 dark:bg-slate-900 dark:text-slate-200 dark:placeholder:text-slate-500\"\r\n [placeholder]=\"searchPlaceholder()\"\r\n [(ngModel)]=\"searchQuery\"\r\n />\r\n @if (searchQuery()) {\r\n <button\r\n type=\"button\"\r\n class=\"absolute end-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-slate-400 hover:bg-slate-200 hover:text-slate-600 transition-all duration-200 dark:hover:bg-slate-700\"\r\n (click)=\"clearSearch()\"\r\n >\r\n <svg\r\n class=\"h-4 w-4\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M6 18L18 6M6 6l12 12\"\r\n />\r\n </svg>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Separator -->\r\n <div class=\"h-6 w-px bg-slate-200 dark:bg-slate-600\"></div>\r\n\r\n <!-- Quick Insert Input -->\r\n <div class=\"relative flex items-center gap-2\">\r\n <span\r\n class=\"text-sm font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wide\"\r\n >Insert:</span\r\n >\r\n <div class=\"relative\">\r\n <input\r\n #customValueInput\r\n type=\"text\"\r\n class=\"w-32 rounded-md border border-dashed border-slate-300 bg-slate-50 px-3 py-1.5 text-sm font-mono text-slate-700 placeholder:text-slate-400 focus:border-primary focus:bg-white focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all duration-200 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-200 dark:placeholder:text-slate-500 dark:focus:bg-slate-900\"\r\n placeholder='123 or \"text\"'\r\n [(ngModel)]=\"customValue\"\r\n (keydown.enter)=\"insertCustomValue()\"\r\n (keydown.escape)=\"clearCustomValue()\"\r\n />\r\n </div>\r\n\r\n <!-- Insert Button with Type Preview -->\r\n @if (customValue()) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-all duration-200 hover:shadow-sm active:scale-95\"\r\n [class]=\"customValueTypeClass()\"\r\n (click)=\"insertCustomValue()\"\r\n [title]=\"'Insert ' + customValueType() + ' (Enter)'\"\r\n >\r\n @switch (customValueType()) {\r\n @case (\"number\") {\r\n <svg\r\n class=\"h-3 w-3\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M7 20l4-16m2 16l4-16M6 9h14M4 15h14\"\r\n />\r\n </svg>\r\n }\r\n @case (\"string\") {\r\n <svg\r\n class=\"h-3 w-3\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z\"\r\n />\r\n </svg>\r\n }\r\n @default {\r\n <svg\r\n class=\"h-3 w-3\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M12 4.5v15m7.5-7.5h-15\"\r\n />\r\n </svg>\r\n }\r\n }\r\n <span class=\"max-w-16 truncate\">{{ customValuePreview() }}</span>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Main Content Row -->\r\n <div class=\"flex flex-1 overflow-hidden\">\r\n <!-- Vertical Tabs -->\r\n <div\r\n class=\"flex w-44 flex-col border-e border-slate-200 dark:border-slate-700\"\r\n >\r\n @for (tab of tabOptions; track tab.value) {\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 px-3 py-2.5 text-start text-sm font-medium transition-all duration-200\"\r\n [class]=\"\r\n activeTab() === tab.value\r\n ? 'bg-white border-e-2 border-primary text-primary dark:bg-slate-900'\r\n : searchQuery() && !tabHasResults(tab.value)\r\n ? 'text-slate-300 dark:text-slate-600'\r\n : 'text-slate-600 hover:bg-slate-100 hover:text-slate-800 dark:text-slate-300 dark:hover:bg-slate-700'\r\n \"\r\n (click)=\"setActiveTab(tab.value)\"\r\n >\r\n <!-- Tab Icons -->\r\n @switch (tab.value) {\r\n @case (\"functions\") {\r\n <svg\r\n class=\"h-5 w-5 shrink-0\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5\"\r\n />\r\n </svg>\r\n }\r\n @case (\"properties\") {\r\n <svg\r\n class=\"h-5 w-5 shrink-0\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z\"\r\n />\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M6 6h.008v.008H6V6z\"\r\n />\r\n </svg>\r\n }\r\n @case (\"operators\") {\r\n <svg\r\n class=\"h-5 w-5 shrink-0\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M15.75 15.75V18m-7.5-6.75h.008v.008H8.25v-.008zm0 2.25h.008v.008H8.25V13.5zm0 2.25h.008v.008H8.25v-.008zm0 2.25h.008v.008H8.25V18zm2.498-6.75h.007v.008h-.007v-.008zm0 2.25h.007v.008h-.007V13.5zm0 2.25h.007v.008h-.007v-.008zm0 2.25h.007v.008h-.007V18zm2.504-6.75h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V13.5zm0 2.25h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V18zm2.498-6.75h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V13.5zM8.25 6h7.5v2.25h-7.5V6zM12 2.25c-1.892 0-3.758.11-5.593.322C5.307 2.7 4.5 3.65 4.5 4.757V19.5a2.25 2.25 0 002.25 2.25h10.5a2.25 2.25 0 002.25-2.25V4.757c0-1.108-.806-2.057-1.907-2.185A48.507 48.507 0 0012 2.25z\"\r\n />\r\n </svg>\r\n }\r\n }\r\n <!-- Label (always visible) -->\r\n <span class=\"truncate whitespace-nowrap text-sm\">{{\r\n tab.label\r\n }}</span>\r\n <!-- Count Badge -->\r\n <span\r\n class=\"ms-auto rounded-full px-1.5 py-0.5 text-xs font-semibold\"\r\n [class]=\"\r\n activeTab() === tab.value\r\n ? 'bg-primary/10 text-primary'\r\n : 'bg-slate-200 text-slate-600 dark:bg-slate-600 dark:text-slate-300'\r\n \"\r\n >\r\n {{ getTabCount(tab.value) }}\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Content Area -->\r\n <div class=\"flex flex-1 flex-col overflow-hidden\">\r\n <!-- Items Grid -->\r\n <div class=\"flex-1 overflow-y-auto p-2.5\">\r\n @switch (activeTab()) {\r\n @case (\"functions\") {\r\n @if (filteredCategories().length === 0) {\r\n <div\r\n class=\"flex h-full items-center justify-center text-sm text-slate-400 dark:text-slate-500\"\r\n >\r\n {{ labelNoFunctions() }}\r\n </div>\r\n } @else {\r\n <div class=\"space-y-3\">\r\n @for (category of filteredCategories(); track category.name) {\r\n <div>\r\n <div class=\"mb-2 flex items-center gap-2\">\r\n <span\r\n class=\"text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400\"\r\n >\r\n {{ category.name }}\r\n </span>\r\n <span\r\n class=\"h-px flex-1 bg-slate-200/60 dark:bg-slate-700/60\"\r\n ></span>\r\n <span\r\n class=\"text-sm text-slate-400 dark:text-slate-500\"\r\n >{{ category.functions.length }}</span\r\n >\r\n </div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (fn of category.functions; track fn.name) {\r\n <mt-formula-toolbar-item\r\n type=\"function\"\r\n [value]=\"fn.name\"\r\n [description]=\"fn.description\"\r\n [signature]=\"fn.signature\"\r\n (onInsert)=\"insertBlock($event)\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n @case (\"properties\") {\r\n @if (filteredProperties().length === 0) {\r\n <div\r\n class=\"flex h-full items-center justify-center text-sm text-slate-400 dark:text-slate-500\"\r\n >\r\n {{ labelNoProperties() }}\r\n </div>\r\n } @else {\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (prop of filteredProperties(); track prop) {\r\n <mt-formula-toolbar-item\r\n type=\"property\"\r\n [value]=\"prop\"\r\n [description]=\"'Property: ' + prop\"\r\n propertyType=\"current\"\r\n (onInsert)=\"insertBlock($event)\"\r\n />\r\n }\r\n </div>\r\n }\r\n }\r\n @case (\"operators\") {\r\n @if (filteredOperators().length === 0) {\r\n <div\r\n class=\"flex h-full items-center justify-center text-sm text-slate-400 dark:text-slate-500\"\r\n >\r\n {{ labelNoOperators() }}\r\n </div>\r\n } @else {\r\n <div class=\"space-y-3\">\r\n @for (group of operatorGroups(); track group.type) {\r\n <div>\r\n <div class=\"mb-2 flex items-center gap-2\">\r\n <span\r\n class=\"text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400\"\r\n >\r\n {{ group.type }}\r\n </span>\r\n <span\r\n class=\"h-px flex-1 bg-slate-200/60 dark:bg-slate-700/60\"\r\n ></span>\r\n </div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (op of group.operators; track op.symbol) {\r\n <mt-formula-toolbar-item\r\n type=\"operator\"\r\n [value]=\"op.symbol\"\r\n [description]=\"op.description\"\r\n (onInsert)=\"insertBlock($event)\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FormulaToolbarItem, selector: "mt-formula-toolbar-item", inputs: ["type", "value", "display", "description", "signature", "propertyType"], outputs: ["onInsert"] }] });
|
|
1062
|
+
}
|
|
1063
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormulaToolbar, decorators: [{
|
|
1064
|
+
type: Component,
|
|
1065
|
+
args: [{ selector: 'mt-formula-toolbar', standalone: true, imports: [CommonModule, FormsModule, FormulaToolbarItem], template: "<div class=\"flex h-52 flex-col border-b border-slate-200 dark:border-slate-700\">\r\n <!-- Top Bar: Search + Quick Insert -->\r\n <div\r\n class=\"flex items-center gap-2 border-b border-slate-200 px-3 py-2 dark:border-slate-700\"\r\n >\r\n <!-- Search Field -->\r\n <div class=\"relative flex-1\">\r\n <svg\r\n class=\"absolute start-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\r\n />\r\n </svg>\r\n <input\r\n type=\"text\"\r\n class=\"w-full rounded-lg border border-slate-300 bg-white py-1.5 ps-9 pe-8 text-sm text-slate-700 placeholder:text-slate-400 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all duration-200 dark:border-slate-600 dark:bg-slate-900 dark:text-slate-200 dark:placeholder:text-slate-500\"\r\n [placeholder]=\"searchPlaceholder()\"\r\n [(ngModel)]=\"searchQuery\"\r\n />\r\n @if (searchQuery()) {\r\n <button\r\n type=\"button\"\r\n class=\"absolute end-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-slate-400 hover:bg-slate-200 hover:text-slate-600 transition-all duration-200 dark:hover:bg-slate-700\"\r\n (click)=\"clearSearch()\"\r\n >\r\n <svg\r\n class=\"h-4 w-4\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M6 18L18 6M6 6l12 12\"\r\n />\r\n </svg>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Separator -->\r\n <div class=\"h-6 w-px bg-slate-200 dark:bg-slate-600\"></div>\r\n\r\n <!-- Quick Insert Input -->\r\n <div class=\"relative flex items-center gap-2\">\r\n <span\r\n class=\"text-sm font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wide\"\r\n >Insert:</span\r\n >\r\n <div class=\"relative\">\r\n <input\r\n #customValueInput\r\n type=\"text\"\r\n class=\"w-32 rounded-md border border-dashed border-slate-300 bg-slate-50 px-3 py-1.5 text-sm font-mono text-slate-700 placeholder:text-slate-400 focus:border-primary focus:bg-white focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all duration-200 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-200 dark:placeholder:text-slate-500 dark:focus:bg-slate-900\"\r\n placeholder='123 or \"text\"'\r\n [(ngModel)]=\"customValue\"\r\n (keydown.enter)=\"insertCustomValue()\"\r\n (keydown.escape)=\"clearCustomValue()\"\r\n />\r\n </div>\r\n\r\n <!-- Insert Button with Type Preview -->\r\n @if (customValue()) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-all duration-200 hover:shadow-sm active:scale-95\"\r\n [class]=\"customValueTypeClass()\"\r\n (click)=\"insertCustomValue()\"\r\n [title]=\"'Insert ' + customValueType() + ' (Enter)'\"\r\n >\r\n @switch (customValueType()) {\r\n @case (\"number\") {\r\n <svg\r\n class=\"h-3 w-3\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M7 20l4-16m2 16l4-16M6 9h14M4 15h14\"\r\n />\r\n </svg>\r\n }\r\n @case (\"string\") {\r\n <svg\r\n class=\"h-3 w-3\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z\"\r\n />\r\n </svg>\r\n }\r\n @default {\r\n <svg\r\n class=\"h-3 w-3\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M12 4.5v15m7.5-7.5h-15\"\r\n />\r\n </svg>\r\n }\r\n }\r\n <span class=\"max-w-16 truncate\">{{ customValuePreview() }}</span>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Main Content Row -->\r\n <div class=\"flex flex-1 overflow-hidden\">\r\n <!-- Vertical Tabs -->\r\n <div\r\n class=\"flex w-44 flex-col border-e border-slate-200 dark:border-slate-700\"\r\n >\r\n @for (tab of tabOptions; track tab.value) {\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 px-3 py-2.5 text-start text-sm font-medium transition-all duration-200\"\r\n [class]=\"\r\n activeTab() === tab.value\r\n ? 'bg-white border-e-2 border-primary text-primary dark:bg-slate-900'\r\n : searchQuery() && !tabHasResults(tab.value)\r\n ? 'text-slate-300 dark:text-slate-600'\r\n : 'text-slate-600 hover:bg-slate-100 hover:text-slate-800 dark:text-slate-300 dark:hover:bg-slate-700'\r\n \"\r\n (click)=\"setActiveTab(tab.value)\"\r\n >\r\n <!-- Tab Icons -->\r\n @switch (tab.value) {\r\n @case (\"functions\") {\r\n <svg\r\n class=\"h-5 w-5 shrink-0\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5\"\r\n />\r\n </svg>\r\n }\r\n @case (\"properties\") {\r\n <svg\r\n class=\"h-5 w-5 shrink-0\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z\"\r\n />\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M6 6h.008v.008H6V6z\"\r\n />\r\n </svg>\r\n }\r\n @case (\"operators\") {\r\n <svg\r\n class=\"h-5 w-5 shrink-0\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M15.75 15.75V18m-7.5-6.75h.008v.008H8.25v-.008zm0 2.25h.008v.008H8.25V13.5zm0 2.25h.008v.008H8.25v-.008zm0 2.25h.008v.008H8.25V18zm2.498-6.75h.007v.008h-.007v-.008zm0 2.25h.007v.008h-.007V13.5zm0 2.25h.007v.008h-.007v-.008zm0 2.25h.007v.008h-.007V18zm2.504-6.75h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V13.5zm0 2.25h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V18zm2.498-6.75h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V13.5zM8.25 6h7.5v2.25h-7.5V6zM12 2.25c-1.892 0-3.758.11-5.593.322C5.307 2.7 4.5 3.65 4.5 4.757V19.5a2.25 2.25 0 002.25 2.25h10.5a2.25 2.25 0 002.25-2.25V4.757c0-1.108-.806-2.057-1.907-2.185A48.507 48.507 0 0012 2.25z\"\r\n />\r\n </svg>\r\n }\r\n }\r\n <!-- Label (always visible) -->\r\n <span class=\"truncate whitespace-nowrap text-sm\">{{\r\n tab.label\r\n }}</span>\r\n <!-- Count Badge -->\r\n <span\r\n class=\"ms-auto rounded-full px-1.5 py-0.5 text-xs font-semibold\"\r\n [class]=\"\r\n activeTab() === tab.value\r\n ? 'bg-primary/10 text-primary'\r\n : 'bg-slate-200 text-slate-600 dark:bg-slate-600 dark:text-slate-300'\r\n \"\r\n >\r\n {{ getTabCount(tab.value) }}\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Content Area -->\r\n <div class=\"flex flex-1 flex-col overflow-hidden\">\r\n <!-- Items Grid -->\r\n <div class=\"flex-1 overflow-y-auto p-2.5\">\r\n @switch (activeTab()) {\r\n @case (\"functions\") {\r\n @if (filteredCategories().length === 0) {\r\n <div\r\n class=\"flex h-full items-center justify-center text-sm text-slate-400 dark:text-slate-500\"\r\n >\r\n {{ labelNoFunctions() }}\r\n </div>\r\n } @else {\r\n <div class=\"space-y-3\">\r\n @for (category of filteredCategories(); track category.name) {\r\n <div>\r\n <div class=\"mb-2 flex items-center gap-2\">\r\n <span\r\n class=\"text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400\"\r\n >\r\n {{ category.name }}\r\n </span>\r\n <span\r\n class=\"h-px flex-1 bg-slate-200/60 dark:bg-slate-700/60\"\r\n ></span>\r\n <span\r\n class=\"text-sm text-slate-400 dark:text-slate-500\"\r\n >{{ category.functions.length }}</span\r\n >\r\n </div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (fn of category.functions; track fn.name) {\r\n <mt-formula-toolbar-item\r\n type=\"function\"\r\n [value]=\"fn.name\"\r\n [description]=\"fn.description\"\r\n [signature]=\"fn.signature\"\r\n (onInsert)=\"insertBlock($event)\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n @case (\"properties\") {\r\n @if (filteredProperties().length === 0) {\r\n <div\r\n class=\"flex h-full items-center justify-center text-sm text-slate-400 dark:text-slate-500\"\r\n >\r\n {{ labelNoProperties() }}\r\n </div>\r\n } @else {\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (prop of filteredProperties(); track prop) {\r\n <mt-formula-toolbar-item\r\n type=\"property\"\r\n [value]=\"prop\"\r\n [description]=\"'Property: ' + prop\"\r\n propertyType=\"current\"\r\n (onInsert)=\"insertBlock($event)\"\r\n />\r\n }\r\n </div>\r\n }\r\n }\r\n @case (\"operators\") {\r\n @if (filteredOperators().length === 0) {\r\n <div\r\n class=\"flex h-full items-center justify-center text-sm text-slate-400 dark:text-slate-500\"\r\n >\r\n {{ labelNoOperators() }}\r\n </div>\r\n } @else {\r\n <div class=\"space-y-3\">\r\n @for (group of operatorGroups(); track group.type) {\r\n <div>\r\n <div class=\"mb-2 flex items-center gap-2\">\r\n <span\r\n class=\"text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400\"\r\n >\r\n {{ group.type }}\r\n </span>\r\n <span\r\n class=\"h-px flex-1 bg-slate-200/60 dark:bg-slate-700/60\"\r\n ></span>\r\n </div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (op of group.operators; track op.symbol) {\r\n <mt-formula-toolbar-item\r\n type=\"operator\"\r\n [value]=\"op.symbol\"\r\n [description]=\"op.description\"\r\n (onInsert)=\"insertBlock($event)\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n" }]
|
|
1066
|
+
}], ctorParameters: () => [], propDecorators: { knownProperties: [{ type: i0.Input, args: [{ isSignal: true, alias: "knownProperties", required: false }] }], functionCategories: [{ type: i0.Input, args: [{ isSignal: true, alias: "functionCategories", required: false }] }], operators: [{ type: i0.Input, args: [{ isSignal: true, alias: "operators", required: false }] }], initialTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialTab", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], onBlockInsert: [{ type: i0.Output, args: ["onBlockInsert"] }], onTabChange: [{ type: i0.Output, args: ["onTabChange"] }] } });
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Formula Status Bar Component
|
|
1070
|
+
* Displays validation status, complexity, and dependencies
|
|
1071
|
+
*
|
|
1072
|
+
* This is a PURE input/output component with NO service dependencies.
|
|
1073
|
+
*/
|
|
1074
|
+
class FormulaStatusBar {
|
|
1075
|
+
// ============ INPUTS ============
|
|
1076
|
+
/** Validation result */
|
|
1077
|
+
validation = input(null, ...(ngDevMode ? [{ debugName: "validation" }] : []));
|
|
1078
|
+
/** Labels for i18n support */
|
|
1079
|
+
labels = input({}, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
1080
|
+
// ============ COMPUTED ============
|
|
1081
|
+
/** Is formula valid */
|
|
1082
|
+
isValid = computed(() => this.validation()?.isValid ?? true, ...(ngDevMode ? [{ debugName: "isValid" }] : []));
|
|
1083
|
+
/** Complexity score */
|
|
1084
|
+
complexity = computed(() => this.validation()?.complexity ?? 0, ...(ngDevMode ? [{ debugName: "complexity" }] : []));
|
|
1085
|
+
/** Dependencies list */
|
|
1086
|
+
dependencies = computed(() => this.validation()?.dependencies ?? [], ...(ngDevMode ? [{ debugName: "dependencies" }] : []));
|
|
1087
|
+
/** Dependencies as text */
|
|
1088
|
+
dependenciesText = computed(() => {
|
|
1089
|
+
const deps = this.dependencies();
|
|
1090
|
+
if (deps.length <= 3) {
|
|
1091
|
+
return deps.join(', ');
|
|
1092
|
+
}
|
|
1093
|
+
return `${deps.slice(0, 3).join(', ')} +${deps.length - 3}`;
|
|
1094
|
+
}, ...(ngDevMode ? [{ debugName: "dependenciesText" }] : []));
|
|
1095
|
+
/** First error */
|
|
1096
|
+
firstError = computed(() => this.validation()?.errors[0] ?? null, ...(ngDevMode ? [{ debugName: "firstError" }] : []));
|
|
1097
|
+
/** Complexity CSS class */
|
|
1098
|
+
complexityClass = computed(() => {
|
|
1099
|
+
const c = this.complexity();
|
|
1100
|
+
if (c <= 10)
|
|
1101
|
+
return 'text-emerald-600 dark:text-emerald-400';
|
|
1102
|
+
if (c <= 25)
|
|
1103
|
+
return 'text-amber-600 dark:text-amber-400';
|
|
1104
|
+
return 'text-rose-600 dark:text-rose-400';
|
|
1105
|
+
}, ...(ngDevMode ? [{ debugName: "complexityClass" }] : []));
|
|
1106
|
+
// ============ COMPUTED LABELS ============
|
|
1107
|
+
labelValid = computed(() => this.labels()?.valid ?? 'Valid', ...(ngDevMode ? [{ debugName: "labelValid" }] : []));
|
|
1108
|
+
labelInvalid = computed(() => this.labels()?.invalid ?? 'Invalid', ...(ngDevMode ? [{ debugName: "labelInvalid" }] : []));
|
|
1109
|
+
labelComplexity = computed(() => this.labels()?.complexity ?? 'Complexity', ...(ngDevMode ? [{ debugName: "labelComplexity" }] : []));
|
|
1110
|
+
labelDependencies = computed(() => this.labels()?.dependencies ?? 'Dependencies', ...(ngDevMode ? [{ debugName: "labelDependencies" }] : []));
|
|
1111
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormulaStatusBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1112
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FormulaStatusBar, isStandalone: true, selector: "mt-formula-status-bar", inputs: { validation: { classPropertyName: "validation", publicName: "validation", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div\r\n class=\"flex items-center gap-4 border-t border-slate-200 px-4 py-2 text-sm dark:border-slate-700\"\r\n>\r\n <!-- Validation Status -->\r\n <div class=\"flex items-center gap-1.5\">\r\n @if (isValid()) {\r\n <span\r\n class=\"flex h-5 w-5 items-center justify-center rounded-full bg-emerald-100 text-emerald-600 dark:bg-emerald-900 dark:text-emerald-400\"\r\n >\r\n <svg\r\n class=\"h-3.5 w-3.5\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2.5\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M5 13l4 4L19 7\"\r\n />\r\n </svg>\r\n </span>\r\n <span class=\"font-medium text-emerald-600 dark:text-emerald-400\">\r\n {{ labelValid() }}\r\n </span>\r\n } @else {\r\n <span\r\n class=\"flex h-5 w-5 items-center justify-center rounded-full bg-rose-100 text-rose-600 dark:bg-rose-900 dark:text-rose-400\"\r\n >\r\n <svg\r\n class=\"h-3.5 w-3.5\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2.5\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M6 18L18 6M6 6l12 12\"\r\n />\r\n </svg>\r\n </span>\r\n <span class=\"font-medium text-rose-600 dark:text-rose-400\">\r\n {{ labelInvalid() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <!-- Separator -->\r\n <div class=\"h-4 w-px bg-slate-300 dark:bg-slate-600\"></div>\r\n\r\n <!-- Complexity -->\r\n <div class=\"flex items-center gap-1.5 text-slate-500 dark:text-slate-400\">\r\n <svg class=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z\"\r\n />\r\n </svg>\r\n <span>{{ labelComplexity() }}:</span>\r\n <span class=\"font-medium\" [class]=\"complexityClass()\">\r\n {{ complexity() }}\r\n </span>\r\n </div>\r\n\r\n <!-- Dependencies -->\r\n @if (dependencies().length > 0) {\r\n <div class=\"h-4 w-px bg-slate-300 dark:bg-slate-600\"></div>\r\n <div class=\"flex items-center gap-1.5 text-slate-500 dark:text-slate-400\">\r\n <svg\r\n class=\"h-4 w-4\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1\"\r\n />\r\n </svg>\r\n <span>{{ labelDependencies() }}:</span>\r\n <span class=\"font-medium text-slate-700 dark:text-slate-300\">\r\n {{ dependenciesText() }}\r\n </span>\r\n </div>\r\n }\r\n\r\n <!-- Error message (if any) -->\r\n @if (firstError(); as error) {\r\n <div\r\n class=\"ms-auto flex items-center gap-1.5 text-rose-600 dark:text-rose-400\"\r\n >\r\n <svg\r\n class=\"h-4 w-4\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\r\n />\r\n </svg>\r\n <span class=\"text-xs\">{{ error.message }}</span>\r\n <span class=\"text-xs opacity-60\"\r\n >({{ error.line }}:{{ error.column }})</span\r\n >\r\n </div>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
1113
|
+
}
|
|
1114
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormulaStatusBar, decorators: [{
|
|
1115
|
+
type: Component,
|
|
1116
|
+
args: [{ selector: 'mt-formula-status-bar', standalone: true, imports: [CommonModule], template: "<div\r\n class=\"flex items-center gap-4 border-t border-slate-200 px-4 py-2 text-sm dark:border-slate-700\"\r\n>\r\n <!-- Validation Status -->\r\n <div class=\"flex items-center gap-1.5\">\r\n @if (isValid()) {\r\n <span\r\n class=\"flex h-5 w-5 items-center justify-center rounded-full bg-emerald-100 text-emerald-600 dark:bg-emerald-900 dark:text-emerald-400\"\r\n >\r\n <svg\r\n class=\"h-3.5 w-3.5\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2.5\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M5 13l4 4L19 7\"\r\n />\r\n </svg>\r\n </span>\r\n <span class=\"font-medium text-emerald-600 dark:text-emerald-400\">\r\n {{ labelValid() }}\r\n </span>\r\n } @else {\r\n <span\r\n class=\"flex h-5 w-5 items-center justify-center rounded-full bg-rose-100 text-rose-600 dark:bg-rose-900 dark:text-rose-400\"\r\n >\r\n <svg\r\n class=\"h-3.5 w-3.5\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2.5\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M6 18L18 6M6 6l12 12\"\r\n />\r\n </svg>\r\n </span>\r\n <span class=\"font-medium text-rose-600 dark:text-rose-400\">\r\n {{ labelInvalid() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <!-- Separator -->\r\n <div class=\"h-4 w-px bg-slate-300 dark:bg-slate-600\"></div>\r\n\r\n <!-- Complexity -->\r\n <div class=\"flex items-center gap-1.5 text-slate-500 dark:text-slate-400\">\r\n <svg class=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z\"\r\n />\r\n </svg>\r\n <span>{{ labelComplexity() }}:</span>\r\n <span class=\"font-medium\" [class]=\"complexityClass()\">\r\n {{ complexity() }}\r\n </span>\r\n </div>\r\n\r\n <!-- Dependencies -->\r\n @if (dependencies().length > 0) {\r\n <div class=\"h-4 w-px bg-slate-300 dark:bg-slate-600\"></div>\r\n <div class=\"flex items-center gap-1.5 text-slate-500 dark:text-slate-400\">\r\n <svg\r\n class=\"h-4 w-4\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1\"\r\n />\r\n </svg>\r\n <span>{{ labelDependencies() }}:</span>\r\n <span class=\"font-medium text-slate-700 dark:text-slate-300\">\r\n {{ dependenciesText() }}\r\n </span>\r\n </div>\r\n }\r\n\r\n <!-- Error message (if any) -->\r\n @if (firstError(); as error) {\r\n <div\r\n class=\"ms-auto flex items-center gap-1.5 text-rose-600 dark:text-rose-400\"\r\n >\r\n <svg\r\n class=\"h-4 w-4\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke=\"currentColor\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\r\n />\r\n </svg>\r\n <span class=\"text-xs\">{{ error.message }}</span>\r\n <span class=\"text-xs opacity-60\"\r\n >({{ error.line }}:{{ error.column }})</span\r\n >\r\n </div>\r\n }\r\n</div>\r\n" }]
|
|
1117
|
+
}], propDecorators: { validation: [{ type: i0.Input, args: [{ isSignal: true, alias: "validation", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }] } });
|
|
1118
|
+
|
|
1119
|
+
// Formula components public API
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Generated bundle index. Do not edit.
|
|
1123
|
+
*/
|
|
1124
|
+
|
|
1125
|
+
export { FormulaEditor, FormulaStatusBar, FormulaToolbar, FormulaToolbarItem, cloneBlock, cloneToken, cloneTokens, createFunctionBlock, createFunctionTokens, createLiteralBlock, createLiteralToken, createOperatorBlock, createOperatorToken, createPropertyBlock, createPropertyToken, findFunctionRange, generateFunctionId, generateSmartBlockId, generateTokenId, getArgumentIndexAtPosition, getFunctionTokens, isValidDropPosition, parseSignature, recalculateDepths, serializeTokens };
|
|
1126
|
+
//# sourceMappingURL=masterteam-components-formula.mjs.map
|