mirror of
https://github.com/adoyle0/leptos-use.git
synced 2025-01-23 00:59:22 -05:00
added better demo integration and styling to book
This commit is contained in:
parent
6a9a0b5927
commit
5057bf0765
31 changed files with 610 additions and 775 deletions
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.3
|
||||||
|
|
||||||
|
- Added `use_scroll`.
|
|
@ -14,6 +14,6 @@ repository = "https://github.com/Synphonyte/leptos-use"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
leptos = "0.3"
|
leptos = "0.3"
|
||||||
web-sys = "0.3"
|
web-sys = { version = "0.3", features = ["ScrollToOptions", "ScrollBehavior"] }
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
|
@ -7,7 +7,6 @@ title = "Leptos-Use Documentation"
|
||||||
|
|
||||||
[output.html]
|
[output.html]
|
||||||
no-section-label = true
|
no-section-label = true
|
||||||
additional-css = ["custom.css"]
|
additional-css = ["src/custom.css", "src/demo.css"]
|
||||||
additional-js = ["demo-iframe.js"]
|
|
||||||
|
|
||||||
[preprocessor.cmdrun]
|
[preprocessor.cmdrun]
|
|
@ -1,15 +0,0 @@
|
||||||
iframe.demo {
|
|
||||||
transition: height 0.25s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-container {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-container > a.demo-source {
|
|
||||||
position: absolute;
|
|
||||||
right: 10px;
|
|
||||||
top: 5px;
|
|
||||||
font-size: 80%;
|
|
||||||
color: rgb(96 165 250);
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
const iframes = Array.prototype.slice.apply(document.getElementsByTagName("iframe"));
|
|
||||||
for (const [i, iframe] of iframes.entries()) {
|
|
||||||
iframe.style.height = iframe.getBoundingClientRect().height + "px";
|
|
||||||
|
|
||||||
iframe.addEventListener('load', () => {
|
|
||||||
const innerBody = window.frames[i].document.body;
|
|
||||||
innerBody.style.overflow = "hidden";
|
|
||||||
|
|
||||||
const resize = () => {
|
|
||||||
if (innerBody.scrollHeight == 0) {
|
|
||||||
window.setTimeout(resize, 50);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
iframe.style.height = innerBody.scrollHeight + "px";
|
|
||||||
}
|
|
||||||
|
|
||||||
window.setTimeout(resize, 50);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -29,6 +29,32 @@ def build_and_copy_demo(category, md_name):
|
||||||
shutil.copytree(example_output_path, target_path,
|
shutil.copytree(example_output_path, target_path,
|
||||||
dirs_exist_ok=True)
|
dirs_exist_ok=True)
|
||||||
|
|
||||||
|
with open(os.path.join(target_path, "index.html"), "r") as f:
|
||||||
|
html = f.read().replace("./demo", f"./{name}/demo")
|
||||||
|
head = html.split("<head>")[1].split("</head>")[0]
|
||||||
|
body = html.split("<body>")[1].split("</body>")[0]
|
||||||
|
|
||||||
|
book_html_path = os.path.join("book", category, f"{name}.html")
|
||||||
|
with open(book_html_path, "r") as f:
|
||||||
|
html = f.read()
|
||||||
|
head_split = html.split("<head>")
|
||||||
|
target_head = head_split[1].split("</head>")[0]
|
||||||
|
body_split = html.split("<body>")[1].split("</body>")
|
||||||
|
target_body = body_split[0]
|
||||||
|
|
||||||
|
with open(book_html_path, "w") as f:
|
||||||
|
f.write(
|
||||||
|
f"""{head_split[0]}
|
||||||
|
<head>
|
||||||
|
{head}
|
||||||
|
{target_head}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{body}
|
||||||
|
{target_body}
|
||||||
|
</body>
|
||||||
|
{body_split[1]}""")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
8
docs/book/src/custom.css
Normal file
8
docs/book/src/custom.css
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.light {
|
||||||
|
--fg: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre > code {
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
66
docs/book/src/demo.css
Normal file
66
docs/book/src/demo.css
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
:root {
|
||||||
|
--brand-color: #EF3939;
|
||||||
|
--brand-color-dark: #9c2525;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container > a.demo-source {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container > a.demo-source > i.fa {
|
||||||
|
font-size: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container {
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: #f6f7f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ayu .demo-container, .navy .demo-container, .coal .demo-container {
|
||||||
|
background-color: #1d1f21;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container button {
|
||||||
|
background-color: var(--brand-color);
|
||||||
|
font-family: inherit;
|
||||||
|
padding: 3px 15px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
color: white;
|
||||||
|
margin: 1rem 0;
|
||||||
|
border-bottom: 2px solid var(--brand-color-dark);
|
||||||
|
text-shadow: 1px 1px 1px var(--brand-color-dark);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container button:hover {
|
||||||
|
background-color: var(--brand-color-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container button:active {
|
||||||
|
border-bottom: 0;
|
||||||
|
border-top: 2px solid var(--brand-color-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container p {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container .note {
|
||||||
|
opacity: 0.7;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
|
@ -34,9 +34,8 @@ def process_line(line, name):
|
||||||
if stripped.startswith("[Link to Demo](https://"):
|
if stripped.startswith("[Link to Demo](https://"):
|
||||||
example_link = stripped.replace("[Link to Demo](", "").replace(")", "")
|
example_link = stripped.replace("[Link to Demo](", "").replace(")", "")
|
||||||
result = f'''<div class="demo-container">
|
result = f'''<div class="demo-container">
|
||||||
<a class="demo-source" href="{example_link}/src/main.rs" target="_blank">source</a>
|
<a class="demo-source" href="{example_link}/src/main.rs" target="_blank">source <i class="fa fa-github"></i></a>
|
||||||
<iframe class="demo" src="{name}/demo/index.html" width="100%" frameborder="0">
|
<div id="demo-anchor"></div>
|
||||||
</iframe>
|
|
||||||
</div>'''
|
</div>'''
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
# Functions
|
# Functions
|
||||||
|
|
||||||
<!-- cmdrun python3 generate_function_overview.py browser -->
|
<!-- cmdrun python3 generate_function_overview.py browser -->
|
||||||
|
|
||||||
|
<!-- cmdrun python3 generate_function_overview.py sensors -->
|
||||||
|
|
||||||
|
<!-- cmdrun python3 generate_function_overview.py utilities -->
|
||||||
|
|
3
docs/book/src/sensors/use_scroll.md
Normal file
3
docs/book/src/sensors/use_scroll.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# use_event_listener
|
||||||
|
|
||||||
|
<!-- cmdrun python3 ../extract_doc_comment.py use_scroll -->
|
|
@ -1,22 +1,16 @@
|
||||||
A simple example for `use_throttle_fn`.
|
A simple example for `use_throttle_fn`.
|
||||||
|
|
||||||
If you don't have it installed already, install [Trunk](https://trunkrs.dev/) and [Tailwind](https://tailwindcss.com/docs/installation)
|
If you don't have it installed already, install [Trunk](https://trunkrs.dev/)
|
||||||
as well as the nightly toolchain for Rust and the wasm32-unknown-unknown target:
|
as well as the nightly toolchain for Rust and the wasm32-unknown-unknown target:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo install trunk
|
cargo install trunk
|
||||||
npm install -D tailwindcss
|
|
||||||
rustup toolchain install nightly
|
rustup toolchain install nightly
|
||||||
rustup target add wasm32-unknown-unknown
|
rustup target add wasm32-unknown-unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, open two terminals. In the first one, run:
|
To run the demo:
|
||||||
|
|
||||||
```
|
|
||||||
npx tailwindcss -i ./input.css -o ./style/output.css --watch
|
|
||||||
```
|
|
||||||
|
|
||||||
In the second one, run:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
trunk serve --open
|
trunk serve --open
|
||||||
|
```
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
[build]
|
[build]
|
||||||
public_url = "/leptos-use/utilities/use_throttle_fn/demo/"
|
public_url = "./demo/"
|
|
@ -1,7 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head></head>
|
||||||
<link data-trunk rel="css" href="style/output.css">
|
<body></body>
|
||||||
</head>
|
|
||||||
<body></body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
|
@ -1,5 +1,6 @@
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_use::use_throttle_fn;
|
use leptos_use::use_throttle_fn;
|
||||||
|
use leptos_use::utils::demo_or_body;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Demo(cx: Scope) -> impl IntoView {
|
fn Demo(cx: Scope) -> impl IntoView {
|
||||||
|
@ -15,12 +16,11 @@ fn Demo(cx: Scope) -> impl IntoView {
|
||||||
set_click_count(click_count() + 1);
|
set_click_count(click_count() + 1);
|
||||||
throttled_fn();
|
throttled_fn();
|
||||||
}
|
}
|
||||||
class="rounded bg-blue-500 hover:bg-blue-400 py-2 px-4 text-white"
|
|
||||||
>
|
>
|
||||||
"Smash me!"
|
"Smash me!"
|
||||||
</button>
|
</button>
|
||||||
<p class="my-2"><small class="block">"Delay is set to 1000ms for this demo."</small></p>
|
<div class="note">"Delay is set to 1000ms for this demo."</div>
|
||||||
<p class="my-3">"Button clicked: " { click_count }</p>
|
<p>"Button clicked: " { click_count }</p>
|
||||||
<p>"Event handler called: " { throttled_count }</p>
|
<p>"Event handler called: " { throttled_count }</p>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,7 @@ fn main() {
|
||||||
_ = console_log::init_with_level(log::Level::Debug);
|
_ = console_log::init_with_level(log::Level::Debug);
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
mount_to_body(|cx| {
|
mount_to(demo_or_body(), |cx| {
|
||||||
view! {cx,
|
view! { cx, <Demo /> }
|
||||||
<div class="p-6 bg-gray-700 text-gray-300">
|
|
||||||
<Demo />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,574 +0,0 @@
|
||||||
/*
|
|
||||||
! tailwindcss v3.3.1 | MIT License | https://tailwindcss.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
|
||||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
|
||||||
*/
|
|
||||||
|
|
||||||
*,
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
/* 1 */
|
|
||||||
border-width: 0;
|
|
||||||
/* 2 */
|
|
||||||
border-style: solid;
|
|
||||||
/* 2 */
|
|
||||||
border-color: #e5e7eb;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
--tw-content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use a consistent sensible line-height in all browsers.
|
|
||||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
|
||||||
3. Use a more readable tab size.
|
|
||||||
4. Use the user's configured `sans` font-family by default.
|
|
||||||
5. Use the user's configured `sans` font-feature-settings by default.
|
|
||||||
6. Use the user's configured `sans` font-variation-settings by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
html {
|
|
||||||
line-height: 1.5;
|
|
||||||
/* 1 */
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
/* 2 */
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
/* 3 */
|
|
||||||
-o-tab-size: 4;
|
|
||||||
tab-size: 4;
|
|
||||||
/* 3 */
|
|
||||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
/* 4 */
|
|
||||||
font-feature-settings: normal;
|
|
||||||
/* 5 */
|
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 6 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove the margin in all browsers.
|
|
||||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
/* 1 */
|
|
||||||
line-height: inherit;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Add the correct height in Firefox.
|
|
||||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
|
||||||
3. Ensure horizontal rules are visible by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
hr {
|
|
||||||
height: 0;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
|
||||||
/* 2 */
|
|
||||||
border-top-width: 1px;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
abbr:where([title]) {
|
|
||||||
-webkit-text-decoration: underline dotted;
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the default font size and weight for headings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-size: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reset links to optimize for opt-in styling instead of opt-out.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font weight in Edge and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use the user's configured `mono` font family by default.
|
|
||||||
2. Correct the odd `em` font sizing in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp,
|
|
||||||
pre {
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
/* 1 */
|
|
||||||
font-size: 1em;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font size in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 0;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
|
||||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
|
||||||
3. Remove gaps between table borders by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
table {
|
|
||||||
text-indent: 0;
|
|
||||||
/* 1 */
|
|
||||||
border-color: inherit;
|
|
||||||
/* 2 */
|
|
||||||
border-collapse: collapse;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Change the font styles in all browsers.
|
|
||||||
2. Remove the margin in Firefox and Safari.
|
|
||||||
3. Remove default padding in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
optgroup,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-family: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-size: 100%;
|
|
||||||
/* 1 */
|
|
||||||
font-weight: inherit;
|
|
||||||
/* 1 */
|
|
||||||
line-height: inherit;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
|
||||||
/* 1 */
|
|
||||||
margin: 0;
|
|
||||||
/* 2 */
|
|
||||||
padding: 0;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inheritance of text transform in Edge and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Remove default button styles.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
[type='button'],
|
|
||||||
[type='reset'],
|
|
||||||
[type='submit'] {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
/* 1 */
|
|
||||||
background-color: transparent;
|
|
||||||
/* 2 */
|
|
||||||
background-image: none;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Use the modern Firefox focus style for all focusable elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-focusring {
|
|
||||||
outline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-ui-invalid {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct vertical alignment in Chrome and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Correct the cursor style of increment and decrement buttons in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-inner-spin-button,
|
|
||||||
::-webkit-outer-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the odd appearance in Chrome and Safari.
|
|
||||||
2. Correct the outline style in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[type='search'] {
|
|
||||||
-webkit-appearance: textfield;
|
|
||||||
/* 1 */
|
|
||||||
outline-offset: -2px;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inner padding in Chrome and Safari on macOS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Change font properties to `inherit` in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
/* 1 */
|
|
||||||
font: inherit;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct display in Chrome and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Removes the default spacing and border for appropriate elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
blockquote,
|
|
||||||
dl,
|
|
||||||
dd,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
hr,
|
|
||||||
figure,
|
|
||||||
p,
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
menu {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent resizing textareas horizontally by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
|
||||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
|
||||||
*/
|
|
||||||
|
|
||||||
input::-moz-placeholder, textarea::-moz-placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
/* 1 */
|
|
||||||
color: #9ca3af;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
input::placeholder,
|
|
||||||
textarea::placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
/* 1 */
|
|
||||||
color: #9ca3af;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the default cursor for buttons.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
[role="button"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Make sure disabled buttons don't get the pointer cursor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
|
||||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg,
|
|
||||||
video,
|
|
||||||
canvas,
|
|
||||||
audio,
|
|
||||||
iframe,
|
|
||||||
embed,
|
|
||||||
object {
|
|
||||||
display: block;
|
|
||||||
/* 1 */
|
|
||||||
vertical-align: middle;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
video {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
*, ::before, ::after {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
::backdrop {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-2 {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-3 {
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rounded {
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-blue-500 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-700 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-6 {
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-4 {
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.py-2 {
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-300 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(209 213 219 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-white {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:bg-blue-400:hover {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
content: {
|
|
||||||
files: ["*.html", "./src/**/*.rs"],
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
|
@ -1,51 +1,55 @@
|
||||||
use leptos::html::ElementDescriptor;
|
use leptos::html::ElementDescriptor;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
/// Used as an argument type to make it easily possible to pass either
|
/// Used as an argument type to make it easily possible to pass either
|
||||||
/// * a `web_sys` element that implements `EventTarget`,
|
/// * a `web_sys` element that implements `E` (for example `EventTarget` or `Element`),
|
||||||
/// * an `Option<T>` where `T` is the web_sys element,
|
/// * an `Option<T>` where `T` is the web_sys element,
|
||||||
/// * a `Signal<T>` where `T` is the web_sys element,
|
/// * a `Signal<T>` where `T` is the web_sys element,
|
||||||
/// * a `Signal<Option<T>>` where `T` is the web_sys element,
|
/// * a `Signal<Option<T>>` where `T` is the web_sys element,
|
||||||
/// * a `NodeRef`
|
/// * a `NodeRef`
|
||||||
/// into a function. Used for example in [`use_event_listener`].
|
/// into a function. Used for example in [`use_event_listener`].
|
||||||
pub enum EventTargetMaybeSignal<T>
|
pub enum ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
Static(Option<T>),
|
Static(Option<T>),
|
||||||
Dynamic(Signal<Option<T>>),
|
Dynamic(Signal<Option<T>>),
|
||||||
|
_Phantom(PhantomData<E>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for EventTargetMaybeSignal<T>
|
impl<T, E> Default for ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Static(None)
|
Self::Static(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Clone for EventTargetMaybeSignal<T>
|
impl<T, E> Clone for ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Static(t) => Self::Static(t.clone()),
|
Self::Static(t) => Self::Static(t.clone()),
|
||||||
Self::Dynamic(s) => Self::Dynamic(*s),
|
Self::Dynamic(s) => Self::Dynamic(*s),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> SignalGet<Option<T>> for EventTargetMaybeSignal<T>
|
impl<T, E> SignalGet<Option<T>> for ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn get(&self) -> Option<T> {
|
fn get(&self) -> Option<T> {
|
||||||
match self {
|
match self {
|
||||||
Self::Static(t) => t.clone(),
|
Self::Static(t) => t.clone(),
|
||||||
Self::Dynamic(s) => s.get(),
|
Self::Dynamic(s) => s.get(),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,18 +57,20 @@ where
|
||||||
match self {
|
match self {
|
||||||
Self::Static(t) => Some(t.clone()),
|
Self::Static(t) => Some(t.clone()),
|
||||||
Self::Dynamic(s) => s.try_get(),
|
Self::Dynamic(s) => s.try_get(),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> SignalWith<Option<T>> for EventTargetMaybeSignal<T>
|
impl<T, E> SignalWith<Option<T>> for ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn with<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> O {
|
fn with<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> O {
|
||||||
match self {
|
match self {
|
||||||
Self::Static(t) => f(t),
|
Self::Static(t) => f(t),
|
||||||
Self::Dynamic(s) => s.with(f),
|
Self::Dynamic(s) => s.with(f),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,18 +78,20 @@ where
|
||||||
match self {
|
match self {
|
||||||
Self::Static(t) => Some(f(t)),
|
Self::Static(t) => Some(f(t)),
|
||||||
Self::Dynamic(s) => s.try_with(f),
|
Self::Dynamic(s) => s.try_with(f),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> SignalWithUntracked<Option<T>> for EventTargetMaybeSignal<T>
|
impl<T, E> SignalWithUntracked<Option<T>> for ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn with_untracked<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> O {
|
fn with_untracked<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> O {
|
||||||
match self {
|
match self {
|
||||||
Self::Static(t) => f(t),
|
Self::Static(t) => f(t),
|
||||||
Self::Dynamic(s) => s.with_untracked(f),
|
Self::Dynamic(s) => s.with_untracked(f),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,18 +99,20 @@ where
|
||||||
match self {
|
match self {
|
||||||
Self::Static(t) => Some(f(t)),
|
Self::Static(t) => Some(f(t)),
|
||||||
Self::Dynamic(s) => s.try_with_untracked(f),
|
Self::Dynamic(s) => s.try_with_untracked(f),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> SignalGetUntracked<Option<T>> for EventTargetMaybeSignal<T>
|
impl<T, E> SignalGetUntracked<Option<T>> for ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn get_untracked(&self) -> Option<T> {
|
fn get_untracked(&self) -> Option<T> {
|
||||||
match self {
|
match self {
|
||||||
Self::Static(t) => t.clone(),
|
Self::Static(t) => t.clone(),
|
||||||
Self::Dynamic(s) => s.get_untracked(),
|
Self::Dynamic(s) => s.get_untracked(),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,36 +120,37 @@ where
|
||||||
match self {
|
match self {
|
||||||
Self::Static(t) => Some(t.clone()),
|
Self::Static(t) => Some(t.clone()),
|
||||||
Self::Dynamic(s) => s.try_get_untracked(),
|
Self::Dynamic(s) => s.try_get_untracked(),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<(Scope, T)> for EventTargetMaybeSignal<T>
|
impl<T, E> From<(Scope, T)> for ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn from(value: (Scope, T)) -> Self {
|
fn from(value: (Scope, T)) -> Self {
|
||||||
EventTargetMaybeSignal::Static(Some(value.1))
|
ElementMaybeSignal::Static(Some(value.1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<(Scope, Option<T>)> for EventTargetMaybeSignal<T>
|
impl<T, E> From<(Scope, Option<T>)> for ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn from(target: (Scope, Option<T>)) -> Self {
|
fn from(target: (Scope, Option<T>)) -> Self {
|
||||||
EventTargetMaybeSignal::Static(target.1)
|
ElementMaybeSignal::Static(target.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_from_signal_option {
|
macro_rules! impl_from_signal_option {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
impl<T> From<(Scope, $ty)> for EventTargetMaybeSignal<T>
|
impl<T, E> From<(Scope, $ty)> for ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn from(target: (Scope, $ty)) -> Self {
|
fn from(target: (Scope, $ty)) -> Self {
|
||||||
EventTargetMaybeSignal::Dynamic(target.1.into())
|
ElementMaybeSignal::Dynamic(target.1.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -152,14 +163,14 @@ impl_from_signal_option!(Memo<Option<T>>);
|
||||||
|
|
||||||
macro_rules! impl_from_signal {
|
macro_rules! impl_from_signal {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
impl<T> From<(Scope, $ty)> for EventTargetMaybeSignal<T>
|
impl<T, E> From<(Scope, $ty)> for ElementMaybeSignal<T, E>
|
||||||
where
|
where
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<E> + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn from(target: (Scope, $ty)) -> Self {
|
fn from(target: (Scope, $ty)) -> Self {
|
||||||
let (cx, signal) = target;
|
let (cx, signal) = target;
|
||||||
|
|
||||||
EventTargetMaybeSignal::Dynamic(Signal::derive(cx, move || Some(signal.get())))
|
ElementMaybeSignal::Dynamic(Signal::derive(cx, move || Some(signal.get())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -170,19 +181,26 @@ impl_from_signal!(ReadSignal<T>);
|
||||||
impl_from_signal!(RwSignal<T>);
|
impl_from_signal!(RwSignal<T>);
|
||||||
impl_from_signal!(Memo<T>);
|
impl_from_signal!(Memo<T>);
|
||||||
|
|
||||||
impl<R> From<(Scope, NodeRef<R>)> for EventTargetMaybeSignal<web_sys::EventTarget>
|
macro_rules! impl_from_node_ref {
|
||||||
where
|
($ty:ty) => {
|
||||||
R: ElementDescriptor + Clone + 'static,
|
impl<R> From<(Scope, NodeRef<R>)> for ElementMaybeSignal<$ty, $ty>
|
||||||
{
|
where
|
||||||
fn from(target: (Scope, NodeRef<R>)) -> Self {
|
R: ElementDescriptor + Clone + 'static,
|
||||||
let (cx, node_ref) = target;
|
{
|
||||||
|
fn from(target: (Scope, NodeRef<R>)) -> Self {
|
||||||
|
let (cx, node_ref) = target;
|
||||||
|
|
||||||
EventTargetMaybeSignal::Dynamic(Signal::derive(cx, move || {
|
ElementMaybeSignal::Dynamic(Signal::derive(cx, move || {
|
||||||
node_ref.get().map(move |el| {
|
node_ref.get().map(move |el| {
|
||||||
let el = el.into_any();
|
let el = el.into_any();
|
||||||
let el: web_sys::EventTarget = el.deref().clone().into();
|
let el: $ty = el.deref().clone().into();
|
||||||
el
|
el
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_from_node_ref!(web_sys::EventTarget);
|
||||||
|
impl_from_node_ref!(web_sys::Element);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
pub mod core;
|
pub mod core;
|
||||||
|
pub mod use_debounce_fn;
|
||||||
pub mod use_event_listener;
|
pub mod use_event_listener;
|
||||||
pub mod use_scroll;
|
pub mod use_scroll;
|
||||||
pub mod use_throttle_fn;
|
pub mod use_throttle_fn;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
pub use use_debounce_fn::*;
|
||||||
pub use use_event_listener::use_event_listener;
|
pub use use_event_listener::use_event_listener;
|
||||||
pub use use_scroll::*;
|
pub use use_scroll::*;
|
||||||
pub use use_throttle_fn::*;
|
pub use use_throttle_fn::*;
|
||||||
|
|
42
src/use_debounce_fn.rs
Normal file
42
src/use_debounce_fn.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use crate::utils::{
|
||||||
|
create_filter_wrapper, create_filter_wrapper_with_arg, debounce_filter, DebounceOptions,
|
||||||
|
};
|
||||||
|
use leptos::MaybeSignal;
|
||||||
|
|
||||||
|
pub fn use_debounce_fn<F>(func: F, ms: impl Into<MaybeSignal<f64>>) -> impl FnMut()
|
||||||
|
where
|
||||||
|
F: FnOnce() + Clone + 'static,
|
||||||
|
{
|
||||||
|
use_debounce_fn_with_options(func, ms, Default::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn use_debounce_fn_with_options<F>(
|
||||||
|
func: F,
|
||||||
|
ms: impl Into<MaybeSignal<f64>>,
|
||||||
|
options: DebounceOptions,
|
||||||
|
) -> impl FnMut()
|
||||||
|
where
|
||||||
|
F: FnOnce() + Clone + 'static,
|
||||||
|
{
|
||||||
|
create_filter_wrapper(debounce_filter(ms, options), func)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn use_debounce_fn_with_arg<F, Arg>(func: F, ms: impl Into<MaybeSignal<f64>>) -> impl FnMut(Arg)
|
||||||
|
where
|
||||||
|
F: FnOnce(Arg) + Clone + 'static,
|
||||||
|
Arg: Clone + 'static,
|
||||||
|
{
|
||||||
|
use_debounce_fn_with_arg_and_options(func, ms, Default::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn use_debounce_fn_with_arg_and_options<F, Arg>(
|
||||||
|
func: F,
|
||||||
|
ms: impl Into<MaybeSignal<f64>>,
|
||||||
|
options: DebounceOptions,
|
||||||
|
) -> impl FnMut(Arg)
|
||||||
|
where
|
||||||
|
F: FnOnce(Arg) + Clone + 'static,
|
||||||
|
Arg: Clone + 'static,
|
||||||
|
{
|
||||||
|
create_filter_wrapper_with_arg(debounce_filter(ms, options), func)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::core::EventTargetMaybeSignal;
|
use crate::core::ElementMaybeSignal;
|
||||||
use leptos::ev::EventDescriptor;
|
use leptos::ev::EventDescriptor;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
@ -90,7 +90,7 @@ pub fn use_event_listener<Ev, El, T, F>(
|
||||||
) -> Box<dyn Fn()>
|
) -> Box<dyn Fn()>
|
||||||
where
|
where
|
||||||
Ev: EventDescriptor + 'static,
|
Ev: EventDescriptor + 'static,
|
||||||
(Scope, El): Into<EventTargetMaybeSignal<T>>,
|
(Scope, El): Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<web_sys::EventTarget> + Clone + 'static,
|
||||||
F: FnMut(<Ev as EventDescriptor>::EventType) + 'static,
|
F: FnMut(<Ev as EventDescriptor>::EventType) + 'static,
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,34 +1,69 @@
|
||||||
use crate::core::EventTargetMaybeSignal;
|
use crate::core::ElementMaybeSignal;
|
||||||
|
use crate::use_debounce_fn_with_arg;
|
||||||
|
use crate::utils::{CloneableFn, CloneableFnWithArg, CloneableFnWithReturn};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
|
||||||
|
/// We have to check if the scroll amount is close enough to some threshold in order to
|
||||||
|
/// more accurately calculate arrivedState. This is because scrollTop/scrollLeft are non-rounded
|
||||||
|
/// numbers, while scrollHeight/scrollWidth and clientHeight/clientWidth are rounded.
|
||||||
|
/// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
|
||||||
|
const ARRIVED_STATE_THRESHOLD_PIXELS: f64 = 1.0;
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub fn use_scroll<El, T, Fx, Fy>(
|
pub fn use_scroll_with_options<El, T>(
|
||||||
cx: Scope,
|
cx: Scope,
|
||||||
element: El,
|
element: El,
|
||||||
options: UseScrollOptions,
|
options: UseScrollOptions,
|
||||||
) -> UseScrollReturn
|
) -> UseScrollReturn
|
||||||
where
|
where
|
||||||
(Scope, El): Into<EventTargetMaybeSignal<T>>,
|
(Scope, El): Into<ElementMaybeSignal<T, web_sys::Element>>,
|
||||||
T: Into<web_sys::EventTarget> + Clone + 'static,
|
T: Into<web_sys::Element> + Clone + 'static,
|
||||||
Fx: Fn(f64),
|
|
||||||
Fy: Fn(f64),
|
|
||||||
{
|
{
|
||||||
// TODO : implement
|
// TODO : implement
|
||||||
|
|
||||||
let (x, set_x) = create_signal(cx, 0.0);
|
let (internal_x, set_internal_x) = create_signal(cx, 0.0);
|
||||||
let (y, set_y) = create_signal(cx, 0.0);
|
let (internal_y, set_internal_y) = create_signal(cx, 0.0);
|
||||||
|
|
||||||
let (is_scrolling, _) = create_signal(cx, false);
|
let signal = (cx, element).into();
|
||||||
let (arrived_state, _) = create_signal(
|
let behavior = options.behavior;
|
||||||
cx,
|
|
||||||
Directions {
|
let scroll_to = move |x: Option<f64>, y: Option<f64>| {
|
||||||
left: false,
|
let element = signal.get_untracked();
|
||||||
right: false,
|
|
||||||
top: false,
|
if let Some(element) = element {
|
||||||
bottom: false,
|
let element = element.into();
|
||||||
},
|
|
||||||
);
|
let mut scroll_options = web_sys::ScrollToOptions::new();
|
||||||
let (directions, _) = create_signal(
|
scroll_options.behavior(behavior.into());
|
||||||
|
|
||||||
|
if let Some(x) = x {
|
||||||
|
scroll_options.left(x);
|
||||||
|
}
|
||||||
|
if let Some(y) = y {
|
||||||
|
scroll_options.top(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.scroll_to_with_scroll_to_options(&scroll_options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let scroll = scroll_to.clone();
|
||||||
|
let set_x = Box::new(move |x| scroll(Some(x), None));
|
||||||
|
|
||||||
|
let scroll = scroll_to.clone();
|
||||||
|
let set_y = Box::new(move |y| scroll(None, Some(y)));
|
||||||
|
|
||||||
|
let (is_scrolling, set_is_scrolling) = create_signal(cx, false);
|
||||||
|
let (arrived_state, set_arrived_state) = create_signal(
|
||||||
|
cx,
|
||||||
|
Directions {
|
||||||
|
left: true,
|
||||||
|
right: false,
|
||||||
|
top: true,
|
||||||
|
bottom: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let (directions, set_directions) = create_signal(
|
||||||
cx,
|
cx,
|
||||||
Directions {
|
Directions {
|
||||||
left: false,
|
left: false,
|
||||||
|
@ -38,11 +73,29 @@ where
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let on_stop = options.on_stop;
|
||||||
|
let on_scroll_end = move |e| {
|
||||||
|
if !is_scrolling.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_is_scrolling(false);
|
||||||
|
set_directions.update(|directions| {
|
||||||
|
directions.left = false;
|
||||||
|
directions.right = false;
|
||||||
|
directions.top = false;
|
||||||
|
directions.bottom = false;
|
||||||
|
on_stop(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
let on_scroll_end_debounced =
|
||||||
|
use_debounce_fn_with_arg(on_scroll_end, options.throttle + options.idle);
|
||||||
|
|
||||||
UseScrollReturn {
|
UseScrollReturn {
|
||||||
x: x.into(),
|
x: internal_x.into(),
|
||||||
set_x: Box::new(move |x| set_x.set(x)),
|
set_x,
|
||||||
y: y.into(),
|
y: internal_y.into(),
|
||||||
set_y: Box::new(move |y| set_y.set(y)),
|
set_y,
|
||||||
is_scrolling: is_scrolling.into(),
|
is_scrolling: is_scrolling.into(),
|
||||||
arrived_state: arrived_state.into(),
|
arrived_state: arrived_state.into(),
|
||||||
directions: directions.into(),
|
directions: directions.into(),
|
||||||
|
@ -50,39 +103,61 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Options for [`use_scroll`].
|
/// Options for [`use_scroll`].
|
||||||
#[derive(Default)]
|
|
||||||
pub struct UseScrollOptions {
|
pub struct UseScrollOptions {
|
||||||
/// Throttle time in milliseconds for the scroll events. Defaults to 0 (disabled).
|
/// Throttle time in milliseconds for the scroll events. Defaults to 0 (disabled).
|
||||||
pub throttle: u32,
|
pub throttle: f64,
|
||||||
|
|
||||||
/// After scrolling ends we wait idle + throttle milliseconds before we consider scrolling to have stopped.
|
/// After scrolling ends we wait idle + throttle milliseconds before we consider scrolling to have stopped.
|
||||||
/// Defaults to 200.
|
/// Defaults to 200.
|
||||||
pub idle: u32,
|
pub idle: f64,
|
||||||
|
|
||||||
/// Threshold in pixels when we consider a side to have arrived (`UseScrollReturn::arrived_state`).
|
/// Threshold in pixels when we consider a side to have arrived (`UseScrollReturn::arrived_state`).
|
||||||
pub offset: ScrollOffset,
|
pub offset: ScrollOffset,
|
||||||
|
|
||||||
/// Callback when scrolling is happening.
|
/// Callback when scrolling is happening.
|
||||||
pub on_scroll: Option<Box<dyn Fn()>>,
|
pub on_scroll: Box<dyn CloneableFn>,
|
||||||
|
|
||||||
/// Callback when scrolling stops (after `idle` + `throttle` milliseconds have passed).
|
/// Callback when scrolling stops (after `idle` + `throttle` milliseconds have passed).
|
||||||
pub on_stop: Option<Box<dyn Fn()>>,
|
pub on_stop: Box<dyn CloneableFnWithArg<web_sys::Event>>,
|
||||||
|
|
||||||
/// Options passed to the `addEventListener("scroll", ...)` call
|
/// Options passed to the `addEventListener("scroll", ...)` call
|
||||||
pub event_listener_options: Option<web_sys::AddEventListenerOptions>,
|
pub event_listener_options: web_sys::AddEventListenerOptions,
|
||||||
|
|
||||||
/// When changing the `x` or `y` signals this specifies the scroll behaviour.
|
/// When changing the `x` or `y` signals this specifies the scroll behaviour.
|
||||||
/// Can be `Auto` (= not smooth) or `Smooth`. Defaults to `Auto`.
|
/// Can be `Auto` (= not smooth) or `Smooth`. Defaults to `Auto`.
|
||||||
pub behavior: ScrollBehavior,
|
pub behavior: ScrollBehavior,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
impl Default for UseScrollOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
throttle: 0.0,
|
||||||
|
idle: 200.0,
|
||||||
|
offset: Default::default(),
|
||||||
|
on_scroll: Default::default(),
|
||||||
|
on_stop: Default::default(),
|
||||||
|
event_listener_options: Default::default(),
|
||||||
|
behavior: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Copy, Clone)]
|
||||||
pub enum ScrollBehavior {
|
pub enum ScrollBehavior {
|
||||||
#[default]
|
#[default]
|
||||||
Auto,
|
Auto,
|
||||||
Smooth,
|
Smooth,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<web_sys::ScrollBehavior> for ScrollBehavior {
|
||||||
|
fn into(self) -> web_sys::ScrollBehavior {
|
||||||
|
match self {
|
||||||
|
ScrollBehavior::Auto => web_sys::ScrollBehavior::Auto,
|
||||||
|
ScrollBehavior::Smooth => web_sys::ScrollBehavior::Smooth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct UseScrollReturn {
|
pub struct UseScrollReturn {
|
||||||
pub x: Signal<f64>,
|
pub x: Signal<f64>,
|
||||||
pub set_x: Box<dyn Fn(f64)>,
|
pub set_x: Box<dyn Fn(f64)>,
|
||||||
|
@ -101,7 +176,7 @@ pub struct Directions {
|
||||||
pub bottom: bool,
|
pub bottom: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Copy, Clone)]
|
||||||
pub struct ScrollOffset {
|
pub struct ScrollOffset {
|
||||||
pub left: f64,
|
pub left: f64,
|
||||||
pub top: f64,
|
pub top: f64,
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::utils::{create_filter_wrapper, create_filter_wrapper_with_arg, throttle_filter};
|
use crate::utils::{
|
||||||
|
create_filter_wrapper_with_return, create_filter_wrapper_with_return_and_arg, throttle_filter,
|
||||||
|
};
|
||||||
use leptos::MaybeSignal;
|
use leptos::MaybeSignal;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@ -41,10 +43,9 @@ pub use crate::utils::ThrottleOptions;
|
||||||
/// ```
|
/// ```
|
||||||
/// # use leptos::*;
|
/// # use leptos::*;
|
||||||
/// # use leptos_use::{ThrottleOptions, use_throttle_fn_with_options};
|
/// # use leptos_use::{ThrottleOptions, use_throttle_fn_with_options};
|
||||||
///
|
|
||||||
/// # #[component]
|
/// # #[component]
|
||||||
/// # fn Demo(cx: Scope) -> impl IntoView {
|
/// # fn Demo(cx: Scope) -> impl IntoView {
|
||||||
/// let throttled_fn = use_throttle_fn_with_options(
|
/// let throttled_fn = use_throttle_fn_with_options(
|
||||||
/// || {
|
/// || {
|
||||||
/// // do something, it will be called at most 1 time per second
|
/// // do something, it will be called at most 1 time per second
|
||||||
/// },
|
/// },
|
||||||
|
@ -58,7 +59,7 @@ pub use crate::utils::ThrottleOptions;
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// If your function that you want to throttle takes an argument there are also the versions
|
/// If you want to throttle a function that takes an argument there are also the versions
|
||||||
/// [`use_throttle_fn_with_args`] and [`use_throttle_fn_with_args_and_options`].
|
/// [`use_throttle_fn_with_args`] and [`use_throttle_fn_with_args_and_options`].
|
||||||
///
|
///
|
||||||
/// ## Recommended Reading
|
/// ## Recommended Reading
|
||||||
|
@ -69,7 +70,7 @@ pub fn use_throttle_fn<F, R>(
|
||||||
ms: impl Into<MaybeSignal<f64>>,
|
ms: impl Into<MaybeSignal<f64>>,
|
||||||
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
|
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
|
||||||
where
|
where
|
||||||
F: FnMut() -> R + Clone + 'static,
|
F: FnOnce() -> R + Clone + 'static,
|
||||||
R: 'static,
|
R: 'static,
|
||||||
{
|
{
|
||||||
use_throttle_fn_with_options(func, ms, Default::default())
|
use_throttle_fn_with_options(func, ms, Default::default())
|
||||||
|
@ -82,10 +83,10 @@ pub fn use_throttle_fn_with_options<F, R>(
|
||||||
options: ThrottleOptions,
|
options: ThrottleOptions,
|
||||||
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
|
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
|
||||||
where
|
where
|
||||||
F: FnMut() -> R + Clone + 'static,
|
F: FnOnce() -> R + Clone + 'static,
|
||||||
R: 'static,
|
R: 'static,
|
||||||
{
|
{
|
||||||
create_filter_wrapper(throttle_filter(ms, options), func)
|
create_filter_wrapper_with_return(throttle_filter(ms, options), func)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Version of [`use_throttle_fn`] with an argument for the throttled function. See the docs for [`use_throttle_fn`] for how to use.
|
/// Version of [`use_throttle_fn`] with an argument for the throttled function. See the docs for [`use_throttle_fn`] for how to use.
|
||||||
|
@ -94,8 +95,8 @@ pub fn use_throttle_fn_with_arg<F, Arg, R>(
|
||||||
ms: impl Into<MaybeSignal<f64>>,
|
ms: impl Into<MaybeSignal<f64>>,
|
||||||
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
|
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
|
||||||
where
|
where
|
||||||
F: FnMut(Arg) -> R + Clone + 'static,
|
F: FnOnce(Arg) -> R + Clone + 'static,
|
||||||
Arg: 'static,
|
Arg: Clone + 'static,
|
||||||
R: 'static,
|
R: 'static,
|
||||||
{
|
{
|
||||||
use_throttle_fn_with_arg_and_options(func, ms, Default::default())
|
use_throttle_fn_with_arg_and_options(func, ms, Default::default())
|
||||||
|
@ -108,9 +109,9 @@ pub fn use_throttle_fn_with_arg_and_options<F, Arg, R>(
|
||||||
options: ThrottleOptions,
|
options: ThrottleOptions,
|
||||||
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
|
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
|
||||||
where
|
where
|
||||||
F: FnMut(Arg) -> R + Clone + 'static,
|
F: FnOnce(Arg) -> R + Clone + 'static,
|
||||||
Arg: 'static,
|
Arg: Clone + 'static,
|
||||||
R: 'static,
|
R: 'static,
|
||||||
{
|
{
|
||||||
create_filter_wrapper_with_arg(throttle_filter(ms, options), func)
|
create_filter_wrapper_with_return_and_arg(throttle_filter(ms, options), func)
|
||||||
}
|
}
|
||||||
|
|
101
src/utils/clonable_fn.rs
Normal file
101
src/utils/clonable_fn.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
pub trait CloneableFnWithReturn<R>: FnOnce() -> R {
|
||||||
|
fn clone_box(&self) -> Box<dyn CloneableFnWithReturn<R>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, R> CloneableFnWithReturn<R> for F
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R + Clone + 'static,
|
||||||
|
R: 'static,
|
||||||
|
{
|
||||||
|
fn clone_box(&self) -> Box<dyn CloneableFnWithReturn<R>> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> Clone for Box<dyn CloneableFnWithReturn<R>> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
(**self).clone_box()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Default + 'static> Default for Box<dyn CloneableFnWithReturn<R>> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Box::new(|| Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CloneableFnWithReturnAndArg<R, Arg>: FnOnce(Arg) -> R {
|
||||||
|
fn clone_box(&self) -> Box<dyn CloneableFnWithReturnAndArg<R, Arg>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, R, Arg> CloneableFnWithReturnAndArg<R, Arg> for F
|
||||||
|
where
|
||||||
|
F: FnMut(Arg) -> R + Clone + 'static,
|
||||||
|
R: 'static,
|
||||||
|
{
|
||||||
|
fn clone_box(&self) -> Box<dyn CloneableFnWithReturnAndArg<R, Arg>> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R, Arg> Clone for Box<dyn CloneableFnWithReturnAndArg<R, Arg>> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
(**self).clone_box()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Default + 'static, Arg> Default for Box<dyn CloneableFnWithReturnAndArg<R, Arg>> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Box::new(|_| Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CloneableFn: FnOnce() {
|
||||||
|
fn clone_box(&self) -> Box<dyn CloneableFn>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> CloneableFn for F
|
||||||
|
where
|
||||||
|
F: FnOnce() + Clone + 'static,
|
||||||
|
{
|
||||||
|
fn clone_box(&self) -> Box<dyn CloneableFn> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Box<dyn CloneableFn> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
(**self).clone_box()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Box<dyn CloneableFn> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Box::new(|| {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CloneableFnWithArg<Arg>: FnOnce(Arg) {
|
||||||
|
fn clone_box(&self) -> Box<dyn CloneableFnWithArg<Arg>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, Arg> CloneableFnWithArg<Arg> for F
|
||||||
|
where
|
||||||
|
F: FnMut(Arg) + Clone + 'static,
|
||||||
|
{
|
||||||
|
fn clone_box(&self) -> Box<dyn CloneableFnWithArg<Arg>> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Arg> Clone for Box<dyn CloneableFnWithArg<Arg>> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
(**self).clone_box()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Arg> Default for Box<dyn CloneableFnWithArg<Arg>> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Box::new(|_| {})
|
||||||
|
}
|
||||||
|
}
|
9
src/utils/demo.rs
Normal file
9
src/utils/demo.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use leptos::document;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
pub fn demo_or_body() -> web_sys::HtmlElement {
|
||||||
|
document()
|
||||||
|
.get_element_by_id("demo-anchor")
|
||||||
|
.map(|e| e.unchecked_into::<web_sys::HtmlElement>())
|
||||||
|
.unwrap_or(document().body().expect("body to exist"))
|
||||||
|
}
|
80
src/utils/filters/debounce.rs
Normal file
80
src/utils/filters/debounce.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
use crate::utils::CloneableFnWithReturn;
|
||||||
|
use leptos::leptos_dom::helpers::TimeoutHandle;
|
||||||
|
use leptos::{set_timeout_with_handle, MaybeSignal, SignalGetUntracked};
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DebounceOptions {
|
||||||
|
/// The maximum time allowed to be delayed before it's invoked.
|
||||||
|
/// In milliseconds.
|
||||||
|
max_wait: MaybeSignal<Option<f64>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debounce_filter(
|
||||||
|
ms: impl Into<MaybeSignal<f64>>,
|
||||||
|
options: DebounceOptions,
|
||||||
|
) -> impl FnMut(Box<dyn CloneableFnWithReturn<()>>) -> Rc<RefCell<Option<()>>> {
|
||||||
|
let timer = Rc::new(Cell::new(None::<TimeoutHandle>));
|
||||||
|
let max_timer = Rc::new(Cell::new(None::<TimeoutHandle>));
|
||||||
|
|
||||||
|
let clear_timeout = move |timer: &Rc<Cell<Option<TimeoutHandle>>>| {
|
||||||
|
if let Some(handle) = timer.get() {
|
||||||
|
handle.clear();
|
||||||
|
timer.set(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let ms = ms.into();
|
||||||
|
|
||||||
|
move |invoke: Box<dyn CloneableFnWithReturn<()>>| {
|
||||||
|
let duration = ms.get_untracked();
|
||||||
|
let max_duration = options.max_wait.get_untracked();
|
||||||
|
|
||||||
|
// TODO : return value like throttle_filter?
|
||||||
|
|
||||||
|
clear_timeout(&timer);
|
||||||
|
|
||||||
|
if duration <= 0.0 || max_duration.is_some_and(|d| d <= 0.0) {
|
||||||
|
clear_timeout(&max_timer);
|
||||||
|
|
||||||
|
invoke();
|
||||||
|
return Rc::new(RefCell::new(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the max_timer. Clears the regular timer on invoke
|
||||||
|
if let Some(max_duration) = max_duration {
|
||||||
|
if max_timer.get().is_none() {
|
||||||
|
let timer = Rc::clone(&timer);
|
||||||
|
let invok = invoke.clone();
|
||||||
|
max_timer.set(
|
||||||
|
set_timeout_with_handle(
|
||||||
|
move || {
|
||||||
|
clear_timeout(&timer);
|
||||||
|
invok();
|
||||||
|
},
|
||||||
|
Duration::from_millis(max_duration as u64),
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_timer = Rc::clone(&max_timer);
|
||||||
|
|
||||||
|
// Create the regular timer. Clears the max timer on invoke
|
||||||
|
timer.set(
|
||||||
|
set_timeout_with_handle(
|
||||||
|
move || {
|
||||||
|
clear_timeout(&max_timer);
|
||||||
|
invoke();
|
||||||
|
},
|
||||||
|
Duration::from_millis(duration as u64),
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Rc::new(RefCell::new(None))
|
||||||
|
}
|
||||||
|
}
|
62
src/utils/filters/mod.rs
Normal file
62
src/utils/filters/mod.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
mod debounce;
|
||||||
|
mod throttle;
|
||||||
|
|
||||||
|
pub use debounce::*;
|
||||||
|
pub use throttle::*;
|
||||||
|
|
||||||
|
use crate::utils::CloneableFnWithReturn;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub fn create_filter_wrapper<F, Filter>(mut filter: Filter, func: F) -> impl FnMut()
|
||||||
|
where
|
||||||
|
F: FnOnce() + Clone + 'static,
|
||||||
|
Filter: FnMut(Box<dyn CloneableFnWithReturn<()>>) -> Rc<RefCell<Option<()>>>,
|
||||||
|
{
|
||||||
|
move || {
|
||||||
|
filter(Box::new(func.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_filter_wrapper_with_arg<F, Arg, Filter>(
|
||||||
|
mut filter: Filter,
|
||||||
|
func: F,
|
||||||
|
) -> impl FnMut(Arg)
|
||||||
|
where
|
||||||
|
F: FnOnce(Arg) + Clone + 'static,
|
||||||
|
Arg: Clone + 'static,
|
||||||
|
Filter: FnMut(Box<dyn CloneableFnWithReturn<()>>) -> Rc<RefCell<Option<()>>>,
|
||||||
|
{
|
||||||
|
move |arg: Arg| {
|
||||||
|
let mut func = func.clone();
|
||||||
|
filter(Box::new(move || func(arg)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_filter_wrapper_with_return<F, R, Filter>(
|
||||||
|
mut filter: Filter,
|
||||||
|
func: F,
|
||||||
|
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R + Clone + 'static,
|
||||||
|
R: 'static,
|
||||||
|
Filter: FnMut(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>>,
|
||||||
|
{
|
||||||
|
move || filter(Box::new(func.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_filter_wrapper_with_return_and_arg<F, Arg, R, Filter>(
|
||||||
|
mut filter: Filter,
|
||||||
|
func: F,
|
||||||
|
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
|
||||||
|
where
|
||||||
|
F: FnOnce(Arg) -> R + Clone + 'static,
|
||||||
|
R: 'static,
|
||||||
|
Arg: Clone + 'static,
|
||||||
|
Filter: FnMut(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>>,
|
||||||
|
{
|
||||||
|
move |arg: Arg| {
|
||||||
|
let mut func = func.clone();
|
||||||
|
filter(Box::new(move || func(arg)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::utils::CloneableFnWithReturn;
|
||||||
use js_sys::Date;
|
use js_sys::Date;
|
||||||
use leptos::leptos_dom::helpers::TimeoutHandle;
|
use leptos::leptos_dom::helpers::TimeoutHandle;
|
||||||
use leptos::{set_timeout_with_handle, MaybeSignal, SignalGetUntracked};
|
use leptos::{set_timeout_with_handle, MaybeSignal, SignalGetUntracked};
|
||||||
|
@ -6,38 +7,6 @@ use std::cmp::max;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn create_filter_wrapper<F, R, Filter>(
|
|
||||||
mut filter: Filter,
|
|
||||||
func: F,
|
|
||||||
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
|
|
||||||
where
|
|
||||||
F: FnMut() -> R + Clone + 'static,
|
|
||||||
R: 'static,
|
|
||||||
Filter: FnMut(Box<dyn FnOnce() -> R>) -> Rc<RefCell<Option<R>>>,
|
|
||||||
{
|
|
||||||
move || {
|
|
||||||
let wrapped_func = Box::new(func.clone());
|
|
||||||
filter(wrapped_func)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_filter_wrapper_with_arg<F, Arg, R, Filter>(
|
|
||||||
mut filter: Filter,
|
|
||||||
func: F,
|
|
||||||
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
|
|
||||||
where
|
|
||||||
F: FnMut(Arg) -> R + Clone + 'static,
|
|
||||||
R: 'static,
|
|
||||||
Arg: 'static,
|
|
||||||
Filter: FnMut(Box<dyn FnOnce() -> R>) -> Rc<RefCell<Option<R>>>,
|
|
||||||
{
|
|
||||||
move |arg: Arg| {
|
|
||||||
let mut func = func.clone();
|
|
||||||
let wrapped_func = Box::new(move || func(arg));
|
|
||||||
filter(wrapped_func)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct ThrottleOptions {
|
pub struct ThrottleOptions {
|
||||||
pub trailing: bool,
|
pub trailing: bool,
|
||||||
|
@ -56,7 +25,7 @@ impl Default for ThrottleOptions {
|
||||||
pub fn throttle_filter<R>(
|
pub fn throttle_filter<R>(
|
||||||
ms: impl Into<MaybeSignal<f64>>,
|
ms: impl Into<MaybeSignal<f64>>,
|
||||||
options: ThrottleOptions,
|
options: ThrottleOptions,
|
||||||
) -> impl FnMut(Box<dyn FnOnce() -> R>) -> Rc<RefCell<Option<R>>>
|
) -> impl FnMut(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>>
|
||||||
where
|
where
|
||||||
R: 'static,
|
R: 'static,
|
||||||
{
|
{
|
||||||
|
@ -75,7 +44,7 @@ where
|
||||||
|
|
||||||
let ms = ms.into();
|
let ms = ms.into();
|
||||||
|
|
||||||
move |mut _invoke: Box<dyn FnOnce() -> R>| {
|
move |mut _invoke: Box<dyn CloneableFnWithReturn<R>>| {
|
||||||
let duration = ms.get_untracked();
|
let duration = ms.get_untracked();
|
||||||
let elapsed = Date::now() - last_exec.get();
|
let elapsed = Date::now() - last_exec.get();
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub fn noop() -> Box<dyn FnMut()> {
|
|
||||||
Box::new(|| {})
|
|
||||||
}
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
mod clonable_fn;
|
||||||
|
mod demo;
|
||||||
mod filters;
|
mod filters;
|
||||||
mod is;
|
|
||||||
|
|
||||||
|
pub use clonable_fn::*;
|
||||||
|
pub use demo::*;
|
||||||
pub use filters::*;
|
pub use filters::*;
|
||||||
pub use is::*;
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue