@monimesl/dot-renderer-react 0.1.0-alpha.9 → 1.0.0-beta.0
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.
- package/README.md +24 -20
- package/dist/color.js +25 -36
- package/dist/components/amount-input.js +26 -21
- package/dist/components/avatar.d.ts +6 -0
- package/dist/components/avatar.js +68 -0
- package/dist/components/box.js +56 -0
- package/dist/components/carousel.js +70 -66
- package/dist/components/code-input.js +6 -7
- package/dist/components/disclosure.d.ts +6 -0
- package/dist/components/disclosure.js +76 -0
- package/dist/components/empty-state.js +2 -4
- package/dist/components/feedback.js +39 -23
- package/dist/components/footer.js +3 -2
- package/dist/components/grid.js +4 -5
- package/dist/components/https.js +11 -0
- package/dist/components/icon-artwork.js +139 -70
- package/dist/components/icon-button.js +28 -0
- package/dist/components/icon-svg.js +27 -0
- package/dist/components/icon.js +5 -20
- package/dist/components/index.d.ts +9 -1
- package/dist/components/index.js +22 -2
- package/dist/components/input.js +24 -2
- package/dist/components/link.js +1 -8
- package/dist/components/list.js +17 -17
- package/dist/components/numbers.js +9 -0
- package/dist/components/option.js +4 -3
- package/dist/components/padding.js +18 -7
- package/dist/components/prose-context.js +5 -0
- package/dist/components/prose.d.ts +9 -0
- package/dist/components/prose.js +67 -0
- package/dist/components/rich-text.d.ts +6 -0
- package/dist/components/rich-text.js +73 -0
- package/dist/components/scroll.js +27 -25
- package/dist/components/select.js +4 -3
- package/dist/components/sheet.js +9 -8
- package/dist/components/slider.d.ts +6 -0
- package/dist/components/slider.js +57 -0
- package/dist/components/stack.d.ts +6 -0
- package/dist/components/stack.js +25 -0
- package/dist/components/stepper.d.ts +6 -0
- package/dist/components/stepper.js +92 -0
- package/dist/components/steps.d.ts +7 -0
- package/dist/components/steps.js +110 -0
- package/dist/components/tabs.js +23 -19
- package/dist/components/text.js +74 -13
- package/dist/components/toast.js +4 -4
- package/dist/components/toggles.js +6 -7
- package/dist/components/tokens.js +14 -2
- package/dist/components/tones.js +13 -7
- package/dist/components/typed-text.js +2 -2
- package/dist/components/view.js +19 -9
- package/dist/index.d.ts +9 -1
- package/dist/index.js +10 -2
- package/dist/markdown.js +255 -0
- package/dist/page-root.js +1 -1
- package/dist/pull-to-refresh.js +120 -0
- package/dist/root.js +31 -11
- package/dist/styles.css +1 -95
- package/dist/theme.d.ts +3 -2
- package/dist/theme.js +60 -15
- package/package.json +8 -7
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
//#region src/markdown.ts
|
|
2
|
+
const HEADING = /^(#{1,6})\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
|
+
const MAX_INLINE_LENGTH = 2e4;
|
|
11
|
+
function parseMarkdown(markdown) {
|
|
12
|
+
return parseBlocks(markdown.replace(/\r\n?/g, "\n").split("\n"));
|
|
13
|
+
}
|
|
14
|
+
function parseBlocks(lines) {
|
|
15
|
+
const blocks = [];
|
|
16
|
+
let index = 0;
|
|
17
|
+
while (index < lines.length) {
|
|
18
|
+
const line = lines[index];
|
|
19
|
+
if (line.trim() === "") {
|
|
20
|
+
index += 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const single = oneLineBlock(line);
|
|
24
|
+
if (single) {
|
|
25
|
+
blocks.push(single);
|
|
26
|
+
index += 1;
|
|
27
|
+
} else if (QUOTE.test(line)) {
|
|
28
|
+
const quoted = [];
|
|
29
|
+
while (index < lines.length && QUOTE.test(lines[index])) {
|
|
30
|
+
quoted.push(lines[index].replace(QUOTE, ""));
|
|
31
|
+
index += 1;
|
|
32
|
+
}
|
|
33
|
+
blocks.push({
|
|
34
|
+
kind: "quote",
|
|
35
|
+
blocks: parseBlocks(quoted)
|
|
36
|
+
});
|
|
37
|
+
} else if (LIST_ITEM.test(line)) {
|
|
38
|
+
const { list, next } = parseList(lines, index);
|
|
39
|
+
blocks.push(list);
|
|
40
|
+
index = next;
|
|
41
|
+
} else {
|
|
42
|
+
const paragraph = [];
|
|
43
|
+
while (index < lines.length && !endsParagraph(lines[index])) {
|
|
44
|
+
paragraph.push(lines[index]);
|
|
45
|
+
index += 1;
|
|
46
|
+
}
|
|
47
|
+
blocks.push({
|
|
48
|
+
kind: "paragraph",
|
|
49
|
+
content: parseInline(joinLines(paragraph))
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return blocks;
|
|
54
|
+
}
|
|
55
|
+
function oneLineBlock(line) {
|
|
56
|
+
const heading = HEADING.exec(line);
|
|
57
|
+
if (heading) return {
|
|
58
|
+
kind: "heading",
|
|
59
|
+
level: Math.min(heading[1].length, 3),
|
|
60
|
+
content: parseInline(withoutClosingHashes(heading[2]))
|
|
61
|
+
};
|
|
62
|
+
if (RULE.test(line)) return { kind: "rule" };
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
const picture = LINK.exec(trimmed);
|
|
65
|
+
if (picture && picture[1] === "!" && picture[0].length === trimmed.length) return {
|
|
66
|
+
kind: "image",
|
|
67
|
+
alt: picture[2],
|
|
68
|
+
src: picture[3]
|
|
69
|
+
};
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function withoutClosingHashes(text) {
|
|
73
|
+
let end = text.trimEnd().length;
|
|
74
|
+
while (end > 0 && text[end - 1] === "#") end -= 1;
|
|
75
|
+
return text.slice(0, end).trimEnd();
|
|
76
|
+
}
|
|
77
|
+
function endsParagraph(line) {
|
|
78
|
+
return line.trim() === "" || HEADING.test(line) || RULE.test(line) || QUOTE.test(line) || LIST_ITEM.test(line);
|
|
79
|
+
}
|
|
80
|
+
function joinLines(lines) {
|
|
81
|
+
let text = "";
|
|
82
|
+
lines.forEach((line, index) => {
|
|
83
|
+
text += line.trim().replace(/\\$/, "");
|
|
84
|
+
if (index < lines.length - 1) text += /( {2,}|\\)$/.test(line) ? "\n" : " ";
|
|
85
|
+
});
|
|
86
|
+
return text;
|
|
87
|
+
}
|
|
88
|
+
function parseList(lines, start) {
|
|
89
|
+
const ordered = isOrdered(LIST_ITEM.exec(lines[start]));
|
|
90
|
+
const items = [];
|
|
91
|
+
let index = start;
|
|
92
|
+
while (index < lines.length) {
|
|
93
|
+
const marker = LIST_ITEM.exec(lines[index]);
|
|
94
|
+
if (!marker || isOrdered(marker) !== ordered) break;
|
|
95
|
+
const indent = marker[0].length;
|
|
96
|
+
const itemLines = [lines[index].slice(indent)];
|
|
97
|
+
index += 1;
|
|
98
|
+
while (index < lines.length) {
|
|
99
|
+
const line = lines[index];
|
|
100
|
+
if (line.trim() === "") {
|
|
101
|
+
if (!lines[index + 1]?.startsWith(ITEM_INDENT)) break;
|
|
102
|
+
itemLines.push("");
|
|
103
|
+
} else if (line.startsWith(ITEM_INDENT)) itemLines.push(withoutIndent(line, indent));
|
|
104
|
+
else if (endsParagraph(line)) break;
|
|
105
|
+
else itemLines.push(line);
|
|
106
|
+
index += 1;
|
|
107
|
+
}
|
|
108
|
+
items.push(parseBlocks(itemLines));
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
list: {
|
|
112
|
+
kind: "list",
|
|
113
|
+
ordered,
|
|
114
|
+
items
|
|
115
|
+
},
|
|
116
|
+
next: index
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function isOrdered(marker) {
|
|
120
|
+
return /\d/.test(marker[1]);
|
|
121
|
+
}
|
|
122
|
+
function withoutIndent(line, indent) {
|
|
123
|
+
const spaces = line.length - line.trimStart().length;
|
|
124
|
+
return line.slice(Math.min(spaces, indent));
|
|
125
|
+
}
|
|
126
|
+
function parseInline(text) {
|
|
127
|
+
if (text.length > MAX_INLINE_LENGTH) return [{
|
|
128
|
+
kind: "text",
|
|
129
|
+
text
|
|
130
|
+
}];
|
|
131
|
+
const inlines = [];
|
|
132
|
+
const closingSearches = /* @__PURE__ */ new Map();
|
|
133
|
+
let plain = "";
|
|
134
|
+
let index = 0;
|
|
135
|
+
const flush = () => {
|
|
136
|
+
if (plain) {
|
|
137
|
+
inlines.push({
|
|
138
|
+
kind: "text",
|
|
139
|
+
text: plain
|
|
140
|
+
});
|
|
141
|
+
plain = "";
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const take = (inline, length) => {
|
|
145
|
+
flush();
|
|
146
|
+
inlines.push(inline);
|
|
147
|
+
index += length;
|
|
148
|
+
};
|
|
149
|
+
while (index < text.length) {
|
|
150
|
+
const char = text[index];
|
|
151
|
+
const next = text[index + 1];
|
|
152
|
+
const rest = text.slice(index);
|
|
153
|
+
if (char === "\\" && next !== void 0 && PUNCTUATION.test(next)) {
|
|
154
|
+
plain += next;
|
|
155
|
+
index += 2;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (char === "\n") {
|
|
159
|
+
take({ kind: "break" }, 1);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (char === "`") {
|
|
163
|
+
const codeEnd = text.indexOf("`", index + 1);
|
|
164
|
+
if (codeEnd > index + 1) {
|
|
165
|
+
take({
|
|
166
|
+
kind: "code",
|
|
167
|
+
text: text.slice(index + 1, codeEnd)
|
|
168
|
+
}, codeEnd + 1 - index);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const link = LINK.exec(rest);
|
|
173
|
+
if (link) {
|
|
174
|
+
const [whole, picture, label, href] = link;
|
|
175
|
+
take(picture === "!" ? {
|
|
176
|
+
kind: "text",
|
|
177
|
+
text: label
|
|
178
|
+
} : {
|
|
179
|
+
kind: "link",
|
|
180
|
+
href,
|
|
181
|
+
children: parseInline(label)
|
|
182
|
+
}, whole.length);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const autolink = AUTOLINK.exec(rest);
|
|
186
|
+
if (autolink) {
|
|
187
|
+
const [whole, href] = autolink;
|
|
188
|
+
take({
|
|
189
|
+
kind: "link",
|
|
190
|
+
href,
|
|
191
|
+
children: [{
|
|
192
|
+
kind: "text",
|
|
193
|
+
text: href
|
|
194
|
+
}]
|
|
195
|
+
}, whole.length);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (char === "*" || char === "_") {
|
|
199
|
+
const strong = between(text, index, char + char, closingSearches);
|
|
200
|
+
if (strong) {
|
|
201
|
+
take({
|
|
202
|
+
kind: "strong",
|
|
203
|
+
children: parseInline(strong.inner)
|
|
204
|
+
}, strong.length);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const emphasis = between(text, index, char, closingSearches);
|
|
208
|
+
if (emphasis) {
|
|
209
|
+
take({
|
|
210
|
+
kind: "emphasis",
|
|
211
|
+
children: parseInline(emphasis.inner)
|
|
212
|
+
}, emphasis.length);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
plain += char;
|
|
217
|
+
index += 1;
|
|
218
|
+
}
|
|
219
|
+
flush();
|
|
220
|
+
return inlines;
|
|
221
|
+
}
|
|
222
|
+
function between(text, start, delimiter, closingSearches) {
|
|
223
|
+
const open = start + delimiter.length;
|
|
224
|
+
if (!text.startsWith(delimiter, start) || open >= text.length || /\s/.test(text[open])) return null;
|
|
225
|
+
const underscore = delimiter.startsWith("_");
|
|
226
|
+
if (underscore && /\w/.test(text[start - 1] ?? "")) return null;
|
|
227
|
+
let searchEnds = closingSearches.get(delimiter);
|
|
228
|
+
if (!searchEnds) {
|
|
229
|
+
searchEnds = /* @__PURE__ */ new Map();
|
|
230
|
+
closingSearches.set(delimiter, searchEnds);
|
|
231
|
+
}
|
|
232
|
+
const placesLookedAt = [];
|
|
233
|
+
let close = text.indexOf(delimiter, open + 1);
|
|
234
|
+
while (close !== -1) {
|
|
235
|
+
const earlierEnd = searchEnds.get(close);
|
|
236
|
+
if (earlierEnd !== void 0) {
|
|
237
|
+
close = earlierEnd;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
placesLookedAt.push(close);
|
|
241
|
+
const touchesSpace = /\s/.test(text[close - 1]);
|
|
242
|
+
const insideWord = underscore && /\w/.test(text[close + delimiter.length] ?? "");
|
|
243
|
+
const startsPair = delimiter.length === 1 && text[close + 1] === delimiter;
|
|
244
|
+
if (!touchesSpace && !insideWord && !startsPair) break;
|
|
245
|
+
close = text.indexOf(delimiter, close + (startsPair ? 2 : 1));
|
|
246
|
+
}
|
|
247
|
+
for (const place of placesLookedAt) searchEnds.set(place, close);
|
|
248
|
+
if (close === -1) return null;
|
|
249
|
+
return {
|
|
250
|
+
inner: text.slice(open, close),
|
|
251
|
+
length: close + delimiter.length - start
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
export { parseInline, parseMarkdown };
|
package/dist/page-root.js
CHANGED
|
@@ -6,7 +6,7 @@ function PageRoot({ page, components }) {
|
|
|
6
6
|
const context = useMemo(() => ({
|
|
7
7
|
h(type, props, ...children) {
|
|
8
8
|
const component = components[type];
|
|
9
|
-
if (!component) throw new Error(`The renderer has no component
|
|
9
|
+
if (!component) throw new Error(`The renderer has no component for <${type}>. Check the tag's name, or pass a component for it to the renderer.`);
|
|
10
10
|
return createElement(component, props, ...children);
|
|
11
11
|
},
|
|
12
12
|
text: toText,
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useTheme } from "./theme.js";
|
|
2
|
+
import { CENTERED } from "./components/tokens.js";
|
|
3
|
+
import { LoadingIcon } from "./components/spinner.js";
|
|
4
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/pull-to-refresh.tsx
|
|
7
|
+
const PULL_DISTANCE = 64;
|
|
8
|
+
const LONGEST_PULL = PULL_DISTANCE * 1.5;
|
|
9
|
+
const RESISTANCE = .5;
|
|
10
|
+
const INDICATOR_SIZE = 36;
|
|
11
|
+
const DRAG_DISTANCE = 6;
|
|
12
|
+
function PullToRefresh({ page, children }) {
|
|
13
|
+
const { colors } = useTheme();
|
|
14
|
+
const refreshing = useSyncExternalStore(page.subscribeToRefreshing, page.isRefreshing);
|
|
15
|
+
const [pullDistance, setPullDistance] = useState(0);
|
|
16
|
+
const areaRef = useRef(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const area = areaRef.current;
|
|
19
|
+
const view = area.ownerDocument.defaultView;
|
|
20
|
+
let startY;
|
|
21
|
+
let pulled = 0;
|
|
22
|
+
let dragged = false;
|
|
23
|
+
const begin = (y) => {
|
|
24
|
+
const atTop = view.scrollY <= 0;
|
|
25
|
+
startY = page.refreshable && atTop && !page.isRefreshing() ? y : void 0;
|
|
26
|
+
dragged = false;
|
|
27
|
+
};
|
|
28
|
+
const move = (y, event) => {
|
|
29
|
+
if (startY === void 0) return;
|
|
30
|
+
const distance = (y - startY) * RESISTANCE;
|
|
31
|
+
if (distance <= 0) {
|
|
32
|
+
pulled = 0;
|
|
33
|
+
setPullDistance(0);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (event.cancelable) event.preventDefault();
|
|
37
|
+
if (distance > DRAG_DISTANCE) dragged = true;
|
|
38
|
+
pulled = Math.min(distance, LONGEST_PULL);
|
|
39
|
+
setPullDistance(pulled);
|
|
40
|
+
};
|
|
41
|
+
const end = () => {
|
|
42
|
+
if (startY !== void 0 && pulled >= PULL_DISTANCE) page.refresh();
|
|
43
|
+
startY = void 0;
|
|
44
|
+
pulled = 0;
|
|
45
|
+
setPullDistance(0);
|
|
46
|
+
};
|
|
47
|
+
const ignoreDragClick = (event) => {
|
|
48
|
+
if (dragged) {
|
|
49
|
+
dragged = false;
|
|
50
|
+
event.stopPropagation();
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const touchStart = (event) => begin(event.touches[0].clientY);
|
|
55
|
+
const touchMove = (event) => move(event.touches[0].clientY, event);
|
|
56
|
+
const mouseDown = (event) => {
|
|
57
|
+
if (event.button === 0) begin(event.clientY);
|
|
58
|
+
};
|
|
59
|
+
const mouseMove = (event) => move(event.clientY, event);
|
|
60
|
+
const removeListeners = [
|
|
61
|
+
listen(area, "touchstart", touchStart, { passive: true }),
|
|
62
|
+
listen(area, "touchmove", touchMove, { passive: false }),
|
|
63
|
+
listen(area, "touchend", end),
|
|
64
|
+
listen(area, "touchcancel", end),
|
|
65
|
+
listen(area, "mousedown", mouseDown),
|
|
66
|
+
listen(area, "click", ignoreDragClick, { capture: true }),
|
|
67
|
+
listen(view, "mousemove", mouseMove),
|
|
68
|
+
listen(view, "mouseup", end)
|
|
69
|
+
];
|
|
70
|
+
return () => {
|
|
71
|
+
for (const removeListener of removeListeners) removeListener();
|
|
72
|
+
};
|
|
73
|
+
}, [page]);
|
|
74
|
+
const indicatorDistance = refreshing ? PULL_DISTANCE : pullDistance;
|
|
75
|
+
const arrowTurn = Math.min(pullDistance, PULL_DISTANCE) / PULL_DISTANCE * 180;
|
|
76
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
77
|
+
ref: areaRef,
|
|
78
|
+
className: "dot-pull-to-refresh",
|
|
79
|
+
style: { display: "contents" },
|
|
80
|
+
children: [children, indicatorDistance > 0 ? /* @__PURE__ */ jsx("span", {
|
|
81
|
+
role: refreshing ? "status" : void 0,
|
|
82
|
+
"aria-label": refreshing ? "Refreshing" : void 0,
|
|
83
|
+
style: {
|
|
84
|
+
position: "fixed",
|
|
85
|
+
top: 0,
|
|
86
|
+
left: "50%",
|
|
87
|
+
zIndex: 2,
|
|
88
|
+
...CENTERED,
|
|
89
|
+
width: INDICATOR_SIZE,
|
|
90
|
+
height: INDICATOR_SIZE,
|
|
91
|
+
marginLeft: -18,
|
|
92
|
+
borderRadius: "50%",
|
|
93
|
+
background: colors.surface,
|
|
94
|
+
color: colors.primary,
|
|
95
|
+
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
|
|
96
|
+
transform: `translateY(${indicatorDistance - INDICATOR_SIZE / 2}px)`,
|
|
97
|
+
transition: pullDistance > 0 ? "none" : "transform 0.2s",
|
|
98
|
+
pointerEvents: "none"
|
|
99
|
+
},
|
|
100
|
+
children: refreshing ? /* @__PURE__ */ jsx(LoadingIcon, { size: 20 }) : /* @__PURE__ */ jsx("svg", {
|
|
101
|
+
width: 20,
|
|
102
|
+
height: 20,
|
|
103
|
+
viewBox: "0 0 24 24",
|
|
104
|
+
fill: "none",
|
|
105
|
+
stroke: "currentColor",
|
|
106
|
+
strokeWidth: 2.5,
|
|
107
|
+
strokeLinecap: "round",
|
|
108
|
+
strokeLinejoin: "round",
|
|
109
|
+
style: { transform: `rotate(${arrowTurn}deg)` },
|
|
110
|
+
children: /* @__PURE__ */ jsx("path", { d: "M12 5v14M6 13l6 6 6-6" })
|
|
111
|
+
})
|
|
112
|
+
}) : null]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function listen(target, type, listener, options) {
|
|
116
|
+
target.addEventListener(type, listener, options);
|
|
117
|
+
return () => target.removeEventListener(type, listener, options);
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
export { PullToRefresh };
|
package/dist/root.js
CHANGED
|
@@ -1,43 +1,63 @@
|
|
|
1
1
|
import { FONT_FAMILY, ThemeContext, resolveTheme, useTheme } from "./theme.js";
|
|
2
|
+
import { HOME_INDICATOR } from "./components/padding.js";
|
|
2
3
|
import { PortalContainerContext } from "./portal.js";
|
|
3
4
|
import { PageRoot } from "./page-root.js";
|
|
5
|
+
import { PullToRefresh } from "./pull-to-refresh.js";
|
|
6
|
+
import { mergeColors } from "@monimesl/dot-core";
|
|
4
7
|
import { useMemo, useState, useSyncExternalStore } from "react";
|
|
5
8
|
import { jsx } from "react/jsx-runtime";
|
|
6
9
|
//#region src/root.tsx
|
|
7
10
|
function Root({ page, dotContext, components, wrapper: Wrapper }) {
|
|
8
11
|
const theme = useSyncExternalStore(dotContext.theme.subscribe, dotContext.theme.get);
|
|
9
12
|
const systemContext = useSyncExternalStore(dotContext.systemContext.subscribe, dotContext.systemContext.get);
|
|
10
|
-
const
|
|
13
|
+
const pageColors = useSyncExternalStore(page.subscribeToColors, page.getColors);
|
|
14
|
+
const resolvedTheme = useMemo(() => resolveTheme(theme, systemContext.scheme, pageColors), [
|
|
15
|
+
theme,
|
|
16
|
+
systemContext.scheme,
|
|
17
|
+
pageColors
|
|
18
|
+
]);
|
|
11
19
|
const [rootElement, setRootElement] = useState(null);
|
|
12
|
-
const
|
|
20
|
+
const pageContent = /* @__PURE__ */ jsx(PageRoot, {
|
|
13
21
|
page,
|
|
14
22
|
components
|
|
15
23
|
});
|
|
16
24
|
return /* @__PURE__ */ jsx(ThemeContext.Provider, {
|
|
17
|
-
value:
|
|
25
|
+
value: resolvedTheme,
|
|
18
26
|
children: /* @__PURE__ */ jsx("div", {
|
|
19
27
|
ref: setRootElement,
|
|
20
28
|
className: "dot-theme-root",
|
|
21
|
-
"data-dot-scheme":
|
|
29
|
+
"data-dot-scheme": resolvedTheme.scheme,
|
|
22
30
|
children: /* @__PURE__ */ jsx(PortalContainerContext.Provider, {
|
|
23
31
|
value: rootElement,
|
|
24
|
-
children:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
children:
|
|
30
|
-
})
|
|
32
|
+
children: /* @__PURE__ */ jsx(PullToRefresh, {
|
|
33
|
+
page,
|
|
34
|
+
children: Wrapper ? /* @__PURE__ */ jsx(Wrapper, {
|
|
35
|
+
theme: withPageColors(theme, resolvedTheme.scheme, pageColors),
|
|
36
|
+
children: pageContent
|
|
37
|
+
}) : /* @__PURE__ */ jsx(DefaultSurface, { children: pageContent })
|
|
38
|
+
})
|
|
31
39
|
})
|
|
32
40
|
})
|
|
33
41
|
});
|
|
34
42
|
}
|
|
43
|
+
function withPageColors(theme, scheme, pageColors) {
|
|
44
|
+
return {
|
|
45
|
+
...theme,
|
|
46
|
+
scheme,
|
|
47
|
+
colors: {
|
|
48
|
+
...theme.colors,
|
|
49
|
+
...mergeColors(theme.colors ?? {}, pageColors)
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
35
53
|
function DefaultSurface({ children }) {
|
|
36
54
|
const { colors } = useTheme();
|
|
37
55
|
return /* @__PURE__ */ jsx("div", {
|
|
38
56
|
className: "dot-surface",
|
|
39
57
|
style: {
|
|
58
|
+
boxSizing: "border-box",
|
|
40
59
|
minHeight: "100vh",
|
|
60
|
+
paddingBottom: HOME_INDICATOR,
|
|
41
61
|
display: "flex",
|
|
42
62
|
flexDirection: "column",
|
|
43
63
|
background: colors.background,
|
package/dist/styles.css
CHANGED
|
@@ -1,95 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Dot's own stylesheet: what inline styles can't say, such as pseudo-elements, keyframes and
|
|
3
|
-
* rules for every element. The global rules join the lowest layer, so they never override a
|
|
4
|
-
* component's own styles; the components' rules are 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 width, a View as wide as its widest fixed-size child. Children are styled
|
|
67
|
-
here rather than wrapped, as 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: with a max-width of 100%, it would count for none. */
|
|
75
|
-
.dot-scroll .dot-image {
|
|
76
|
-
max-width: none;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/* Sheet: slides up over a page that dims, and back down when it closes. Base UI marks the first
|
|
80
|
-
and last frames, and waits for the 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-disclosure>summary{list-style:none}.dot-disclosure>summary::-webkit-details-marker{display:none}.dot-steps>.dot-step:last-child .dot-step-line{display: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/dist/theme.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ColorScheme, Theme, ThemeShape } from "@monimesl/dot-core";
|
|
1
|
+
import { ColorScheme, PageColors, Theme, ThemeShape } from "@monimesl/dot-core";
|
|
2
2
|
//#region src/theme.d.ts
|
|
3
3
|
export interface ResolvedColors {
|
|
4
4
|
primary: string;
|
|
@@ -17,9 +17,10 @@ export interface ResolvedColors {
|
|
|
17
17
|
export interface ResolvedTheme {
|
|
18
18
|
scheme: ColorScheme;
|
|
19
19
|
colors: ResolvedColors;
|
|
20
|
+
customColors: Readonly<Record<string, string>>;
|
|
20
21
|
shape: ThemeShape;
|
|
21
22
|
radius: number;
|
|
22
23
|
}
|
|
23
|
-
export declare function resolveTheme(theme: Theme, systemScheme: ColorScheme): ResolvedTheme;
|
|
24
|
+
export declare function resolveTheme(theme: Theme, systemScheme: ColorScheme, pageColors?: PageColors): ResolvedTheme;
|
|
24
25
|
export declare function useTheme(): ResolvedTheme;
|
|
25
26
|
//#endregion
|
package/dist/theme.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { darken, mix } from "./color.js";
|
|
2
|
+
import { DEFAULT_BACKGROUND, READABLE_CONTRAST, contrast, isDark, pageBackground, textOn, themeScheme } from "@monimesl/dot-core";
|
|
2
3
|
import { createContext, useContext } from "react";
|
|
3
4
|
//#region src/theme.ts
|
|
4
5
|
const FONT_FAMILY = "system-ui, sans-serif";
|
|
@@ -11,7 +12,7 @@ const SHAPE_RADIUS = {
|
|
|
11
12
|
const DEFAULT_COLORS = {
|
|
12
13
|
light: {
|
|
13
14
|
primary: "#1c1c1e",
|
|
14
|
-
background:
|
|
15
|
+
background: DEFAULT_BACKGROUND.light,
|
|
15
16
|
surface: "#ffffff",
|
|
16
17
|
text: "#111111",
|
|
17
18
|
subtle: "#8e8e93",
|
|
@@ -24,7 +25,7 @@ const DEFAULT_COLORS = {
|
|
|
24
25
|
},
|
|
25
26
|
dark: {
|
|
26
27
|
primary: "#f5f5f5",
|
|
27
|
-
background:
|
|
28
|
+
background: DEFAULT_BACKGROUND.dark,
|
|
28
29
|
surface: "#1e1e1e",
|
|
29
30
|
text: "#f5f5f5",
|
|
30
31
|
subtle: "#8e8e93",
|
|
@@ -36,37 +37,81 @@ const DEFAULT_COLORS = {
|
|
|
36
37
|
danger: "#ff6961"
|
|
37
38
|
}
|
|
38
39
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const scheme = !theme.scheme || theme.scheme === "system" ? systemScheme : theme.scheme;
|
|
40
|
+
function resolveTheme(theme, systemScheme, pageColors = {}) {
|
|
41
|
+
const scheme = themeScheme(theme, systemScheme);
|
|
42
42
|
const defaults = DEFAULT_COLORS[scheme];
|
|
43
|
-
const
|
|
44
|
-
const
|
|
43
|
+
const themeColors = theme.colors ?? {};
|
|
44
|
+
const ownColor = (name) => pageColors[name]?.[scheme] ?? themeColors[name]?.[scheme];
|
|
45
45
|
const shape = theme.shape ?? "soft";
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
46
|
+
const background = pageBackground(pageColors.background, theme, systemScheme);
|
|
47
|
+
const onDashBackground = background === defaults.background;
|
|
48
|
+
let surfaces = defaults;
|
|
49
|
+
if (!onDashBackground) surfaces = surfacesOn(background);
|
|
50
|
+
const surface = ownColor("surface") ?? surfaces.surface;
|
|
51
|
+
const text = ownColor("text") ?? readableText(defaults.text, background);
|
|
52
|
+
let subtle = defaults.subtle;
|
|
53
|
+
if (!onDashBackground) subtle = quieterText(text, background, surface);
|
|
54
|
+
const primary = ownColor("primary") ?? defaults.primary;
|
|
49
55
|
return {
|
|
50
56
|
scheme,
|
|
51
57
|
colors: {
|
|
52
58
|
...defaults,
|
|
53
59
|
...primaryAndOnPrimary(primary, surface, text, scheme),
|
|
54
|
-
background
|
|
60
|
+
background,
|
|
55
61
|
surface,
|
|
56
|
-
text
|
|
62
|
+
text,
|
|
63
|
+
muted: ownColor("muted") ?? surfaces.muted,
|
|
64
|
+
border: ownColor("border") ?? surfaces.border,
|
|
65
|
+
subtle: ownColor("subtle") ?? subtle
|
|
57
66
|
},
|
|
67
|
+
customColors: resolveCustomColors(themeColors.custom ?? {}, scheme),
|
|
58
68
|
shape,
|
|
59
69
|
radius: SHAPE_RADIUS[shape]
|
|
60
70
|
};
|
|
61
71
|
}
|
|
72
|
+
function surfacesOn(background) {
|
|
73
|
+
if (isDark(background)) return {
|
|
74
|
+
surface: mix(background, "#ffffff", .08),
|
|
75
|
+
muted: mix(background, "#ffffff", .14),
|
|
76
|
+
border: mix(background, "#ffffff", .16)
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
surface: mix(background, "#ffffff", .75),
|
|
80
|
+
muted: mix(background, "#000000", .06),
|
|
81
|
+
border: mix(background, "#000000", .1)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const QUIET_CONTRAST = 3;
|
|
85
|
+
function quieterText(text, background, surface) {
|
|
86
|
+
for (let shareOfBackground = .5; shareOfBackground > 0; shareOfBackground -= .05) {
|
|
87
|
+
const quieter = mix(text, background, shareOfBackground);
|
|
88
|
+
const readsOnBackground = contrast(quieter, background) >= QUIET_CONTRAST;
|
|
89
|
+
const readsOnSurface = contrast(quieter, surface) >= QUIET_CONTRAST;
|
|
90
|
+
if (readsOnBackground && readsOnSurface) return quieter;
|
|
91
|
+
}
|
|
92
|
+
return text;
|
|
93
|
+
}
|
|
94
|
+
function readableText(text, background) {
|
|
95
|
+
if (contrast(text, background) >= READABLE_CONTRAST) return text;
|
|
96
|
+
return textOn(background);
|
|
97
|
+
}
|
|
98
|
+
function resolveCustomColors(custom, scheme) {
|
|
99
|
+
const resolved = {};
|
|
100
|
+
for (const [name, color] of Object.entries(custom)) {
|
|
101
|
+
const value = color[scheme] ?? color.light ?? color.dark;
|
|
102
|
+
if (value) resolved[name] = value;
|
|
103
|
+
}
|
|
104
|
+
return resolved;
|
|
105
|
+
}
|
|
106
|
+
const MOST_DARKENING = 15;
|
|
62
107
|
function primaryAndOnPrimary(primary, surface, text, scheme) {
|
|
63
|
-
if (contrast(primary, surface) >=
|
|
108
|
+
if (contrast(primary, surface) >= READABLE_CONTRAST) return {
|
|
64
109
|
primary,
|
|
65
110
|
onPrimary: surface
|
|
66
111
|
};
|
|
67
112
|
if (scheme === "light") for (let points = .5; points <= MOST_DARKENING; points += .5) {
|
|
68
113
|
const darker = darken(primary, points);
|
|
69
|
-
if (contrast(darker, surface) >=
|
|
114
|
+
if (contrast(darker, surface) >= READABLE_CONTRAST) return {
|
|
70
115
|
primary: darker,
|
|
71
116
|
onPrimary: surface
|
|
72
117
|
};
|