@astryxdesign/core 0.7.7-canary.e629e7d → 0.7.7-canary.e68774f
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/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +117 -170
- package/dist/Token/Token.d.ts +2 -2
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +12 -4
- package/dist/Token/TokenNamingContext.d.ts +14 -0
- package/dist/Token/TokenNamingContext.d.ts.map +1 -0
- package/dist/Token/TokenNamingContext.js +23 -0
- package/package.json +4 -4
- package/src/PowerSearch/PowerSearch.tsx +164 -172
- package/src/Token/Token.tsx +13 -4
- package/src/Token/TokenNamingContext.ts +28 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PowerSearch.d.ts","sourceRoot":"","sources":["../../src/PowerSearch/PowerSearch.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,EAGL,KAAK,yBAAyB,EAC/B,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"PowerSearch.d.ts","sourceRoot":"","sources":["../../src/PowerSearch/PowerSearch.tsx"],"names":[],"mappings":"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"}
|
|
@@ -20,6 +20,7 @@ import { TokenizerClearAllMessageContext } from "../Tokenizer/TokenizerClearAllM
|
|
|
20
20
|
import { TokenizerItemRemovableContext } from "../Tokenizer/TokenizerItemRemovableContext.js";
|
|
21
21
|
import { TypeaheadItem } from "../Typeahead/TypeaheadItem.js";
|
|
22
22
|
import { Token } from "../Token/index.js";
|
|
23
|
+
import { TokenNamingContext } from "../Token/TokenNamingContext.js";
|
|
23
24
|
import { Avatar } from "../Avatar/index.js";
|
|
24
25
|
import { layerAnimations } from "../Layer/layerAnimations.stylex.js";
|
|
25
26
|
import { Icon } from "../Icon/index.js";
|
|
@@ -41,7 +42,7 @@ import { useLocale } from "../i18n/useLocale.js";
|
|
|
41
42
|
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
42
43
|
import { registerEnglishFallback, useComponentTranslator } from "../i18n/useComponentTranslator.js";
|
|
43
44
|
import english from "../i18n/generated-english/PowerSearch/PowerSearch.generated.js";
|
|
44
|
-
import { jsx as _jsx,
|
|
45
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
45
46
|
registerEnglishFallback(english);
|
|
46
47
|
// =============================================================================
|
|
47
48
|
// Icon mapping for typeahead entries
|
|
@@ -90,45 +91,25 @@ function truncateString(value, limit) {
|
|
|
90
91
|
function getEnumLabel(values, value) {
|
|
91
92
|
return values.find(v => v.value === value)?.label ?? value;
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The visible text of a filter token's value. The token renders it, and the
|
|
97
|
+
* token's remove button and editor dialog are named by the same text.
|
|
98
|
+
*/
|
|
99
|
+
function tokenValueText(operatorValue, filterValue, maxLength, locale) {
|
|
99
100
|
switch (filterValue.type) {
|
|
100
101
|
case 'empty':
|
|
101
102
|
return null;
|
|
102
103
|
case 'string':
|
|
103
|
-
return
|
|
104
|
-
...{
|
|
105
|
-
className: "x1lvx875"
|
|
106
|
-
},
|
|
107
|
-
children: truncateString(filterValue.value, maxLength)
|
|
108
|
-
});
|
|
104
|
+
return truncateString(filterValue.value, maxLength);
|
|
109
105
|
case 'integer':
|
|
110
106
|
case 'float':
|
|
111
|
-
return
|
|
112
|
-
...{
|
|
113
|
-
className: "x1lvx875"
|
|
114
|
-
},
|
|
115
|
-
children: filterValue.value
|
|
116
|
-
});
|
|
107
|
+
return String(filterValue.value);
|
|
117
108
|
case 'enum':
|
|
118
109
|
if (operatorValue.type === 'enum') {
|
|
119
|
-
return
|
|
120
|
-
...{
|
|
121
|
-
className: "x1lvx875"
|
|
122
|
-
},
|
|
123
|
-
children: truncateString(getEnumLabel(operatorValue.values, filterValue.value), maxLength)
|
|
124
|
-
});
|
|
110
|
+
return truncateString(getEnumLabel(operatorValue.values, filterValue.value), maxLength);
|
|
125
111
|
}
|
|
126
|
-
return
|
|
127
|
-
...{
|
|
128
|
-
className: "x1lvx875"
|
|
129
|
-
},
|
|
130
|
-
children: truncateString(filterValue.value, maxLength)
|
|
131
|
-
});
|
|
112
|
+
return truncateString(filterValue.value, maxLength);
|
|
132
113
|
case 'string_list':
|
|
133
114
|
{
|
|
134
115
|
const items = filterValue.value;
|
|
@@ -136,28 +117,13 @@ function PowerSearchTokenValue({
|
|
|
136
117
|
return null;
|
|
137
118
|
}
|
|
138
119
|
if (items.length === 1) {
|
|
139
|
-
return
|
|
140
|
-
...{
|
|
141
|
-
className: "x1lvx875"
|
|
142
|
-
},
|
|
143
|
-
children: truncateString(items[0], maxLength)
|
|
144
|
-
});
|
|
120
|
+
return truncateString(items[0], maxLength);
|
|
145
121
|
}
|
|
146
122
|
const totalLength = items.reduce((sum, s) => sum + s.length, 0);
|
|
147
123
|
if (totalLength > maxLength) {
|
|
148
|
-
return
|
|
149
|
-
...{
|
|
150
|
-
className: "x1lvx875"
|
|
151
|
-
},
|
|
152
|
-
children: [items.length, " items"]
|
|
153
|
-
});
|
|
124
|
+
return `${items.length} items`;
|
|
154
125
|
}
|
|
155
|
-
return
|
|
156
|
-
...{
|
|
157
|
-
className: "x1lvx875"
|
|
158
|
-
},
|
|
159
|
-
children: items.join(', ')
|
|
160
|
-
});
|
|
126
|
+
return items.join(', ');
|
|
161
127
|
}
|
|
162
128
|
case 'enum_list':
|
|
163
129
|
{
|
|
@@ -168,43 +134,18 @@ function PowerSearchTokenValue({
|
|
|
168
134
|
if (operatorValue.type === 'enum_list') {
|
|
169
135
|
const labels = items.map(v => getEnumLabel(operatorValue.values, v));
|
|
170
136
|
if (labels.length === 1) {
|
|
171
|
-
return
|
|
172
|
-
...{
|
|
173
|
-
className: "x1lvx875"
|
|
174
|
-
},
|
|
175
|
-
children: truncateString(labels[0], maxLength)
|
|
176
|
-
});
|
|
137
|
+
return truncateString(labels[0], maxLength);
|
|
177
138
|
}
|
|
178
139
|
const totalLength = labels.reduce((sum, s) => sum + s.length, 0);
|
|
179
140
|
if (totalLength > maxLength) {
|
|
180
|
-
return
|
|
181
|
-
...{
|
|
182
|
-
className: "x1lvx875"
|
|
183
|
-
},
|
|
184
|
-
children: [labels.length, " items"]
|
|
185
|
-
});
|
|
141
|
+
return `${labels.length} items`;
|
|
186
142
|
}
|
|
187
|
-
return
|
|
188
|
-
...{
|
|
189
|
-
className: "x1lvx875"
|
|
190
|
-
},
|
|
191
|
-
children: labels.join(', ')
|
|
192
|
-
});
|
|
143
|
+
return labels.join(', ');
|
|
193
144
|
}
|
|
194
145
|
if (items.length === 1) {
|
|
195
|
-
return
|
|
196
|
-
...{
|
|
197
|
-
className: "x1lvx875"
|
|
198
|
-
},
|
|
199
|
-
children: truncateString(items[0], maxLength)
|
|
200
|
-
});
|
|
146
|
+
return truncateString(items[0], maxLength);
|
|
201
147
|
}
|
|
202
|
-
return
|
|
203
|
-
...{
|
|
204
|
-
className: "x1lvx875"
|
|
205
|
-
},
|
|
206
|
-
children: [items.length, " items"]
|
|
207
|
-
});
|
|
148
|
+
return `${items.length} items`;
|
|
208
149
|
}
|
|
209
150
|
case 'entity_list':
|
|
210
151
|
{
|
|
@@ -213,89 +154,46 @@ function PowerSearchTokenValue({
|
|
|
213
154
|
return null;
|
|
214
155
|
}
|
|
215
156
|
if (entities.length === 1) {
|
|
216
|
-
return
|
|
217
|
-
...{
|
|
218
|
-
className: "x1lvx875"
|
|
219
|
-
},
|
|
220
|
-
children: truncateString(entities[0].label, maxLength)
|
|
221
|
-
});
|
|
157
|
+
return truncateString(entities[0].label, maxLength);
|
|
222
158
|
}
|
|
223
159
|
const totalLength = entities.reduce((sum, e) => sum + e.label.length, 0);
|
|
224
160
|
if (totalLength > maxLength) {
|
|
225
|
-
return
|
|
226
|
-
...{
|
|
227
|
-
className: "x1lvx875"
|
|
228
|
-
},
|
|
229
|
-
children: [entities.length, " items"]
|
|
230
|
-
});
|
|
161
|
+
return `${entities.length} items`;
|
|
231
162
|
}
|
|
232
|
-
return
|
|
233
|
-
...{
|
|
234
|
-
className: "x1lvx875"
|
|
235
|
-
},
|
|
236
|
-
children: entities.map(e => e.label).join(', ')
|
|
237
|
-
});
|
|
163
|
+
return entities.map(e => e.label).join(', ');
|
|
238
164
|
}
|
|
239
165
|
case 'time':
|
|
240
|
-
return
|
|
241
|
-
...{
|
|
242
|
-
className: "x1lvx875"
|
|
243
|
-
},
|
|
244
|
-
children: filterValue.value
|
|
245
|
-
});
|
|
166
|
+
return filterValue.value;
|
|
246
167
|
case 'date_absolute':
|
|
247
|
-
|
|
248
|
-
const formatted = formatDateAbsoluteCompact(filterValue.unixSeconds, locale);
|
|
249
|
-
return /*#__PURE__*/_jsx("span", {
|
|
250
|
-
...{
|
|
251
|
-
className: "x1lvx875"
|
|
252
|
-
},
|
|
253
|
-
children: truncateString(formatted, maxLength)
|
|
254
|
-
});
|
|
255
|
-
}
|
|
168
|
+
return truncateString(formatDateAbsoluteCompact(filterValue.unixSeconds, locale), maxLength);
|
|
256
169
|
case 'date_relative':
|
|
257
|
-
return
|
|
258
|
-
...{
|
|
259
|
-
className: "x1lvx875"
|
|
260
|
-
},
|
|
261
|
-
children: truncateString(filterValue.value, maxLength)
|
|
262
|
-
});
|
|
170
|
+
return truncateString(filterValue.value, maxLength);
|
|
263
171
|
case 'date_range':
|
|
264
|
-
return
|
|
265
|
-
...{
|
|
266
|
-
className: "x1lvx875"
|
|
267
|
-
},
|
|
268
|
-
children: "date range"
|
|
269
|
-
});
|
|
172
|
+
return 'date range';
|
|
270
173
|
case 'custom':
|
|
271
174
|
if (operatorValue.type === 'custom') {
|
|
272
|
-
return
|
|
273
|
-
...{
|
|
274
|
-
className: "x1lvx875"
|
|
275
|
-
},
|
|
276
|
-
children: truncateString(operatorValue.getString(filterValue.value), maxLength)
|
|
277
|
-
});
|
|
175
|
+
return truncateString(operatorValue.getString(filterValue.value), maxLength);
|
|
278
176
|
}
|
|
279
|
-
return
|
|
280
|
-
...{
|
|
281
|
-
className: "x1lvx875"
|
|
282
|
-
},
|
|
283
|
-
children: filterValue.value
|
|
284
|
-
});
|
|
177
|
+
return filterValue.value;
|
|
285
178
|
case 'nested':
|
|
286
179
|
{
|
|
287
180
|
const count = filterValue.value.length;
|
|
288
|
-
return
|
|
289
|
-
...{
|
|
290
|
-
className: "x1lvx875"
|
|
291
|
-
},
|
|
292
|
-
children: count === 1 ? '1 filter' : `${count} filters`
|
|
293
|
-
});
|
|
181
|
+
return count === 1 ? '1 filter' : `${count} filters`;
|
|
294
182
|
}
|
|
295
183
|
default:
|
|
296
184
|
return null;
|
|
297
185
|
}
|
|
298
186
|
}
|
|
187
|
+
function PowerSearchTokenValue({
|
|
188
|
+
text
|
|
189
|
+
}) {
|
|
190
|
+
return /*#__PURE__*/_jsx("span", {
|
|
191
|
+
...{
|
|
192
|
+
className: "x1lvx875"
|
|
193
|
+
},
|
|
194
|
+
children: text
|
|
195
|
+
});
|
|
196
|
+
}
|
|
299
197
|
|
|
300
198
|
// =============================================================================
|
|
301
199
|
// Types
|
|
@@ -309,6 +207,14 @@ function PowerSearchTokenValue({
|
|
|
309
207
|
// Component
|
|
310
208
|
// =============================================================================
|
|
311
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Whether a press on a token with this operator commits the filter instead of
|
|
212
|
+
* opening its editor: an `empty` operator with no `empty` Editor override.
|
|
213
|
+
*/
|
|
214
|
+
function commitsWithoutEditor(operator, componentOverrides) {
|
|
215
|
+
return operator?.value.type === 'empty' && componentOverrides?.empty?.Editor == null;
|
|
216
|
+
}
|
|
217
|
+
|
|
312
218
|
/**
|
|
313
219
|
* Structured filter bar where each token represents a filter (field + operator + value).
|
|
314
220
|
*
|
|
@@ -423,6 +329,28 @@ export function PowerSearch({
|
|
|
423
329
|
type: 'idle'
|
|
424
330
|
});
|
|
425
331
|
|
|
332
|
+
// A default filter token's visible text: its label (field and operator)
|
|
333
|
+
// and its value. The token's remove button and editor dialog are named by
|
|
334
|
+
// the same text.
|
|
335
|
+
const tokenText = useCallback(filter => {
|
|
336
|
+
const field = config.getField(filter.field);
|
|
337
|
+
const operator = config.getOperator(filter.field, filter.operator);
|
|
338
|
+
const fieldLabel = field?.label ?? '';
|
|
339
|
+
const operatorLabel = operator ? resolveOperatorLabel(operator, t) : '';
|
|
340
|
+
const label = `${fieldLabel}: ${operatorLabel}`.trim();
|
|
341
|
+
const value = operator ? tokenValueText(operator.value, filter.value, Math.max(maxTokenLength - fieldLabel.length - operatorLabel.length, 10), locale) : null;
|
|
342
|
+
return {
|
|
343
|
+
label,
|
|
344
|
+
value,
|
|
345
|
+
full: value != null ? `${label} ${value}` : label
|
|
346
|
+
};
|
|
347
|
+
}, [config, maxTokenLength, t, locale]);
|
|
348
|
+
// The built-in token being edited. A token from a `components` override
|
|
349
|
+
// names itself, so its editor keeps the plain popover.
|
|
350
|
+
const editedFilter = popoverState.type === 'editing' ? filters[popoverState.filterIndex] : undefined;
|
|
351
|
+
const editedOperator = editedFilter != null ? config.getOperator(editedFilter.field, editedFilter.operator) : undefined;
|
|
352
|
+
const editedBuiltInFilter = editedOperator == null || componentOverrides?.[editedOperator.value.type]?.Token == null ? editedFilter : undefined;
|
|
353
|
+
|
|
426
354
|
// Layer for positioning the edit popover anchored to the tokenizer
|
|
427
355
|
const handleLayerHide = useCallback(() => {
|
|
428
356
|
setPopoverStateRaw({
|
|
@@ -434,9 +362,13 @@ export function PowerSearch({
|
|
|
434
362
|
hasLightDismiss: true,
|
|
435
363
|
hasCloseButton: false,
|
|
436
364
|
hasAutoFocus: false,
|
|
437
|
-
//
|
|
438
|
-
// stays on the tokenizer input, so a
|
|
439
|
-
|
|
365
|
+
// Adding: the popup's own listbox/menu content is the exposed semantics,
|
|
366
|
+
// and focus stays on the tokenizer input, so a dialog wrapper is
|
|
367
|
+
// incorrect. Editing a token: focus moves into the editor's controls, so
|
|
368
|
+
// the editor is a non-modal dialog named by the token's visible text.
|
|
369
|
+
role: editedBuiltInFilter != null ? 'dialog' : 'none',
|
|
370
|
+
isModal: false,
|
|
371
|
+
dialogLabel: editedBuiltInFilter != null ? tokenText(editedBuiltInFilter).full : undefined
|
|
440
372
|
});
|
|
441
373
|
|
|
442
374
|
// Wrapper that manages layer visibility and tokenizer focus alongside state
|
|
@@ -567,7 +499,7 @@ export function PowerSearch({
|
|
|
567
499
|
// edit: commit it as that editor's save would, the way adding one does,
|
|
568
500
|
// instead of opening it. A consumer's `empty` Editor override still opens.
|
|
569
501
|
const operator = config.getOperator(filter.field, filter.operator);
|
|
570
|
-
if (operator
|
|
502
|
+
if (commitsWithoutEditor(operator, componentOverrides)) {
|
|
571
503
|
const newFilters = [...filters];
|
|
572
504
|
newFilters[index] = {
|
|
573
505
|
field: filter.field,
|
|
@@ -652,13 +584,14 @@ export function PowerSearch({
|
|
|
652
584
|
// Default token rendering
|
|
653
585
|
const fieldLabel = field?.label ?? '';
|
|
654
586
|
const operatorLabel = operator ? resolveOperatorLabel(operator, t) : '';
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
587
|
+
const text = filter != null ? tokenText(filter) : {
|
|
588
|
+
label: `${fieldLabel}: ${operatorLabel}`.trim(),
|
|
589
|
+
value: null
|
|
590
|
+
};
|
|
591
|
+
const tokenLabel = text.label;
|
|
592
|
+
const fullText = text.value != null ? `${text.label} ${text.value}` : text.label;
|
|
593
|
+
const valueContent = text.value != null ? /*#__PURE__*/_jsx(PowerSearchTokenValue, {
|
|
594
|
+
text: text.value
|
|
662
595
|
}) : undefined;
|
|
663
596
|
|
|
664
597
|
// Show entity photo as token icon for single-entity filters
|
|
@@ -671,24 +604,38 @@ export function PowerSearch({
|
|
|
671
604
|
size: 16
|
|
672
605
|
});
|
|
673
606
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
607
|
+
|
|
608
|
+
// A token that opens its editor says that it opens a dialog, and the
|
|
609
|
+
// naming context names it by all its visible text.
|
|
610
|
+
const editorProps = handleClick != null && !commitsWithoutEditor(operator, componentOverrides) ? {
|
|
611
|
+
'aria-haspopup': 'dialog',
|
|
612
|
+
'aria-expanded': popoverState.type === 'editing' && popoverState.filterIndex === filterIndex
|
|
613
|
+
} : null;
|
|
614
|
+
return /*#__PURE__*/_jsx(TokenNamingContext, {
|
|
615
|
+
value: {
|
|
616
|
+
actionName: fullText,
|
|
617
|
+
removeName: fullText
|
|
618
|
+
},
|
|
619
|
+
children: /*#__PURE__*/_jsx(Token, {
|
|
620
|
+
label: tokenLabel,
|
|
621
|
+
size: size,
|
|
622
|
+
icon: tokenIcon,
|
|
623
|
+
...editorProps,
|
|
624
|
+
endContent: valueContent,
|
|
625
|
+
onMouseDown: handleClick ? e => {
|
|
626
|
+
// Keep a token press from entering Tokenizer's input-focus
|
|
627
|
+
// redirect. Click still edits/removes; Tab entry is unchanged.
|
|
628
|
+
e.preventDefault();
|
|
629
|
+
} : undefined,
|
|
630
|
+
onClick: handleClick ? e => {
|
|
631
|
+
e.stopPropagation();
|
|
632
|
+
handleClick();
|
|
633
|
+
} : undefined,
|
|
634
|
+
onRemove: handleRemove,
|
|
635
|
+
isDisabled: isDisabled
|
|
636
|
+
})
|
|
690
637
|
});
|
|
691
|
-
}, [filters, config, configProp, maxTokenLength,
|
|
638
|
+
}, [filters, config, configProp, maxTokenLength, size, isReadOnly, isDisabled, handleTokenClick, componentOverrides, t, popoverState, tokenText]);
|
|
692
639
|
|
|
693
640
|
// Custom typeahead item renderer — adds field icon and description
|
|
694
641
|
const renderItem = useCallback(item => {
|
package/dist/Token/Token.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - /apps/storybook/stories/Token.stories.tsx (storybook stories)
|
|
13
13
|
* - /packages/cli/assets/templates/blocks/components/Token/ (showcase blocks)
|
|
14
14
|
*/
|
|
15
|
-
import type
|
|
15
|
+
import { type ReactNode } from 'react';
|
|
16
16
|
import type { BaseProps } from '../BaseProps.js';
|
|
17
17
|
import type { TokenColorMap } from './index.js';
|
|
18
18
|
/**
|
|
@@ -95,7 +95,7 @@ export interface TokenProps extends BaseProps<HTMLElement> {
|
|
|
95
95
|
* <Token label="Link" href="/path" />
|
|
96
96
|
* ```
|
|
97
97
|
*/
|
|
98
|
-
export declare function Token({ label, size, color, icon, isDisabled, onRemove, onClick, href, description, endContent, isLabelHidden, xstyle, className, style, 'data-testid': testId, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, ref, ...rest }: TokenProps): import("react").JSX.Element;
|
|
98
|
+
export declare function Token({ label, size, color, icon, isDisabled, onRemove, onClick, href, description, endContent, isLabelHidden, xstyle, className, style, 'data-testid': testId, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-haspopup': ariaHasPopup, 'aria-expanded': ariaExpanded, ref, ...rest }: TokenProps): import("react").JSX.Element;
|
|
99
99
|
export declare namespace Token {
|
|
100
100
|
var displayName: string;
|
|
101
101
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Token.d.ts","sourceRoot":"","sources":["../../src/Token/Token.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"Token.d.ts","sourceRoot":"","sources":["../../src/Token/Token.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAM,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAiB1C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAK5C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,SAAS,CAAC;AAe3C;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,MAAM,WAAW,UAAW,SAAQ,SAAS,CAAC,WAAW,CAAC;IACxD,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACzC;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACxC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AA6JD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,IAAW,EACX,KAAiB,EACjB,IAAI,EACJ,UAAkB,EAClB,QAAQ,EACR,OAAO,EACP,IAAI,EACJ,WAAW,EACX,UAAU,EACV,aAAqB,EACrB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,iBAAiB,EAAE,cAAc,EACjC,kBAAkB,EAAE,eAAe,EACnC,eAAe,EAAE,YAAY,EAC7B,eAAe,EAAE,YAAY,EAC7B,GAAG,EACH,GAAG,IAAI,EACR,EAAE,UAAU,+BA8QZ;yBApSe,KAAK"}
|
package/dist/Token/Token.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* - /apps/storybook/stories/Token.stories.tsx (storybook stories)
|
|
17
17
|
* - /packages/cli/assets/templates/blocks/components/Token/ (showcase blocks)
|
|
18
18
|
*/
|
|
19
|
+
import { use } from 'react';
|
|
19
20
|
import * as stylex from '@stylexjs/stylex';
|
|
20
21
|
import "../theme/tokens.stylex.js";
|
|
21
22
|
import { colorVars, spacingVars, radiusVars, sizeVars, durationVars, easeVars, fontWeightVars, typeScaleVars } from "../theme/tokens.stylex.js";
|
|
@@ -29,6 +30,7 @@ import { focusOutlineProps } from "../utils/focusOutline.stylex.js";
|
|
|
29
30
|
import { interactionOverlayStyles } from "../utils/interactionOverlay.stylex.js";
|
|
30
31
|
import { usePressFeedback } from "../hooks/usePressFeedback.js";
|
|
31
32
|
import { disabledInteractionProps } from "../utils/disabledInteraction.js";
|
|
33
|
+
import { TokenNamingContext } from "./TokenNamingContext.js";
|
|
32
34
|
import { registerEnglishFallback, useComponentTranslator } from "../i18n/useComponentTranslator.js";
|
|
33
35
|
import english from "../i18n/generated-english/Token/Token.generated.js";
|
|
34
36
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -216,6 +218,8 @@ export function Token({
|
|
|
216
218
|
'data-testid': testId,
|
|
217
219
|
'aria-labelledby': ariaLabelledBy,
|
|
218
220
|
'aria-describedby': ariaDescribedBy,
|
|
221
|
+
'aria-haspopup': ariaHasPopup,
|
|
222
|
+
'aria-expanded': ariaExpanded,
|
|
219
223
|
ref,
|
|
220
224
|
...rest
|
|
221
225
|
}) {
|
|
@@ -230,6 +234,7 @@ export function Token({
|
|
|
230
234
|
onClick,
|
|
231
235
|
isDisabled
|
|
232
236
|
});
|
|
237
|
+
const naming = use(TokenNamingContext);
|
|
233
238
|
|
|
234
239
|
// When role is 'button' via context (no explicit onClick), treat as
|
|
235
240
|
// if onClick was provided — the popover attaches its own handler.
|
|
@@ -237,7 +242,7 @@ export function Token({
|
|
|
237
242
|
const removeButton = onRemove != null && /*#__PURE__*/_jsx("button", {
|
|
238
243
|
type: "button",
|
|
239
244
|
"aria-label": t('@astryx.token.remove', {
|
|
240
|
-
label
|
|
245
|
+
label: naming?.removeName ?? label
|
|
241
246
|
}),
|
|
242
247
|
onClick: e => {
|
|
243
248
|
e.stopPropagation();
|
|
@@ -268,12 +273,14 @@ export function Token({
|
|
|
268
273
|
}), endContent, removeButton]
|
|
269
274
|
});
|
|
270
275
|
|
|
271
|
-
// Naming inputs reach the element that owns the token's action:
|
|
272
|
-
// button of a clickable token, otherwise the root
|
|
276
|
+
// Naming and popup inputs reach the element that owns the token's action:
|
|
277
|
+
// the inner button of a clickable token, otherwise the root
|
|
273
278
|
// (architecture:public-component-api INV5).
|
|
274
279
|
const labellingProps = {
|
|
275
280
|
'aria-labelledby': ariaLabelledBy,
|
|
276
|
-
'aria-describedby': ariaDescribedBy
|
|
281
|
+
'aria-describedby': ariaDescribedBy,
|
|
282
|
+
'aria-haspopup': ariaHasPopup,
|
|
283
|
+
'aria-expanded': ariaExpanded
|
|
277
284
|
};
|
|
278
285
|
const sharedProps = {
|
|
279
286
|
'data-testid': testId,
|
|
@@ -398,6 +405,7 @@ export function Token({
|
|
|
398
405
|
children: [icon, /*#__PURE__*/_jsx("button", {
|
|
399
406
|
type: "button",
|
|
400
407
|
onClick: effectiveOnClick,
|
|
408
|
+
"aria-label": ariaLabelledBy == null ? naming?.actionName : undefined,
|
|
401
409
|
...labellingProps,
|
|
402
410
|
"aria-disabled": isDisabled || undefined,
|
|
403
411
|
...(isDisabled ? disabledInteractionProps() : null),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Names for a Token whose visible text runs past its label. PowerSearch
|
|
3
|
+
* provides them: its tokens show the value as `endContent`.
|
|
4
|
+
*
|
|
5
|
+
* - `actionName` names a clickable token's inner button in place of `label`.
|
|
6
|
+
* - `removeName` names the remove button in place of `label`. The remove word
|
|
7
|
+
* still comes from the catalog, so each locale keeps its word order.
|
|
8
|
+
*/
|
|
9
|
+
export interface TokenNaming {
|
|
10
|
+
actionName?: string;
|
|
11
|
+
removeName?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const TokenNamingContext: import("react").Context<TokenNaming | null>;
|
|
14
|
+
//# sourceMappingURL=TokenNamingContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenNamingContext.d.ts","sourceRoot":"","sources":["../../src/Token/TokenNamingContext.ts"],"names":[],"mappings":"AAaA;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,kBAAkB,6CAA0C,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file TokenNamingContext.ts
|
|
7
|
+
* @input Naming parts from a caller whose token value renders outside its label
|
|
8
|
+
* @output Internal context: the names of a token's action and remove button
|
|
9
|
+
* @position Internal to Core; not exported from the Token entry point
|
|
10
|
+
*/
|
|
11
|
+
import { createContext } from 'react';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Names for a Token whose visible text runs past its label. PowerSearch
|
|
15
|
+
* provides them: its tokens show the value as `endContent`.
|
|
16
|
+
*
|
|
17
|
+
* - `actionName` names a clickable token's inner button in place of `label`.
|
|
18
|
+
* - `removeName` names the remove button in place of `label`. The remove word
|
|
19
|
+
* still comes from the catalog, so each locale keeps its word order.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const TokenNamingContext = /*#__PURE__*/createContext(null);
|
|
23
|
+
TokenNamingContext.displayName = 'TokenNamingContext';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.7-canary.
|
|
3
|
+
"version": "0.7.7-canary.e68774f",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -865,9 +865,9 @@
|
|
|
865
865
|
"react-dom": ">=19.0.0"
|
|
866
866
|
},
|
|
867
867
|
"devDependencies": {
|
|
868
|
-
"@astryxdesign/a11y-spec": "0.7.7-canary.
|
|
869
|
-
"@astryxdesign/cli": "0.7.7-canary.
|
|
870
|
-
"@astryxdesign/interaction-harness": "0.7.7-canary.
|
|
868
|
+
"@astryxdesign/a11y-spec": "0.7.7-canary.e68774f",
|
|
869
|
+
"@astryxdesign/cli": "0.7.7-canary.e68774f",
|
|
870
|
+
"@astryxdesign/interaction-harness": "0.7.7-canary.e68774f",
|
|
871
871
|
"@babel/cli": "^7.29.7",
|
|
872
872
|
"@babel/core": "^7.29.7",
|
|
873
873
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -34,6 +34,7 @@ import {TokenizerClearAllMessageContext} from '../Tokenizer/TokenizerClearAllMes
|
|
|
34
34
|
import {TokenizerItemRemovableContext} from '../Tokenizer/TokenizerItemRemovableContext';
|
|
35
35
|
import {TypeaheadItem} from '../Typeahead/TypeaheadItem';
|
|
36
36
|
import {Token} from '../Token';
|
|
37
|
+
import {TokenNamingContext} from '../Token/TokenNamingContext';
|
|
37
38
|
import {Avatar} from '../Avatar';
|
|
38
39
|
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
39
40
|
import {Icon} from '../Icon';
|
|
@@ -75,6 +76,7 @@ import type {
|
|
|
75
76
|
OperatorValue,
|
|
76
77
|
EnumItem,
|
|
77
78
|
PowerSearchComponents,
|
|
79
|
+
PowerSearchOperator,
|
|
78
80
|
} from './types';
|
|
79
81
|
|
|
80
82
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
@@ -158,52 +160,35 @@ function getEnumLabel(values: ReadonlyArray<EnumItem>, value: string): string {
|
|
|
158
160
|
return values.find(v => v.value === value)?.label ?? value;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}) {
|
|
163
|
+
/**
|
|
164
|
+
* The visible text of a filter token's value. The token renders it, and the
|
|
165
|
+
* token's remove button and editor dialog are named by the same text.
|
|
166
|
+
*/
|
|
167
|
+
function tokenValueText(
|
|
168
|
+
operatorValue: OperatorValue,
|
|
169
|
+
filterValue: FilterValue,
|
|
170
|
+
maxLength: number,
|
|
171
|
+
locale: Locale,
|
|
172
|
+
): string | null {
|
|
172
173
|
switch (filterValue.type) {
|
|
173
174
|
case 'empty':
|
|
174
175
|
return null;
|
|
175
176
|
|
|
176
177
|
case 'string':
|
|
177
|
-
return (
|
|
178
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
179
|
-
{truncateString(filterValue.value, maxLength)}
|
|
180
|
-
</span>
|
|
181
|
-
);
|
|
178
|
+
return truncateString(filterValue.value, maxLength);
|
|
182
179
|
|
|
183
180
|
case 'integer':
|
|
184
181
|
case 'float':
|
|
185
|
-
return (
|
|
186
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
187
|
-
{filterValue.value}
|
|
188
|
-
</span>
|
|
189
|
-
);
|
|
182
|
+
return String(filterValue.value);
|
|
190
183
|
|
|
191
184
|
case 'enum':
|
|
192
185
|
if (operatorValue.type === 'enum') {
|
|
193
|
-
return (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
getEnumLabel(operatorValue.values, filterValue.value),
|
|
197
|
-
maxLength,
|
|
198
|
-
)}
|
|
199
|
-
</span>
|
|
186
|
+
return truncateString(
|
|
187
|
+
getEnumLabel(operatorValue.values, filterValue.value),
|
|
188
|
+
maxLength,
|
|
200
189
|
);
|
|
201
190
|
}
|
|
202
|
-
return (
|
|
203
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
204
|
-
{truncateString(filterValue.value, maxLength)}
|
|
205
|
-
</span>
|
|
206
|
-
);
|
|
191
|
+
return truncateString(filterValue.value, maxLength);
|
|
207
192
|
|
|
208
193
|
case 'string_list': {
|
|
209
194
|
const items = filterValue.value;
|
|
@@ -211,25 +196,13 @@ function PowerSearchTokenValue({
|
|
|
211
196
|
return null;
|
|
212
197
|
}
|
|
213
198
|
if (items.length === 1) {
|
|
214
|
-
return (
|
|
215
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
216
|
-
{truncateString(items[0], maxLength)}
|
|
217
|
-
</span>
|
|
218
|
-
);
|
|
199
|
+
return truncateString(items[0], maxLength);
|
|
219
200
|
}
|
|
220
201
|
const totalLength = items.reduce((sum, s) => sum + s.length, 0);
|
|
221
202
|
if (totalLength > maxLength) {
|
|
222
|
-
return
|
|
223
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
224
|
-
{items.length} items
|
|
225
|
-
</span>
|
|
226
|
-
);
|
|
203
|
+
return `${items.length} items`;
|
|
227
204
|
}
|
|
228
|
-
return (
|
|
229
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
230
|
-
{items.join(', ')}
|
|
231
|
-
</span>
|
|
232
|
-
);
|
|
205
|
+
return items.join(', ');
|
|
233
206
|
}
|
|
234
207
|
|
|
235
208
|
case 'enum_list': {
|
|
@@ -240,38 +213,18 @@ function PowerSearchTokenValue({
|
|
|
240
213
|
if (operatorValue.type === 'enum_list') {
|
|
241
214
|
const labels = items.map(v => getEnumLabel(operatorValue.values, v));
|
|
242
215
|
if (labels.length === 1) {
|
|
243
|
-
return (
|
|
244
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
245
|
-
{truncateString(labels[0], maxLength)}
|
|
246
|
-
</span>
|
|
247
|
-
);
|
|
216
|
+
return truncateString(labels[0], maxLength);
|
|
248
217
|
}
|
|
249
218
|
const totalLength = labels.reduce((sum, s) => sum + s.length, 0);
|
|
250
219
|
if (totalLength > maxLength) {
|
|
251
|
-
return
|
|
252
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
253
|
-
{labels.length} items
|
|
254
|
-
</span>
|
|
255
|
-
);
|
|
220
|
+
return `${labels.length} items`;
|
|
256
221
|
}
|
|
257
|
-
return (
|
|
258
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
259
|
-
{labels.join(', ')}
|
|
260
|
-
</span>
|
|
261
|
-
);
|
|
222
|
+
return labels.join(', ');
|
|
262
223
|
}
|
|
263
224
|
if (items.length === 1) {
|
|
264
|
-
return (
|
|
265
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
266
|
-
{truncateString(items[0], maxLength)}
|
|
267
|
-
</span>
|
|
268
|
-
);
|
|
225
|
+
return truncateString(items[0], maxLength);
|
|
269
226
|
}
|
|
270
|
-
return
|
|
271
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
272
|
-
{items.length} items
|
|
273
|
-
</span>
|
|
274
|
-
);
|
|
227
|
+
return `${items.length} items`;
|
|
275
228
|
}
|
|
276
229
|
|
|
277
230
|
case 'entity_list': {
|
|
@@ -280,80 +233,42 @@ function PowerSearchTokenValue({
|
|
|
280
233
|
return null;
|
|
281
234
|
}
|
|
282
235
|
if (entities.length === 1) {
|
|
283
|
-
return (
|
|
284
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
285
|
-
{truncateString(entities[0].label, maxLength)}
|
|
286
|
-
</span>
|
|
287
|
-
);
|
|
236
|
+
return truncateString(entities[0].label, maxLength);
|
|
288
237
|
}
|
|
289
238
|
const totalLength = entities.reduce((sum, e) => sum + e.label.length, 0);
|
|
290
239
|
if (totalLength > maxLength) {
|
|
291
|
-
return
|
|
292
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
293
|
-
{entities.length} items
|
|
294
|
-
</span>
|
|
295
|
-
);
|
|
240
|
+
return `${entities.length} items`;
|
|
296
241
|
}
|
|
297
|
-
return (
|
|
298
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
299
|
-
{entities.map(e => e.label).join(', ')}
|
|
300
|
-
</span>
|
|
301
|
-
);
|
|
242
|
+
return entities.map(e => e.label).join(', ');
|
|
302
243
|
}
|
|
303
244
|
|
|
304
245
|
case 'time':
|
|
305
|
-
return
|
|
306
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
307
|
-
{filterValue.value}
|
|
308
|
-
</span>
|
|
309
|
-
);
|
|
246
|
+
return filterValue.value;
|
|
310
247
|
|
|
311
|
-
case 'date_absolute':
|
|
312
|
-
|
|
313
|
-
filterValue.unixSeconds,
|
|
314
|
-
|
|
248
|
+
case 'date_absolute':
|
|
249
|
+
return truncateString(
|
|
250
|
+
formatDateAbsoluteCompact(filterValue.unixSeconds, locale),
|
|
251
|
+
maxLength,
|
|
315
252
|
);
|
|
316
|
-
return (
|
|
317
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
318
|
-
{truncateString(formatted, maxLength)}
|
|
319
|
-
</span>
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
253
|
|
|
323
254
|
case 'date_relative':
|
|
324
|
-
return (
|
|
325
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
326
|
-
{truncateString(filterValue.value, maxLength)}
|
|
327
|
-
</span>
|
|
328
|
-
);
|
|
255
|
+
return truncateString(filterValue.value, maxLength);
|
|
329
256
|
|
|
330
257
|
case 'date_range':
|
|
331
|
-
return
|
|
258
|
+
return 'date range';
|
|
332
259
|
|
|
333
260
|
case 'custom':
|
|
334
261
|
if (operatorValue.type === 'custom') {
|
|
335
|
-
return (
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
operatorValue.getString(filterValue.value),
|
|
339
|
-
maxLength,
|
|
340
|
-
)}
|
|
341
|
-
</span>
|
|
262
|
+
return truncateString(
|
|
263
|
+
operatorValue.getString(filterValue.value),
|
|
264
|
+
maxLength,
|
|
342
265
|
);
|
|
343
266
|
}
|
|
344
|
-
return
|
|
345
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
346
|
-
{filterValue.value}
|
|
347
|
-
</span>
|
|
348
|
-
);
|
|
267
|
+
return filterValue.value;
|
|
349
268
|
|
|
350
269
|
case 'nested': {
|
|
351
270
|
const count = filterValue.value.length;
|
|
352
|
-
return
|
|
353
|
-
<span {...stylex.props(tokenValueStyles.value)}>
|
|
354
|
-
{count === 1 ? '1 filter' : `${count} filters`}
|
|
355
|
-
</span>
|
|
356
|
-
);
|
|
271
|
+
return count === 1 ? '1 filter' : `${count} filters`;
|
|
357
272
|
}
|
|
358
273
|
|
|
359
274
|
default:
|
|
@@ -361,6 +276,10 @@ function PowerSearchTokenValue({
|
|
|
361
276
|
}
|
|
362
277
|
}
|
|
363
278
|
|
|
279
|
+
function PowerSearchTokenValue({text}: {text: string}) {
|
|
280
|
+
return <span {...stylex.props(tokenValueStyles.value)}>{text}</span>;
|
|
281
|
+
}
|
|
282
|
+
|
|
364
283
|
// =============================================================================
|
|
365
284
|
// Types
|
|
366
285
|
// =============================================================================
|
|
@@ -491,6 +410,20 @@ type PopoverState =
|
|
|
491
410
|
// Component
|
|
492
411
|
// =============================================================================
|
|
493
412
|
|
|
413
|
+
/**
|
|
414
|
+
* Whether a press on a token with this operator commits the filter instead of
|
|
415
|
+
* opening its editor: an `empty` operator with no `empty` Editor override.
|
|
416
|
+
*/
|
|
417
|
+
function commitsWithoutEditor(
|
|
418
|
+
operator: PowerSearchOperator | undefined,
|
|
419
|
+
componentOverrides: PowerSearchComponents | undefined,
|
|
420
|
+
): boolean {
|
|
421
|
+
return (
|
|
422
|
+
operator?.value.type === 'empty' &&
|
|
423
|
+
componentOverrides?.empty?.Editor == null
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
494
427
|
/**
|
|
495
428
|
* Structured filter bar where each token represents a filter (field + operator + value).
|
|
496
429
|
*
|
|
@@ -608,6 +541,47 @@ export function PowerSearch({
|
|
|
608
541
|
type: 'idle',
|
|
609
542
|
});
|
|
610
543
|
|
|
544
|
+
// A default filter token's visible text: its label (field and operator)
|
|
545
|
+
// and its value. The token's remove button and editor dialog are named by
|
|
546
|
+
// the same text.
|
|
547
|
+
const tokenText = useCallback(
|
|
548
|
+
(filter: PowerSearchFilter) => {
|
|
549
|
+
const field = config.getField(filter.field);
|
|
550
|
+
const operator = config.getOperator(filter.field, filter.operator);
|
|
551
|
+
const fieldLabel = field?.label ?? '';
|
|
552
|
+
const operatorLabel = operator ? resolveOperatorLabel(operator, t) : '';
|
|
553
|
+
const label = `${fieldLabel}: ${operatorLabel}`.trim();
|
|
554
|
+
const value = operator
|
|
555
|
+
? tokenValueText(
|
|
556
|
+
operator.value,
|
|
557
|
+
filter.value,
|
|
558
|
+
Math.max(
|
|
559
|
+
maxTokenLength - fieldLabel.length - operatorLabel.length,
|
|
560
|
+
10,
|
|
561
|
+
),
|
|
562
|
+
locale,
|
|
563
|
+
)
|
|
564
|
+
: null;
|
|
565
|
+
return {label, value, full: value != null ? `${label} ${value}` : label};
|
|
566
|
+
},
|
|
567
|
+
[config, maxTokenLength, t, locale],
|
|
568
|
+
);
|
|
569
|
+
// The built-in token being edited. A token from a `components` override
|
|
570
|
+
// names itself, so its editor keeps the plain popover.
|
|
571
|
+
const editedFilter =
|
|
572
|
+
popoverState.type === 'editing'
|
|
573
|
+
? filters[popoverState.filterIndex]
|
|
574
|
+
: undefined;
|
|
575
|
+
const editedOperator =
|
|
576
|
+
editedFilter != null
|
|
577
|
+
? config.getOperator(editedFilter.field, editedFilter.operator)
|
|
578
|
+
: undefined;
|
|
579
|
+
const editedBuiltInFilter =
|
|
580
|
+
editedOperator == null ||
|
|
581
|
+
componentOverrides?.[editedOperator.value.type]?.Token == null
|
|
582
|
+
? editedFilter
|
|
583
|
+
: undefined;
|
|
584
|
+
|
|
611
585
|
// Layer for positioning the edit popover anchored to the tokenizer
|
|
612
586
|
const handleLayerHide = useCallback(() => {
|
|
613
587
|
setPopoverStateRaw({type: 'idle'});
|
|
@@ -618,9 +592,16 @@ export function PowerSearch({
|
|
|
618
592
|
hasLightDismiss: true,
|
|
619
593
|
hasCloseButton: false,
|
|
620
594
|
hasAutoFocus: false,
|
|
621
|
-
//
|
|
622
|
-
// stays on the tokenizer input, so a
|
|
623
|
-
|
|
595
|
+
// Adding: the popup's own listbox/menu content is the exposed semantics,
|
|
596
|
+
// and focus stays on the tokenizer input, so a dialog wrapper is
|
|
597
|
+
// incorrect. Editing a token: focus moves into the editor's controls, so
|
|
598
|
+
// the editor is a non-modal dialog named by the token's visible text.
|
|
599
|
+
role: editedBuiltInFilter != null ? 'dialog' : 'none',
|
|
600
|
+
isModal: false,
|
|
601
|
+
dialogLabel:
|
|
602
|
+
editedBuiltInFilter != null
|
|
603
|
+
? tokenText(editedBuiltInFilter).full
|
|
604
|
+
: undefined,
|
|
624
605
|
});
|
|
625
606
|
|
|
626
607
|
// Wrapper that manages layer visibility and tokenizer focus alongside state
|
|
@@ -789,10 +770,7 @@ export function PowerSearch({
|
|
|
789
770
|
// edit: commit it as that editor's save would, the way adding one does,
|
|
790
771
|
// instead of opening it. A consumer's `empty` Editor override still opens.
|
|
791
772
|
const operator = config.getOperator(filter.field, filter.operator);
|
|
792
|
-
if (
|
|
793
|
-
operator?.value.type === 'empty' &&
|
|
794
|
-
componentOverrides?.empty?.Editor == null
|
|
795
|
-
) {
|
|
773
|
+
if (commitsWithoutEditor(operator, componentOverrides)) {
|
|
796
774
|
const newFilters = [...filters];
|
|
797
775
|
newFilters[index] = {
|
|
798
776
|
field: filter.field,
|
|
@@ -897,20 +875,16 @@ export function PowerSearch({
|
|
|
897
875
|
// Default token rendering
|
|
898
876
|
const fieldLabel = field?.label ?? '';
|
|
899
877
|
const operatorLabel = operator ? resolveOperatorLabel(operator, t) : '';
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
878
|
+
const text =
|
|
879
|
+
filter != null
|
|
880
|
+
? tokenText(filter)
|
|
881
|
+
: {label: `${fieldLabel}: ${operatorLabel}`.trim(), value: null};
|
|
882
|
+
const tokenLabel = text.label;
|
|
883
|
+
const fullText =
|
|
884
|
+
text.value != null ? `${text.label} ${text.value}` : text.label;
|
|
906
885
|
const valueContent =
|
|
907
|
-
|
|
908
|
-
<PowerSearchTokenValue
|
|
909
|
-
operatorValue={operator.value}
|
|
910
|
-
filterValue={filter.value}
|
|
911
|
-
maxLength={adjustedMaxLength}
|
|
912
|
-
locale={locale}
|
|
913
|
-
/>
|
|
886
|
+
text.value != null ? (
|
|
887
|
+
<PowerSearchTokenValue text={text.value} />
|
|
914
888
|
) : undefined;
|
|
915
889
|
|
|
916
890
|
// Show entity photo as token icon for single-entity filters
|
|
@@ -924,32 +898,49 @@ export function PowerSearch({
|
|
|
924
898
|
tokenIcon = <Avatar src={entity.photo} name={entity.label} size={16} />;
|
|
925
899
|
}
|
|
926
900
|
|
|
901
|
+
// A token that opens its editor says that it opens a dialog, and the
|
|
902
|
+
// naming context names it by all its visible text.
|
|
903
|
+
const editorProps =
|
|
904
|
+
handleClick != null &&
|
|
905
|
+
!commitsWithoutEditor(operator, componentOverrides)
|
|
906
|
+
? {
|
|
907
|
+
'aria-haspopup': 'dialog' as const,
|
|
908
|
+
'aria-expanded':
|
|
909
|
+
popoverState.type === 'editing' &&
|
|
910
|
+
popoverState.filterIndex === filterIndex,
|
|
911
|
+
}
|
|
912
|
+
: null;
|
|
913
|
+
|
|
927
914
|
return (
|
|
928
|
-
<
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
915
|
+
<TokenNamingContext
|
|
916
|
+
value={{actionName: fullText, removeName: fullText}}>
|
|
917
|
+
<Token
|
|
918
|
+
label={tokenLabel}
|
|
919
|
+
size={size}
|
|
920
|
+
icon={tokenIcon}
|
|
921
|
+
{...editorProps}
|
|
922
|
+
endContent={valueContent}
|
|
923
|
+
onMouseDown={
|
|
924
|
+
handleClick
|
|
925
|
+
? e => {
|
|
926
|
+
// Keep a token press from entering Tokenizer's input-focus
|
|
927
|
+
// redirect. Click still edits/removes; Tab entry is unchanged.
|
|
928
|
+
e.preventDefault();
|
|
929
|
+
}
|
|
930
|
+
: undefined
|
|
931
|
+
}
|
|
932
|
+
onClick={
|
|
933
|
+
handleClick
|
|
934
|
+
? (e: React.MouseEvent) => {
|
|
935
|
+
e.stopPropagation();
|
|
936
|
+
handleClick();
|
|
937
|
+
}
|
|
938
|
+
: undefined
|
|
939
|
+
}
|
|
940
|
+
onRemove={handleRemove}
|
|
941
|
+
isDisabled={isDisabled}
|
|
942
|
+
/>
|
|
943
|
+
</TokenNamingContext>
|
|
953
944
|
);
|
|
954
945
|
},
|
|
955
946
|
[
|
|
@@ -957,13 +948,14 @@ export function PowerSearch({
|
|
|
957
948
|
config,
|
|
958
949
|
configProp,
|
|
959
950
|
maxTokenLength,
|
|
960
|
-
locale,
|
|
961
951
|
size,
|
|
962
952
|
isReadOnly,
|
|
963
953
|
isDisabled,
|
|
964
954
|
handleTokenClick,
|
|
965
955
|
componentOverrides,
|
|
966
956
|
t,
|
|
957
|
+
popoverState,
|
|
958
|
+
tokenText,
|
|
967
959
|
],
|
|
968
960
|
);
|
|
969
961
|
|
package/src/Token/Token.tsx
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - /packages/cli/assets/templates/blocks/components/Token/ (showcase blocks)
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type
|
|
20
|
+
import {use, type ReactNode} from 'react';
|
|
21
21
|
import * as stylex from '@stylexjs/stylex';
|
|
22
22
|
import {
|
|
23
23
|
colorVars,
|
|
@@ -41,6 +41,7 @@ import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
|
41
41
|
import {usePressFeedback} from '../hooks/usePressFeedback';
|
|
42
42
|
import type {TokenColorMap} from './index';
|
|
43
43
|
import {disabledInteractionProps} from '../utils/disabledInteraction';
|
|
44
|
+
import {TokenNamingContext} from './TokenNamingContext';
|
|
44
45
|
import {
|
|
45
46
|
registerEnglishFallback,
|
|
46
47
|
useComponentTranslator,
|
|
@@ -309,6 +310,8 @@ export function Token({
|
|
|
309
310
|
'data-testid': testId,
|
|
310
311
|
'aria-labelledby': ariaLabelledBy,
|
|
311
312
|
'aria-describedby': ariaDescribedBy,
|
|
313
|
+
'aria-haspopup': ariaHasPopup,
|
|
314
|
+
'aria-expanded': ariaExpanded,
|
|
312
315
|
ref,
|
|
313
316
|
...rest
|
|
314
317
|
}: TokenProps) {
|
|
@@ -319,6 +322,7 @@ export function Token({
|
|
|
319
322
|
const t = useComponentTranslator();
|
|
320
323
|
const LinkComponent = useLinkComponent();
|
|
321
324
|
const role = useInteractiveRole({href, onClick, isDisabled});
|
|
325
|
+
const naming = use(TokenNamingContext);
|
|
322
326
|
|
|
323
327
|
// When role is 'button' via context (no explicit onClick), treat as
|
|
324
328
|
// if onClick was provided — the popover attaches its own handler.
|
|
@@ -327,7 +331,9 @@ export function Token({
|
|
|
327
331
|
const removeButton = onRemove != null && (
|
|
328
332
|
<button
|
|
329
333
|
type="button"
|
|
330
|
-
aria-label={t('@astryx.token.remove', {
|
|
334
|
+
aria-label={t('@astryx.token.remove', {
|
|
335
|
+
label: naming?.removeName ?? label,
|
|
336
|
+
})}
|
|
331
337
|
onClick={e => {
|
|
332
338
|
e.stopPropagation();
|
|
333
339
|
onRemove(e);
|
|
@@ -352,12 +358,14 @@ export function Token({
|
|
|
352
358
|
</>
|
|
353
359
|
);
|
|
354
360
|
|
|
355
|
-
// Naming inputs reach the element that owns the token's action:
|
|
356
|
-
// button of a clickable token, otherwise the root
|
|
361
|
+
// Naming and popup inputs reach the element that owns the token's action:
|
|
362
|
+
// the inner button of a clickable token, otherwise the root
|
|
357
363
|
// (architecture:public-component-api INV5).
|
|
358
364
|
const labellingProps = {
|
|
359
365
|
'aria-labelledby': ariaLabelledBy,
|
|
360
366
|
'aria-describedby': ariaDescribedBy,
|
|
367
|
+
'aria-haspopup': ariaHasPopup,
|
|
368
|
+
'aria-expanded': ariaExpanded,
|
|
361
369
|
};
|
|
362
370
|
|
|
363
371
|
const sharedProps = {
|
|
@@ -532,6 +540,7 @@ export function Token({
|
|
|
532
540
|
<button
|
|
533
541
|
type="button"
|
|
534
542
|
onClick={effectiveOnClick}
|
|
543
|
+
aria-label={ariaLabelledBy == null ? naming?.actionName : undefined}
|
|
535
544
|
{...labellingProps}
|
|
536
545
|
aria-disabled={isDisabled || undefined}
|
|
537
546
|
{...(isDisabled ? disabledInteractionProps() : null)}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file TokenNamingContext.ts
|
|
7
|
+
* @input Naming parts from a caller whose token value renders outside its label
|
|
8
|
+
* @output Internal context: the names of a token's action and remove button
|
|
9
|
+
* @position Internal to Core; not exported from the Token entry point
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {createContext} from 'react';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Names for a Token whose visible text runs past its label. PowerSearch
|
|
16
|
+
* provides them: its tokens show the value as `endContent`.
|
|
17
|
+
*
|
|
18
|
+
* - `actionName` names a clickable token's inner button in place of `label`.
|
|
19
|
+
* - `removeName` names the remove button in place of `label`. The remove word
|
|
20
|
+
* still comes from the catalog, so each locale keeps its word order.
|
|
21
|
+
*/
|
|
22
|
+
export interface TokenNaming {
|
|
23
|
+
actionName?: string;
|
|
24
|
+
removeName?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const TokenNamingContext = createContext<TokenNaming | null>(null);
|
|
28
|
+
TokenNamingContext.displayName = 'TokenNamingContext';
|