@ox-content/vite-plugin 3.0.0-alpha.17 → 3.0.0-alpha.18
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/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5677 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5677 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -3
- package/dist/index.mjs.map +1 -1
- package/dist/reader-chrome.cjs.map +1 -1
- package/dist/reader-chrome.d.cts +58 -5703
- package/dist/reader-chrome.d.mts +58 -5703
- package/dist/reader-chrome.mjs.map +1 -1
- package/dist/styles/all.css +1 -0
- package/dist/styles/core.css +53 -1
- package/dist/styles/reader-chrome.css +12 -9
- package/dist/styles/theme-transition.css +37 -0
- package/dist/styles/twitter-full.css +22 -4
- package/dist/theme-transition-client.cjs +161 -0
- package/dist/theme-transition-client.d.cts +21 -0
- package/dist/theme-transition-client.d.mts +21 -0
- package/dist/theme-transition-client.mjs +160 -0
- package/dist/twitter-client.cjs +84 -0
- package/dist/twitter-client.d.cts +11 -0
- package/dist/twitter-client.d.mts +11 -0
- package/dist/twitter-client.mjs +83 -0
- package/dist/vitepress.cjs +3 -0
- package/dist/vitepress.cjs.map +1 -1
- package/dist/vitepress.mjs +3 -0
- package/dist/vitepress.mjs.map +1 -1
- package/package.json +25 -4
- package/dist/reader-chrome.d.cts.map +0 -1
- package/dist/reader-chrome.d.mts.map +0 -1
- package/dist/reader-chrome2.d.cts +0 -2
- package/dist/reader-chrome2.d.mts +0 -2
- package/dist/theme-tokens2.d.cts +0 -75
- package/dist/theme-tokens2.d.cts.map +0 -1
- package/dist/theme-tokens2.d.mts +0 -75
- package/dist/theme-tokens2.d.mts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reader-chrome.mjs","names":[],"sources":["../src/reader-chrome.ts"],"sourcesContent":["import { importNapiModuleSync } from \"./napi\";\nimport type { ReaderChromeOptions, ResolvedReaderChrome } from \"./
|
|
1
|
+
{"version":3,"file":"reader-chrome.mjs","names":[],"sources":["../src/reader-chrome.ts"],"sourcesContent":["import { importNapiModuleSync } from \"./napi\";\nimport type { ReaderChromeOptions, ResolvedReaderChrome } from \"./reader-chrome-options\";\n\nexport type { ReaderChromeOptions, ResolvedReaderChrome };\n\ntype EnabledReaderChrome = Exclude<ResolvedReaderChrome, false>;\n\nexport type ReaderChromeInput = boolean | ReaderChromeOptions | EnabledReaderChrome | undefined;\n\nconst EMPTY_ATTRIBUTES = Object.freeze({}) as Readonly<Record<string, \"\">>;\n\n/**\n * Resolve the public reader-chrome option shape.\n *\n * `true` mirrors `ssg.readerChrome: true`: copy, outbound-link decoration, and\n * back-to-top are enabled. Object fields default on, so `{ copy: false }`\n * disables only the copy button.\n */\nexport function resolveReaderChromeInput(input: ReaderChromeInput = true): ResolvedReaderChrome {\n if (!input) {\n return false;\n }\n if (input === true) {\n return { copy: true, externalLinks: true, backToTop: true };\n }\n return {\n copy: input.copy ?? true,\n externalLinks: input.externalLinks ?? true,\n backToTop: input.backToTop ?? true,\n };\n}\n\n/**\n * Apply the same native reader-chrome HTML transform used by the built-in SSG.\n *\n * This rewrites rendered article HTML. It wraps fenced `<pre>` blocks for copy\n * controls and decorates outbound links when enabled. Back-to-top remains a\n * document-shell control, so it is exposed through the asset helpers.\n */\nexport function applyReaderChromeHtml(html: string, input: ReaderChromeInput = true): string {\n const chrome = resolveReaderChromeInput(input);\n if (!chrome || (!chrome.copy && !chrome.externalLinks)) {\n return html;\n }\n return importNapiModuleSync().applySsgReaderChromeHtml(html, {\n copy: chrome.copy,\n externalLinks: chrome.externalLinks,\n backToTop: chrome.backToTop,\n });\n}\n\n/**\n * Root attributes consumed by the browser runtime.\n */\nexport function readerChromeAttributes(\n input: ReaderChromeInput = true,\n): Readonly<Record<string, \"\">> {\n const chrome = resolveReaderChromeInput(input);\n if (!readerChromeIsEnabled(chrome)) {\n return EMPTY_ATTRIBUTES;\n }\n\n return {\n \"data-ox-reader-chrome\": \"\",\n ...(chrome.copy ? { \"data-ox-copy\": \"\" as const } : {}),\n ...(chrome.externalLinks ? { \"data-ox-external-links\": \"\" as const } : {}),\n ...(chrome.backToTop ? { \"data-ox-back-to-top\": \"\" as const } : {}),\n };\n}\n\n/**\n * Render the root attributes as a leading-space HTML fragment.\n *\n * Useful in template strings: `<article${renderReaderChromeAttributes()}>`.\n */\nexport function renderReaderChromeAttributes(input: ReaderChromeInput = true): string {\n return Object.keys(readerChromeAttributes(input))\n .map((name) => ` ${name}`)\n .join(\"\");\n}\n\n/**\n * CSS shared by the built-in SSG and custom hosts.\n */\nexport function readerChromeCss(input: ReaderChromeInput = true): string {\n return readerChromeIsEnabled(resolveReaderChromeInput(input))\n ? importNapiModuleSync().getSsgReaderChromeCss()\n : \"\";\n}\n\n/**\n * Auto-initializing script shared by the built-in SSG and custom hosts.\n */\nexport function readerChromeScript(input: ReaderChromeInput = true): string {\n return readerChromeNeedsJs(resolveReaderChromeInput(input))\n ? importNapiModuleSync().getSsgReaderChromeScript()\n : \"\";\n}\n\n/**\n * Inline stylesheet tag for hosts that do not import\n * `@ox-content/vite-plugin/styles/reader-chrome.css`.\n */\nexport function renderReaderChromeStyleTag(input: ReaderChromeInput = true): string {\n const css = readerChromeCss(input);\n return css ? `<style data-ox-style=\"reader-chrome\">\\n${css}</style>` : \"\";\n}\n\n/**\n * Inline auto-init script for static hosts that do not bundle\n * `@ox-content/vite-plugin/reader-chrome/client`.\n */\nexport function renderReaderChromeScriptTag(input: ReaderChromeInput = true): string {\n const js = readerChromeScript(input);\n return js ? `<script data-ox-script=\"reader-chrome\">\\n${js}</script>` : \"\";\n}\n\nexport function readerChromeIsEnabled(\n chrome: ResolvedReaderChrome,\n): chrome is Exclude<ResolvedReaderChrome, false> {\n return Boolean(chrome && (chrome.copy || chrome.externalLinks || chrome.backToTop));\n}\n\nexport function readerChromeNeedsJs(chrome: ResolvedReaderChrome): boolean {\n return Boolean(chrome && (chrome.copy || chrome.backToTop));\n}\n"],"mappings":";;AASA,MAAM,mBAAmB,OAAO,OAAO,CAAC,CAAC;;;;;;;;AASzC,SAAgB,yBAAyB,QAA2B,MAA4B;CAC9F,IAAI,CAAC,OACH,OAAO;CAET,IAAI,UAAU,MACZ,OAAO;EAAE,MAAM;EAAM,eAAe;EAAM,WAAW;CAAK;CAE5D,OAAO;EACL,MAAM,MAAM,QAAQ;EACpB,eAAe,MAAM,iBAAiB;EACtC,WAAW,MAAM,aAAa;CAChC;AACF;;;;;;;;AASA,SAAgB,sBAAsB,MAAc,QAA2B,MAAc;CAC3F,MAAM,SAAS,yBAAyB,KAAK;CAC7C,IAAI,CAAC,UAAW,CAAC,OAAO,QAAQ,CAAC,OAAO,eACtC,OAAO;CAET,OAAO,qBAAqB,CAAC,CAAC,yBAAyB,MAAM;EAC3D,MAAM,OAAO;EACb,eAAe,OAAO;EACtB,WAAW,OAAO;CACpB,CAAC;AACH;;;;AAKA,SAAgB,uBACd,QAA2B,MACG;CAC9B,MAAM,SAAS,yBAAyB,KAAK;CAC7C,IAAI,CAAC,sBAAsB,MAAM,GAC/B,OAAO;CAGT,OAAO;EACL,yBAAyB;EACzB,GAAI,OAAO,OAAO,EAAE,gBAAgB,GAAY,IAAI,CAAC;EACrD,GAAI,OAAO,gBAAgB,EAAE,0BAA0B,GAAY,IAAI,CAAC;EACxE,GAAI,OAAO,YAAY,EAAE,uBAAuB,GAAY,IAAI,CAAC;CACnE;AACF;;;;;;AAOA,SAAgB,6BAA6B,QAA2B,MAAc;CACpF,OAAO,OAAO,KAAK,uBAAuB,KAAK,CAAC,CAAC,CAC9C,KAAK,SAAS,IAAI,MAAM,CAAC,CACzB,KAAK,EAAE;AACZ;;;;AAKA,SAAgB,gBAAgB,QAA2B,MAAc;CACvE,OAAO,sBAAsB,yBAAyB,KAAK,CAAC,IACxD,qBAAqB,CAAC,CAAC,sBAAsB,IAC7C;AACN;;;;AAKA,SAAgB,mBAAmB,QAA2B,MAAc;CAC1E,OAAO,oBAAoB,yBAAyB,KAAK,CAAC,IACtD,qBAAqB,CAAC,CAAC,yBAAyB,IAChD;AACN;;;;;AAMA,SAAgB,2BAA2B,QAA2B,MAAc;CAClF,MAAM,MAAM,gBAAgB,KAAK;CACjC,OAAO,MAAM,0CAA0C,IAAI,YAAY;AACzE;;;;;AAMA,SAAgB,4BAA4B,QAA2B,MAAc;CACnF,MAAM,KAAK,mBAAmB,KAAK;CACnC,OAAO,KAAK,4CAA4C,GAAG,cAAa;AAC1E;AAEA,SAAgB,sBACd,QACgD;CAChD,OAAO,QAAQ,WAAW,OAAO,QAAQ,OAAO,iBAAiB,OAAO,UAAU;AACpF;AAEA,SAAgB,oBAAoB,QAAuC;CACzE,OAAO,QAAQ,WAAW,OAAO,QAAQ,OAAO,UAAU;AAC5D"}
|
package/dist/styles/all.css
CHANGED
package/dist/styles/core.css
CHANGED
|
@@ -1963,11 +1963,52 @@ a:hover {
|
|
|
1963
1963
|
.ox-breadcrumbs-item:not(:last-child)::after {
|
|
1964
1964
|
margin-inline: 0.25rem;
|
|
1965
1965
|
}
|
|
1966
|
+
/* The desktop rhythm scaled for a phone: body blocks 0.875rem apart, framed
|
|
1967
|
+
* blocks 1rem, headings still clearing more above than below. The steps keep
|
|
1968
|
+
* the same relationships, just sized for a 360-420px column. */
|
|
1966
1969
|
.content h1 {
|
|
1967
1970
|
font-size: 1.5rem;
|
|
1971
|
+
/* 1.08 is a display line-height for a title that never wraps. A mobile
|
|
1972
|
+
* title almost always wraps, and at 24px those lines collide. */
|
|
1973
|
+
line-height: 1.25;
|
|
1974
|
+
letter-spacing: -0.02em;
|
|
1975
|
+
margin-bottom: 0.75rem;
|
|
1968
1976
|
}
|
|
1969
1977
|
.content h2 {
|
|
1970
|
-
font-size: 1.
|
|
1978
|
+
font-size: 1.25rem;
|
|
1979
|
+
margin-top: 2rem;
|
|
1980
|
+
margin-bottom: 0.6rem;
|
|
1981
|
+
}
|
|
1982
|
+
/* h2 was scaled down here and h3 was not, so an h3 rendered larger than the
|
|
1983
|
+
* h2 above it and the section hierarchy read backwards. */
|
|
1984
|
+
.content h3 {
|
|
1985
|
+
font-size: 1.0625rem;
|
|
1986
|
+
margin-top: 1.5rem;
|
|
1987
|
+
margin-bottom: 0.4rem;
|
|
1988
|
+
}
|
|
1989
|
+
.content h2 + h3 {
|
|
1990
|
+
margin-top: 1rem;
|
|
1991
|
+
}
|
|
1992
|
+
.content h4 {
|
|
1993
|
+
font-size: 0.9375rem;
|
|
1994
|
+
margin-top: 1.15rem;
|
|
1995
|
+
margin-bottom: 0.3rem;
|
|
1996
|
+
}
|
|
1997
|
+
.content p {
|
|
1998
|
+
margin-bottom: 0.875rem;
|
|
1999
|
+
}
|
|
2000
|
+
.content ul,
|
|
2001
|
+
.content ol {
|
|
2002
|
+
margin: 0.875rem 0;
|
|
2003
|
+
padding-left: 1.25rem;
|
|
2004
|
+
}
|
|
2005
|
+
.content blockquote,
|
|
2006
|
+
.content table,
|
|
2007
|
+
.content figure,
|
|
2008
|
+
/* The code wrapper is also styled by a sheet concatenated after this one, so
|
|
2009
|
+
* it needs the extra specificity to win. */
|
|
2010
|
+
.content .ox-code {
|
|
2011
|
+
margin-block: 1rem;
|
|
1971
2012
|
}
|
|
1972
2013
|
.content code {
|
|
1973
2014
|
font-size: 0.82em;
|
|
@@ -2180,6 +2221,17 @@ a:hover {
|
|
|
2180
2221
|
.content h1 {
|
|
2181
2222
|
font-size: 1.35rem;
|
|
2182
2223
|
}
|
|
2224
|
+
.content h2 {
|
|
2225
|
+
font-size: 1.15rem;
|
|
2226
|
+
margin-top: 1.75rem;
|
|
2227
|
+
}
|
|
2228
|
+
.content h3 {
|
|
2229
|
+
font-size: 1rem;
|
|
2230
|
+
margin-top: 1.25rem;
|
|
2231
|
+
}
|
|
2232
|
+
.content h4 {
|
|
2233
|
+
font-size: 0.9rem;
|
|
2234
|
+
}
|
|
2183
2235
|
.content code {
|
|
2184
2236
|
font-size: 0.8em;
|
|
2185
2237
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
.ox-code {
|
|
2
|
-
--ox-copy-reserved-inline-size:
|
|
2
|
+
--ox-copy-reserved-inline-size: max(
|
|
3
|
+
2rem,
|
|
4
|
+
calc(var(--ox-copy-control-size, 1.75rem) + var(--ox-copy-inset, 0.5rem) - 0.25rem)
|
|
5
|
+
);
|
|
3
6
|
position: relative;
|
|
4
7
|
margin: 1.25rem 0;
|
|
5
8
|
}
|
|
@@ -17,12 +20,12 @@
|
|
|
17
20
|
}
|
|
18
21
|
.ox-copy {
|
|
19
22
|
position: absolute;
|
|
20
|
-
inset-block-start: 0.5rem;
|
|
21
|
-
inset-inline-end: 0.5rem;
|
|
23
|
+
inset-block-start: var(--ox-copy-inset, 0.5rem);
|
|
24
|
+
inset-inline-end: var(--ox-copy-inset, 0.5rem);
|
|
22
25
|
z-index: 1;
|
|
23
26
|
display: grid;
|
|
24
|
-
width: 1.75rem;
|
|
25
|
-
height: 1.75rem;
|
|
27
|
+
width: var(--ox-copy-control-size, 1.75rem);
|
|
28
|
+
height: var(--ox-copy-control-size, 1.75rem);
|
|
26
29
|
padding: 0;
|
|
27
30
|
place-items: center;
|
|
28
31
|
border: 1px solid transparent;
|
|
@@ -41,16 +44,16 @@
|
|
|
41
44
|
z-index: 2;
|
|
42
45
|
min-width: 0;
|
|
43
46
|
min-height: 0;
|
|
44
|
-
width: 1.75rem;
|
|
45
|
-
height: 1.75rem;
|
|
47
|
+
width: var(--ox-copy-control-size, 1.75rem);
|
|
48
|
+
height: var(--ox-copy-control-size, 1.75rem);
|
|
46
49
|
padding: 0;
|
|
47
50
|
font: inherit;
|
|
48
51
|
line-height: 1;
|
|
49
52
|
}
|
|
50
53
|
.ox-copy::before {
|
|
51
54
|
content: "";
|
|
52
|
-
width: 0.8125rem;
|
|
53
|
-
height: 0.8125rem;
|
|
55
|
+
width: var(--ox-copy-icon-size, 0.8125rem);
|
|
56
|
+
height: var(--ox-copy-icon-size, 0.8125rem);
|
|
54
57
|
background-color: currentColor;
|
|
55
58
|
-webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='9' y='9' width='11' height='11' rx='2'/><path d='M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3'/></svg>")
|
|
56
59
|
center / contain no-repeat;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* Same-document colour-scheme change. Every rule is scoped to an attribute the
|
|
2
|
+
runtime sets for the length of one theme toggle, so none of it can reach the
|
|
3
|
+
cross-document `@view-transition` used for MPA navigation — that transition
|
|
4
|
+
needs the UA cross-fade left alone. */
|
|
5
|
+
|
|
6
|
+
/* The circle is drawn by clipping one snapshot over the other, so the UA
|
|
7
|
+
cross-fade has to stop first: two half-opacity snapshots would show the clip
|
|
8
|
+
through the layer beneath it. */
|
|
9
|
+
:root[data-ox-theme-transition]::view-transition-old(root),
|
|
10
|
+
:root[data-ox-theme-transition]::view-transition-new(root) {
|
|
11
|
+
animation: none;
|
|
12
|
+
mix-blend-mode: normal;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Whichever snapshot the circle is drawn on has to sit on top of the other. */
|
|
16
|
+
:root[data-ox-theme-transition="expand"]::view-transition-old(root) {
|
|
17
|
+
z-index: 1;
|
|
18
|
+
}
|
|
19
|
+
:root[data-ox-theme-transition="expand"]::view-transition-new(root) {
|
|
20
|
+
z-index: 2;
|
|
21
|
+
}
|
|
22
|
+
:root[data-ox-theme-transition="shrink"]::view-transition-old(root) {
|
|
23
|
+
z-index: 2;
|
|
24
|
+
}
|
|
25
|
+
:root[data-ox-theme-transition="shrink"]::view-transition-new(root) {
|
|
26
|
+
z-index: 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Element-level colour transitions would animate underneath the snapshot and
|
|
30
|
+
bleed the old palette into the new one. The runtime holds this attribute
|
|
31
|
+
only while the theme is mutated, and drops it once both snapshots exist. */
|
|
32
|
+
:root[data-ox-theme-transition-suppress],
|
|
33
|
+
:root[data-ox-theme-transition-suppress] *,
|
|
34
|
+
:root[data-ox-theme-transition-suppress] *::before,
|
|
35
|
+
:root[data-ox-theme-transition-suppress] *::after {
|
|
36
|
+
transition: none !important;
|
|
37
|
+
}
|
|
@@ -149,9 +149,10 @@
|
|
|
149
149
|
--ox-tweet-verified-gold: rgb(226, 179, 64);
|
|
150
150
|
--ox-tweet-verified-gray: rgb(130, 154, 171);
|
|
151
151
|
--ox-tweet-icon-x: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.746l7.727-8.83L1.254 2.25H8.08l4.257 5.622L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z'/%3E%3C/svg%3E");
|
|
152
|
-
--ox-tweet-icon-like: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='
|
|
153
|
-
--ox-tweet-icon-reply: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M1.751 10c0-4.42 3.584-8 8.005-8h4.366c4.49 0 8.129 3.64 8.129 8.13 0 2.96-1.607 5.68-4.196 7.11l-8.054 4.46v-3.69h-.067c-4.49.1-8.183-3.51-8.183-8.
|
|
154
|
-
--ox-tweet-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='
|
|
152
|
+
--ox-tweet-icon-like: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M20.884 13.19c-1.351 2.48-4.001 5.12-8.379 7.67l-.503.3-.504-.3c-4.379-2.55-7.029-5.19-8.382-7.67-1.36-2.5-1.41-4.86-.514-6.67.887-1.79 2.647-2.91 4.601-3.01 1.651-.09 3.368.56 4.798 2.01 1.429-1.45 3.146-2.1 4.796-2.01 1.954.1 3.714 1.22 4.601 3.01.896 1.81.846 4.17-.514 6.67z'/%3E%3C/svg%3E");
|
|
153
|
+
--ox-tweet-icon-reply: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M1.751 10c0-4.42 3.584-8 8.005-8h4.366c4.49 0 8.129 3.64 8.129 8.13 0 2.96-1.607 5.68-4.196 7.11l-8.054 4.46v-3.69h-.067c-4.49.1-8.183-3.51-8.183-8.01z'/%3E%3C/svg%3E");
|
|
154
|
+
--ox-tweet-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M18.36 5.64c-1.95-1.96-5.11-1.96-7.07 0L9.88 7.05 8.46 5.64l1.42-1.42c2.73-2.73 7.16-2.73 9.9 0 2.73 2.74 2.73 7.17 0 9.9l-1.42 1.42-1.41-1.42 1.41-1.41c1.96-1.96 1.96-5.12 0-7.07zm-2.12 3.53l-7.07 7.07-1.41-1.41 7.07-7.07 1.41 1.41zm-12.02.71l1.42-1.42 1.41 1.42-1.41 1.41c-1.96 1.96-1.96 5.12 0 7.07 1.95 1.96 5.11 1.96 7.07 0l1.41-1.41 1.42 1.41-1.42 1.42c-2.73 2.73-7.16 2.73-9.9 0-2.73-2.74-2.73-7.17 0-9.9z'/%3E%3C/svg%3E");
|
|
155
|
+
--ox-tweet-icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M9.64 18.952l-5.55-4.861 1.317-1.504 3.951 3.459 8.459-10.948L19.4 6.32 9.64 18.952z'/%3E%3C/svg%3E");
|
|
155
156
|
--ox-tweet-icon-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M13.5 8.5c0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5S11.17 7 12 7s1.5.67 1.5 1.5zM13 17v-5h-2v5h2zm-1 5.25c5.66 0 10.25-4.59 10.25-10.25S17.66 1.75 12 1.75 1.75 6.34 1.75 12 6.34 22.25 12 22.25zM20.25 12c0 4.56-3.69 8.25-8.25 8.25S3.75 16.56 3.75 12 7.44 3.75 12 3.75s8.25 3.69 8.25 8.25z'/%3E%3C/svg%3E");
|
|
156
157
|
--ox-tweet-icon-badge: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M22.25 12c0-1.43-.88-2.67-2.19-3.34.46-1.39.2-2.9-.81-3.91s-2.52-1.27-3.91-.81c-.66-1.31-1.91-2.19-3.34-2.19s-2.67.88-3.34 2.19c-1.4-.46-2.91-.2-3.91.81s-1.27 2.52-.81 3.91c-1.31.67-2.19 1.91-2.19 3.34s.88 2.67 2.19 3.34c-.46 1.39-.2 2.9.81 3.91s2.52 1.27 3.91.81c.67 1.31 1.91 2.19 3.34 2.19s2.67-.88 3.34-2.19c1.4.46 2.91.2 3.91-.81s1.27-2.52.81-3.91c1.31-.67 2.19-1.91 2.19-3.34zm-11.71 4.2L6.8 12.46l1.41-1.42 2.26 2.26 4.8-5.23 1.47 1.36-6.2 6.77z'/%3E%3C/svg%3E");
|
|
157
158
|
box-sizing: border-box;
|
|
@@ -316,6 +317,11 @@
|
|
|
316
317
|
mask-image: var(--ox-tweet-icon-copy);
|
|
317
318
|
}
|
|
318
319
|
|
|
320
|
+
.ox-tweet--full .ox-tweet__action--copy[data-ox-tweet-copied] .ox-tweet__icon--copy {
|
|
321
|
+
-webkit-mask-image: var(--ox-tweet-icon-check);
|
|
322
|
+
mask-image: var(--ox-tweet-icon-check);
|
|
323
|
+
}
|
|
324
|
+
|
|
319
325
|
.ox-tweet--full .ox-tweet__icon--info {
|
|
320
326
|
-webkit-mask-image: var(--ox-tweet-icon-info);
|
|
321
327
|
mask-image: var(--ox-tweet-icon-info);
|
|
@@ -535,6 +541,18 @@
|
|
|
535
541
|
display: none;
|
|
536
542
|
}
|
|
537
543
|
|
|
544
|
+
.ox-tweet--full .ox-tweet__copy-status {
|
|
545
|
+
position: absolute;
|
|
546
|
+
width: 1px;
|
|
547
|
+
height: 1px;
|
|
548
|
+
padding: 0;
|
|
549
|
+
margin: -1px;
|
|
550
|
+
overflow: hidden;
|
|
551
|
+
clip: rect(0, 0, 0, 0);
|
|
552
|
+
white-space: nowrap;
|
|
553
|
+
border: 0;
|
|
554
|
+
}
|
|
555
|
+
|
|
538
556
|
.ox-tweet--full .ox-tweet__action--copy[data-ox-tweet-copied] .ox-tweet__copy-text {
|
|
539
557
|
display: none;
|
|
540
558
|
}
|
|
@@ -545,7 +563,7 @@
|
|
|
545
563
|
|
|
546
564
|
.ox-tweet--full .ox-tweet__replies {
|
|
547
565
|
padding: 0.25rem 0;
|
|
548
|
-
margin: 0
|
|
566
|
+
margin: 0;
|
|
549
567
|
}
|
|
550
568
|
|
|
551
569
|
.ox-tweet--full .ox-tweet__replies-link {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Generated from crates/ox_content_ssg/src/html/theme_transition_runtime.js.
|
|
2
|
+
// Run npm/vite-plugin-ox-content/scripts/build-theme-transition-client.mjs.
|
|
3
|
+
|
|
4
|
+
"use strict";
|
|
5
|
+
// Circular reveal for a same-document colour-scheme change.
|
|
6
|
+
//
|
|
7
|
+
// Prior art: @hooray's VitePress implementation, by way of @ryoppippi's
|
|
8
|
+
// svelte-fancy-darkmode, which this is a framework-neutral port of.
|
|
9
|
+
//
|
|
10
|
+
// This wraps a *same-document* theme mutation. It is unrelated to the
|
|
11
|
+
// cross-document `@view-transition` used for MPA navigation, and the CSS it
|
|
12
|
+
// depends on is scoped to `data-ox-theme-transition` so the two lifecycles
|
|
13
|
+
// cannot reach each other's snapshots.
|
|
14
|
+
|
|
15
|
+
const OX_THEME_TRANSITION_ATTR = "data-ox-theme-transition";
|
|
16
|
+
const OX_THEME_TRANSITION_SUPPRESS_ATTR = "data-ox-theme-transition-suppress";
|
|
17
|
+
|
|
18
|
+
let oxThemeTransitionCurrent = null;
|
|
19
|
+
|
|
20
|
+
function oxThemeTransitionRoot() {
|
|
21
|
+
return typeof document === "undefined" ? null : document.documentElement;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function oxThemeTransitionReducedMotion() {
|
|
25
|
+
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function oxThemeTransitionSupported(root) {
|
|
29
|
+
return Boolean(
|
|
30
|
+
root &&
|
|
31
|
+
typeof document !== "undefined" &&
|
|
32
|
+
typeof document.startViewTransition === "function" &&
|
|
33
|
+
typeof root.animate === "function",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// A keyboard or programmatic activation still produces a click event, but with
|
|
38
|
+
// no meaningful coordinates: `detail` is 0 and clientX/clientY report 0. Only
|
|
39
|
+
// a real pointer press is trusted, so the reveal never starts from the corner
|
|
40
|
+
// of the screen when someone tabs to the button.
|
|
41
|
+
function oxThemeTransitionOrigin(event) {
|
|
42
|
+
const viewportCentre = {
|
|
43
|
+
x: (typeof innerWidth === "number" ? innerWidth : 0) / 2,
|
|
44
|
+
y: (typeof innerHeight === "number" ? innerHeight : 0) / 2,
|
|
45
|
+
};
|
|
46
|
+
if (!event) return viewportCentre;
|
|
47
|
+
|
|
48
|
+
const fromPointer =
|
|
49
|
+
typeof event.clientX === "number" &&
|
|
50
|
+
typeof event.clientY === "number" &&
|
|
51
|
+
(event.detail > 0 || (typeof event.pointerType === "string" && event.pointerType !== ""));
|
|
52
|
+
if (fromPointer) return { x: event.clientX, y: event.clientY };
|
|
53
|
+
|
|
54
|
+
const target = event.currentTarget ?? event.target;
|
|
55
|
+
if (target && typeof target.getBoundingClientRect === "function") {
|
|
56
|
+
const rect = target.getBoundingClientRect();
|
|
57
|
+
if (rect.width > 0 || rect.height > 0) {
|
|
58
|
+
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return viewportCentre;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The circle has to reach whichever viewport corner is furthest away, or the
|
|
65
|
+
// old theme stays visible in one corner when the animation ends.
|
|
66
|
+
function oxThemeTransitionRadius(origin) {
|
|
67
|
+
const width = typeof innerWidth === "number" ? innerWidth : 0;
|
|
68
|
+
const height = typeof innerHeight === "number" ? innerHeight : 0;
|
|
69
|
+
return Math.hypot(Math.max(origin.x, width - origin.x), Math.max(origin.y, height - origin.y));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Element-level colour transitions would otherwise animate underneath the
|
|
73
|
+
// snapshot and bleed the old palette into the new one. They are suppressed
|
|
74
|
+
// only while the mutation happens, and restored as soon as both snapshots
|
|
75
|
+
// have been captured.
|
|
76
|
+
function oxThemeTransitionSuppress(root) {
|
|
77
|
+
root.setAttribute(OX_THEME_TRANSITION_SUPPRESS_ATTR, "");
|
|
78
|
+
return () => {
|
|
79
|
+
// Reading a layout property flushes the suppressed styles before they are
|
|
80
|
+
// allowed to animate again.
|
|
81
|
+
void root.offsetHeight;
|
|
82
|
+
root.removeAttribute(OX_THEME_TRANSITION_SUPPRESS_ATTR);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// eslint-disable-next-line no-unused-vars
|
|
87
|
+
function applyThemeTransition(options) {
|
|
88
|
+
const settings = options ?? {};
|
|
89
|
+
const apply = settings.apply;
|
|
90
|
+
if (typeof apply !== "function") return Promise.resolve();
|
|
91
|
+
|
|
92
|
+
const root = oxThemeTransitionRoot();
|
|
93
|
+
if (!oxThemeTransitionSupported(root) || oxThemeTransitionReducedMotion()) {
|
|
94
|
+
apply();
|
|
95
|
+
return Promise.resolve();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// A second toggle while one is still running would capture a half-animated
|
|
99
|
+
// snapshot. Settle the running one first; its cleanup is synchronous.
|
|
100
|
+
if (oxThemeTransitionCurrent) {
|
|
101
|
+
const running = oxThemeTransitionCurrent;
|
|
102
|
+
oxThemeTransitionCurrent = null;
|
|
103
|
+
if (typeof running.skipTransition === "function") running.skipTransition();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Going dark expands the incoming snapshot over the outgoing one; going
|
|
107
|
+
// light shrinks the outgoing snapshot away to reveal the incoming one
|
|
108
|
+
// underneath. Either way the circle grows out of, or collapses into, the
|
|
109
|
+
// point the reader activated.
|
|
110
|
+
const expanding = settings.nextTheme !== "light";
|
|
111
|
+
const origin = oxThemeTransitionOrigin(settings.event);
|
|
112
|
+
const radius = oxThemeTransitionRadius(origin);
|
|
113
|
+
const duration = typeof settings.duration === "number" ? settings.duration : 420;
|
|
114
|
+
const easing = typeof settings.easing === "string" ? settings.easing : "ease-in-out";
|
|
115
|
+
|
|
116
|
+
root.setAttribute(OX_THEME_TRANSITION_ATTR, expanding ? "expand" : "shrink");
|
|
117
|
+
|
|
118
|
+
let restore = () => {};
|
|
119
|
+
const transition = document.startViewTransition(() => {
|
|
120
|
+
restore = oxThemeTransitionSuppress(root);
|
|
121
|
+
apply();
|
|
122
|
+
});
|
|
123
|
+
oxThemeTransitionCurrent = transition;
|
|
124
|
+
|
|
125
|
+
const clipFrom = `circle(0px at ${origin.x}px ${origin.y}px)`;
|
|
126
|
+
const clipTo = `circle(${radius}px at ${origin.x}px ${origin.y}px)`;
|
|
127
|
+
|
|
128
|
+
transition.ready.then(
|
|
129
|
+
() => {
|
|
130
|
+
restore();
|
|
131
|
+
root.animate(
|
|
132
|
+
{ clipPath: expanding ? [clipFrom, clipTo] : [clipTo, clipFrom] },
|
|
133
|
+
{
|
|
134
|
+
duration,
|
|
135
|
+
easing,
|
|
136
|
+
pseudoElement: expanding ? "::view-transition-new(root)" : "::view-transition-old(root)",
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
},
|
|
140
|
+
() => {
|
|
141
|
+
// A skipped or unsupported transition still has to hand the suppressed
|
|
142
|
+
// styles back.
|
|
143
|
+
restore();
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const settled = () => {
|
|
148
|
+
restore();
|
|
149
|
+
// A rapid second toggle skips this one, which lands here *after* the newer
|
|
150
|
+
// transition has already claimed the attribute. Only the transition still
|
|
151
|
+
// holding the slot is allowed to clear it.
|
|
152
|
+
if (oxThemeTransitionCurrent !== transition) return;
|
|
153
|
+
oxThemeTransitionCurrent = null;
|
|
154
|
+
root.removeAttribute(OX_THEME_TRANSITION_ATTR);
|
|
155
|
+
};
|
|
156
|
+
// `finished` rejects when a transition is skipped, which is a normal outcome
|
|
157
|
+
// here — both arms clean up and neither leaves an unhandled rejection.
|
|
158
|
+
return transition.finished.then(settled, settled);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { applyThemeTransition };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ThemeTransitionOptions {
|
|
2
|
+
/** Activation event. Supplies the reveal origin; omit for the viewport centre. */
|
|
3
|
+
event?: Event;
|
|
4
|
+
/** Theme being switched to. `"light"` collapses the circle, anything else grows it. */
|
|
5
|
+
nextTheme?: string;
|
|
6
|
+
/** Synchronous theme mutation, run while both snapshots are captured. */
|
|
7
|
+
apply: () => void;
|
|
8
|
+
/** Reveal duration in milliseconds. Defaults to 420. */
|
|
9
|
+
duration?: number;
|
|
10
|
+
/** Reveal easing. Defaults to `"ease-in-out"`. */
|
|
11
|
+
easing?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Runs `apply` inside a circular view transition, or immediately when View
|
|
16
|
+
* Transitions are unavailable or the reader asked for reduced motion.
|
|
17
|
+
*
|
|
18
|
+
* Resolves once the transition has settled. A skipped transition resolves too,
|
|
19
|
+
* so the returned promise never rejects.
|
|
20
|
+
*/
|
|
21
|
+
export declare function applyThemeTransition(options: ThemeTransitionOptions): Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ThemeTransitionOptions {
|
|
2
|
+
/** Activation event. Supplies the reveal origin; omit for the viewport centre. */
|
|
3
|
+
event?: Event;
|
|
4
|
+
/** Theme being switched to. `"light"` collapses the circle, anything else grows it. */
|
|
5
|
+
nextTheme?: string;
|
|
6
|
+
/** Synchronous theme mutation, run while both snapshots are captured. */
|
|
7
|
+
apply: () => void;
|
|
8
|
+
/** Reveal duration in milliseconds. Defaults to 420. */
|
|
9
|
+
duration?: number;
|
|
10
|
+
/** Reveal easing. Defaults to `"ease-in-out"`. */
|
|
11
|
+
easing?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Runs `apply` inside a circular view transition, or immediately when View
|
|
16
|
+
* Transitions are unavailable or the reader asked for reduced motion.
|
|
17
|
+
*
|
|
18
|
+
* Resolves once the transition has settled. A skipped transition resolves too,
|
|
19
|
+
* so the returned promise never rejects.
|
|
20
|
+
*/
|
|
21
|
+
export declare function applyThemeTransition(options: ThemeTransitionOptions): Promise<void>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Generated from crates/ox_content_ssg/src/html/theme_transition_runtime.js.
|
|
2
|
+
// Run npm/vite-plugin-ox-content/scripts/build-theme-transition-client.mjs.
|
|
3
|
+
|
|
4
|
+
// Circular reveal for a same-document colour-scheme change.
|
|
5
|
+
//
|
|
6
|
+
// Prior art: @hooray's VitePress implementation, by way of @ryoppippi's
|
|
7
|
+
// svelte-fancy-darkmode, which this is a framework-neutral port of.
|
|
8
|
+
//
|
|
9
|
+
// This wraps a *same-document* theme mutation. It is unrelated to the
|
|
10
|
+
// cross-document `@view-transition` used for MPA navigation, and the CSS it
|
|
11
|
+
// depends on is scoped to `data-ox-theme-transition` so the two lifecycles
|
|
12
|
+
// cannot reach each other's snapshots.
|
|
13
|
+
|
|
14
|
+
const OX_THEME_TRANSITION_ATTR = "data-ox-theme-transition";
|
|
15
|
+
const OX_THEME_TRANSITION_SUPPRESS_ATTR = "data-ox-theme-transition-suppress";
|
|
16
|
+
|
|
17
|
+
let oxThemeTransitionCurrent = null;
|
|
18
|
+
|
|
19
|
+
function oxThemeTransitionRoot() {
|
|
20
|
+
return typeof document === "undefined" ? null : document.documentElement;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function oxThemeTransitionReducedMotion() {
|
|
24
|
+
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function oxThemeTransitionSupported(root) {
|
|
28
|
+
return Boolean(
|
|
29
|
+
root &&
|
|
30
|
+
typeof document !== "undefined" &&
|
|
31
|
+
typeof document.startViewTransition === "function" &&
|
|
32
|
+
typeof root.animate === "function",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A keyboard or programmatic activation still produces a click event, but with
|
|
37
|
+
// no meaningful coordinates: `detail` is 0 and clientX/clientY report 0. Only
|
|
38
|
+
// a real pointer press is trusted, so the reveal never starts from the corner
|
|
39
|
+
// of the screen when someone tabs to the button.
|
|
40
|
+
function oxThemeTransitionOrigin(event) {
|
|
41
|
+
const viewportCentre = {
|
|
42
|
+
x: (typeof innerWidth === "number" ? innerWidth : 0) / 2,
|
|
43
|
+
y: (typeof innerHeight === "number" ? innerHeight : 0) / 2,
|
|
44
|
+
};
|
|
45
|
+
if (!event) return viewportCentre;
|
|
46
|
+
|
|
47
|
+
const fromPointer =
|
|
48
|
+
typeof event.clientX === "number" &&
|
|
49
|
+
typeof event.clientY === "number" &&
|
|
50
|
+
(event.detail > 0 || (typeof event.pointerType === "string" && event.pointerType !== ""));
|
|
51
|
+
if (fromPointer) return { x: event.clientX, y: event.clientY };
|
|
52
|
+
|
|
53
|
+
const target = event.currentTarget ?? event.target;
|
|
54
|
+
if (target && typeof target.getBoundingClientRect === "function") {
|
|
55
|
+
const rect = target.getBoundingClientRect();
|
|
56
|
+
if (rect.width > 0 || rect.height > 0) {
|
|
57
|
+
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return viewportCentre;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The circle has to reach whichever viewport corner is furthest away, or the
|
|
64
|
+
// old theme stays visible in one corner when the animation ends.
|
|
65
|
+
function oxThemeTransitionRadius(origin) {
|
|
66
|
+
const width = typeof innerWidth === "number" ? innerWidth : 0;
|
|
67
|
+
const height = typeof innerHeight === "number" ? innerHeight : 0;
|
|
68
|
+
return Math.hypot(Math.max(origin.x, width - origin.x), Math.max(origin.y, height - origin.y));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Element-level colour transitions would otherwise animate underneath the
|
|
72
|
+
// snapshot and bleed the old palette into the new one. They are suppressed
|
|
73
|
+
// only while the mutation happens, and restored as soon as both snapshots
|
|
74
|
+
// have been captured.
|
|
75
|
+
function oxThemeTransitionSuppress(root) {
|
|
76
|
+
root.setAttribute(OX_THEME_TRANSITION_SUPPRESS_ATTR, "");
|
|
77
|
+
return () => {
|
|
78
|
+
// Reading a layout property flushes the suppressed styles before they are
|
|
79
|
+
// allowed to animate again.
|
|
80
|
+
void root.offsetHeight;
|
|
81
|
+
root.removeAttribute(OX_THEME_TRANSITION_SUPPRESS_ATTR);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// eslint-disable-next-line no-unused-vars
|
|
86
|
+
function applyThemeTransition(options) {
|
|
87
|
+
const settings = options ?? {};
|
|
88
|
+
const apply = settings.apply;
|
|
89
|
+
if (typeof apply !== "function") return Promise.resolve();
|
|
90
|
+
|
|
91
|
+
const root = oxThemeTransitionRoot();
|
|
92
|
+
if (!oxThemeTransitionSupported(root) || oxThemeTransitionReducedMotion()) {
|
|
93
|
+
apply();
|
|
94
|
+
return Promise.resolve();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A second toggle while one is still running would capture a half-animated
|
|
98
|
+
// snapshot. Settle the running one first; its cleanup is synchronous.
|
|
99
|
+
if (oxThemeTransitionCurrent) {
|
|
100
|
+
const running = oxThemeTransitionCurrent;
|
|
101
|
+
oxThemeTransitionCurrent = null;
|
|
102
|
+
if (typeof running.skipTransition === "function") running.skipTransition();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Going dark expands the incoming snapshot over the outgoing one; going
|
|
106
|
+
// light shrinks the outgoing snapshot away to reveal the incoming one
|
|
107
|
+
// underneath. Either way the circle grows out of, or collapses into, the
|
|
108
|
+
// point the reader activated.
|
|
109
|
+
const expanding = settings.nextTheme !== "light";
|
|
110
|
+
const origin = oxThemeTransitionOrigin(settings.event);
|
|
111
|
+
const radius = oxThemeTransitionRadius(origin);
|
|
112
|
+
const duration = typeof settings.duration === "number" ? settings.duration : 420;
|
|
113
|
+
const easing = typeof settings.easing === "string" ? settings.easing : "ease-in-out";
|
|
114
|
+
|
|
115
|
+
root.setAttribute(OX_THEME_TRANSITION_ATTR, expanding ? "expand" : "shrink");
|
|
116
|
+
|
|
117
|
+
let restore = () => {};
|
|
118
|
+
const transition = document.startViewTransition(() => {
|
|
119
|
+
restore = oxThemeTransitionSuppress(root);
|
|
120
|
+
apply();
|
|
121
|
+
});
|
|
122
|
+
oxThemeTransitionCurrent = transition;
|
|
123
|
+
|
|
124
|
+
const clipFrom = `circle(0px at ${origin.x}px ${origin.y}px)`;
|
|
125
|
+
const clipTo = `circle(${radius}px at ${origin.x}px ${origin.y}px)`;
|
|
126
|
+
|
|
127
|
+
transition.ready.then(
|
|
128
|
+
() => {
|
|
129
|
+
restore();
|
|
130
|
+
root.animate(
|
|
131
|
+
{ clipPath: expanding ? [clipFrom, clipTo] : [clipTo, clipFrom] },
|
|
132
|
+
{
|
|
133
|
+
duration,
|
|
134
|
+
easing,
|
|
135
|
+
pseudoElement: expanding ? "::view-transition-new(root)" : "::view-transition-old(root)",
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
() => {
|
|
140
|
+
// A skipped or unsupported transition still has to hand the suppressed
|
|
141
|
+
// styles back.
|
|
142
|
+
restore();
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const settled = () => {
|
|
147
|
+
restore();
|
|
148
|
+
// A rapid second toggle skips this one, which lands here *after* the newer
|
|
149
|
+
// transition has already claimed the attribute. Only the transition still
|
|
150
|
+
// holding the slot is allowed to clear it.
|
|
151
|
+
if (oxThemeTransitionCurrent !== transition) return;
|
|
152
|
+
oxThemeTransitionCurrent = null;
|
|
153
|
+
root.removeAttribute(OX_THEME_TRANSITION_ATTR);
|
|
154
|
+
};
|
|
155
|
+
// `finished` rejects when a transition is skipped, which is a normal outcome
|
|
156
|
+
// here — both arms clean up and neither leaves an unhandled rejection.
|
|
157
|
+
return transition.finished.then(settled, settled);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export { applyThemeTransition };
|