@mittwald/flow-react-components 1.6.0-next.21 → 1.6.0-next.24

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.
@@ -12,7 +12,7 @@ import { defaultLightTheme } from "./themes/defaultEditorTheme.mjs";
12
12
  import { useCodeEditorExtensions } from "./hooks/useCodeEditorExtensions.mjs";
13
13
  import { CopyButton } from "../CopyButton/CopyButton.mjs";
14
14
  import clsx from "clsx";
15
- import { useCallback, useId, useState } from "react";
15
+ import { useCallback, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
16
16
  import { jsx, jsxs } from "react/jsx-runtime";
17
17
  import { useObjectRef } from "react-aria";
18
18
  import CodeMirror, { EditorView } from "@uiw/react-codemirror";
@@ -22,7 +22,7 @@ import CodeMirror, { EditorView } from "@uiw/react-codemirror";
22
22
  * @flowStatus new
23
23
  */
24
24
  var CodeEditor = flowComponent("CodeEditor", (props) => {
25
- const { ref, children, className, language, extensions, isReadOnly, isDisabled, isInvalid, isRequired, validationBehavior: _ignoredValidationBehavior, value, showLineNumbers = true, showCodeFolding = true, showCodeIndentationMakers = true, showLinterMarkers = true, showActiveLineMarker = true, copyable = true, height, minHeight, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...rest } = useControlledHostValueProps(props, "");
25
+ const { ref, children, className, language, extensions, isReadOnly, isDisabled, isInvalid, isRequired, validationBehavior: _ignoredValidationBehavior, value, onChange, onStatistics, showLineNumbers = true, showCodeFolding = true, showCodeIndentationMakers = true, showLinterMarkers = true, showActiveLineMarker = true, copyable = true, height, minHeight, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...rest } = useControlledHostValueProps(props, "");
26
26
  const { FieldErrorView, FieldErrorCaptureContext, wrapperProps, controlProps, fieldPropsContext } = useFieldComponent(props, "CodeEditor");
27
27
  const rootClassName = clsx(wrapperProps.className, CodeEditor_module_default.codeEditor, className);
28
28
  const labelId = useId();
@@ -83,14 +83,48 @@ var CodeEditor = flowComponent("CodeEditor", (props) => {
83
83
  */
84
84
  const labelledBy = joinIds(ariaLabelledBy, hasLabel && labelId);
85
85
  const describedBy = joinIds(hasDescription && descriptionId, controlProps["aria-describedby"]);
86
- const contentAttributes = EditorView.contentAttributes.of({
86
+ /**
87
+ * CodeMirror reconfigures the editor whenever `extensions`, `basicSetup` or
88
+ * `onChange` is a new object. All three are stable, so a render with
89
+ * unchanged props leaves the editor as it is.
90
+ */
91
+ const allExtensions = useMemo(() => [...enabledExtensions, EditorView.contentAttributes.of({
87
92
  ...labelledBy ? { "aria-labelledby": labelledBy } : {},
88
93
  ...ariaLabel ? { "aria-label": ariaLabel } : {},
89
94
  ...describedBy ? { "aria-describedby": describedBy } : {},
90
95
  ...isRequired ? { "aria-required": "true" } : {},
91
96
  ...isInvalid ? { "aria-invalid": "true" } : {},
92
97
  ...isDisabled ? { "aria-disabled": "true" } : {}
98
+ })], [
99
+ enabledExtensions,
100
+ labelledBy,
101
+ ariaLabel,
102
+ describedBy,
103
+ isRequired,
104
+ isInvalid,
105
+ isDisabled
106
+ ]);
107
+ const handlersRef = useRef({
108
+ onChange,
109
+ onStatistics
93
110
  });
111
+ useLayoutEffect(() => {
112
+ handlersRef.current = {
113
+ onChange,
114
+ onStatistics
115
+ };
116
+ });
117
+ const stableOnChange = useCallback((value) => handlersRef.current.onChange(value), []);
118
+ const stableOnStatistics = useCallback((statistics) => handlersRef.current.onStatistics?.(statistics), []);
119
+ const highlightActiveLine = showActiveLineMarker && !isDisabled;
120
+ const basicSetup = useMemo(() => ({
121
+ highlightActiveLine,
122
+ highlightActiveLineGutter: highlightActiveLine,
123
+ autocompletion: false,
124
+ lineNumbers: false,
125
+ foldGutter: false,
126
+ highlightSelectionMatches: false
127
+ }), [highlightActiveLine]);
94
128
  const localRef = useObjectRef(ref);
95
129
  useMakeFocusable(localRef);
96
130
  const isSkeleton = useSkeletonMode();
@@ -105,14 +139,9 @@ var CodeEditor = flowComponent("CodeEditor", (props) => {
105
139
  const editor = /* @__PURE__ */ jsxs(CodeMirror, {
106
140
  ...rest,
107
141
  value,
108
- basicSetup: {
109
- highlightActiveLine: showActiveLineMarker && !isDisabled,
110
- highlightActiveLineGutter: showActiveLineMarker && !isDisabled,
111
- autocompletion: false,
112
- lineNumbers: false,
113
- foldGutter: false,
114
- highlightSelectionMatches: false
115
- },
142
+ onChange: stableOnChange,
143
+ onStatistics: stableOnStatistics,
144
+ basicSetup,
116
145
  theme: defaultLightTheme,
117
146
  "data-invalid": isInvalid || void 0,
118
147
  "data-disabled": isDisabled || void 0,
@@ -122,7 +151,7 @@ var CodeEditor = flowComponent("CodeEditor", (props) => {
122
151
  ref: (codeMirrorRef) => {
123
152
  if (codeMirrorRef?.editor) localRef.current = codeMirrorRef.editor;
124
153
  },
125
- extensions: [...enabledExtensions, contentAttributes],
154
+ extensions: allExtensions,
126
155
  height: height ?? minHeight,
127
156
  children: [copyable && /* @__PURE__ */ jsx(CopyButton, {
128
157
  className: CodeEditor_module_default.copyButton,
@@ -1 +1 @@
1
- {"version":3,"file":"CodeEditor.mjs","names":["styles"],"sources":["../../../../../../../src/components/CodeEditor/CodeEditor.tsx"],"sourcesContent":["import type { ReactCodeMirrorProps } from \"@uiw/react-codemirror\";\nimport CodeMirror, { EditorView } from \"@uiw/react-codemirror\";\nimport { useCallback, useId, useState } from \"react\";\nimport {\n flowComponent,\n type FlowComponentProps,\n} from \"@/lib/componentFactory/flowComponent\";\nimport { useControlledHostValueProps } from \"@/lib/remote/useControlledHostValueProps\";\nimport { joinIds, useFieldComponent } from \"@/lib/hooks/useFieldComponent\";\nimport { type PropsContext, PropsContextProvider } from \"@/lib/propsContext\";\nimport clsx from \"clsx\";\nimport styles from \"./CodeEditor.module.scss\";\nimport { type CodeEditorLanguage } from \"@/components/CodeEditor/languages\";\nimport { useMakeFocusable } from \"@/lib/hooks/dom/useMakeFocusable\";\nimport { useObjectRef } from \"react-aria\";\nimport { defaultLightTheme } from \"@/components/CodeEditor/themes/defaultEditorTheme\";\nimport {\n type CodeEditorSetup,\n useCodeEditorExtensions,\n} from \"@/components/CodeEditor/hooks/useCodeEditorExtensions\";\nimport { CopyButton } from \"@/components/CopyButton\";\nimport { UiComponentTunnelExit } from \"@/components/UiComponentTunnel/UiComponentTunnelExit\";\nimport { useSkeletonMode } from \"@/components/SkeletonMode/skeletonModeContext\";\n\nexport interface CodeEditorProps\n extends\n Omit<ReactCodeMirrorProps, \"theme\" | \"lang\" | \"basicSetup\" | \"readOnly\">,\n CodeEditorSetup,\n FlowComponentProps {\n /** The initial code of an uncontrolled editor. */\n defaultValue?: string;\n /**\n * Whether the code can be read but not edited.\n *\n * @default false\n */\n isReadOnly?: boolean;\n /**\n * Whether the editor is disabled. The code can neither be edited, focused nor\n * copied.\n *\n * @default false\n */\n isDisabled?: boolean;\n /**\n * Whether the editor is displayed as invalid.\n *\n * @default false\n */\n isInvalid?: boolean;\n /** The elements class name. */\n className?: string;\n /**\n * The language the code is highlighted as. A language the editor does not\n * know is shown as plain text.\n */\n language?: CodeEditorLanguage | (string & {});\n /**\n * Whether a button to copy the code to the clipboard is shown.\n *\n * @default true\n */\n copyable?: boolean;\n /**\n * Whether the field must be filled in. Only marks the editor as required for\n * assistive technology — the editor does not validate itself.\n *\n * @default false\n */\n isRequired?: boolean;\n /**\n * @internal Set by `<Field />` on every form field. The editor has no\n * react-aria validation to forward it to and drops it.\n */\n validationBehavior?: unknown;\n}\n\n/**\n * @flr-generate all\n * @flowStatus new\n */\nexport const CodeEditor = flowComponent(\"CodeEditor\", (props) => {\n const {\n ref,\n children,\n className,\n language,\n extensions,\n isReadOnly,\n isDisabled,\n isInvalid,\n isRequired,\n validationBehavior: _ignoredValidationBehavior,\n value,\n showLineNumbers = true,\n showCodeFolding = true,\n showCodeIndentationMakers = true,\n showLinterMarkers = true,\n showActiveLineMarker = true,\n copyable = true,\n height,\n minHeight,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...rest\n } = useControlledHostValueProps(props, \"\");\n\n const {\n FieldErrorView,\n FieldErrorCaptureContext,\n wrapperProps,\n controlProps,\n fieldPropsContext,\n } = useFieldComponent(props, \"CodeEditor\");\n\n const rootClassName = clsx(\n wrapperProps.className,\n styles.codeEditor,\n className,\n );\n\n const labelId = useId();\n const descriptionId = useId();\n\n /**\n * Both are optional. Their refs tell whether they are rendered, so the editor\n * never references an element that does not exist – a dangling id fails HTML\n * validators and a11y checks. The callbacks are stable: a new one per render\n * would detach and re-attach on every render.\n */\n const [hasLabel, setHasLabel] = useState(false);\n const [hasDescription, setHasDescription] = useState(false);\n const labelRef = useCallback((element: Element | null) => {\n setHasLabel(!!element);\n }, []);\n const descriptionRef = useCallback((element: Element | null) => {\n setHasDescription(!!element);\n }, []);\n\n /**\n * The label and the field description are declared as children of the code\n * editor, but must be rendered outside of the editor itself. They are\n * tunneled out of the editor – just like the field error. Their ids are the\n * ones the editor references.\n */\n const propsContext: PropsContext = {\n ...fieldPropsContext,\n Label: {\n ...fieldPropsContext.Label,\n id: labelId,\n ref: labelRef,\n tunnel: { id: \"label\", component: \"CodeEditor\" },\n },\n FieldDescription: {\n ...fieldPropsContext.FieldDescription,\n id: descriptionId,\n ref: descriptionRef,\n tunnel: { id: \"fieldDescription\", component: \"CodeEditor\" },\n },\n };\n\n const enabledExtensions = useCodeEditorExtensions(language, extensions, {\n showLineNumbers: showLineNumbers,\n showCodeIndentationMakers: showCodeIndentationMakers,\n showCodeFolding: showCodeFolding,\n showLinterMarkers: showLinterMarkers,\n });\n\n /**\n * The editable element of CodeMirror is the `.cm-content` element – not the\n * root element the props are applied to. Its ARIA attributes are set through\n * the content attributes facet.\n *\n * Without a label, an `aria-label` names the editor instead.\n */\n const labelledBy = joinIds(ariaLabelledBy, hasLabel && labelId);\n const describedBy = joinIds(\n hasDescription && descriptionId,\n controlProps[\"aria-describedby\"],\n );\n\n const contentAttributes = EditorView.contentAttributes.of({\n ...(labelledBy ? { \"aria-labelledby\": labelledBy } : {}),\n ...(ariaLabel ? { \"aria-label\": ariaLabel } : {}),\n ...(describedBy ? { \"aria-describedby\": describedBy } : {}),\n ...(isRequired ? { \"aria-required\": \"true\" } : {}),\n ...(isInvalid ? { \"aria-invalid\": \"true\" } : {}),\n ...(isDisabled ? { \"aria-disabled\": \"true\" } : {}),\n });\n\n const localRef = useObjectRef(ref);\n\n useMakeFocusable(localRef);\n\n const isSkeleton = useSkeletonMode();\n\n /* No CodeMirror: one surface, as high as the code in the editor's line\n height. The children still render, so a label or a field description\n tunnels out and follows its own skeleton rule. */\n const skeleton = (\n <div\n className={clsx(\n styles.codeMirror,\n isReadOnly && styles.readonly,\n styles.skeleton,\n )}\n >\n <div\n className={styles.skeletonLines}\n style={{ height: height ?? minHeight }}\n >\n {(value ?? \"\").split(\"\\n\").map((line, index) => (\n <div key={index}>{line || \" \"}</div>\n ))}\n </div>\n {children}\n </div>\n );\n\n const editor = (\n <CodeMirror\n {...rest}\n value={value}\n basicSetup={{\n highlightActiveLine: showActiveLineMarker && !isDisabled,\n highlightActiveLineGutter: showActiveLineMarker && !isDisabled,\n autocompletion: false,\n lineNumbers: false,\n foldGutter: false,\n highlightSelectionMatches: false,\n }}\n theme={defaultLightTheme}\n data-invalid={isInvalid || undefined}\n data-disabled={isDisabled || undefined}\n readOnly={isReadOnly || isDisabled}\n editable={!isDisabled}\n className={clsx(styles.codeMirror, isReadOnly && styles.readonly)}\n ref={(codeMirrorRef) => {\n if (codeMirrorRef?.editor) {\n localRef.current = codeMirrorRef.editor;\n }\n }}\n extensions={[...enabledExtensions, contentAttributes]}\n height={height ?? minHeight}\n >\n {copyable && (\n <CopyButton\n className={styles.copyButton}\n size=\"s\"\n variant=\"soft\"\n text={value}\n isDisabled={isDisabled}\n />\n )}\n {children}\n </CodeMirror>\n );\n\n return (\n <div\n className={rootClassName}\n data-disabled={isDisabled || undefined}\n inert={isSkeleton || undefined}\n >\n <PropsContextProvider props={propsContext}>\n <UiComponentTunnelExit id=\"label\" component=\"CodeEditor\" />\n <FieldErrorCaptureContext>\n {isSkeleton ? skeleton : editor}\n </FieldErrorCaptureContext>\n <UiComponentTunnelExit id=\"fieldDescription\" component=\"CodeEditor\" />\n <FieldErrorView />\n </PropsContextProvider>\n </div>\n );\n});\n\nexport default CodeEditor;\n"],"mappings":"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"}
1
+ {"version":3,"file":"CodeEditor.mjs","names":["styles"],"sources":["../../../../../../../src/components/CodeEditor/CodeEditor.tsx"],"sourcesContent":["import type { ReactCodeMirrorProps, Statistics } from \"@uiw/react-codemirror\";\nimport CodeMirror, { EditorView } from \"@uiw/react-codemirror\";\nimport {\n useCallback,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport {\n flowComponent,\n type FlowComponentProps,\n} from \"@/lib/componentFactory/flowComponent\";\nimport { useControlledHostValueProps } from \"@/lib/remote/useControlledHostValueProps\";\nimport { joinIds, useFieldComponent } from \"@/lib/hooks/useFieldComponent\";\nimport { type PropsContext, PropsContextProvider } from \"@/lib/propsContext\";\nimport clsx from \"clsx\";\nimport styles from \"./CodeEditor.module.scss\";\nimport { type CodeEditorLanguage } from \"@/components/CodeEditor/languages\";\nimport { useMakeFocusable } from \"@/lib/hooks/dom/useMakeFocusable\";\nimport { useObjectRef } from \"react-aria\";\nimport { defaultLightTheme } from \"@/components/CodeEditor/themes/defaultEditorTheme\";\nimport {\n type CodeEditorSetup,\n useCodeEditorExtensions,\n} from \"@/components/CodeEditor/hooks/useCodeEditorExtensions\";\nimport { CopyButton } from \"@/components/CopyButton\";\nimport { UiComponentTunnelExit } from \"@/components/UiComponentTunnel/UiComponentTunnelExit\";\nimport { useSkeletonMode } from \"@/components/SkeletonMode/skeletonModeContext\";\n\nexport interface CodeEditorProps\n extends\n Omit<ReactCodeMirrorProps, \"theme\" | \"lang\" | \"basicSetup\" | \"readOnly\">,\n CodeEditorSetup,\n FlowComponentProps {\n /** The initial code of an uncontrolled editor. */\n defaultValue?: string;\n /**\n * Whether the code can be read but not edited.\n *\n * @default false\n */\n isReadOnly?: boolean;\n /**\n * Whether the editor is disabled. The code can neither be edited, focused nor\n * copied.\n *\n * @default false\n */\n isDisabled?: boolean;\n /**\n * Whether the editor is displayed as invalid.\n *\n * @default false\n */\n isInvalid?: boolean;\n /** The elements class name. */\n className?: string;\n /**\n * The language the code is highlighted as. A language the editor does not\n * know is shown as plain text.\n */\n language?: CodeEditorLanguage | (string & {});\n /**\n * Whether a button to copy the code to the clipboard is shown.\n *\n * @default true\n */\n copyable?: boolean;\n /**\n * Whether the field must be filled in. Only marks the editor as required for\n * assistive technology — the editor does not validate itself.\n *\n * @default false\n */\n isRequired?: boolean;\n /**\n * @internal Set by `<Field />` on every form field. The editor has no\n * react-aria validation to forward it to and drops it.\n */\n validationBehavior?: unknown;\n}\n\n/**\n * @flr-generate all\n * @flowStatus new\n */\nexport const CodeEditor = flowComponent(\"CodeEditor\", (props) => {\n const {\n ref,\n children,\n className,\n language,\n extensions,\n isReadOnly,\n isDisabled,\n isInvalid,\n isRequired,\n validationBehavior: _ignoredValidationBehavior,\n value,\n onChange,\n onStatistics,\n showLineNumbers = true,\n showCodeFolding = true,\n showCodeIndentationMakers = true,\n showLinterMarkers = true,\n showActiveLineMarker = true,\n copyable = true,\n height,\n minHeight,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...rest\n } = useControlledHostValueProps(props, \"\");\n\n const {\n FieldErrorView,\n FieldErrorCaptureContext,\n wrapperProps,\n controlProps,\n fieldPropsContext,\n } = useFieldComponent(props, \"CodeEditor\");\n\n const rootClassName = clsx(\n wrapperProps.className,\n styles.codeEditor,\n className,\n );\n\n const labelId = useId();\n const descriptionId = useId();\n\n /**\n * Both are optional. Their refs tell whether they are rendered, so the editor\n * never references an element that does not exist – a dangling id fails HTML\n * validators and a11y checks. The callbacks are stable: a new one per render\n * would detach and re-attach on every render.\n */\n const [hasLabel, setHasLabel] = useState(false);\n const [hasDescription, setHasDescription] = useState(false);\n const labelRef = useCallback((element: Element | null) => {\n setHasLabel(!!element);\n }, []);\n const descriptionRef = useCallback((element: Element | null) => {\n setHasDescription(!!element);\n }, []);\n\n /**\n * The label and the field description are declared as children of the code\n * editor, but must be rendered outside of the editor itself. They are\n * tunneled out of the editor – just like the field error. Their ids are the\n * ones the editor references.\n */\n const propsContext: PropsContext = {\n ...fieldPropsContext,\n Label: {\n ...fieldPropsContext.Label,\n id: labelId,\n ref: labelRef,\n tunnel: { id: \"label\", component: \"CodeEditor\" },\n },\n FieldDescription: {\n ...fieldPropsContext.FieldDescription,\n id: descriptionId,\n ref: descriptionRef,\n tunnel: { id: \"fieldDescription\", component: \"CodeEditor\" },\n },\n };\n\n const enabledExtensions = useCodeEditorExtensions(language, extensions, {\n showLineNumbers: showLineNumbers,\n showCodeIndentationMakers: showCodeIndentationMakers,\n showCodeFolding: showCodeFolding,\n showLinterMarkers: showLinterMarkers,\n });\n\n /**\n * The editable element of CodeMirror is the `.cm-content` element – not the\n * root element the props are applied to. Its ARIA attributes are set through\n * the content attributes facet.\n *\n * Without a label, an `aria-label` names the editor instead.\n */\n const labelledBy = joinIds(ariaLabelledBy, hasLabel && labelId);\n const describedBy = joinIds(\n hasDescription && descriptionId,\n controlProps[\"aria-describedby\"],\n );\n\n /**\n * CodeMirror reconfigures the editor whenever `extensions`, `basicSetup` or\n * `onChange` is a new object. All three are stable, so a render with\n * unchanged props leaves the editor as it is.\n */\n const allExtensions = useMemo(\n () => [\n ...enabledExtensions,\n EditorView.contentAttributes.of({\n ...(labelledBy ? { \"aria-labelledby\": labelledBy } : {}),\n ...(ariaLabel ? { \"aria-label\": ariaLabel } : {}),\n ...(describedBy ? { \"aria-describedby\": describedBy } : {}),\n ...(isRequired ? { \"aria-required\": \"true\" } : {}),\n ...(isInvalid ? { \"aria-invalid\": \"true\" } : {}),\n ...(isDisabled ? { \"aria-disabled\": \"true\" } : {}),\n }),\n ],\n [\n enabledExtensions,\n labelledBy,\n ariaLabel,\n describedBy,\n isRequired,\n isInvalid,\n isDisabled,\n ],\n );\n\n /*\n * A new `onChange` on every render would reconfigure the editor as well.\n * CodeMirror's update listener captures `onStatistics` along with it, so both\n * call the latest handler through a ref.\n */\n const handlersRef = useRef({ onChange, onStatistics });\n useLayoutEffect(() => {\n handlersRef.current = { onChange, onStatistics };\n });\n const stableOnChange = useCallback(\n (value: string) => handlersRef.current.onChange(value),\n [],\n );\n const stableOnStatistics = useCallback(\n (statistics: Statistics) => handlersRef.current.onStatistics?.(statistics),\n [],\n );\n\n const highlightActiveLine = showActiveLineMarker && !isDisabled;\n const basicSetup = useMemo(\n () => ({\n highlightActiveLine,\n highlightActiveLineGutter: highlightActiveLine,\n autocompletion: false,\n lineNumbers: false,\n foldGutter: false,\n highlightSelectionMatches: false,\n }),\n [highlightActiveLine],\n );\n\n const localRef = useObjectRef(ref);\n\n useMakeFocusable(localRef);\n\n const isSkeleton = useSkeletonMode();\n\n /* No CodeMirror: one surface, as high as the code in the editor's line\n height. The children still render, so a label or a field description\n tunnels out and follows its own skeleton rule. */\n const skeleton = (\n <div\n className={clsx(\n styles.codeMirror,\n isReadOnly && styles.readonly,\n styles.skeleton,\n )}\n >\n <div\n className={styles.skeletonLines}\n style={{ height: height ?? minHeight }}\n >\n {(value ?? \"\").split(\"\\n\").map((line, index) => (\n <div key={index}>{line || \" \"}</div>\n ))}\n </div>\n {children}\n </div>\n );\n\n const editor = (\n <CodeMirror\n {...rest}\n value={value}\n onChange={stableOnChange}\n onStatistics={stableOnStatistics}\n basicSetup={basicSetup}\n theme={defaultLightTheme}\n data-invalid={isInvalid || undefined}\n data-disabled={isDisabled || undefined}\n readOnly={isReadOnly || isDisabled}\n editable={!isDisabled}\n className={clsx(styles.codeMirror, isReadOnly && styles.readonly)}\n ref={(codeMirrorRef) => {\n if (codeMirrorRef?.editor) {\n localRef.current = codeMirrorRef.editor;\n }\n }}\n extensions={allExtensions}\n height={height ?? minHeight}\n >\n {copyable && (\n <CopyButton\n className={styles.copyButton}\n size=\"s\"\n variant=\"soft\"\n text={value}\n isDisabled={isDisabled}\n />\n )}\n {children}\n </CodeMirror>\n );\n\n return (\n <div\n className={rootClassName}\n data-disabled={isDisabled || undefined}\n inert={isSkeleton || undefined}\n >\n <PropsContextProvider props={propsContext}>\n <UiComponentTunnelExit id=\"label\" component=\"CodeEditor\" />\n <FieldErrorCaptureContext>\n {isSkeleton ? skeleton : editor}\n </FieldErrorCaptureContext>\n <UiComponentTunnelExit id=\"fieldDescription\" component=\"CodeEditor\" />\n <FieldErrorView />\n </PropsContextProvider>\n </div>\n );\n});\n\nexport default CodeEditor;\n"],"mappings":"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"}
@@ -2,38 +2,51 @@
2
2
  /* */
3
3
  import supportedCodeEditorLanguages, { isCodeEditorLanguage } from "../languages/index.mjs";
4
4
  import { gutterSpacer } from "../extensions/gutterSpacer.mjs";
5
+ import { useMemo } from "react";
5
6
  import { lineNumbers } from "@uiw/react-codemirror";
6
7
  import { foldGutter } from "@codemirror/language";
7
8
  import { lintGutter } from "@codemirror/lint";
8
9
  import { indentationMarkers } from "@replit/codemirror-indentation-markers";
9
10
  //#region src/components/CodeEditor/hooks/useCodeEditorExtensions.tsx
10
11
  var markerColor = "var(--code-editor--indentation-marker-color)";
11
- var indentationMarkerColors = {
12
+ var codeIndentationMarkers = indentationMarkers({ colors: {
12
13
  light: markerColor,
13
14
  dark: markerColor,
14
15
  activeLight: markerColor,
15
16
  activeDark: markerColor
16
- };
17
+ } });
18
+ var noExtensions = [];
17
19
  /** @internal */
18
- var useCodeEditorExtensions = (language, extensions = [], options = {
20
+ var useCodeEditorExtensions = (language, extensions = noExtensions, options = {
19
21
  showCodeFolding: true,
20
22
  showCodeIndentationMakers: true,
21
23
  showLineNumbers: true,
22
24
  showLinterMarkers: true
23
25
  }) => {
24
- if (options.showLineNumbers) extensions.push(lineNumbers());
25
- if (options.showCodeFolding) extensions.push(foldGutter({ markerDOM: (open) => {
26
- const element = document.createElement("span");
27
- element.classList.add("cm-foldGutter");
28
- element.classList.add(open ? "cm-foldGutter-open" : "cm-foldGutter-closed");
29
- element.innerText = open ? "⌄" : "›";
30
- return element;
31
- } }));
32
- if (options.showLinterMarkers) extensions.push(lintGutter());
33
- if (options.showLineNumbers && (!options.showCodeFolding || !options.showLinterMarkers)) extensions.push(gutterSpacer());
34
- if (options.showCodeIndentationMakers) extensions.push(indentationMarkers({ colors: indentationMarkerColors }));
35
- if (language && isCodeEditorLanguage(language)) supportedCodeEditorLanguages[language]?.map((loader) => extensions.push(loader()));
36
- return extensions;
26
+ const { showCodeFolding, showCodeIndentationMakers, showLineNumbers, showLinterMarkers } = options;
27
+ return useMemo(() => {
28
+ const enabledExtensions = [...extensions];
29
+ if (showLineNumbers) enabledExtensions.push(lineNumbers());
30
+ if (showCodeFolding) enabledExtensions.push(foldGutter({ markerDOM: (open) => {
31
+ const element = document.createElement("span");
32
+ element.classList.add("cm-foldGutter");
33
+ element.classList.add(open ? "cm-foldGutter-open" : "cm-foldGutter-closed");
34
+ element.innerText = open ? "⌄" : "›";
35
+ return element;
36
+ } }));
37
+ if (showLinterMarkers) enabledExtensions.push(lintGutter());
38
+ if (showLineNumbers && (!showCodeFolding || !showLinterMarkers)) enabledExtensions.push(gutterSpacer());
39
+ if (showCodeIndentationMakers) enabledExtensions.push(codeIndentationMarkers);
40
+ if (language && isCodeEditorLanguage(language)) for (const loader of supportedCodeEditorLanguages[language] ?? []) enabledExtensions.push(loader());
41
+ return enabledExtensions;
42
+ }, [
43
+ language,
44
+ extensions,
45
+ showCodeFolding,
46
+ showCodeIndentationMakers,
47
+ showLineNumbers,
48
+ showLinterMarkers
49
+ ]);
37
50
  };
38
51
  //#endregion
39
52
  export { useCodeEditorExtensions };
@@ -1 +1 @@
1
- {"version":3,"file":"useCodeEditorExtensions.mjs","names":[],"sources":["../../../../../../../../src/components/CodeEditor/hooks/useCodeEditorExtensions.tsx"],"sourcesContent":["import { type Extension, lineNumbers } from \"@uiw/react-codemirror\";\nimport { lintGutter } from \"@codemirror/lint\";\nimport supportedCodeEditorLanguages, {\n isCodeEditorLanguage,\n} from \"@/components/CodeEditor/languages\";\nimport { indentationMarkers } from \"@replit/codemirror-indentation-markers\";\nimport { foldGutter } from \"@codemirror/language\";\nimport { gutterSpacer } from \"@/components/CodeEditor/extensions/gutterSpacer\";\n\n/*\n * Every slot gets the same color: the library picks between its light and dark\n * one by CodeMirror's dark theme flag, which Flow's theme never sets, and the\n * active block needs no color of its own.\n */\nconst markerColor = \"var(--code-editor--indentation-marker-color)\";\nconst indentationMarkerColors = {\n light: markerColor,\n dark: markerColor,\n activeLight: markerColor,\n activeDark: markerColor,\n};\n\nexport interface CodeEditorSetup {\n /**\n * Whether the line the cursor is on is highlighted.\n *\n * @default true\n */\n showActiveLineMarker?: boolean;\n /**\n * Whether the gutter offers controls to fold and unfold code blocks.\n *\n * @default true\n */\n showCodeFolding?: boolean;\n /**\n * Whether indentation levels are visualized with guide lines.\n *\n * @default true\n */\n showCodeIndentationMakers?: boolean;\n /**\n * Whether line numbers are shown in the gutter.\n *\n * @default true\n */\n showLineNumbers?: boolean;\n /**\n * Whether linter results are shown in the gutter.\n *\n * @default true\n */\n showLinterMarkers?: boolean;\n}\n\n/** @internal */\nexport const useCodeEditorExtensions = (\n language?: string,\n extensions: Extension[] = [],\n options: CodeEditorSetup = {\n showCodeFolding: true,\n showCodeIndentationMakers: true,\n showLineNumbers: true,\n showLinterMarkers: true,\n },\n) => {\n if (options.showLineNumbers) {\n extensions.push(lineNumbers());\n }\n\n if (options.showCodeFolding) {\n extensions.push(\n foldGutter({\n markerDOM: (open) => {\n const element = document.createElement(\"span\");\n element.classList.add(\"cm-foldGutter\");\n element.classList.add(\n open ? \"cm-foldGutter-open\" : \"cm-foldGutter-closed\",\n );\n element.innerText = open ? \"⌄\" : \"›\";\n return element;\n },\n }),\n );\n }\n\n if (options.showLinterMarkers) {\n extensions.push(lintGutter());\n }\n\n if (\n options.showLineNumbers &&\n (!options.showCodeFolding || !options.showLinterMarkers)\n ) {\n extensions.push(gutterSpacer());\n }\n\n if (options.showCodeIndentationMakers) {\n extensions.push(indentationMarkers({ colors: indentationMarkerColors }));\n }\n\n if (language && isCodeEditorLanguage(language)) {\n supportedCodeEditorLanguages[language]?.map((loader) =>\n extensions.push(loader()),\n );\n }\n\n return extensions;\n};\n"],"mappings":";;;;;;;;;AAcA,IAAM,cAAc;AACpB,IAAM,0BAA0B;CAC9B,OAAO;CACP,MAAM;CACN,aAAa;CACb,YAAY;AACd;;AAoCA,IAAa,2BACX,UACA,aAA0B,CAAC,GAC3B,UAA2B;CACzB,iBAAiB;CACjB,2BAA2B;CAC3B,iBAAiB;CACjB,mBAAmB;AACrB,MACG;CACH,IAAI,QAAQ,iBACV,WAAW,KAAK,YAAY,CAAC;CAG/B,IAAI,QAAQ,iBACV,WAAW,KACT,WAAW,EACT,YAAY,SAAS;EACnB,MAAM,UAAU,SAAS,cAAc,MAAM;EAC7C,QAAQ,UAAU,IAAI,eAAe;EACrC,QAAQ,UAAU,IAChB,OAAO,uBAAuB,sBAChC;EACA,QAAQ,YAAY,OAAO,MAAM;EACjC,OAAO;CACT,EACF,CAAC,CACH;CAGF,IAAI,QAAQ,mBACV,WAAW,KAAK,WAAW,CAAC;CAG9B,IACE,QAAQ,oBACP,CAAC,QAAQ,mBAAmB,CAAC,QAAQ,oBAEtC,WAAW,KAAK,aAAa,CAAC;CAGhC,IAAI,QAAQ,2BACV,WAAW,KAAK,mBAAmB,EAAE,QAAQ,wBAAwB,CAAC,CAAC;CAGzE,IAAI,YAAY,qBAAqB,QAAQ,GAC3C,6BAA6B,SAAS,EAAE,KAAK,WAC3C,WAAW,KAAK,OAAO,CAAC,CAC1B;CAGF,OAAO;AACT"}
1
+ {"version":3,"file":"useCodeEditorExtensions.mjs","names":[],"sources":["../../../../../../../../src/components/CodeEditor/hooks/useCodeEditorExtensions.tsx"],"sourcesContent":["import { type Extension, lineNumbers } from \"@uiw/react-codemirror\";\nimport { lintGutter } from \"@codemirror/lint\";\nimport supportedCodeEditorLanguages, {\n isCodeEditorLanguage,\n} from \"@/components/CodeEditor/languages\";\nimport { indentationMarkers } from \"@replit/codemirror-indentation-markers\";\nimport { foldGutter } from \"@codemirror/language\";\nimport { gutterSpacer } from \"@/components/CodeEditor/extensions/gutterSpacer\";\nimport { useMemo } from \"react\";\n\n/*\n * Every slot gets the same color: the library picks between its light and dark\n * one by CodeMirror's dark theme flag, which Flow's theme never sets, and the\n * active block needs no color of its own.\n */\nconst markerColor = \"var(--code-editor--indentation-marker-color)\";\n\n/*\n * Created once: every call brings a new style module along, and CodeMirror\n * never unmounts a style module – one per render grows the document's styles.\n */\nconst codeIndentationMarkers = indentationMarkers({\n colors: {\n light: markerColor,\n dark: markerColor,\n activeLight: markerColor,\n activeDark: markerColor,\n },\n});\n\nexport interface CodeEditorSetup {\n /**\n * Whether the line the cursor is on is highlighted.\n *\n * @default true\n */\n showActiveLineMarker?: boolean;\n /**\n * Whether the gutter offers controls to fold and unfold code blocks.\n *\n * @default true\n */\n showCodeFolding?: boolean;\n /**\n * Whether indentation levels are visualized with guide lines.\n *\n * @default true\n */\n showCodeIndentationMakers?: boolean;\n /**\n * Whether line numbers are shown in the gutter.\n *\n * @default true\n */\n showLineNumbers?: boolean;\n /**\n * Whether linter results are shown in the gutter.\n *\n * @default true\n */\n showLinterMarkers?: boolean;\n}\n\nconst noExtensions: Extension[] = [];\n\n/** @internal */\nexport const useCodeEditorExtensions = (\n language?: string,\n extensions: Extension[] = noExtensions,\n options: CodeEditorSetup = {\n showCodeFolding: true,\n showCodeIndentationMakers: true,\n showLineNumbers: true,\n showLinterMarkers: true,\n },\n): Extension[] => {\n const {\n showCodeFolding,\n showCodeIndentationMakers,\n showLineNumbers,\n showLinterMarkers,\n } = options;\n\n return useMemo(() => {\n const enabledExtensions = [...extensions];\n\n if (showLineNumbers) {\n enabledExtensions.push(lineNumbers());\n }\n\n if (showCodeFolding) {\n enabledExtensions.push(\n foldGutter({\n markerDOM: (open) => {\n const element = document.createElement(\"span\");\n element.classList.add(\"cm-foldGutter\");\n element.classList.add(\n open ? \"cm-foldGutter-open\" : \"cm-foldGutter-closed\",\n );\n element.innerText = open ? \"⌄\" : \"›\";\n return element;\n },\n }),\n );\n }\n\n if (showLinterMarkers) {\n enabledExtensions.push(lintGutter());\n }\n\n if (showLineNumbers && (!showCodeFolding || !showLinterMarkers)) {\n enabledExtensions.push(gutterSpacer());\n }\n\n if (showCodeIndentationMakers) {\n enabledExtensions.push(codeIndentationMarkers);\n }\n\n if (language && isCodeEditorLanguage(language)) {\n for (const loader of supportedCodeEditorLanguages[language] ?? []) {\n enabledExtensions.push(loader());\n }\n }\n\n return enabledExtensions;\n }, [\n language,\n extensions,\n showCodeFolding,\n showCodeIndentationMakers,\n showLineNumbers,\n showLinterMarkers,\n ]);\n};\n"],"mappings":";;;;;;;;;;AAeA,IAAM,cAAc;AAMpB,IAAM,yBAAyB,mBAAmB,EAChD,QAAQ;CACN,OAAO;CACP,MAAM;CACN,aAAa;CACb,YAAY;AACd,EACF,CAAC;AAmCD,IAAM,eAA4B,CAAC;;AAGnC,IAAa,2BACX,UACA,aAA0B,cAC1B,UAA2B;CACzB,iBAAiB;CACjB,2BAA2B;CAC3B,iBAAiB;CACjB,mBAAmB;AACrB,MACgB;CAChB,MAAM,EACJ,iBACA,2BACA,iBACA,sBACE;CAEJ,OAAO,cAAc;EACnB,MAAM,oBAAoB,CAAC,GAAG,UAAU;EAExC,IAAI,iBACF,kBAAkB,KAAK,YAAY,CAAC;EAGtC,IAAI,iBACF,kBAAkB,KAChB,WAAW,EACT,YAAY,SAAS;GACnB,MAAM,UAAU,SAAS,cAAc,MAAM;GAC7C,QAAQ,UAAU,IAAI,eAAe;GACrC,QAAQ,UAAU,IAChB,OAAO,uBAAuB,sBAChC;GACA,QAAQ,YAAY,OAAO,MAAM;GACjC,OAAO;EACT,EACF,CAAC,CACH;EAGF,IAAI,mBACF,kBAAkB,KAAK,WAAW,CAAC;EAGrC,IAAI,oBAAoB,CAAC,mBAAmB,CAAC,oBAC3C,kBAAkB,KAAK,aAAa,CAAC;EAGvC,IAAI,2BACF,kBAAkB,KAAK,sBAAsB;EAG/C,IAAI,YAAY,qBAAqB,QAAQ,GAC3C,KAAK,MAAM,UAAU,6BAA6B,aAAa,CAAC,GAC9D,kBAAkB,KAAK,OAAO,CAAC;EAInC,OAAO;CACT,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;AACH"}
@@ -1,33 +1,65 @@
1
1
  "use client"
2
2
  /* */
3
- import { createContext, useCallback, useContext, useEffect, useRef } from "react";
3
+ import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  //#region src/components/DeprecationWarningProvider/DeprecationWarningProvider.tsx
6
6
  var DeprecationWarningContext = createContext(void 0);
7
+ /**
8
+ * Messages a provider has passed to its `onWarning`. Held by the provider, not
9
+ * keyed by the handler: an inline `onWarning` is a new function on every
10
+ * render.
11
+ */
12
+ var ReportedWarningsContext = createContext(void 0);
13
+ /** Messages already written to `console.warn`, across all providers. */
14
+ var consoleReportedWarnings = /* @__PURE__ */ new Set();
15
+ /**
16
+ * Fallback for a bare `DeprecationWarningContext.Provider`, which has no
17
+ * provider-held set.
18
+ */
19
+ var reportedWarningsByHandler = /* @__PURE__ */ new WeakMap();
20
+ var getHandlerReportedWarnings = (handler) => {
21
+ let reported = reportedWarningsByHandler.get(handler);
22
+ if (!reported) {
23
+ reported = /* @__PURE__ */ new Set();
24
+ reportedWarningsByHandler.set(handler, reported);
25
+ }
26
+ return reported;
27
+ };
7
28
  var DeprecationWarningProvider = (props) => {
8
29
  const { children, onWarning } = props;
30
+ const [reportedWarnings] = useState(() => /* @__PURE__ */ new Set());
9
31
  return /* @__PURE__ */ jsx(DeprecationWarningContext.Provider, {
10
32
  value: onWarning,
11
- children
33
+ children: /* @__PURE__ */ jsx(ReportedWarningsContext.Provider, {
34
+ value: reportedWarnings,
35
+ children
36
+ })
12
37
  });
13
38
  };
14
39
  var useWarnDeprecation = () => {
15
40
  const onWarning = useContext(DeprecationWarningContext);
16
- const reportedWarnings = useRef(/* @__PURE__ */ new Set());
41
+ const providerReportedWarnings = useContext(ReportedWarningsContext);
42
+ const handlerReportedWarnings = onWarning ? providerReportedWarnings ?? getHandlerReportedWarnings(onWarning) : void 0;
17
43
  const pendingWarnings = useRef([]);
18
44
  useEffect(() => {
45
+ if (pendingWarnings.current.length === 0) return;
19
46
  const warnings = pendingWarnings.current;
20
47
  pendingWarnings.current = [];
21
48
  for (const message of warnings) {
22
- console.warn(message);
23
- onWarning?.(message);
49
+ if (!consoleReportedWarnings.has(message)) {
50
+ consoleReportedWarnings.add(message);
51
+ console.warn(message);
52
+ }
53
+ if (onWarning && !handlerReportedWarnings?.has(message)) {
54
+ handlerReportedWarnings?.add(message);
55
+ onWarning(message);
56
+ }
24
57
  }
25
58
  });
26
59
  return useCallback((message) => {
27
- if (reportedWarnings.current.has(message)) return;
28
- reportedWarnings.current.add(message);
60
+ if (consoleReportedWarnings.has(message) && (handlerReportedWarnings?.has(message) ?? true) || pendingWarnings.current.includes(message)) return;
29
61
  pendingWarnings.current.push(message);
30
- }, []);
62
+ }, [handlerReportedWarnings]);
31
63
  };
32
64
  //#endregion
33
65
  export { DeprecationWarningContext, DeprecationWarningProvider, DeprecationWarningProvider as default, useWarnDeprecation };
@@ -1 +1 @@
1
- {"version":3,"file":"DeprecationWarningProvider.mjs","names":[],"sources":["../../../../../../../src/components/DeprecationWarningProvider/DeprecationWarningProvider.tsx"],"sourcesContent":["import {\n createContext,\n type FC,\n type PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useRef,\n} from \"react\";\n\nexport type DeprecationWarningHandler = (message: string) => void;\n\nexport interface DeprecationWarningProviderProps extends PropsWithChildren {\n onWarning?: DeprecationWarningHandler;\n}\n\nexport const DeprecationWarningContext = createContext<\n DeprecationWarningHandler | undefined\n>(undefined);\n\nexport const DeprecationWarningProvider: FC<DeprecationWarningProviderProps> = (\n props,\n) => {\n const { children, onWarning } = props;\n\n return (\n <DeprecationWarningContext.Provider value={onWarning}>\n {children}\n </DeprecationWarningContext.Provider>\n );\n};\n\nexport const useWarnDeprecation = () => {\n const onWarning = useContext(DeprecationWarningContext);\n const reportedWarnings = useRef(new Set<string>());\n const pendingWarnings = useRef<string[]>([]);\n\n useEffect(() => {\n const warnings = pendingWarnings.current;\n pendingWarnings.current = [];\n\n for (const message of warnings) {\n console.warn(message);\n onWarning?.(message);\n }\n });\n\n return useCallback((message: string) => {\n if (reportedWarnings.current.has(message)) {\n return;\n }\n\n reportedWarnings.current.add(message);\n pendingWarnings.current.push(message);\n }, []);\n};\n\nexport default DeprecationWarningProvider;\n"],"mappings":";;;;;AAgBA,IAAa,4BAA4B,cAEvC,KAAA,CAAS;AAEX,IAAa,8BACX,UACG;CACH,MAAM,EAAE,UAAU,cAAc;CAEhC,OACE,oBAAC,0BAA0B,UAA3B;EAAoC,OAAO;EACxC;CACiC,CAAA;AAExC;AAEA,IAAa,2BAA2B;CACtC,MAAM,YAAY,WAAW,yBAAyB;CACtD,MAAM,mBAAmB,uBAAO,IAAI,IAAY,CAAC;CACjD,MAAM,kBAAkB,OAAiB,CAAC,CAAC;CAE3C,gBAAgB;EACd,MAAM,WAAW,gBAAgB;EACjC,gBAAgB,UAAU,CAAC;EAE3B,KAAK,MAAM,WAAW,UAAU;GAC9B,QAAQ,KAAK,OAAO;GACpB,YAAY,OAAO;EACrB;CACF,CAAC;CAED,OAAO,aAAa,YAAoB;EACtC,IAAI,iBAAiB,QAAQ,IAAI,OAAO,GACtC;EAGF,iBAAiB,QAAQ,IAAI,OAAO;EACpC,gBAAgB,QAAQ,KAAK,OAAO;CACtC,GAAG,CAAC,CAAC;AACP"}
1
+ {"version":3,"file":"DeprecationWarningProvider.mjs","names":[],"sources":["../../../../../../../src/components/DeprecationWarningProvider/DeprecationWarningProvider.tsx"],"sourcesContent":["import {\n createContext,\n type FC,\n type PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nexport type DeprecationWarningHandler = (message: string) => void;\n\nexport interface DeprecationWarningProviderProps extends PropsWithChildren {\n onWarning?: DeprecationWarningHandler;\n}\n\nexport const DeprecationWarningContext = createContext<\n DeprecationWarningHandler | undefined\n>(undefined);\n\n/**\n * Messages a provider has passed to its `onWarning`. Held by the provider, not\n * keyed by the handler: an inline `onWarning` is a new function on every\n * render.\n */\nconst ReportedWarningsContext = createContext<Set<string> | undefined>(\n undefined,\n);\n\n/** Messages already written to `console.warn`, across all providers. */\nconst consoleReportedWarnings = new Set<string>();\n\n/**\n * Fallback for a bare `DeprecationWarningContext.Provider`, which has no\n * provider-held set.\n */\nconst reportedWarningsByHandler = new WeakMap<\n DeprecationWarningHandler,\n Set<string>\n>();\n\nconst getHandlerReportedWarnings = (handler: DeprecationWarningHandler) => {\n let reported = reportedWarningsByHandler.get(handler);\n if (!reported) {\n reported = new Set();\n reportedWarningsByHandler.set(handler, reported);\n }\n return reported;\n};\n\nexport const DeprecationWarningProvider: FC<DeprecationWarningProviderProps> = (\n props,\n) => {\n const { children, onWarning } = props;\n const [reportedWarnings] = useState(() => new Set<string>());\n\n return (\n <DeprecationWarningContext.Provider value={onWarning}>\n <ReportedWarningsContext.Provider value={reportedWarnings}>\n {children}\n </ReportedWarningsContext.Provider>\n </DeprecationWarningContext.Provider>\n );\n};\n\nexport const useWarnDeprecation = () => {\n const onWarning = useContext(DeprecationWarningContext);\n const providerReportedWarnings = useContext(ReportedWarningsContext);\n const handlerReportedWarnings = onWarning\n ? (providerReportedWarnings ?? getHandlerReportedWarnings(onWarning))\n : undefined;\n\n const pendingWarnings = useRef<string[]>([]);\n\n useEffect(() => {\n if (pendingWarnings.current.length === 0) {\n return;\n }\n\n const warnings = pendingWarnings.current;\n pendingWarnings.current = [];\n\n for (const message of warnings) {\n if (!consoleReportedWarnings.has(message)) {\n consoleReportedWarnings.add(message);\n console.warn(message);\n }\n if (onWarning && !handlerReportedWarnings?.has(message)) {\n handlerReportedWarnings?.add(message);\n onWarning(message);\n }\n }\n });\n\n return useCallback(\n (message: string) => {\n const isReported =\n consoleReportedWarnings.has(message) &&\n (handlerReportedWarnings?.has(message) ?? true);\n\n if (isReported || pendingWarnings.current.includes(message)) {\n return;\n }\n pendingWarnings.current.push(message);\n },\n [handlerReportedWarnings],\n );\n};\n\nexport default DeprecationWarningProvider;\n"],"mappings":";;;;;AAiBA,IAAa,4BAA4B,cAEvC,KAAA,CAAS;;;;;;AAOX,IAAM,0BAA0B,cAC9B,KAAA,CACF;;AAGA,IAAM,0CAA0B,IAAI,IAAY;;;;;AAMhD,IAAM,4CAA4B,IAAI,QAGpC;AAEF,IAAM,8BAA8B,YAAuC;CACzE,IAAI,WAAW,0BAA0B,IAAI,OAAO;CACpD,IAAI,CAAC,UAAU;EACb,2BAAW,IAAI,IAAI;EACnB,0BAA0B,IAAI,SAAS,QAAQ;CACjD;CACA,OAAO;AACT;AAEA,IAAa,8BACX,UACG;CACH,MAAM,EAAE,UAAU,cAAc;CAChC,MAAM,CAAC,oBAAoB,+BAAe,IAAI,IAAY,CAAC;CAE3D,OACE,oBAAC,0BAA0B,UAA3B;EAAoC,OAAO;EACzC,UAAA,oBAAC,wBAAwB,UAAzB;GAAkC,OAAO;GACtC;EAC+B,CAAA;CACA,CAAA;AAExC;AAEA,IAAa,2BAA2B;CACtC,MAAM,YAAY,WAAW,yBAAyB;CACtD,MAAM,2BAA2B,WAAW,uBAAuB;CACnE,MAAM,0BAA0B,YAC3B,4BAA4B,2BAA2B,SAAS,IACjE,KAAA;CAEJ,MAAM,kBAAkB,OAAiB,CAAC,CAAC;CAE3C,gBAAgB;EACd,IAAI,gBAAgB,QAAQ,WAAW,GACrC;EAGF,MAAM,WAAW,gBAAgB;EACjC,gBAAgB,UAAU,CAAC;EAE3B,KAAK,MAAM,WAAW,UAAU;GAC9B,IAAI,CAAC,wBAAwB,IAAI,OAAO,GAAG;IACzC,wBAAwB,IAAI,OAAO;IACnC,QAAQ,KAAK,OAAO;GACtB;GACA,IAAI,aAAa,CAAC,yBAAyB,IAAI,OAAO,GAAG;IACvD,yBAAyB,IAAI,OAAO;IACpC,UAAU,OAAO;GACnB;EACF;CACF,CAAC;CAED,OAAO,aACJ,YAAoB;EAKnB,IAHE,wBAAwB,IAAI,OAAO,MAClC,yBAAyB,IAAI,OAAO,KAAK,SAE1B,gBAAgB,QAAQ,SAAS,OAAO,GACxD;EAEF,gBAAgB,QAAQ,KAAK,OAAO;CACtC,GACA,CAAC,uBAAuB,CAC1B;AACF"}
@@ -13,7 +13,7 @@ import { getHeadingLevelWithOffset } from "./lib/getHeadingLevelWithOffset.mjs";
13
13
  import { isExternalHref } from "./lib/isExternalHref.mjs";
14
14
  import { SkeletonBlockText, SkeletonInlineContent } from "./components/SkeletonBlocks.mjs";
15
15
  import clsx from "clsx";
16
- import { Children, isValidElement, useId } from "react";
16
+ import { Children, isValidElement, useId, useMemo } from "react";
17
17
  import { jsx } from "react/jsx-runtime";
18
18
  import ReactMarkdown from "react-markdown";
19
19
  import remarkGfm from "remark-gfm";
@@ -26,7 +26,7 @@ var Markdown = (props) => {
26
26
  const { children, color = "default", className, headingOffset = 0, components: customComponents, ref, allowElement, allowedElements, disallowedElements, rehypePlugins, remarkPlugins, remarkRehypeOptions, skipHtml, unwrapDisallowed, urlTransform, ...rest } = props;
27
27
  const isSkeleton = useSkeletonMode();
28
28
  const clobberPrefix = `user-content-${useId().replace(/[^\w-]/g, "")}-`;
29
- const defaultComponents = {
29
+ const defaultComponents = useMemo(() => ({
30
30
  a: (props) => /* @__PURE__ */ jsx(Link, {
31
31
  id: props.id,
32
32
  "aria-label": props["aria-label"],
@@ -115,12 +115,16 @@ var Markdown = (props) => {
115
115
  color,
116
116
  children: /* @__PURE__ */ jsx("blockquote", { children: props.children })
117
117
  })
118
- };
118
+ }), [
119
+ color,
120
+ headingOffset,
121
+ isSkeleton
122
+ ]);
119
123
  const textContent = extractTextFromFirstChild(children);
120
- const mergedComponents = {
124
+ const mergedComponents = useMemo(() => ({
121
125
  ...defaultComponents,
122
126
  ...customComponents
123
- };
127
+ }), [defaultComponents, customComponents]);
124
128
  return /* @__PURE__ */ jsx("div", {
125
129
  className: clsx(Markdown_module_default.markdown, className),
126
130
  ...rest,
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.mjs","names":["styles"],"sources":["../../../../../../../src/components/Markdown/Markdown.tsx"],"sourcesContent":["import { CodeBlock } from \"@/components/CodeBlock\";\nimport { Heading } from \"@/components/Heading\";\nimport { InlineCode } from \"@/components/InlineCode\";\nimport { Link } from \"@/components/Link\";\nimport { Separator } from \"@/components/Separator\";\nimport { Text } from \"@/components/Text\";\nimport type { CSSProperties, FC, Ref } from \"react\";\nimport { Children, isValidElement, useId } from \"react\";\nimport type { Components, Options } from \"react-markdown\";\nimport ReactMarkdown from \"react-markdown\";\nimport styles from \"./Markdown.module.scss\";\nimport { extractTextFromFirstChild } from \"@/lib/react/remote\";\nimport type { AlphaColor, PropsWithClassName } from \"@/lib/types/props\";\nimport clsx from \"clsx\";\nimport remarkGfm from \"remark-gfm\";\nimport { getHeadingLevelWithOffset } from \"@/components/Markdown/lib/getHeadingLevelWithOffset\";\nimport { isExternalHref } from \"@/components/Markdown/lib/isExternalHref\";\nimport type { CodeEditorLanguage } from \"@/components/CodeEditor/languages\";\nimport {\n SkeletonBlockText,\n SkeletonInlineContent,\n} from \"@/components/Markdown/components/SkeletonBlocks\";\nimport { useSkeletonMode } from \"@/components/SkeletonMode/skeletonModeContext\";\n\nexport interface MarkdownProps\n extends PropsWithClassName, Omit<Options, \"components\"> {\n /** The color schema of the markdown component. @default \"default\" */\n color?: \"default\" | AlphaColor;\n /** Shifts all heading levels by the given offset. @default 0 */\n headingOffset?: number;\n /** Allows overriding markdown element renderers from outside. */\n components?: Components;\n /** @internal */\n style?: CSSProperties;\n /** A ref to the elements root. */\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * @flr-generate all\n * @flr-ignore-props components\n */\nexport const Markdown: FC<MarkdownProps> = (props) => {\n const {\n children,\n color = \"default\",\n className,\n headingOffset = 0,\n components: customComponents,\n ref,\n allowElement,\n allowedElements,\n disallowedElements,\n rehypePlugins,\n remarkPlugins,\n remarkRehypeOptions,\n skipHtml,\n unwrapDisallowed,\n urlTransform,\n ...rest\n } = props;\n\n const isSkeleton = useSkeletonMode();\n /* Footnote ids are per document, so two instances on one page would share\n them and a back link would jump into the other one. */\n const clobberPrefix = `user-content-${useId().replace(/[^\\w-]/g, \"\")}-`;\n\n const defaultComponents: Components = {\n /* GFM footnotes link through `id` (the back link's target), `aria-label`\n and `aria-describedby`. */\n a: (props) => (\n <Link\n id={props.id}\n aria-label={props[\"aria-label\"]}\n aria-describedby={props[\"aria-describedby\"]}\n target={isExternalHref(props.href) ? \"_blank\" : undefined}\n color={color}\n href={props.href}\n >\n {props.children}\n </Link>\n ),\n p: (props) => (\n <Text elementType=\"p\" color={color}>\n {props.children}\n </Text>\n ),\n code: (props) => <InlineCode color={color}>{props.children}</InlineCode>,\n h1: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(1, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h2: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(2, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h3: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(3, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h4: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(4, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h5: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(5, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h6: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(6, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n hr: () => <Separator />,\n pre: (props) => {\n const preElementContent = Children.toArray(props.children)[0];\n\n return (\n <CodeBlock\n copyable={false}\n language={\n isValidElement<{ className?: string }>(preElementContent) &&\n preElementContent.props.className\n ? (preElementContent.props.className.replace(\n \"language-\",\n \"\",\n ) as CodeEditorLanguage)\n : undefined\n }\n code={String(\n isValidElement<{ children: string }>(preElementContent)\n ? preElementContent.props.children\n : preElementContent,\n ).trim()}\n />\n );\n },\n ul: ({ node: ignoredNode, ...props }) => (\n <SkeletonBlockText color={color}>\n <ul {...props} />\n </SkeletonBlockText>\n ),\n ol: ({ node: ignoredNode, ...props }) => (\n <SkeletonBlockText color={color}>\n <ol {...props} />\n </SkeletonBlockText>\n ),\n li: ({ node: ignoredNode, children, ...props }) => (\n <li {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </li>\n ),\n th: ({ node: ignoredNode, children, ...props }) => (\n <th {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </th>\n ),\n td: ({ node: ignoredNode, children, ...props }) => (\n <td {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </td>\n ),\n /* A text bar resets the mode for its content, so the image reads it\n from here. A skeleton issues no request. */\n img: ({ node: ignoredNode, ...props }) =>\n isSkeleton ? null : <img {...props} />,\n blockquote: (props) => (\n <SkeletonBlockText color={color}>\n <blockquote>{props.children}</blockquote>\n </SkeletonBlockText>\n ),\n };\n\n const textContent = extractTextFromFirstChild(children);\n const mergedComponents: Components = {\n ...defaultComponents,\n ...customComponents,\n };\n\n return (\n <div className={clsx(styles.markdown, className)} {...rest} ref={ref}>\n <ReactMarkdown\n allowElement={allowElement}\n allowedElements={allowedElements}\n disallowedElements={disallowedElements}\n rehypePlugins={rehypePlugins}\n remarkPlugins={remarkPlugins ?? [remarkGfm]}\n remarkRehypeOptions={{ clobberPrefix, ...remarkRehypeOptions }}\n skipHtml={skipHtml}\n unwrapDisallowed={unwrapDisallowed}\n urlTransform={urlTransform}\n components={mergedComponents}\n >\n {textContent}\n </ReactMarkdown>\n </div>\n );\n};\n\nexport default Markdown;\n"],"mappings":"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"}
1
+ {"version":3,"file":"Markdown.mjs","names":["styles"],"sources":["../../../../../../../src/components/Markdown/Markdown.tsx"],"sourcesContent":["import { CodeBlock } from \"@/components/CodeBlock\";\nimport { Heading } from \"@/components/Heading\";\nimport { InlineCode } from \"@/components/InlineCode\";\nimport { Link } from \"@/components/Link\";\nimport { Separator } from \"@/components/Separator\";\nimport { Text } from \"@/components/Text\";\nimport type { CSSProperties, FC, Ref } from \"react\";\nimport { Children, isValidElement, useId, useMemo } from \"react\";\nimport type { Components, Options } from \"react-markdown\";\nimport ReactMarkdown from \"react-markdown\";\nimport styles from \"./Markdown.module.scss\";\nimport { extractTextFromFirstChild } from \"@/lib/react/remote\";\nimport type { AlphaColor, PropsWithClassName } from \"@/lib/types/props\";\nimport clsx from \"clsx\";\nimport remarkGfm from \"remark-gfm\";\nimport { getHeadingLevelWithOffset } from \"@/components/Markdown/lib/getHeadingLevelWithOffset\";\nimport { isExternalHref } from \"@/components/Markdown/lib/isExternalHref\";\nimport type { CodeEditorLanguage } from \"@/components/CodeEditor/languages\";\nimport {\n SkeletonBlockText,\n SkeletonInlineContent,\n} from \"@/components/Markdown/components/SkeletonBlocks\";\nimport { useSkeletonMode } from \"@/components/SkeletonMode/skeletonModeContext\";\n\nexport interface MarkdownProps\n extends PropsWithClassName, Omit<Options, \"components\"> {\n /** The color schema of the markdown component. @default \"default\" */\n color?: \"default\" | AlphaColor;\n /** Shifts all heading levels by the given offset. @default 0 */\n headingOffset?: number;\n /** Allows overriding markdown element renderers from outside. */\n components?: Components;\n /** @internal */\n style?: CSSProperties;\n /** A ref to the elements root. */\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * @flr-generate all\n * @flr-ignore-props components\n */\nexport const Markdown: FC<MarkdownProps> = (props) => {\n const {\n children,\n color = \"default\",\n className,\n headingOffset = 0,\n components: customComponents,\n ref,\n allowElement,\n allowedElements,\n disallowedElements,\n rehypePlugins,\n remarkPlugins,\n remarkRehypeOptions,\n skipHtml,\n unwrapDisallowed,\n urlTransform,\n ...rest\n } = props;\n\n const isSkeleton = useSkeletonMode();\n /* Footnote ids are per document, so two instances on one page would share\n them and a back link would jump into the other one. */\n const clobberPrefix = `user-content-${useId().replace(/[^\\w-]/g, \"\")}-`;\n\n /* react-markdown uses the renderers as element types: new ones on every\n render would remount the whole tree. */\n const defaultComponents = useMemo<Components>(\n () => ({\n /* GFM footnotes link through `id` (the back link's target), `aria-label`\n and `aria-describedby`. */\n a: (props) => (\n <Link\n id={props.id}\n aria-label={props[\"aria-label\"]}\n aria-describedby={props[\"aria-describedby\"]}\n target={isExternalHref(props.href) ? \"_blank\" : undefined}\n color={color}\n href={props.href}\n >\n {props.children}\n </Link>\n ),\n p: (props) => (\n <Text elementType=\"p\" color={color}>\n {props.children}\n </Text>\n ),\n code: (props) => <InlineCode color={color}>{props.children}</InlineCode>,\n h1: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(1, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h2: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(2, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h3: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(3, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h4: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(4, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h5: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(5, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h6: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(6, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n hr: () => <Separator />,\n pre: (props) => {\n const preElementContent = Children.toArray(props.children)[0];\n\n return (\n <CodeBlock\n copyable={false}\n language={\n isValidElement<{ className?: string }>(preElementContent) &&\n preElementContent.props.className\n ? (preElementContent.props.className.replace(\n \"language-\",\n \"\",\n ) as CodeEditorLanguage)\n : undefined\n }\n code={String(\n isValidElement<{ children: string }>(preElementContent)\n ? preElementContent.props.children\n : preElementContent,\n ).trim()}\n />\n );\n },\n ul: ({ node: ignoredNode, ...props }) => (\n <SkeletonBlockText color={color}>\n <ul {...props} />\n </SkeletonBlockText>\n ),\n ol: ({ node: ignoredNode, ...props }) => (\n <SkeletonBlockText color={color}>\n <ol {...props} />\n </SkeletonBlockText>\n ),\n li: ({ node: ignoredNode, children, ...props }) => (\n <li {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </li>\n ),\n th: ({ node: ignoredNode, children, ...props }) => (\n <th {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </th>\n ),\n td: ({ node: ignoredNode, children, ...props }) => (\n <td {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </td>\n ),\n /* A text bar resets the mode for its content, so the image reads it\n from here. A skeleton issues no request. */\n img: ({ node: ignoredNode, ...props }) =>\n isSkeleton ? null : <img {...props} />,\n blockquote: (props) => (\n <SkeletonBlockText color={color}>\n <blockquote>{props.children}</blockquote>\n </SkeletonBlockText>\n ),\n }),\n [color, headingOffset, isSkeleton],\n );\n\n const textContent = extractTextFromFirstChild(children);\n const mergedComponents = useMemo<Components>(\n () => ({\n ...defaultComponents,\n ...customComponents,\n }),\n [defaultComponents, customComponents],\n );\n\n return (\n <div className={clsx(styles.markdown, className)} {...rest} ref={ref}>\n <ReactMarkdown\n allowElement={allowElement}\n allowedElements={allowedElements}\n disallowedElements={disallowedElements}\n rehypePlugins={rehypePlugins}\n remarkPlugins={remarkPlugins ?? [remarkGfm]}\n remarkRehypeOptions={{ clobberPrefix, ...remarkRehypeOptions }}\n skipHtml={skipHtml}\n unwrapDisallowed={unwrapDisallowed}\n urlTransform={urlTransform}\n components={mergedComponents}\n >\n {textContent}\n </ReactMarkdown>\n </div>\n );\n};\n\nexport default Markdown;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,YAA+B,UAAU;CACpD,MAAM,EACJ,UACA,QAAQ,WACR,WACA,gBAAgB,GAChB,YAAY,kBACZ,KACA,cACA,iBACA,oBACA,eACA,eACA,qBACA,UACA,kBACA,cACA,GAAG,SACD;CAEJ,MAAM,aAAa,gBAAgB;CAGnC,MAAM,gBAAgB,gBAAgB,MAAM,CAAC,CAAC,QAAQ,WAAW,EAAE,EAAE;CAIrE,MAAM,oBAAoB,eACjB;EAGL,IAAI,UACF,oBAAC,MAAD;GACE,IAAI,MAAM;GACV,cAAY,MAAM;GAClB,oBAAkB,MAAM;GACxB,QAAQ,eAAe,MAAM,IAAI,IAAI,WAAW,KAAA;GACzC;GACP,MAAM,MAAM;GAEX,UAAA,MAAM;EACH,CAAA;EAER,IAAI,UACF,oBAAC,MAAD;GAAM,aAAY;GAAW;GAC1B,UAAA,MAAM;EACH,CAAA;EAER,OAAO,UAAU,oBAAC,YAAD;GAAmB;GAAQ,UAAA,MAAM;EAAqB,CAAA;EACvE,KAAK,UACH,oBAAC,SAAD;GACE,IAAI,MAAM;GACV,OAAO,0BAA0B,GAAG,aAAa;GAC1C;GAEN,UAAA,MAAM;EACA,CAAA;EAEX,KAAK,UACH,oBAAC,SAAD;GACE,IAAI,MAAM;GACV,OAAO,0BAA0B,GAAG,aAAa;GAC1C;GAEN,UAAA,MAAM;EACA,CAAA;EAEX,KAAK,UACH,oBAAC,SAAD;GACE,IAAI,MAAM;GACV,OAAO,0BAA0B,GAAG,aAAa;GAC1C;GAEN,UAAA,MAAM;EACA,CAAA;EAEX,KAAK,UACH,oBAAC,SAAD;GACE,IAAI,MAAM;GACV,OAAO,0BAA0B,GAAG,aAAa;GAC1C;GAEN,UAAA,MAAM;EACA,CAAA;EAEX,KAAK,UACH,oBAAC,SAAD;GACE,IAAI,MAAM;GACV,OAAO,0BAA0B,GAAG,aAAa;GAC1C;GAEN,UAAA,MAAM;EACA,CAAA;EAEX,KAAK,UACH,oBAAC,SAAD;GACE,IAAI,MAAM;GACV,OAAO,0BAA0B,GAAG,aAAa;GAC1C;GAEN,UAAA,MAAM;EACA,CAAA;EAEX,UAAU,oBAAC,WAAD,CAAY,CAAA;EACtB,MAAM,UAAU;GACd,MAAM,oBAAoB,SAAS,QAAQ,MAAM,QAAQ,CAAC,CAAC;GAE3D,OACE,oBAAC,WAAD;IACE,UAAU;IACV,UACE,eAAuC,iBAAiB,KACxD,kBAAkB,MAAM,YACnB,kBAAkB,MAAM,UAAU,QACjC,aACA,EACF,IACA,KAAA;IAEN,MAAM,OACJ,eAAqC,iBAAiB,IAClD,kBAAkB,MAAM,WACxB,iBACN,CAAC,CAAC,KAAK;GACR,CAAA;EAEL;EACA,KAAK,EAAE,MAAM,aAAa,GAAG,YAC3B,oBAAC,mBAAD;GAA0B;GACxB,UAAA,oBAAC,MAAD,EAAI,GAAI,MAAQ,CAAA;EACC,CAAA;EAErB,KAAK,EAAE,MAAM,aAAa,GAAG,YAC3B,oBAAC,mBAAD;GAA0B;GACxB,UAAA,oBAAC,MAAD,EAAI,GAAI,MAAQ,CAAA;EACC,CAAA;EAErB,KAAK,EAAE,MAAM,aAAa,UAAU,GAAG,YACrC,oBAAC,MAAD;GAAI,GAAI;GACN,UAAA,oBAAC,uBAAD,EAAwB,SAAgC,CAAA;EACtD,CAAA;EAEN,KAAK,EAAE,MAAM,aAAa,UAAU,GAAG,YACrC,oBAAC,MAAD;GAAI,GAAI;GACN,UAAA,oBAAC,uBAAD,EAAwB,SAAgC,CAAA;EACtD,CAAA;EAEN,KAAK,EAAE,MAAM,aAAa,UAAU,GAAG,YACrC,oBAAC,MAAD;GAAI,GAAI;GACN,UAAA,oBAAC,uBAAD,EAAwB,SAAgC,CAAA;EACtD,CAAA;EAIN,MAAM,EAAE,MAAM,aAAa,GAAG,YAC5B,aAAa,OAAO,oBAAC,OAAD,EAAK,GAAI,MAAQ,CAAA;EACvC,aAAa,UACX,oBAAC,mBAAD;GAA0B;GACxB,UAAA,oBAAC,cAAD,EAAA,UAAa,MAAM,SAAqB,CAAA;EACvB,CAAA;CAEvB,IACA;EAAC;EAAO;EAAe;CAAU,CACnC;CAEA,MAAM,cAAc,0BAA0B,QAAQ;CACtD,MAAM,mBAAmB,eAChB;EACL,GAAG;EACH,GAAG;CACL,IACA,CAAC,mBAAmB,gBAAgB,CACtC;CAEA,OACE,oBAAC,OAAD;EAAK,WAAW,KAAKA,wBAAO,UAAU,SAAS;EAAG,GAAI;EAAW;EAC/D,UAAA,oBAAC,eAAD;GACgB;GACG;GACG;GACL;GACf,eAAe,iBAAiB,CAAC,SAAS;GAC1C,qBAAqB;IAAE;IAAe,GAAG;GAAoB;GACnD;GACQ;GACJ;GACd,YAAY;GAEX,UAAA;EACY,CAAA;CACZ,CAAA;AAET"}
@@ -13,6 +13,20 @@ var pickComponentTranslations = (componentName, data) => {
13
13
  return pick(components ?? {}, [componentName])[componentName] ?? {};
14
14
  });
15
15
  };
16
+ var compiledMessages = /* @__PURE__ */ new Map();
17
+ var getCompiledMessage = (message, locale) => {
18
+ let messagesOfLocale = compiledMessages.get(locale);
19
+ if (!messagesOfLocale) {
20
+ messagesOfLocale = /* @__PURE__ */ new Map();
21
+ compiledMessages.set(locale, messagesOfLocale);
22
+ }
23
+ let compiled = messagesOfLocale.get(message);
24
+ if (!compiled) {
25
+ compiled = new IntlMessageFormat(message, locale);
26
+ messagesOfLocale.set(message, compiled);
27
+ }
28
+ return compiled;
29
+ };
16
30
  var useLocalizedStringFormatter = (strings, componentName) => {
17
31
  const locale = useLanguage();
18
32
  const { translations, translate } = useTranslationProvider();
@@ -31,8 +45,7 @@ var useLocalizedStringFormatter = (strings, componentName) => {
31
45
  if (customTranslated !== void 0) return customTranslated;
32
46
  }
33
47
  const translationString = dictionary.getStringForLocale(key, locale);
34
- const translationMessage = new IntlMessageFormat(translationString, locale);
35
- return String(translationMessage.format(variables));
48
+ return String(getCompiledMessage(translationString, locale).format(variables));
36
49
  }, [
37
50
  translate,
38
51
  locale,
@@ -1 +1 @@
1
- {"version":3,"file":"useLocalizedStringFormatter.mjs","names":[],"sources":["../../../../../../../src/components/TranslationProvider/useLocalizedStringFormatter.ts"],"sourcesContent":["import {\n type Translations,\n useTranslationProvider,\n} from \"@/components/TranslationProvider/TranslationProvider\";\nimport { mergeDeep } from \"remeda\";\nimport type { FlowComponentName } from \"@/components/propTypes\";\nimport { mapValues, pick } from \"remeda\";\nimport type {\n Variables,\n LocalizedStrings as LocalizedStringsIntl,\n} from \"@internationalized/string\";\nimport IntlMessageFormat from \"intl-messageformat\";\nimport { useLocalizedStringDictionary } from \"@react-aria/i18n\";\nimport { useCallback, useMemo } from \"react\";\nimport { useLanguage } from \"@/lib/hooks/useLanguage\";\n\nexport type LocalizedStrings = LocalizedStringsIntl<string, string>;\nexport type LocalizedComponentName = FlowComponentName | string;\n\nexport type TranslationFunction = (\n key: string,\n variables: Variables,\n meta: {\n component: string;\n locale: string;\n },\n) => string | undefined;\n\nconst pickComponentTranslations = (\n componentName: LocalizedComponentName,\n data?: Translations,\n) => {\n if (!data) {\n return {};\n }\n\n return mapValues(data, (components) => {\n const picked = pick(components ?? {}, [componentName]);\n return picked[componentName] ?? {};\n });\n};\n\nexport const useLocalizedStringFormatter = (\n strings: LocalizedStrings,\n componentName: LocalizedComponentName,\n) => {\n const locale = useLanguage();\n const { translations, translate } = useTranslationProvider();\n\n const mergedStrings = useMemo(\n () =>\n mergeDeep(\n strings,\n pickComponentTranslations(componentName, translations),\n ),\n [strings, translations, componentName],\n );\n\n const dictionary = useLocalizedStringDictionary(mergedStrings, componentName);\n const formatFunction = useCallback(\n (key: string, variables?: Variables): string => {\n if (translate) {\n const customTranslated = translate(key, variables, {\n component: componentName,\n locale,\n });\n\n if (customTranslated !== undefined) {\n return customTranslated;\n }\n }\n\n const translationString = dictionary.getStringForLocale(key, locale);\n const translationMessage = new IntlMessageFormat(\n translationString,\n locale,\n );\n\n return String(translationMessage.format(variables));\n },\n [translate, locale, componentName, dictionary],\n );\n\n return useMemo(() => ({ format: formatFunction }), [formatFunction]);\n};\n"],"mappings":";;;;;;;;;AA4BA,IAAM,6BACJ,eACA,SACG;CACH,IAAI,CAAC,MACH,OAAO,CAAC;CAGV,OAAO,UAAU,OAAO,eAAe;EAErC,OADe,KAAK,cAAc,CAAC,GAAG,CAAC,aAAa,CAC7C,CAAA,CAAO,kBAAkB,CAAC;CACnC,CAAC;AACH;AAEA,IAAa,+BACX,SACA,kBACG;CACH,MAAM,SAAS,YAAY;CAC3B,MAAM,EAAE,cAAc,cAAc,uBAAuB;CAE3D,MAAM,gBAAgB,cAElB,UACE,SACA,0BAA0B,eAAe,YAAY,CACvD,GACF;EAAC;EAAS;EAAc;CAAa,CACvC;CAEA,MAAM,aAAa,6BAA6B,eAAe,aAAa;CAC5E,MAAM,iBAAiB,aACpB,KAAa,cAAkC;EAC9C,IAAI,WAAW;GACb,MAAM,mBAAmB,UAAU,KAAK,WAAW;IACjD,WAAW;IACX;GACF,CAAC;GAED,IAAI,qBAAqB,KAAA,GACvB,OAAO;EAEX;EAEA,MAAM,oBAAoB,WAAW,mBAAmB,KAAK,MAAM;EACnE,MAAM,qBAAqB,IAAI,kBAC7B,mBACA,MACF;EAEA,OAAO,OAAO,mBAAmB,OAAO,SAAS,CAAC;CACpD,GACA;EAAC;EAAW;EAAQ;EAAe;CAAU,CAC/C;CAEA,OAAO,eAAe,EAAE,QAAQ,eAAe,IAAI,CAAC,cAAc,CAAC;AACrE"}
1
+ {"version":3,"file":"useLocalizedStringFormatter.mjs","names":[],"sources":["../../../../../../../src/components/TranslationProvider/useLocalizedStringFormatter.ts"],"sourcesContent":["import {\n type Translations,\n useTranslationProvider,\n} from \"@/components/TranslationProvider/TranslationProvider\";\nimport { mergeDeep } from \"remeda\";\nimport type { FlowComponentName } from \"@/components/propTypes\";\nimport { mapValues, pick } from \"remeda\";\nimport type {\n Variables,\n LocalizedStrings as LocalizedStringsIntl,\n} from \"@internationalized/string\";\nimport IntlMessageFormat from \"intl-messageformat\";\nimport { useLocalizedStringDictionary } from \"@react-aria/i18n\";\nimport { useCallback, useMemo } from \"react\";\nimport { useLanguage } from \"@/lib/hooks/useLanguage\";\n\nexport type LocalizedStrings = LocalizedStringsIntl<string, string>;\nexport type LocalizedComponentName = FlowComponentName | string;\n\nexport type TranslationFunction = (\n key: string,\n variables: Variables,\n meta: {\n component: string;\n locale: string;\n },\n) => string | undefined;\n\nconst pickComponentTranslations = (\n componentName: LocalizedComponentName,\n data?: Translations,\n) => {\n if (!data) {\n return {};\n }\n\n return mapValues(data, (components) => {\n const picked = pick(components ?? {}, [componentName]);\n return picked[componentName] ?? {};\n });\n};\n\n// Compiling parses the message — do it once per locale and message.\nconst compiledMessages = new Map<string, Map<string, IntlMessageFormat>>();\n\nconst getCompiledMessage = (message: string, locale: string) => {\n let messagesOfLocale = compiledMessages.get(locale);\n if (!messagesOfLocale) {\n messagesOfLocale = new Map();\n compiledMessages.set(locale, messagesOfLocale);\n }\n\n let compiled = messagesOfLocale.get(message);\n if (!compiled) {\n compiled = new IntlMessageFormat(message, locale);\n messagesOfLocale.set(message, compiled);\n }\n\n return compiled;\n};\n\nexport const useLocalizedStringFormatter = (\n strings: LocalizedStrings,\n componentName: LocalizedComponentName,\n) => {\n const locale = useLanguage();\n const { translations, translate } = useTranslationProvider();\n\n const mergedStrings = useMemo(\n () =>\n mergeDeep(\n strings,\n pickComponentTranslations(componentName, translations),\n ),\n [strings, translations, componentName],\n );\n\n const dictionary = useLocalizedStringDictionary(mergedStrings, componentName);\n const formatFunction = useCallback(\n (key: string, variables?: Variables): string => {\n if (translate) {\n const customTranslated = translate(key, variables, {\n component: componentName,\n locale,\n });\n\n if (customTranslated !== undefined) {\n return customTranslated;\n }\n }\n\n const translationString = dictionary.getStringForLocale(key, locale);\n return String(\n getCompiledMessage(translationString, locale).format(variables),\n );\n },\n [translate, locale, componentName, dictionary],\n );\n\n return useMemo(() => ({ format: formatFunction }), [formatFunction]);\n};\n"],"mappings":";;;;;;;;;AA4BA,IAAM,6BACJ,eACA,SACG;CACH,IAAI,CAAC,MACH,OAAO,CAAC;CAGV,OAAO,UAAU,OAAO,eAAe;EAErC,OADe,KAAK,cAAc,CAAC,GAAG,CAAC,aAAa,CAC7C,CAAA,CAAO,kBAAkB,CAAC;CACnC,CAAC;AACH;AAGA,IAAM,mCAAmB,IAAI,IAA4C;AAEzE,IAAM,sBAAsB,SAAiB,WAAmB;CAC9D,IAAI,mBAAmB,iBAAiB,IAAI,MAAM;CAClD,IAAI,CAAC,kBAAkB;EACrB,mCAAmB,IAAI,IAAI;EAC3B,iBAAiB,IAAI,QAAQ,gBAAgB;CAC/C;CAEA,IAAI,WAAW,iBAAiB,IAAI,OAAO;CAC3C,IAAI,CAAC,UAAU;EACb,WAAW,IAAI,kBAAkB,SAAS,MAAM;EAChD,iBAAiB,IAAI,SAAS,QAAQ;CACxC;CAEA,OAAO;AACT;AAEA,IAAa,+BACX,SACA,kBACG;CACH,MAAM,SAAS,YAAY;CAC3B,MAAM,EAAE,cAAc,cAAc,uBAAuB;CAE3D,MAAM,gBAAgB,cAElB,UACE,SACA,0BAA0B,eAAe,YAAY,CACvD,GACF;EAAC;EAAS;EAAc;CAAa,CACvC;CAEA,MAAM,aAAa,6BAA6B,eAAe,aAAa;CAC5E,MAAM,iBAAiB,aACpB,KAAa,cAAkC;EAC9C,IAAI,WAAW;GACb,MAAM,mBAAmB,UAAU,KAAK,WAAW;IACjD,WAAW;IACX;GACF,CAAC;GAED,IAAI,qBAAqB,KAAA,GACvB,OAAO;EAEX;EAEA,MAAM,oBAAoB,WAAW,mBAAmB,KAAK,MAAM;EACnE,OAAO,OACL,mBAAmB,mBAAmB,MAAM,CAAC,CAAC,OAAO,SAAS,CAChE;CACF,GACA;EAAC;EAAW;EAAQ;EAAe;CAAU,CAC/C;CAEA,OAAO,eAAe,EAAE,QAAQ,eAAe,IAAI,CAAC,cAAc,CAAC;AACrE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mittwald/flow-react-components",
3
- "version": "1.6.0-next.21",
3
+ "version": "1.6.0-next.24",
4
4
  "type": "module",
5
5
  "description": "A React implementation of Flow, mittwald’s design system",
6
6
  "homepage": "https://flow.mittwald.de",
@@ -84,9 +84,9 @@
84
84
  "@internationalized/string-compiler": "^3.4.1",
85
85
  "@lezer/common": "^1.5.2",
86
86
  "@lezer/highlight": "^1.2.3",
87
- "@mittwald/flow-icons": "^1.6.0-next.21",
87
+ "@mittwald/flow-icons": "^1.6.0-next.24",
88
88
  "@mittwald/password-tools-js": "^3.0.0",
89
- "@mittwald/react-tunnel": "^1.6.0-next.21",
89
+ "@mittwald/react-tunnel": "^1.6.0-next.24",
90
90
  "@mittwald/react-use-promise": "^4.2.2",
91
91
  "@react-aria/form": "^3.2.1",
92
92
  "@react-aria/i18n": "^3.13.1",
@@ -139,13 +139,13 @@
139
139
  "@internationalized/date": "^3.12.3",
140
140
  "@lezer/generator": "^1.8.0",
141
141
  "@lezer/lr": "^1.4.10",
142
- "@mittwald/flow-components-base": "1.6.0-next.21",
143
- "@mittwald/flow-core": "1.6.0-next.21",
144
- "@mittwald/flow-design-tokens": "1.6.0-next.21",
145
- "@mittwald/flow-icons-base": "1.6.0-next.21",
142
+ "@mittwald/flow-components-base": "1.6.0-next.24",
143
+ "@mittwald/flow-core": "1.6.0-next.24",
144
+ "@mittwald/flow-design-tokens": "1.6.0-next.24",
145
+ "@mittwald/flow-icons-base": "1.6.0-next.24",
146
146
  "@mittwald/react-use-promise": "^4.2.2",
147
147
  "@mittwald/remote-dom-react": "1.2.2-mittwald.10",
148
- "@mittwald/typescript-config": "1.6.0-next.21",
148
+ "@mittwald/typescript-config": "1.6.0-next.24",
149
149
  "@nx/storybook": "^22.7.6",
150
150
  "@storybook/addon-a11y": "^10.5.10",
151
151
  "@storybook/addon-actions": "^9.0.8",
@@ -199,7 +199,7 @@
199
199
  },
200
200
  "peerDependencies": {
201
201
  "@internationalized/date": "^3.12.2",
202
- "@mittwald/flow-icons-pro": "1.6.0-next.21",
202
+ "@mittwald/flow-icons-pro": "1.6.0-next.24",
203
203
  "@mittwald/react-use-promise": "^4.2.2",
204
204
  "next": "^16.2.3",
205
205
  "react": "^19.2.0",
@@ -223,5 +223,5 @@
223
223
  "browserslist": [
224
224
  "baseline widely available"
225
225
  ],
226
- "gitHead": "c75ca489f4a68d285b3a7513fab391ef31978680"
226
+ "gitHead": "b1bd22a026e392422446e902bf92c8921b63c94d"
227
227
  }