@monimesl/dot-renderer-react 0.1.0-alpha.16 → 0.1.0-alpha.19

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.
@@ -0,0 +1,11 @@
1
+ //#region src/components/https.ts
2
+ function isHttps(href) {
3
+ if (!href) return false;
4
+ try {
5
+ return new URL(href).protocol === "https:";
6
+ } catch {
7
+ return false;
8
+ }
9
+ }
10
+ //#endregion
11
+ export { isHttps };
@@ -15,11 +15,14 @@ import { Input } from "./input.js";
15
15
  import { Link } from "./link.js";
16
16
  import { List, ListItem } from "./list.js";
17
17
  import { Option } from "./option.js";
18
+ import { Bullet, BulletList, Prose, Quote } from "./prose.js";
18
19
  import { QRCode } from "./qr-code.js";
20
+ import { RichText } from "./rich-text.js";
19
21
  import { Scroll } from "./scroll.js";
20
22
  import { Select } from "./select.js";
21
23
  import { Sheet } from "./sheet.js";
22
24
  import { Skeleton } from "./skeleton.js";
25
+ import { Slider } from "./slider.js";
23
26
  import { Spinner } from "./spinner.js";
24
27
  import { Stack } from "./stack.js";
25
28
  import { Tab, Tabs } from "./tabs.js";
@@ -28,4 +31,4 @@ import { TextArea } from "./text-area.js";
28
31
  import { Toast } from "./toast.js";
29
32
  import { Checkbox, Radio, RadioGroup, Switch } from "./toggles.js";
30
33
  import { View } from "./view.js";
31
- export { AmountInput, Badge, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, QRCode, Radio, RadioGroup, Scroll, Select, Sheet, Skeleton, Spinner, Stack, Switch, Tab, Tabs, Text, TextArea, Toast, View };
34
+ export { AmountInput, Badge, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Switch, Tab, Tabs, Text, TextArea, Toast, View };
@@ -17,14 +17,17 @@ import { Input } from "./input.js";
17
17
  import { Link } from "./link.js";
18
18
  import { List, ListItem } from "./list.js";
19
19
  import { Option } from "./option.js";
20
+ import { Bullet, BulletList, Prose, Quote } from "./prose.js";
20
21
  import { QRCode } from "./qr-code.js";
22
+ import { Text } from "./text.js";
23
+ import { RichText } from "./rich-text.js";
21
24
  import { Scroll } from "./scroll.js";
22
25
  import { Sheet } from "./sheet.js";
23
26
  import { Select } from "./select.js";
24
27
  import { Skeleton } from "./skeleton.js";
28
+ import { Slider } from "./slider.js";
25
29
  import { Stack } from "./stack.js";
26
30
  import { Tab, Tabs } from "./tabs.js";
27
- import { Text } from "./text.js";
28
31
  import { TextArea } from "./text-area.js";
29
32
  import { Toast } from "./toast.js";
30
33
  import { Checkbox, Radio, RadioGroup, Switch } from "./toggles.js";
@@ -33,6 +36,8 @@ import { View } from "./view.js";
33
36
  var components_exports = /* @__PURE__ */ __exportAll({
34
37
  AmountInput: () => AmountInput,
35
38
  Badge: () => Badge,
39
+ Bullet: () => Bullet,
40
+ BulletList: () => BulletList,
36
41
  Button: () => Button,
37
42
  Carousel: () => Carousel,
38
43
  Checkbox: () => Checkbox,
@@ -53,13 +58,17 @@ var components_exports = /* @__PURE__ */ __exportAll({
53
58
  Notice: () => Notice,
54
59
  Option: () => Option,
55
60
  ProgressBar: () => ProgressBar,
61
+ Prose: () => Prose,
56
62
  QRCode: () => QRCode,
63
+ Quote: () => Quote,
57
64
  Radio: () => Radio,
58
65
  RadioGroup: () => RadioGroup,
66
+ RichText: () => RichText,
59
67
  Scroll: () => Scroll,
60
68
  Select: () => Select,
61
69
  Sheet: () => Sheet,
62
70
  Skeleton: () => Skeleton,
71
+ Slider: () => Slider,
63
72
  Spinner: () => Spinner,
64
73
  Stack: () => Stack,
65
74
  Switch: () => Switch,
@@ -71,4 +80,4 @@ var components_exports = /* @__PURE__ */ __exportAll({
71
80
  View: () => View
72
81
  });
73
82
  //#endregion
74
- export { AmountInput, Badge, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, QRCode, Radio, RadioGroup, Scroll, Select, Sheet, Skeleton, Spinner, Stack, Switch, Tab, Tabs, Text, TextArea, Toast, View, components_exports };
83
+ export { AmountInput, Badge, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Switch, Tab, Tabs, Text, TextArea, Toast, View, components_exports };
@@ -1,17 +1,10 @@
1
1
  import { useTheme } from "../theme.js";
2
2
  import { toneColor } from "./tones.js";
3
3
  import { pressable } from "./pressable.js";
4
+ import { isHttps } from "./https.js";
4
5
  import { createEvent } from "@monimesl/dot-core";
5
6
  import { jsx } from "react/jsx-runtime";
6
7
  //#region src/components/link.tsx
7
- function isHttps(href) {
8
- if (!href) return false;
9
- try {
10
- return new URL(href).protocol === "https:";
11
- } catch {
12
- return false;
13
- }
14
- }
15
8
  const Link$1 = ({ href, tone, onTap, children }) => {
16
9
  const { colors } = useTheme();
17
10
  const style = {
@@ -0,0 +1,5 @@
1
+ import { createContext } from "react";
2
+ //#region src/components/prose-context.ts
3
+ const InsideProse = createContext(false);
4
+ //#endregion
5
+ export { InsideProse };
@@ -0,0 +1,9 @@
1
+ import { ReactComponent } from "../types.js";
2
+ import { Bullet, BulletList, Prose, Quote } from "@monimesl/dot-core";
3
+ //#region src/components/prose.d.ts
4
+ declare const Prose$1: ReactComponent<typeof Prose>;
5
+ declare const Quote$1: ReactComponent<typeof Quote>;
6
+ declare const BulletList$1: ReactComponent<typeof BulletList>;
7
+ declare const Bullet$1: ReactComponent<typeof Bullet>;
8
+ //#endregion
9
+ export { Bullet$1 as Bullet, BulletList$1 as BulletList, Prose$1 as Prose, Quote$1 as Quote };
@@ -0,0 +1,67 @@
1
+ import { useTheme } from "../theme.js";
2
+ import { FONT_SIZE, SELECTABLE } from "./tokens.js";
3
+ import { paddingStyle } from "./padding.js";
4
+ import { InsideProse } from "./prose-context.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/components/prose.tsx
7
+ const READING_WIDTH = 680;
8
+ const Prose = ({ children, ...padding }) => /* @__PURE__ */ jsx("div", {
9
+ className: "dot-prose",
10
+ style: {
11
+ display: "flex",
12
+ flexDirection: "column",
13
+ gap: 12,
14
+ width: "100%",
15
+ maxWidth: READING_WIDTH,
16
+ marginInline: "auto",
17
+ ...SELECTABLE,
18
+ ...paddingStyle(padding)
19
+ },
20
+ children: /* @__PURE__ */ jsx(InsideProse.Provider, {
21
+ value: true,
22
+ children
23
+ })
24
+ });
25
+ const Quote = ({ cite, children }) => {
26
+ const { colors } = useTheme();
27
+ return /* @__PURE__ */ jsxs("blockquote", {
28
+ className: "dot-quote",
29
+ style: {
30
+ display: "flex",
31
+ flexDirection: "column",
32
+ gap: 6,
33
+ margin: 0,
34
+ paddingInlineStart: 14,
35
+ borderInlineStart: `3px solid ${colors.subtle}`,
36
+ fontStyle: "italic"
37
+ },
38
+ children: [children, cite && /* @__PURE__ */ jsxs("span", {
39
+ style: {
40
+ fontSize: FONT_SIZE.caption,
41
+ fontStyle: "normal",
42
+ color: colors.subtle
43
+ },
44
+ children: ["— ", cite]
45
+ })]
46
+ });
47
+ };
48
+ const BulletList = ({ ordered, children }) => {
49
+ return /* @__PURE__ */ jsx(ordered ? "ol" : "ul", {
50
+ className: "dot-bullets",
51
+ style: {
52
+ display: "grid",
53
+ gap: 6,
54
+ margin: 0,
55
+ paddingInlineStart: 22,
56
+ listStyleType: ordered ? "decimal" : "disc"
57
+ },
58
+ children
59
+ });
60
+ };
61
+ const Bullet = ({ children }) => /* @__PURE__ */ jsx("li", {
62
+ className: "dot-bullet",
63
+ style: { display: "list-item" },
64
+ children
65
+ });
66
+ //#endregion
67
+ export { Bullet, BulletList, Prose, Quote };
@@ -0,0 +1,6 @@
1
+ import { ReactComponent } from "../types.js";
2
+ import { RichText } from "@monimesl/dot-core";
3
+ //#region src/components/rich-text.d.ts
4
+ declare const RichText$1: ReactComponent<typeof RichText>;
5
+ //#endregion
6
+ export { RichText$1 as RichText };
@@ -0,0 +1,73 @@
1
+ import { useTheme } from "../theme.js";
2
+ import { Divider } from "./divider.js";
3
+ import { Image } from "./image.js";
4
+ import { isHttps } from "./https.js";
5
+ import { Link } from "./link.js";
6
+ import { Bullet, BulletList, Prose, Quote } from "./prose.js";
7
+ import { parseMarkdown } from "../markdown.js";
8
+ import { Text } from "./text.js";
9
+ import { useMemo } from "react";
10
+ import { jsx } from "react/jsx-runtime";
11
+ //#region src/components/rich-text.tsx
12
+ const HEADING_TEXT = {
13
+ 1: { variant: "title" },
14
+ 2: { variant: "subtitle" },
15
+ 3: { weight: "bold" }
16
+ };
17
+ const RichText = ({ markdown }) => {
18
+ const blocks = useMemo(() => parseMarkdown(markdown ?? ""), [markdown]);
19
+ return /* @__PURE__ */ jsx(Prose, { children: blocks.map(renderBlock) });
20
+ };
21
+ function renderBlock(block, key) {
22
+ switch (block.kind) {
23
+ case "heading": return /* @__PURE__ */ jsx(Text, {
24
+ ...HEADING_TEXT[block.level],
25
+ children: block.content.map(renderInline)
26
+ }, key);
27
+ case "paragraph": return /* @__PURE__ */ jsx(Text, { children: block.content.map(renderInline) }, key);
28
+ case "list": return /* @__PURE__ */ jsx(BulletList, {
29
+ ordered: block.ordered,
30
+ children: block.items.map((item, itemKey) => /* @__PURE__ */ jsx(Bullet, { children: item.map(renderBlock) }, itemKey))
31
+ }, key);
32
+ case "quote": return /* @__PURE__ */ jsx(Quote, { children: block.blocks.map(renderBlock) }, key);
33
+ case "image": return /* @__PURE__ */ jsx(Image, {
34
+ src: block.src,
35
+ alt: block.alt
36
+ }, key);
37
+ case "rule": return /* @__PURE__ */ jsx(Divider, {}, key);
38
+ }
39
+ }
40
+ function renderInline(inline, key) {
41
+ switch (inline.kind) {
42
+ case "text": return inline.text;
43
+ case "strong": return /* @__PURE__ */ jsx(Text, {
44
+ weight: "bold",
45
+ children: inline.children.map(renderInline)
46
+ }, key);
47
+ case "emphasis": return /* @__PURE__ */ jsx(Text, {
48
+ italic: true,
49
+ children: inline.children.map(renderInline)
50
+ }, key);
51
+ case "code": return /* @__PURE__ */ jsx(Code, { text: inline.text }, key);
52
+ case "link": return isHttps(inline.href) ? /* @__PURE__ */ jsx(Link, {
53
+ href: inline.href,
54
+ children: inline.children.map(renderInline)
55
+ }, key) : inline.children.map(renderInline);
56
+ case "break": return /* @__PURE__ */ jsx("br", {}, key);
57
+ }
58
+ }
59
+ function Code({ text }) {
60
+ const { colors } = useTheme();
61
+ return /* @__PURE__ */ jsx("code", {
62
+ style: {
63
+ padding: "1px 4px",
64
+ borderRadius: 4,
65
+ background: colors.muted,
66
+ fontFamily: "ui-monospace, monospace",
67
+ fontSize: "0.9em"
68
+ },
69
+ children: text
70
+ });
71
+ }
72
+ //#endregion
73
+ export { RichText };
@@ -0,0 +1,6 @@
1
+ import { ReactComponent } from "../types.js";
2
+ import { Slider } from "@monimesl/dot-core";
3
+ //#region src/components/slider.d.ts
4
+ declare const Slider$1: ReactComponent<typeof Slider>;
5
+ //#endregion
6
+ export { Slider$1 as Slider };
@@ -0,0 +1,57 @@
1
+ import { useTheme } from "../theme.js";
2
+ import { DISABLED_OPACITY, FONT_SIZE } from "./tokens.js";
3
+ import { Field, FieldLabel } from "./field.js";
4
+ import { useFieldNote } from "./field-box.js";
5
+ import { useTypedText } from "./typed-text.js";
6
+ import { createEvent } from "@monimesl/dot-core";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/components/slider.tsx
9
+ const Slider$1 = ({ value, min = 0, max = 100, step = 1, showValue, label, hint, disabled, onChange }) => {
10
+ const { colors } = useTheme();
11
+ const [position, move] = useTypedText(value === void 0 ? void 0 : String(value), onChange !== void 0);
12
+ const { noteId, controlAttributes } = useFieldNote(hint, void 0);
13
+ const shown = position === "" ? String(min) : position;
14
+ return /* @__PURE__ */ jsxs(Field, {
15
+ label: void 0,
16
+ hint,
17
+ error: void 0,
18
+ noteId,
19
+ children: [label || showValue ? /* @__PURE__ */ jsxs("span", {
20
+ style: {
21
+ display: "flex",
22
+ justifyContent: "space-between"
23
+ },
24
+ children: [/* @__PURE__ */ jsx(FieldLabel, { children: label }), showValue ? /* @__PURE__ */ jsx("span", {
25
+ style: {
26
+ fontSize: FONT_SIZE.caption,
27
+ color: colors.subtle,
28
+ fontVariantNumeric: "tabular-nums"
29
+ },
30
+ children: shown
31
+ }) : null]
32
+ }) : null, /* @__PURE__ */ jsx("input", {
33
+ className: "dot-slider",
34
+ type: "range",
35
+ min,
36
+ max,
37
+ step,
38
+ value: shown,
39
+ disabled,
40
+ "aria-label": label,
41
+ ...controlAttributes,
42
+ onChange: (event) => {
43
+ const next = event.currentTarget.value;
44
+ move(next);
45
+ onChange?.(createEvent("change", { value: Number(next) }));
46
+ },
47
+ style: {
48
+ width: "100%",
49
+ margin: 0,
50
+ accentColor: colors.primary,
51
+ opacity: disabled ? DISABLED_OPACITY : 1
52
+ }
53
+ })]
54
+ });
55
+ };
56
+ //#endregion
57
+ export { Slider$1 as Slider };
@@ -1,41 +1,83 @@
1
1
  import { useTheme } from "../theme.js";
2
- import { FONT_SIZE, WEIGHTS } from "./tokens.js";
2
+ import { FONT_SIZE, SELECTABLE, WEIGHTS } from "./tokens.js";
3
3
  import { paddingStyle } from "./padding.js";
4
4
  import { toneColor } from "./tones.js";
5
+ import { InsideProse } from "./prose-context.js";
5
6
  import { customColor } from "./box.js";
6
7
  import { TONES } from "@monimesl/dot-core";
7
8
  import { createContext, useContext } from "react";
8
9
  import { jsx } from "react/jsx-runtime";
9
10
  //#region src/components/text.tsx
10
11
  const InsideText = createContext(false);
11
- const DEFAULT_WEIGHT = {
12
- display: "bold",
13
- title: "bold",
14
- subtitle: "medium",
15
- body: "regular",
16
- caption: "regular"
12
+ const VARIANTS = {
13
+ display: {
14
+ weight: "bold",
15
+ lineHeight: 1.15,
16
+ heading: true
17
+ },
18
+ title: {
19
+ weight: "bold",
20
+ lineHeight: 1.25,
21
+ heading: true
22
+ },
23
+ subtitle: {
24
+ weight: "medium",
25
+ lineHeight: 1.35,
26
+ heading: true
27
+ },
28
+ lead: {
29
+ weight: "regular",
30
+ lineHeight: 1.5,
31
+ heading: false
32
+ },
33
+ body: {
34
+ weight: "regular",
35
+ lineHeight: 1.5,
36
+ heading: false
37
+ },
38
+ caption: {
39
+ weight: "regular",
40
+ lineHeight: 1.4,
41
+ heading: false
42
+ }
43
+ };
44
+ const LEADING_CHANGE = {
45
+ tight: -.1,
46
+ normal: 0,
47
+ relaxed: .2
17
48
  };
18
49
  const DECORATION = {
19
50
  none: "none",
20
51
  underline: "underline",
21
52
  strikethrough: "line-through"
22
53
  };
23
- const Text$1 = ({ variant = "body", tone, weight, align, maxLines, decoration, numeric, children, ...padding }) => {
54
+ const Text$1 = ({ variant = "body", tone, weight, align, maxLines, decoration, numeric, leading, italic, selectable, children, ...padding }) => {
24
55
  const theme = useTheme();
25
56
  const nested = useContext(InsideText);
57
+ const inProse = useContext(InsideProse);
58
+ const { heading, lineHeight, weight: defaultWeight } = VARIANTS[variant];
59
+ const defaultLeading = inProse && !heading ? "relaxed" : "normal";
26
60
  const style = {
27
61
  display: nested ? "inline" : "block",
28
- margin: 0,
62
+ marginInline: 0,
63
+ marginBlockEnd: 0,
64
+ marginBlockStart: inProse && heading ? void 0 : 0,
29
65
  fontSize: FONT_SIZE[variant],
30
- lineHeight: "normal",
31
- fontWeight: WEIGHTS[weight ?? DEFAULT_WEIGHT[variant]],
32
- letterSpacing: "normal",
66
+ fontWeight: WEIGHTS[weight ?? defaultWeight],
67
+ fontStyle: italic ? "italic" : void 0,
68
+ letterSpacing: variant === "display" ? "-0.02em" : "normal",
33
69
  color: tone ? textColor(tone, theme) : "inherit",
34
70
  textAlign: align,
35
71
  textDecorationLine: decoration ? DECORATION[decoration] : void 0,
36
72
  fontVariantNumeric: numeric ? "tabular-nums" : void 0,
73
+ ...selectable ? SELECTABLE : void 0,
37
74
  ...paddingStyle(padding)
38
75
  };
76
+ if (!nested) {
77
+ style.lineHeight = lineHeight + LEADING_CHANGE[leading ?? defaultLeading];
78
+ style.textWrap = heading ? "balance" : "pretty";
79
+ style.overflowWrap = "anywhere";
80
+ }
39
81
  if (maxLines) {
40
82
  style.display = "-webkit-box";
41
83
  style.WebkitLineClamp = maxLines;
@@ -46,8 +88,8 @@ const Text$1 = ({ variant = "body", tone, weight, align, maxLines, decoration, n
46
88
  value: true,
47
89
  children
48
90
  });
49
- if (variant === "title" && !nested) return /* @__PURE__ */ jsx("h2", {
50
- className: "dot-text",
91
+ if ((variant === "title" || variant === "subtitle") && !nested) return /* @__PURE__ */ jsx(variant === "title" ? "h2" : "h3", {
92
+ className: "dot-text dot-heading",
51
93
  style,
52
94
  children: content
53
95
  });
@@ -8,6 +8,7 @@ const SPACE = {
8
8
  const FONT_SIZE = {
9
9
  caption: 13,
10
10
  body: 15,
11
+ lead: 17,
11
12
  subtitle: 17,
12
13
  title: 20,
13
14
  display: 32
@@ -35,5 +36,9 @@ const PILL_STYLE = {
35
36
  lineHeight: "16px",
36
37
  textAlign: "center"
37
38
  };
39
+ const SELECTABLE = {
40
+ userSelect: "text",
41
+ WebkitUserSelect: "text"
42
+ };
38
43
  //#endregion
39
- export { CARD_RADIUS_SCALE, DISABLED_OPACITY, FONT_SIZE, ICON_SIZE, PILL_STYLE, SPACE, WEIGHTS };
44
+ export { CARD_RADIUS_SCALE, DISABLED_OPACITY, FONT_SIZE, ICON_SIZE, PILL_STYLE, SELECTABLE, SPACE, WEIGHTS };
package/dist/index.d.ts CHANGED
@@ -18,11 +18,14 @@ import { Input } from "./components/input.js";
18
18
  import { Link } from "./components/link.js";
19
19
  import { List, ListItem } from "./components/list.js";
20
20
  import { Option } from "./components/option.js";
21
+ import { Bullet, BulletList, Prose, Quote } from "./components/prose.js";
21
22
  import { QRCode } from "./components/qr-code.js";
23
+ import { RichText } from "./components/rich-text.js";
22
24
  import { Scroll } from "./components/scroll.js";
23
25
  import { Select } from "./components/select.js";
24
26
  import { Sheet } from "./components/sheet.js";
25
27
  import { Skeleton } from "./components/skeleton.js";
28
+ import { Slider } from "./components/slider.js";
26
29
  import { Spinner } from "./components/spinner.js";
27
30
  import { Stack } from "./components/stack.js";
28
31
  import { Tab, Tabs } from "./components/tabs.js";
@@ -33,4 +36,4 @@ import { Checkbox, Radio, RadioGroup, Switch } from "./components/toggles.js";
33
36
  import { View } from "./components/view.js";
34
37
  import "./components/index.js";
35
38
  import { ResolvedColors, ResolvedTheme, resolveTheme, useTheme } from "./theme.js";
36
- export { AmountInput, Badge, type BuiltinReactComponents, Button, Carousel, Checkbox, Chip, CodeInput, type CreateReactRendererOptions, DetailRow, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, QRCode, Radio, RadioGroup, type ReactComponent, type ReactComponents, type ReactRendererOptions, type ResolvedColors, type ResolvedTheme, Scroll, Select, Sheet, Skeleton, Spinner, Stack, Switch, Tab, Tabs, Text, TextArea, Toast, View, type Wrapper, createReactRenderer, reactRenderer as default, resolveTheme, useTheme };
39
+ export { AmountInput, Badge, type BuiltinReactComponents, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, type CreateReactRendererOptions, DetailRow, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, type ReactComponent, type ReactComponents, type ReactRendererOptions, type ResolvedColors, type ResolvedTheme, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Switch, Tab, Tabs, Text, TextArea, Toast, View, type Wrapper, createReactRenderer, reactRenderer as default, resolveTheme, useTheme };
package/dist/index.js CHANGED
@@ -17,14 +17,17 @@ import { Input } from "./components/input.js";
17
17
  import { Link } from "./components/link.js";
18
18
  import { List, ListItem } from "./components/list.js";
19
19
  import { Option } from "./components/option.js";
20
+ import { Bullet, BulletList, Prose, Quote } from "./components/prose.js";
20
21
  import { QRCode } from "./components/qr-code.js";
22
+ import { Text } from "./components/text.js";
23
+ import { RichText } from "./components/rich-text.js";
21
24
  import { Scroll } from "./components/scroll.js";
22
25
  import { Sheet } from "./components/sheet.js";
23
26
  import { Select } from "./components/select.js";
24
27
  import { Skeleton } from "./components/skeleton.js";
28
+ import { Slider } from "./components/slider.js";
25
29
  import { Stack } from "./components/stack.js";
26
30
  import { Tab, Tabs } from "./components/tabs.js";
27
- import { Text } from "./components/text.js";
28
31
  import { TextArea } from "./components/text-area.js";
29
32
  import { Toast } from "./components/toast.js";
30
33
  import { Checkbox, Radio, RadioGroup, Switch } from "./components/toggles.js";
@@ -33,4 +36,4 @@ import { createReactRenderer, reactRenderer } from "./renderer.js";
33
36
  //#region src/index.ts
34
37
  var src_default = reactRenderer;
35
38
  //#endregion
36
- export { AmountInput, Badge, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, QRCode, Radio, RadioGroup, Scroll, Select, Sheet, Skeleton, Spinner, Stack, Switch, Tab, Tabs, Text, TextArea, Toast, View, createReactRenderer, src_default as default, resolveTheme, useTheme };
39
+ export { AmountInput, Badge, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Switch, Tab, Tabs, Text, TextArea, Toast, View, createReactRenderer, src_default as default, resolveTheme, useTheme };
@@ -0,0 +1,226 @@
1
+ //#region src/markdown.ts
2
+ const HEADING = /^(#{1,6})\s+(.*?)\s*#*\s*$/;
3
+ const RULE = /^ {0,3}([-*_])( *\1){2,} *$/;
4
+ const QUOTE = /^ {0,3}> ?/;
5
+ const LIST_ITEM = /^ {0,3}([-*+]|\d{1,9}[.)])\s+/;
6
+ const LINK = /^(!?)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/;
7
+ const AUTOLINK = /^<(https?:\/\/[^>\s]+)>/;
8
+ const PUNCTUATION = /[!-/:-@[-`{-~]/;
9
+ const ITEM_INDENT = " ";
10
+ function parseMarkdown(markdown) {
11
+ return parseBlocks(markdown.replace(/\r\n?/g, "\n").split("\n"));
12
+ }
13
+ function parseBlocks(lines) {
14
+ const blocks = [];
15
+ let index = 0;
16
+ while (index < lines.length) {
17
+ const line = lines[index];
18
+ if (line.trim() === "") {
19
+ index += 1;
20
+ continue;
21
+ }
22
+ const single = oneLineBlock(line);
23
+ if (single) {
24
+ blocks.push(single);
25
+ index += 1;
26
+ } else if (QUOTE.test(line)) {
27
+ const quoted = [];
28
+ while (index < lines.length && QUOTE.test(lines[index])) {
29
+ quoted.push(lines[index].replace(QUOTE, ""));
30
+ index += 1;
31
+ }
32
+ blocks.push({
33
+ kind: "quote",
34
+ blocks: parseBlocks(quoted)
35
+ });
36
+ } else if (LIST_ITEM.test(line)) {
37
+ const { list, next } = parseList(lines, index);
38
+ blocks.push(list);
39
+ index = next;
40
+ } else {
41
+ const paragraph = [];
42
+ while (index < lines.length && !endsParagraph(lines[index])) {
43
+ paragraph.push(lines[index]);
44
+ index += 1;
45
+ }
46
+ blocks.push({
47
+ kind: "paragraph",
48
+ content: parseInline(joinLines(paragraph))
49
+ });
50
+ }
51
+ }
52
+ return blocks;
53
+ }
54
+ function oneLineBlock(line) {
55
+ const heading = HEADING.exec(line);
56
+ if (heading) return {
57
+ kind: "heading",
58
+ level: Math.min(heading[1].length, 3),
59
+ content: parseInline(heading[2])
60
+ };
61
+ if (RULE.test(line)) return { kind: "rule" };
62
+ const trimmed = line.trim();
63
+ const picture = LINK.exec(trimmed);
64
+ if (picture && picture[1] === "!" && picture[0].length === trimmed.length) return {
65
+ kind: "image",
66
+ alt: picture[2],
67
+ src: picture[3]
68
+ };
69
+ return null;
70
+ }
71
+ function endsParagraph(line) {
72
+ return line.trim() === "" || HEADING.test(line) || RULE.test(line) || QUOTE.test(line) || LIST_ITEM.test(line);
73
+ }
74
+ function joinLines(lines) {
75
+ let text = "";
76
+ lines.forEach((line, index) => {
77
+ text += line.trim().replace(/\\$/, "");
78
+ if (index < lines.length - 1) text += /( {2,}|\\)$/.test(line) ? "\n" : " ";
79
+ });
80
+ return text;
81
+ }
82
+ function parseList(lines, start) {
83
+ const ordered = isOrdered(LIST_ITEM.exec(lines[start]));
84
+ const items = [];
85
+ let index = start;
86
+ while (index < lines.length) {
87
+ const marker = LIST_ITEM.exec(lines[index]);
88
+ if (!marker || isOrdered(marker) !== ordered) break;
89
+ const indent = marker[0].length;
90
+ const itemLines = [lines[index].slice(indent)];
91
+ index += 1;
92
+ while (index < lines.length) {
93
+ const line = lines[index];
94
+ if (line.trim() === "") {
95
+ if (!lines[index + 1]?.startsWith(ITEM_INDENT)) break;
96
+ itemLines.push("");
97
+ } else if (line.startsWith(ITEM_INDENT)) itemLines.push(withoutIndent(line, indent));
98
+ else if (endsParagraph(line)) break;
99
+ else itemLines.push(line);
100
+ index += 1;
101
+ }
102
+ items.push(parseBlocks(itemLines));
103
+ }
104
+ return {
105
+ list: {
106
+ kind: "list",
107
+ ordered,
108
+ items
109
+ },
110
+ next: index
111
+ };
112
+ }
113
+ function isOrdered(marker) {
114
+ return /\d/.test(marker[1]);
115
+ }
116
+ function withoutIndent(line, indent) {
117
+ const spaces = line.length - line.trimStart().length;
118
+ return line.slice(Math.min(spaces, indent));
119
+ }
120
+ function parseInline(text) {
121
+ const inlines = [];
122
+ let plain = "";
123
+ let index = 0;
124
+ const flush = () => {
125
+ if (plain) {
126
+ inlines.push({
127
+ kind: "text",
128
+ text: plain
129
+ });
130
+ plain = "";
131
+ }
132
+ };
133
+ const take = (inline, length) => {
134
+ flush();
135
+ inlines.push(inline);
136
+ index += length;
137
+ };
138
+ while (index < text.length) {
139
+ const char = text[index];
140
+ const next = text[index + 1];
141
+ const rest = text.slice(index);
142
+ if (char === "\\" && next !== void 0 && PUNCTUATION.test(next)) {
143
+ plain += next;
144
+ index += 2;
145
+ continue;
146
+ }
147
+ if (char === "\n") {
148
+ take({ kind: "break" }, 1);
149
+ continue;
150
+ }
151
+ const codeEnd = char === "`" ? text.indexOf("`", index + 1) : -1;
152
+ if (codeEnd > index + 1) {
153
+ take({
154
+ kind: "code",
155
+ text: text.slice(index + 1, codeEnd)
156
+ }, codeEnd + 1 - index);
157
+ continue;
158
+ }
159
+ const link = LINK.exec(rest);
160
+ if (link) {
161
+ const [whole, picture, label, href] = link;
162
+ take(picture === "!" ? {
163
+ kind: "text",
164
+ text: label
165
+ } : {
166
+ kind: "link",
167
+ href,
168
+ children: parseInline(label)
169
+ }, whole.length);
170
+ continue;
171
+ }
172
+ const autolink = AUTOLINK.exec(rest);
173
+ if (autolink) {
174
+ const [whole, href] = autolink;
175
+ take({
176
+ kind: "link",
177
+ href,
178
+ children: [{
179
+ kind: "text",
180
+ text: href
181
+ }]
182
+ }, whole.length);
183
+ continue;
184
+ }
185
+ const strong = char === "*" || char === "_" ? between(text, index, char + char) : null;
186
+ if (strong) {
187
+ take({
188
+ kind: "strong",
189
+ children: parseInline(strong.inner)
190
+ }, strong.length);
191
+ continue;
192
+ }
193
+ const emphasis = char === "*" || char === "_" ? between(text, index, char) : null;
194
+ if (emphasis) {
195
+ take({
196
+ kind: "emphasis",
197
+ children: parseInline(emphasis.inner)
198
+ }, emphasis.length);
199
+ continue;
200
+ }
201
+ plain += char;
202
+ index += 1;
203
+ }
204
+ flush();
205
+ return inlines;
206
+ }
207
+ function between(text, start, delimiter) {
208
+ const open = start + delimiter.length;
209
+ if (!text.startsWith(delimiter, start) || open >= text.length || /\s/.test(text[open])) return null;
210
+ const underscore = delimiter.startsWith("_");
211
+ if (underscore && /\w/.test(text[start - 1] ?? "")) return null;
212
+ let close = text.indexOf(delimiter, open + 1);
213
+ while (close !== -1) {
214
+ const touchesSpace = /\s/.test(text[close - 1]);
215
+ const insideWord = underscore && /\w/.test(text[close + delimiter.length] ?? "");
216
+ const startsPair = delimiter.length === 1 && text[close + 1] === delimiter;
217
+ if (!touchesSpace && !insideWord && !startsPair) return {
218
+ inner: text.slice(open, close),
219
+ length: close + delimiter.length - start
220
+ };
221
+ close = text.indexOf(delimiter, close + (startsPair ? 2 : 1));
222
+ }
223
+ return null;
224
+ }
225
+ //#endregion
226
+ export { parseInline, parseMarkdown };
package/dist/styles.css CHANGED
@@ -1,95 +1 @@
1
- /*
2
- * Dot's stylesheet, for what inline styles can't do, such as pseudo-elements, keyframes and
3
- * rules for every element. Global rules sit in the lowest layer, so they never override a
4
- * component's own styles. Component rules stay outside the layers.
5
- */
6
-
7
- @layer base {
8
- .dot-theme-root,
9
- .dot-theme-root *,
10
- .dot-theme-root ::before,
11
- .dot-theme-root ::after {
12
- box-sizing: border-box;
13
- }
14
-
15
- .dot-theme-root :is(button, input, select, textarea) {
16
- font: inherit;
17
- }
18
-
19
- [data-dot-scheme='light'] {
20
- color-scheme: light;
21
- }
22
-
23
- [data-dot-scheme='dark'] {
24
- color-scheme: dark;
25
- }
26
- }
27
-
28
- /* Skeleton: pulses down to 40% every 1.2s. */
29
- @keyframes dot-skeleton-pulse {
30
- 0%,
31
- 100% {
32
- opacity: 1;
33
- }
34
-
35
- 50% {
36
- opacity: 0.4;
37
- }
38
- }
39
-
40
- /* Image: no wider than the space it's given, unless a Scroll lets it keep its own width. */
41
- .dot-image {
42
- display: block;
43
- max-width: 100%;
44
- }
45
-
46
- /* List: a full-width line under each row but the last, in the color the list passes. */
47
- .dot-list > .dot-list-item:not(:last-child) {
48
- border-bottom: 1px solid var(--dot-list-divider);
49
- }
50
-
51
- /* Scroll: a row that scrolls sideways with the finger, without a scroll bar. */
52
- .dot-scroll {
53
- position: relative;
54
- display: flex;
55
- flex-direction: row;
56
- overflow-x: auto;
57
- overflow-y: hidden;
58
- overscroll-behavior-x: contain;
59
- scrollbar-width: none;
60
- }
61
-
62
- .dot-scroll::-webkit-scrollbar {
63
- display: none;
64
- }
65
-
66
- /* Each child keeps its own width. A View is as wide as its widest fixed-size child. Children are
67
- styled here instead of wrapped, because a For loop's items reach the row as one child. */
68
- .dot-scroll > * {
69
- flex: 0 0 auto;
70
- width: min-content;
71
- scroll-snap-align: var(--dot-scroll-snap);
72
- }
73
-
74
- /* A picture's own width sets its item's width. With max-width: 100% it would count for nothing. */
75
- .dot-scroll .dot-image {
76
- max-width: none;
77
- }
78
-
79
- /* Sheet: slides up over a dimmed page, and back down when it closes. Base UI marks the first and
80
- last frames, and waits for these transitions before it removes the sheet. */
81
- .dot-sheet-backdrop {
82
- transition: opacity 0.2s;
83
- }
84
-
85
- .dot-sheet {
86
- transition: transform 0.2s ease-out;
87
- }
88
-
89
- .dot-sheet-backdrop:is([data-starting-style], [data-ending-style]) {
90
- opacity: 0;
91
- }
92
-
93
- .dot-sheet:is([data-starting-style], [data-ending-style]) {
94
- transform: translateY(100%);
95
- }
1
+ @layer base{.dot-theme-root,.dot-theme-root *,.dot-theme-root :before,.dot-theme-root :after{box-sizing:border-box}.dot-theme-root :is(button,input,select,textarea){font:inherit}[data-dot-scheme=light]{color-scheme:light}[data-dot-scheme=dark]{color-scheme:dark}.dot-theme-root{-webkit-user-select:none;user-select:none}.dot-theme-root :is(input,textarea){-webkit-user-select:text;user-select:text}}@keyframes dot-skeleton-pulse{0%,to{opacity:1}50%{opacity:.4}}.dot-image{max-width:100%;display:block}.dot-list>.dot-list-item:not(:last-child){border-bottom:1px solid var(--dot-list-divider)}.dot-scroll{overscroll-behavior-x:contain;scrollbar-width:none;flex-direction:row;display:flex;position:relative;overflow:auto hidden}.dot-scroll::-webkit-scrollbar{display:none}.dot-scroll>*{scroll-snap-align:var(--dot-scroll-snap);flex:none;width:min-content}.dot-scroll .dot-image{max-width:none}.dot-sheet-backdrop{transition:opacity .2s}.dot-sheet{transition:transform .2s ease-out}.dot-sheet-backdrop:is([data-starting-style],[data-ending-style]){opacity:0}.dot-sheet:is([data-starting-style],[data-ending-style]){transform:translateY(100%)}.dot-prose>.dot-heading{margin-block-start:.5em}.dot-prose>.dot-heading:first-child{margin-block-start:0}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monimesl/dot-renderer-react",
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.19",
4
4
  "description": "React renderer for Dot webviews",
5
5
  "type": "module",
6
6
  "repository": {
@@ -29,8 +29,8 @@
29
29
  "react": "^19.3.0",
30
30
  "react-dom": "^19.3.0",
31
31
  "uqr": "^0.1.3",
32
- "@monimesl/dot-webview": "0.1.0-alpha.16",
33
- "@monimesl/dot-core": "0.1.0-alpha.16"
32
+ "@monimesl/dot-core": "0.1.0-alpha.19",
33
+ "@monimesl/dot-webview": "0.1.0-alpha.19"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@base-ui/react": "1.8.0",
@@ -39,19 +39,20 @@
39
39
  "@types/react": "^19.3.0",
40
40
  "@types/react-dom": "^19.3.0",
41
41
  "jsdom": "^30.1.1",
42
+ "lightningcss": "1.33.0",
42
43
  "publint": "^0.3.24",
43
44
  "rimraf": "^6.1.3",
44
45
  "rolldown": "^1.2.9",
45
46
  "tsdown": "^0.23.0",
46
47
  "typescript": "^5.9.3",
47
48
  "vitest": "^5.0.1",
48
- "@monimesl/dot-service": "0.1.0-alpha.16"
49
+ "@monimesl/dot-service": "0.1.0-alpha.19"
49
50
  },
50
51
  "publishConfig": {
51
52
  "access": "public"
52
53
  },
53
54
  "scripts": {
54
- "build": "pnpm icons:generate && tsdown && cp src/styles/base.css dist/styles.css && cp node_modules/@phosphor-icons/core/LICENSE dist/LICENSE-PHOSPHOR && node scripts/build-react-host.mjs",
55
+ "build": "pnpm icons:generate && tsdown && node scripts/build-styles.mjs && cp node_modules/@phosphor-icons/core/LICENSE dist/LICENSE-PHOSPHOR && node scripts/build-react-host.mjs",
55
56
  "icons:generate": "node scripts/generate-icon-artwork.mjs && prettier --write src/components/icon-artwork.ts",
56
57
  "dev": "tsdown --watch",
57
58
  "typecheck": "tsc --noEmit -p tsconfig.json",