@mittwald/flow-react-components 1.3.5 → 1.4.0-next.1

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 (81) hide show
  1. package/MIGRATION.md +25 -2
  2. package/dist/assets/component-index.json +1 -1
  3. package/dist/assets/doc-properties.json +1 -1
  4. package/dist/css/all-layered.css +1 -1
  5. package/dist/css/all.css +1 -1
  6. package/dist/js/_virtual/_24.locale.mjs +10 -2
  7. package/dist/js/default.mjs +2 -1
  8. package/dist/js/flr-universal.mjs +2 -1
  9. package/dist/js/packages/components/src/components/Action/models/ActionModel.mjs +25 -8
  10. package/dist/js/packages/components/src/components/Action/models/ActionModel.mjs.map +1 -1
  11. package/dist/js/packages/components/src/components/CheckboxButton/CheckboxButton.mjs +0 -1
  12. package/dist/js/packages/components/src/components/CheckboxButton/CheckboxButton.mjs.map +1 -1
  13. package/dist/js/packages/components/src/components/CheckboxButton/CheckboxButton.module.mjs +2 -4
  14. package/dist/js/packages/components/src/components/CheckboxButton/CheckboxButton.module.mjs.map +1 -1
  15. package/dist/js/packages/components/src/components/ContextMenu/ContextMenu.mjs +2 -2
  16. package/dist/js/packages/components/src/components/ContextMenu/ContextMenu.mjs.map +1 -1
  17. package/dist/js/packages/components/src/components/ContextMenu/components/ContextMenuSection/ContextMenuSection.mjs +2 -2
  18. package/dist/js/packages/components/src/components/ContextMenu/components/ContextMenuSection/ContextMenuSection.mjs.map +1 -1
  19. package/dist/js/packages/components/src/components/ContextMenu/lib.mjs +7 -3
  20. package/dist/js/packages/components/src/components/ContextMenu/lib.mjs.map +1 -1
  21. package/dist/js/packages/components/src/components/CopyButton/CopyButton.mjs +4 -3
  22. package/dist/js/packages/components/src/components/CopyButton/CopyButton.mjs.map +1 -1
  23. package/dist/js/packages/components/src/components/Icon/components/icons/IconAIHosting.mjs +19 -0
  24. package/dist/js/packages/components/src/components/Icon/components/icons/IconAIHosting.mjs.map +1 -0
  25. package/dist/js/packages/components/src/components/Image/Image.mjs +2 -2
  26. package/dist/js/packages/components/src/components/Image/Image.mjs.map +1 -1
  27. package/dist/js/packages/components/src/components/Image/Image.module.mjs +3 -1
  28. package/dist/js/packages/components/src/components/Image/Image.module.mjs.map +1 -1
  29. package/dist/js/packages/components/src/components/List/components/Header/components/AllFiltersModal/AllFiltersModal.mjs +2 -2
  30. package/dist/js/packages/components/src/components/List/components/Header/components/AllFiltersModal/AllFiltersModal.mjs.map +1 -1
  31. package/dist/js/packages/components/src/components/List/components/Header/components/AllFiltersModal/FilterAccordionDateRange.mjs +2 -2
  32. package/dist/js/packages/components/src/components/List/components/Header/components/AllFiltersModal/FilterAccordionDateRange.mjs.map +1 -1
  33. package/dist/js/packages/components/src/components/List/components/Header/components/AllFiltersModal/FilterAccordionRadioGroup.mjs +2 -2
  34. package/dist/js/packages/components/src/components/List/components/Header/components/AllFiltersModal/FilterAccordionRadioGroup.mjs.map +1 -1
  35. package/dist/js/packages/components/src/components/MarkdownEditor/MarkdownEditor.mjs +16 -3
  36. package/dist/js/packages/components/src/components/MarkdownEditor/MarkdownEditor.mjs.map +1 -1
  37. package/dist/js/packages/components/src/components/MarkdownEditor/MarkdownEditor.module.mjs +4 -2
  38. package/dist/js/packages/components/src/components/MarkdownEditor/MarkdownEditor.module.mjs.map +1 -1
  39. package/dist/js/packages/components/src/components/MarkdownEditor/components/AttachmentButton.mjs +47 -0
  40. package/dist/js/packages/components/src/components/MarkdownEditor/components/AttachmentButton.mjs.map +1 -0
  41. package/dist/js/packages/components/src/components/MarkdownEditor/components/Toolbar.mjs +21 -20
  42. package/dist/js/packages/components/src/components/MarkdownEditor/components/Toolbar.mjs.map +1 -1
  43. package/dist/js/packages/components/src/components/MarkdownEditor/components/ToolbarButton.mjs +1 -1
  44. package/dist/js/packages/components/src/components/MarkdownEditor/components/ToolbarButton.mjs.map +1 -1
  45. package/dist/js/packages/components/src/components/MarkdownEditor/lib/fileUpload.mjs +73 -0
  46. package/dist/js/packages/components/src/components/MarkdownEditor/lib/fileUpload.mjs.map +1 -0
  47. package/dist/js/packages/components/src/components/MarkdownEditor/lib/useFileUpload.mjs +141 -0
  48. package/dist/js/packages/components/src/components/MarkdownEditor/lib/useFileUpload.mjs.map +1 -0
  49. package/dist/js/packages/components/src/components/Modal/Modal.mjs +6 -4
  50. package/dist/js/packages/components/src/components/Modal/Modal.mjs.map +1 -1
  51. package/dist/js/packages/components/src/components/OverlayTrigger/OverlayTrigger.mjs +1 -0
  52. package/dist/js/packages/components/src/components/OverlayTrigger/OverlayTrigger.mjs.map +1 -1
  53. package/dist/js/packages/components/src/lib/controller/overlay/OverlayContextProvider.mjs +6 -3
  54. package/dist/js/packages/components/src/lib/controller/overlay/OverlayContextProvider.mjs.map +1 -1
  55. package/dist/js/packages/components/src/lib/controller/overlay/OverlayController.mjs.map +1 -1
  56. package/dist/js/packages/components/src/lib/controller/overlay/context.mjs +1 -1
  57. package/dist/js/packages/components/src/lib/controller/overlay/context.mjs.map +1 -1
  58. package/dist/js/packages/components/src/lib/controller/overlay/useOverlayController.mjs +1 -1
  59. package/dist/js/packages/components/src/lib/controller/overlay/useOverlayController.mjs.map +1 -1
  60. package/dist/js/packages/design-tokens/dist/json-runtime/all-dark.mjs +1 -1
  61. package/dist/js/packages/design-tokens/dist/json-runtime/all-dark.mjs.map +1 -1
  62. package/dist/js/packages/design-tokens/dist/json-runtime/all-light.mjs +1 -1
  63. package/dist/js/packages/design-tokens/dist/json-runtime/all-light.mjs.map +1 -1
  64. package/dist/types/components/Action/models/ActionModel.d.ts +12 -4
  65. package/dist/types/components/Action/types.d.ts +9 -2
  66. package/dist/types/components/ContextMenu/lib.d.ts +5 -1
  67. package/dist/types/components/CopyButton/CopyButton.d.ts +5 -0
  68. package/dist/types/components/Icon/components/icons/IconAIHosting.d.ts +4 -0
  69. package/dist/types/components/Icon/components/icons/index.d.ts +1 -0
  70. package/dist/types/components/Image/Image.d.ts +7 -1
  71. package/dist/types/components/MarkdownEditor/MarkdownEditor.d.ts +20 -0
  72. package/dist/types/components/MarkdownEditor/components/AttachmentButton.d.ts +1 -0
  73. package/dist/types/components/MarkdownEditor/components/Toolbar.d.ts +11 -2
  74. package/dist/types/components/MarkdownEditor/components/ToolbarButton.d.ts +8 -2
  75. package/dist/types/components/MarkdownEditor/index.d.ts +1 -1
  76. package/dist/types/components/MarkdownEditor/lib/fileUpload.d.ts +19 -0
  77. package/dist/types/components/MarkdownEditor/lib/useFileUpload.d.ts +1 -0
  78. package/dist/types/lib/controller/overlay/OverlayContextProvider.d.ts +9 -0
  79. package/dist/types/lib/controller/overlay/OverlayController.d.ts +11 -1
  80. package/dist/types/lib/controller/overlay/context.d.ts +19 -2
  81. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"file":"FilterAccordionRadioGroup.mjs","names":[],"sources":["../../../../../../../../../../../src/components/List/components/Header/components/AllFiltersModal/FilterAccordionRadioGroup.tsx"],"sourcesContent":["import { type FC } from \"react\";\nimport RadioGroupView from \"@/views/RadioGroupView\";\nimport RadioView from \"@/views/RadioView\";\nimport type { Filter } from \"@/components/List/model/filter/Filter\";\nimport ButtonView from \"@/views/ButtonView\";\nimport FlexView from \"@/views/FlexView\";\nimport { useLocalizedStringFormatter } from \"@/components/TranslationProvider/useLocalizedStringFormatter\";\nimport locales from \"../../../../locales/*.locale.json\";\n\ninterface Props {\n filter: Filter;\n}\n\nexport const FilterAccordionRadioGroup: FC<Props> = (props) => {\n const { filter } = props;\n\n const activeKeys = filter.values.filter((v) => v.isActive).map((v) => v.id);\n\n const name = filter.name ?? filter.property;\n\n const stringFormatter = useLocalizedStringFormatter(locales, \"List\");\n\n return (\n <FlexView direction=\"column\" gap=\"m\">\n <RadioGroupView\n value={activeKeys[0]}\n m={[1, 1]}\n key={activeKeys[0]}\n aria-label={name}\n >\n {filter.values.map((v) => (\n <RadioView\n key={v.id}\n value={v.id}\n onPress={() => {\n if (!v.isActive) {\n v.toggle();\n }\n }}\n >\n {v.render()}\n </RadioView>\n ))}\n </RadioGroupView>\n {activeKeys.length > 0 && (\n <ButtonView\n size=\"s\"\n color=\"secondary\"\n variant=\"soft\"\n onPress={() => filter.clear()}\n >\n {stringFormatter.format(\"filters.clearSelection\")}\n </ButtonView>\n )}\n </FlexView>\n );\n};\n"],"mappings":";;;;;;;;;;;AAaA,IAAa,6BAAwC,UAAU;CAC7D,MAAM,EAAE,WAAW;CAEnB,MAAM,aAAa,OAAO,OAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,KAAK,MAAM,EAAE,EAAE;CAE1E,MAAM,OAAO,OAAO,QAAQ,OAAO;CAEnC,MAAM,kBAAkB,4BAA4B,kBAAS,MAAM;CAEnE,OACE,qBAAC,UAAD;EAAU,WAAU;EAAS,KAAI;EAAjC,UAAA,CACE,oBAAC,gBAAD;GACE,OAAO,WAAW;GAClB,GAAG,CAAC,GAAG,CAAC;GAER,cAAY;GAEX,UAAA,OAAO,OAAO,KAAK,MAClB,oBAAC,WAAD;IAEE,OAAO,EAAE;IACT,eAAe;KACb,IAAI,CAAC,EAAE,UACL,EAAE,OAAO;IAEb;IAEC,UAAA,EAAE,OAAO;GACD,GATJ,EAAE,EASE,CACZ;EACa,GAhBT,WAAW,EAgBF,GACf,WAAW,SAAS,KACnB,oBAAC,YAAD;GACE,MAAK;GACL,OAAM;GACN,SAAQ;GACR,eAAe,OAAO,MAAM;GAE3B,UAAA,gBAAgB,OAAO,wBAAwB;EACtC,CAAA,CAEN;;AAEd"}
1
+ {"version":3,"file":"FilterAccordionRadioGroup.mjs","names":[],"sources":["../../../../../../../../../../../src/components/List/components/Header/components/AllFiltersModal/FilterAccordionRadioGroup.tsx"],"sourcesContent":["import { type FC } from \"react\";\nimport RadioGroupView from \"@/views/RadioGroupView\";\nimport RadioView from \"@/views/RadioView\";\nimport type { Filter } from \"@/components/List/model/filter/Filter\";\nimport ButtonView from \"@/views/ButtonView\";\nimport { Flex } from \"@/components/Flex\";\nimport { useLocalizedStringFormatter } from \"@/components/TranslationProvider/useLocalizedStringFormatter\";\nimport locales from \"../../../../locales/*.locale.json\";\n\ninterface Props {\n filter: Filter;\n}\n\nexport const FilterAccordionRadioGroup: FC<Props> = (props) => {\n const { filter } = props;\n\n const activeKeys = filter.values.filter((v) => v.isActive).map((v) => v.id);\n\n const name = filter.name ?? filter.property;\n\n const stringFormatter = useLocalizedStringFormatter(locales, \"List\");\n\n return (\n <Flex direction=\"column\" gap=\"m\">\n <RadioGroupView\n value={activeKeys[0]}\n m={[1, 1]}\n key={activeKeys[0]}\n aria-label={name}\n >\n {filter.values.map((v) => (\n <RadioView\n key={v.id}\n value={v.id}\n onPress={() => {\n if (!v.isActive) {\n v.toggle();\n }\n }}\n >\n {v.render()}\n </RadioView>\n ))}\n </RadioGroupView>\n {activeKeys.length > 0 && (\n <ButtonView\n size=\"s\"\n color=\"secondary\"\n variant=\"soft\"\n onPress={() => filter.clear()}\n >\n {stringFormatter.format(\"filters.clearSelection\")}\n </ButtonView>\n )}\n </Flex>\n );\n};\n"],"mappings":";;;;;;;;;;;AAaA,IAAa,6BAAwC,UAAU;CAC7D,MAAM,EAAE,WAAW;CAEnB,MAAM,aAAa,OAAO,OAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,KAAK,MAAM,EAAE,EAAE;CAE1E,MAAM,OAAO,OAAO,QAAQ,OAAO;CAEnC,MAAM,kBAAkB,4BAA4B,kBAAS,MAAM;CAEnE,OACE,qBAAC,MAAD;EAAM,WAAU;EAAS,KAAI;EAA7B,UAAA,CACE,oBAAC,gBAAD;GACE,OAAO,WAAW;GAClB,GAAG,CAAC,GAAG,CAAC;GAER,cAAY;GAEX,UAAA,OAAO,OAAO,KAAK,MAClB,oBAAC,WAAD;IAEE,OAAO,EAAE;IACT,eAAe;KACb,IAAI,CAAC,EAAE,UACL,EAAE,OAAO;IAEb;IAEC,UAAA,EAAE,OAAO;GACD,GATJ,EAAE,EASE,CACZ;EACa,GAhBT,WAAW,EAgBF,GACf,WAAW,SAAS,KACnB,oBAAC,YAAD;GACE,MAAK;GACL,OAAM;GACN,SAAQ;GACR,eAAe,OAAO,MAAM;GAE3B,UAAA,gBAAgB,OAAO,wBAAwB;EACtC,CAAA,CAEV;;AAEV"}
@@ -10,6 +10,7 @@ import { TextArea } from "../TextArea/TextArea.mjs";
10
10
  import { modifyValueByType } from "./lib/modifyValueByType.mjs";
11
11
  import { Toolbar } from "./components/Toolbar.mjs";
12
12
  import { modifyValueByMarkdownSyntax, scrollToCursor } from "./lib/modifyValueByMarkdownSyntax.mjs";
13
+ import { useFileUpload } from "./lib/useFileUpload.mjs";
13
14
  import clsx from "clsx";
14
15
  import { useState } from "react";
15
16
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -21,11 +22,19 @@ var toolbarActionsTunnel = {
21
22
  };
22
23
  /** @flr-generate all */
23
24
  var MarkdownEditor = flowComponent("MarkdownEditor", (props) => {
24
- const { isDisabled, isReadOnly, children, className, rows = 5, autoResizeMaxRows, headingOffset, markdownComponent: MarkdownComponent = Markdown, value, onChange, ref, ...rest } = useControlledHostValueProps(props, "");
25
+ const { isDisabled, isReadOnly, children, className, rows = 5, autoResizeMaxRows, headingOffset, markdownComponent: MarkdownComponent = Markdown, value, onChange, uploadFile, accept, ref, ...rest } = useControlledHostValueProps(props, "");
25
26
  const inputRef = useObjectRef(ref);
26
27
  const [mode, setMode] = useState("editor");
27
28
  const toolbarActionsDisabled = isDisabled || mode === "preview";
28
- const rootClassName = clsx(MarkdownEditor_module_default.markdownEditor, MarkdownEditor_module_default[`mode-${mode}`], className);
29
+ const { dropProps, isDropTarget, uploadFiles } = useFileUpload({
30
+ accept,
31
+ uploadFile,
32
+ isDisabled: isDisabled || isReadOnly || mode === "preview",
33
+ value,
34
+ onChange,
35
+ textAreaRef: inputRef
36
+ });
37
+ const rootClassName = clsx(MarkdownEditor_module_default.markdownEditor, MarkdownEditor_module_default[`mode-${mode}`], isDropTarget && MarkdownEditor_module_default.dropTarget, className);
29
38
  const toolbarActionsPropsContext = {
30
39
  ...overlayTriggersTunneledTo(toolbarActionsTunnel),
31
40
  Button: {
@@ -63,6 +72,7 @@ var MarkdownEditor = flowComponent("MarkdownEditor", (props) => {
63
72
  onChange(newValue);
64
73
  };
65
74
  return /* @__PURE__ */ jsx("div", {
75
+ ...dropProps,
66
76
  className: rootClassName,
67
77
  children: /* @__PURE__ */ jsx(TextArea, {
68
78
  ...rest,
@@ -90,7 +100,10 @@ var MarkdownEditor = flowComponent("MarkdownEditor", (props) => {
90
100
  currentMode: mode,
91
101
  isDisabled,
92
102
  onModeChange: setMode,
93
- onToolPressed: handleToolButtonPressed
103
+ onToolPressed: handleToolButtonPressed,
104
+ accept,
105
+ isReadOnly,
106
+ onFilesSelected: uploadFiles
94
107
  })
95
108
  ]
96
109
  })
@@ -1 +1 @@
1
- {"version":3,"file":"MarkdownEditor.mjs","names":[],"sources":["../../../../../../../src/components/MarkdownEditor/MarkdownEditor.tsx"],"sourcesContent":["import { type ComponentType, type KeyboardEventHandler, useState } from \"react\";\nimport styles from \"./MarkdownEditor.module.scss\";\nimport {\n Markdown as DefaultMarkdown,\n type MarkdownProps,\n} from \"@/components/Markdown\";\nimport { TextArea, type TextAreaProps } from \"@/components/TextArea\";\nimport { Toolbar } from \"@/components/MarkdownEditor/components/Toolbar\";\nimport clsx from \"clsx\";\nimport { flowComponent } from \"@/lib/componentFactory/flowComponent\";\nimport { useObjectRef } from \"@react-aria/utils\";\nimport {\n modifyValueByMarkdownSyntax,\n scrollToCursor,\n} from \"@/components/MarkdownEditor/lib/modifyValueByMarkdownSyntax\";\nimport {\n type InsertType,\n modifyValueByType,\n} from \"@/components/MarkdownEditor/lib/modifyValueByType\";\nimport { useControlledHostValueProps } from \"@/lib/remote/useControlledHostValueProps\";\nimport {\n overlayTriggersTunneledTo,\n type PropsContext,\n PropsContextProvider,\n} from \"@/lib/propsContext\";\n\nexport type MarkdownEditorMode = \"editor\" | \"preview\";\n\nconst toolbarActionsTunnel = {\n id: \"toolbarActions\",\n component: \"MarkdownEditor\",\n} as const;\n\nexport interface MarkdownEditorProps\n extends TextAreaProps, Pick<MarkdownProps, \"headingOffset\"> {\n /**\n * Allows replacing the markdown preview renderer implementation. Defaults to\n * the internal `Markdown` component.\n */\n markdownComponent?: ComponentType<MarkdownProps>;\n}\n\n/** @flr-generate all */\nexport const MarkdownEditor = flowComponent(\"MarkdownEditor\", (props) => {\n const {\n isDisabled,\n isReadOnly,\n children,\n className,\n rows = 5,\n autoResizeMaxRows,\n headingOffset,\n markdownComponent: MarkdownComponent = DefaultMarkdown,\n value,\n onChange,\n ref,\n ...rest\n } = useControlledHostValueProps(props, \"\");\n\n const inputRef = useObjectRef(ref);\n const [mode, setMode] = useState<MarkdownEditorMode>(\"editor\");\n const toolbarActionsDisabled = isDisabled || mode === \"preview\";\n\n const rootClassName = clsx(\n styles.markdownEditor,\n styles[`mode-${mode}`],\n className,\n );\n\n const toolbarActionsPropsContext: PropsContext = {\n ...overlayTriggersTunneledTo(toolbarActionsTunnel),\n Button: {\n tunnel: toolbarActionsTunnel,\n size: \"s\",\n variant: \"plain\",\n color: \"dark\",\n isDisabled: toolbarActionsDisabled,\n },\n };\n\n const handleKeyDown: KeyboardEventHandler = (event) => {\n if (event.key !== \"Enter\") {\n return;\n }\n\n const modifyParams = modifyValueByMarkdownSyntax(value, inputRef);\n if (!modifyParams) {\n return;\n }\n\n const { newValue, newSelectionStart, newSelectionEnd } = modifyParams;\n\n requestAnimationFrame(() => {\n if (inputRef.current) {\n inputRef.current.value = newValue;\n inputRef.current?.setSelectionRange(newSelectionStart, newSelectionEnd);\n scrollToCursor(newValue, inputRef.current);\n }\n });\n\n event.preventDefault();\n onChange(newValue);\n };\n\n const handleToolButtonPressed = (type: InsertType) => {\n const { newValue, newSelectionStart, newSelectionEnd } = modifyValueByType(\n value,\n type,\n inputRef,\n );\n\n requestAnimationFrame(() => {\n if (inputRef.current) {\n inputRef.current.value = newValue;\n inputRef.current.setSelectionRange(newSelectionStart, newSelectionEnd);\n inputRef.current.focus();\n }\n });\n\n onChange(newValue);\n };\n\n return (\n <div className={rootClassName}>\n <TextArea\n {...rest}\n aria-hidden={mode === \"preview\"}\n isReadOnly={isReadOnly || mode === \"preview\"}\n isDisabled={isDisabled}\n ref={inputRef}\n value={value}\n rows={rows}\n autoResizeMaxRows={autoResizeMaxRows}\n onChange={onChange}\n onKeyDown={handleKeyDown}\n >\n <PropsContextProvider\n props={toolbarActionsPropsContext}\n dependencies={[toolbarActionsDisabled]}\n >\n {children}\n {mode === \"preview\" && (\n <MarkdownComponent\n headingOffset={headingOffset}\n className={styles.markdown}\n style={{\n height: inputRef.current?.offsetHeight,\n }}\n >\n {value}\n </MarkdownComponent>\n )}\n <Toolbar\n currentMode={mode}\n isDisabled={isDisabled}\n onModeChange={setMode}\n onToolPressed={handleToolButtonPressed}\n />\n </PropsContextProvider>\n </TextArea>\n </div>\n );\n});\n\nexport default MarkdownEditor;\n"],"mappings":";;;;;;;;;;;;;;;;;AA4BA,IAAM,uBAAuB;CAC3B,IAAI;CACJ,WAAW;AACb;;AAYA,IAAa,iBAAiB,cAAc,mBAAmB,UAAU;CACvE,MAAM,EACJ,YACA,YACA,UACA,WACA,OAAO,GACP,mBACA,eACA,mBAAmB,oBAAoB,UACvC,OACA,UACA,KACA,GAAG,SACD,4BAA4B,OAAO,EAAE;CAEzC,MAAM,WAAW,aAAa,GAAG;CACjC,MAAM,CAAC,MAAM,WAAW,SAA6B,QAAQ;CAC7D,MAAM,yBAAyB,cAAc,SAAS;CAEtD,MAAM,gBAAgB,KACpB,8BAAO,gBACP,8BAAO,QAAQ,SACf,SACF;CAEA,MAAM,6BAA2C;EAC/C,GAAG,0BAA0B,oBAAoB;EACjD,QAAQ;GACN,QAAQ;GACR,MAAM;GACN,SAAS;GACT,OAAO;GACP,YAAY;EACd;CACF;CAEA,MAAM,iBAAuC,UAAU;EACrD,IAAI,MAAM,QAAQ,SAChB;EAGF,MAAM,eAAe,4BAA4B,OAAO,QAAQ;EAChE,IAAI,CAAC,cACH;EAGF,MAAM,EAAE,UAAU,mBAAmB,oBAAoB;EAEzD,4BAA4B;GAC1B,IAAI,SAAS,SAAS;IACpB,SAAS,QAAQ,QAAQ;IACzB,SAAS,SAAS,kBAAkB,mBAAmB,eAAe;IACtE,eAAe,UAAU,SAAS,OAAO;GAC3C;EACF,CAAC;EAED,MAAM,eAAe;EACrB,SAAS,QAAQ;CACnB;CAEA,MAAM,2BAA2B,SAAqB;EACpD,MAAM,EAAE,UAAU,mBAAmB,oBAAoB,kBACvD,OACA,MACA,QACF;EAEA,4BAA4B;GAC1B,IAAI,SAAS,SAAS;IACpB,SAAS,QAAQ,QAAQ;IACzB,SAAS,QAAQ,kBAAkB,mBAAmB,eAAe;IACrE,SAAS,QAAQ,MAAM;GACzB;EACF,CAAC;EAED,SAAS,QAAQ;CACnB;CAEA,OACE,oBAAC,OAAD;EAAK,WAAW;EACd,UAAA,oBAAC,UAAD;GACE,GAAI;GACJ,eAAa,SAAS;GACtB,YAAY,cAAc,SAAS;GACvB;GACZ,KAAK;GACE;GACD;GACa;GACT;GACV,WAAW;GAEX,UAAA,qBAAC,sBAAD;IACE,OAAO;IACP,cAAc,CAAC,sBAAsB;IAFvC,UAAA;KAIG;KACA,SAAS,aACR,oBAAC,mBAAD;MACiB;MACf,WAAW,8BAAO;MAClB,OAAO,EACL,QAAQ,SAAS,SAAS,aAC5B;MAEC,UAAA;KACgB,CAAA;KAErB,oBAAC,SAAD;MACE,aAAa;MACD;MACZ,cAAc;MACd,eAAe;KAChB,CAAA;IACmB;;EACd,CAAA;CACP,CAAA;AAET,CAAC"}
1
+ {"version":3,"file":"MarkdownEditor.mjs","names":[],"sources":["../../../../../../../src/components/MarkdownEditor/MarkdownEditor.tsx"],"sourcesContent":["import { type ComponentType, type KeyboardEventHandler, useState } from \"react\";\nimport styles from \"./MarkdownEditor.module.scss\";\nimport {\n Markdown as DefaultMarkdown,\n type MarkdownProps,\n} from \"@/components/Markdown\";\nimport { TextArea, type TextAreaProps } from \"@/components/TextArea\";\nimport { Toolbar } from \"@/components/MarkdownEditor/components/Toolbar\";\nimport clsx from \"clsx\";\nimport { flowComponent } from \"@/lib/componentFactory/flowComponent\";\nimport { useObjectRef } from \"@react-aria/utils\";\nimport {\n modifyValueByMarkdownSyntax,\n scrollToCursor,\n} from \"@/components/MarkdownEditor/lib/modifyValueByMarkdownSyntax\";\nimport {\n type InsertType,\n modifyValueByType,\n} from \"@/components/MarkdownEditor/lib/modifyValueByType\";\nimport type { MarkdownEditorUpload } from \"@/components/MarkdownEditor/lib/fileUpload\";\nimport { useFileUpload } from \"@/components/MarkdownEditor/lib/useFileUpload\";\nimport { useControlledHostValueProps } from \"@/lib/remote/useControlledHostValueProps\";\nimport {\n overlayTriggersTunneledTo,\n type PropsContext,\n PropsContextProvider,\n} from \"@/lib/propsContext\";\n\nexport type MarkdownEditorMode = \"editor\" | \"preview\";\n\nexport type { MarkdownEditorUpload };\n\nconst toolbarActionsTunnel = {\n id: \"toolbarActions\",\n component: \"MarkdownEditor\",\n} as const;\n\nexport interface MarkdownEditorProps\n extends TextAreaProps, Pick<MarkdownProps, \"headingOffset\"> {\n /**\n * Allows replacing the markdown preview renderer implementation. Defaults to\n * the internal `Markdown` component.\n */\n markdownComponent?: ComponentType<MarkdownProps>;\n /**\n * Uploads a file the user dropped onto the editor, pasted into it or picked\n * with its attachment button. A placeholder comment sits at the cursor while\n * the returned promise is pending: resolving replaces it with markdown\n * linking to the file — an image embed for an image — and rejecting takes it\n * back out. Without this prop the editor takes no files at all.\n *\n * Named without an `on` prefix on purpose: the remote generator turns an\n * `on*` prop into a fire-and-forget event, and this one has to hand a result\n * back.\n */\n uploadFile?: (file: File) => Promise<MarkdownEditorUpload>;\n /**\n * Which file types the editor takes, as the `accept` attribute of a file\n * input: a comma separated list of MIME types (`image/png`), MIME wildcards\n * (`image/*`) and file extensions (`.png`). Defaults to every type.\n */\n accept?: string;\n}\n\n/** @flr-generate all */\nexport const MarkdownEditor = flowComponent(\"MarkdownEditor\", (props) => {\n const {\n isDisabled,\n isReadOnly,\n children,\n className,\n rows = 5,\n autoResizeMaxRows,\n headingOffset,\n markdownComponent: MarkdownComponent = DefaultMarkdown,\n value,\n onChange,\n uploadFile,\n accept,\n ref,\n ...rest\n } = useControlledHostValueProps(props, \"\");\n\n const inputRef = useObjectRef(ref);\n const [mode, setMode] = useState<MarkdownEditorMode>(\"editor\");\n const toolbarActionsDisabled = isDisabled || mode === \"preview\";\n\n const { dropProps, isDropTarget, uploadFiles } = useFileUpload({\n accept,\n uploadFile,\n isDisabled: isDisabled || isReadOnly || mode === \"preview\",\n value,\n onChange,\n textAreaRef: inputRef,\n });\n\n const rootClassName = clsx(\n styles.markdownEditor,\n styles[`mode-${mode}`],\n isDropTarget && styles.dropTarget,\n className,\n );\n\n const toolbarActionsPropsContext: PropsContext = {\n ...overlayTriggersTunneledTo(toolbarActionsTunnel),\n Button: {\n tunnel: toolbarActionsTunnel,\n size: \"s\",\n variant: \"plain\",\n color: \"dark\",\n isDisabled: toolbarActionsDisabled,\n },\n };\n\n const handleKeyDown: KeyboardEventHandler = (event) => {\n if (event.key !== \"Enter\") {\n return;\n }\n\n const modifyParams = modifyValueByMarkdownSyntax(value, inputRef);\n if (!modifyParams) {\n return;\n }\n\n const { newValue, newSelectionStart, newSelectionEnd } = modifyParams;\n\n requestAnimationFrame(() => {\n if (inputRef.current) {\n inputRef.current.value = newValue;\n inputRef.current?.setSelectionRange(newSelectionStart, newSelectionEnd);\n scrollToCursor(newValue, inputRef.current);\n }\n });\n\n event.preventDefault();\n onChange(newValue);\n };\n\n const handleToolButtonPressed = (type: InsertType) => {\n const { newValue, newSelectionStart, newSelectionEnd } = modifyValueByType(\n value,\n type,\n inputRef,\n );\n\n requestAnimationFrame(() => {\n if (inputRef.current) {\n inputRef.current.value = newValue;\n inputRef.current.setSelectionRange(newSelectionStart, newSelectionEnd);\n inputRef.current.focus();\n }\n });\n\n onChange(newValue);\n };\n\n return (\n <div {...dropProps} className={rootClassName}>\n <TextArea\n {...rest}\n aria-hidden={mode === \"preview\"}\n isReadOnly={isReadOnly || mode === \"preview\"}\n isDisabled={isDisabled}\n ref={inputRef}\n value={value}\n rows={rows}\n autoResizeMaxRows={autoResizeMaxRows}\n onChange={onChange}\n onKeyDown={handleKeyDown}\n >\n <PropsContextProvider\n props={toolbarActionsPropsContext}\n dependencies={[toolbarActionsDisabled]}\n >\n {children}\n {mode === \"preview\" && (\n <MarkdownComponent\n headingOffset={headingOffset}\n className={styles.markdown}\n style={{\n height: inputRef.current?.offsetHeight,\n }}\n >\n {value}\n </MarkdownComponent>\n )}\n <Toolbar\n currentMode={mode}\n isDisabled={isDisabled}\n onModeChange={setMode}\n onToolPressed={handleToolButtonPressed}\n accept={accept}\n isReadOnly={isReadOnly}\n onFilesSelected={uploadFiles}\n />\n </PropsContextProvider>\n </TextArea>\n </div>\n );\n});\n\nexport default MarkdownEditor;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgCA,IAAM,uBAAuB;CAC3B,IAAI;CACJ,WAAW;AACb;;AA8BA,IAAa,iBAAiB,cAAc,mBAAmB,UAAU;CACvE,MAAM,EACJ,YACA,YACA,UACA,WACA,OAAO,GACP,mBACA,eACA,mBAAmB,oBAAoB,UACvC,OACA,UACA,YACA,QACA,KACA,GAAG,SACD,4BAA4B,OAAO,EAAE;CAEzC,MAAM,WAAW,aAAa,GAAG;CACjC,MAAM,CAAC,MAAM,WAAW,SAA6B,QAAQ;CAC7D,MAAM,yBAAyB,cAAc,SAAS;CAEtD,MAAM,EAAE,WAAW,cAAc,gBAAgB,cAAc;EAC7D;EACA;EACA,YAAY,cAAc,cAAc,SAAS;EACjD;EACA;EACA,aAAa;CACf,CAAC;CAED,MAAM,gBAAgB,KACpB,8BAAO,gBACP,8BAAO,QAAQ,SACf,gBAAgB,8BAAO,YACvB,SACF;CAEA,MAAM,6BAA2C;EAC/C,GAAG,0BAA0B,oBAAoB;EACjD,QAAQ;GACN,QAAQ;GACR,MAAM;GACN,SAAS;GACT,OAAO;GACP,YAAY;EACd;CACF;CAEA,MAAM,iBAAuC,UAAU;EACrD,IAAI,MAAM,QAAQ,SAChB;EAGF,MAAM,eAAe,4BAA4B,OAAO,QAAQ;EAChE,IAAI,CAAC,cACH;EAGF,MAAM,EAAE,UAAU,mBAAmB,oBAAoB;EAEzD,4BAA4B;GAC1B,IAAI,SAAS,SAAS;IACpB,SAAS,QAAQ,QAAQ;IACzB,SAAS,SAAS,kBAAkB,mBAAmB,eAAe;IACtE,eAAe,UAAU,SAAS,OAAO;GAC3C;EACF,CAAC;EAED,MAAM,eAAe;EACrB,SAAS,QAAQ;CACnB;CAEA,MAAM,2BAA2B,SAAqB;EACpD,MAAM,EAAE,UAAU,mBAAmB,oBAAoB,kBACvD,OACA,MACA,QACF;EAEA,4BAA4B;GAC1B,IAAI,SAAS,SAAS;IACpB,SAAS,QAAQ,QAAQ;IACzB,SAAS,QAAQ,kBAAkB,mBAAmB,eAAe;IACrE,SAAS,QAAQ,MAAM;GACzB;EACF,CAAC;EAED,SAAS,QAAQ;CACnB;CAEA,OACE,oBAAC,OAAD;EAAK,GAAI;EAAW,WAAW;EAC7B,UAAA,oBAAC,UAAD;GACE,GAAI;GACJ,eAAa,SAAS;GACtB,YAAY,cAAc,SAAS;GACvB;GACZ,KAAK;GACE;GACD;GACa;GACT;GACV,WAAW;GAEX,UAAA,qBAAC,sBAAD;IACE,OAAO;IACP,cAAc,CAAC,sBAAsB;IAFvC,UAAA;KAIG;KACA,SAAS,aACR,oBAAC,mBAAD;MACiB;MACf,WAAW,8BAAO;MAClB,OAAO,EACL,QAAQ,SAAS,SAAS,aAC5B;MAEC,UAAA;KACgB,CAAA;KAErB,oBAAC,SAAD;MACE,aAAa;MACD;MACZ,cAAc;MACd,eAAe;MACP;MACI;MACZ,iBAAiB;KAClB,CAAA;IACmB;;EACd,CAAA;CACP,CAAA;AAET,CAAC"}
@@ -6,6 +6,7 @@ var toolbar = "flow--markdown-editor--toolbar";
6
6
  var toolbarButtons = "flow--markdown-editor--toolbar-buttons";
7
7
  var modeButton = "flow--markdown-editor--mode-button";
8
8
  var markdown = "flow--markdown-editor--markdown";
9
+ var dropTarget = "flow--markdown-editor--drop-target";
9
10
  var MarkdownEditor_module_default = {
10
11
  markdownEditor,
11
12
  toolbar,
@@ -13,9 +14,10 @@ var MarkdownEditor_module_default = {
13
14
  modeButton,
14
15
  markdown,
15
16
  "mode-editor": "flow--markdown-editor--mode-editor",
16
- "mode-preview": "flow--markdown-editor--mode-preview"
17
+ "mode-preview": "flow--markdown-editor--mode-preview",
18
+ dropTarget
17
19
  };
18
20
  //#endregion
19
- export { MarkdownEditor_module_default as default, markdown, markdownEditor, modeButton, toolbar, toolbarButtons };
21
+ export { MarkdownEditor_module_default as default, dropTarget, markdown, markdownEditor, modeButton, toolbar, toolbarButtons };
20
22
 
21
23
  //# sourceMappingURL=MarkdownEditor.module.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"MarkdownEditor.module.mjs","names":[],"sources":["../../../../../../../src/components/MarkdownEditor/MarkdownEditor.module.scss"],"sourcesContent":["@use \"@/styles/mixins/formControl\";\n@use \"@/styles/mixins/scrollbar\";\n\n.markdownEditor {\n display: grid;\n grid-template-areas:\n \"label\"\n \"toolbar\"\n \"content\"\n \"fieldDescription\";\n\n .toolbar {\n grid-area: toolbar;\n display: flex;\n justify-content: space-between;\n padding: var(--form-control--padding-y);\n column-gap: var(--form-control--padding-y);\n flex-wrap: wrap;\n\n @include formControl.formBorder;\n @include formControl.formColor;\n @include formControl.formAnimation;\n\n & {\n border-bottom: none;\n border-end-start-radius: 0;\n border-end-end-radius: 0;\n }\n }\n\n .toolbarButtons {\n display: flex;\n flex-wrap: wrap;\n column-gap: var(--form-control--padding-y);\n }\n\n .modeButton {\n margin-inline-start: auto;\n }\n\n .markdown {\n overflow-y: auto;\n\n @include formControl.formBorder;\n @include formControl.formColor;\n }\n\n :global(.flow--form-field) {\n display: contents;\n }\n\n /* The inner text-area element is react-aria's, but Flow's TextArea puts a\n class on it — select through that class instead of the element type. */\n :global(.flow--text-area--input),\n .markdown {\n @include scrollbar.scrollbar;\n\n grid-area: content;\n padding-block: calc(\n var(--form-control--padding-y) - var(--form-control--border-width)\n );\n padding-inline: var(--form-control--padding-x);\n border-start-start-radius: 0;\n border-start-end-radius: 0;\n }\n\n &.mode-editor {\n .markdown {\n display: none;\n }\n }\n\n &.mode-preview {\n :global(.flow--text-area--input) {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n width: 1px;\n height: 1px;\n inset-inline-start: 0;\n }\n }\n\n /* States */\n\n &:has(:global(.flow--text-area--input:hover)) .toolbar {\n background-color: var(--form-control--background-color--hover);\n }\n\n &:has(:global(.flow--text-area--input[data-invalid])) .toolbar,\n &:has(:global(.flow--text-area--input[data-invalid])) .markdown {\n background-color: var(--form-control--background-color--invalid);\n border-color: var(--form-control--border-color--invalid);\n }\n\n &:has(:global(.flow--text-area--input[data-focused=\"true\"])) .toolbar,\n &:has(:global(.flow--text-area--input[data-focused=\"true\"])) .markdown {\n background-color: var(--form-control--background-color--focused);\n border-color: var(--form-control--border-color--focused);\n }\n\n &:has(:global(.flow--text-area--input[data-disabled])).mode-editor .toolbar {\n background-color: var(--form-control--background-color--disabled);\n border-color: var(--form-control--border-color--disabled);\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"MarkdownEditor.module.mjs","names":[],"sources":["../../../../../../../src/components/MarkdownEditor/MarkdownEditor.module.scss"],"sourcesContent":["@use \"@/styles/mixins/formControl\";\n@use \"@/styles/mixins/scrollbar\";\n\n.markdownEditor {\n display: grid;\n grid-template-areas:\n \"label\"\n \"toolbar\"\n \"content\"\n \"fieldDescription\";\n\n .toolbar {\n grid-area: toolbar;\n display: flex;\n justify-content: space-between;\n padding: var(--form-control--padding-y);\n column-gap: var(--form-control--padding-y);\n flex-wrap: wrap;\n\n @include formControl.formBorder;\n @include formControl.formColor;\n @include formControl.formAnimation;\n\n & {\n border-bottom: none;\n border-end-start-radius: 0;\n border-end-end-radius: 0;\n }\n }\n\n .toolbarButtons {\n display: flex;\n flex-wrap: wrap;\n column-gap: var(--form-control--padding-y);\n }\n\n .modeButton {\n margin-inline-start: auto;\n }\n\n .markdown {\n overflow-y: auto;\n\n @include formControl.formBorder;\n @include formControl.formColor;\n }\n\n :global(.flow--form-field) {\n display: contents;\n }\n\n /* The inner text-area element is react-aria's, but Flow's TextArea puts a\n class on it — select through that class instead of the element type. */\n :global(.flow--text-area--input),\n .markdown {\n @include scrollbar.scrollbar;\n\n grid-area: content;\n padding-block: calc(\n var(--form-control--padding-y) - var(--form-control--border-width)\n );\n padding-inline: var(--form-control--padding-x);\n border-start-start-radius: 0;\n border-start-end-radius: 0;\n }\n\n &.mode-editor {\n .markdown {\n display: none;\n }\n }\n\n &.mode-preview {\n :global(.flow--text-area--input) {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n width: 1px;\n height: 1px;\n inset-inline-start: 0;\n }\n }\n\n /* States */\n\n &:has(:global(.flow--text-area--input:hover)) .toolbar {\n background-color: var(--form-control--background-color--hover);\n }\n\n &:has(:global(.flow--text-area--input[data-invalid])) .toolbar,\n &:has(:global(.flow--text-area--input[data-invalid])) .markdown {\n background-color: var(--form-control--background-color--invalid);\n border-color: var(--form-control--border-color--invalid);\n }\n\n &:has(:global(.flow--text-area--input[data-focused=\"true\"])) .toolbar,\n &:has(:global(.flow--text-area--input[data-focused=\"true\"])) .markdown {\n background-color: var(--form-control--background-color--focused);\n border-color: var(--form-control--border-color--focused);\n }\n\n &:has(:global(.flow--text-area--input[data-disabled])).mode-editor .toolbar {\n background-color: var(--form-control--background-color--disabled);\n border-color: var(--form-control--border-color--disabled);\n }\n\n /* A drag over the editor reads as its focused state -- dropping focuses it. */\n &.dropTarget {\n .toolbar,\n .markdown,\n :global(.flow--text-area--input) {\n background-color: var(--form-control--background-color--focused);\n border-color: var(--form-control--border-color--focused);\n }\n }\n}\n"],"mappings":""}
@@ -0,0 +1,47 @@
1
+ "use client"
2
+ /* */
3
+ import { IconAttachment } from "../../Icon/components/icons/IconAttachment.mjs";
4
+ import { ToolbarButton } from "./ToolbarButton.mjs";
5
+ import { useRef } from "react";
6
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
+ import { useVisuallyHidden } from "react-aria";
8
+ //#region src/components/MarkdownEditor/components/AttachmentButton.tsx
9
+ /**
10
+ * The toolbar's file picker — the keyboard and pointer equivalent of dropping a
11
+ * file onto the editor. A `ToolbarButton` like every other tool, plus the
12
+ * hidden input it opens.
13
+ *
14
+ * @internal
15
+ */
16
+ var AttachmentButton = (props) => {
17
+ const { accept, isDisabled, onSelect } = props;
18
+ const inputRef = useRef(null);
19
+ const { visuallyHiddenProps } = useVisuallyHidden();
20
+ const handlePress = () => {
21
+ const input = inputRef.current;
22
+ if (!input) return;
23
+ input.value = "";
24
+ input.click();
25
+ };
26
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ToolbarButton, {
27
+ type: "attachment",
28
+ isDisabled,
29
+ onPress: handlePress,
30
+ children: /* @__PURE__ */ jsx(IconAttachment, {})
31
+ }), /* @__PURE__ */ jsx("input", {
32
+ ...visuallyHiddenProps,
33
+ ref: inputRef,
34
+ type: "file",
35
+ accept,
36
+ multiple: true,
37
+ tabIndex: -1,
38
+ onChange: (event) => {
39
+ const files = event.target.files;
40
+ if (files?.length) onSelect(Array.from(files));
41
+ }
42
+ })] });
43
+ };
44
+ //#endregion
45
+ export { AttachmentButton };
46
+
47
+ //# sourceMappingURL=AttachmentButton.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttachmentButton.mjs","names":[],"sources":["../../../../../../../../src/components/MarkdownEditor/components/AttachmentButton.tsx"],"sourcesContent":["import { type FC, useRef } from \"react\";\nimport { useVisuallyHidden } from \"react-aria\";\nimport { IconAttachment } from \"@/components/Icon/components/icons\";\nimport { ToolbarButton } from \"@/components/MarkdownEditor/components/ToolbarButton\";\n\n/** @internal */\nexport interface AttachmentButtonProps {\n /** Which file types to offer, as an `accept` attribute value. */\n accept?: string;\n isDisabled?: boolean;\n /** Called with the files the user picked. */\n onSelect: (files: File[]) => void;\n}\n\n/**\n * The toolbar's file picker — the keyboard and pointer equivalent of dropping a\n * file onto the editor. A `ToolbarButton` like every other tool, plus the\n * hidden input it opens.\n *\n * @internal\n */\nexport const AttachmentButton: FC<AttachmentButtonProps> = (props) => {\n const { accept, isDisabled, onSelect } = props;\n\n const inputRef = useRef<HTMLInputElement>(null);\n const { visuallyHiddenProps } = useVisuallyHidden();\n\n const handlePress = () => {\n const input = inputRef.current;\n\n if (!input) {\n return;\n }\n\n /*\n * Cleared first: an input that still holds the previous file fires no\n * `change` when the same file is picked again.\n */\n input.value = \"\";\n input.click();\n };\n\n return (\n <>\n <ToolbarButton\n type=\"attachment\"\n isDisabled={isDisabled}\n onPress={handlePress}\n >\n <IconAttachment />\n </ToolbarButton>\n\n {/*\n * A plain input rather than `FileField`'s `FileInput`: that one renders an\n * `Aria.Input`, which reads the `InputContext` the surrounding TextArea's\n * `Aria.TextField` provides and would be handed the editor's text as its\n * value — which a file input rejects outright.\n *\n * `tabIndex={-1}` keeps the hidden input out of the tab order; the button\n * in front of it is the focusable affordance.\n */}\n <input\n {...visuallyHiddenProps}\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple\n tabIndex={-1}\n onChange={(event) => {\n const files = event.target.files;\n\n if (files?.length) {\n onSelect(Array.from(files));\n }\n }}\n />\n </>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,IAAa,oBAA+C,UAAU;CACpE,MAAM,EAAE,QAAQ,YAAY,aAAa;CAEzC,MAAM,WAAW,OAAyB,IAAI;CAC9C,MAAM,EAAE,wBAAwB,kBAAkB;CAElD,MAAM,oBAAoB;EACxB,MAAM,QAAQ,SAAS;EAEvB,IAAI,CAAC,OACH;EAOF,MAAM,QAAQ;EACd,MAAM,MAAM;CACd;CAEA,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,eAAD;EACE,MAAK;EACO;EACZ,SAAS;EAET,UAAA,oBAAC,gBAAD,CAAiB,CAAA;CACJ,CAAA,GAWf,oBAAC,SAAD;EACE,GAAI;EACJ,KAAK;EACL,MAAK;EACG;EACR,UAAA;EACA,UAAU;EACV,WAAW,UAAU;GACnB,MAAM,QAAQ,MAAM,OAAO;GAE3B,IAAI,OAAO,QACT,SAAS,MAAM,KAAK,KAAK,CAAC;EAE9B;CACD,CAAA,CACD,EAAA,CAAA;AAEN"}
@@ -12,14 +12,18 @@ import { UiComponentTunnelExit } from "../../UiComponentTunnel/UiComponentTunnel
12
12
  import MarkdownEditor_module_default from "../MarkdownEditor.module.mjs";
13
13
  import { ToolbarButton } from "./ToolbarButton.mjs";
14
14
  import { ModeButton } from "./ModeButton.mjs";
15
+ import { AttachmentButton } from "./AttachmentButton.mjs";
15
16
  import "react";
16
17
  import { jsx, jsxs } from "react/jsx-runtime";
17
18
  //#region src/components/MarkdownEditor/components/Toolbar.tsx
18
19
  var Toolbar = (props) => {
19
- const sharedToolButtonProps = {
20
- onPress: props.onToolPressed,
21
- isDisabled: props.isDisabled || props.currentMode === "preview"
22
- };
20
+ const isDisabled = props.isDisabled || props.currentMode === "preview";
21
+ /** The props of a tool that inserts markdown — all of them but the attachment. */
22
+ const insertTool = (type) => ({
23
+ type,
24
+ isDisabled,
25
+ onPress: () => props.onToolPressed(type)
26
+ });
23
27
  return /* @__PURE__ */ jsxs("div", {
24
28
  className: MarkdownEditor_module_default.toolbar,
25
29
  role: "toolbar",
@@ -27,45 +31,42 @@ var Toolbar = (props) => {
27
31
  className: MarkdownEditor_module_default.toolbarButtons,
28
32
  children: [
29
33
  /* @__PURE__ */ jsx(ToolbarButton, {
30
- ...sharedToolButtonProps,
31
- type: "bold",
34
+ ...insertTool("bold"),
32
35
  children: /* @__PURE__ */ jsx(IconBold, {})
33
36
  }),
34
37
  /* @__PURE__ */ jsx(ToolbarButton, {
35
- ...sharedToolButtonProps,
36
- type: "italic",
38
+ ...insertTool("italic"),
37
39
  children: /* @__PURE__ */ jsx(IconItalic, {})
38
40
  }),
39
41
  /* @__PURE__ */ jsx(ToolbarButton, {
40
- ...sharedToolButtonProps,
41
- type: "strikeThrough",
42
+ ...insertTool("strikeThrough"),
42
43
  children: /* @__PURE__ */ jsx(IconStrikeThrough, {})
43
44
  }),
44
45
  /* @__PURE__ */ jsx(ToolbarButton, {
45
- ...sharedToolButtonProps,
46
- type: "quote",
46
+ ...insertTool("quote"),
47
47
  children: /* @__PURE__ */ jsx(IconQuote, {})
48
48
  }),
49
49
  /* @__PURE__ */ jsx(ToolbarButton, {
50
- ...sharedToolButtonProps,
51
- type: "code",
50
+ ...insertTool("code"),
52
51
  children: /* @__PURE__ */ jsx(IconCode, {})
53
52
  }),
54
53
  /* @__PURE__ */ jsx(ToolbarButton, {
55
- ...sharedToolButtonProps,
56
- type: "link",
54
+ ...insertTool("link"),
57
55
  children: /* @__PURE__ */ jsx(IconLink, {})
58
56
  }),
59
57
  /* @__PURE__ */ jsx(ToolbarButton, {
60
- ...sharedToolButtonProps,
61
- type: "unorderedList",
58
+ ...insertTool("unorderedList"),
62
59
  children: /* @__PURE__ */ jsx(IconUnorderedList, {})
63
60
  }),
64
61
  /* @__PURE__ */ jsx(ToolbarButton, {
65
- ...sharedToolButtonProps,
66
- type: "orderedList",
62
+ ...insertTool("orderedList"),
67
63
  children: /* @__PURE__ */ jsx(IconOrderedList, {})
68
64
  }),
65
+ props.onFilesSelected && /* @__PURE__ */ jsx(AttachmentButton, {
66
+ accept: props.accept,
67
+ isDisabled: isDisabled || props.isReadOnly,
68
+ onSelect: props.onFilesSelected
69
+ }),
69
70
  /* @__PURE__ */ jsx(UiComponentTunnelExit, {
70
71
  id: "toolbarActions",
71
72
  component: "MarkdownEditor"
@@ -1 +1 @@
1
- {"version":3,"file":"Toolbar.mjs","names":[],"sources":["../../../../../../../../src/components/MarkdownEditor/components/Toolbar.tsx"],"sourcesContent":["import { type FC } from \"react\";\nimport styles from \"@/components/MarkdownEditor/MarkdownEditor.module.scss\";\nimport {\n IconBold,\n IconCode,\n IconItalic,\n IconLink,\n IconOrderedList,\n IconQuote,\n IconStrikeThrough,\n IconUnorderedList,\n} from \"@/components/Icon/components/icons\";\nimport {\n ToolbarButton,\n type ToolBarButtonProps,\n} from \"@/components/MarkdownEditor/components/ToolbarButton\";\nimport {\n ModeButton,\n type ModeButtonProps,\n} from \"@/components/MarkdownEditor/components/ModeButton\";\nimport { type ButtonProps } from \"@/components/Button\";\nimport { UiComponentTunnelExit } from \"@/components/UiComponentTunnel/UiComponentTunnelExit\";\n\ninterface ToolbarProps extends Pick<ButtonProps, \"isDisabled\"> {\n currentMode: ModeButtonProps[\"currentMode\"];\n onModeChange: ModeButtonProps[\"onChange\"];\n onToolPressed: ToolBarButtonProps[\"onPress\"];\n}\n\nexport const Toolbar: FC<ToolbarProps> = (props) => {\n const sharedToolButtonProps: Partial<ToolBarButtonProps> = {\n onPress: props.onToolPressed,\n isDisabled: props.isDisabled || props.currentMode === \"preview\",\n };\n\n return (\n <div className={styles.toolbar} role=\"toolbar\">\n <div className={styles.toolbarButtons}>\n <ToolbarButton {...sharedToolButtonProps} type=\"bold\">\n <IconBold />\n </ToolbarButton>\n\n <ToolbarButton {...sharedToolButtonProps} type=\"italic\">\n <IconItalic />\n </ToolbarButton>\n\n <ToolbarButton {...sharedToolButtonProps} type=\"strikeThrough\">\n <IconStrikeThrough />\n </ToolbarButton>\n\n <ToolbarButton {...sharedToolButtonProps} type=\"quote\">\n <IconQuote />\n </ToolbarButton>\n\n <ToolbarButton {...sharedToolButtonProps} type=\"code\">\n <IconCode />\n </ToolbarButton>\n\n <ToolbarButton {...sharedToolButtonProps} type=\"link\">\n <IconLink />\n </ToolbarButton>\n\n <ToolbarButton {...sharedToolButtonProps} type=\"unorderedList\">\n <IconUnorderedList />\n </ToolbarButton>\n\n <ToolbarButton {...sharedToolButtonProps} type=\"orderedList\">\n <IconOrderedList />\n </ToolbarButton>\n\n <UiComponentTunnelExit id=\"toolbarActions\" component=\"MarkdownEditor\" />\n </div>\n\n <ModeButton\n onChange={props.onModeChange}\n currentMode={props.currentMode}\n isDisabled={props.isDisabled ?? false}\n />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA6BA,IAAa,WAA6B,UAAU;CAClD,MAAM,wBAAqD;EACzD,SAAS,MAAM;EACf,YAAY,MAAM,cAAc,MAAM,gBAAgB;CACxD;CAEA,OACE,qBAAC,OAAD;EAAK,WAAW,8BAAO;EAAS,MAAK;EAArC,UAAA,CACE,qBAAC,OAAD;GAAK,WAAW,8BAAO;GAAvB,UAAA;IACE,oBAAC,eAAD;KAAe,GAAI;KAAuB,MAAK;KAC7C,UAAA,oBAAC,UAAD,CAAW,CAAA;IACE,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI;KAAuB,MAAK;KAC7C,UAAA,oBAAC,YAAD,CAAa,CAAA;IACA,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI;KAAuB,MAAK;KAC7C,UAAA,oBAAC,mBAAD,CAAoB,CAAA;IACP,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI;KAAuB,MAAK;KAC7C,UAAA,oBAAC,WAAD,CAAY,CAAA;IACC,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI;KAAuB,MAAK;KAC7C,UAAA,oBAAC,UAAD,CAAW,CAAA;IACE,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI;KAAuB,MAAK;KAC7C,UAAA,oBAAC,UAAD,CAAW,CAAA;IACE,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI;KAAuB,MAAK;KAC7C,UAAA,oBAAC,mBAAD,CAAoB,CAAA;IACP,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI;KAAuB,MAAK;KAC7C,UAAA,oBAAC,iBAAD,CAAkB,CAAA;IACL,CAAA;IAEf,oBAAC,uBAAD;KAAuB,IAAG;KAAiB,WAAU;IAAkB,CAAA;GACpE;EAEL,CAAA,GAAA,oBAAC,YAAD;GACE,UAAU,MAAM;GAChB,aAAa,MAAM;GACnB,YAAY,MAAM,cAAc;EACjC,CAAA,CACE;;AAET"}
1
+ {"version":3,"file":"Toolbar.mjs","names":[],"sources":["../../../../../../../../src/components/MarkdownEditor/components/Toolbar.tsx"],"sourcesContent":["import { type FC } from \"react\";\nimport styles from \"@/components/MarkdownEditor/MarkdownEditor.module.scss\";\nimport {\n IconBold,\n IconCode,\n IconItalic,\n IconLink,\n IconOrderedList,\n IconQuote,\n IconStrikeThrough,\n IconUnorderedList,\n} from \"@/components/Icon/components/icons\";\nimport {\n ToolbarButton,\n type ToolBarButtonProps,\n} from \"@/components/MarkdownEditor/components/ToolbarButton\";\nimport {\n ModeButton,\n type ModeButtonProps,\n} from \"@/components/MarkdownEditor/components/ModeButton\";\nimport { type ButtonProps } from \"@/components/Button\";\nimport { type InsertType } from \"@/components/MarkdownEditor/lib/modifyValueByType\";\nimport { UiComponentTunnelExit } from \"@/components/UiComponentTunnel/UiComponentTunnelExit\";\nimport { AttachmentButton } from \"@/components/MarkdownEditor/components/AttachmentButton\";\n\ninterface ToolbarProps extends Pick<ButtonProps, \"isDisabled\"> {\n currentMode: ModeButtonProps[\"currentMode\"];\n onModeChange: ModeButtonProps[\"onChange\"];\n onToolPressed: (type: InsertType) => void;\n /** Which file types the attachment button offers. */\n accept?: string;\n /** Renders the attachment button when set. */\n onFilesSelected?: (files: File[]) => void;\n /**\n * Disables the attachment button, which a read-only editor takes no files\n * for.\n */\n isReadOnly?: boolean;\n}\n\nexport const Toolbar: FC<ToolbarProps> = (props) => {\n const isDisabled = props.isDisabled || props.currentMode === \"preview\";\n\n /** The props of a tool that inserts markdown — all of them but the attachment. */\n const insertTool = (type: InsertType): ToolBarButtonProps => ({\n type,\n isDisabled,\n onPress: () => props.onToolPressed(type),\n });\n\n return (\n <div className={styles.toolbar} role=\"toolbar\">\n <div className={styles.toolbarButtons}>\n <ToolbarButton {...insertTool(\"bold\")}>\n <IconBold />\n </ToolbarButton>\n\n <ToolbarButton {...insertTool(\"italic\")}>\n <IconItalic />\n </ToolbarButton>\n\n <ToolbarButton {...insertTool(\"strikeThrough\")}>\n <IconStrikeThrough />\n </ToolbarButton>\n\n <ToolbarButton {...insertTool(\"quote\")}>\n <IconQuote />\n </ToolbarButton>\n\n <ToolbarButton {...insertTool(\"code\")}>\n <IconCode />\n </ToolbarButton>\n\n <ToolbarButton {...insertTool(\"link\")}>\n <IconLink />\n </ToolbarButton>\n\n <ToolbarButton {...insertTool(\"unorderedList\")}>\n <IconUnorderedList />\n </ToolbarButton>\n\n <ToolbarButton {...insertTool(\"orderedList\")}>\n <IconOrderedList />\n </ToolbarButton>\n\n {props.onFilesSelected && (\n <AttachmentButton\n accept={props.accept}\n isDisabled={isDisabled || props.isReadOnly}\n onSelect={props.onFilesSelected}\n />\n )}\n\n <UiComponentTunnelExit id=\"toolbarActions\" component=\"MarkdownEditor\" />\n </div>\n\n <ModeButton\n onChange={props.onModeChange}\n currentMode={props.currentMode}\n isDisabled={props.isDisabled ?? false}\n />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwCA,IAAa,WAA6B,UAAU;CAClD,MAAM,aAAa,MAAM,cAAc,MAAM,gBAAgB;;CAG7D,MAAM,cAAc,UAA0C;EAC5D;EACA;EACA,eAAe,MAAM,cAAc,IAAI;CACzC;CAEA,OACE,qBAAC,OAAD;EAAK,WAAW,8BAAO;EAAS,MAAK;EAArC,UAAA,CACE,qBAAC,OAAD;GAAK,WAAW,8BAAO;GAAvB,UAAA;IACE,oBAAC,eAAD;KAAe,GAAI,WAAW,MAAM;KAClC,UAAA,oBAAC,UAAD,CAAW,CAAA;IACE,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI,WAAW,QAAQ;KACpC,UAAA,oBAAC,YAAD,CAAa,CAAA;IACA,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI,WAAW,eAAe;KAC3C,UAAA,oBAAC,mBAAD,CAAoB,CAAA;IACP,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI,WAAW,OAAO;KACnC,UAAA,oBAAC,WAAD,CAAY,CAAA;IACC,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI,WAAW,MAAM;KAClC,UAAA,oBAAC,UAAD,CAAW,CAAA;IACE,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI,WAAW,MAAM;KAClC,UAAA,oBAAC,UAAD,CAAW,CAAA;IACE,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI,WAAW,eAAe;KAC3C,UAAA,oBAAC,mBAAD,CAAoB,CAAA;IACP,CAAA;IAEf,oBAAC,eAAD;KAAe,GAAI,WAAW,aAAa;KACzC,UAAA,oBAAC,iBAAD,CAAkB,CAAA;IACL,CAAA;IAEd,MAAM,mBACL,oBAAC,kBAAD;KACE,QAAQ,MAAM;KACd,YAAY,cAAc,MAAM;KAChC,UAAU,MAAM;IACjB,CAAA;IAGH,oBAAC,uBAAD;KAAuB,IAAG;KAAiB,WAAU;IAAkB,CAAA;GACpE;EAEL,CAAA,GAAA,oBAAC,YAAD;GACE,UAAU,MAAM;GAChB,aAAa,MAAM;GACnB,YAAY,MAAM,cAAc;EACjC,CAAA,CACE;;AAET"}
@@ -17,7 +17,7 @@ var ToolbarButton = (props) => {
17
17
  size: "s",
18
18
  variant: "plain",
19
19
  color: "dark",
20
- onPress: () => onPress?.(type),
20
+ onPress,
21
21
  children
22
22
  });
23
23
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ToolbarButton.mjs","names":[],"sources":["../../../../../../../../src/components/MarkdownEditor/components/ToolbarButton.tsx"],"sourcesContent":["import { type FC } from \"react\";\nimport { type InsertType } from \"@/components/MarkdownEditor/lib/modifyValueByType\";\nimport { Button, type ButtonProps } from \"@/components/Button\";\nimport { useLocalizedStringFormatter } from \"@/components/TranslationProvider/useLocalizedStringFormatter\";\nimport locales from \"../locales/*.locale.json\";\n\nexport interface ToolBarButtonProps extends Pick<\n ButtonProps,\n \"isDisabled\" | \"children\"\n> {\n type: InsertType;\n onPress?: (type: InsertType) => void;\n}\n\nexport const ToolbarButton: FC<ToolBarButtonProps> = (props) => {\n const { children, type, onPress, ...rest } = props;\n\n const stringFormatter = useLocalizedStringFormatter(\n locales,\n \"MarkdownEditor\",\n );\n\n return (\n <Button\n {...rest}\n tunnel={null}\n data-button-type={type}\n aria-label={stringFormatter.format(`toolbar.${type}`)}\n size=\"s\"\n variant=\"plain\"\n color=\"dark\"\n onPress={() => onPress?.(type)}\n >\n {children}\n </Button>\n );\n};\n"],"mappings":";;;;;;;;AAcA,IAAa,iBAAyC,UAAU;CAC9D,MAAM,EAAE,UAAU,MAAM,SAAS,GAAG,SAAS;CAE7C,MAAM,kBAAkB,4BACtB,kBACA,gBACF;CAEA,OACE,oBAAC,QAAD;EACE,GAAI;EACJ,QAAQ;EACR,oBAAkB;EAClB,cAAY,gBAAgB,OAAO,WAAW,MAAM;EACpD,MAAK;EACL,SAAQ;EACR,OAAM;EACN,eAAe,UAAU,IAAI;EAE5B;CACK,CAAA;AAEZ"}
1
+ {"version":3,"file":"ToolbarButton.mjs","names":[],"sources":["../../../../../../../../src/components/MarkdownEditor/components/ToolbarButton.tsx"],"sourcesContent":["import { type FC } from \"react\";\nimport { type InsertType } from \"@/components/MarkdownEditor/lib/modifyValueByType\";\nimport { Button, type ButtonProps } from \"@/components/Button\";\nimport { useLocalizedStringFormatter } from \"@/components/TranslationProvider/useLocalizedStringFormatter\";\nimport locales from \"../locales/*.locale.json\";\n\n/**\n * What a toolbar button is: every markdown syntax it inserts, plus the one tool\n * that does something else. The name is also the `toolbar.<type>` locale key of\n * the button's label.\n */\nexport type ToolbarButtonType = InsertType | \"attachment\";\n\nexport interface ToolBarButtonProps extends Pick<\n ButtonProps,\n \"isDisabled\" | \"children\"\n> {\n type: ToolbarButtonType;\n onPress?: () => void;\n}\n\nexport const ToolbarButton: FC<ToolBarButtonProps> = (props) => {\n const { children, type, onPress, ...rest } = props;\n\n const stringFormatter = useLocalizedStringFormatter(\n locales,\n \"MarkdownEditor\",\n );\n\n return (\n <Button\n {...rest}\n tunnel={null}\n data-button-type={type}\n aria-label={stringFormatter.format(`toolbar.${type}`)}\n size=\"s\"\n variant=\"plain\"\n color=\"dark\"\n onPress={onPress}\n >\n {children}\n </Button>\n );\n};\n"],"mappings":";;;;;;;;AAqBA,IAAa,iBAAyC,UAAU;CAC9D,MAAM,EAAE,UAAU,MAAM,SAAS,GAAG,SAAS;CAE7C,MAAM,kBAAkB,4BACtB,kBACA,gBACF;CAEA,OACE,oBAAC,QAAD;EACE,GAAI;EACJ,QAAQ;EACR,oBAAkB;EAClB,cAAY,gBAAgB,OAAO,WAAW,MAAM;EACpD,MAAK;EACL,SAAQ;EACR,OAAM;EACG;EAER;CACK,CAAA;AAEZ"}
@@ -0,0 +1,73 @@
1
+ "use client"
2
+ /* */
3
+ //#region src/components/MarkdownEditor/lib/fileUpload.ts
4
+ var escapeComment = (fileName) => fileName.replace(/-(?=-)/g, "- ");
5
+ var escapeLinkLabel = (label) => label.replace(/[\\[\]]/g, "\\$&").replace(/\s+/g, " ").trim();
6
+ var escapeLinkTarget = (url) => /[\s()]/.test(url) ? `<${url.replace(/[<>]/g, encodeURIComponent)}>` : url;
7
+ /**
8
+ * What sits at the cursor while a file uploads. An HTML comment, so it stays
9
+ * out of the rendered preview and out of a message sent before the upload
10
+ * finished — the same marker GitHub leaves behind.
11
+ *
12
+ * @internal
13
+ */
14
+ var uploadPlaceholder = (fileName) => `<!-- Uploading "${escapeComment(fileName)}"... -->`;
15
+ /**
16
+ * The markdown a finished upload leaves behind: an embed for an image, a link
17
+ * for everything else.
18
+ *
19
+ * @internal
20
+ */
21
+ var uploadedFileMarkdown = (file, upload) => {
22
+ const link = `[${escapeLinkLabel(upload.name ?? file.name)}](${escapeLinkTarget(upload.url)})`;
23
+ return file.type.startsWith("image/") ? `!${link}` : link;
24
+ };
25
+ /**
26
+ * Splices `insert` into `value` at `caret`, on a line of its own, and reports
27
+ * where the cursor ends up.
28
+ *
29
+ * @internal
30
+ */
31
+ var insertAtCaret = (value, insert, caret) => {
32
+ const before = value.slice(0, caret);
33
+ const after = value.slice(caret);
34
+ const inserted = `${before.length > 0 && !before.endsWith("\n") ? "\n" : ""}${insert}${after.startsWith("\n") ? "" : "\n"}`;
35
+ return {
36
+ value: `${before}${inserted}${after}`,
37
+ selectionStart: caret + inserted.length,
38
+ selectionEnd: caret + inserted.length
39
+ };
40
+ };
41
+ /**
42
+ * Replaces the first occurrence of `search`, carrying the cursor along. Returns
43
+ * `null` when the editor no longer contains it — the user deleted the
44
+ * placeholder while the upload was running, and the result is dropped.
45
+ *
46
+ * @internal
47
+ */
48
+ var replacePlaceholder = (value, search, replacement, selectionStart, selectionEnd) => {
49
+ const index = value.indexOf(search);
50
+ if (index === -1) return null;
51
+ const end = index + search.length;
52
+ const delta = replacement.length - search.length;
53
+ const shift = (position) => {
54
+ if (position <= index) return position;
55
+ return position >= end ? position + delta : index + replacement.length;
56
+ };
57
+ return {
58
+ value: value.slice(0, index) + replacement + value.slice(end),
59
+ selectionStart: shift(selectionStart),
60
+ selectionEnd: shift(selectionEnd)
61
+ };
62
+ };
63
+ /**
64
+ * Takes a placeholder back out after a failed upload, including the line break
65
+ * it was inserted with.
66
+ *
67
+ * @internal
68
+ */
69
+ var removePlaceholder = (value, placeholder, selectionStart, selectionEnd) => replacePlaceholder(value, `${placeholder}\n`, "", selectionStart, selectionEnd) ?? replacePlaceholder(value, placeholder, "", selectionStart, selectionEnd);
70
+ //#endregion
71
+ export { insertAtCaret, removePlaceholder, replacePlaceholder, uploadPlaceholder, uploadedFileMarkdown };
72
+
73
+ //# sourceMappingURL=fileUpload.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileUpload.mjs","names":[],"sources":["../../../../../../../../src/components/MarkdownEditor/lib/fileUpload.ts"],"sourcesContent":["/**\n * The result of uploading a file dropped into, pasted into or selected in a\n * `MarkdownEditor`.\n */\nexport interface MarkdownEditorUpload {\n /** The URL the uploaded file is available at. */\n url: string;\n /**\n * The link text of the inserted markdown — the alt text for an image.\n * Defaults to the name of the uploaded file.\n */\n name?: string;\n}\n\n/** A new editor value together with where the cursor ends up in it. */\nexport interface ValueEdit {\n value: string;\n selectionStart: number;\n selectionEnd: number;\n}\n\n/*\n * A file name that closes the comment early would leave the rest of it as\n * visible text. Every way to close one starts with `--`: `-->` does, `--!>`\n * does too, and dropping only `-->` can forge a new one — `a-->>b` collapses to\n * `a-->b`. Breaking up every `--` is what holds in all three.\n */\nconst escapeComment = (fileName: string) => fileName.replace(/-(?=-)/g, \"- \");\n\n/*\n * Brackets are what ends a markdown link label, and a line break ends the link\n * altogether. A file name may legally contain both.\n *\n * The backslash is escaped in the same pass, and has to be: escaping only the\n * brackets turns `a\\]b` into `a\\\\]b`, which markdown reads as an escaped\n * backslash followed by a bare `]` — the label ends right there.\n */\nconst escapeLinkLabel = (label: string) =>\n label\n .replace(/[\\\\[\\]]/g, \"\\\\$&\")\n .replace(/\\s+/g, \" \")\n .trim();\n\n/*\n * A bare link target ends at the first whitespace or unbalanced parenthesis.\n * Pointy brackets take a target that contains either — and are themselves the\n * only thing that cannot appear inside them.\n */\nconst escapeLinkTarget = (url: string) =>\n /[\\s()]/.test(url) ? `<${url.replace(/[<>]/g, encodeURIComponent)}>` : url;\n\n/**\n * What sits at the cursor while a file uploads. An HTML comment, so it stays\n * out of the rendered preview and out of a message sent before the upload\n * finished — the same marker GitHub leaves behind.\n *\n * @internal\n */\nexport const uploadPlaceholder = (fileName: string): string =>\n `<!-- Uploading \"${escapeComment(fileName)}\"... -->`;\n\n/**\n * The markdown a finished upload leaves behind: an embed for an image, a link\n * for everything else.\n *\n * @internal\n */\nexport const uploadedFileMarkdown = (\n file: Pick<File, \"name\" | \"type\">,\n upload: MarkdownEditorUpload,\n): string => {\n const label = escapeLinkLabel(upload.name ?? file.name);\n const target = escapeLinkTarget(upload.url);\n const link = `[${label}](${target})`;\n\n return file.type.startsWith(\"image/\") ? `!${link}` : link;\n};\n\n/**\n * Splices `insert` into `value` at `caret`, on a line of its own, and reports\n * where the cursor ends up.\n *\n * @internal\n */\nexport const insertAtCaret = (\n value: string,\n insert: string,\n caret: number,\n): ValueEdit => {\n const before = value.slice(0, caret);\n const after = value.slice(caret);\n\n const prefix = before.length > 0 && !before.endsWith(\"\\n\") ? \"\\n\" : \"\";\n const suffix = after.startsWith(\"\\n\") ? \"\" : \"\\n\";\n const inserted = `${prefix}${insert}${suffix}`;\n\n return {\n value: `${before}${inserted}${after}`,\n selectionStart: caret + inserted.length,\n selectionEnd: caret + inserted.length,\n };\n};\n\n/**\n * Replaces the first occurrence of `search`, carrying the cursor along. Returns\n * `null` when the editor no longer contains it — the user deleted the\n * placeholder while the upload was running, and the result is dropped.\n *\n * @internal\n */\nexport const replacePlaceholder = (\n value: string,\n search: string,\n replacement: string,\n selectionStart: number,\n selectionEnd: number,\n): ValueEdit | null => {\n const index = value.indexOf(search);\n\n if (index === -1) {\n return null;\n }\n\n const end = index + search.length;\n const delta = replacement.length - search.length;\n\n /*\n * A cursor before the placeholder does not move, one after it moves by the\n * length difference, and one inside it — the user was typing where the file\n * landed — comes to rest behind the replacement.\n */\n const shift = (position: number) => {\n if (position <= index) {\n return position;\n }\n return position >= end ? position + delta : index + replacement.length;\n };\n\n return {\n value: value.slice(0, index) + replacement + value.slice(end),\n selectionStart: shift(selectionStart),\n selectionEnd: shift(selectionEnd),\n };\n};\n\n/**\n * Takes a placeholder back out after a failed upload, including the line break\n * it was inserted with.\n *\n * @internal\n */\nexport const removePlaceholder = (\n value: string,\n placeholder: string,\n selectionStart: number,\n selectionEnd: number,\n): ValueEdit | null =>\n replacePlaceholder(\n value,\n `${placeholder}\\n`,\n \"\",\n selectionStart,\n selectionEnd,\n ) ?? replacePlaceholder(value, placeholder, \"\", selectionStart, selectionEnd);\n"],"mappings":";;;AA2BA,IAAM,iBAAiB,aAAqB,SAAS,QAAQ,WAAW,IAAI;AAU5E,IAAM,mBAAmB,UACvB,MACG,QAAQ,YAAY,MAAM,CAAC,CAC3B,QAAQ,QAAQ,GAAG,CAAC,CACpB,KAAK;AAOV,IAAM,oBAAoB,QACxB,SAAS,KAAK,GAAG,IAAI,IAAI,IAAI,QAAQ,SAAS,kBAAkB,EAAE,KAAK;;;;;;;;AASzE,IAAa,qBAAqB,aAChC,mBAAmB,cAAc,QAAQ,EAAE;;;;;;;AAQ7C,IAAa,wBACX,MACA,WACW;CAGX,MAAM,OAAO,IAFC,gBAAgB,OAAO,QAAQ,KAAK,IAEjC,EAAM,IADR,iBAAiB,OAAO,GACZ,EAAO;CAElC,OAAO,KAAK,KAAK,WAAW,QAAQ,IAAI,IAAI,SAAS;AACvD;;;;;;;AAQA,IAAa,iBACX,OACA,QACA,UACc;CACd,MAAM,SAAS,MAAM,MAAM,GAAG,KAAK;CACnC,MAAM,QAAQ,MAAM,MAAM,KAAK;CAI/B,MAAM,WAAW,GAFF,OAAO,SAAS,KAAK,CAAC,OAAO,SAAS,IAAI,IAAI,OAAO,KAEvC,SADd,MAAM,WAAW,IAAI,IAAI,KAAK;CAG7C,OAAO;EACL,OAAO,GAAG,SAAS,WAAW;EAC9B,gBAAgB,QAAQ,SAAS;EACjC,cAAc,QAAQ,SAAS;CACjC;AACF;;;;;;;;AASA,IAAa,sBACX,OACA,QACA,aACA,gBACA,iBACqB;CACrB,MAAM,QAAQ,MAAM,QAAQ,MAAM;CAElC,IAAI,UAAU,IACZ,OAAO;CAGT,MAAM,MAAM,QAAQ,OAAO;CAC3B,MAAM,QAAQ,YAAY,SAAS,OAAO;CAO1C,MAAM,SAAS,aAAqB;EAClC,IAAI,YAAY,OACd,OAAO;EAET,OAAO,YAAY,MAAM,WAAW,QAAQ,QAAQ,YAAY;CAClE;CAEA,OAAO;EACL,OAAO,MAAM,MAAM,GAAG,KAAK,IAAI,cAAc,MAAM,MAAM,GAAG;EAC5D,gBAAgB,MAAM,cAAc;EACpC,cAAc,MAAM,YAAY;CAClC;AACF;;;;;;;AAQA,IAAa,qBACX,OACA,aACA,gBACA,iBAEA,mBACE,OACA,GAAG,YAAY,KACf,IACA,gBACA,YACF,KAAK,mBAAmB,OAAO,aAAa,IAAI,gBAAgB,YAAY"}
@@ -0,0 +1,141 @@
1
+ "use client"
2
+ /* */
3
+ import { useLocalizedStringFormatter } from "../../TranslationProvider/useLocalizedStringFormatter.mjs";
4
+ import { matchesAccept } from "../../../lib/files/acceptedFiles.mjs";
5
+ import __locale_default from "../../../../../../_virtual/_24.locale.mjs";
6
+ import { insertAtCaret, removePlaceholder, replacePlaceholder, uploadPlaceholder, uploadedFileMarkdown } from "./fileUpload.mjs";
7
+ import { useEffect, useRef, useState } from "react";
8
+ import { announce } from "@react-aria/live-announcer";
9
+ //#region src/components/MarkdownEditor/lib/useFileUpload.ts
10
+ /** The files of a paste or a drop that `accept` covers. */
11
+ var acceptedFrom = (files, accept) => Array.from(files ?? []).filter((file) => matchesAccept(file, accept));
12
+ /**
13
+ * Drop, paste and toolbar uploads for the `MarkdownEditor`: a placeholder
14
+ * comment goes to the cursor right away and is replaced by a markdown link once
15
+ * the consumer's upload resolves.
16
+ *
17
+ * @internal
18
+ */
19
+ var useFileUpload = (options) => {
20
+ const { accept, uploadFile, isDisabled, value, onChange, textAreaRef } = options;
21
+ const stringFormatter = useLocalizedStringFormatter(__locale_default, "MarkdownEditor");
22
+ const isEnabled = !!uploadFile && !isDisabled;
23
+ const valueRef = useRef(value);
24
+ useEffect(() => {
25
+ valueRef.current = value;
26
+ }, [value]);
27
+ const readValue = () => textAreaRef.current?.value ?? valueRef.current;
28
+ const wasFocusedRef = useRef(false);
29
+ useEffect(() => {
30
+ const textArea = textAreaRef.current;
31
+ if (!textArea) return;
32
+ const rememberFocus = () => {
33
+ wasFocusedRef.current = true;
34
+ };
35
+ textArea.addEventListener("focus", rememberFocus);
36
+ return () => textArea.removeEventListener("focus", rememberFocus);
37
+ }, [textAreaRef]);
38
+ const readSelection = () => {
39
+ const textArea = textAreaRef.current;
40
+ const fallback = readValue().length;
41
+ return {
42
+ start: textArea?.selectionStart ?? fallback,
43
+ end: textArea?.selectionEnd ?? fallback
44
+ };
45
+ };
46
+ const applyEdit = (edit) => {
47
+ valueRef.current = edit.value;
48
+ const textArea = textAreaRef.current;
49
+ if (textArea) {
50
+ textArea.value = edit.value;
51
+ requestAnimationFrame(() => {
52
+ textArea.setSelectionRange(edit.selectionStart, edit.selectionEnd);
53
+ });
54
+ }
55
+ onChange(edit.value);
56
+ };
57
+ const runUpload = async (upload, file, placeholder) => {
58
+ announce(stringFormatter.format("upload.pending", { name: file.name }), "polite");
59
+ try {
60
+ const result = await upload(file);
61
+ const selection = readSelection();
62
+ const edit = replacePlaceholder(readValue(), placeholder, uploadedFileMarkdown(file, result), selection.start, selection.end);
63
+ if (edit) applyEdit(edit);
64
+ announce(stringFormatter.format("upload.succeeded", { name: file.name }), "polite");
65
+ } catch {
66
+ const selection = readSelection();
67
+ const edit = removePlaceholder(readValue(), placeholder, selection.start, selection.end);
68
+ if (edit) applyEdit(edit);
69
+ announce(stringFormatter.format("upload.failed", { name: file.name }), "assertive");
70
+ }
71
+ };
72
+ const uploadFiles = (files) => {
73
+ if (!uploadFile || files.length === 0) return;
74
+ const textArea = textAreaRef.current;
75
+ const caret = textArea && wasFocusedRef.current ? textArea.selectionStart : readValue().length;
76
+ const uploads = files.map((file) => ({
77
+ file,
78
+ placeholder: uploadPlaceholder(file.name)
79
+ }));
80
+ applyEdit(insertAtCaret(readValue(), uploads.map((upload) => upload.placeholder).join("\n"), caret));
81
+ textArea?.focus();
82
+ for (const upload of uploads) runUpload(uploadFile, upload.file, upload.placeholder);
83
+ };
84
+ const uploadFilesRef = useRef(uploadFiles);
85
+ uploadFilesRef.current = uploadFiles;
86
+ useEffect(() => {
87
+ const textArea = textAreaRef.current;
88
+ if (!textArea || !isEnabled) return;
89
+ const handlePaste = (event) => {
90
+ const files = acceptedFrom(event.clipboardData?.files, accept);
91
+ if (files.length === 0) return;
92
+ event.preventDefault();
93
+ uploadFilesRef.current(files);
94
+ };
95
+ textArea.addEventListener("paste", handlePaste);
96
+ return () => textArea.removeEventListener("paste", handlePaste);
97
+ }, [
98
+ isEnabled,
99
+ accept,
100
+ textAreaRef
101
+ ]);
102
+ const dragHasFiles = (event) => event.dataTransfer.types.includes("Files");
103
+ const dragDepth = useRef(0);
104
+ const [isDropTarget, setIsDropTarget] = useState(false);
105
+ const endDrag = () => {
106
+ dragDepth.current = 0;
107
+ setIsDropTarget(false);
108
+ };
109
+ return {
110
+ dropProps: isEnabled ? {
111
+ onDragEnter: (event) => {
112
+ if (!dragHasFiles(event)) return;
113
+ event.preventDefault();
114
+ dragDepth.current += 1;
115
+ setIsDropTarget(true);
116
+ },
117
+ onDragOver: (event) => {
118
+ if (!dragHasFiles(event)) return;
119
+ event.preventDefault();
120
+ event.dataTransfer.dropEffect = "copy";
121
+ },
122
+ onDragLeave: (event) => {
123
+ if (!dragHasFiles(event)) return;
124
+ dragDepth.current = Math.max(0, dragDepth.current - 1);
125
+ if (dragDepth.current === 0) setIsDropTarget(false);
126
+ },
127
+ onDrop: (event) => {
128
+ if (!dragHasFiles(event)) return;
129
+ event.preventDefault();
130
+ endDrag();
131
+ uploadFilesRef.current(acceptedFrom(event.dataTransfer.files, accept));
132
+ }
133
+ } : {},
134
+ isDropTarget,
135
+ uploadFiles: uploadFile ? uploadFiles : void 0
136
+ };
137
+ };
138
+ //#endregion
139
+ export { useFileUpload };
140
+
141
+ //# sourceMappingURL=useFileUpload.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFileUpload.mjs","names":[],"sources":["../../../../../../../../src/components/MarkdownEditor/lib/useFileUpload.ts"],"sourcesContent":["import {\n type DragEvent as ReactDragEvent,\n type DragEventHandler,\n type RefObject,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { announce } from \"@react-aria/live-announcer\";\nimport { matchesAccept } from \"@/lib/files/acceptedFiles\";\nimport {\n insertAtCaret,\n type MarkdownEditorUpload,\n removePlaceholder,\n replacePlaceholder,\n uploadedFileMarkdown,\n uploadPlaceholder,\n type ValueEdit,\n} from \"@/components/MarkdownEditor/lib/fileUpload\";\nimport { useLocalizedStringFormatter } from \"@/components/TranslationProvider/useLocalizedStringFormatter\";\nimport locales from \"../locales/*.locale.json\";\n\n/** The files of a paste or a drop that `accept` covers. */\nconst acceptedFrom = (files: FileList | null | undefined, accept?: string) =>\n Array.from(files ?? []).filter((file) => matchesAccept(file, accept));\n\n/** @internal */\nexport type MarkdownEditorFileUploadHandler = (\n file: File,\n) => Promise<MarkdownEditorUpload>;\n\ninterface Options {\n /** Which file types to take, as an `accept` attribute value. */\n accept?: string;\n /** Uploads one file. Uploading is off entirely while this is undefined. */\n uploadFile?: MarkdownEditorFileUploadHandler;\n /** Turns dropping, pasting and the toolbar button off. */\n isDisabled?: boolean;\n value: string;\n onChange: (value: string) => void;\n textAreaRef: RefObject<HTMLTextAreaElement | null>;\n}\n\ninterface DropProps {\n onDragEnter?: DragEventHandler;\n onDragOver?: DragEventHandler;\n onDragLeave?: DragEventHandler;\n onDrop?: DragEventHandler;\n}\n\ninterface FileUpload {\n /** Drop handling for the editor's root element. */\n dropProps: DropProps;\n /** Whether a drag is currently over the editor. */\n isDropTarget: boolean;\n /**\n * Uploads the files the toolbar button picked. `undefined` while uploading is\n * off.\n */\n uploadFiles?: (files: File[]) => void;\n}\n\n/**\n * Drop, paste and toolbar uploads for the `MarkdownEditor`: a placeholder\n * comment goes to the cursor right away and is replaced by a markdown link once\n * the consumer's upload resolves.\n *\n * @internal\n */\nexport const useFileUpload = (options: Options): FileUpload => {\n const { accept, uploadFile, isDisabled, value, onChange, textAreaRef } =\n options;\n\n const stringFormatter = useLocalizedStringFormatter(\n locales,\n \"MarkdownEditor\",\n );\n\n const isEnabled = !!uploadFile && !isDisabled;\n\n /*\n * An upload resolves long after the drop, and the editor has kept taking\n * input in the meantime. The textarea's own value is the only one that is\n * current by then — the `value` prop is a render old whenever two uploads\n * resolve in the same tick.\n */\n const valueRef = useRef(value);\n useEffect(() => {\n valueRef.current = value;\n }, [value]);\n\n const readValue = () => textAreaRef.current?.value ?? valueRef.current;\n\n /*\n * A textarea keeps its `selectionStart` after losing focus, so the caret is\n * still readable once the attachment button has taken the focus away. What it\n * cannot tell apart is a caret at position 0 from one that was never placed —\n * both read 0, and appending is the better answer for the second.\n */\n const wasFocusedRef = useRef(false);\n useEffect(() => {\n const textArea = textAreaRef.current;\n\n if (!textArea) {\n return;\n }\n\n const rememberFocus = () => {\n wasFocusedRef.current = true;\n };\n\n textArea.addEventListener(\"focus\", rememberFocus);\n return () => textArea.removeEventListener(\"focus\", rememberFocus);\n }, [textAreaRef]);\n\n const readSelection = () => {\n const textArea = textAreaRef.current;\n const fallback = readValue().length;\n return {\n start: textArea?.selectionStart ?? fallback,\n end: textArea?.selectionEnd ?? fallback,\n };\n };\n\n const applyEdit = (edit: ValueEdit) => {\n valueRef.current = edit.value;\n\n const textArea = textAreaRef.current;\n if (textArea) {\n /*\n * Written before the re-render so an upload resolving in the same tick\n * reads this one's result, and the selection restored after it, once\n * React has rendered the controlled value.\n */\n textArea.value = edit.value;\n requestAnimationFrame(() => {\n textArea.setSelectionRange(edit.selectionStart, edit.selectionEnd);\n });\n }\n\n onChange(edit.value);\n };\n\n const runUpload = async (\n upload: MarkdownEditorFileUploadHandler,\n file: File,\n placeholder: string,\n ) => {\n announce(\n stringFormatter.format(\"upload.pending\", { name: file.name }),\n \"polite\",\n );\n\n try {\n const result = await upload(file);\n const selection = readSelection();\n const edit = replacePlaceholder(\n readValue(),\n placeholder,\n uploadedFileMarkdown(file, result),\n selection.start,\n selection.end,\n );\n\n if (edit) {\n applyEdit(edit);\n }\n\n announce(\n stringFormatter.format(\"upload.succeeded\", { name: file.name }),\n \"polite\",\n );\n } catch {\n const selection = readSelection();\n const edit = removePlaceholder(\n readValue(),\n placeholder,\n selection.start,\n selection.end,\n );\n\n if (edit) {\n applyEdit(edit);\n }\n\n announce(\n stringFormatter.format(\"upload.failed\", { name: file.name }),\n \"assertive\",\n );\n }\n };\n\n const uploadFiles = (files: File[]) => {\n if (!uploadFile || files.length === 0) {\n return;\n }\n\n const textArea = textAreaRef.current;\n const caret =\n textArea && wasFocusedRef.current\n ? textArea.selectionStart\n : readValue().length;\n\n const uploads = files.map((file) => ({\n file,\n placeholder: uploadPlaceholder(file.name),\n }));\n\n applyEdit(\n insertAtCaret(\n readValue(),\n uploads.map((upload) => upload.placeholder).join(\"\\n\"),\n caret,\n ),\n );\n textArea?.focus();\n\n for (const upload of uploads) {\n void runUpload(uploadFile, upload.file, upload.placeholder);\n }\n };\n\n const uploadFilesRef = useRef(uploadFiles);\n uploadFilesRef.current = uploadFiles;\n\n /*\n * The paste listener is attached imperatively rather than through react-aria's\n * `useClipboard`, which calls `preventDefault` on every paste it sees and\n * would take plain text pasting with it.\n */\n useEffect(() => {\n const textArea = textAreaRef.current;\n\n if (!textArea || !isEnabled) {\n return;\n }\n\n const handlePaste = (event: ClipboardEvent) => {\n const files = acceptedFrom(event.clipboardData?.files, accept);\n\n if (files.length === 0) {\n return;\n }\n\n event.preventDefault();\n uploadFilesRef.current(files);\n };\n\n textArea.addEventListener(\"paste\", handlePaste);\n return () => textArea.removeEventListener(\"paste\", handlePaste);\n }, [isEnabled, accept, textAreaRef]);\n\n /*\n * Native handlers rather than react-aria's `useDrop`: that one claims every\n * drag it sees — its `onDrop` calls `preventDefault` and `stopPropagation`\n * unconditionally — which is right for a dedicated drop surface like\n * `FileDropZone`, but here it would swallow a plain text drag into the\n * textarea. A drag is claimed only when it carries files, and\n * `dataTransfer.types` is the only place that says so: the public\n * `DragTypes` of `getDropOperation` offers just `has(type)`, which cannot\n * express it — the wildcard type is always true, and WebKit reports every\n * file drag as an unknown type that answers true to anything.\n */\n const dragHasFiles = (event: ReactDragEvent) =>\n event.dataTransfer.types.includes(\"Files\");\n\n /*\n * `dragenter` and `dragleave` fire again for every child the pointer\n * crosses, so the highlight follows their balance rather than the last one.\n */\n const dragDepth = useRef(0);\n const [isDropTarget, setIsDropTarget] = useState(false);\n\n const endDrag = () => {\n dragDepth.current = 0;\n setIsDropTarget(false);\n };\n\n const dropProps: DropProps = isEnabled\n ? {\n onDragEnter: (event) => {\n if (!dragHasFiles(event)) {\n return;\n }\n event.preventDefault();\n dragDepth.current += 1;\n setIsDropTarget(true);\n },\n onDragOver: (event) => {\n if (!dragHasFiles(event)) {\n return;\n }\n // Without this the browser refuses the drop and opens the file instead.\n event.preventDefault();\n event.dataTransfer.dropEffect = \"copy\";\n },\n onDragLeave: (event) => {\n if (!dragHasFiles(event)) {\n return;\n }\n dragDepth.current = Math.max(0, dragDepth.current - 1);\n if (dragDepth.current === 0) {\n setIsDropTarget(false);\n }\n },\n onDrop: (event) => {\n if (!dragHasFiles(event)) {\n return;\n }\n event.preventDefault();\n endDrag();\n uploadFilesRef.current(\n acceptedFrom(event.dataTransfer.files, accept),\n );\n },\n }\n : {};\n\n return {\n dropProps,\n isDropTarget,\n uploadFiles: uploadFile ? uploadFiles : undefined,\n };\n};\n"],"mappings":";;;;;;;;;;AAuBA,IAAM,gBAAgB,OAAoC,WACxD,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,SAAS,cAAc,MAAM,MAAM,CAAC;;;;;;;;AA6CtE,IAAa,iBAAiB,YAAiC;CAC7D,MAAM,EAAE,QAAQ,YAAY,YAAY,OAAO,UAAU,gBACvD;CAEF,MAAM,kBAAkB,4BACtB,kBACA,gBACF;CAEA,MAAM,YAAY,CAAC,CAAC,cAAc,CAAC;CAQnC,MAAM,WAAW,OAAO,KAAK;CAC7B,gBAAgB;EACd,SAAS,UAAU;CACrB,GAAG,CAAC,KAAK,CAAC;CAEV,MAAM,kBAAkB,YAAY,SAAS,SAAS,SAAS;CAQ/D,MAAM,gBAAgB,OAAO,KAAK;CAClC,gBAAgB;EACd,MAAM,WAAW,YAAY;EAE7B,IAAI,CAAC,UACH;EAGF,MAAM,sBAAsB;GAC1B,cAAc,UAAU;EAC1B;EAEA,SAAS,iBAAiB,SAAS,aAAa;EAChD,aAAa,SAAS,oBAAoB,SAAS,aAAa;CAClE,GAAG,CAAC,WAAW,CAAC;CAEhB,MAAM,sBAAsB;EAC1B,MAAM,WAAW,YAAY;EAC7B,MAAM,WAAW,UAAU,CAAC,CAAC;EAC7B,OAAO;GACL,OAAO,UAAU,kBAAkB;GACnC,KAAK,UAAU,gBAAgB;EACjC;CACF;CAEA,MAAM,aAAa,SAAoB;EACrC,SAAS,UAAU,KAAK;EAExB,MAAM,WAAW,YAAY;EAC7B,IAAI,UAAU;GAMZ,SAAS,QAAQ,KAAK;GACtB,4BAA4B;IAC1B,SAAS,kBAAkB,KAAK,gBAAgB,KAAK,YAAY;GACnE,CAAC;EACH;EAEA,SAAS,KAAK,KAAK;CACrB;CAEA,MAAM,YAAY,OAChB,QACA,MACA,gBACG;EACH,SACE,gBAAgB,OAAO,kBAAkB,EAAE,MAAM,KAAK,KAAK,CAAC,GAC5D,QACF;EAEA,IAAI;GACF,MAAM,SAAS,MAAM,OAAO,IAAI;GAChC,MAAM,YAAY,cAAc;GAChC,MAAM,OAAO,mBACX,UAAU,GACV,aACA,qBAAqB,MAAM,MAAM,GACjC,UAAU,OACV,UAAU,GACZ;GAEA,IAAI,MACF,UAAU,IAAI;GAGhB,SACE,gBAAgB,OAAO,oBAAoB,EAAE,MAAM,KAAK,KAAK,CAAC,GAC9D,QACF;EACF,QAAQ;GACN,MAAM,YAAY,cAAc;GAChC,MAAM,OAAO,kBACX,UAAU,GACV,aACA,UAAU,OACV,UAAU,GACZ;GAEA,IAAI,MACF,UAAU,IAAI;GAGhB,SACE,gBAAgB,OAAO,iBAAiB,EAAE,MAAM,KAAK,KAAK,CAAC,GAC3D,WACF;EACF;CACF;CAEA,MAAM,eAAe,UAAkB;EACrC,IAAI,CAAC,cAAc,MAAM,WAAW,GAClC;EAGF,MAAM,WAAW,YAAY;EAC7B,MAAM,QACJ,YAAY,cAAc,UACtB,SAAS,iBACT,UAAU,CAAC,CAAC;EAElB,MAAM,UAAU,MAAM,KAAK,UAAU;GACnC;GACA,aAAa,kBAAkB,KAAK,IAAI;EAC1C,EAAE;EAEF,UACE,cACE,UAAU,GACV,QAAQ,KAAK,WAAW,OAAO,WAAW,CAAC,CAAC,KAAK,IAAI,GACrD,KACF,CACF;EACA,UAAU,MAAM;EAEhB,KAAK,MAAM,UAAU,SACnB,UAAe,YAAY,OAAO,MAAM,OAAO,WAAW;CAE9D;CAEA,MAAM,iBAAiB,OAAO,WAAW;CACzC,eAAe,UAAU;CAOzB,gBAAgB;EACd,MAAM,WAAW,YAAY;EAE7B,IAAI,CAAC,YAAY,CAAC,WAChB;EAGF,MAAM,eAAe,UAA0B;GAC7C,MAAM,QAAQ,aAAa,MAAM,eAAe,OAAO,MAAM;GAE7D,IAAI,MAAM,WAAW,GACnB;GAGF,MAAM,eAAe;GACrB,eAAe,QAAQ,KAAK;EAC9B;EAEA,SAAS,iBAAiB,SAAS,WAAW;EAC9C,aAAa,SAAS,oBAAoB,SAAS,WAAW;CAChE,GAAG;EAAC;EAAW;EAAQ;CAAW,CAAC;CAanC,MAAM,gBAAgB,UACpB,MAAM,aAAa,MAAM,SAAS,OAAO;CAM3C,MAAM,YAAY,OAAO,CAAC;CAC1B,MAAM,CAAC,cAAc,mBAAmB,SAAS,KAAK;CAEtD,MAAM,gBAAgB;EACpB,UAAU,UAAU;EACpB,gBAAgB,KAAK;CACvB;CA0CA,OAAO;EACL,WAzC2B,YACzB;GACE,cAAc,UAAU;IACtB,IAAI,CAAC,aAAa,KAAK,GACrB;IAEF,MAAM,eAAe;IACrB,UAAU,WAAW;IACrB,gBAAgB,IAAI;GACtB;GACA,aAAa,UAAU;IACrB,IAAI,CAAC,aAAa,KAAK,GACrB;IAGF,MAAM,eAAe;IACrB,MAAM,aAAa,aAAa;GAClC;GACA,cAAc,UAAU;IACtB,IAAI,CAAC,aAAa,KAAK,GACrB;IAEF,UAAU,UAAU,KAAK,IAAI,GAAG,UAAU,UAAU,CAAC;IACrD,IAAI,UAAU,YAAY,GACxB,gBAAgB,KAAK;GAEzB;GACA,SAAS,UAAU;IACjB,IAAI,CAAC,aAAa,KAAK,GACrB;IAEF,MAAM,eAAe;IACrB,QAAQ;IACR,eAAe,QACb,aAAa,MAAM,aAAa,OAAO,MAAM,CAC/C;GACF;EACF,IACA,CAAC;EAIH;EACA,aAAa,aAAa,cAAc,KAAA;CAC1C;AACF"}
@@ -86,14 +86,16 @@ var Modal = flowComponent("Modal", (props) => {
86
86
  };
87
87
  }),
88
88
  closeOverlay: dynamic((props) => {
89
- if (props.closeOverlay === void 0) return;
90
- if (props.closeOverlay instanceof OverlayController || typeof props.closeOverlay === "string") return {
89
+ const { closeOverlay } = props;
90
+ if (closeOverlay === void 0 || closeOverlay === false) return closeOverlay;
91
+ if (closeOverlay === true) return { bypassConfirmation: true };
92
+ if (closeOverlay instanceof OverlayController || typeof closeOverlay === "string") return {
91
93
  bypassConfirmation: true,
92
- overlay: props.closeOverlay
94
+ overlay: closeOverlay
93
95
  };
94
96
  return {
95
97
  bypassConfirmation: true,
96
- ...props.closeOverlay
98
+ ...closeOverlay
97
99
  };
98
100
  })
99
101
  }