myoperator-ui 0.0.221 → 0.0.222

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 (2) hide show
  1. package/dist/index.js +293 -81
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -17765,6 +17765,101 @@ export const IvrBotConfig = React.forwardRef(
17765
17765
  );
17766
17766
 
17767
17767
  IvrBotConfig.displayName = "IvrBotConfig";
17768
+ `, prefix)
17769
+ },
17770
+ {
17771
+ name: "create-function-validation.ts",
17772
+ content: prefixTailwindClasses(`import type { KeyValuePair } from "./types";
17773
+
17774
+ /** HTTP(S) URL prefix check \u2014 used for Create Function API URL field. */
17775
+ export const URL_REGEX = /^https?:\\/\\//;
17776
+
17777
+ export const HEADER_KEY_REGEX = /^[!#$%&'*+\\-.^_\`|~0-9a-zA-Z]+$/;
17778
+
17779
+ /** Single message for invalid header keys (KeyValueTable + submit validation). */
17780
+ export const HEADER_KEY_INVALID_MESSAGE =
17781
+ "Invalid header key. Use only alphanumeric and !#$%&'*+-.^_\`|~ characters.";
17782
+
17783
+ // Query parameter validation (aligned with apiIntegrationSchema.queryParams)
17784
+ export const QUERY_PARAM_KEY_MAX = 512;
17785
+ export const QUERY_PARAM_VALUE_MAX = 2048;
17786
+ export const QUERY_PARAM_KEY_PATTERN = /^[a-zA-Z0-9_.\\-~]+$/;
17787
+
17788
+ export function validateQueryParamKey(key: string): string | undefined {
17789
+ if (!key.trim()) return "Query param key is required";
17790
+ if (key.length > QUERY_PARAM_KEY_MAX) return "key cannot exceed 512 characters.";
17791
+ if (!QUERY_PARAM_KEY_PATTERN.test(key)) return "Invalid query parameter key.";
17792
+ return undefined;
17793
+ }
17794
+
17795
+ export function validateQueryParamValue(value: string): string | undefined {
17796
+ if (!value.trim()) return "Query param value is required";
17797
+ if (value.length > QUERY_PARAM_VALUE_MAX) return "value cannot exceed 2048 characters.";
17798
+ return undefined;
17799
+ }
17800
+
17801
+ export function queryParamsHaveErrors(rows: KeyValuePair[]): boolean {
17802
+ return rows.some((row) => {
17803
+ const hasInput = row.key.trim() !== "" || row.value.trim() !== "";
17804
+ if (!hasInput) return false;
17805
+ return (
17806
+ validateQueryParamKey(row.key) !== undefined ||
17807
+ validateQueryParamValue(row.value) !== undefined
17808
+ );
17809
+ });
17810
+ }
17811
+
17812
+ export const URL_REQUIRED_MESSAGE = "API URL is required";
17813
+ export const URL_FORMAT_MESSAGE = "URL must start with http:// or https://";
17814
+
17815
+ /** On save: URL is required and must start with http:// or https:// */
17816
+ export function getUrlSubmitValidationError(value: string): string {
17817
+ const t = value.trim();
17818
+ if (!t) return URL_REQUIRED_MESSAGE;
17819
+ if (!URL_REGEX.test(t)) return URL_FORMAT_MESSAGE;
17820
+ return "";
17821
+ }
17822
+
17823
+ /** On blur while typing: empty clears; non-empty must match URL_REGEX. */
17824
+ export function getUrlBlurValidationError(value: string): string {
17825
+ if (value.trim() && !URL_REGEX.test(value.trim())) return URL_FORMAT_MESSAGE;
17826
+ return "";
17827
+ }
17828
+
17829
+ export const BODY_JSON_ERROR_MESSAGE = "Body must be valid JSON";
17830
+
17831
+ /** Empty body is valid; non-empty must parse as JSON. */
17832
+ export function getBodyJsonValidationError(value: string): string {
17833
+ if (!value.trim()) return "";
17834
+ try {
17835
+ JSON.parse(value.trim());
17836
+ return "";
17837
+ } catch {
17838
+ return BODY_JSON_ERROR_MESSAGE;
17839
+ }
17840
+ }
17841
+
17842
+ export type HeaderRowFieldErrors = { key?: string; value?: string };
17843
+
17844
+ /** When either key or value has text, both are required; key must match HEADER_KEY_REGEX. */
17845
+ export function getHeaderRowSubmitErrors(row: KeyValuePair): HeaderRowFieldErrors {
17846
+ const hasInput = row.key.trim() !== "" || row.value.trim() !== "";
17847
+ if (!hasInput) return {};
17848
+ const errors: HeaderRowFieldErrors = {};
17849
+ if (!row.key.trim()) errors.key = "Header key is required";
17850
+ else if (!HEADER_KEY_REGEX.test(row.key)) {
17851
+ errors.key = HEADER_KEY_INVALID_MESSAGE;
17852
+ }
17853
+ if (!row.value.trim()) errors.value = "Header value is required";
17854
+ return errors;
17855
+ }
17856
+
17857
+ export function headerRowsHaveSubmitErrors(rows: KeyValuePair[]): boolean {
17858
+ return rows.some((row) => {
17859
+ const e = getHeaderRowSubmitErrors(row);
17860
+ return Boolean(e.key || e.value);
17861
+ });
17862
+ }
17768
17863
  `, prefix)
17769
17864
  },
17770
17865
  {
@@ -17792,6 +17887,20 @@ import type {
17792
17887
  VariableItem,
17793
17888
  VariableFormData,
17794
17889
  } from "./types";
17890
+ import {
17891
+ HEADER_KEY_INVALID_MESSAGE,
17892
+ HEADER_KEY_REGEX,
17893
+ QUERY_PARAM_KEY_MAX,
17894
+ QUERY_PARAM_VALUE_MAX,
17895
+ getBodyJsonValidationError,
17896
+ getHeaderRowSubmitErrors,
17897
+ getUrlBlurValidationError,
17898
+ getUrlSubmitValidationError,
17899
+ headerRowsHaveSubmitErrors,
17900
+ queryParamsHaveErrors,
17901
+ validateQueryParamKey,
17902
+ validateQueryParamValue,
17903
+ } from "./create-function-validation";
17795
17904
 
17796
17905
  const HTTP_METHODS: HttpMethod[] = ["GET", "POST", "PUT", "DELETE", "PATCH"];
17797
17906
  const FUNCTION_NAME_MAX = 100;
@@ -17801,14 +17910,13 @@ const HEADER_KEY_MAX = 512;
17801
17910
  const HEADER_VALUE_MAX = 2048;
17802
17911
 
17803
17912
  const FUNCTION_NAME_REGEX = /^(?!_+$)(?=.*[a-zA-Z])[a-zA-Z][a-zA-Z0-9_]*$/;
17913
+
17914
+ /** Spaces \u2192 underscores so users can type natural phrases without invalid-name errors. */
17915
+ function normalizeFunctionNameInput(value: string): string {
17916
+ return value.replace(/ /g, "_");
17917
+ }
17804
17918
  const VARIABLE_NAME_MAX = 30;
17805
17919
  const VARIABLE_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]*$/;
17806
- const URL_REGEX = /^https?:\\/\\//;
17807
- const HEADER_KEY_REGEX = /^[!#$%&'*+\\-.^_\`|~0-9a-zA-Z]+$/;
17808
- // Query parameter validation (aligned with apiIntegrationSchema.queryParams)
17809
- const QUERY_PARAM_KEY_MAX = 512;
17810
- const QUERY_PARAM_VALUE_MAX = 2048;
17811
- const QUERY_PARAM_KEY_PATTERN = /^[a-zA-Z0-9_.\\-~]+$/;
17812
17920
 
17813
17921
  const DEFAULT_SESSION_VARIABLES = [
17814
17922
  "{{Caller number}}",
@@ -17816,19 +17924,6 @@ const DEFAULT_SESSION_VARIABLES = [
17816
17924
  "{{Contact Details}}",
17817
17925
  ];
17818
17926
 
17819
- function validateQueryParamKey(key: string): string | undefined {
17820
- if (!key.trim()) return "Query param key is required";
17821
- if (key.length > QUERY_PARAM_KEY_MAX) return "key cannot exceed 512 characters.";
17822
- if (!QUERY_PARAM_KEY_PATTERN.test(key)) return "Invalid query parameter key.";
17823
- return undefined;
17824
- }
17825
-
17826
- function validateQueryParamValue(value: string): string | undefined {
17827
- if (!value.trim()) return "Query param value is required";
17828
- if (value.length > QUERY_PARAM_VALUE_MAX) return "value cannot exceed 2048 characters.";
17829
- return undefined;
17830
- }
17831
-
17832
17927
  function generateId() {
17833
17928
  return Math.random().toString(36).slice(2, 9);
17834
17929
  }
@@ -17841,6 +17936,13 @@ interface TriggerState {
17841
17936
  to: number;
17842
17937
  }
17843
17938
 
17939
+ /** Where to insert \`{{name}}\` after the user saves "Create new variable" */
17940
+ type VarInsertContext =
17941
+ | { kind: "url"; from: number; to: number }
17942
+ | { kind: "body"; from: number; to: number }
17943
+ | { kind: "header"; rowId: string; from: number; to: number }
17944
+ | { kind: "query"; rowId: string; from: number; to: number };
17945
+
17844
17946
  function detectVarTrigger(value: string, cursor: number): TriggerState | null {
17845
17947
  const before = value.slice(0, cursor);
17846
17948
  const match = /\\{\\{([^}]*)$/.exec(before);
@@ -18210,7 +18312,7 @@ function VariableInput({
18210
18312
  onChange: (v: string) => void;
18211
18313
  sessionVariables: string[];
18212
18314
  variableGroups?: VariableGroup[];
18213
- onAddVariable?: () => void;
18315
+ onAddVariable?: (range: { from: number; to: number }) => void;
18214
18316
  onEditVariable?: (variable: string) => void;
18215
18317
  placeholder?: string;
18216
18318
  maxLength?: number;
@@ -18373,7 +18475,11 @@ function VariableInput({
18373
18475
  variableGroups={trigger ? variableGroups : undefined}
18374
18476
  filterQuery={trigger?.query ?? ""}
18375
18477
  onSelect={handleSelect}
18376
- onAddVariable={onAddVariable}
18478
+ onAddVariable={
18479
+ onAddVariable && trigger
18480
+ ? () => onAddVariable({ from: trigger.from, to: trigger.to })
18481
+ : undefined
18482
+ }
18377
18483
  onEditVariable={onEditVariable}
18378
18484
  style={popupStyle}
18379
18485
  />
@@ -18428,7 +18534,7 @@ function KeyValueTable({
18428
18534
  keyRegexError?: string;
18429
18535
  sessionVariables?: string[];
18430
18536
  variableGroups?: VariableGroup[];
18431
- onAddVariable?: () => void;
18537
+ onAddVariable?: (ctx: { rowId: string; from: number; to: number }) => void;
18432
18538
  onEditVariable?: (variable: string) => void;
18433
18539
  disabled?: boolean;
18434
18540
  }) {
@@ -18513,7 +18619,11 @@ function KeyValueTable({
18513
18619
  onChange={(v) => update(row.id, { value: v })}
18514
18620
  sessionVariables={sessionVariables}
18515
18621
  variableGroups={variableGroups}
18516
- onAddVariable={onAddVariable}
18622
+ onAddVariable={
18623
+ onAddVariable
18624
+ ? (range) => onAddVariable({ rowId: row.id, ...range })
18625
+ : undefined
18626
+ }
18517
18627
  onEditVariable={onEditVariable}
18518
18628
  placeholder="Type {{ to add variables"
18519
18629
  maxLength={valueMaxLength}
@@ -18632,14 +18742,38 @@ export const CreateFunctionModal = React.forwardRef(
18632
18742
  const [varModalOpen, setVarModalOpen] = React.useState(false);
18633
18743
  const [varModalMode, setVarModalMode] = React.useState<"create" | "edit">("create");
18634
18744
  const [varModalInitialData, setVarModalInitialData] = React.useState<VariableItem | undefined>();
18745
+ /** Field + \`{{\u2026\` range to replace with \`{{name}}\` after create saves */
18746
+ const [varInsertContext, setVarInsertContext] = React.useState<VarInsertContext | null>(null);
18635
18747
 
18636
- const handleAddVariableClick = () => {
18748
+ const openVariableCreateModal = React.useCallback(() => {
18637
18749
  setVarModalMode("create");
18638
18750
  setVarModalInitialData(undefined);
18639
18751
  setVarModalOpen(true);
18640
- };
18752
+ }, []);
18753
+
18754
+ const handleVarModalOpenChange = React.useCallback((next: boolean) => {
18755
+ setVarModalOpen(next);
18756
+ if (!next) setVarInsertContext(null);
18757
+ }, []);
18758
+
18759
+ const handleAddVariableFromHeader = React.useCallback(
18760
+ (ctx: { rowId: string; from: number; to: number }) => {
18761
+ setVarInsertContext({ kind: "header", ...ctx });
18762
+ openVariableCreateModal();
18763
+ },
18764
+ [openVariableCreateModal]
18765
+ );
18766
+
18767
+ const handleAddVariableFromQuery = React.useCallback(
18768
+ (ctx: { rowId: string; from: number; to: number }) => {
18769
+ setVarInsertContext({ kind: "query", ...ctx });
18770
+ openVariableCreateModal();
18771
+ },
18772
+ [openVariableCreateModal]
18773
+ );
18641
18774
 
18642
18775
  const handleEditVariableClick = (variableName: string) => {
18776
+ setVarInsertContext(null);
18643
18777
  const variable = variableGroups
18644
18778
  ?.flatMap((g) => g.items)
18645
18779
  .find((item) => item.name === variableName);
@@ -18649,6 +18783,35 @@ export const CreateFunctionModal = React.forwardRef(
18649
18783
  };
18650
18784
 
18651
18785
  const handleVariableSave = (data: VariableFormData) => {
18786
+ const trimmedName = data.name.trim();
18787
+ const insertToken = \`{{\${trimmedName}}}\`;
18788
+
18789
+ if (varModalMode === "create" && varInsertContext) {
18790
+ const ctx = varInsertContext;
18791
+ if (ctx.kind === "url") {
18792
+ setUrl((u) => insertVar(u, insertToken, ctx.from, ctx.to));
18793
+ } else if (ctx.kind === "body") {
18794
+ setBody((b) => insertVar(b, insertToken, ctx.from, ctx.to));
18795
+ } else if (ctx.kind === "header") {
18796
+ setHeaders((rows) =>
18797
+ rows.map((r) =>
18798
+ r.id === ctx.rowId
18799
+ ? { ...r, value: insertVar(r.value, insertToken, ctx.from, ctx.to) }
18800
+ : r
18801
+ )
18802
+ );
18803
+ } else if (ctx.kind === "query") {
18804
+ setQueryParams((rows) =>
18805
+ rows.map((r) =>
18806
+ r.id === ctx.rowId
18807
+ ? { ...r, value: insertVar(r.value, insertToken, ctx.from, ctx.to) }
18808
+ : r
18809
+ )
18810
+ );
18811
+ }
18812
+ setVarInsertContext(null);
18813
+ }
18814
+
18652
18815
  if (varModalMode === "create") {
18653
18816
  onAddVariable?.(data);
18654
18817
  } else {
@@ -18672,6 +18835,28 @@ export const CreateFunctionModal = React.forwardRef(
18672
18835
  ? sessionVariables.filter((v) => v.toLowerCase().includes(bodyTrigger.query))
18673
18836
  : [];
18674
18837
 
18838
+ const handleAddVariableFromUrl = React.useCallback(() => {
18839
+ if (urlTrigger) {
18840
+ setVarInsertContext({
18841
+ kind: "url",
18842
+ from: urlTrigger.from,
18843
+ to: urlTrigger.to,
18844
+ });
18845
+ }
18846
+ openVariableCreateModal();
18847
+ }, [urlTrigger, openVariableCreateModal]);
18848
+
18849
+ const handleAddVariableFromBody = React.useCallback(() => {
18850
+ if (bodyTrigger) {
18851
+ setVarInsertContext({
18852
+ kind: "body",
18853
+ from: bodyTrigger.from,
18854
+ to: bodyTrigger.to,
18855
+ });
18856
+ }
18857
+ openVariableCreateModal();
18858
+ }, [bodyTrigger, openVariableCreateModal]);
18859
+
18675
18860
  const computePopupStyle = (
18676
18861
  el: HTMLTextAreaElement | HTMLInputElement,
18677
18862
  cursor: number
@@ -18719,6 +18904,7 @@ export const CreateFunctionModal = React.forwardRef(
18719
18904
  setUrlPopupStyle(undefined);
18720
18905
  setBodyPopupStyle(undefined);
18721
18906
  setTestVarValues({});
18907
+ setVarInsertContext(null);
18722
18908
  }
18723
18909
  // Re-run only when modal opens; intentionally exclude deep deps to avoid mid-session resets
18724
18910
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -18744,6 +18930,7 @@ export const CreateFunctionModal = React.forwardRef(
18744
18930
  setUrlPopupStyle(undefined);
18745
18931
  setBodyPopupStyle(undefined);
18746
18932
  setTestVarValues({});
18933
+ setVarInsertContext(null);
18747
18934
  }, [initialData, initialStep, initialTab]);
18748
18935
 
18749
18936
  const handleClose = React.useCallback(() => {
@@ -18762,45 +18949,33 @@ export const CreateFunctionModal = React.forwardRef(
18762
18949
  };
18763
18950
 
18764
18951
  const validateUrl = (value: string) => {
18765
- if (value.trim() && !URL_REGEX.test(value.trim())) {
18766
- setUrlError("URL must start with http:// or https://");
18767
- } else {
18768
- setUrlError("");
18769
- }
18952
+ setUrlError(getUrlBlurValidationError(value));
18770
18953
  };
18771
18954
 
18772
18955
  const validateBody = (value: string) => {
18773
- if (value.trim()) {
18774
- try {
18775
- JSON.parse(value.trim());
18776
- setBodyError("");
18777
- } catch {
18778
- setBodyError("Body must be valid JSON");
18779
- }
18780
- } else {
18781
- setBodyError("");
18782
- }
18956
+ setBodyError(getBodyJsonValidationError(value));
18783
18957
  };
18784
18958
 
18785
18959
  const handleNext = () => {
18786
18960
  if (disabled || (name.trim() && prompt.trim().length >= promptMinLength)) setStep(2);
18787
18961
  };
18788
18962
 
18789
- const queryParamsHaveErrors = (rows: KeyValuePair[]): boolean =>
18790
- rows.some((row) => {
18791
- const hasInput = row.key.trim() !== "" || row.value.trim() !== "";
18792
- if (!hasInput) return false;
18793
- return (
18794
- validateQueryParamKey(row.key) !== undefined ||
18795
- validateQueryParamValue(row.value) !== undefined
18796
- );
18797
- });
18798
-
18799
18963
  const handleSubmit = () => {
18800
- if (step === 2) {
18801
- setStep2SubmitAttempted(true);
18802
- if (queryParamsHaveErrors(queryParams)) return;
18803
- }
18964
+ if (step !== 2) return;
18965
+
18966
+ setStep2SubmitAttempted(true);
18967
+
18968
+ const urlErr = getUrlSubmitValidationError(url);
18969
+ setUrlError(urlErr);
18970
+
18971
+ const bodyErr = getBodyJsonValidationError(body);
18972
+ setBodyError(bodyErr);
18973
+
18974
+ if (queryParamsHaveErrors(queryParams)) return;
18975
+ if (urlErr || bodyErr) return;
18976
+
18977
+ if (headerRowsHaveSubmitErrors(headers)) return;
18978
+
18804
18979
  const data: CreateFunctionData = {
18805
18980
  name: name.trim(),
18806
18981
  prompt: prompt.trim(),
@@ -18829,7 +19004,7 @@ export const CreateFunctionModal = React.forwardRef(
18829
19004
  queryParams: queryParams.map((q) => ({ ...q, value: substituteVars(q.value) })),
18830
19005
  body: substituteVars(body),
18831
19006
  };
18832
- const response = await onTestApi(step2);
19007
+ const response = await onTestApi(step2, { ...testVarValues });
18833
19008
  setApiResponse(response);
18834
19009
  } finally {
18835
19010
  setIsTesting(false);
@@ -18868,18 +19043,11 @@ export const CreateFunctionModal = React.forwardRef(
18868
19043
  });
18869
19044
  };
18870
19045
 
18871
- const headersHaveKeyErrors = headers.some(
18872
- (row) => row.key.trim() && HEADER_KEY_REGEX && !HEADER_KEY_REGEX.test(row.key)
18873
- );
18874
-
18875
19046
  const isStep1Valid =
18876
19047
  name.trim().length > 0 &&
18877
19048
  FUNCTION_NAME_REGEX.test(name.trim()) &&
18878
19049
  prompt.trim().length >= promptMinLength;
18879
19050
 
18880
- const isStep2Valid =
18881
- !urlError && !bodyError && !headersHaveKeyErrors && !queryParamsHaveErrors(queryParams);
18882
-
18883
19051
  const tabLabels: Record<FunctionTabType, string> = {
18884
19052
  header: \`Header (\${headers.length})\`,
18885
19053
  queryParams: \`Query params (\${queryParams.length})\`,
@@ -18937,10 +19105,15 @@ export const CreateFunctionModal = React.forwardRef(
18937
19105
  maxLength={FUNCTION_NAME_MAX}
18938
19106
  disabled={disabled}
18939
19107
  onChange={(e) => {
18940
- setName(e.target.value);
18941
- if (nameError) validateName(e.target.value);
19108
+ const normalized = normalizeFunctionNameInput(
19109
+ e.target.value
19110
+ );
19111
+ setName(normalized);
19112
+ if (nameError) validateName(normalized);
18942
19113
  }}
18943
- onBlur={(e) => validateName(e.target.value)}
19114
+ onBlur={(e) =>
19115
+ validateName(normalizeFunctionNameInput(e.target.value))
19116
+ }
18944
19117
  placeholder="Enter name of the function"
18945
19118
  className={cn(inputCls, "pr-16")}
18946
19119
  />
@@ -18982,11 +19155,13 @@ export const CreateFunctionModal = React.forwardRef(
18982
19155
  <span className="text-sm text-semantic-text-muted tracking-[0.048px]">
18983
19156
  API URL
18984
19157
  </span>
18985
- <div
19158
+ <div
18986
19159
  className={cn(
18987
- "flex h-[42px] rounded border border-solid border-semantic-border-input overflow-visible bg-semantic-bg-primary",
18988
- "focus-within:border-semantic-border-input-focus focus-within:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
18989
- "transition-shadow"
19160
+ "flex h-[42px] rounded border border-solid overflow-visible bg-semantic-bg-primary",
19161
+ "transition-shadow",
19162
+ urlError
19163
+ ? "border-semantic-error-primary focus-within:border-semantic-error-primary"
19164
+ : "border-semantic-border-input focus-within:border-semantic-border-input-focus focus-within:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
18990
19165
  )}
18991
19166
  >
18992
19167
  {/* Method selector */}
@@ -19040,6 +19215,8 @@ export const CreateFunctionModal = React.forwardRef(
19040
19215
  setUrlPopupStyle(undefined);
19041
19216
  }}
19042
19217
  placeholder="Enter URL or Type {{ to add variables"
19218
+ aria-invalid={Boolean(urlError)}
19219
+ aria-describedby={urlError ? "fn-api-url-error" : undefined}
19043
19220
  className={cn(
19044
19221
  "h-full w-full px-3 text-base text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none",
19045
19222
  disabled && "opacity-50 cursor-not-allowed"
@@ -19050,14 +19227,16 @@ export const CreateFunctionModal = React.forwardRef(
19050
19227
  variableGroups={urlTrigger ? variableGroups : undefined}
19051
19228
  filterQuery={urlTrigger?.query ?? ""}
19052
19229
  onSelect={handleUrlVarSelect}
19053
- onAddVariable={onAddVariable ? handleAddVariableClick : undefined}
19230
+ onAddVariable={onAddVariable ? handleAddVariableFromUrl : undefined}
19054
19231
  onEditVariable={onEditVariable ? handleEditVariableClick : undefined}
19055
19232
  style={urlPopupStyle}
19056
19233
  />
19057
19234
  </div>
19058
19235
  </div>
19059
19236
  {urlError && (
19060
- <p className="m-0 text-sm text-semantic-error-primary">{urlError}</p>
19237
+ <p id="fn-api-url-error" className="m-0 text-sm text-semantic-error-primary">
19238
+ {urlError}
19239
+ </p>
19061
19240
  )}
19062
19241
  </div>
19063
19242
 
@@ -19095,10 +19274,20 @@ export const CreateFunctionModal = React.forwardRef(
19095
19274
  keyMaxLength={HEADER_KEY_MAX}
19096
19275
  valueMaxLength={HEADER_VALUE_MAX}
19097
19276
  keyRegex={HEADER_KEY_REGEX}
19098
- keyRegexError="Invalid header key. Use only alphanumeric and !#$%&'*+-.^_\`|~ characters."
19277
+ keyRegexError={HEADER_KEY_INVALID_MESSAGE}
19278
+ getRowErrors={(row) => {
19279
+ if (!step2SubmitAttempted) {
19280
+ const errors: RowErrors = {};
19281
+ if (row.key.trim() && !HEADER_KEY_REGEX.test(row.key)) {
19282
+ errors.key = HEADER_KEY_INVALID_MESSAGE;
19283
+ }
19284
+ return errors;
19285
+ }
19286
+ return getHeaderRowSubmitErrors(row);
19287
+ }}
19099
19288
  sessionVariables={sessionVariables}
19100
19289
  variableGroups={variableGroups}
19101
- onAddVariable={handleAddVariableClick}
19290
+ onAddVariable={handleAddVariableFromHeader}
19102
19291
  onEditVariable={handleEditVariableClick}
19103
19292
  disabled={disabled}
19104
19293
  />
@@ -19120,7 +19309,7 @@ export const CreateFunctionModal = React.forwardRef(
19120
19309
  }}
19121
19310
  sessionVariables={sessionVariables}
19122
19311
  variableGroups={variableGroups}
19123
- onAddVariable={handleAddVariableClick}
19312
+ onAddVariable={handleAddVariableFromQuery}
19124
19313
  onEditVariable={handleEditVariableClick}
19125
19314
  disabled={disabled}
19126
19315
  />
@@ -19136,6 +19325,8 @@ export const CreateFunctionModal = React.forwardRef(
19136
19325
  value={body}
19137
19326
  maxLength={BODY_MAX}
19138
19327
  disabled={disabled}
19328
+ aria-invalid={Boolean(bodyError)}
19329
+ aria-describedby={bodyError ? "fn-body-error" : undefined}
19139
19330
  onChange={(e) => {
19140
19331
  setBody(e.target.value);
19141
19332
  if (bodyError) validateBody(e.target.value);
@@ -19165,13 +19356,15 @@ export const CreateFunctionModal = React.forwardRef(
19165
19356
  variableGroups={bodyTrigger ? variableGroups : undefined}
19166
19357
  filterQuery={bodyTrigger?.query ?? ""}
19167
19358
  onSelect={handleBodyVarSelect}
19168
- onAddVariable={onAddVariable ? handleAddVariableClick : undefined}
19359
+ onAddVariable={onAddVariable ? handleAddVariableFromBody : undefined}
19169
19360
  onEditVariable={onEditVariable ? handleEditVariableClick : undefined}
19170
19361
  style={bodyPopupStyle}
19171
19362
  />
19172
19363
  </div>
19173
19364
  {bodyError && (
19174
- <p className="m-0 text-sm text-semantic-error-primary">{bodyError}</p>
19365
+ <p id="fn-body-error" className="m-0 text-sm text-semantic-error-primary">
19366
+ {bodyError}
19367
+ </p>
19175
19368
  )}
19176
19369
  </div>
19177
19370
  )}
@@ -19270,10 +19463,11 @@ export const CreateFunctionModal = React.forwardRef(
19270
19463
  Back
19271
19464
  </Button>
19272
19465
  <Button
19466
+ type="button"
19273
19467
  variant="default"
19274
19468
  className="flex-1 sm:flex-none"
19275
19469
  onClick={handleSubmit}
19276
- disabled={!isStep2Valid || disabled}
19470
+ disabled={disabled}
19277
19471
  >
19278
19472
  Submit
19279
19473
  </Button>
@@ -19285,7 +19479,7 @@ export const CreateFunctionModal = React.forwardRef(
19285
19479
 
19286
19480
  <VariableFormModal
19287
19481
  open={varModalOpen}
19288
- onOpenChange={setVarModalOpen}
19482
+ onOpenChange={handleVarModalOpenChange}
19289
19483
  mode={varModalMode}
19290
19484
  initialData={varModalInitialData}
19291
19485
  onSave={handleVariableSave}
@@ -20956,11 +21150,20 @@ export interface CreateFunctionData
20956
21150
  extends CreateFunctionStep1Data,
20957
21151
  CreateFunctionStep2Data {}
20958
21152
 
21153
+ /**
21154
+ * Test values for "Test Your API": keys are full placeholders as in the form
21155
+ * (e.g. \`"{{Caller number}}"\`), values are the strings the user entered.
21156
+ */
21157
+ export type CreateFunctionTestValues = Record<string, string>;
21158
+
20959
21159
  export interface CreateFunctionModalProps {
20960
21160
  open: boolean;
20961
21161
  onOpenChange: (open: boolean) => void;
20962
21162
  onSubmit?: (data: CreateFunctionData) => void;
20963
- onTestApi?: (step2: CreateFunctionStep2Data) => Promise<string>;
21163
+ onTestApi?: (
21164
+ step2: CreateFunctionStep2Data,
21165
+ testValues?: CreateFunctionTestValues
21166
+ ) => Promise<string>;
20964
21167
  /** Pre-fills all fields \u2014 use when opening the modal to edit an existing function */
20965
21168
  initialData?: Partial<CreateFunctionData>;
20966
21169
  /** When true, changes the modal title to "Edit Function" */
@@ -20977,7 +21180,12 @@ export interface CreateFunctionModalProps {
20977
21180
  sessionVariables?: string[];
20978
21181
  /** Grouped variables shown in the {{ autocomplete popup (overrides flat list display when provided) */
20979
21182
  variableGroups?: VariableGroup[];
20980
- /** Called when user saves a new variable from the autocomplete popup */
21183
+ /**
21184
+ * Called when user saves a new variable from the autocomplete popup.
21185
+ * The modal replaces the open \`{{\u2026\` fragment in the focused field with \`{{name}}\`.
21186
+ * When using \`variableGroups\`, merge the new item into the matching group in your state
21187
+ * so it appears in the dropdown on the next open.
21188
+ */
20981
21189
  onAddVariable?: (data: VariableFormData) => void;
20982
21190
  /** Called when user edits a variable from the autocomplete popup */
20983
21191
  onEditVariable?: (originalName: string, data: VariableFormData) => void;
@@ -21040,7 +21248,10 @@ export interface IvrBotConfigProps {
21040
21248
  functionEditDisabled?: boolean;
21041
21249
  /** Independently disables the function delete button */
21042
21250
  functionDeleteDisabled?: boolean;
21043
- onTestApi?: (step2: CreateFunctionStep2Data) => Promise<string>;
21251
+ onTestApi?: (
21252
+ step2: CreateFunctionStep2Data,
21253
+ testValues?: CreateFunctionTestValues
21254
+ ) => Promise<string>;
21044
21255
  /** Hover text for the info icon in the Functions card header */
21045
21256
  functionsInfoTooltip?: string;
21046
21257
  /** Hover text for the info icon in the Knowledge Base card header */
@@ -21132,6 +21343,7 @@ export type {
21132
21343
  IvrBotConfigProps,
21133
21344
  IvrBotConfigData,
21134
21345
  CreateFunctionData,
21346
+ CreateFunctionTestValues,
21135
21347
  CreateFunctionStep1Data,
21136
21348
  CreateFunctionStep2Data,
21137
21349
  FunctionItem,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-ui",
3
- "version": "0.0.221",
3
+ "version": "0.0.222",
4
4
  "description": "CLI for adding myOperator UI components to your project",
5
5
  "type": "module",
6
6
  "exports": "./dist/index.js",