@dxos/react-ui-editor 0.6.8-main.3be982f → 0.6.8-staging.63bcb81

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 (53) hide show
  1. package/dist/lib/browser/index.mjs +535 -521
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/TextEditor.stories.d.ts +5 -2
  5. package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/defaults.d.ts +2 -2
  7. package/dist/types/src/defaults.d.ts.map +1 -1
  8. package/dist/types/src/extensions/doc.d.ts +3 -0
  9. package/dist/types/src/extensions/doc.d.ts.map +1 -1
  10. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  11. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  12. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  13. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  14. package/dist/types/src/extensions/markdown/formatting.d.ts +1 -1
  15. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  16. package/dist/types/src/extensions/markdown/highlight.d.ts +2 -1
  17. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  18. package/dist/types/src/extensions/markdown/link-paste.d.ts +3 -0
  19. package/dist/types/src/extensions/markdown/link-paste.d.ts.map +1 -1
  20. package/dist/types/src/extensions/markdown/link.d.ts +3 -1
  21. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  22. package/dist/types/src/extensions/state.d.ts +14 -14
  23. package/dist/types/src/extensions/state.d.ts.map +1 -1
  24. package/dist/types/src/extensions/util/react.d.ts +1 -1
  25. package/dist/types/src/extensions/util/react.d.ts.map +1 -1
  26. package/dist/types/src/hooks/useTextEditor.d.ts +5 -3
  27. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  28. package/dist/types/src/index.d.ts +1 -0
  29. package/dist/types/src/index.d.ts.map +1 -1
  30. package/dist/types/src/styles/markdown.d.ts +7 -17
  31. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  32. package/dist/types/src/styles/theme.d.ts +3 -1
  33. package/dist/types/src/styles/theme.d.ts.map +1 -1
  34. package/dist/types/src/styles/tokens.d.ts +5 -7
  35. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  36. package/package.json +24 -24
  37. package/src/TextEditor.stories.tsx +40 -27
  38. package/src/defaults.ts +9 -2
  39. package/src/extensions/doc.ts +3 -0
  40. package/src/extensions/factories.ts +3 -2
  41. package/src/extensions/folding.tsx +5 -7
  42. package/src/extensions/markdown/bundle.ts +1 -3
  43. package/src/extensions/markdown/decorate.ts +31 -24
  44. package/src/extensions/markdown/formatting.ts +3 -1
  45. package/src/extensions/markdown/highlight.ts +33 -19
  46. package/src/extensions/markdown/link-paste.ts +3 -0
  47. package/src/extensions/state.ts +41 -35
  48. package/src/extensions/util/react.tsx +3 -4
  49. package/src/hooks/useTextEditor.ts +24 -29
  50. package/src/index.ts +2 -0
  51. package/src/styles/markdown.ts +17 -40
  52. package/src/styles/theme.ts +91 -86
  53. package/src/styles/tokens.ts +9 -7
@@ -7,7 +7,7 @@ import { HighlightStyle } from '@codemirror/language';
7
7
  import { tags, styleTags, Tag } from '@lezer/highlight';
8
8
  import { type MarkdownConfig, Table } from '@lezer/markdown';
9
9
 
10
- import { blockquote, bold, code, codeMark, getToken, heading, italic, mark, strikethrough } from '../../styles';
10
+ import { getToken, theme } from '../../styles';
11
11
 
12
12
  /**
13
13
  * Custom tags defined and processed by the GFM lezer extension.
@@ -49,6 +49,8 @@ export const markdownTagsExtensions: MarkdownConfig[] = [
49
49
  },
50
50
  ];
51
51
 
52
+ export type HighlightOptions = {};
53
+
52
54
  /**
53
55
  * Styling based on `lezer` parser tags.
54
56
  * https://codemirror.net/examples/styling
@@ -60,7 +62,7 @@ export const markdownTagsExtensions: MarkdownConfig[] = [
60
62
  * - https://github.com/codemirror/language/blob/main/src/highlight.ts#L194
61
63
  * - https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts#L115
62
64
  */
63
- export const markdownHighlightStyle = (readonly?: boolean) => {
65
+ export const markdownHighlightStyle = (_options: HighlightOptions = {}) => {
64
66
  return HighlightStyle.define(
65
67
  [
66
68
  {
@@ -99,6 +101,7 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
99
101
  tags.inserted,
100
102
  tags.invalid,
101
103
  ],
104
+ // TODO(burdon): Explain.
102
105
  color: 'inherit !important',
103
106
  },
104
107
 
@@ -111,38 +114,49 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
111
114
  markdownTags.LinkReference,
112
115
  markdownTags.ListMark,
113
116
  ],
114
- class: mark,
117
+ class: theme.mark,
115
118
  },
116
119
 
117
120
  // Markdown marks.
118
121
  {
119
- tag: [markdownTags.CodeMark, markdownTags.HeaderMark, markdownTags.QuoteMark, markdownTags.EmphasisMark],
120
- class: mark,
122
+ tag: [
123
+ //
124
+ markdownTags.CodeMark,
125
+ markdownTags.HeaderMark,
126
+ markdownTags.QuoteMark,
127
+ markdownTags.EmphasisMark,
128
+ ],
129
+ class: theme.mark,
121
130
  },
122
131
 
123
132
  // E.g., code block language (after ```).
124
133
  {
125
- tag: [tags.function(tags.variableName), tags.labelName],
126
- class: codeMark,
134
+ tag: [
135
+ //
136
+ tags.function(tags.variableName),
137
+ tags.labelName,
138
+ ],
139
+ class: theme.codeMark,
127
140
  },
128
141
 
142
+ // Fonts.
129
143
  {
130
144
  tag: [tags.monospace],
131
145
  class: 'font-mono',
132
146
  },
133
147
 
134
148
  // Headings.
135
- { tag: tags.heading1, class: heading(1) },
136
- { tag: tags.heading2, class: heading(2) },
137
- { tag: tags.heading3, class: heading(3) },
138
- { tag: tags.heading4, class: heading(4) },
139
- { tag: tags.heading5, class: heading(5) },
140
- { tag: tags.heading6, class: heading(6) },
149
+ { tag: tags.heading1, class: theme.heading(1) },
150
+ { tag: tags.heading2, class: theme.heading(2) },
151
+ { tag: tags.heading3, class: theme.heading(3) },
152
+ { tag: tags.heading4, class: theme.heading(4) },
153
+ { tag: tags.heading5, class: theme.heading(5) },
154
+ { tag: tags.heading6, class: theme.heading(6) },
141
155
 
142
156
  // Emphasis.
143
- { tag: tags.emphasis, class: italic },
144
- { tag: tags.strong, class: bold },
145
- { tag: tags.strikethrough, class: strikethrough },
157
+ { tag: tags.emphasis, class: 'italic' },
158
+ { tag: tags.strong, class: 'font-bold' },
159
+ { tag: tags.strikethrough, class: 'line-through' },
146
160
 
147
161
  // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
148
162
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
@@ -151,12 +165,12 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
151
165
  // IMPORTANT: Therefore, the fenced code block will use the base editor font unless changed by an extension.
152
166
  {
153
167
  tag: [markdownTags.CodeText, markdownTags.InlineCode],
154
- class: code,
168
+ class: theme.code,
155
169
  },
156
170
 
157
171
  {
158
172
  tag: [markdownTags.QuoteMark],
159
- class: blockquote,
173
+ class: theme.blockquote,
160
174
  },
161
175
 
162
176
  {
@@ -167,7 +181,7 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
167
181
  {
168
182
  scope: markdownLanguage,
169
183
  all: {
170
- fontFamily: getToken('fontFamily.body', []).join(','),
184
+ fontFamily: getToken('fontFamily.body'),
171
185
  },
172
186
  },
173
187
  );
@@ -7,6 +7,9 @@ import { type EditorState, Transaction } from '@codemirror/state';
7
7
  import { ViewPlugin, type ViewUpdate, type PluginValue } from '@codemirror/view';
8
8
  import { type SyntaxNode } from '@lezer/common';
9
9
 
10
+ /**
11
+ * Formats pasted URLs as markdown links and images.
12
+ */
10
13
  export const linkPastePlugin = ViewPlugin.fromClass(
11
14
  class implements PluginValue {
12
15
  update(update: ViewUpdate) {
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Extension, Transaction } from '@codemirror/state';
5
+ import { type Extension, Transaction, type TransactionSpec } from '@codemirror/state';
6
6
  import { EditorView, keymap } from '@codemirror/view';
7
7
 
8
8
  import { debounce } from '@dxos/async';
@@ -11,76 +11,82 @@ import { isNotFalsy } from '@dxos/util';
11
11
 
12
12
  import { documentId } from './doc';
13
13
 
14
- const scrollAnnotation = 'dxos.org/cm/scrolling';
14
+ const stateRestoreAnnotation = 'dxos.org/cm/state-restore';
15
15
 
16
- // NOTE: Serializable.
17
- export type SelectionState = {
18
- scrollTo: {
19
- from: number;
20
- };
21
- selection: {
22
- anchor: number;
23
- head?: number;
24
- };
16
+ export type EditorSelection = {
17
+ anchor: number;
18
+ head?: number;
19
+ };
20
+
21
+ export type EditorSelectionState = {
22
+ scrollTo?: number;
23
+ selection?: EditorSelection;
25
24
  };
26
25
 
27
- export type StateOptions = {
28
- setState: (id: string, state: SelectionState) => void;
29
- getState: (id: string) => SelectionState | undefined;
26
+ export type EditorStateOptions = {
27
+ setState: (id: string, state: EditorSelectionState) => void;
28
+ getState: (id: string) => EditorSelectionState | undefined;
30
29
  };
31
30
 
32
31
  const keyPrefix = 'dxos.org/react-ui-editor/state';
33
- export const localStorageStateStoreAdapter: StateOptions = {
34
- setState: (id, state) => {
35
- invariant(id);
36
- localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state));
37
- },
32
+ export const localStorageStateStoreAdapter: EditorStateOptions = {
38
33
  getState: (id) => {
39
34
  invariant(id);
40
35
  const state = localStorage.getItem(`${keyPrefix}/${id}`);
41
36
  return state ? JSON.parse(state) : undefined;
42
37
  },
38
+
39
+ setState: (id, state) => {
40
+ invariant(id);
41
+ localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state));
42
+ },
43
+ };
44
+
45
+ export const createEditorStateTransaction = ({ scrollTo, selection }: EditorSelectionState): TransactionSpec => {
46
+ return {
47
+ selection,
48
+ scrollIntoView: !scrollTo,
49
+ effects: scrollTo ? EditorView.scrollIntoView(scrollTo, { yMargin: 96 }) : undefined,
50
+ annotations: Transaction.userEvent.of(stateRestoreAnnotation),
51
+ };
43
52
  };
44
53
 
45
54
  /**
46
55
  * Track scrolling and selection state to be restored when switching to document.
47
56
  */
48
- export const state = ({ getState, setState }: Partial<StateOptions> = {}): Extension => {
57
+ export const state = ({ getState, setState }: Partial<EditorStateOptions> = {}): Extension => {
49
58
  const setStateDebounced = debounce(setState!, 1_000);
50
59
 
51
60
  return [
52
61
  // TODO(burdon): Track scrolling (currently only updates when cursor moves).
53
- EditorView.updateListener.of(({ view, changes, transactions }) => {
54
- // TODO(burdon): Don't react to initial scroll.
62
+ // EditorView.domEventHandlers({
63
+ // scroll: (event) => {
64
+ // setStateDebounced(id, {});
65
+ // },
66
+ // }),
67
+ EditorView.updateListener.of(({ view, transactions }) => {
55
68
  const id = view.state.facet(documentId);
56
- if (!id || transactions.some((tr) => tr.isUserEvent(scrollAnnotation))) {
69
+ if (!id || transactions.some((tr) => tr.isUserEvent(stateRestoreAnnotation))) {
57
70
  return;
58
71
  }
59
72
 
60
73
  if (setState) {
61
- const { top } = view.dom.getBoundingClientRect();
62
- const pos = view.posAtCoords({ x: 0, y: top });
74
+ const { scrollTop } = view.scrollDOM;
75
+ const pos = view.posAtCoords({ x: 0, y: scrollTop });
63
76
  if (pos !== null) {
64
77
  const { anchor, head } = view.state.selection.main;
65
- setStateDebounced(id, {
66
- scrollTo: { from: pos, yMargin: 0 },
67
- selection: { anchor, head },
68
- });
78
+ setStateDebounced(id, { scrollTo: pos, selection: { anchor, head } });
69
79
  }
70
80
  }
71
81
  }),
72
82
  getState &&
73
83
  keymap.of([
74
84
  {
75
- key: 'ctrl-r', // TODO(burdon): Setting to jump back to bookmark.
85
+ key: 'ctrl-r', // TODO(burdon): Setting to jump back to selection.
76
86
  run: (view) => {
77
87
  const state = getState(view.state.facet(documentId));
78
88
  if (state) {
79
- view.dispatch({
80
- effects: EditorView.scrollIntoView(state.scrollTo.from, { yMargin: 0 }),
81
- selection: state.selection,
82
- annotations: Transaction.userEvent.of(scrollAnnotation),
83
- });
89
+ view.dispatch(createEditorStateTransaction(state));
84
90
  }
85
91
  return true;
86
92
  },
@@ -8,8 +8,7 @@ import { createRoot } from 'react-dom/client';
8
8
  import { ThemeProvider } from '@dxos/react-ui';
9
9
  import { defaultTx } from '@dxos/react-ui-theme';
10
10
 
11
- export const renderRoot = (node: ReactNode) => {
12
- const el = document.createElement('div');
13
- createRoot(el).render(<ThemeProvider tx={defaultTx}>{node}</ThemeProvider>);
14
- return el;
11
+ export const renderRoot = (root: HTMLElement, node: ReactNode): HTMLElement => {
12
+ createRoot(root).render(<ThemeProvider tx={defaultTx}>{node}</ThemeProvider>);
13
+ return root;
15
14
  };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { EditorState, type EditorStateConfig, type StateEffect } from '@codemirror/state';
5
+ import { EditorState, type EditorStateConfig } from '@codemirror/state';
6
6
  import { EditorView } from '@codemirror/view';
7
7
  import { useFocusableGroup } from '@fluentui/react-tabster';
8
8
  import {
@@ -17,10 +17,9 @@ import {
17
17
  } from 'react';
18
18
 
19
19
  import { log } from '@dxos/log';
20
- import { useDefaultValue } from '@dxos/react-ui';
21
20
  import { isNotFalsy, type MaybeFunction } from '@dxos/util';
22
21
 
23
- import { documentId, editorInputMode } from '../extensions';
22
+ import { createEditorStateTransaction, documentId, editorInputMode, type EditorSelection } from '../extensions';
24
23
  import { logChanges } from '../util';
25
24
 
26
25
  export type UseTextEditor = {
@@ -41,12 +40,13 @@ export type CursorInfo = {
41
40
  after?: string;
42
41
  };
43
42
 
44
- export type UseTextEditorProps = Pick<EditorStateConfig, 'selection' | 'extensions'> & {
43
+ export type UseTextEditorProps = Pick<EditorStateConfig, 'extensions'> & {
45
44
  id?: string;
46
45
  initialValue?: string;
47
46
  className?: string;
48
47
  autoFocus?: boolean;
49
- scrollTo?: StateEffect<unknown>;
48
+ scrollTo?: number;
49
+ selection?: EditorSelection;
50
50
  moveToEndOfLine?: boolean;
51
51
  debug?: boolean;
52
52
  };
@@ -60,22 +60,14 @@ export const useTextEditor = (
60
60
  props: MaybeFunction<UseTextEditorProps> = {},
61
61
  deps: DependencyList = [],
62
62
  ): UseTextEditor => {
63
- const {
64
- id,
65
- initialValue,
66
- selection,
67
- extensions,
68
- autoFocus,
69
- scrollTo: _scrollTo,
70
- moveToEndOfLine,
71
- debug,
72
- } = useMemo<UseTextEditorProps>(() => {
73
- return typeof props === 'function' ? props() : props;
74
- }, deps ?? []);
63
+ const { id, initialValue, extensions, autoFocus, scrollTo, selection, moveToEndOfLine, debug } =
64
+ useMemo<UseTextEditorProps>(() => {
65
+ return typeof props === 'function' ? props() : props;
66
+ }, deps ?? []);
75
67
 
76
68
  // NOTE: Increments by 2 in strict mode.
77
69
  const [instanceId] = useState(() => `text-editor-${++instanceCount}`);
78
- const scrollTo = useDefaultValue(_scrollTo, EditorView.scrollIntoView(0, { yMargin: 0 }));
70
+ // Callback once view is created.
79
71
  const onUpdate = useRef<() => void>();
80
72
  const [view, setView] = useState<EditorView>();
81
73
  const parentRef = useRef<HTMLDivElement>(null);
@@ -85,8 +77,12 @@ export const useTextEditor = (
85
77
  if (parentRef.current) {
86
78
  log('create', { id, instanceId, doc: initialValue?.length ?? 0 });
87
79
 
88
- let initialSelection = selection;
89
- if (moveToEndOfLine && selection === undefined) {
80
+ let initialSelection;
81
+ if (selection?.anchor && initialValue?.length) {
82
+ if (selection.anchor <= initialValue.length && (selection?.head ?? 0) <= initialValue.length) {
83
+ initialSelection = selection;
84
+ }
85
+ } else if (moveToEndOfLine && selection === undefined) {
90
86
  const index = initialValue?.indexOf('\n');
91
87
  const anchor = !index || index === -1 ? 0 : index;
92
88
  initialSelection = { anchor };
@@ -105,7 +101,9 @@ export const useTextEditor = (
105
101
  }),
106
102
  extensions,
107
103
  EditorView.updateListener.of(() => {
108
- onUpdate.current?.();
104
+ setTimeout(() => {
105
+ onUpdate.current?.();
106
+ });
109
107
  }),
110
108
  ].filter(isNotFalsy),
111
109
  });
@@ -113,7 +111,6 @@ export const useTextEditor = (
113
111
  // https://codemirror.net/docs/ref/#view.EditorViewConfig
114
112
  view = new EditorView({
115
113
  parent: parentRef.current,
116
- scrollTo,
117
114
  selection: initialSelection,
118
115
  state,
119
116
  // NOTE: Uncomment to debug/monitor all transactions.
@@ -143,13 +140,11 @@ export const useTextEditor = (
143
140
 
144
141
  useEffect(() => {
145
142
  if (view) {
146
- // TODO(burdon): Set selection after first update (since content may rerender on focus)?
147
- if (scrollTo) {
148
- onUpdate.current = () => {
149
- onUpdate.current = undefined;
150
- view.dispatch({ effects: scrollTo && [scrollTo], scrollIntoView: !scrollTo });
151
- };
152
- }
143
+ // NOTE: Set selection after first update (since content may rerender on focus).
144
+ onUpdate.current = () => {
145
+ onUpdate.current = undefined;
146
+ view.dispatch(createEditorStateTransaction({ scrollTo, selection }));
147
+ };
153
148
 
154
149
  // Remove tabster attribute (rely on custom keymap).
155
150
  if (view.state.facet(editorInputMode).noTabster) {
package/src/index.ts CHANGED
@@ -10,6 +10,8 @@ export { tags } from '@lezer/highlight';
10
10
 
11
11
  export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
12
12
 
13
+ export { getToken } from './styles';
14
+
13
15
  export * from './components';
14
16
  export * from './defaults';
15
17
  export * from './extensions';
@@ -6,48 +6,25 @@ import { mx } from '@dxos/react-ui-theme';
6
6
 
7
7
  export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
8
8
 
9
- // TODO(burdon): Better way to align vertically than negative margin? Font-specific?
10
9
  // https://tailwindcss.com/docs/font-weight
11
10
  const headings: Record<HeadingLevel, string> = {
12
- 1: 'mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl',
13
- 2: 'mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl',
14
- 3: 'mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl',
15
- 4: 'mbs-4 mbe-2 font-medium text-inherit no-underline text-xl',
16
- 5: 'mbs-4 mbe-2 font-medium text-inherit no-underline text-lg',
17
- 6: 'mbs-4 mbe-2 font-medium text-inherit no-underline',
11
+ 1: 'text-4xl',
12
+ 2: 'text-3xl',
13
+ 3: 'text-2xl',
14
+ 4: 'text-xl',
15
+ 5: 'text-lg',
16
+ 6: 'text-md',
18
17
  };
19
18
 
20
- // TODO(burdon): Themes.
21
- export const heading = (level: HeadingLevel) => {
22
- return mx(headings[level], 'dark:text-primary-400');
19
+ // TODO(burdon): Define theme as facet (used in multiple extensions).
20
+ // TODO(burdon): Organize theme styles for widgets.
21
+ export const theme = {
22
+ mark: 'opacity-50',
23
+ code: 'font-mono !no-underline text-neutral-700 dark:text-neutral-300',
24
+ codeMark: 'font-mono text-primary-500',
25
+ // TODO(burdon): Replace with widget.
26
+ blockquote: 'pl-1 mr-1 border-is-4 border-orange-500 dark:border-orange-500 dark:text-neutral-500',
27
+ heading: (level: HeadingLevel) => {
28
+ return mx(headings[level], 'dark:text-primary-400');
29
+ },
23
30
  };
24
-
25
- export const text = 'text-neutral-800 dark:text-neutral-200';
26
- export const light = 'text-neutral-200 dark:text-neutral-800';
27
-
28
- export const mark = mx('!font-normal !no-underline !text-inherit opacity-40', light);
29
-
30
- export const paragraph = 'mlb-1';
31
-
32
- export const bold = 'font-bold';
33
- export const italic = 'italic';
34
- export const strikethrough = 'line-through';
35
-
36
- export const code = 'font-mono !no-underline text-neutral-700 dark:text-neutral-300';
37
- export const codeMark = 'font-mono text-primary-500';
38
- export const codeBlock = 'mlb-2 font-mono bg-neutral-500/10 p-3 rounded';
39
-
40
- export const inlineUrl = mx(code, 'px-1');
41
-
42
- export const blockquote = mx('pl-1 mr-1 border-is-4 border-orange-500 dark:border-orange-500 text-transparent');
43
-
44
- export const horizontalRule =
45
- 'flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800';
46
-
47
- // TODO(thure): Tailwind was not seeing `[&>li:before]:content-["•"]` as a utility class, but it would work if instead of `"•"` it was `"X"`… why?
48
- export const unorderedList =
49
- 'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[attr(marker)] [&>li:before]:mlb-1 [&>li:before]:mie-2';
50
- export const orderedList =
51
- 'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[counters(section,_".")_"._"] [counter-reset:section] [&>li:before]:mlb-1';
52
-
53
- export const listItem = 'contents before:[counter-increment:section]';