@intlayer/design-system 9.3.1 → 9.3.3

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 (129) hide show
  1. package/dist/esm/api/hooks/auth.mjs +2 -0
  2. package/dist/esm/api/hooks/auth.mjs.map +1 -1
  3. package/dist/esm/api/index.mjs +2 -2
  4. package/dist/esm/components/Browser/Browser.mjs +1 -1
  5. package/dist/esm/components/Browser/Browser.mjs.map +1 -1
  6. package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
  7. package/dist/esm/components/CopyButton/index.mjs +2 -1
  8. package/dist/esm/components/CopyButton/index.mjs.map +1 -1
  9. package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs +4 -5
  10. package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs.map +1 -1
  11. package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +6 -10
  12. package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs.map +1 -1
  13. package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs +1 -1
  14. package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.mjs +1 -1
  15. package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.mjs.map +1 -1
  16. package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +6 -6
  17. package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs.map +1 -1
  18. package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.mjs +1 -1
  19. package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.mjs.map +1 -1
  20. package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +3 -7
  21. package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
  22. package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +1 -1
  23. package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +2 -2
  24. package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +1 -1
  25. package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs +19 -12
  26. package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs.map +1 -1
  27. package/dist/esm/components/Form/FormBase.mjs.map +1 -1
  28. package/dist/esm/components/Form/elements/FormElementWrapper.mjs +1 -1
  29. package/dist/esm/components/Form/elements/FormElementWrapper.mjs.map +1 -1
  30. package/dist/esm/components/HTMLRender/HTMLRender.mjs +2 -2
  31. package/dist/esm/components/HTMLRender/HTMLRender.mjs.map +1 -1
  32. package/dist/esm/components/IDE/Code.mjs +3 -3
  33. package/dist/esm/components/IDE/Code.mjs.map +1 -1
  34. package/dist/esm/components/IDE/CodeBlockClient.mjs +16 -4
  35. package/dist/esm/components/IDE/CodeBlockClient.mjs.map +1 -1
  36. package/dist/esm/components/IDE/CodeBlockHighlight.mjs +14 -5
  37. package/dist/esm/components/IDE/CodeBlockHighlight.mjs.map +1 -1
  38. package/dist/esm/components/IDE/CodeBlockServer.mjs +5 -3
  39. package/dist/esm/components/IDE/CodeBlockServer.mjs.map +1 -1
  40. package/dist/esm/components/IDE/CodeBlockShiki.mjs +18 -18
  41. package/dist/esm/components/IDE/CodeBlockShiki.mjs.map +1 -1
  42. package/dist/esm/components/IDE/IDE.mjs +2 -5
  43. package/dist/esm/components/IDE/IDE.mjs.map +1 -1
  44. package/dist/esm/components/IDE/MarkDownRender.mjs +1 -2
  45. package/dist/esm/components/IDE/MarkDownRender.mjs.map +1 -1
  46. package/dist/esm/components/IDE/index.mjs +2 -2
  47. package/dist/esm/components/IDE/shikiThemes.mjs +18 -0
  48. package/dist/esm/components/IDE/shikiThemes.mjs.map +1 -0
  49. package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs +105 -4
  50. package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs.map +1 -1
  51. package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +16 -16
  52. package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
  53. package/dist/esm/components/MaxHeightSmoother/index.mjs +1 -1
  54. package/dist/esm/components/MaxHeightSmoother/index.mjs.map +1 -1
  55. package/dist/esm/components/MaxWidthSmoother/index.mjs +1 -1
  56. package/dist/esm/components/MaxWidthSmoother/index.mjs.map +1 -1
  57. package/dist/esm/components/Pattern/GridDistortionPattern.mjs +100 -15
  58. package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
  59. package/dist/esm/components/Popover/dynamic.mjs +5 -4
  60. package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
  61. package/dist/esm/components/Popover/index.mjs +2 -2
  62. package/dist/esm/components/Popover/static.mjs +47 -18
  63. package/dist/esm/components/Popover/static.mjs.map +1 -1
  64. package/dist/esm/components/Tab/Tab.mjs +37 -16
  65. package/dist/esm/components/Tab/Tab.mjs.map +1 -1
  66. package/dist/esm/components/TextArea/AutoSizeTextArea.mjs +3 -2
  67. package/dist/esm/components/TextArea/AutoSizeTextArea.mjs.map +1 -1
  68. package/dist/esm/components/index.mjs +3 -3
  69. package/dist/esm/hooks/useDevice.mjs +16 -6
  70. package/dist/esm/hooks/useDevice.mjs.map +1 -1
  71. package/dist/esm/hooks/useItemSelector.mjs +107 -112
  72. package/dist/esm/hooks/useItemSelector.mjs.map +1 -1
  73. package/dist/esm/libs/auth.mjs +16 -3
  74. package/dist/esm/libs/auth.mjs.map +1 -1
  75. package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs +1 -1
  76. package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs.map +1 -1
  77. package/dist/types/api/hooks/auth.d.ts.map +1 -1
  78. package/dist/types/components/Browser/Browser.d.ts +7 -1
  79. package/dist/types/components/Browser/Browser.d.ts.map +1 -1
  80. package/dist/types/components/CopyButton/index.d.ts +9 -3
  81. package/dist/types/components/CopyButton/index.d.ts.map +1 -1
  82. package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts +0 -1
  83. package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts.map +1 -1
  84. package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts +0 -1
  85. package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts.map +1 -1
  86. package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.d.ts +1 -1
  87. package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts +0 -1
  88. package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts.map +1 -1
  89. package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.d.ts +1 -1
  90. package/dist/types/components/ExpandCollapse/ExpandCollapse.d.ts.map +1 -1
  91. package/dist/types/components/Form/FormBase.d.ts +1 -1
  92. package/dist/types/components/Form/elements/FormElementWrapper.d.ts +1 -1
  93. package/dist/types/components/HTMLRender/HTMLRender.d.ts +1 -2
  94. package/dist/types/components/HTMLRender/HTMLRender.d.ts.map +1 -1
  95. package/dist/types/components/IDE/Code.d.ts +6 -1
  96. package/dist/types/components/IDE/Code.d.ts.map +1 -1
  97. package/dist/types/components/IDE/CodeBlockClient.d.ts +18 -2
  98. package/dist/types/components/IDE/CodeBlockClient.d.ts.map +1 -1
  99. package/dist/types/components/IDE/CodeBlockHighlight.d.ts +3 -2
  100. package/dist/types/components/IDE/CodeBlockHighlight.d.ts.map +1 -1
  101. package/dist/types/components/IDE/CodeBlockServer.d.ts +0 -1
  102. package/dist/types/components/IDE/CodeBlockServer.d.ts.map +1 -1
  103. package/dist/types/components/IDE/CodeBlockShiki.d.ts +0 -1
  104. package/dist/types/components/IDE/CodeBlockShiki.d.ts.map +1 -1
  105. package/dist/types/components/IDE/IDE.d.ts +0 -1
  106. package/dist/types/components/IDE/IDE.d.ts.map +1 -1
  107. package/dist/types/components/IDE/MarkDownRender.d.ts +0 -1
  108. package/dist/types/components/IDE/MarkDownRender.d.ts.map +1 -1
  109. package/dist/types/components/IDE/index.d.ts +2 -2
  110. package/dist/types/components/IDE/shikiThemes.d.ts +18 -0
  111. package/dist/types/components/IDE/shikiThemes.d.ts.map +1 -0
  112. package/dist/types/components/MarkDownRender/MarkDownIframe.d.ts.map +1 -1
  113. package/dist/types/components/MarkDownRender/MarkDownRender.d.ts +1 -2
  114. package/dist/types/components/MarkDownRender/MarkDownRender.d.ts.map +1 -1
  115. package/dist/types/components/Pattern/GridDistortionPattern.d.ts.map +1 -1
  116. package/dist/types/components/Popover/dynamic.d.ts.map +1 -1
  117. package/dist/types/components/Popover/index.d.ts +2 -2
  118. package/dist/types/components/Popover/static.d.ts +6 -1
  119. package/dist/types/components/Popover/static.d.ts.map +1 -1
  120. package/dist/types/components/Tab/Tab.d.ts +2 -2
  121. package/dist/types/components/Tab/Tab.d.ts.map +1 -1
  122. package/dist/types/components/index.d.ts +3 -3
  123. package/dist/types/hooks/useItemSelector.d.ts.map +1 -1
  124. package/dist/types/libs/auth.d.ts +3 -0
  125. package/dist/types/libs/auth.d.ts.map +1 -1
  126. package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts +9 -3
  127. package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts.map +1 -1
  128. package/package.json +43 -38
  129. package/tailwind.css +27 -1
@@ -13,8 +13,8 @@ import { StructureEditor } from "./StructureEditor.mjs";
13
13
  import { useEffect, useState } from "react";
14
14
  import { jsx, jsxs } from "react/jsx-runtime";
15
15
  import { ArrowLeft } from "lucide-react";
16
- import { useConfiguration, useDictionariesRecordActions, useFocusUnmergedDictionary } from "@intlayer/editor-react";
17
16
  import { useIntlayer } from "react-intlayer";
17
+ import { useConfiguration, useDictionariesRecordActions, useFocusUnmergedDictionary } from "@intlayer/editor-react";
18
18
 
19
19
  //#region src/components/DictionaryFieldEditor/DictionaryFieldEditor.tsx
20
20
  const DictionaryFieldEditor = ({ dictionary, onClickDictionaryList, isDarkMode, mode, onDelete, onSave, showReturnButton = true, rightContent }) => {
@@ -94,14 +94,10 @@ const DictionaryFieldEditor = ({ dictionary, onClickDictionaryList, isDarkMode,
94
94
  children: [
95
95
  mode.includes("remote") && activeTab === "details" && /* @__PURE__ */ jsx(DictionaryDetailsForm, {
96
96
  dictionary,
97
- mode,
98
- isDarkMode
97
+ mode
99
98
  }),
100
99
  activeTab === "structure" && /* @__PURE__ */ jsx(StructureEditor, { dictionary }),
101
- activeTab === "content" && /* @__PURE__ */ jsx(ContentEditor, {
102
- dictionary,
103
- isDarkMode
104
- }),
100
+ activeTab === "content" && /* @__PURE__ */ jsx(ContentEditor, { dictionary }),
105
101
  activeTab === "json" && /* @__PURE__ */ jsx(JSONEditor, {
106
102
  dictionary,
107
103
  isDarkMode
@@ -1 +1 @@
1
- {"version":3,"file":"DictionaryFieldEditor.mjs","names":[],"sources":["../../../../src/components/DictionaryFieldEditor/DictionaryFieldEditor.tsx"],"sourcesContent":["'use client';\n\nimport { Container } from '@components/Container';\nimport { CopyToClipboard } from '@components/CopyToClipboard';\nimport {\n useConfiguration,\n useDictionariesRecordActions,\n useFocusUnmergedDictionary,\n} from '@intlayer/editor-react';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { ArrowLeft } from 'lucide-react';\nimport { type FC, type ReactNode, useEffect, useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button } from '../Button';\nimport { LocaleSwitcherContentProvider } from '../LocaleSwitcherContentDropDown';\nimport { TabSelector } from '../TabSelector';\nimport { ContentEditor } from './ContentEditor';\nimport { DictionaryDetailsForm } from './DictionaryDetails/DictionaryDetailsForm';\nimport { JSONEditor } from './JSONEditor';\nimport { SaveForm } from './SaveForm/SaveForm';\nimport { StructureEditor } from './StructureEditor';\n\ntype DictionaryFieldEditorProps = {\n dictionary: Dictionary;\n onClickDictionaryList?: () => void;\n onDelete?: () => void;\n onSave?: () => void;\n isDarkMode?: boolean;\n mode: ('local' | 'remote')[];\n showReturnButton?: boolean;\n rightContent?: ReactNode;\n};\n\nexport const DictionaryFieldEditor: FC<DictionaryFieldEditorProps> = ({\n dictionary,\n onClickDictionaryList,\n isDarkMode,\n mode,\n onDelete,\n onSave,\n showReturnButton = true,\n rightContent,\n}) => {\n const config = useConfiguration();\n const { returnToDictionaryList } = useIntlayer('dictionary-field-editor');\n const { focusedContent, setFocusedContent } = useFocusUnmergedDictionary();\n const { setLocaleDictionary } = useDictionariesRecordActions();\n const [activeTab, setActiveTab] = useState<string>('content');\n\n useEffect(() => {\n setFocusedContent({\n ...(focusedContent ?? {}),\n dictionaryKey: dictionary.key,\n dictionaryLocalId: dictionary.localId,\n });\n setLocaleDictionary(dictionary);\n }, []);\n\n return (\n <LocaleSwitcherContentProvider\n availableLocales={config?.internationalization?.locales ?? []}\n >\n <div className=\"relative flex h-full min-h-0 w-full flex-1 flex-col md:overflow-hidden\">\n {showReturnButton && (\n <Button\n onClick={onClickDictionaryList}\n variant=\"hoverable\"\n className=\"z-10 mr-auto mb-6 ml-5 shrink-0\"\n color=\"text\"\n Icon={ArrowLeft}\n label={returnToDictionaryList.label.value}\n >\n {returnToDictionaryList.text}\n </Button>\n )}\n\n <div className=\"mb-22 flex min-h-0 flex-1 flex-col overflow-hidden\">\n {/* Tab headers */}\n <div className=\"sticky top-0 z-10 flex shrink-0 gap-3 rounded-xl bg-background/20 p-3 pb-4\">\n <TabSelector\n selectedChoice={activeTab}\n tabs={[\n ...(mode.includes('remote')\n ? [\n <button\n key=\"details\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'details'}\n onClick={() => setActiveTab('details')}\n type=\"button\"\n >\n Details\n </button>,\n ]\n : []),\n <button\n key=\"structure\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'structure'}\n onClick={() => setActiveTab('structure')}\n type=\"button\"\n >\n Structure\n </button>,\n <button\n key=\"content\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'content'}\n onClick={() => setActiveTab('content')}\n type=\"button\"\n >\n Content\n </button>,\n <button\n key=\"json\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'json'}\n onClick={() => setActiveTab('json')}\n type=\"button\"\n >\n JSON\n </button>,\n ]}\n hoverable\n color=\"text\"\n />\n <div className=\"flex w-full items-center justify-end\">\n {rightContent}\n </div>\n </div>\n {/* Tab content — only active panel is mounted */}\n <div className=\"min-h-0 flex-1 overflow-y-auto p-6\">\n <div className=\"flex w-full min-w-0 flex-col items-stretch gap-6\">\n {mode.includes('remote') && activeTab === 'details' && (\n <DictionaryDetailsForm\n dictionary={dictionary}\n mode={mode}\n isDarkMode={isDarkMode}\n />\n )}\n {activeTab === 'structure' && (\n <StructureEditor dictionary={dictionary} />\n )}\n {activeTab === 'content' && (\n <ContentEditor\n dictionary={dictionary}\n isDarkMode={isDarkMode}\n />\n )}\n {activeTab === 'json' && (\n <JSONEditor dictionary={dictionary} isDarkMode={isDarkMode} />\n )}\n </div>\n </div>\n </div>\n\n <div className=\"absolute bottom-3 z-20 w-full p-2\">\n <Container\n color=\"card\"\n roundedSize=\"2xl\"\n padding=\"sm\"\n className=\"w-full shrink-0 flex-row flex-wrap items-center justify-end gap-10 bg-background/20 md:bottom-0\"\n >\n <CopyToClipboard\n text={dictionary.id!}\n className=\"text-nowrap text-neutral text-sm\"\n size={9}\n >\n {dictionary.id}\n </CopyToClipboard>\n <SaveForm\n dictionary={dictionary}\n mode={mode}\n onDelete={() => {\n setFocusedContent(null);\n onDelete?.();\n }}\n onSave={onSave}\n />\n </Container>\n </div>\n </div>\n </LocaleSwitcherContentProvider>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiCA,MAAa,yBAAyD,EACpE,YACA,uBACA,YACA,MACA,UACA,QACA,mBAAmB,MACnB,mBACI;CACJ,MAAM,SAAS,iBAAiB;CAChC,MAAM,EAAE,2BAA2B,YAAY,yBAAyB;CACxE,MAAM,EAAE,gBAAgB,sBAAsB,2BAA2B;CACzE,MAAM,EAAE,wBAAwB,6BAA6B;CAC7D,MAAM,CAAC,WAAW,gBAAgB,SAAiB,SAAS;CAE5D,gBAAgB;EACd,kBAAkB;GAChB,GAAI,kBAAkB,CAAC;GACvB,eAAe,WAAW;GAC1B,mBAAmB,WAAW;EAChC,CAAC;EACD,oBAAoB,UAAU;CAChC,GAAG,CAAC,CAAC;CAEL,OACE,oBAAC,+BAAD;EACE,kBAAkB,QAAQ,sBAAsB,WAAW,CAAC;YAE5D,qBAAC,OAAD;GAAK,WAAU;aAAf;IACG,oBACC,oBAAC,QAAD;KACE,SAAS;KACT,SAAQ;KACR,WAAU;KACV,OAAM;KACN,MAAM;KACN,OAAO,uBAAuB,MAAM;eAEnC,uBAAuB;IAClB;IAGV,qBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,oBAAC,aAAD;OACE,gBAAgB;OAChB,MAAM;QACJ,GAAI,KAAK,SAAS,QAAQ,IACtB,CACE,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,SAAS;SACrC,MAAK;mBACN;QAEO,GAPF,SAOE,CACV,IACA,CAAC;QACL,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,WAAW;SACvC,MAAK;mBACN;QAEO,GAPF,WAOE;QACR,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,SAAS;SACrC,MAAK;mBACN;QAEO,GAPF,SAOE;QACR,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,MAAM;SAClC,MAAK;mBACN;QAEO,GAPF,MAOE;OACV;OACA;OACA,OAAM;MACP,IACD,oBAAC,OAAD;OAAK,WAAU;iBACZ;MACE,EACF;SAEL,oBAAC,OAAD;MAAK,WAAU;gBACb,qBAAC,OAAD;OAAK,WAAU;iBAAf;QACG,KAAK,SAAS,QAAQ,KAAK,cAAc,aACxC,oBAAC,uBAAD;SACc;SACN;SACM;QACb;QAEF,cAAc,eACb,oBAAC,iBAAD,EAA6B,WAAa;QAE3C,cAAc,aACb,oBAAC,eAAD;SACc;SACA;QACb;QAEF,cAAc,UACb,oBAAC,YAAD;SAAwB;SAAwB;QAAa;OAE5D;;KACF,EACF;;IAEL,oBAAC,OAAD;KAAK,WAAU;eACb,qBAAC,WAAD;MACE,OAAM;MACN,aAAY;MACZ,SAAQ;MACR,WAAU;gBAJZ,CAME,oBAAC,iBAAD;OACE,MAAM,WAAW;OACjB,WAAU;OACV,MAAM;iBAEL,WAAW;MACG,IACjB,oBAAC,UAAD;OACc;OACN;OACN,gBAAgB;QACd,kBAAkB,IAAI;QACtB,WAAW;OACb;OACQ;MACT,EACQ;;IACR;GACF;;CACwB;AAEnC"}
1
+ {"version":3,"file":"DictionaryFieldEditor.mjs","names":[],"sources":["../../../../src/components/DictionaryFieldEditor/DictionaryFieldEditor.tsx"],"sourcesContent":["'use client';\n\nimport { Container } from '@components/Container';\nimport { CopyToClipboard } from '@components/CopyToClipboard';\nimport {\n useConfiguration,\n useDictionariesRecordActions,\n useFocusUnmergedDictionary,\n} from '@intlayer/editor-react';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { ArrowLeft } from 'lucide-react';\nimport { type FC, type ReactNode, useEffect, useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button } from '../Button';\nimport { LocaleSwitcherContentProvider } from '../LocaleSwitcherContentDropDown';\nimport { TabSelector } from '../TabSelector';\nimport { ContentEditor } from './ContentEditor';\nimport { DictionaryDetailsForm } from './DictionaryDetails/DictionaryDetailsForm';\nimport { JSONEditor } from './JSONEditor';\nimport { SaveForm } from './SaveForm/SaveForm';\nimport { StructureEditor } from './StructureEditor';\n\ntype DictionaryFieldEditorProps = {\n dictionary: Dictionary;\n onClickDictionaryList?: () => void;\n onDelete?: () => void;\n onSave?: () => void;\n isDarkMode?: boolean;\n mode: ('local' | 'remote')[];\n showReturnButton?: boolean;\n rightContent?: ReactNode;\n};\n\nexport const DictionaryFieldEditor: FC<DictionaryFieldEditorProps> = ({\n dictionary,\n onClickDictionaryList,\n isDarkMode,\n mode,\n onDelete,\n onSave,\n showReturnButton = true,\n rightContent,\n}) => {\n const config = useConfiguration();\n const { returnToDictionaryList } = useIntlayer('dictionary-field-editor');\n const { focusedContent, setFocusedContent } = useFocusUnmergedDictionary();\n const { setLocaleDictionary } = useDictionariesRecordActions();\n const [activeTab, setActiveTab] = useState<string>('content');\n\n useEffect(() => {\n setFocusedContent({\n ...(focusedContent ?? {}),\n dictionaryKey: dictionary.key,\n dictionaryLocalId: dictionary.localId,\n });\n setLocaleDictionary(dictionary);\n }, []);\n\n return (\n <LocaleSwitcherContentProvider\n availableLocales={config?.internationalization?.locales ?? []}\n >\n <div className=\"relative flex h-full min-h-0 w-full flex-1 flex-col md:overflow-hidden\">\n {showReturnButton && (\n <Button\n onClick={onClickDictionaryList}\n variant=\"hoverable\"\n className=\"z-10 mr-auto mb-6 ml-5 shrink-0\"\n color=\"text\"\n Icon={ArrowLeft}\n label={returnToDictionaryList.label.value}\n >\n {returnToDictionaryList.text}\n </Button>\n )}\n\n <div className=\"mb-22 flex min-h-0 flex-1 flex-col overflow-hidden\">\n {/* Tab headers */}\n <div className=\"sticky top-0 z-10 flex shrink-0 gap-3 rounded-xl bg-background/20 p-3 pb-4\">\n <TabSelector\n selectedChoice={activeTab}\n tabs={[\n ...(mode.includes('remote')\n ? [\n <button\n key=\"details\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'details'}\n onClick={() => setActiveTab('details')}\n type=\"button\"\n >\n Details\n </button>,\n ]\n : []),\n <button\n key=\"structure\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'structure'}\n onClick={() => setActiveTab('structure')}\n type=\"button\"\n >\n Structure\n </button>,\n <button\n key=\"content\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'content'}\n onClick={() => setActiveTab('content')}\n type=\"button\"\n >\n Content\n </button>,\n <button\n key=\"json\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'json'}\n onClick={() => setActiveTab('json')}\n type=\"button\"\n >\n JSON\n </button>,\n ]}\n hoverable\n color=\"text\"\n />\n <div className=\"flex w-full items-center justify-end\">\n {rightContent}\n </div>\n </div>\n {/* Tab content — only active panel is mounted */}\n <div className=\"min-h-0 flex-1 overflow-y-auto p-6\">\n <div className=\"flex w-full min-w-0 flex-col items-stretch gap-6\">\n {mode.includes('remote') && activeTab === 'details' && (\n <DictionaryDetailsForm dictionary={dictionary} mode={mode} />\n )}\n {activeTab === 'structure' && (\n <StructureEditor dictionary={dictionary} />\n )}\n {activeTab === 'content' && (\n <ContentEditor dictionary={dictionary} />\n )}\n {activeTab === 'json' && (\n <JSONEditor dictionary={dictionary} isDarkMode={isDarkMode} />\n )}\n </div>\n </div>\n </div>\n\n <div className=\"absolute bottom-3 z-20 w-full p-2\">\n <Container\n color=\"card\"\n roundedSize=\"2xl\"\n padding=\"sm\"\n className=\"w-full shrink-0 flex-row flex-wrap items-center justify-end gap-10 bg-background/20 md:bottom-0\"\n >\n <CopyToClipboard\n text={dictionary.id!}\n className=\"text-nowrap text-neutral text-sm\"\n size={9}\n >\n {dictionary.id}\n </CopyToClipboard>\n <SaveForm\n dictionary={dictionary}\n mode={mode}\n onDelete={() => {\n setFocusedContent(null);\n onDelete?.();\n }}\n onSave={onSave}\n />\n </Container>\n </div>\n </div>\n </LocaleSwitcherContentProvider>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiCA,MAAa,yBAAyD,EACpE,YACA,uBACA,YACA,MACA,UACA,QACA,mBAAmB,MACnB,mBACI;CACJ,MAAM,SAAS,iBAAiB;CAChC,MAAM,EAAE,2BAA2B,YAAY,yBAAyB;CACxE,MAAM,EAAE,gBAAgB,sBAAsB,2BAA2B;CACzE,MAAM,EAAE,wBAAwB,6BAA6B;CAC7D,MAAM,CAAC,WAAW,gBAAgB,SAAiB,SAAS;CAE5D,gBAAgB;EACd,kBAAkB;GAChB,GAAI,kBAAkB,CAAC;GACvB,eAAe,WAAW;GAC1B,mBAAmB,WAAW;EAChC,CAAC;EACD,oBAAoB,UAAU;CAChC,GAAG,CAAC,CAAC;CAEL,OACE,oBAAC,+BAAD;EACE,kBAAkB,QAAQ,sBAAsB,WAAW,CAAC;YAE5D,qBAAC,OAAD;GAAK,WAAU;aAAf;IACG,oBACC,oBAAC,QAAD;KACE,SAAS;KACT,SAAQ;KACR,WAAU;KACV,OAAM;KACN,MAAM;KACN,OAAO,uBAAuB,MAAM;eAEnC,uBAAuB;IAClB;IAGV,qBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,oBAAC,aAAD;OACE,gBAAgB;OAChB,MAAM;QACJ,GAAI,KAAK,SAAS,QAAQ,IACtB,CACE,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,SAAS;SACrC,MAAK;mBACN;QAEO,GAPF,SAOE,CACV,IACA,CAAC;QACL,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,WAAW;SACvC,MAAK;mBACN;QAEO,GAPF,WAOE;QACR,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,SAAS;SACrC,MAAK;mBACN;QAEO,GAPF,SAOE;QACR,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,MAAM;SAClC,MAAK;mBACN;QAEO,GAPF,MAOE;OACV;OACA;OACA,OAAM;MACP,IACD,oBAAC,OAAD;OAAK,WAAU;iBACZ;MACE,EACF;SAEL,oBAAC,OAAD;MAAK,WAAU;gBACb,qBAAC,OAAD;OAAK,WAAU;iBAAf;QACG,KAAK,SAAS,QAAQ,KAAK,cAAc,aACxC,oBAAC,uBAAD;SAAmC;SAAkB;QAAO;QAE7D,cAAc,eACb,oBAAC,iBAAD,EAA6B,WAAa;QAE3C,cAAc,aACb,oBAAC,eAAD,EAA2B,WAAa;QAEzC,cAAc,UACb,oBAAC,YAAD;SAAwB;SAAwB;QAAa;OAE5D;;KACF,EACF;;IAEL,oBAAC,OAAD;KAAK,WAAU;eACb,qBAAC,WAAD;MACE,OAAM;MACN,aAAY;MACZ,SAAQ;MACR,WAAU;gBAJZ,CAME,oBAAC,iBAAD;OACE,MAAM,WAAW;OACjB,WAAU;OACV,MAAM;iBAEL,WAAW;MACG,IACjB,oBAAC,UAAD;OACc;OACN;OACN,gBAAgB;QACd,kBAAkB,IAAI;QACtB,WAAW;OACb;OACQ;MACT,EACQ;;IACR;GACF;;CACwB;AAEnC"}
@@ -5,8 +5,8 @@ import { getIsEditableSection } from "../getIsEditableSection.mjs";
5
5
  import { useState } from "react";
6
6
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { ChevronRight, Plus } from "lucide-react";
8
- import { useEditedContentActions, useEditorLocale, useFocusUnmergedDictionary } from "@intlayer/editor-react";
9
8
  import { useIntlayer } from "react-intlayer";
9
+ import { useEditedContentActions, useEditorLocale, useFocusUnmergedDictionary } from "@intlayer/editor-react";
10
10
  import { getContentNodeByKeyPath, getEmptyNode, getNodeType } from "@intlayer/core/dictionaryManipulator";
11
11
  import * as NodeTypes from "@intlayer/types/nodeType";
12
12
  import { isSameKeyPath } from "@intlayer/core/utils";
@@ -1,17 +1,17 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../../utils/cn.mjs";
4
+ import { Button } from "../../Button/Button.mjs";
4
5
  import { useSession } from "../../../api/useAuth/useSession.mjs";
5
6
  import { useAuth } from "../../../api/useAuth/useAuth.mjs";
6
7
  import { useDeleteDictionary, usePushDictionaries } from "../../../api/hooks/dictionary.mjs";
7
8
  import { useWriteDictionary } from "../../../api/hooks/editor.mjs";
8
- import { Button } from "../../Button/Button.mjs";
9
9
  import { Modal } from "../../Modal/Modal.mjs";
10
10
  import { useState } from "react";
11
11
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { ArrowUpFromLine, Download, RotateCcw, Save, Trash } from "lucide-react";
13
- import { useDictionariesRecordActions, useEditedContent } from "@intlayer/editor-react";
14
13
  import { useIntlayer } from "react-intlayer";
14
+ import { useDictionariesRecordActions, useEditedContent } from "@intlayer/editor-react";
15
15
 
16
16
  //#region src/components/DictionaryFieldEditor/SaveForm/SaveForm.tsx
17
17
  const SaveForm = ({ dictionary, mode, className, onDelete, onSave, ...props }) => {
@@ -6,8 +6,8 @@ import { EditableFieldInput } from "../../EditableField/EditableFieldInput.mjs";
6
6
  import { NodeTypeSelector } from "../NodeTypeSelector.mjs";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  import { Plus, Trash } from "lucide-react";
9
- import { useConfiguration, useEditedContentActions, useFocusUnmergedDictionary } from "@intlayer/editor-react";
10
9
  import { useIntlayer } from "react-intlayer";
10
+ import { useConfiguration, useEditedContentActions, useFocusUnmergedDictionary } from "@intlayer/editor-react";
11
11
  import { getDefaultNode, getNodeChildren, getNodeType } from "@intlayer/core/dictionaryManipulator";
12
12
  import * as NodeTypes from "@intlayer/types/nodeType";
13
13
  import { isSameKeyPath } from "@intlayer/core/utils";
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { cn } from "../../utils/cn.mjs";
4
4
  import { MaxHeightSmoother } from "../MaxHeightSmoother/index.mjs";
5
- import { useLayoutEffect, useRef, useState } from "react";
5
+ import { useCallback, useEffect, useRef, useState } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { useIntlayer } from "react-intlayer";
8
8
 
@@ -63,21 +63,28 @@ const DEFAULT_MIN_HEIGHT = 700;
63
63
  const ExpandCollapse = ({ isRollable = true, minHeight = DEFAULT_MIN_HEIGHT, children, className }) => {
64
64
  const [codeContainerHeight, setCodeContainerHeight] = useState(0);
65
65
  const [isCollapsed, setIsCollapsed] = useState(true);
66
- const codeContainerRef = useRef(null);
66
+ const resizeObserverRef = useRef(null);
67
67
  const { expandCollapseContent } = useIntlayer("expand-collapse");
68
68
  const isTooBig = codeContainerHeight > minHeight;
69
- useLayoutEffect(() => {
70
- const measure = () => {
71
- if (codeContainerRef.current) setCodeContainerHeight(codeContainerRef.current.clientHeight);
72
- };
73
- measure();
74
- window.addEventListener("resize", measure);
75
- return () => window.removeEventListener("resize", measure);
76
- }, [children]);
69
+ const observeContainer = useCallback((container) => {
70
+ resizeObserverRef.current?.disconnect();
71
+ resizeObserverRef.current = null;
72
+ if (!container) return;
73
+ if (typeof ResizeObserver === "undefined") {
74
+ setCodeContainerHeight(container.clientHeight);
75
+ return;
76
+ }
77
+ const observer = new ResizeObserver(([entry]) => {
78
+ if (entry) setCodeContainerHeight(entry.target.clientHeight);
79
+ });
80
+ observer.observe(container);
81
+ resizeObserverRef.current = observer;
82
+ }, []);
83
+ useEffect(() => () => resizeObserverRef.current?.disconnect(), []);
77
84
  if (!isRollable) return children;
78
85
  if (!isTooBig) return /* @__PURE__ */ jsx("div", {
79
86
  className: cn("grid w-full", className),
80
- ref: codeContainerRef,
87
+ ref: observeContainer,
81
88
  children
82
89
  });
83
90
  return /* @__PURE__ */ jsxs(MaxHeightSmoother, {
@@ -86,7 +93,7 @@ const ExpandCollapse = ({ isRollable = true, minHeight = DEFAULT_MIN_HEIGHT, chi
86
93
  className: "w-full overflow-x-auto overflow-y-hidden",
87
94
  children: [/* @__PURE__ */ jsx("div", {
88
95
  className: cn("grid w-full", className),
89
- ref: codeContainerRef,
96
+ ref: observeContainer,
90
97
  children
91
98
  }), /* @__PURE__ */ jsx("button", {
92
99
  className: cn("absolute right-0 bottom-0 flex w-full cursor-pointer items-center justify-center rounded-t-2xl bg-linear-to-t from-card/80 to-transparent px-3 py-0.5 text-md text-neutral-700 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur transition-all duration-300 hover:py-1 dark:text-neutral-400", isCollapsed ? "w-full" : "w-32"),
@@ -1 +1 @@
1
- {"version":3,"file":"ExpandCollapse.mjs","names":[],"sources":["../../../../src/components/ExpandCollapse/ExpandCollapse.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport {\n type FC,\n type ReactNode,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\n/**\n * Props for the ExpandCollapse component\n */\nexport type ExpandCollapseProps = {\n /** Whether the component should provide expand/collapse functionality. If false, renders children directly */\n isRollable?: boolean;\n /** Minimum height in pixels before showing the expand/collapse toggle */\n minHeight?: number;\n /** Content that may overflow and trigger the expand/collapse behavior */\n children: ReactNode;\n /** Additional CSS classes for styling customization */\n className?: string;\n};\n\n/** Default minimum height threshold for triggering expand/collapse behavior */\nconst DEFAULT_MIN_HEIGHT = 700;\n\n/**\n * ExpandCollapse Component\n *\n * A smart content container that automatically provides expand/collapse functionality\n * when content exceeds a specified height threshold. Features smooth animations,\n * internationalized toggle text, and intelligent height detection.\n *\n * @example\n * ```tsx\n * <ExpandCollapse minHeight={300}>\n * <div>Very long content that will be collapsed...</div>\n * </ExpandCollapse>\n * ```\n *\n * ## Key Features\n * - **Smart Detection**: Automatically detects when content exceeds height threshold\n * - **Smooth Animations**: Uses CSS transitions for smooth expand/collapse effects\n * - **Internationalization**: Toggle text supports multiple languages via Intlayer\n * - **Customizable Height**: Configurable minimum height threshold\n * - **Performance Optimized**: Only applies collapse behavior when necessary\n * - **Accessibility**: Proper ARIA attributes and keyboard support\n *\n * ## Behavior Logic\n * 1. **Measurement Phase**: Measures actual content height on mount\n * 2. **Comparison**: Compares content height against minHeight threshold\n * 3. **Conditional Rendering**:\n * - If content ≤ minHeight: Renders normally without collapse functionality\n * - If content > minHeight: Enables expand/collapse with toggle button\n * 4. **State Management**: Manages collapsed/expanded state with smooth transitions\n *\n * ## When to Use\n * - Long-form content (articles, documentation, code blocks)\n * - Lists or tables that may grow beyond comfortable viewing height\n * - User-generated content with unpredictable length\n * - FAQ sections or expandable content cards\n * - Code examples or JSON data display\n *\n * ## Accessibility Features\n * - **Keyboard Navigation**: Toggle button is focusable and keyboard accessible\n * - **Screen Reader Support**: Proper ARIA labels and state announcements\n * - **Visual Indicators**: Clear visual cues for collapsed/expanded states\n * - **Smooth Animations**: Respects user preferences for reduced motion\n *\n * ## Internationalization\n * - Supports multiple languages through Intlayer integration\n * - Toggle text automatically adapts to current locale\n * - Includes translations for \"Show all\" and \"Show less\" states\n *\n * @param props - ExpandCollapseProps\n * @returns React functional component\n */\nexport const ExpandCollapse: FC<ExpandCollapseProps> = ({\n isRollable = true,\n minHeight = DEFAULT_MIN_HEIGHT,\n children,\n className,\n}) => {\n const [codeContainerHeight, setCodeContainerHeight] = useState(0);\n const [isCollapsed, setIsCollapsed] = useState(true);\n const codeContainerRef = useRef<HTMLDivElement>(null);\n const { expandCollapseContent } = useIntlayer('expand-collapse');\n\n const isTooBig = codeContainerHeight > minHeight;\n\n useLayoutEffect(() => {\n const measure = () => {\n if (codeContainerRef.current) {\n setCodeContainerHeight(codeContainerRef.current.clientHeight);\n }\n };\n\n measure();\n\n window.addEventListener('resize', measure);\n return () => window.removeEventListener('resize', measure);\n }, [children]);\n\n if (!isRollable) {\n return children;\n }\n\n if (!isTooBig) {\n return (\n <div className={cn('grid w-full', className)} ref={codeContainerRef}>\n {children}\n </div>\n );\n }\n\n return (\n <MaxHeightSmoother\n isHidden={isCollapsed}\n minHeight={minHeight}\n className=\"w-full overflow-x-auto overflow-y-hidden\"\n >\n <div className={cn('grid w-full', className)} ref={codeContainerRef}>\n {children}\n </div>\n <button\n className={cn(\n 'absolute right-0 bottom-0 flex w-full cursor-pointer items-center justify-center rounded-t-2xl bg-linear-to-t from-card/80 to-transparent px-3 py-0.5 text-md text-neutral-700 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur transition-all duration-300 hover:py-1 dark:text-neutral-400',\n isCollapsed ? 'w-full' : 'w-32'\n )}\n type=\"button\"\n onClick={() => setIsCollapsed((prev) => !prev)}\n >\n {expandCollapseContent(isCollapsed)}\n </button>\n </MaxHeightSmoother>\n );\n};\n"],"mappings":";;;;;;;;;;AA4BA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqD3B,MAAa,kBAA2C,EACtD,aAAa,MACb,YAAY,oBACZ,UACA,gBACI;CACJ,MAAM,CAAC,qBAAqB,0BAA0B,SAAS,CAAC;CAChE,MAAM,CAAC,aAAa,kBAAkB,SAAS,IAAI;CACnD,MAAM,mBAAmB,OAAuB,IAAI;CACpD,MAAM,EAAE,0BAA0B,YAAY,iBAAiB;CAE/D,MAAM,WAAW,sBAAsB;CAEvC,sBAAsB;EACpB,MAAM,gBAAgB;GACpB,IAAI,iBAAiB,SACnB,uBAAuB,iBAAiB,QAAQ,YAAY;EAEhE;EAEA,QAAQ;EAER,OAAO,iBAAiB,UAAU,OAAO;EACzC,aAAa,OAAO,oBAAoB,UAAU,OAAO;CAC3D,GAAG,CAAC,QAAQ,CAAC;CAEb,IAAI,CAAC,YACH,OAAO;CAGT,IAAI,CAAC,UACH,OACE,oBAAC,OAAD;EAAK,WAAW,GAAG,eAAe,SAAS;EAAG,KAAK;EAChD;CACE;CAIT,OACE,qBAAC,mBAAD;EACE,UAAU;EACC;EACX,WAAU;YAHZ,CAKE,oBAAC,OAAD;GAAK,WAAW,GAAG,eAAe,SAAS;GAAG,KAAK;GAChD;EACE,IACL,oBAAC,UAAD;GACE,WAAW,GACT,qSACA,cAAc,WAAW,MAC3B;GACA,MAAK;GACL,eAAe,gBAAgB,SAAS,CAAC,IAAI;aAE5C,sBAAsB,WAAW;EAC5B,EACS;;AAEvB"}
1
+ {"version":3,"file":"ExpandCollapse.mjs","names":[],"sources":["../../../../src/components/ExpandCollapse/ExpandCollapse.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport {\n type FC,\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\n/**\n * Props for the ExpandCollapse component\n */\nexport type ExpandCollapseProps = {\n /** Whether the component should provide expand/collapse functionality. If false, renders children directly */\n isRollable?: boolean;\n /** Minimum height in pixels before showing the expand/collapse toggle */\n minHeight?: number;\n /** Content that may overflow and trigger the expand/collapse behavior */\n children: ReactNode;\n /** Additional CSS classes for styling customization */\n className?: string;\n};\n\n/** Default minimum height threshold for triggering expand/collapse behavior */\nconst DEFAULT_MIN_HEIGHT = 700;\n\n/**\n * ExpandCollapse Component\n *\n * A smart content container that automatically provides expand/collapse functionality\n * when content exceeds a specified height threshold. Features smooth animations,\n * internationalized toggle text, and intelligent height detection.\n *\n * @example\n * ```tsx\n * <ExpandCollapse minHeight={300}>\n * <div>Very long content that will be collapsed...</div>\n * </ExpandCollapse>\n * ```\n *\n * ## Key Features\n * - **Smart Detection**: Automatically detects when content exceeds height threshold\n * - **Smooth Animations**: Uses CSS transitions for smooth expand/collapse effects\n * - **Internationalization**: Toggle text supports multiple languages via Intlayer\n * - **Customizable Height**: Configurable minimum height threshold\n * - **Performance Optimized**: Only applies collapse behavior when necessary\n * - **Accessibility**: Proper ARIA attributes and keyboard support\n *\n * ## Behavior Logic\n * 1. **Measurement Phase**: Measures actual content height on mount\n * 2. **Comparison**: Compares content height against minHeight threshold\n * 3. **Conditional Rendering**:\n * - If content ≤ minHeight: Renders normally without collapse functionality\n * - If content > minHeight: Enables expand/collapse with toggle button\n * 4. **State Management**: Manages collapsed/expanded state with smooth transitions\n *\n * ## When to Use\n * - Long-form content (articles, documentation, code blocks)\n * - Lists or tables that may grow beyond comfortable viewing height\n * - User-generated content with unpredictable length\n * - FAQ sections or expandable content cards\n * - Code examples or JSON data display\n *\n * ## Accessibility Features\n * - **Keyboard Navigation**: Toggle button is focusable and keyboard accessible\n * - **Screen Reader Support**: Proper ARIA labels and state announcements\n * - **Visual Indicators**: Clear visual cues for collapsed/expanded states\n * - **Smooth Animations**: Respects user preferences for reduced motion\n *\n * ## Internationalization\n * - Supports multiple languages through Intlayer integration\n * - Toggle text automatically adapts to current locale\n * - Includes translations for \"Show all\" and \"Show less\" states\n *\n * @param props - ExpandCollapseProps\n * @returns React functional component\n */\nexport const ExpandCollapse: FC<ExpandCollapseProps> = ({\n isRollable = true,\n minHeight = DEFAULT_MIN_HEIGHT,\n children,\n className,\n}) => {\n const [codeContainerHeight, setCodeContainerHeight] = useState(0);\n const [isCollapsed, setIsCollapsed] = useState(true);\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const { expandCollapseContent } = useIntlayer('expand-collapse');\n\n const isTooBig = codeContainerHeight > minHeight;\n\n /*\n * A `ResizeObserver` is handed the size the browser has already computed,\n * where reading `clientHeight` from an effect forces it to lay the page out\n * again on the spot. That distinction matters here because a documentation\n * page mounts one of these per code block: dozens of synchronous reflows over\n * a very large document. The observer also reports later size changes, which\n * is what the window `resize` listener used to cover.\n *\n * It attaches through a callback ref rather than an effect because crossing\n * the `minHeight` threshold re-parents the container into `MaxHeightSmoother`\n * — a different element, which an effect bound to mount would never observe.\n */\n const observeContainer = useCallback((container: HTMLDivElement | null) => {\n resizeObserverRef.current?.disconnect();\n resizeObserverRef.current = null;\n\n if (!container) return;\n\n if (typeof ResizeObserver === 'undefined') {\n setCodeContainerHeight(container.clientHeight);\n return;\n }\n\n const observer = new ResizeObserver(([entry]) => {\n // Read inside the callback, where the layout is already up to date.\n if (entry) setCodeContainerHeight(entry.target.clientHeight);\n });\n\n observer.observe(container);\n resizeObserverRef.current = observer;\n }, []);\n\n useEffect(() => () => resizeObserverRef.current?.disconnect(), []);\n\n if (!isRollable) {\n return children;\n }\n\n if (!isTooBig) {\n return (\n <div className={cn('grid w-full', className)} ref={observeContainer}>\n {children}\n </div>\n );\n }\n\n return (\n <MaxHeightSmoother\n isHidden={isCollapsed}\n minHeight={minHeight}\n className=\"w-full overflow-x-auto overflow-y-hidden\"\n >\n <div className={cn('grid w-full', className)} ref={observeContainer}>\n {children}\n </div>\n <button\n className={cn(\n 'absolute right-0 bottom-0 flex w-full cursor-pointer items-center justify-center rounded-t-2xl bg-linear-to-t from-card/80 to-transparent px-3 py-0.5 text-md text-neutral-700 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur transition-all duration-300 hover:py-1 dark:text-neutral-400',\n isCollapsed ? 'w-full' : 'w-32'\n )}\n type=\"button\"\n onClick={() => setIsCollapsed((prev) => !prev)}\n >\n {expandCollapseContent(isCollapsed)}\n </button>\n </MaxHeightSmoother>\n );\n};\n"],"mappings":";;;;;;;;;;AA6BA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqD3B,MAAa,kBAA2C,EACtD,aAAa,MACb,YAAY,oBACZ,UACA,gBACI;CACJ,MAAM,CAAC,qBAAqB,0BAA0B,SAAS,CAAC;CAChE,MAAM,CAAC,aAAa,kBAAkB,SAAS,IAAI;CACnD,MAAM,oBAAoB,OAA8B,IAAI;CAC5D,MAAM,EAAE,0BAA0B,YAAY,iBAAiB;CAE/D,MAAM,WAAW,sBAAsB;CAcvC,MAAM,mBAAmB,aAAa,cAAqC;EACzE,kBAAkB,SAAS,WAAW;EACtC,kBAAkB,UAAU;EAE5B,IAAI,CAAC,WAAW;EAEhB,IAAI,OAAO,mBAAmB,aAAa;GACzC,uBAAuB,UAAU,YAAY;GAC7C;EACF;EAEA,MAAM,WAAW,IAAI,gBAAgB,CAAC,WAAW;GAE/C,IAAI,OAAO,uBAAuB,MAAM,OAAO,YAAY;EAC7D,CAAC;EAED,SAAS,QAAQ,SAAS;EAC1B,kBAAkB,UAAU;CAC9B,GAAG,CAAC,CAAC;CAEL,sBAAsB,kBAAkB,SAAS,WAAW,GAAG,CAAC,CAAC;CAEjE,IAAI,CAAC,YACH,OAAO;CAGT,IAAI,CAAC,UACH,OACE,oBAAC,OAAD;EAAK,WAAW,GAAG,eAAe,SAAS;EAAG,KAAK;EAChD;CACE;CAIT,OACE,qBAAC,mBAAD;EACE,UAAU;EACC;EACX,WAAU;YAHZ,CAKE,oBAAC,OAAD;GAAK,WAAW,GAAG,eAAe,SAAS;GAAG,KAAK;GAChD;EACE,IACL,oBAAC,UAAD;GACE,WAAW,GACT,qSACA,cAAc,WAAW,MAC3B;GACA,MAAK;GACL,eAAe,gBAAgB,SAAS,CAAC,IAAI;aAE5C,sBAAsB,WAAW;EAC5B,EACS;;AAEvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"FormBase.mjs","names":["useFormReactHookForm"],"sources":["../../../../src/components/Form/FormBase.tsx"],"sourcesContent":["'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { cn } from '@utils/cn';\nimport type { HTMLAttributes, KeyboardEvent } from 'react';\nimport {\n FormProvider,\n type FormProviderProps,\n type UseFormProps,\n useForm as useFormReactHookForm,\n useFormState,\n} from 'react-hook-form';\nimport type { ZodObject, z } from 'zod/v4';\n\ntype FormProps<T extends ZodObject> = HTMLAttributes<HTMLFormElement> &\n FormProviderProps<z.infer<T>> & {\n schema?: T;\n onSubmit?: (data: z.infer<T>) => void | Promise<void>;\n onSubmitSuccess?: (data: z.infer<T>) => void | Promise<void>;\n onSubmitError?: (error: Error) => void | Promise<void>;\n autoComplete?: boolean;\n };\n\nconst awaitFunction = async (fn: any) => {\n // Check if result is a Promise (Thenable)\n\n if (fn && typeof fn.then === 'function') {\n // It's a Promise, so wait for it to resolve\n return await fn;\n }\n // If not a Promise, it will just execute without awaiting\n return fn;\n};\n\nexport const Form = <T extends ZodObject>({\n schema,\n onSubmit: onSubmitProp,\n onSubmitSuccess: onSubmitSuccessProp,\n onSubmitError: onSubmitErrorProp,\n className,\n children,\n autoComplete,\n method,\n ...props\n}: FormProps<T> & { method?: string }) => {\n const onSubmit = async (values: z.infer<T>) => {\n const parsedValues = schema?.safeParse(values) ?? {\n success: true,\n data: undefined,\n };\n\n // onSubmitProp?.(values);\n await awaitFunction(onSubmitProp?.(values));\n\n if (parsedValues.success) {\n await awaitFunction(onSubmitSuccessProp?.(parsedValues.data!));\n } else {\n await awaitFunction(\n onSubmitErrorProp?.(\n new Error(\n parsedValues.error.issues.map((error) => error.message).join(', ')\n )\n )\n );\n }\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLFormElement>) => {\n if (e.key !== 'Enter') return;\n const target = e.target as HTMLElement;\n if (target.tagName !== 'INPUT') return;\n e.preventDefault();\n const formEl = e.currentTarget;\n const focusable = Array.from(\n formEl.querySelectorAll<HTMLElement>(\n 'input:not([disabled]):not([type=\"hidden\"]), textarea:not([disabled])'\n )\n );\n const idx = focusable.indexOf(target);\n if (idx >= 0 && idx < focusable.length - 1) {\n focusable[idx + 1]?.focus();\n }\n };\n\n return (\n <FormProvider {...props}>\n <form\n className={cn('flex flex-col gap-y-6', className)}\n onSubmit={props.handleSubmit(onSubmit)}\n onKeyDown={handleKeyDown}\n autoComplete={autoComplete ? 'on' : 'off'}\n noValidate\n method={method}\n >\n {children}\n </form>\n </FormProvider>\n );\n};\n\nexport const useForm = <T extends ZodObject>(\n schema: T,\n props?: UseFormProps<z.infer<T>>\n) => {\n const form = useFormReactHookForm<z.infer<T>>({\n resolver: zodResolver(schema as any),\n ...props,\n });\n\n const { isSubmitting, isSubmitted, isLoading, isValid } = useFormState({\n control: form.control,\n });\n\n return {\n form,\n isSubmitting,\n isSubmitted,\n isLoading,\n isValid,\n };\n};\n"],"mappings":";;;;;;;;AAuBA,MAAM,gBAAgB,OAAO,OAAY;CAGvC,IAAI,MAAM,OAAO,GAAG,SAAS,YAE3B,OAAO,MAAM;CAGf,OAAO;AACT;AAEA,MAAa,QAA6B,EACxC,QACA,UAAU,cACV,iBAAiB,qBACjB,eAAe,mBACf,WACA,UACA,cACA,QACA,GAAG,YACqC;CACxC,MAAM,WAAW,OAAO,WAAuB;EAC7C,MAAM,eAAe,QAAQ,UAAU,MAAM,KAAK;GAChD,SAAS;GACT,MAAM;EACR;EAGA,MAAM,cAAc,eAAe,MAAM,CAAC;EAE1C,IAAI,aAAa,SACf,MAAM,cAAc,sBAAsB,aAAa,IAAK,CAAC;OAE7D,MAAM,cACJ,oBACE,IAAI,MACF,aAAa,MAAM,OAAO,KAAK,UAAU,MAAM,OAAO,CAAC,CAAC,KAAK,IAAI,CACnE,CACF,CACF;CAEJ;CAEA,MAAM,iBAAiB,MAAsC;EAC3D,IAAI,EAAE,QAAQ,SAAS;EACvB,MAAM,SAAS,EAAE;EACjB,IAAI,OAAO,YAAY,SAAS;EAChC,EAAE,eAAe;EACjB,MAAM,SAAS,EAAE;EACjB,MAAM,YAAY,MAAM,KACtB,OAAO,iBACL,wEACF,CACF;EACA,MAAM,MAAM,UAAU,QAAQ,MAAM;EACpC,IAAI,OAAO,KAAK,MAAM,UAAU,SAAS,GACvC,UAAU,MAAM,EAAE,EAAE,MAAM;CAE9B;CAEA,OACE,oBAAC,cAAD;EAAc,GAAI;YAChB,oBAAC,QAAD;GACE,WAAW,GAAG,yBAAyB,SAAS;GAChD,UAAU,MAAM,aAAa,QAAQ;GACrC,WAAW;GACX,cAAc,eAAe,OAAO;GACpC;GACQ;GAEP;EACG;CACM;AAElB;AAEA,MAAa,WACX,QACA,UACG;CACH,MAAM,OAAOA,UAAiC;EAC5C,UAAU,YAAY,MAAa;EACnC,GAAG;CACL,CAAC;CAED,MAAM,EAAE,cAAc,aAAa,WAAW,YAAY,aAAa,EACrE,SAAS,KAAK,QAChB,CAAC;CAED,OAAO;EACL;EACA;EACA;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"FormBase.mjs","names":["useFormReactHookForm"],"sources":["../../../../src/components/Form/FormBase.tsx"],"sourcesContent":["'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { cn } from '@utils/cn';\nimport type { HTMLAttributes, KeyboardEvent } from 'react';\nimport {\n FormProvider,\n type FormProviderProps,\n type UseFormProps,\n useForm as useFormReactHookForm,\n useFormState,\n} from 'react-hook-form';\nimport type { ZodObject, z } from 'zod';\n\ntype FormProps<T extends ZodObject> = HTMLAttributes<HTMLFormElement> &\n FormProviderProps<z.infer<T>> & {\n schema?: T;\n onSubmit?: (data: z.infer<T>) => void | Promise<void>;\n onSubmitSuccess?: (data: z.infer<T>) => void | Promise<void>;\n onSubmitError?: (error: Error) => void | Promise<void>;\n autoComplete?: boolean;\n };\n\nconst awaitFunction = async (fn: any) => {\n // Check if result is a Promise (Thenable)\n\n if (fn && typeof fn.then === 'function') {\n // It's a Promise, so wait for it to resolve\n return await fn;\n }\n // If not a Promise, it will just execute without awaiting\n return fn;\n};\n\nexport const Form = <T extends ZodObject>({\n schema,\n onSubmit: onSubmitProp,\n onSubmitSuccess: onSubmitSuccessProp,\n onSubmitError: onSubmitErrorProp,\n className,\n children,\n autoComplete,\n method,\n ...props\n}: FormProps<T> & { method?: string }) => {\n const onSubmit = async (values: z.infer<T>) => {\n const parsedValues = schema?.safeParse(values) ?? {\n success: true,\n data: undefined,\n };\n\n // onSubmitProp?.(values);\n await awaitFunction(onSubmitProp?.(values));\n\n if (parsedValues.success) {\n await awaitFunction(onSubmitSuccessProp?.(parsedValues.data!));\n } else {\n await awaitFunction(\n onSubmitErrorProp?.(\n new Error(\n parsedValues.error.issues.map((error) => error.message).join(', ')\n )\n )\n );\n }\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLFormElement>) => {\n if (e.key !== 'Enter') return;\n const target = e.target as HTMLElement;\n if (target.tagName !== 'INPUT') return;\n e.preventDefault();\n const formEl = e.currentTarget;\n const focusable = Array.from(\n formEl.querySelectorAll<HTMLElement>(\n 'input:not([disabled]):not([type=\"hidden\"]), textarea:not([disabled])'\n )\n );\n const idx = focusable.indexOf(target);\n if (idx >= 0 && idx < focusable.length - 1) {\n focusable[idx + 1]?.focus();\n }\n };\n\n return (\n <FormProvider {...props}>\n <form\n className={cn('flex flex-col gap-y-6', className)}\n onSubmit={props.handleSubmit(onSubmit)}\n onKeyDown={handleKeyDown}\n autoComplete={autoComplete ? 'on' : 'off'}\n noValidate\n method={method}\n >\n {children}\n </form>\n </FormProvider>\n );\n};\n\nexport const useForm = <T extends ZodObject>(\n schema: T,\n props?: UseFormProps<z.infer<T>>\n) => {\n const form = useFormReactHookForm<z.infer<T>>({\n resolver: zodResolver(schema as any),\n ...props,\n });\n\n const { isSubmitting, isSubmitted, isLoading, isValid } = useFormState({\n control: form.control,\n });\n\n return {\n form,\n isSubmitting,\n isSubmitted,\n isLoading,\n isValid,\n };\n};\n"],"mappings":";;;;;;;;AAuBA,MAAM,gBAAgB,OAAO,OAAY;CAGvC,IAAI,MAAM,OAAO,GAAG,SAAS,YAE3B,OAAO,MAAM;CAGf,OAAO;AACT;AAEA,MAAa,QAA6B,EACxC,QACA,UAAU,cACV,iBAAiB,qBACjB,eAAe,mBACf,WACA,UACA,cACA,QACA,GAAG,YACqC;CACxC,MAAM,WAAW,OAAO,WAAuB;EAC7C,MAAM,eAAe,QAAQ,UAAU,MAAM,KAAK;GAChD,SAAS;GACT,MAAM;EACR;EAGA,MAAM,cAAc,eAAe,MAAM,CAAC;EAE1C,IAAI,aAAa,SACf,MAAM,cAAc,sBAAsB,aAAa,IAAK,CAAC;OAE7D,MAAM,cACJ,oBACE,IAAI,MACF,aAAa,MAAM,OAAO,KAAK,UAAU,MAAM,OAAO,CAAC,CAAC,KAAK,IAAI,CACnE,CACF,CACF;CAEJ;CAEA,MAAM,iBAAiB,MAAsC;EAC3D,IAAI,EAAE,QAAQ,SAAS;EACvB,MAAM,SAAS,EAAE;EACjB,IAAI,OAAO,YAAY,SAAS;EAChC,EAAE,eAAe;EACjB,MAAM,SAAS,EAAE;EACjB,MAAM,YAAY,MAAM,KACtB,OAAO,iBACL,wEACF,CACF;EACA,MAAM,MAAM,UAAU,QAAQ,MAAM;EACpC,IAAI,OAAO,KAAK,MAAM,UAAU,SAAS,GACvC,UAAU,MAAM,EAAE,EAAE,MAAM;CAE9B;CAEA,OACE,oBAAC,cAAD;EAAc,GAAI;YAChB,oBAAC,QAAD;GACE,WAAW,GAAG,yBAAyB,SAAS;GAChD,UAAU,MAAM,aAAa,QAAQ;GACrC,WAAW;GACX,cAAc,eAAe,OAAO;GACpC;GACQ;GAEP;EACG;CACM;AAElB;AAEA,MAAa,WACX,QACA,UACG;CACH,MAAM,OAAOA,UAAiC;EAC5C,UAAU,YAAY,MAAa;EACnC,GAAG;CACL,CAAC;CAED,MAAM,EAAE,cAAc,aAAa,WAAW,YAAY,aAAa,EACrE,SAAS,KAAK,QAChB,CAAC;CAED,OAAO;EACL;EACA;EACA;EACA;EACA;CACF;AACF"}
@@ -3,7 +3,7 @@
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { FormProvider, useForm } from "react-hook-form";
5
5
  import { zodResolver } from "@hookform/resolvers/zod";
6
- import { z } from "zod/v4";
6
+ import { z } from "zod";
7
7
 
8
8
  //#region src/components/Form/elements/FormElementWrapper.tsx
9
9
  const Schema = z.object({ test: z.string().min(3, "test error") });
@@ -1 +1 @@
1
- {"version":3,"file":"FormElementWrapper.mjs","names":[],"sources":["../../../../../src/components/Form/elements/FormElementWrapper.tsx"],"sourcesContent":["'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport type { FC, HTMLProps, SubmitEvent } from 'react';\nimport { FormProvider, useForm } from 'react-hook-form';\nimport { z } from 'zod/v4';\n\nconst Schema = z.object({\n test: z.string().min(3, 'test error'),\n});\n\ntype FormElementWrapperProps = HTMLProps<HTMLFormElement> & {\n schema?: z.ZodObject;\n values?: z.infer<z.ZodObject>;\n};\n\nexport const FormElementWrapper: FC<FormElementWrapperProps> = (props) => {\n const objectFormSchema = props.schema ?? Schema;\n\n const form = useForm<z.infer<typeof objectFormSchema>>({\n resolver: zodResolver(objectFormSchema as any),\n values: props.values,\n });\n\n const { children, onSubmit, ...rest } = props;\n\n const onSubmitForm = (\n values: z.infer<typeof objectFormSchema>,\n event: SubmitEvent<HTMLFormElement>\n ) => {\n const parsedValues = objectFormSchema.safeParse(values);\n if (parsedValues.success) {\n onSubmit?.(event);\n }\n };\n\n return (\n <FormProvider {...form}>\n <form\n {...rest}\n onSubmit={(e) => {\n e.preventDefault();\n form.handleSubmit((values) => onSubmitForm(values, e));\n }}\n >\n {children}\n </form>\n </FormProvider>\n );\n};\n"],"mappings":";;;;;;;;AAOA,MAAM,SAAS,EAAE,OAAO,EACtB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,GAAG,YAAY,EACtC,CAAC;AAOD,MAAa,sBAAmD,UAAU;CACxE,MAAM,mBAAmB,MAAM,UAAU;CAEzC,MAAM,OAAO,QAA0C;EACrD,UAAU,YAAY,gBAAuB;EAC7C,QAAQ,MAAM;CAChB,CAAC;CAED,MAAM,EAAE,UAAU,UAAU,GAAG,SAAS;CAExC,MAAM,gBACJ,QACA,UACG;EAEH,IADqB,iBAAiB,UAAU,MACjC,CAAC,CAAC,SACf,WAAW,KAAK;CAEpB;CAEA,OACE,oBAAC,cAAD;EAAc,GAAI;YAChB,oBAAC,QAAD;GACE,GAAI;GACJ,WAAW,MAAM;IACf,EAAE,eAAe;IACjB,KAAK,cAAc,WAAW,aAAa,QAAQ,CAAC,CAAC;GACvD;GAEC;EACG;CACM;AAElB"}
1
+ {"version":3,"file":"FormElementWrapper.mjs","names":[],"sources":["../../../../../src/components/Form/elements/FormElementWrapper.tsx"],"sourcesContent":["'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport type { FC, HTMLProps, SubmitEvent } from 'react';\nimport { FormProvider, useForm } from 'react-hook-form';\nimport { z } from 'zod';\n\nconst Schema = z.object({\n test: z.string().min(3, 'test error'),\n});\n\ntype FormElementWrapperProps = HTMLProps<HTMLFormElement> & {\n schema?: z.ZodObject;\n values?: z.infer<z.ZodObject>;\n};\n\nexport const FormElementWrapper: FC<FormElementWrapperProps> = (props) => {\n const objectFormSchema = props.schema ?? Schema;\n\n const form = useForm<z.infer<typeof objectFormSchema>>({\n resolver: zodResolver(objectFormSchema as any),\n values: props.values,\n });\n\n const { children, onSubmit, ...rest } = props;\n\n const onSubmitForm = (\n values: z.infer<typeof objectFormSchema>,\n event: SubmitEvent<HTMLFormElement>\n ) => {\n const parsedValues = objectFormSchema.safeParse(values);\n if (parsedValues.success) {\n onSubmit?.(event);\n }\n };\n\n return (\n <FormProvider {...form}>\n <form\n {...rest}\n onSubmit={(e) => {\n e.preventDefault();\n form.handleSubmit((values) => onSubmitForm(values, e));\n }}\n >\n {children}\n </form>\n </FormProvider>\n );\n};\n"],"mappings":";;;;;;;;AAOA,MAAM,SAAS,EAAE,OAAO,EACtB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,GAAG,YAAY,EACtC,CAAC;AAOD,MAAa,sBAAmD,UAAU;CACxE,MAAM,mBAAmB,MAAM,UAAU;CAEzC,MAAM,OAAO,QAA0C;EACrD,UAAU,YAAY,gBAAuB;EAC7C,QAAQ,MAAM;CAChB,CAAC;CAED,MAAM,EAAE,UAAU,UAAU,GAAG,SAAS;CAExC,MAAM,gBACJ,QACA,UACG;EAEH,IADqB,iBAAiB,UAAU,MACjC,CAAC,CAAC,SACf,WAAW,KAAK;CAEpB;CAEA,OACE,oBAAC,cAAD;EAAc,GAAI;YAChB,oBAAC,QAAD;GACE,GAAI;GACJ,WAAW,MAAM;IACf,EAAE,eAAe;IACjB,KAAK,cAAc,WAAW,aAAa,QAAQ,CAAC,CAAC;GACvD;GAEC;EACG;CACM;AAElB"}
@@ -48,8 +48,8 @@ const getIntlayerHTMLOptions = () => ({ components: {
48
48
  * A comprehensive HTML renderer that transforms HTML text into rich,
49
49
  * interactive content with custom styling and Intlayer integration.
50
50
  */
51
- const HTMLRenderer = ({ children, isDarkMode, locale, components: componentsProp }) => {
52
- const htmlOptions = getIntlayerHTMLOptions(isDarkMode ?? false);
51
+ const HTMLRenderer = ({ children, locale, components: componentsProp }) => {
52
+ const htmlOptions = getIntlayerHTMLOptions();
53
53
  const htmlContent = renderHTML(children, { components: {
54
54
  ...htmlOptions.components,
55
55
  a: (props) => /* @__PURE__ */ jsx(Link, {
@@ -1 +1 @@
1
- {"version":3,"file":"HTMLRender.mjs","names":[],"sources":["../../../../src/components/HTMLRender/HTMLRender.tsx"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport type { ComponentProps, FC } from 'react';\nimport {\n type HTMLRenderer as HTMLRendererIntlayer,\n type RenderHTMLProps,\n renderHTML,\n} from 'react-intlayer/html';\nimport { H1, H2, H3, H4, H5, H6 } from '../Headers';\nimport { Link } from '../Link';\n\ntype HTMLRendererProps = {\n children: string;\n isDarkMode?: boolean;\n locale?: LocalesValues;\n components?: ComponentProps<typeof HTMLRendererIntlayer>['components'];\n};\n\nexport const getIntlayerHTMLOptions: (isDarkMode: boolean) => RenderHTMLProps =\n () => ({\n components: {\n h1: (props) => <H1 isClickable={true} {...props} />,\n h2: (props) => <H2 isClickable={true} className=\"mt-16\" {...props} />,\n h3: (props) => <H3 isClickable={true} className=\"mt-5\" {...props} />,\n h4: (props) => <H4 isClickable={true} className=\"mt-3\" {...props} />,\n h5: (props) => <H5 isClickable={true} className=\"mt-3\" {...props} />,\n h6: (props) => <H6 isClickable={true} className=\"mt-3\" {...props} />,\n a: (props) => (\n <Link\n isExternalLink={props.href?.startsWith('http')}\n underlined={true}\n label=\"\"\n {...props}\n color=\"neutral\"\n />\n ),\n },\n });\n\n/**\n * HTMLRenderer Component\n *\n * A comprehensive HTML renderer that transforms HTML text into rich,\n * interactive content with custom styling and Intlayer integration.\n */\nexport const HTMLRenderer: FC<HTMLRendererProps> = ({\n children,\n isDarkMode,\n locale,\n components: componentsProp,\n}) => {\n const htmlOptions = getIntlayerHTMLOptions(isDarkMode ?? false);\n\n const htmlContent = renderHTML(children, {\n components: {\n ...htmlOptions.components,\n a: (props) => (\n <Link\n isExternalLink={props.href?.startsWith('http')}\n underlined={true}\n locale={locale}\n label=\"\"\n {...props}\n color=\"neutral\"\n />\n ),\n ...componentsProp,\n },\n });\n\n return <>{htmlContent}</>;\n};\n"],"mappings":";;;;;;AAiBA,MAAa,gCACJ,EACL,YAAY;CACV,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,GAAI;CAAQ;CAClD,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,WAAU;EAAQ,GAAI;CAAQ;CACpE,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,WAAU;EAAO,GAAI;CAAQ;CACnE,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,WAAU;EAAO,GAAI;CAAQ;CACnE,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,WAAU;EAAO,GAAI;CAAQ;CACnE,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,WAAU;EAAO,GAAI;CAAQ;CACnE,IAAI,UACF,oBAAC,MAAD;EACE,gBAAgB,MAAM,MAAM,WAAW,MAAM;EAC7C,YAAY;EACZ,OAAM;EACN,GAAI;EACJ,OAAM;CACP;AAEL,EACF;;;;;;;AAQF,MAAa,gBAAuC,EAClD,UACA,YACA,QACA,YAAY,qBACR;CACJ,MAAM,cAAc,uBAAuB,cAAc,KAAK;CAE9D,MAAM,cAAc,WAAW,UAAU,EACvC,YAAY;EACV,GAAG,YAAY;EACf,IAAI,UACF,oBAAC,MAAD;GACE,gBAAgB,MAAM,MAAM,WAAW,MAAM;GAC7C,YAAY;GACJ;GACR,OAAM;GACN,GAAI;GACJ,OAAM;EACP;EAEH,GAAG;CACL,EACF,CAAC;CAED,OAAO,0CAAG,YAAc;AAC1B"}
1
+ {"version":3,"file":"HTMLRender.mjs","names":[],"sources":["../../../../src/components/HTMLRender/HTMLRender.tsx"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport type { ComponentProps, FC } from 'react';\nimport {\n type HTMLRenderer as HTMLRendererIntlayer,\n type RenderHTMLProps,\n renderHTML,\n} from 'react-intlayer/html';\nimport { H1, H2, H3, H4, H5, H6 } from '../Headers';\nimport { Link } from '../Link';\n\ntype HTMLRendererProps = {\n children: string;\n locale?: LocalesValues;\n components?: ComponentProps<typeof HTMLRendererIntlayer>['components'];\n};\n\nexport const getIntlayerHTMLOptions: () => RenderHTMLProps = () => ({\n components: {\n h1: (props) => <H1 isClickable={true} {...props} />,\n h2: (props) => <H2 isClickable={true} className=\"mt-16\" {...props} />,\n h3: (props) => <H3 isClickable={true} className=\"mt-5\" {...props} />,\n h4: (props) => <H4 isClickable={true} className=\"mt-3\" {...props} />,\n h5: (props) => <H5 isClickable={true} className=\"mt-3\" {...props} />,\n h6: (props) => <H6 isClickable={true} className=\"mt-3\" {...props} />,\n a: (props) => (\n <Link\n isExternalLink={props.href?.startsWith('http')}\n underlined={true}\n label=\"\"\n {...props}\n color=\"neutral\"\n />\n ),\n },\n});\n\n/**\n * HTMLRenderer Component\n *\n * A comprehensive HTML renderer that transforms HTML text into rich,\n * interactive content with custom styling and Intlayer integration.\n */\nexport const HTMLRenderer: FC<HTMLRendererProps> = ({\n children,\n locale,\n components: componentsProp,\n}) => {\n const htmlOptions = getIntlayerHTMLOptions();\n\n const htmlContent = renderHTML(children, {\n components: {\n ...htmlOptions.components,\n a: (props) => (\n <Link\n isExternalLink={props.href?.startsWith('http')}\n underlined={true}\n locale={locale}\n label=\"\"\n {...props}\n color=\"neutral\"\n />\n ),\n ...componentsProp,\n },\n });\n\n return <>{htmlContent}</>;\n};\n"],"mappings":";;;;;;AAgBA,MAAa,gCAAuD,EAClE,YAAY;CACV,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,GAAI;CAAQ;CAClD,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,WAAU;EAAQ,GAAI;CAAQ;CACpE,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,WAAU;EAAO,GAAI;CAAQ;CACnE,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,WAAU;EAAO,GAAI;CAAQ;CACnE,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,WAAU;EAAO,GAAI;CAAQ;CACnE,KAAK,UAAU,oBAAC,IAAD;EAAI,aAAa;EAAM,WAAU;EAAO,GAAI;CAAQ;CACnE,IAAI,UACF,oBAAC,MAAD;EACE,gBAAgB,MAAM,MAAM,WAAW,MAAM;EAC7C,YAAY;EACZ,OAAM;EACN,GAAI;EACJ,OAAM;CACP;AAEL,EACF;;;;;;;AAQA,MAAa,gBAAuC,EAClD,UACA,QACA,YAAY,qBACR;CACJ,MAAM,cAAc,uBAAuB;CAE3C,MAAM,cAAc,WAAW,UAAU,EACvC,YAAY;EACV,GAAG,YAAY;EACf,IAAI,UACF,oBAAC,MAAD;GACE,gBAAgB,MAAM,MAAM,WAAW,MAAM;GAC7C,YAAY;GACJ;GACR,OAAM;GACN,GAAI;GACJ,OAAM;EACP;EAEH,GAAG;CACL,EACF,CAAC;CAED,OAAO,0CAAG,YAAc;AAC1B"}
@@ -28,7 +28,7 @@ const parseFormats = (raw) => {
28
28
  } catch {}
29
29
  return [raw];
30
30
  };
31
- const Code = ({ children, language, isDarkMode, showHeader = true, showLineNumbers = true, className, fileName, packageManager, codeFormat: rawCodeFormat, contentDeclarationFormat: rawContentDeclarationFormat, isRollable = true, lang, ...props }) => {
31
+ const Code = ({ children, language, highlightedHtml, showHeader = true, showLineNumbers = true, className, fileName, packageManager, codeFormat: rawCodeFormat, contentDeclarationFormat: rawContentDeclarationFormat, isRollable = true, lang, ...props }) => {
32
32
  const { codeFormat: selectedCodeFormat, contentDeclarationFormat: selectedContentDeclarationFormat } = useCodeContext();
33
33
  const codeFormats = useMemo(() => parseFormats(rawCodeFormat), [rawCodeFormat]);
34
34
  const contentFormats = useMemo(() => parseFormats(rawContentDeclarationFormat), [rawContentDeclarationFormat]);
@@ -130,11 +130,11 @@ const Code = ({ children, language, isDarkMode, showHeader = true, showLineNumbe
130
130
  children: isMultiFormat ? /* @__PURE__ */ jsx(CodeBlockHighlight, {
131
131
  originalLang: language,
132
132
  targetFormat: resolvedFormat,
133
- isDarkMode,
133
+ initialHtml: highlightedHtml,
134
134
  children: rawCode
135
135
  }) : /* @__PURE__ */ jsx(CodeBlock, {
136
136
  lang: language,
137
- isDarkMode,
137
+ highlightedHtml,
138
138
  children: rawCode
139
139
  })
140
140
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"Code.mjs","names":[],"sources":["../../../../src/components/IDE/Code.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport type { FC, HTMLAttributes, ReactNode } from 'react';\nimport { useEffect, useMemo, useState } from 'react';\nimport { Container } from '../Container';\nimport { ExpandCollapse } from '../ExpandCollapse';\nimport { CodeBlock } from './CodeBlockClient';\nimport { CodeBlockHighlight } from './CodeBlockHighlight';\nimport { CodeConditionalRender } from './CodeConditionalRenderer';\nimport type {\n CodeFormat,\n ContentDeclarationFormat,\n PackageManager,\n} from './CodeContext';\nimport { useCodeContext } from './CodeContext';\nimport { CodeFormatSelector } from './CodeFormatSelector';\nimport { ContentDeclarationFormatSelector } from './ContentDeclarationFormatSelector';\nimport { CopyCode } from './CopyCode';\nimport { PackageManagerSelector } from './PackageManagerSelector';\nimport type { CodeLanguage } from './shikiLanguages';\n\nexport type CodeCompAttributes = {\n fileName?: string;\n packageManager?: PackageManager;\n /** Single format, a JSON-array string, or an array of formats. */\n codeFormat?: CodeFormat | string | string[];\n contentDeclarationFormat?: ContentDeclarationFormat | string | string[];\n};\n\ntype CodeCompProps = {\n children: ReactNode;\n fileName?: string;\n language: CodeLanguage;\n isDarkMode?: boolean;\n showHeader?: boolean;\n showLineNumbers?: boolean;\n isRollable?: boolean;\n} & CodeCompAttributes &\n HTMLAttributes<HTMLDivElement>;\n\nconst MIN_HEIGHT = 700;\n\n/** Languages that use JSX syntax — CommonJS doesn't make sense for these. */\nconst JSX_LANGUAGES = new Set(['tsx', 'jsx']);\n\n/** Parse a codeFormat prop that may be a single value, a JSON-array string, or already an array. */\nconst parseFormats = (\n raw: string | string[] | undefined\n): CodeFormat[] | undefined => {\n if (!raw) return undefined;\n if (Array.isArray(raw)) return raw as CodeFormat[];\n if (typeof raw === 'string' && raw.startsWith('[')) {\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) return parsed as CodeFormat[];\n } catch {\n /* ignore */\n }\n }\n return [raw as CodeFormat];\n};\n\nexport const Code: FC<CodeCompProps> = ({\n children,\n language,\n isDarkMode,\n showHeader = true,\n showLineNumbers = true,\n className,\n fileName,\n packageManager,\n codeFormat: rawCodeFormat,\n contentDeclarationFormat: rawContentDeclarationFormat,\n isRollable = true,\n lang, // Destructure html lang prop to prevent passing invalid BCP-47 language tag to Code component\n ...props\n}) => {\n const {\n codeFormat: selectedCodeFormat,\n contentDeclarationFormat: selectedContentDeclarationFormat,\n } = useCodeContext();\n\n // Parse whichever attribute is present as an array of formats.\n const codeFormats = useMemo(\n () => parseFormats(rawCodeFormat as string | undefined),\n [rawCodeFormat]\n );\n const contentFormats = useMemo(\n () => parseFormats(rawContentDeclarationFormat as string | undefined),\n [rawContentDeclarationFormat]\n );\n\n // A block is \"multi-format\" when it has multiple formats including TypeScript\n // (the canonical source). Such blocks transform at runtime.\n const isMultiCodeFormat =\n codeFormats !== undefined &&\n codeFormats.length > 1 &&\n codeFormats.includes('typescript');\n\n const isMultiContentFormat =\n contentFormats !== undefined &&\n contentFormats.length > 1 &&\n contentFormats.includes('typescript');\n\n const isMultiFormat = isMultiCodeFormat || isMultiContentFormat;\n\n // Determine which context format drives this block's selection.\n // content declaration blocks use selectedContentDeclarationFormat;\n // regular code blocks use selectedCodeFormat.\n const selectedFormat: CodeFormat = isMultiContentFormat\n ? (selectedContentDeclarationFormat as CodeFormat)\n : selectedCodeFormat;\n\n // The formats actually relevant for transformation (no 'json', no 'commonjs' for JSX).\n const effectiveFormats = useMemo<CodeFormat[] | undefined>(() => {\n const base = isMultiContentFormat ? contentFormats : codeFormats;\n if (!base) return base;\n let filtered = base.filter((f) => f !== 'json') as CodeFormat[];\n if (JSX_LANGUAGES.has(language as string)) {\n filtered = filtered.filter((f) => f !== 'commonjs');\n }\n return filtered;\n }, [isMultiContentFormat, contentFormats, codeFormats, language]);\n\n // When the globally-selected format isn't valid for this block\n // (e.g. CJS selected but this is a JSX file), fall back to the last valid one.\n const resolvedFormat: Exclude<CodeFormat, 'json'> = useMemo(() => {\n if (!effectiveFormats || effectiveFormats.includes(selectedFormat)) {\n return selectedFormat as Exclude<CodeFormat, 'json'>;\n }\n return (effectiveFormats[effectiveFormats.length - 1] ??\n 'typescript') as Exclude<CodeFormat, 'json'>;\n }, [effectiveFormats, selectedFormat]);\n\n // ── Async filename derivation (dynamic import of transformer) ──────────────\n // We derive the displayed fileName so the header updates when format changes.\n // deriveFileName is tiny but it lives inside codeTransformer, which is only\n // imported when actually needed (non-TypeScript selection).\n const [displayedFileName, setDisplayedFileName] = useState<\n string | undefined\n >(fileName);\n\n useEffect(() => {\n if (!isMultiFormat || resolvedFormat === 'typescript') {\n setDisplayedFileName(fileName);\n return;\n }\n if (!fileName) return;\n\n let cancelled = false;\n (async () => {\n const { deriveFileName } = await import('./codeTransformer');\n if (!cancelled)\n setDisplayedFileName(deriveFileName(fileName, resolvedFormat));\n })();\n return () => {\n cancelled = true;\n };\n }, [fileName, isMultiFormat, resolvedFormat]);\n\n // ── Async copy text (transformed code for CopyCode) ───────────────────────\n const rawCode = useMemo(() => {\n const code = Array.isArray(children) ? children.join('') : String(children);\n return code.endsWith('\\n') ? code.slice(0, -1) : code;\n }, [children]);\n\n const [copyCode, setCopyCode] = useState<string>(rawCode);\n\n useEffect(() => {\n if (!isMultiFormat || resolvedFormat === 'typescript') {\n setCopyCode(rawCode);\n return;\n }\n let cancelled = false;\n (async () => {\n const { transformCode } = await import('./codeTransformer');\n if (!cancelled) setCopyCode(transformCode(rawCode, resolvedFormat));\n })();\n return () => {\n cancelled = true;\n };\n }, [rawCode, isMultiFormat, resolvedFormat]);\n\n const hadSelectInHeader =\n packageManager || rawCodeFormat || rawContentDeclarationFormat;\n\n return (\n <CodeConditionalRender\n packageManager={packageManager}\n codeFormat={rawCodeFormat as string | undefined}\n contentDeclarationFormat={\n rawContentDeclarationFormat as string | undefined\n }\n >\n <Container\n className={cn(\n 'relative min-w-0 max-w-full text-sm leading-6',\n showLineNumbers && 'with-line-number ml-0',\n className\n )}\n transparency=\"lg\"\n {...props}\n >\n {showHeader && (\n <>\n <div className=\"grid w-full grid-cols-[1fr_auto] items-center justify-between rounded-t-xl bg-card/50 py-1.5 pr-12 pl-4 text-neutral text-xs\">\n <span className=\"truncate\">{displayedFileName ?? language}</span>\n <div className=\"flex items-center gap-2\">\n {packageManager && <PackageManagerSelector />}\n {rawCodeFormat && (\n <CodeFormatSelector availableFormats={effectiveFormats} />\n )}\n {rawContentDeclarationFormat && (\n <ContentDeclarationFormatSelector />\n )}\n </div>\n </div>\n <div className=\"sticky top-46 z-20\">\n <div\n className={cn(\n 'absolute right-2 bottom-0 flex h-7 items-center',\n hadSelectInHeader && 'h-11'\n )}\n >\n <CopyCode code={copyCode} />\n </div>\n </div>\n </>\n )}\n <ExpandCollapse\n minHeight={MIN_HEIGHT}\n isRollable={isRollable}\n className=\"min-w-0 max-w-full overflow-x-auto p-2\"\n >\n {isMultiFormat ? (\n /*\n * Multi-format: CodeBlockHighlight manages both the transformation\n * (dynamic import of codeTransformer) and the Shiki highlighting in\n * a single useEffect. The previous highlighted output stays visible\n * while the new one loads — no white-text flash.\n */\n <CodeBlockHighlight\n originalLang={language}\n targetFormat={resolvedFormat}\n isDarkMode={isDarkMode}\n >\n {rawCode}\n </CodeBlockHighlight>\n ) : (\n /*\n * Single-format: use the original Suspense-based async Shiki renderer\n * (good for SSR / static content).\n */\n <CodeBlock lang={language} isDarkMode={isDarkMode}>\n {rawCode}\n </CodeBlock>\n )}\n </ExpandCollapse>\n </Container>\n </CodeConditionalRender>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"Code.mjs","names":[],"sources":["../../../../src/components/IDE/Code.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport type { FC, HTMLAttributes, ReactNode } from 'react';\nimport { useEffect, useMemo, useState } from 'react';\nimport { Container } from '../Container';\nimport { ExpandCollapse } from '../ExpandCollapse';\nimport { CodeBlock } from './CodeBlockClient';\nimport { CodeBlockHighlight } from './CodeBlockHighlight';\nimport { CodeConditionalRender } from './CodeConditionalRenderer';\nimport type {\n CodeFormat,\n ContentDeclarationFormat,\n PackageManager,\n} from './CodeContext';\nimport { useCodeContext } from './CodeContext';\nimport { CodeFormatSelector } from './CodeFormatSelector';\nimport { ContentDeclarationFormatSelector } from './ContentDeclarationFormatSelector';\nimport { CopyCode } from './CopyCode';\nimport { PackageManagerSelector } from './PackageManagerSelector';\nimport type { CodeLanguage } from './shikiLanguages';\n\nexport type CodeCompAttributes = {\n fileName?: string;\n packageManager?: PackageManager;\n /** Single format, a JSON-array string, or an array of formats. */\n codeFormat?: CodeFormat | string | string[];\n contentDeclarationFormat?: ContentDeclarationFormat | string | string[];\n};\n\ntype CodeCompProps = {\n children: ReactNode;\n fileName?: string;\n language: CodeLanguage;\n /**\n * Markup for `children`, highlighted by Shiki ahead of time. When present the\n * block paints straight from it and the highlighter is never downloaded —\n * except when the reader switches the block to a non-TypeScript format.\n */\n highlightedHtml?: string;\n showHeader?: boolean;\n showLineNumbers?: boolean;\n isRollable?: boolean;\n} & CodeCompAttributes &\n HTMLAttributes<HTMLDivElement>;\n\nconst MIN_HEIGHT = 700;\n\n/** Languages that use JSX syntax — CommonJS doesn't make sense for these. */\nconst JSX_LANGUAGES = new Set(['tsx', 'jsx']);\n\n/** Parse a codeFormat prop that may be a single value, a JSON-array string, or already an array. */\nconst parseFormats = (\n raw: string | string[] | undefined\n): CodeFormat[] | undefined => {\n if (!raw) return undefined;\n if (Array.isArray(raw)) return raw as CodeFormat[];\n if (typeof raw === 'string' && raw.startsWith('[')) {\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) return parsed as CodeFormat[];\n } catch {\n /* ignore */\n }\n }\n return [raw as CodeFormat];\n};\n\nexport const Code: FC<CodeCompProps> = ({\n children,\n language,\n highlightedHtml,\n showHeader = true,\n showLineNumbers = true,\n className,\n fileName,\n packageManager,\n codeFormat: rawCodeFormat,\n contentDeclarationFormat: rawContentDeclarationFormat,\n isRollable = true,\n lang, // Destructure html lang prop to prevent passing invalid BCP-47 language tag to Code component\n ...props\n}) => {\n const {\n codeFormat: selectedCodeFormat,\n contentDeclarationFormat: selectedContentDeclarationFormat,\n } = useCodeContext();\n\n // Parse whichever attribute is present as an array of formats.\n const codeFormats = useMemo(\n () => parseFormats(rawCodeFormat as string | undefined),\n [rawCodeFormat]\n );\n const contentFormats = useMemo(\n () => parseFormats(rawContentDeclarationFormat as string | undefined),\n [rawContentDeclarationFormat]\n );\n\n // A block is \"multi-format\" when it has multiple formats including TypeScript\n // (the canonical source). Such blocks transform at runtime.\n const isMultiCodeFormat =\n codeFormats !== undefined &&\n codeFormats.length > 1 &&\n codeFormats.includes('typescript');\n\n const isMultiContentFormat =\n contentFormats !== undefined &&\n contentFormats.length > 1 &&\n contentFormats.includes('typescript');\n\n const isMultiFormat = isMultiCodeFormat || isMultiContentFormat;\n\n // Determine which context format drives this block's selection.\n // content declaration blocks use selectedContentDeclarationFormat;\n // regular code blocks use selectedCodeFormat.\n const selectedFormat: CodeFormat = isMultiContentFormat\n ? (selectedContentDeclarationFormat as CodeFormat)\n : selectedCodeFormat;\n\n // The formats actually relevant for transformation (no 'json', no 'commonjs' for JSX).\n const effectiveFormats = useMemo<CodeFormat[] | undefined>(() => {\n const base = isMultiContentFormat ? contentFormats : codeFormats;\n if (!base) return base;\n let filtered = base.filter((f) => f !== 'json') as CodeFormat[];\n if (JSX_LANGUAGES.has(language as string)) {\n filtered = filtered.filter((f) => f !== 'commonjs');\n }\n return filtered;\n }, [isMultiContentFormat, contentFormats, codeFormats, language]);\n\n // When the globally-selected format isn't valid for this block\n // (e.g. CJS selected but this is a JSX file), fall back to the last valid one.\n const resolvedFormat: Exclude<CodeFormat, 'json'> = useMemo(() => {\n if (!effectiveFormats || effectiveFormats.includes(selectedFormat)) {\n return selectedFormat as Exclude<CodeFormat, 'json'>;\n }\n return (effectiveFormats[effectiveFormats.length - 1] ??\n 'typescript') as Exclude<CodeFormat, 'json'>;\n }, [effectiveFormats, selectedFormat]);\n\n // ── Async filename derivation (dynamic import of transformer) ──────────────\n // We derive the displayed fileName so the header updates when format changes.\n // deriveFileName is tiny but it lives inside codeTransformer, which is only\n // imported when actually needed (non-TypeScript selection).\n const [displayedFileName, setDisplayedFileName] = useState<\n string | undefined\n >(fileName);\n\n useEffect(() => {\n if (!isMultiFormat || resolvedFormat === 'typescript') {\n setDisplayedFileName(fileName);\n return;\n }\n if (!fileName) return;\n\n let cancelled = false;\n (async () => {\n const { deriveFileName } = await import('./codeTransformer');\n if (!cancelled)\n setDisplayedFileName(deriveFileName(fileName, resolvedFormat));\n })();\n return () => {\n cancelled = true;\n };\n }, [fileName, isMultiFormat, resolvedFormat]);\n\n // ── Async copy text (transformed code for CopyCode) ───────────────────────\n const rawCode = useMemo(() => {\n const code = Array.isArray(children) ? children.join('') : String(children);\n return code.endsWith('\\n') ? code.slice(0, -1) : code;\n }, [children]);\n\n const [copyCode, setCopyCode] = useState<string>(rawCode);\n\n useEffect(() => {\n if (!isMultiFormat || resolvedFormat === 'typescript') {\n setCopyCode(rawCode);\n return;\n }\n let cancelled = false;\n (async () => {\n const { transformCode } = await import('./codeTransformer');\n if (!cancelled) setCopyCode(transformCode(rawCode, resolvedFormat));\n })();\n return () => {\n cancelled = true;\n };\n }, [rawCode, isMultiFormat, resolvedFormat]);\n\n const hadSelectInHeader =\n packageManager || rawCodeFormat || rawContentDeclarationFormat;\n\n return (\n <CodeConditionalRender\n packageManager={packageManager}\n codeFormat={rawCodeFormat as string | undefined}\n contentDeclarationFormat={\n rawContentDeclarationFormat as string | undefined\n }\n >\n <Container\n className={cn(\n 'relative min-w-0 max-w-full text-sm leading-6',\n showLineNumbers && 'with-line-number ml-0',\n className\n )}\n transparency=\"lg\"\n {...props}\n >\n {showHeader && (\n <>\n <div className=\"grid w-full grid-cols-[1fr_auto] items-center justify-between rounded-t-xl bg-card/50 py-1.5 pr-12 pl-4 text-neutral text-xs\">\n <span className=\"truncate\">{displayedFileName ?? language}</span>\n <div className=\"flex items-center gap-2\">\n {packageManager && <PackageManagerSelector />}\n {rawCodeFormat && (\n <CodeFormatSelector availableFormats={effectiveFormats} />\n )}\n {rawContentDeclarationFormat && (\n <ContentDeclarationFormatSelector />\n )}\n </div>\n </div>\n <div className=\"sticky top-46 z-20\">\n <div\n className={cn(\n 'absolute right-2 bottom-0 flex h-7 items-center',\n hadSelectInHeader && 'h-11'\n )}\n >\n <CopyCode code={copyCode} />\n </div>\n </div>\n </>\n )}\n <ExpandCollapse\n minHeight={MIN_HEIGHT}\n isRollable={isRollable}\n className=\"min-w-0 max-w-full overflow-x-auto p-2\"\n >\n {isMultiFormat ? (\n /*\n * Multi-format: CodeBlockHighlight manages both the transformation\n * (dynamic import of codeTransformer) and the Shiki highlighting in\n * a single useEffect. The previous highlighted output stays visible\n * while the new one loads — no white-text flash.\n */\n <CodeBlockHighlight\n originalLang={language}\n targetFormat={resolvedFormat}\n initialHtml={highlightedHtml}\n >\n {rawCode}\n </CodeBlockHighlight>\n ) : (\n /*\n * Single-format: paint the pre-highlighted markup when it was\n * supplied, otherwise fall back to the Suspense-based async Shiki\n * renderer.\n */\n <CodeBlock lang={language} highlightedHtml={highlightedHtml}>\n {rawCode}\n </CodeBlock>\n )}\n </ExpandCollapse>\n </Container>\n </CodeConditionalRender>\n );\n};\n"],"mappings":"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"}
@@ -13,20 +13,32 @@ const CodeDefault = ({ children }) => /* @__PURE__ */ jsx("div", {
13
13
  }, `line-${index}-${line.slice(0, 10)}`)) : children })
14
14
  })
15
15
  });
16
+ /** Wrapper styles shared by the pre-highlighted and the runtime Shiki output. */
17
+ const shikiWrapperClassName = "[&_pre.shiki]:!bg-transparent min-w-0 max-w-full overflow-auto bg-transparent [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden [&_pre.shiki]:max-w-full [&_pre.shiki]:overflow-x-auto [&_pre::-webkit-scrollbar]:hidden [&_pre]:[-ms-overflow-style:none] [&_pre]:[scrollbar-width:none]";
18
+ /**
19
+ * Renders markup produced by Shiki ahead of time (at build/prerender).
20
+ *
21
+ * Both server and client render the exact same string, so hydration is a no-op
22
+ * and the Shiki engine — a ~600 KB WASM bundle plus one grammar per language —
23
+ * never reaches the browser.
24
+ */
25
+ const CodeHighlighted = ({ html }) => /* @__PURE__ */ jsx("div", {
26
+ className: shikiWrapperClassName,
27
+ children: /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } })
28
+ });
16
29
  const CodeBlockShiki = lazy(() => import("./CodeBlockShiki.mjs").then((mod) => ({ default: mod.CodeBlockShiki })));
17
- const CodeBlock = ({ className, onChange, isEditable, children, lang, isDarkMode, ...props }) => /* @__PURE__ */ jsx("div", {
30
+ const CodeBlock = ({ className, onChange, isEditable, children, lang, highlightedHtml, ...props }) => /* @__PURE__ */ jsx("div", {
18
31
  className: cn("flex w-full min-w-0 max-w-full overflow-x-auto", className),
19
32
  ...props,
20
- children: /* @__PURE__ */ jsx(Suspense, {
33
+ children: highlightedHtml ? /* @__PURE__ */ jsx(CodeHighlighted, { html: highlightedHtml }) : /* @__PURE__ */ jsx(Suspense, {
21
34
  fallback: /* @__PURE__ */ jsx(CodeDefault, { children }),
22
35
  children: /* @__PURE__ */ jsx(CodeBlockShiki, {
23
36
  lang,
24
- isDarkMode,
25
37
  children
26
38
  })
27
39
  })
28
40
  });
29
41
 
30
42
  //#endregion
31
- export { CodeBlock, CodeDefault };
43
+ export { CodeBlock, CodeDefault, CodeHighlighted, shikiWrapperClassName };
32
44
  //# sourceMappingURL=CodeBlockClient.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlockClient.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockClient.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport {\n type FC,\n type HTMLAttributes,\n lazy,\n type ReactNode,\n Suspense,\n} from 'react';\nimport type { CodeLanguage } from './shikiLanguages';\n\nexport type { CodeLanguage } from './shikiLanguages';\n\nexport const CodeDefault: FC<{ children: ReactNode }> = ({ children }) => (\n <div className=\"min-w-0 max-w-full overflow-x-auto\">\n <pre className=\"min-w-0 max-w-full overflow-x-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\">\n <code>\n {typeof children === 'string'\n ? children.split('\\n').map((line, index) => (\n <span\n className=\"line block w-full\"\n key={`line-${index}-${line.slice(0, 10)}`}\n >\n {line}\n </span>\n ))\n : children}\n </code>\n </pre>\n </div>\n);\n\n// Lazy load the Shiki component\nconst CodeBlockShiki = lazy(() =>\n import('./CodeBlockShiki').then((mod) => ({\n default: mod.CodeBlockShiki,\n }))\n);\n\nexport type CodeBlockProps = {\n children: React.ReactNode;\n lang: CodeLanguage;\n isDarkMode?: boolean;\n isEditable?: boolean;\n onChange?: (content: string) => void;\n} & Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>;\n\nexport const CodeBlock: FC<CodeBlockProps> = ({\n className,\n onChange,\n isEditable,\n children,\n lang,\n isDarkMode,\n ...props\n}) => (\n <div\n className={cn('flex w-full min-w-0 max-w-full overflow-x-auto', className)}\n {...props}\n >\n <Suspense fallback={<CodeDefault>{children}</CodeDefault>}>\n <CodeBlockShiki lang={lang} isDarkMode={isDarkMode}>\n {children}\n </CodeBlockShiki>\n </Suspense>\n </div>\n);\n"],"mappings":";;;;;AAYA,MAAa,eAA4C,EAAE,eACzD,oBAAC,OAAD;CAAK,WAAU;WACb,oBAAC,OAAD;EAAK,WAAU;YACb,oBAAC,QAAD,YACG,OAAO,aAAa,WACjB,SAAS,MAAM,IAAI,CAAC,CAAC,KAAK,MAAM,UAC9B,oBAAC,QAAD;GACE,WAAU;aAGT;EACG,GAHC,QAAQ,MAAM,GAAG,KAAK,MAAM,GAAG,EAAE,GAGlC,CACP,IACD,SACA;CACH;AACF;AAIP,MAAM,iBAAiB,WACrB,OAAO,uBAAmB,CAAC,MAAM,SAAS,EACxC,SAAS,IAAI,eACf,EAAE,CACJ;AAUA,MAAa,aAAiC,EAC5C,WACA,UACA,YACA,UACA,MACA,YACA,GAAG,YAEH,oBAAC,OAAD;CACE,WAAW,GAAG,kDAAkD,SAAS;CACzE,GAAI;WAEJ,oBAAC,UAAD;EAAU,UAAU,oBAAC,aAAD,EAAc,SAAsB;YACtD,oBAAC,gBAAD;GAAsB;GAAkB;GACrC;EACa;CACR;AACP"}
1
+ {"version":3,"file":"CodeBlockClient.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockClient.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport {\n type FC,\n type HTMLAttributes,\n lazy,\n type ReactNode,\n Suspense,\n} from 'react';\nimport type { CodeLanguage } from './shikiLanguages';\n\nexport type { CodeLanguage } from './shikiLanguages';\n\nexport const CodeDefault: FC<{ children: ReactNode }> = ({ children }) => (\n <div className=\"min-w-0 max-w-full overflow-x-auto\">\n <pre className=\"min-w-0 max-w-full overflow-x-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\">\n <code>\n {typeof children === 'string'\n ? children.split('\\n').map((line, index) => (\n <span\n className=\"line block w-full\"\n key={`line-${index}-${line.slice(0, 10)}`}\n >\n {line}\n </span>\n ))\n : children}\n </code>\n </pre>\n </div>\n);\n\n/** Wrapper styles shared by the pre-highlighted and the runtime Shiki output. */\nexport const shikiWrapperClassName =\n '[&_pre.shiki]:!bg-transparent min-w-0 max-w-full overflow-auto bg-transparent [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden [&_pre.shiki]:max-w-full [&_pre.shiki]:overflow-x-auto [&_pre::-webkit-scrollbar]:hidden [&_pre]:[-ms-overflow-style:none] [&_pre]:[scrollbar-width:none]';\n\n/**\n * Renders markup produced by Shiki ahead of time (at build/prerender).\n *\n * Both server and client render the exact same string, so hydration is a no-op\n * and the Shiki engine — a ~600 KB WASM bundle plus one grammar per language —\n * never reaches the browser.\n */\nexport const CodeHighlighted: FC<{ html: string }> = ({ html }) => (\n <div className={shikiWrapperClassName}>\n <div\n // biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki generates safe HTML for code highlighting\n dangerouslySetInnerHTML={{ __html: html }}\n />\n </div>\n);\n\n// Lazy load the Shiki component\nconst CodeBlockShiki = lazy(() =>\n import('./CodeBlockShiki').then((mod) => ({\n default: mod.CodeBlockShiki,\n }))\n);\n\nexport type CodeBlockProps = {\n children: React.ReactNode;\n lang: CodeLanguage;\n /**\n * Markup produced by Shiki ahead of time. When present the block renders it\n * as-is and no highlighting runs in the browser.\n */\n highlightedHtml?: string;\n isEditable?: boolean;\n onChange?: (content: string) => void;\n} & Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>;\n\nexport const CodeBlock: FC<CodeBlockProps> = ({\n className,\n onChange,\n isEditable,\n children,\n lang,\n highlightedHtml,\n ...props\n}) => (\n <div\n className={cn('flex w-full min-w-0 max-w-full overflow-x-auto', className)}\n {...props}\n >\n {highlightedHtml ? (\n <CodeHighlighted html={highlightedHtml} />\n ) : (\n <Suspense fallback={<CodeDefault>{children}</CodeDefault>}>\n <CodeBlockShiki lang={lang}>{children}</CodeBlockShiki>\n </Suspense>\n )}\n </div>\n);\n"],"mappings":";;;;;AAYA,MAAa,eAA4C,EAAE,eACzD,oBAAC,OAAD;CAAK,WAAU;WACb,oBAAC,OAAD;EAAK,WAAU;YACb,oBAAC,QAAD,YACG,OAAO,aAAa,WACjB,SAAS,MAAM,IAAI,CAAC,CAAC,KAAK,MAAM,UAC9B,oBAAC,QAAD;GACE,WAAU;aAGT;EACG,GAHC,QAAQ,MAAM,GAAG,KAAK,MAAM,GAAG,EAAE,GAGlC,CACP,IACD,SACA;CACH;AACF;;AAIP,MAAa,wBACX;;;;;;;;AASF,MAAa,mBAAyC,EAAE,WACtD,oBAAC,OAAD;CAAK,WAAW;WACd,oBAAC,OAAD,EAEE,yBAAyB,EAAE,QAAQ,KAAK,EACzC;AACE;AAIP,MAAM,iBAAiB,WACrB,OAAO,uBAAmB,CAAC,MAAM,SAAS,EACxC,SAAS,IAAI,eACf,EAAE,CACJ;AAcA,MAAa,aAAiC,EAC5C,WACA,UACA,YACA,UACA,MACA,iBACA,GAAG,YAEH,oBAAC,OAAD;CACE,WAAW,GAAG,kDAAkD,SAAS;CACzE,GAAI;WAEH,kBACC,oBAAC,iBAAD,EAAiB,MAAM,gBAAkB,KAEzC,oBAAC,UAAD;EAAU,UAAU,oBAAC,aAAD,EAAc,SAAsB;YACtD,oBAAC,gBAAD;GAAsB;GAAO;EAAyB;CAC9C;AAET"}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { resolveCodeLanguage } from "./shikiLanguages.mjs";
4
+ import { SHIKI_THEMES } from "./shikiThemes.mjs";
4
5
  import { useEffect, useRef, useState } from "react";
5
6
  import { jsx } from "react/jsx-runtime";
6
7
 
@@ -9,13 +10,20 @@ import { jsx } from "react/jsx-runtime";
9
10
  * Client-side Shiki highlighter that also handles TypeScript→ESM/CJS transformation.
10
11
  *
11
12
  * Everything runs inside a single useEffect so that:
13
+ * - Nothing is imported at all while the canonical TypeScript source is shown
14
+ * and its markup was already highlighted ahead of time.
12
15
  * - The transformer is only dynamically imported when a non-TypeScript format is selected.
13
16
  * - The previous highlighted HTML stays visible while the new one loads (no white-text flash).
14
17
  */
15
- const CodeBlockHighlight = ({ children, originalLang, targetFormat, isDarkMode }) => {
16
- const [html, setHtml] = useState(null);
17
- const prevHtml = useRef(null);
18
+ const CodeBlockHighlight = ({ children, originalLang, targetFormat, initialHtml }) => {
19
+ const [html, setHtml] = useState(initialHtml ?? null);
20
+ const prevHtml = useRef(initialHtml ?? null);
18
21
  useEffect(() => {
22
+ if (initialHtml && targetFormat === "typescript") {
23
+ prevHtml.current = initialHtml;
24
+ setHtml(initialHtml);
25
+ return;
26
+ }
19
27
  let cancelled = false;
20
28
  (async () => {
21
29
  try {
@@ -31,7 +39,8 @@ const CodeBlockHighlight = ({ children, originalLang, targetFormat, isDarkMode }
31
39
  if (cancelled) return;
32
40
  const out = await codeToHtml(String(code), {
33
41
  lang: shikiLang,
34
- theme: isDarkMode ? "github-dark" : "github-light"
42
+ themes: SHIKI_THEMES,
43
+ defaultColor: false
35
44
  });
36
45
  if (!cancelled) {
37
46
  prevHtml.current = out;
@@ -48,7 +57,7 @@ const CodeBlockHighlight = ({ children, originalLang, targetFormat, isDarkMode }
48
57
  children,
49
58
  originalLang,
50
59
  targetFormat,
51
- isDarkMode
60
+ initialHtml
52
61
  ]);
53
62
  const display = html ?? prevHtml.current;
54
63
  if (!display) return /* @__PURE__ */ jsx("pre", {
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlockHighlight.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockHighlight.tsx"],"sourcesContent":["'use client';\n\n/**\n * Client-side Shiki highlighter that also handles TypeScript→ESM/CJS transformation.\n *\n * Everything runs inside a single useEffect so that:\n * - The transformer is only dynamically imported when a non-TypeScript format is selected.\n * - The previous highlighted HTML stays visible while the new one loads (no white-text flash).\n */\n\nimport { type ReactNode, useEffect, useRef, useState } from 'react';\nimport type { CodeFormat } from './CodeContext';\nimport { type CodeLanguage, resolveCodeLanguage } from './shikiLanguages';\n\ntype Props = {\n /** Raw TypeScript source code (the canonical \"source of truth\"). */\n children: ReactNode;\n /** Language of the source (e.g. 'tsx', 'typescript'). */\n originalLang: CodeLanguage;\n /** Currently selected format: 'typescript' | 'esm' | 'commonjs'. */\n targetFormat: Exclude<CodeFormat, 'json'>;\n isDarkMode?: boolean;\n};\n\nexport const CodeBlockHighlight = ({\n children,\n originalLang,\n targetFormat,\n isDarkMode,\n}: Props) => {\n const [html, setHtml] = useState<string | null>(null);\n const prevHtml = useRef<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n\n (async () => {\n try {\n let code = children;\n let shikiLang = resolveCodeLanguage(originalLang).id;\n\n // Only import the transformer when we actually need it.\n if (targetFormat !== 'typescript') {\n const { transformCode, deriveLanguage } = await import(\n './codeTransformer'\n );\n if (cancelled) return;\n code = transformCode(String(children), targetFormat);\n shikiLang = resolveCodeLanguage(\n deriveLanguage(originalLang, targetFormat)\n ).id;\n }\n\n const { codeToHtml } = await import('shiki/bundle/web');\n if (cancelled) return;\n\n const out = await codeToHtml(String(code), {\n lang: shikiLang,\n theme: isDarkMode ? 'github-dark' : 'github-light',\n });\n\n if (!cancelled) {\n prevHtml.current = out;\n setHtml(out);\n }\n } catch {\n // Shiki failed (unknown language, etc.) — fall through to plain-text.\n if (!cancelled) setHtml('');\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [children, originalLang, targetFormat, isDarkMode]);\n\n // Keep the previous highlighted output visible while the new one is loading.\n // This prevents the white-text flash on format switches.\n const display = html ?? prevHtml.current;\n\n if (!display) {\n return (\n <pre className=\"min-w-0 max-w-full overflow-x-auto\">\n <code>{children}</code>\n </pre>\n );\n }\n\n return (\n <div\n dangerouslySetInnerHTML={{ __html: display }}\n style={{ backgroundColor: 'transparent' }}\n />\n );\n};\n"],"mappings":";;;;;;;;;;;;;;AAwBA,MAAa,sBAAsB,EACjC,UACA,cACA,cACA,iBACW;CACX,MAAM,CAAC,MAAM,WAAW,SAAwB,IAAI;CACpD,MAAM,WAAW,OAAsB,IAAI;CAE3C,gBAAgB;EACd,IAAI,YAAY;EAEhB,CAAC,YAAY;GACX,IAAI;IACF,IAAI,OAAO;IACX,IAAI,YAAY,oBAAoB,YAAY,CAAC,CAAC;IAGlD,IAAI,iBAAiB,cAAc;KACjC,MAAM,EAAE,eAAe,mBAAmB,MAAM,OAC9C;KAEF,IAAI,WAAW;KACf,OAAO,cAAc,OAAO,QAAQ,GAAG,YAAY;KACnD,YAAY,oBACV,eAAe,cAAc,YAAY,CAC3C,CAAC,CAAC;IACJ;IAEA,MAAM,EAAE,eAAe,MAAM,OAAO;IACpC,IAAI,WAAW;IAEf,MAAM,MAAM,MAAM,WAAW,OAAO,IAAI,GAAG;KACzC,MAAM;KACN,OAAO,aAAa,gBAAgB;IACtC,CAAC;IAED,IAAI,CAAC,WAAW;KACd,SAAS,UAAU;KACnB,QAAQ,GAAG;IACb;GACF,QAAQ;IAEN,IAAI,CAAC,WAAW,QAAQ,EAAE;GAC5B;EACF,EAAC,CAAE;EAEH,aAAa;GACX,YAAY;EACd;CACF,GAAG;EAAC;EAAU;EAAc;EAAc;CAAU,CAAC;CAIrD,MAAM,UAAU,QAAQ,SAAS;CAEjC,IAAI,CAAC,SACH,OACE,oBAAC,OAAD;EAAK,WAAU;YACb,oBAAC,QAAD,EAAO,SAAe;CACnB;CAIT,OACE,oBAAC,OAAD;EACE,yBAAyB,EAAE,QAAQ,QAAQ;EAC3C,OAAO,EAAE,iBAAiB,cAAc;CACzC;AAEL"}
1
+ {"version":3,"file":"CodeBlockHighlight.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockHighlight.tsx"],"sourcesContent":["'use client';\n\n/**\n * Client-side Shiki highlighter that also handles TypeScript→ESM/CJS transformation.\n *\n * Everything runs inside a single useEffect so that:\n * - Nothing is imported at all while the canonical TypeScript source is shown\n * and its markup was already highlighted ahead of time.\n * - The transformer is only dynamically imported when a non-TypeScript format is selected.\n * - The previous highlighted HTML stays visible while the new one loads (no white-text flash).\n */\n\nimport { type ReactNode, useEffect, useRef, useState } from 'react';\nimport type { CodeFormat } from './CodeContext';\nimport { type CodeLanguage, resolveCodeLanguage } from './shikiLanguages';\nimport { SHIKI_THEMES } from './shikiThemes';\n\ntype Props = {\n /** Raw TypeScript source code (the canonical \"source of truth\"). */\n children: ReactNode;\n /** Language of the source (e.g. 'tsx', 'typescript'). */\n originalLang: CodeLanguage;\n /** Currently selected format: 'typescript' | 'esm' | 'commonjs'. */\n targetFormat: Exclude<CodeFormat, 'json'>;\n /** Markup for the TypeScript source, highlighted ahead of time. */\n initialHtml?: string;\n};\n\nexport const CodeBlockHighlight = ({\n children,\n originalLang,\n targetFormat,\n initialHtml,\n}: Props) => {\n const [html, setHtml] = useState<string | null>(initialHtml ?? null);\n const prevHtml = useRef<string | null>(initialHtml ?? null);\n\n useEffect(() => {\n // The pre-highlighted markup already is the TypeScript rendering — no need\n // to pull Shiki into the browser until another format is picked.\n if (initialHtml && targetFormat === 'typescript') {\n prevHtml.current = initialHtml;\n setHtml(initialHtml);\n return;\n }\n\n let cancelled = false;\n\n (async () => {\n try {\n let code = children;\n let shikiLang = resolveCodeLanguage(originalLang).id;\n\n // Only import the transformer when we actually need it.\n if (targetFormat !== 'typescript') {\n const { transformCode, deriveLanguage } = await import(\n './codeTransformer'\n );\n if (cancelled) return;\n code = transformCode(String(children), targetFormat);\n shikiLang = resolveCodeLanguage(\n deriveLanguage(originalLang, targetFormat)\n ).id;\n }\n\n const { codeToHtml } = await import('shiki/bundle/web');\n if (cancelled) return;\n\n const out = await codeToHtml(String(code), {\n lang: shikiLang,\n themes: SHIKI_THEMES,\n defaultColor: false,\n });\n\n if (!cancelled) {\n prevHtml.current = out;\n setHtml(out);\n }\n } catch {\n // Shiki failed (unknown language, etc.) — fall through to plain-text.\n if (!cancelled) setHtml('');\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [children, originalLang, targetFormat, initialHtml]);\n\n // Keep the previous highlighted output visible while the new one is loading.\n // This prevents the white-text flash on format switches.\n const display = html ?? prevHtml.current;\n\n if (!display) {\n return (\n <pre className=\"min-w-0 max-w-full overflow-x-auto\">\n <code>{children}</code>\n </pre>\n );\n }\n\n return (\n <div\n // biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki generates safe HTML for code highlighting\n dangerouslySetInnerHTML={{ __html: display }}\n style={{ backgroundColor: 'transparent' }}\n />\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA4BA,MAAa,sBAAsB,EACjC,UACA,cACA,cACA,kBACW;CACX,MAAM,CAAC,MAAM,WAAW,SAAwB,eAAe,IAAI;CACnE,MAAM,WAAW,OAAsB,eAAe,IAAI;CAE1D,gBAAgB;EAGd,IAAI,eAAe,iBAAiB,cAAc;GAChD,SAAS,UAAU;GACnB,QAAQ,WAAW;GACnB;EACF;EAEA,IAAI,YAAY;EAEhB,CAAC,YAAY;GACX,IAAI;IACF,IAAI,OAAO;IACX,IAAI,YAAY,oBAAoB,YAAY,CAAC,CAAC;IAGlD,IAAI,iBAAiB,cAAc;KACjC,MAAM,EAAE,eAAe,mBAAmB,MAAM,OAC9C;KAEF,IAAI,WAAW;KACf,OAAO,cAAc,OAAO,QAAQ,GAAG,YAAY;KACnD,YAAY,oBACV,eAAe,cAAc,YAAY,CAC3C,CAAC,CAAC;IACJ;IAEA,MAAM,EAAE,eAAe,MAAM,OAAO;IACpC,IAAI,WAAW;IAEf,MAAM,MAAM,MAAM,WAAW,OAAO,IAAI,GAAG;KACzC,MAAM;KACN,QAAQ;KACR,cAAc;IAChB,CAAC;IAED,IAAI,CAAC,WAAW;KACd,SAAS,UAAU;KACnB,QAAQ,GAAG;IACb;GACF,QAAQ;IAEN,IAAI,CAAC,WAAW,QAAQ,EAAE;GAC5B;EACF,EAAC,CAAE;EAEH,aAAa;GACX,YAAY;EACd;CACF,GAAG;EAAC;EAAU;EAAc;EAAc;CAAW,CAAC;CAItD,MAAM,UAAU,QAAQ,SAAS;CAEjC,IAAI,CAAC,SACH,OACE,oBAAC,OAAD;EAAK,WAAU;YACb,oBAAC,QAAD,EAAO,SAAe;CACnB;CAIT,OACE,oBAAC,OAAD;EAEE,yBAAyB,EAAE,QAAQ,QAAQ;EAC3C,OAAO,EAAE,iBAAiB,cAAc;CACzC;AAEL"}
@@ -1,14 +1,16 @@
1
1
  import { cn } from "../../utils/cn.mjs";
2
+ import { SHIKI_THEMES } from "./shikiThemes.mjs";
2
3
  import { Suspense } from "react";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  import { transformerMetaHighlight, transformerMetaWordHighlight, transformerNotationDiff, transformerNotationErrorLevel, transformerNotationHighlight, transformerNotationWordHighlight } from "@shikijs/transformers";
5
6
  import { codeToHtml } from "shiki/bundle/web";
6
7
 
7
8
  //#region src/components/IDE/CodeBlockServer.tsx
8
- const CodeBlockShiki = (async ({ children, lang, isDarkMode, onChange, ...props }) => {
9
+ const CodeBlockShiki = (async ({ children, lang, onChange, ...props }) => {
9
10
  const shikiOptions = {
10
11
  lang,
11
- theme: isDarkMode ? "github-dark" : "github-light",
12
+ themes: SHIKI_THEMES,
13
+ defaultColor: false,
12
14
  transformers: [
13
15
  transformerNotationDiff(),
14
16
  transformerNotationHighlight(),
@@ -25,7 +27,7 @@ const CodeBlockShiki = (async ({ children, lang, isDarkMode, onChange, ...props
25
27
  style: { backgroundColor: "transparent" }
26
28
  });
27
29
  });
28
- const CodeDefault = ({ children, isEditable, isDarkMode, onChange, ...props }) => /* @__PURE__ */ jsx("div", {
30
+ const CodeDefault = ({ children, isEditable, onChange, ...props }) => /* @__PURE__ */ jsx("div", {
29
31
  contentEditable: isEditable,
30
32
  ...props,
31
33
  children: /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: typeof children === "string" ? children.split("\n").map((line, index) => /* @__PURE__ */ jsx("span", {
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlockServer.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockServer.tsx"],"sourcesContent":["import {\n transformerMetaHighlight,\n transformerMetaWordHighlight,\n transformerNotationDiff,\n transformerNotationErrorLevel,\n transformerNotationHighlight,\n transformerNotationWordHighlight,\n} from '@shikijs/transformers';\nimport { cn } from '@utils/cn';\nimport { type FC, type HTMLAttributes, Suspense } from 'react';\nimport {\n type BundledLanguage,\n type BundledTheme,\n type CodeToHastOptions,\n codeToHtml,\n} from 'shiki/bundle/web';\n\nexport const CodeBlockShiki = (async ({\n children,\n lang,\n isDarkMode,\n onChange,\n ...props\n}: CodeBlockProps) => {\n const shikiOptions: CodeToHastOptions<BundledLanguage, BundledTheme> = {\n lang,\n theme: isDarkMode ? 'github-dark' : 'github-light',\n transformers: [\n transformerNotationDiff(),\n transformerNotationHighlight(),\n transformerNotationWordHighlight(),\n transformerNotationErrorLevel(),\n transformerMetaHighlight(),\n transformerMetaWordHighlight(),\n ],\n };\n\n const out = await codeToHtml(children, shikiOptions);\n\n return (\n <div\n dangerouslySetInnerHTML={{ __html: out }}\n {...props}\n style={{ backgroundColor: 'transparent' }}\n />\n );\n}) as unknown as FC<CodeBlockProps>;\n\nconst CodeDefault: FC<CodeBlockProps> = ({\n children,\n isEditable,\n isDarkMode,\n onChange,\n ...props\n}) => (\n <div contentEditable={isEditable} {...props}>\n <pre>\n <code>\n {typeof children === 'string'\n ? children.split('\\n').map((line, index) => (\n <span className=\"line block w-full\" key={index}>\n {line}\n </span>\n ))\n : children}\n </code>\n </pre>\n </div>\n);\n\nexport type CodeBlockProps = {\n children: string;\n lang: BundledLanguage;\n isDarkMode?: boolean;\n isEditable?: boolean;\n onChange?: (content: string) => void;\n} & Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>;\n\nexport const CodeBlock: FC<CodeBlockProps> = ({\n className,\n onChange,\n isEditable,\n ...props\n}) => (\n <Suspense fallback={<CodeDefault {...props} />}>\n <CodeBlockShiki\n className={cn('flex w-full', className)}\n contentEditable={isEditable}\n onInput={(e) => onChange?.(e.currentTarget.textContent ?? '')}\n {...props}\n />\n </Suspense>\n);\n"],"mappings":";;;;;;;AAiBA,MAAa,kBAAkB,OAAO,EACpC,UACA,MACA,YACA,UACA,GAAG,YACiB;CACpB,MAAM,eAAiE;EACrE;EACA,OAAO,aAAa,gBAAgB;EACpC,cAAc;GACZ,wBAAwB;GACxB,6BAA6B;GAC7B,iCAAiC;GACjC,8BAA8B;GAC9B,yBAAyB;GACzB,6BAA6B;EAC/B;CACF;CAEA,MAAM,MAAM,MAAM,WAAW,UAAU,YAAY;CAEnD,OACE,oBAAC,OAAD;EACE,yBAAyB,EAAE,QAAQ,IAAI;EACvC,GAAI;EACJ,OAAO,EAAE,iBAAiB,cAAc;CACzC;AAEL;AAEA,MAAM,eAAmC,EACvC,UACA,YACA,YACA,UACA,GAAG,YAEH,oBAAC,OAAD;CAAK,iBAAiB;CAAY,GAAI;WACpC,oBAAC,OAAD,YACE,oBAAC,QAAD,YACG,OAAO,aAAa,WACjB,SAAS,MAAM,IAAI,CAAC,CAAC,KAAK,MAAM,UAC9B,oBAAC,QAAD;EAAM,WAAU;YACb;CACG,GAFmC,KAEnC,CACP,IACD,SACA,GACH;AACF;AAWP,MAAa,aAAiC,EAC5C,WACA,UACA,YACA,GAAG,YAEH,oBAAC,UAAD;CAAU,UAAU,oBAAC,aAAD,EAAa,GAAI,MAAQ;WAC3C,oBAAC,gBAAD;EACE,WAAW,GAAG,eAAe,SAAS;EACtC,iBAAiB;EACjB,UAAU,MAAM,WAAW,EAAE,cAAc,eAAe,EAAE;EAC5D,GAAI;CACL;AACO"}
1
+ {"version":3,"file":"CodeBlockServer.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockServer.tsx"],"sourcesContent":["import {\n transformerMetaHighlight,\n transformerMetaWordHighlight,\n transformerNotationDiff,\n transformerNotationErrorLevel,\n transformerNotationHighlight,\n transformerNotationWordHighlight,\n} from '@shikijs/transformers';\nimport { cn } from '@utils/cn';\nimport { type FC, type HTMLAttributes, Suspense } from 'react';\nimport {\n type BundledLanguage,\n type BundledTheme,\n type CodeToHastOptions,\n codeToHtml,\n} from 'shiki/bundle/web';\nimport { SHIKI_THEMES } from './shikiThemes';\n\nexport const CodeBlockShiki = (async ({\n children,\n lang,\n onChange,\n ...props\n}: CodeBlockProps) => {\n const shikiOptions: CodeToHastOptions<BundledLanguage, BundledTheme> = {\n lang,\n themes: SHIKI_THEMES,\n // Both palettes ship as CSS variables, so one markup serves both themes.\n defaultColor: false,\n transformers: [\n transformerNotationDiff(),\n transformerNotationHighlight(),\n transformerNotationWordHighlight(),\n transformerNotationErrorLevel(),\n transformerMetaHighlight(),\n transformerMetaWordHighlight(),\n ],\n };\n\n const out = await codeToHtml(children, shikiOptions);\n\n return (\n <div\n dangerouslySetInnerHTML={{ __html: out }}\n {...props}\n style={{ backgroundColor: 'transparent' }}\n />\n );\n}) as unknown as FC<CodeBlockProps>;\n\nconst CodeDefault: FC<CodeBlockProps> = ({\n children,\n isEditable,\n onChange,\n ...props\n}) => (\n <div contentEditable={isEditable} {...props}>\n <pre>\n <code>\n {typeof children === 'string'\n ? children.split('\\n').map((line, index) => (\n <span className=\"line block w-full\" key={index}>\n {line}\n </span>\n ))\n : children}\n </code>\n </pre>\n </div>\n);\n\nexport type CodeBlockProps = {\n children: string;\n lang: BundledLanguage;\n isEditable?: boolean;\n onChange?: (content: string) => void;\n} & Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>;\n\nexport const CodeBlock: FC<CodeBlockProps> = ({\n className,\n onChange,\n isEditable,\n ...props\n}) => (\n <Suspense fallback={<CodeDefault {...props} />}>\n <CodeBlockShiki\n className={cn('flex w-full', className)}\n contentEditable={isEditable}\n onInput={(e) => onChange?.(e.currentTarget.textContent ?? '')}\n {...props}\n />\n </Suspense>\n);\n"],"mappings":";;;;;;;;AAkBA,MAAa,kBAAkB,OAAO,EACpC,UACA,MACA,UACA,GAAG,YACiB;CACpB,MAAM,eAAiE;EACrE;EACA,QAAQ;EAER,cAAc;EACd,cAAc;GACZ,wBAAwB;GACxB,6BAA6B;GAC7B,iCAAiC;GACjC,8BAA8B;GAC9B,yBAAyB;GACzB,6BAA6B;EAC/B;CACF;CAEA,MAAM,MAAM,MAAM,WAAW,UAAU,YAAY;CAEnD,OACE,oBAAC,OAAD;EACE,yBAAyB,EAAE,QAAQ,IAAI;EACvC,GAAI;EACJ,OAAO,EAAE,iBAAiB,cAAc;CACzC;AAEL;AAEA,MAAM,eAAmC,EACvC,UACA,YACA,UACA,GAAG,YAEH,oBAAC,OAAD;CAAK,iBAAiB;CAAY,GAAI;WACpC,oBAAC,OAAD,YACE,oBAAC,QAAD,YACG,OAAO,aAAa,WACjB,SAAS,MAAM,IAAI,CAAC,CAAC,KAAK,MAAM,UAC9B,oBAAC,QAAD;EAAM,WAAU;YACb;CACG,GAFmC,KAEnC,CACP,IACD,SACA,GACH;AACF;AAUP,MAAa,aAAiC,EAC5C,WACA,UACA,YACA,GAAG,YAEH,oBAAC,UAAD;CAAU,UAAU,oBAAC,aAAD,EAAa,GAAI,MAAQ;WAC3C,oBAAC,gBAAD;EACE,WAAW,GAAG,eAAe,SAAS;EACtC,iBAAiB;EACjB,UAAU,MAAM,WAAW,EAAE,cAAc,eAAe,EAAE;EAC5D,GAAI;CACL;AACO"}