@octaviaflow/core 3.0.4 → 3.0.6

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/index.js CHANGED
@@ -607,6 +607,32 @@ function Breadcrumb({
607
607
  import { motion as motion2 } from "framer-motion";
608
608
  import { useRef as useRef3 } from "react";
609
609
  import { useButton } from "react-aria";
610
+
611
+ // src/utils/a11y.ts
612
+ function resolveAccessibleName(input) {
613
+ if (input.ariaLabelledby) {
614
+ return { "aria-labelledby": input.ariaLabelledby };
615
+ }
616
+ if (input.ariaLabel) {
617
+ return { "aria-label": input.ariaLabel };
618
+ }
619
+ if (typeof input.label === "string" && input.label.trim().length > 0) {
620
+ return { "aria-label": input.label };
621
+ }
622
+ for (const candidate of input.fallbacks ?? []) {
623
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
624
+ return { "aria-label": candidate };
625
+ }
626
+ }
627
+ if (process.env.NODE_ENV !== "production") {
628
+ console.error(
629
+ `[@octaviaflow/core ${input.componentName}] No accessible name. Pass a string \`label\`, or \`aria-label\`, or \`aria-labelledby\` so screen readers can announce this control.`
630
+ );
631
+ }
632
+ return { "aria-label": `Unlabeled ${input.componentName}` };
633
+ }
634
+
635
+ // src/components/Button/Button.tsx
610
636
  import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
611
637
  function Button({
612
638
  variant = "primary",
@@ -624,11 +650,19 @@ function Button({
624
650
  const ref = useRef3(null);
625
651
  const isDisabled = disabled || loading;
626
652
  const resolvedType = type ?? "button";
653
+ const hasVisibleText = typeof children === "string" ? children.trim().length > 0 : Boolean(children);
654
+ const needsAriaName = !hasVisibleText;
655
+ const ariaNameProps = needsAriaName ? resolveAccessibleName({
656
+ ariaLabel: props["aria-label"],
657
+ ariaLabelledby: props["aria-labelledby"],
658
+ componentName: "Button"
659
+ }) : void 0;
627
660
  const { buttonProps } = useButton(
628
661
  {
629
662
  isDisabled,
630
663
  onPress: props.onClick,
631
- type: resolvedType
664
+ type: resolvedType,
665
+ ...ariaNameProps ?? {}
632
666
  },
633
667
  ref
634
668
  );
@@ -644,7 +678,7 @@ function Button({
644
678
  onBlur: _onBlur,
645
679
  ...passthroughProps
646
680
  } = props;
647
- return /* @__PURE__ */ jsxs10(
681
+ return /* @__PURE__ */ jsx10(
648
682
  motion2.button,
649
683
  {
650
684
  ...passthroughProps,
@@ -662,27 +696,32 @@ function Button({
662
696
  "data-loading": loading || void 0,
663
697
  whileTap: isDisabled ? void 0 : { scale: 0.97 },
664
698
  transition: { duration: 0.1 },
665
- children: [
666
- /* @__PURE__ */ jsxs10("span", { className: "ods-btn__content", "aria-hidden": loading, children: [
667
- leftIcon && /* @__PURE__ */ jsx10("span", { className: "ods-btn__icon ods-btn__icon--left", children: leftIcon }),
668
- children && /* @__PURE__ */ jsx10("span", { className: "ods-btn__label", children }),
669
- rightIcon && /* @__PURE__ */ jsx10("span", { className: "ods-btn__icon ods-btn__icon--right", children: rightIcon })
670
- ] }),
671
- loading && /* @__PURE__ */ jsx10("span", { className: "ods-btn__spinner", role: "status", "aria-label": "Loading", children: /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", children: /* @__PURE__ */ jsx10(
672
- "circle",
699
+ children: /* @__PURE__ */ jsxs10("span", { className: "ods-btn__content", children: [
700
+ loading ? /* @__PURE__ */ jsx10(
701
+ "span",
673
702
  {
674
- cx: "12",
675
- cy: "12",
676
- r: "10",
677
- fill: "none",
678
- stroke: "currentColor",
679
- strokeWidth: "2.5",
680
- strokeLinecap: "round",
681
- strokeDasharray: "32",
682
- strokeDashoffset: "12"
703
+ className: "ods-btn__icon ods-btn__icon--left ods-btn__icon--spinner",
704
+ role: "status",
705
+ "aria-label": "Loading",
706
+ children: /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", children: /* @__PURE__ */ jsx10(
707
+ "circle",
708
+ {
709
+ cx: "12",
710
+ cy: "12",
711
+ r: "10",
712
+ fill: "none",
713
+ stroke: "currentColor",
714
+ strokeWidth: "2.5",
715
+ strokeLinecap: "round",
716
+ strokeDasharray: "32",
717
+ strokeDashoffset: "12"
718
+ }
719
+ ) })
683
720
  }
684
- ) }) })
685
- ]
721
+ ) : leftIcon && /* @__PURE__ */ jsx10("span", { className: "ods-btn__icon ods-btn__icon--left", children: leftIcon }),
722
+ children && /* @__PURE__ */ jsx10("span", { className: "ods-btn__label", children }),
723
+ rightIcon && !loading && /* @__PURE__ */ jsx10("span", { className: "ods-btn__icon ods-btn__icon--right", children: rightIcon })
724
+ ] })
686
725
  }
687
726
  );
688
727
  }
@@ -2454,13 +2493,19 @@ function Checkbox({
2454
2493
  defaultSelected: defaultChecked,
2455
2494
  onChange
2456
2495
  });
2496
+ const ariaNameProps = resolveAccessibleName({
2497
+ label,
2498
+ ariaLabel: props["aria-label"],
2499
+ ariaLabelledby: props["aria-labelledby"],
2500
+ componentName: "Checkbox"
2501
+ });
2457
2502
  const { inputProps } = useCheckbox(
2458
2503
  {
2459
2504
  isSelected: state.isSelected,
2460
2505
  isIndeterminate: indeterminate,
2461
2506
  isDisabled: disabled,
2462
2507
  onChange,
2463
- "aria-label": typeof label === "string" ? label : void 0
2508
+ ...ariaNameProps
2464
2509
  },
2465
2510
  state,
2466
2511
  ref
@@ -6010,7 +6055,7 @@ function DescriptionList({
6010
6055
  // src/components/Dialog/Dialog.tsx
6011
6056
  import { AnimatePresence as AnimatePresence3, motion as motion5 } from "framer-motion";
6012
6057
  import { useRef as useRef11 } from "react";
6013
- import { FocusScope, useDialog, useModal, useOverlay } from "react-aria";
6058
+ import { FocusScope, OverlayProvider, useDialog, useModal, useOverlay } from "react-aria";
6014
6059
  import { createPortal } from "react-dom";
6015
6060
 
6016
6061
  // src/utils/motion.ts
@@ -6130,7 +6175,10 @@ function DialogContent({
6130
6175
  }
6131
6176
  function Dialog(props) {
6132
6177
  if (typeof document === "undefined") return null;
6133
- return createPortal(/* @__PURE__ */ jsx26(DialogContent, { ...props }), document.body);
6178
+ return createPortal(
6179
+ /* @__PURE__ */ jsx26(OverlayProvider, { children: /* @__PURE__ */ jsx26(DialogContent, { ...props }) }),
6180
+ document.body
6181
+ );
6134
6182
  }
6135
6183
 
6136
6184
  // src/components/DonutChart/DonutChart.tsx
@@ -6360,11 +6408,27 @@ function MenuPopup({
6360
6408
  }
6361
6409
  return null;
6362
6410
  }
6363
- function DropdownMenu({ trigger, items, align = "start", className }) {
6411
+ function DropdownMenu({
6412
+ trigger,
6413
+ items,
6414
+ align = "start",
6415
+ className,
6416
+ "aria-label": ariaLabel,
6417
+ "aria-labelledby": ariaLabelledby
6418
+ }) {
6364
6419
  const triggerRef = useRef12(null);
6365
6420
  const state = $e3403870bfb691da$export$79fefeb1c2091ac3({});
6366
6421
  const { menuTriggerProps } = useMenuTrigger({}, state, triggerRef);
6367
- const { buttonProps } = useButton3(menuTriggerProps, triggerRef);
6422
+ const ariaNameProps = resolveAccessibleName({
6423
+ ariaLabel,
6424
+ ariaLabelledby,
6425
+ componentName: "DropdownMenu",
6426
+ fallbacks: [typeof trigger === "string" ? trigger : void 0]
6427
+ });
6428
+ const { buttonProps } = useButton3(
6429
+ { ...menuTriggerProps, ...ariaNameProps },
6430
+ triggerRef
6431
+ );
6368
6432
  const { onDrag, onDragStart, onDragEnd, onAnimationStart, ...safeTriggerProps } = buttonProps;
6369
6433
  return /* @__PURE__ */ jsxs28(Fragment6, { children: [
6370
6434
  /* @__PURE__ */ jsx29("button", { ...safeTriggerProps, ref: triggerRef, className: "ods-dropdown__trigger", children: trigger }),
@@ -10178,11 +10242,18 @@ function Radio({
10178
10242
  }) {
10179
10243
  const state = useRadioGroupContext();
10180
10244
  const ref = useRef24(null);
10245
+ const ariaNameProps = resolveAccessibleName({
10246
+ label,
10247
+ ariaLabel: props["aria-label"],
10248
+ ariaLabelledby: props["aria-labelledby"],
10249
+ componentName: "Radio",
10250
+ fallbacks: [value]
10251
+ });
10181
10252
  const { inputProps } = useRadio(
10182
10253
  {
10183
10254
  value,
10184
10255
  isDisabled: disabled,
10185
- "aria-label": label
10256
+ ...ariaNameProps
10186
10257
  },
10187
10258
  state,
10188
10259
  ref
@@ -10780,6 +10851,15 @@ function Select({
10780
10851
  (o) => o.label.toLowerCase().includes(q) || o.description?.toLowerCase().includes(q)
10781
10852
  );
10782
10853
  }, [options, searchQuery, searchable]);
10854
+ const ariaNameProps = useMemo10(
10855
+ () => resolveAccessibleName({
10856
+ label,
10857
+ ariaLabel,
10858
+ ariaLabelledby: ariaLabelledBy,
10859
+ componentName: "Select"
10860
+ }),
10861
+ [label, ariaLabel, ariaLabelledBy]
10862
+ );
10783
10863
  const ariaProps = useMemo10(() => {
10784
10864
  const items = filteredOptions.map((o) => ({
10785
10865
  key: o.value,
@@ -10788,7 +10868,10 @@ function Select({
10788
10868
  isDisabled: o.disabled
10789
10869
  }));
10790
10870
  const props = {
10791
- label: label || "Select",
10871
+ // Visible string label when present; otherwise rely on the aria-*
10872
+ // props for screen-reader name.
10873
+ label: typeof label === "string" ? label : void 0,
10874
+ ...ariaNameProps,
10792
10875
  items,
10793
10876
  children: (item) => /* @__PURE__ */ jsx66($05678f3aee5e7d1a$export$6d08773d2e66f8f2, { textValue: item.label, children: item.label }, item.key),
10794
10877
  isDisabled: disabled,
@@ -10804,7 +10887,7 @@ function Select({
10804
10887
  props.defaultSelectedKey = defaultValue;
10805
10888
  }
10806
10889
  return props;
10807
- }, [filteredOptions, label, disabled, value, defaultValue, onChange]);
10890
+ }, [filteredOptions, label, disabled, value, defaultValue, onChange, ariaNameProps]);
10808
10891
  const state = $29256f53a2edafe9$export$5159ec8b34d4ec12(ariaProps);
10809
10892
  const { triggerProps, menuProps } = useSelect(ariaProps, state, triggerRef);
10810
10893
  const { buttonProps } = useButton4(triggerProps, triggerRef);
@@ -11476,7 +11559,7 @@ function Skeleton({ variant = "text", width, height, lines = 1, className }) {
11476
11559
  // src/components/SlideoutPanel/SlideoutPanel.tsx
11477
11560
  import { AnimatePresence as AnimatePresence9, motion as motion12 } from "framer-motion";
11478
11561
  import { useRef as useRef28 } from "react";
11479
- import { FocusScope as FocusScope2, useDialog as useDialog2, useModal as useModal2, useOverlay as useOverlay2 } from "react-aria";
11562
+ import { FocusScope as FocusScope2, OverlayProvider as OverlayProvider2, useDialog as useDialog2, useModal as useModal2, useOverlay as useOverlay2 } from "react-aria";
11480
11563
  import { createPortal as createPortal8 } from "react-dom";
11481
11564
  import { Fragment as Fragment16, jsx as jsx71, jsxs as jsxs69 } from "react/jsx-runtime";
11482
11565
  var slideVariants2 = {
@@ -11569,7 +11652,10 @@ function SlideoutContent({
11569
11652
  }
11570
11653
  function SlideoutPanel(props) {
11571
11654
  if (typeof document === "undefined") return null;
11572
- return createPortal8(/* @__PURE__ */ jsx71(SlideoutContent, { ...props }), document.body);
11655
+ return createPortal8(
11656
+ /* @__PURE__ */ jsx71(OverlayProvider2, { children: /* @__PURE__ */ jsx71(SlideoutContent, { ...props }) }),
11657
+ document.body
11658
+ );
11573
11659
  }
11574
11660
 
11575
11661
  // src/components/Slider/Slider.tsx
@@ -12160,12 +12246,18 @@ function Switch({
12160
12246
  defaultSelected: defaultChecked,
12161
12247
  onChange
12162
12248
  });
12249
+ const ariaNameProps = resolveAccessibleName({
12250
+ label,
12251
+ ariaLabel: props["aria-label"],
12252
+ ariaLabelledby: props["aria-labelledby"],
12253
+ componentName: "Switch"
12254
+ });
12163
12255
  const { inputProps } = useSwitch(
12164
12256
  {
12165
12257
  isSelected: state.isSelected,
12166
12258
  isDisabled: disabled,
12167
12259
  onChange,
12168
- "aria-label": typeof label === "string" ? label : void 0
12260
+ ...ariaNameProps
12169
12261
  },
12170
12262
  state,
12171
12263
  ref
@@ -12611,9 +12703,18 @@ function Textarea({
12611
12703
  const ref = useRef32(null);
12612
12704
  const errorId = useId4();
12613
12705
  const [charCount, setCharCount] = useState26(() => String(value ?? defaultValue ?? "").length);
12706
+ const ariaNameProps = resolveAccessibleName({
12707
+ label,
12708
+ ariaLabel: props["aria-label"],
12709
+ ariaLabelledby: props["aria-labelledby"],
12710
+ componentName: "Textarea"
12711
+ });
12614
12712
  const { labelProps, inputProps } = useTextField2(
12615
12713
  {
12616
- label: label || props["aria-label"] || "textarea",
12714
+ // Pass a string `label` only when caller gave a string; otherwise rely
12715
+ // on the aria-* props from resolveAccessibleName.
12716
+ label: typeof label === "string" ? label : void 0,
12717
+ ...ariaNameProps,
12617
12718
  isDisabled: disabled,
12618
12719
  errorMessage,
12619
12720
  validationState: error ? "invalid" : void 0,
@@ -15504,7 +15605,7 @@ function YamlViewer({
15504
15605
  // src/provider/OdsProvider.tsx
15505
15606
  import { generateCssVars, resolveConfig } from "@octaviaflow/config";
15506
15607
  import { createContext as createContext3, useContext as useContext3, useEffect as useEffect21, useMemo as useMemo18 } from "react";
15507
- import { OverlayProvider } from "react-aria";
15608
+ import { OverlayProvider as OverlayProvider3 } from "react-aria";
15508
15609
  import { jsx as jsx98 } from "react/jsx-runtime";
15509
15610
  var OdsContext = createContext3(null);
15510
15611
  function OdsProvider({ config: userConfig, children }) {
@@ -15526,7 +15627,7 @@ function OdsProvider({ config: userConfig, children }) {
15526
15627
  }
15527
15628
  }, [resolved]);
15528
15629
  const contextValue = useMemo18(() => ({ config: resolved }), [resolved]);
15529
- return /* @__PURE__ */ jsx98(OdsContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx98(OverlayProvider, { children }) });
15630
+ return /* @__PURE__ */ jsx98(OdsContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx98(OverlayProvider3, { children }) });
15530
15631
  }
15531
15632
  function useOds() {
15532
15633
  const ctx = useContext3(OdsContext);