@astryxdesign/core 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/Avatar/Avatar.d.ts.map +1 -1
  3. package/dist/Avatar/Avatar.js +3 -19
  4. package/dist/Banner/Banner.d.ts +6 -2
  5. package/dist/Banner/Banner.d.ts.map +1 -1
  6. package/dist/Banner/Banner.js +71 -30
  7. package/dist/Banner/index.d.ts +14 -12
  8. package/dist/Banner/index.d.ts.map +1 -1
  9. package/dist/Banner/index.js +10 -8
  10. package/dist/Chat/ChatTokenizedText.js +1 -1
  11. package/dist/ComplexSelector/ComplexSelector.d.ts +38 -4
  12. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  13. package/dist/ComplexSelector/ComplexSelector.js +96 -35
  14. package/dist/ComplexSelector/index.d.ts +2 -2
  15. package/dist/ComplexSelector/index.d.ts.map +1 -1
  16. package/dist/ComplexSelector/index.js +1 -1
  17. package/dist/Markdown/parser.d.ts.map +1 -1
  18. package/dist/Markdown/parser.js +55 -12
  19. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  20. package/dist/PowerSearch/PowerSearch.js +4 -1
  21. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  22. package/dist/PowerSearch/formatFilterValue.js +2 -4
  23. package/dist/Table/columnUtils.d.ts.map +1 -1
  24. package/dist/Table/columnUtils.js +4 -1
  25. package/dist/TextArea/TextArea.d.ts +6 -3
  26. package/dist/TextArea/TextArea.d.ts.map +1 -1
  27. package/dist/TextArea/TextArea.js +17 -6
  28. package/dist/TreeList/TreeList.js +2 -1
  29. package/dist/astryx.css +6 -4
  30. package/dist/astryx.umd.js +51 -51
  31. package/dist/astryx.umd.js.map +4 -4
  32. package/dist/theme/defineTheme.d.ts +8 -3
  33. package/dist/theme/defineTheme.d.ts.map +1 -1
  34. package/dist/theme/defineTheme.js +36 -47
  35. package/dist/theme/mergeComponents.d.ts +20 -0
  36. package/dist/theme/mergeComponents.d.ts.map +1 -0
  37. package/dist/theme/mergeComponents.js +56 -0
  38. package/dist/theme/onMediaTokens.d.ts +6 -1
  39. package/dist/theme/onMediaTokens.d.ts.map +1 -1
  40. package/dist/theme/onMediaTokens.js +11 -3
  41. package/dist/utils/characters.d.ts +27 -0
  42. package/dist/utils/characters.d.ts.map +1 -0
  43. package/dist/utils/characters.js +83 -0
  44. package/dist/utils/index.d.ts +1 -0
  45. package/dist/utils/index.d.ts.map +1 -1
  46. package/dist/utils/index.js +1 -0
  47. package/package.json +2 -2
  48. package/src/Avatar/Avatar.test.tsx +6 -1
  49. package/src/Avatar/Avatar.tsx +3 -21
  50. package/src/Banner/Banner.doc.mjs +9 -7
  51. package/src/Banner/Banner.test.tsx +68 -0
  52. package/src/Banner/Banner.tsx +97 -36
  53. package/src/Banner/index.ts +15 -13
  54. package/src/Chat/ChatTokenizedText.tsx +1 -1
  55. package/src/ComplexSelector/ComplexSelector.doc.mjs +55 -6
  56. package/src/ComplexSelector/ComplexSelector.test.tsx +197 -6
  57. package/src/ComplexSelector/ComplexSelector.tsx +153 -32
  58. package/src/ComplexSelector/index.ts +3 -1
  59. package/src/Markdown/parser.test.ts +53 -0
  60. package/src/Markdown/parser.ts +53 -12
  61. package/src/PowerSearch/PowerSearch.test.tsx +48 -3
  62. package/src/PowerSearch/PowerSearch.tsx +4 -1
  63. package/src/PowerSearch/formatFilterValue.test.ts +22 -0
  64. package/src/PowerSearch/formatFilterValue.ts +2 -4
  65. package/src/Table/Table.test.tsx +6 -0
  66. package/src/Table/columnUtils.ts +3 -1
  67. package/src/TextArea/TextArea.doc.mjs +1 -1
  68. package/src/TextArea/TextArea.test.tsx +72 -0
  69. package/src/TextArea/TextArea.tsx +26 -8
  70. package/src/TreeList/TreeList.doc.mjs +2 -2
  71. package/src/TreeList/TreeList.tsx +1 -1
  72. package/src/theme/defineTheme.test.ts +127 -0
  73. package/src/theme/defineTheme.ts +48 -51
  74. package/src/theme/extensibleAxes.test.ts +365 -0
  75. package/src/theme/mergeComponents.ts +59 -0
  76. package/src/theme/onMediaTokens.ts +9 -2
  77. package/src/utils/characters.test.ts +141 -0
  78. package/src/utils/characters.ts +88 -0
  79. package/src/utils/index.ts +2 -0
@@ -4,8 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file ComplexSelector.tsx
7
- * @input Uses React, StyleX, Field, usePopover
8
- * @output Exports ComplexSelector component for custom selector surfaces
7
+ * @input Uses React, StyleX, Field, Icon slots, Layer positioning, and usePopover
8
+ * @output Exports a rich-selector shell with exact token-sized input and ghost triggers, plus an imperative open/close handle
9
9
  * @position Core implementation; consumed by index.ts
10
10
  *
11
11
  * SYNC: When modified, update:
@@ -13,17 +13,18 @@
13
13
  * - /packages/core/src/ComplexSelector/ComplexSelector.test.tsx
14
14
  * - /packages/core/src/ComplexSelector/index.ts
15
15
  * - /apps/storybook/stories/ComplexSelector.stories.tsx
16
+ * - /packages/cli/assets/templates/blocks/components/ComplexSelector/ (showcase blocks)
16
17
  */
17
- import React, { useCallback, useId, useOptimistic, useTransition } from 'react';
18
+ import React, { useCallback, useId, useImperativeHandle, useOptimistic, useRef, useTransition } from 'react';
18
19
  import * as stylex from '@stylexjs/stylex';
19
20
  import { Field, inputWrapperStyles } from "../Field/index.js";
20
- import { Icon } from "../Icon/index.js";
21
+ import { Icon, renderIconSlot } from "../Icon/index.js";
21
22
  import { Spinner } from "../Spinner/index.js";
22
23
  import { useTranslator } from "../i18n/index.js";
23
24
  import { layerAnimations } from "../Layer/layerAnimations.stylex.js";
24
25
  import { usePopover } from "../Popover/usePopover.js";
25
26
  import "../theme/tokens.stylex.js";
26
- import { colorVars, durationVars, easeVars, radiusVars, sizeVars, spacingVars, typographyVars, typeScaleVars } from "../theme/tokens.stylex.js";
27
+ import { colorVars, durationVars, easeVars, fontWeightVars, radiusVars, sizeVars, spacingVars, typographyVars, typeScaleVars } from "../theme/tokens.stylex.js";
27
28
  import { mergeProps } from "../utils/index.js";
28
29
  import { composeEventHandlers } from "../utils/composeEventHandlers.js";
29
30
  import { focusOutlineStyles } from "../utils/focusOutline.stylex.js";
@@ -82,6 +83,22 @@ const styles = {
82
83
  kMwMTN: "xv1l7n4",
83
84
  $$css: true
84
85
  },
86
+ triggerGhost: {
87
+ kzqmXN: "x14atkfc",
88
+ kMzoRj: "xc342km",
89
+ kWkggS: "xjbqb8w",
90
+ kKwaWg: "x1ilzqfv xq8i9tn",
91
+ kGVxlE: "x1gnnqk1 x1irk71m x15ysqaf",
92
+ k63SB2: "x1e4wzip",
93
+ k1ekBW: "xrafxwg",
94
+ k3aq6I: "x3oybdh xk4oym4",
95
+ $$css: true
96
+ },
97
+ triggerGhostDisabled: {
98
+ kKwaWg: "x18o3ruo",
99
+ k3aq6I: "x1c071of x1pdlv7q",
100
+ $$css: true
101
+ },
85
102
  triggerIcon: {
86
103
  kmuXW: "x2lah0s",
87
104
  $$css: true
@@ -109,15 +126,15 @@ const styles = {
109
126
  $$css: true
110
127
  },
111
128
  sm: {
112
- kAzted: "x1gdqauq",
129
+ kZKoxP: "x6k0iem",
113
130
  $$css: true
114
131
  },
115
132
  md: {
116
- kAzted: "x1uogy3y",
133
+ kZKoxP: "x1ueg155",
117
134
  $$css: true
118
135
  },
119
136
  lg: {
120
- kAzted: "x1xf4nls",
137
+ kZKoxP: "xssyfek",
121
138
  $$css: true
122
139
  },
123
140
  disabled: {
@@ -125,6 +142,16 @@ const styles = {
125
142
  $$css: true
126
143
  }
127
144
  };
145
+
146
+ /**
147
+ * Imperative control surface for ComplexSelector, accessed via the `handleRef`
148
+ * prop. Methods drive the same popover machinery as the built-in trigger, so
149
+ * they respect focus restoration, light dismiss, and Escape. Prefer these
150
+ * callbacks over mirroring open state in the parent — the selector owns its
151
+ * visibility, and imperative calls avoid the focus-management pitfalls of
152
+ * syncing an external `isOpen` prop.
153
+ */
154
+
128
155
  /**
129
156
  * A selector shell for rich, custom selection surfaces.
130
157
  *
@@ -170,8 +197,12 @@ export function ComplexSelector({
170
197
  statusVariant = 'attached',
171
198
  labelTooltip,
172
199
  size = 'md',
200
+ variant = 'input',
201
+ startIcon,
173
202
  width,
174
203
  placement = 'below',
204
+ alignment = 'start',
205
+ handleRef,
175
206
  contentXstyle,
176
207
  xstyle,
177
208
  className,
@@ -182,28 +213,62 @@ export function ComplexSelector({
182
213
  }) {
183
214
  const t = useTranslator();
184
215
  const placeholder = placeholderFromProps ?? t('@astryx.selector.placeholder');
216
+ const effectiveStatusVariant = variant === 'ghost' && statusVariant === 'attached' ? 'detached' : statusVariant;
185
217
  const triggerId = useId();
186
218
  const labelId = useId();
187
219
  const contentId = useId();
188
220
  const descriptionId = useId();
189
221
  const statusMessageId = useId();
190
222
  const ariaDescribedBy = [description ? descriptionId : null, status?.message ? statusMessageId : null].filter(id => id != null).join(' ') || undefined;
223
+ const triggerRef = useRef(null);
224
+ const lastHideTimeRef = useRef(0);
191
225
  const [isPending, startTransition] = useTransition();
192
226
  const [optimisticValue, setOptimisticValue] = useOptimistic(value);
193
227
  const isBusy = isLoading || isPending;
228
+ const handlePopoverHide = useCallback(() => {
229
+ lastHideTimeRef.current = Date.now();
230
+ triggerRef.current?.focus();
231
+ }, []);
194
232
  const popover = usePopover({
195
233
  dialogLabel: label,
196
234
  hasCloseButton: false,
197
235
  hasAutoFocus: true,
198
- // The popup's theme target belongs on the SURFACE — the element painting
199
- // background, radius and elevation — which `usePopover` owns. Rendered on
200
- // the content box below it, a theme's background or radius rule paints the
201
- // wrong box.
202
236
  surfaceTarget: 'complex-selector-popup',
203
- onHide: () => {
204
- document.getElementById(triggerId)?.focus();
205
- }
237
+ onHide: handlePopoverHide
206
238
  });
239
+ const isOpen = popover.isOpen;
240
+ const handleTriggerClick = useCallback(() => {
241
+ if (isDisabled || Date.now() - lastHideTimeRef.current < 50) {
242
+ return;
243
+ }
244
+ if (popover.isOpen) {
245
+ popover.hide();
246
+ } else {
247
+ popover.show();
248
+ }
249
+ }, [isDisabled, popover]);
250
+ const close = useCallback(() => {
251
+ popover.hide();
252
+ }, [popover]);
253
+ useImperativeHandle(handleRef, () => ({
254
+ open: () => {
255
+ if (!isDisabled) {
256
+ popover.show();
257
+ }
258
+ },
259
+ close: () => popover.hide(),
260
+ toggle: () => {
261
+ if (isDisabled) {
262
+ return;
263
+ }
264
+ if (popover.isOpen) {
265
+ popover.hide();
266
+ } else {
267
+ popover.show();
268
+ }
269
+ },
270
+ isOpen: () => popover.isOpen
271
+ }), [isDisabled, popover]);
207
272
  const commitValue = useCallback(nextValue => {
208
273
  onChange?.(nextValue);
209
274
  if (changeAction) {
@@ -217,8 +282,8 @@ export function ComplexSelector({
217
282
  const content = /*#__PURE__*/_jsx("div", {
218
283
  id: contentId,
219
284
  ...stylex.props(styles.content, contentXstyle),
220
- children: children(optimisticValue, commitValue, popover.hide, {
221
- isOpen: popover.isOpen,
285
+ children: children(optimisticValue, commitValue, close, {
286
+ isOpen,
222
287
  isBusy,
223
288
  triggerId,
224
289
  contentId
@@ -229,25 +294,21 @@ export function ComplexSelector({
229
294
  ref: popover.triggerRef,
230
295
  "data-testid": testId,
231
296
  ...props,
232
- onClick: composeEventHandlers(onClickProp, () => {
233
- if (!isDisabled) {
234
- popover.toggle();
235
- }
236
- }),
297
+ onClick: composeEventHandlers(onClickProp, handleTriggerClick),
237
298
  ...mergeProps(themeProps('complex-selector', {
299
+ variant,
238
300
  size,
239
301
  status: status?.type ?? null
240
- }), stylex.props(inputWrapperStyles.base, styles.triggerContainer, styles[size],
241
- // The ring belongs to the wrapper (the focusable `<button>` sits
242
- // inside it), but it must still be a KEYBOARD ring: `:focus-within`
243
- // matched a mouse click on the trigger and drew the outline for
244
- // pointer users too. `focusWithin` here is `:has(:focus-visible)`.
245
- focusOutlineStyles.focusWithin, isDisabled && inputWrapperStyles.disabled, isDisabled && styles.disabled, triggerLabel == null && styles.placeholder, xstyle), className, style),
246
- children: [/*#__PURE__*/_jsx("button", {
302
+ }), stylex.props(inputWrapperStyles.base, styles.triggerContainer, styles[size], variant === 'input' && focusOutlineStyles.focusWithin, variant === 'ghost' && styles.triggerGhost, variant === 'ghost' && focusOutlineStyles.focusWithin, isDisabled && inputWrapperStyles.disabled, variant === 'ghost' && isDisabled && styles.triggerGhostDisabled, isDisabled && styles.disabled, triggerLabel == null && styles.placeholder, xstyle), className, style),
303
+ children: [startIcon && renderIconSlot(startIcon, {
304
+ size: 'sm',
305
+ color: 'secondary'
306
+ }), /*#__PURE__*/_jsx("button", {
307
+ ref: triggerRef,
247
308
  id: triggerId,
248
309
  type: "button",
249
310
  "aria-haspopup": "dialog",
250
- "aria-expanded": popover.isOpen,
311
+ "aria-expanded": isOpen,
251
312
  "aria-controls": contentId,
252
313
  "aria-describedby": ariaDescribedBy,
253
314
  "aria-labelledby": labelId,
@@ -256,7 +317,7 @@ export function ComplexSelector({
256
317
  "aria-busy": isBusy || undefined,
257
318
  disabled: isDisabled,
258
319
  onKeyDown: event => {
259
- if (event.key === 'ArrowDown' && !popover.isOpen && !isDisabled) {
320
+ if (event.key === 'ArrowDown' && !isOpen && !isDisabled) {
260
321
  event.preventDefault();
261
322
  popover.show();
262
323
  }
@@ -281,14 +342,14 @@ export function ComplexSelector({
281
342
  // IS the trigger's icon element — one node carrying the box, the
282
343
  // color, the rotation, and the theme target.
283
344
  ,
284
- xstyle: [styles.triggerIcon, styles.triggerIconRotation, popover.isOpen && styles.triggerIconOpen],
345
+ xstyle: [styles.triggerIcon, styles.triggerIconRotation, isOpen && styles.triggerIconOpen],
285
346
  ...themeProps('complex-selector-indicator-icon', {
286
- state: popover.isOpen ? 'expanded' : 'collapsed'
347
+ state: isOpen ? 'expanded' : 'collapsed'
287
348
  })
288
349
  })]
289
350
  }), popover.render(content, {
290
351
  placement,
291
- alignment: 'start',
352
+ alignment,
292
353
  offset: spacingVars['--spacing-1'],
293
354
  xstyle: [styles.popover, layerAnimations[placement]]
294
355
  })]
@@ -308,7 +369,7 @@ export function ComplexSelector({
308
369
  message: status.message,
309
370
  messageID: status.message ? statusMessageId : undefined
310
371
  } : undefined,
311
- statusVariant: statusVariant,
372
+ statusVariant: effectiveStatusVariant,
312
373
  labelTooltip: labelTooltip,
313
374
  width: width,
314
375
  children: selectorContent
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file index.ts
3
- * @output Exports ComplexSelector and types
3
+ * @output Exports ComplexSelector and its trigger-size contract types
4
4
  * @position Public API entry point
5
5
  */
6
- export { ComplexSelector, type ComplexSelectorProps, type ComplexSelectorRenderState, type ComplexSelectorSize, type ComplexSelectorStatus, } from './ComplexSelector';
6
+ export { ComplexSelector, type ComplexSelectorHandle, type ComplexSelectorProps, type ComplexSelectorRenderState, type ComplexSelectorSize, type ComplexSelectorStatus, type ComplexSelectorVariant, } from './ComplexSelector';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ComplexSelector/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ComplexSelector/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,mBAAmB,CAAC"}
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file index.ts
7
- * @output Exports ComplexSelector and types
7
+ * @output Exports ComplexSelector and its trigger-size contract types
8
8
  * @position Public API entry point
9
9
  */
10
10
  export { ComplexSelector } from "./ComplexSelector.js";
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/Markdown/parser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/Markdown/parser.ts"],"names":[],"mappings":"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"}
@@ -203,7 +203,7 @@ function matchReferenceLink(text, start, linkDefs, opts) {
203
203
  // matches nothing.
204
204
  const label = rawLabel === '' ? linkText : rawLabel;
205
205
  const href = linkDefs.get(normalizeLinkLabel(label));
206
- if (href != null) {
206
+ if (href != null && isSafeUrl(href)) {
207
207
  return {
208
208
  node: {
209
209
  type: 'link',
@@ -222,7 +222,7 @@ function matchReferenceLink(text, start, linkDefs, opts) {
222
222
  return null;
223
223
  }
224
224
  const href = linkDefs.get(normalizeLinkLabel(linkText));
225
- if (href == null) {
225
+ if (href == null || !isSafeUrl(href)) {
226
226
  return null;
227
227
  }
228
228
  return {
@@ -304,6 +304,27 @@ function isWordChar(ch) {
304
304
  return /\w/.test(ch);
305
305
  }
306
306
 
307
+ // ---------------------------------------------------------------------------
308
+ // URL scheme sanitization
309
+ // ---------------------------------------------------------------------------
310
+
311
+ /**
312
+ * Reject URLs with dangerous schemes (javascript:, vbscript:, data:) that
313
+ * could execute arbitrary code when rendered as link hrefs or image srcs.
314
+ * Returns true if the URL is safe to use, false otherwise.
315
+ */
316
+ function isSafeUrl(url) {
317
+ // Trim and collapse whitespace/control chars that browsers tolerate but
318
+ // could bypass a naive prefix check (e.g. "java\nscript:alert(1)").
319
+ // eslint-disable-next-line no-control-regex -- control chars are the bypass
320
+ const normalized = url.replace(/[\x00-\x1f\x7f]/g, '').trim();
321
+ const lower = normalized.toLowerCase();
322
+ if (lower.startsWith('javascript:') || lower.startsWith('vbscript:') || lower.startsWith('data:text/html')) {
323
+ return false;
324
+ }
325
+ return true;
326
+ }
327
+
307
328
  // ---------------------------------------------------------------------------
308
329
  // Inline parser
309
330
  // ---------------------------------------------------------------------------
@@ -419,11 +440,20 @@ function parseInlineImpl(text, opts) {
419
440
  if (altClose !== -1 && text[altClose + 1] === '(') {
420
441
  const srcClose = findClosingParen(text, altClose + 2);
421
442
  if (srcClose !== -1) {
422
- nodes.push({
423
- type: 'image',
424
- src: text.slice(altClose + 2, srcClose),
425
- alt: text.slice(i + 2, altClose)
426
- });
443
+ const src = text.slice(altClose + 2, srcClose);
444
+ if (!isSafeUrl(src)) {
445
+ // Dangerous scheme emit as plain text.
446
+ nodes.push({
447
+ type: 'text',
448
+ content: text.slice(i, srcClose + 1)
449
+ });
450
+ } else {
451
+ nodes.push({
452
+ type: 'image',
453
+ src,
454
+ alt: text.slice(i + 2, altClose)
455
+ });
456
+ }
427
457
  i = srcClose + 1;
428
458
  continue;
429
459
  }
@@ -459,11 +489,20 @@ function parseInlineImpl(text, opts) {
459
489
  if (textClose !== -1 && text[textClose + 1] === '(') {
460
490
  const urlClose = findClosingParen(text, textClose + 2);
461
491
  if (urlClose !== -1) {
462
- nodes.push({
463
- type: 'link',
464
- href: text.slice(textClose + 2, urlClose),
465
- children: parseInlineImpl(text.slice(i + 1, textClose), opts)
466
- });
492
+ const href = text.slice(textClose + 2, urlClose);
493
+ if (!isSafeUrl(href)) {
494
+ // Dangerous scheme — emit as plain text instead of a link.
495
+ nodes.push({
496
+ type: 'text',
497
+ content: text.slice(i, urlClose + 1)
498
+ });
499
+ } else {
500
+ nodes.push({
501
+ type: 'link',
502
+ href,
503
+ children: parseInlineImpl(text.slice(i + 1, textClose), opts)
504
+ });
505
+ }
467
506
  i = urlClose + 1;
468
507
  continue;
469
508
  }
@@ -690,6 +729,10 @@ function scanAutolinksInText(text) {
690
729
  let m;
691
730
  while ((m = re.exec(text)) !== null) {
692
731
  const url = m[1];
732
+ // Skip dangerous URL schemes (javascript:, vbscript:, data:text/html)
733
+ if (!isSafeUrl(url)) {
734
+ continue;
735
+ }
693
736
  matches.push({
694
737
  start: m.index,
695
738
  end: m.index + m[0].length,
@@ -1 +1 @@
1
- {"version":3,"file":"PowerSearch.d.ts","sourceRoot":"","sources":["../../src/PowerSearch/PowerSearch.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PowerSearch.d.ts","sourceRoot":"","sources":["../../src/PowerSearch/PowerSearch.tsx"],"names":[],"mappings":"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"}
@@ -33,6 +33,7 @@ import { formatFilterValue } from "./formatFilterValue.js";
33
33
  import { PowerSearchEditPopover } from "./PowerSearchEditPopover.js";
34
34
  import { resolveOperatorLabel } from "./resolveOperatorLabel.js";
35
35
  import { themeProps } from "../utils/themeProps.js";
36
+ import { truncateCharacters } from "../utils/characters.js";
36
37
  import { useTranslator } from "../i18n/index.js";
37
38
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
38
39
  // =============================================================================
@@ -68,7 +69,9 @@ const popoverLayerStyles = {
68
69
  }
69
70
  };
70
71
  function truncateString(value, limit) {
71
- return value.length > limit + 3 ? value.slice(0, limit) + '...' : value;
72
+ // Same semantics as before strings within limit + 3 pass through, longer
73
+ // ones cut to limit + '...' — but counted in characters, not code units.
74
+ return truncateCharacters(value, limit + 3, '...');
72
75
  }
73
76
  function getEnumLabel(values, value) {
74
77
  return values.find(v => v.value === value)?.label ?? value;
@@ -1 +1 @@
1
- {"version":3,"file":"formatFilterValue.d.ts","sourceRoot":"","sources":["../../src/PowerSearch/formatFilterValue.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAE,WAAW,EAAW,MAAM,SAAS,CAAC;AAClE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AA8C1C,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,MAAM,EACjB,CAAC,EAAE,YAAY,EACf,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAsHR"}
1
+ {"version":3,"file":"formatFilterValue.d.ts","sourceRoot":"","sources":["../../src/PowerSearch/formatFilterValue.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAE,WAAW,EAAW,MAAM,SAAS,CAAC;AAClE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AA4C1C,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,MAAM,EACjB,CAAC,EAAE,YAAY,EACf,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAsHR"}
@@ -10,11 +10,9 @@
10
10
  * - /packages/core/src/PowerSearch/index.ts
11
11
  */
12
12
 
13
+ import { truncateCharacters } from "../utils/characters.js";
13
14
  function truncate(str, maxLength) {
14
- if (str.length <= maxLength) {
15
- return str;
16
- }
17
- return str.slice(0, maxLength - 1) + '\u2026';
15
+ return truncateCharacters(str, maxLength);
18
16
  }
19
17
  function formatEnumLabel(value, enumValues) {
20
18
  const item = enumValues.find(v => v.value === value);
@@ -1 +1 @@
1
- {"version":3,"file":"columnUtils.d.ts","sourceRoot":"","sources":["../../src/Table/columnUtils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAE,SAAS,EAAC,MAAM,OAAO,CAAC;AACpD,OAAO,KAAK,EAAC,WAAW,EAAE,iBAAiB,EAAE,UAAU,EAAC,MAAM,SAAS,CAAC;AAExE,8DAA8D;AAC9D,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAM5C;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,2DAA2D;IAC3D,KAAK,EAAE,aAAa,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC1C,oGAAoG;IACpG,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,GACxB,oBAAoB,CAmEtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,KAAK,GAAE,MAAU,EACjB,OAAO,CAAC,EAAE;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5B,iBAAiB,CAMnB;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAE/C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK9C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,IAAI,EAAE,CAAC,EACP,GAAG,EAAE,MAAM,GACV,SAAS,CAiBX;AA+DD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,IAAI,EAAE,CAAC,EAAE,GACR,WAAW,CAAC,CAAC,CAAC,EAAE,CAmDlB"}
1
+ {"version":3,"file":"columnUtils.d.ts","sourceRoot":"","sources":["../../src/Table/columnUtils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAE,SAAS,EAAC,MAAM,OAAO,CAAC;AACpD,OAAO,KAAK,EAAC,WAAW,EAAE,iBAAiB,EAAE,UAAU,EAAC,MAAM,SAAS,CAAC;AAGxE,8DAA8D;AAC9D,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAM5C;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,2DAA2D;IAC3D,KAAK,EAAE,aAAa,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC1C,oGAAoG;IACpG,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,GACxB,oBAAoB,CAmEtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,KAAK,GAAE,MAAU,EACjB,OAAO,CAAC,EAAE;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5B,iBAAiB,CAMnB;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAE/C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,IAAI,EAAE,CAAC,EACP,GAAG,EAAE,MAAM,GACV,SAAS,CAiBX;AA+DD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,IAAI,EAAE,CAAC,EAAE,GACR,WAAW,CAAC,CAAC,CAAC,EAAE,CAmDlB"}
@@ -11,6 +11,8 @@
11
11
  * - /packages/core/src/Table/index.ts (exports if functions change)
12
12
  */
13
13
 
14
+ import { firstCharacter } from "../utils/characters.js";
15
+
14
16
  /** Default minimum width (in px) for proportional columns. */
15
17
  export const DEFAULT_MIN_COLUMN_WIDTH = 120;
16
18
 
@@ -153,7 +155,8 @@ export function capitalize(str) {
153
155
  if (str.length === 0) {
154
156
  return str;
155
157
  }
156
- return str.charAt(0).toUpperCase() + str.slice(1);
158
+ const first = firstCharacter(str);
159
+ return first.toUpperCase() + str.slice(first.length);
157
160
  }
158
161
 
159
162
  /**
@@ -149,10 +149,13 @@ export interface TextAreaProps extends Omit<BaseProps, 'onChange' | 'defaultValu
149
149
  */
150
150
  onPaste?: (e: ClipboardEvent<HTMLTextAreaElement>) => void;
151
151
  /**
152
- * Maximum number of characters allowed.
153
- * When set, displays a character counter below the textarea.
152
+ * Maximum number of characters allowed, counted as user-perceived
153
+ * characters an emoji or flag sequence counts as one. When set,
154
+ * displays a character counter below the textarea.
154
155
  * Does not enforce the limit natively — the counter shows error styling
155
- * when exceeded, and the consumer can validate via onChange.
156
+ * when exceeded, and the consumer can validate via onChange. Validate with
157
+ * `characterCount` (exported from this package) rather than `value.length`
158
+ * so enforcement matches the displayed count.
156
159
  */
157
160
  maxLength?: number;
158
161
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"TextArea.d.ts","sourceRoot":"","sources":["../../src/TextArea/TextArea.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TextArea.d.ts","sourceRoot":"","sources":["../../src/TextArea/TextArea.tsx"],"names":[],"mappings":"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"}
@@ -15,7 +15,7 @@
15
15
  * - /apps/storybook/stories/TextArea.stories.tsx (storybook stories)
16
16
  * - /packages/cli/assets/templates/blocks/components/TextArea/ (showcase blocks)
17
17
  */
18
- import { useId, useOptimistic, useTransition, useRef, useCallback } from 'react';
18
+ import { useId, useOptimistic, useTransition, useRef, useCallback, useMemo } from 'react';
19
19
  import * as stylex from '@stylexjs/stylex';
20
20
  import "../theme/tokens.stylex.js";
21
21
  import { colorVars, spacingVars, typographyVars, typeScaleVars } from "../theme/tokens.stylex.js";
@@ -29,6 +29,7 @@ import { useInputStatusIcon } from "../hooks/useInputStatusIcon.js";
29
29
  import { useAnnounce } from "../hooks/useAnnounce.js";
30
30
  import { useSize } from "../SizeContext/SizeContext.js";
31
31
  import { themeProps } from "../utils/themeProps.js";
32
+ import { characterCount } from "../utils/characters.js";
32
33
  import { useTranslator } from "../i18n/index.js";
33
34
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
34
35
  const COUNTER_WARNING_THRESHOLD = 0.8;
@@ -208,7 +209,11 @@ export function TextArea({
208
209
  return;
209
210
  }
210
211
  const newValue = e.target.value;
211
- announceCounter(newValue.length);
212
+ // Guarded here, not just inside announceCounter, so textareas without a
213
+ // maxLength never pay for segmenting the whole value on each keystroke.
214
+ if (maxLength != null) {
215
+ announceCounter(characterCount(newValue));
216
+ }
212
217
  onChange?.(newValue, e);
213
218
  if (changeAction && !e.defaultPrevented) {
214
219
  startTransition(async () => {
@@ -217,6 +222,12 @@ export function TextArea({
217
222
  });
218
223
  }
219
224
  };
225
+
226
+ // Counter semantics count user-perceived characters, so an emoji or flag
227
+ // sequence counts as one character, not its code units.
228
+ // Only measured when a counter exists — segmentation is O(value length),
229
+ // and memoized so re-renders that keep the same value skip it entirely.
230
+ const valueLength = useMemo(() => maxLength != null ? characterCount(optimisticValue) : 0, [maxLength, optimisticValue]);
220
231
  const effectivelyDisabled = isDisabled || isBusy;
221
232
 
222
233
  // Focus textarea when clicking anywhere on the wrapper (icons, padding, etc.)
@@ -293,7 +304,7 @@ export function TextArea({
293
304
  "data-autofocus": hasAutoFocus || undefined,
294
305
  "aria-describedby": ariaDescribedBy,
295
306
  "aria-required": isRequired && !isOptional ? 'true' : undefined,
296
- "aria-invalid": status?.type === 'error' || maxLength != null && optimisticValue.length > maxLength ? 'true' : undefined,
307
+ "aria-invalid": status?.type === 'error' || maxLength != null && valueLength > maxLength ? 'true' : undefined,
297
308
  "aria-busy": isBusy || undefined,
298
309
  ...stylex.props(styles.textarea, textareaSizeStyles[size], isDisabled && styles.textareaDisabled, Boolean(startIcon) && styles.textareaWithStartIcon,
299
310
  // Reserve trailing space only when the end slot actually renders
@@ -318,8 +329,8 @@ export function TextArea({
318
329
  1: {
319
330
  className: "x10l6tqk x112dmjn x13oyzgk x47corl x78zum5 x6s0dn4 xzye2dw x9ynric x141an7d x1ltkj2j xjt36v0"
320
331
  }
321
- }[!!(optimisticValue.length > maxLength) << 0],
322
- children: [optimisticValue.length > maxLength &&
332
+ }[!!(valueLength > maxLength) << 0],
333
+ children: [valueLength > maxLength &&
323
334
  /*#__PURE__*/
324
335
  // Non-color cue so the over-limit state isn't conveyed by the red
325
336
  // color alone (WCAG 1.4.1). Decorative — the count text and the
@@ -327,7 +338,7 @@ export function TextArea({
327
338
  _jsx(Icon, {
328
339
  icon: "warning",
329
340
  size: "sm"
330
- }), optimisticValue.length, "/", maxLength]
341
+ }), valueLength, "/", maxLength]
331
342
  })]
332
343
  }), showsDisabledMessage && disabledMessageTooltip.renderTooltip(disabledMessage)]
333
344
  });
@@ -226,7 +226,8 @@ export function TreeList({
226
226
  ref: ref,
227
227
  "data-testid": testId,
228
228
  ...mergeProps(themeProps('tree-list', {
229
- density
229
+ density,
230
+ variant
230
231
  }), stylex.props(styles.root, xstyle), className, style),
231
232
  children: [header != null && /*#__PURE__*/_jsx("div", {
232
233
  id: headerId,