@particle-academy/fancy-flow 0.33.0 → 0.33.2

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.d.cts CHANGED
@@ -391,6 +391,17 @@ type ConfigFieldRendererProps = {
391
391
  field: ConfigField;
392
392
  value: unknown;
393
393
  onChange: (value: unknown) => void;
394
+ /**
395
+ * DOM id for the control this renders.
396
+ *
397
+ * Supplied by the caller rather than generated here so the panel's `<label>`
398
+ * can point at it — a label beside a control is not a label attached to one.
399
+ * Also the agent-facing handle: the Human+ contract asks that every
400
+ * interactive element have a stable identity so an agent targets it instead
401
+ * of guessing at the DOM, and until this existed nothing in the editor had
402
+ * one.
403
+ */
404
+ id?: string;
394
405
  renderCredentialField?: (props: {
395
406
  credentialType: string;
396
407
  value: unknown;
@@ -410,14 +421,17 @@ type ConfigFieldRendererProps = {
410
421
  }) => ReactNode;
411
422
  };
412
423
  /**
413
- * ConfigFieldRenderer — dispatches to the right input element per field
414
- * type. Plain HTML inputs styled via the package's CSS so the package
415
- * stays standalone (no react-fancy import required).
424
+ * ConfigFieldRenderer — dispatches to the right input element per field type.
425
+ *
426
+ * Plain HTML inputs styled via the package's CSS so the package stays
427
+ * standalone (no react-fancy import required) and every surface stays themeable
428
+ * through the `--ff-*` token layer. Hosts that want react-fancy form components
429
+ * can supply their own field renderers via the kind's `renderPanel`.
416
430
  *
417
- * Hosts that want to use react-fancy form components can supply their
418
- * own field renderers via the kind's `renderPanel`.
431
+ * Each control carries the caller's `id` and a `data-ff-field` handle keyed by
432
+ * the field, so a label can point at it and an agent can find it by name.
419
433
  */
420
- declare function ConfigFieldRenderer({ field, value, onChange, renderCredentialField, renderDocumentField, }: ConfigFieldRendererProps): react.JSX.Element | null;
434
+ declare function ConfigFieldRenderer({ field, value, onChange, id, renderCredentialField, renderDocumentField, }: ConfigFieldRendererProps): react.JSX.Element | null;
421
435
 
422
436
  /** Entry-point node — outputs only, no inputs. */
423
437
  declare function TriggerNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
package/dist/index.d.ts CHANGED
@@ -391,6 +391,17 @@ type ConfigFieldRendererProps = {
391
391
  field: ConfigField;
392
392
  value: unknown;
393
393
  onChange: (value: unknown) => void;
394
+ /**
395
+ * DOM id for the control this renders.
396
+ *
397
+ * Supplied by the caller rather than generated here so the panel's `<label>`
398
+ * can point at it — a label beside a control is not a label attached to one.
399
+ * Also the agent-facing handle: the Human+ contract asks that every
400
+ * interactive element have a stable identity so an agent targets it instead
401
+ * of guessing at the DOM, and until this existed nothing in the editor had
402
+ * one.
403
+ */
404
+ id?: string;
394
405
  renderCredentialField?: (props: {
395
406
  credentialType: string;
396
407
  value: unknown;
@@ -410,14 +421,17 @@ type ConfigFieldRendererProps = {
410
421
  }) => ReactNode;
411
422
  };
412
423
  /**
413
- * ConfigFieldRenderer — dispatches to the right input element per field
414
- * type. Plain HTML inputs styled via the package's CSS so the package
415
- * stays standalone (no react-fancy import required).
424
+ * ConfigFieldRenderer — dispatches to the right input element per field type.
425
+ *
426
+ * Plain HTML inputs styled via the package's CSS so the package stays
427
+ * standalone (no react-fancy import required) and every surface stays themeable
428
+ * through the `--ff-*` token layer. Hosts that want react-fancy form components
429
+ * can supply their own field renderers via the kind's `renderPanel`.
416
430
  *
417
- * Hosts that want to use react-fancy form components can supply their
418
- * own field renderers via the kind's `renderPanel`.
431
+ * Each control carries the caller's `id` and a `data-ff-field` handle keyed by
432
+ * the field, so a label can point at it and an agent can find it by name.
419
433
  */
420
- declare function ConfigFieldRenderer({ field, value, onChange, renderCredentialField, renderDocumentField, }: ConfigFieldRendererProps): react.JSX.Element | null;
434
+ declare function ConfigFieldRenderer({ field, value, onChange, id, renderCredentialField, renderDocumentField, }: ConfigFieldRendererProps): react.JSX.Element | null;
421
435
 
422
436
  /** Entry-point node — outputs only, no inputs. */
423
437
  declare function TriggerNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import { onNodeKindsChanged, listNodeKinds, getNodeKind, defaultConfigFor, valid
14
14
  export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig } from './chunk-Q6IPK64P.js';
15
15
  import { getRichInputAdapter } from './chunk-F5RPRB7A.js';
16
16
  export { RichInputPreview, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerRichInputAdapter } from './chunk-F5RPRB7A.js';
17
- import { memo, forwardRef, useState, useMemo, useEffect, useCallback, useRef, useImperativeHandle } from 'react';
17
+ import { memo, forwardRef, useState, useMemo, useEffect, useCallback, useRef, useImperativeHandle, useId } from 'react';
18
18
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
19
19
 
20
20
  function NodeShellInner({
@@ -565,9 +565,11 @@ function ConfigFieldRenderer({
565
565
  field,
566
566
  value,
567
567
  onChange,
568
+ id,
568
569
  renderCredentialField,
569
570
  renderDocumentField
570
571
  }) {
572
+ const handle = { id, "data-ff-field": field.key };
571
573
  switch (field.type) {
572
574
  case "text": {
573
575
  if (field.choices?.length) {
@@ -576,6 +578,7 @@ function ConfigFieldRenderer({
576
578
  return /* @__PURE__ */ jsx(
577
579
  "input",
578
580
  {
581
+ ...handle,
579
582
  className: "ff-panel__input",
580
583
  type: "text",
581
584
  value: value ?? "",
@@ -588,6 +591,7 @@ function ConfigFieldRenderer({
588
591
  return /* @__PURE__ */ jsx(
589
592
  "textarea",
590
593
  {
594
+ ...handle,
591
595
  className: "ff-panel__input ff-panel__input--textarea",
592
596
  rows: field.rows ?? 4,
593
597
  value: value ?? "",
@@ -599,6 +603,7 @@ function ConfigFieldRenderer({
599
603
  return /* @__PURE__ */ jsx(
600
604
  "input",
601
605
  {
606
+ ...handle,
602
607
  className: "ff-panel__input",
603
608
  type: "number",
604
609
  value: value ?? "",
@@ -613,6 +618,7 @@ function ConfigFieldRenderer({
613
618
  /* @__PURE__ */ jsx(
614
619
  "input",
615
620
  {
621
+ ...handle,
616
622
  type: "checkbox",
617
623
  checked: !!value,
618
624
  onChange: (e) => onChange(e.target.checked)
@@ -624,6 +630,7 @@ function ConfigFieldRenderer({
624
630
  return /* @__PURE__ */ jsxs(
625
631
  "select",
626
632
  {
633
+ ...handle,
627
634
  className: "ff-panel__input",
628
635
  value: value ?? "",
629
636
  onChange: (e) => onChange(e.target.value),
@@ -639,6 +646,7 @@ function ConfigFieldRenderer({
639
646
  return /* @__PURE__ */ jsx(
640
647
  "textarea",
641
648
  {
649
+ ...handle,
642
650
  className: "ff-panel__input ff-panel__input--expression",
643
651
  rows: 2,
644
652
  value: value ?? "",
@@ -939,15 +947,20 @@ function NodeConfigPanel({
939
947
  className,
940
948
  style
941
949
  }) {
950
+ const uid = useId();
951
+ const kindName = node ? node.data.kind ?? node.type : null;
952
+ const kind = useMemo(() => kindName ? getNodeKind(kindName) : void 0, [kindName]);
953
+ const config = useMemo(
954
+ () => node?.data?.config ?? {},
955
+ [node?.data]
956
+ );
957
+ const fieldId = (key) => `${uid}-${key}`;
942
958
  if (!node) {
943
959
  return /* @__PURE__ */ jsxs("aside", { className: ["ff-panel", "ff-panel--empty", className ?? ""].filter(Boolean).join(" "), style, children: [
944
960
  header,
945
961
  /* @__PURE__ */ jsx("p", { className: "ff-panel__empty", children: "Select a node to configure it." })
946
962
  ] });
947
963
  }
948
- const kindName = node.data.kind ?? node.type;
949
- const kind = useMemo(() => getNodeKind(kindName), [kindName]);
950
- const config = useMemo(() => node.data.config ?? {}, [node.data]);
951
964
  if (!kind) {
952
965
  return /* @__PURE__ */ jsxs("aside", { className: ["ff-panel", className ?? ""].filter(Boolean).join(" "), style, children: [
953
966
  header,
@@ -988,10 +1001,12 @@ function NodeConfigPanel({
988
1001
  ] }),
989
1002
  kind.description && /* @__PURE__ */ jsx("p", { className: "ff-panel__kind-desc", children: kind.description }),
990
1003
  /* @__PURE__ */ jsxs("div", { className: "ff-panel__field", children: [
991
- /* @__PURE__ */ jsx("label", { className: "ff-panel__label", children: "Label" }),
1004
+ /* @__PURE__ */ jsx("label", { className: "ff-panel__label", htmlFor: fieldId("label"), children: "Label" }),
992
1005
  /* @__PURE__ */ jsx(
993
1006
  "input",
994
1007
  {
1008
+ id: fieldId("label"),
1009
+ "data-ff-field": "label",
995
1010
  className: "ff-panel__input",
996
1011
  value: node.data.label ?? "",
997
1012
  onChange: (e) => setLabel(e.target.value),
@@ -1000,10 +1015,12 @@ function NodeConfigPanel({
1000
1015
  )
1001
1016
  ] }),
1002
1017
  /* @__PURE__ */ jsxs("div", { className: "ff-panel__field", children: [
1003
- /* @__PURE__ */ jsx("label", { className: "ff-panel__label", children: "Description" }),
1018
+ /* @__PURE__ */ jsx("label", { className: "ff-panel__label", htmlFor: fieldId("description"), children: "Description" }),
1004
1019
  /* @__PURE__ */ jsx(
1005
1020
  "textarea",
1006
1021
  {
1022
+ id: fieldId("description"),
1023
+ "data-ff-field": "description",
1007
1024
  className: "ff-panel__input ff-panel__input--textarea",
1008
1025
  rows: 2,
1009
1026
  value: node.data.description ?? "",
@@ -1018,7 +1035,7 @@ function NodeConfigPanel({
1018
1035
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1019
1036
  (kind.configSchema ?? []).length > 0 && /* @__PURE__ */ jsx("hr", { className: "ff-panel__divider" }),
1020
1037
  (kind.configSchema ?? []).map((field) => /* @__PURE__ */ jsxs("div", { className: "ff-panel__field", children: [
1021
- /* @__PURE__ */ jsxs("label", { className: "ff-panel__label", children: [
1038
+ /* @__PURE__ */ jsxs("label", { className: "ff-panel__label", htmlFor: fieldId(field.key), children: [
1022
1039
  field.label,
1023
1040
  field.required && /* @__PURE__ */ jsx("span", { className: "ff-panel__required", "aria-hidden": true, children: " *" })
1024
1041
  ] }),
@@ -1026,6 +1043,7 @@ function NodeConfigPanel({
1026
1043
  /* @__PURE__ */ jsx(
1027
1044
  ConfigFieldRenderer,
1028
1045
  {
1046
+ id: fieldId(field.key),
1029
1047
  field,
1030
1048
  value: config[field.key],
1031
1049
  onChange: (v) => setConfigValue(field.key, v),