From 5057bf07654b113bab46d76488aeafc3222d38a9 Mon Sep 17 00:00:00 2001 From: Maccesch Date: Fri, 26 May 2023 18:09:01 +0100 Subject: [PATCH] added better demo integration and styling to book --- CHANGELOG.md | 5 + Cargo.toml | 2 +- docs/book/book.toml | 3 +- docs/book/custom.css | 15 - docs/book/demo-iframe.js | 19 - docs/book/post_build.py | 26 + docs/book/src/custom.css | 8 + docs/book/src/demo.css | 66 ++ docs/book/src/extract_doc_comment.py | 5 +- docs/book/src/getting_started/functions.md | 4 + docs/book/src/sensors/use_scroll.md | 3 + examples/use_throttle_fn/README.md | 12 +- examples/use_throttle_fn/Trunk.toml | 2 +- examples/use_throttle_fn/index.html | 6 +- examples/use_throttle_fn/input.css | 3 - examples/use_throttle_fn/src/main.rs | 14 +- examples/use_throttle_fn/style/output.css | 574 ------------------ examples/use_throttle_fn/tailwind.config.js | 10 - src/core/event_target_maybe_signal.rs | 100 +-- src/lib.rs | 2 + src/use_debounce_fn.rs | 42 ++ src/use_event_listener.rs | 4 +- src/use_scroll.rs | 137 ++++- src/use_throttle_fn.rs | 25 +- src/utils/clonable_fn.rs | 101 +++ src/utils/demo.rs | 9 + src/utils/filters/debounce.rs | 80 +++ src/utils/filters/mod.rs | 62 ++ src/utils/{filters.rs => filters/throttle.rs} | 37 +- src/utils/is.rs | 3 - src/utils/mod.rs | 6 +- 31 files changed, 610 insertions(+), 775 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 docs/book/custom.css delete mode 100644 docs/book/demo-iframe.js create mode 100644 docs/book/src/custom.css create mode 100644 docs/book/src/demo.css create mode 100644 docs/book/src/sensors/use_scroll.md delete mode 100644 examples/use_throttle_fn/input.css delete mode 100644 examples/use_throttle_fn/style/output.css delete mode 100644 examples/use_throttle_fn/tailwind.config.js create mode 100644 src/use_debounce_fn.rs create mode 100644 src/utils/clonable_fn.rs create mode 100644 src/utils/demo.rs create mode 100644 src/utils/filters/debounce.rs create mode 100644 src/utils/filters/mod.rs rename src/utils/{filters.rs => filters/throttle.rs} (74%) delete mode 100644 src/utils/is.rs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..295292c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.1.3 + +- Added `use_scroll`. \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index a69f9cb..6eef86a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,6 @@ repository = "https://github.com/Synphonyte/leptos-use" [dependencies] leptos = "0.3" -web-sys = "0.3" +web-sys = { version = "0.3", features = ["ScrollToOptions", "ScrollBehavior"] } wasm-bindgen = "0.2" js-sys = "0.3" \ No newline at end of file diff --git a/docs/book/book.toml b/docs/book/book.toml index 6b2c4d9..296d614 100644 --- a/docs/book/book.toml +++ b/docs/book/book.toml @@ -7,7 +7,6 @@ title = "Leptos-Use Documentation" [output.html] no-section-label = true -additional-css = ["custom.css"] -additional-js = ["demo-iframe.js"] +additional-css = ["src/custom.css", "src/demo.css"] [preprocessor.cmdrun] \ No newline at end of file diff --git a/docs/book/custom.css b/docs/book/custom.css deleted file mode 100644 index 5cab92c..0000000 --- a/docs/book/custom.css +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/docs/book/demo-iframe.js b/docs/book/demo-iframe.js deleted file mode 100644 index 465b1a7..0000000 --- a/docs/book/demo-iframe.js +++ /dev/null @@ -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); - }); -} diff --git a/docs/book/post_build.py b/docs/book/post_build.py index 4ba852d..81bb266 100644 --- a/docs/book/post_build.py +++ b/docs/book/post_build.py @@ -29,6 +29,32 @@ def build_and_copy_demo(category, md_name): shutil.copytree(example_output_path, target_path, 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("")[1].split("")[0] + body = html.split("")[1].split("")[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("") + target_head = head_split[1].split("")[0] + body_split = html.split("")[1].split("") + target_body = body_split[0] + + with open(book_html_path, "w") as f: + f.write( + f"""{head_split[0]} + + {head} + {target_head} + + + {body} + {target_body} + +{body_split[1]}""") + if __name__ == '__main__': main() diff --git a/docs/book/src/custom.css b/docs/book/src/custom.css new file mode 100644 index 0000000..a400dc7 --- /dev/null +++ b/docs/book/src/custom.css @@ -0,0 +1,8 @@ +.light { + --fg: #333; +} + +pre > code { + border-radius: 5px; + padding: 1.5rem; +} \ No newline at end of file diff --git a/docs/book/src/demo.css b/docs/book/src/demo.css new file mode 100644 index 0000000..a0d77f5 --- /dev/null +++ b/docs/book/src/demo.css @@ -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; +} \ No newline at end of file diff --git a/docs/book/src/extract_doc_comment.py b/docs/book/src/extract_doc_comment.py index 44e3711..f796bb7 100644 --- a/docs/book/src/extract_doc_comment.py +++ b/docs/book/src/extract_doc_comment.py @@ -34,9 +34,8 @@ def process_line(line, name): if stripped.startswith("[Link to Demo](https://"): example_link = stripped.replace("[Link to Demo](", "").replace(")", "") result = f'''
- source - + source +
''' else: diff --git a/docs/book/src/getting_started/functions.md b/docs/book/src/getting_started/functions.md index b2124ff..330337d 100644 --- a/docs/book/src/getting_started/functions.md +++ b/docs/book/src/getting_started/functions.md @@ -1,3 +1,7 @@ # Functions + + + + diff --git a/docs/book/src/sensors/use_scroll.md b/docs/book/src/sensors/use_scroll.md new file mode 100644 index 0000000..88d257b --- /dev/null +++ b/docs/book/src/sensors/use_scroll.md @@ -0,0 +1,3 @@ +# use_event_listener + + diff --git a/examples/use_throttle_fn/README.md b/examples/use_throttle_fn/README.md index 891a616..6e6057c 100644 --- a/examples/use_throttle_fn/README.md +++ b/examples/use_throttle_fn/README.md @@ -1,22 +1,16 @@ 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: ```bash cargo install trunk -npm install -D tailwindcss rustup toolchain install nightly rustup target add wasm32-unknown-unknown ``` -Then, open two terminals. In the first one, run: - -``` -npx tailwindcss -i ./input.css -o ./style/output.css --watch -``` - -In the second one, run: +To run the demo: ```bash trunk serve --open +``` diff --git a/examples/use_throttle_fn/Trunk.toml b/examples/use_throttle_fn/Trunk.toml index 87c8044..f521021 100644 --- a/examples/use_throttle_fn/Trunk.toml +++ b/examples/use_throttle_fn/Trunk.toml @@ -1,2 +1,2 @@ [build] -public_url = "/leptos-use/utilities/use_throttle_fn/demo/" \ No newline at end of file +public_url = "./demo/" \ No newline at end of file diff --git a/examples/use_throttle_fn/index.html b/examples/use_throttle_fn/index.html index ae249a6..25f83eb 100644 --- a/examples/use_throttle_fn/index.html +++ b/examples/use_throttle_fn/index.html @@ -1,7 +1,5 @@ - - - - + + diff --git a/examples/use_throttle_fn/input.css b/examples/use_throttle_fn/input.css deleted file mode 100644 index bd6213e..0000000 --- a/examples/use_throttle_fn/input.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; \ No newline at end of file diff --git a/examples/use_throttle_fn/src/main.rs b/examples/use_throttle_fn/src/main.rs index 2373915..6aeab76 100644 --- a/examples/use_throttle_fn/src/main.rs +++ b/examples/use_throttle_fn/src/main.rs @@ -1,5 +1,6 @@ use leptos::*; use leptos_use::use_throttle_fn; +use leptos_use::utils::demo_or_body; #[component] fn Demo(cx: Scope) -> impl IntoView { @@ -15,12 +16,11 @@ fn Demo(cx: Scope) -> impl IntoView { set_click_count(click_count() + 1); throttled_fn(); } - class="rounded bg-blue-500 hover:bg-blue-400 py-2 px-4 text-white" > "Smash me!" -

"Delay is set to 1000ms for this demo."

-

"Button clicked: " { click_count }

+
"Delay is set to 1000ms for this demo."
+

"Button clicked: " { click_count }

"Event handler called: " { throttled_count }

} } @@ -29,11 +29,7 @@ fn main() { _ = console_log::init_with_level(log::Level::Debug); console_error_panic_hook::set_once(); - mount_to_body(|cx| { - view! {cx, -
- -
- } + mount_to(demo_or_body(), |cx| { + view! { cx, } }) } diff --git a/examples/use_throttle_fn/style/output.css b/examples/use_throttle_fn/style/output.css deleted file mode 100644 index bf61798..0000000 --- a/examples/use_throttle_fn/style/output.css +++ /dev/null @@ -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)); -} \ No newline at end of file diff --git a/examples/use_throttle_fn/tailwind.config.js b/examples/use_throttle_fn/tailwind.config.js deleted file mode 100644 index 599eb9f..0000000 --- a/examples/use_throttle_fn/tailwind.config.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: { - files: ["*.html", "./src/**/*.rs"], - }, - theme: { - extend: {}, - }, - plugins: [], -} \ No newline at end of file diff --git a/src/core/event_target_maybe_signal.rs b/src/core/event_target_maybe_signal.rs index 7a15f7a..47f2992 100644 --- a/src/core/event_target_maybe_signal.rs +++ b/src/core/event_target_maybe_signal.rs @@ -1,51 +1,55 @@ use leptos::html::ElementDescriptor; use leptos::*; +use std::marker::PhantomData; use std::ops::Deref; /// 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` where `T` is the web_sys element, /// * a `Signal` where `T` is the web_sys element, /// * a `Signal>` where `T` is the web_sys element, /// * a `NodeRef` /// into a function. Used for example in [`use_event_listener`]. -pub enum EventTargetMaybeSignal +pub enum ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { Static(Option), Dynamic(Signal>), + _Phantom(PhantomData), } -impl Default for EventTargetMaybeSignal +impl Default for ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { fn default() -> Self { Self::Static(None) } } -impl Clone for EventTargetMaybeSignal +impl Clone for ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { fn clone(&self) -> Self { match self { Self::Static(t) => Self::Static(t.clone()), Self::Dynamic(s) => Self::Dynamic(*s), + _ => unreachable!(), } } } -impl SignalGet> for EventTargetMaybeSignal +impl SignalGet> for ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { fn get(&self) -> Option { match self { Self::Static(t) => t.clone(), Self::Dynamic(s) => s.get(), + _ => unreachable!(), } } @@ -53,18 +57,20 @@ where match self { Self::Static(t) => Some(t.clone()), Self::Dynamic(s) => s.try_get(), + _ => unreachable!(), } } } -impl SignalWith> for EventTargetMaybeSignal +impl SignalWith> for ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { fn with(&self, f: impl FnOnce(&Option) -> O) -> O { match self { Self::Static(t) => f(t), Self::Dynamic(s) => s.with(f), + _ => unreachable!(), } } @@ -72,18 +78,20 @@ where match self { Self::Static(t) => Some(f(t)), Self::Dynamic(s) => s.try_with(f), + _ => unreachable!(), } } } -impl SignalWithUntracked> for EventTargetMaybeSignal +impl SignalWithUntracked> for ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { fn with_untracked(&self, f: impl FnOnce(&Option) -> O) -> O { match self { Self::Static(t) => f(t), Self::Dynamic(s) => s.with_untracked(f), + _ => unreachable!(), } } @@ -91,18 +99,20 @@ where match self { Self::Static(t) => Some(f(t)), Self::Dynamic(s) => s.try_with_untracked(f), + _ => unreachable!(), } } } -impl SignalGetUntracked> for EventTargetMaybeSignal +impl SignalGetUntracked> for ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { fn get_untracked(&self) -> Option { match self { Self::Static(t) => t.clone(), Self::Dynamic(s) => s.get_untracked(), + _ => unreachable!(), } } @@ -110,36 +120,37 @@ where match self { Self::Static(t) => Some(t.clone()), Self::Dynamic(s) => s.try_get_untracked(), + _ => unreachable!(), } } } -impl From<(Scope, T)> for EventTargetMaybeSignal +impl From<(Scope, T)> for ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { fn from(value: (Scope, T)) -> Self { - EventTargetMaybeSignal::Static(Some(value.1)) + ElementMaybeSignal::Static(Some(value.1)) } } -impl From<(Scope, Option)> for EventTargetMaybeSignal +impl From<(Scope, Option)> for ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { fn from(target: (Scope, Option)) -> Self { - EventTargetMaybeSignal::Static(target.1) + ElementMaybeSignal::Static(target.1) } } macro_rules! impl_from_signal_option { ($ty:ty) => { - impl From<(Scope, $ty)> for EventTargetMaybeSignal + impl From<(Scope, $ty)> for ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { 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>); macro_rules! impl_from_signal { ($ty:ty) => { - impl From<(Scope, $ty)> for EventTargetMaybeSignal + impl From<(Scope, $ty)> for ElementMaybeSignal where - T: Into + Clone + 'static, + T: Into + Clone + 'static, { fn from(target: (Scope, $ty)) -> Self { 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); impl_from_signal!(RwSignal); impl_from_signal!(Memo); -impl From<(Scope, NodeRef)> for EventTargetMaybeSignal -where - R: ElementDescriptor + Clone + 'static, -{ - fn from(target: (Scope, NodeRef)) -> Self { - let (cx, node_ref) = target; +macro_rules! impl_from_node_ref { + ($ty:ty) => { + impl From<(Scope, NodeRef)> for ElementMaybeSignal<$ty, $ty> + where + R: ElementDescriptor + Clone + 'static, + { + fn from(target: (Scope, NodeRef)) -> Self { + let (cx, node_ref) = target; - EventTargetMaybeSignal::Dynamic(Signal::derive(cx, move || { - node_ref.get().map(move |el| { - let el = el.into_any(); - let el: web_sys::EventTarget = el.deref().clone().into(); - el - }) - })) - } + ElementMaybeSignal::Dynamic(Signal::derive(cx, move || { + node_ref.get().map(move |el| { + let el = el.into_any(); + let el: $ty = el.deref().clone().into(); + el + }) + })) + } + } + }; } + +impl_from_node_ref!(web_sys::EventTarget); +impl_from_node_ref!(web_sys::Element); diff --git a/src/lib.rs b/src/lib.rs index 98ed266..adc221c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ pub mod core; +pub mod use_debounce_fn; pub mod use_event_listener; pub mod use_scroll; pub mod use_throttle_fn; pub mod utils; +pub use use_debounce_fn::*; pub use use_event_listener::use_event_listener; pub use use_scroll::*; pub use use_throttle_fn::*; diff --git a/src/use_debounce_fn.rs b/src/use_debounce_fn.rs new file mode 100644 index 0000000..168b4cd --- /dev/null +++ b/src/use_debounce_fn.rs @@ -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(func: F, ms: impl Into>) -> impl FnMut() +where + F: FnOnce() + Clone + 'static, +{ + use_debounce_fn_with_options(func, ms, Default::default()) +} + +pub fn use_debounce_fn_with_options( + func: F, + ms: impl Into>, + options: DebounceOptions, +) -> impl FnMut() +where + F: FnOnce() + Clone + 'static, +{ + create_filter_wrapper(debounce_filter(ms, options), func) +} + +pub fn use_debounce_fn_with_arg(func: F, ms: impl Into>) -> 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( + func: F, + ms: impl Into>, + options: DebounceOptions, +) -> impl FnMut(Arg) +where + F: FnOnce(Arg) + Clone + 'static, + Arg: Clone + 'static, +{ + create_filter_wrapper_with_arg(debounce_filter(ms, options), func) +} diff --git a/src/use_event_listener.rs b/src/use_event_listener.rs index 6c42896..343cffd 100644 --- a/src/use_event_listener.rs +++ b/src/use_event_listener.rs @@ -1,4 +1,4 @@ -use crate::core::EventTargetMaybeSignal; +use crate::core::ElementMaybeSignal; use leptos::ev::EventDescriptor; use leptos::*; use std::cell::RefCell; @@ -90,7 +90,7 @@ pub fn use_event_listener( ) -> Box where Ev: EventDescriptor + 'static, - (Scope, El): Into>, + (Scope, El): Into>, T: Into + Clone + 'static, F: FnMut(::EventType) + 'static, { diff --git a/src/use_scroll.rs b/src/use_scroll.rs index 5acceb4..927d911 100644 --- a/src/use_scroll.rs +++ b/src/use_scroll.rs @@ -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::*; +/// 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)] -pub fn use_scroll( +pub fn use_scroll_with_options( cx: Scope, element: El, options: UseScrollOptions, ) -> UseScrollReturn where - (Scope, El): Into>, - T: Into + Clone + 'static, - Fx: Fn(f64), - Fy: Fn(f64), + (Scope, El): Into>, + T: Into + Clone + 'static, { // TODO : implement - let (x, set_x) = create_signal(cx, 0.0); - let (y, set_y) = create_signal(cx, 0.0); + let (internal_x, set_internal_x) = create_signal(cx, 0.0); + let (internal_y, set_internal_y) = create_signal(cx, 0.0); - let (is_scrolling, _) = create_signal(cx, false); - let (arrived_state, _) = create_signal( - cx, - Directions { - left: false, - right: false, - top: false, - bottom: false, - }, - ); - let (directions, _) = create_signal( + let signal = (cx, element).into(); + let behavior = options.behavior; + + let scroll_to = move |x: Option, y: Option| { + let element = signal.get_untracked(); + + if let Some(element) = element { + let element = element.into(); + + let mut scroll_options = web_sys::ScrollToOptions::new(); + 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, Directions { 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 { - x: x.into(), - set_x: Box::new(move |x| set_x.set(x)), - y: y.into(), - set_y: Box::new(move |y| set_y.set(y)), + x: internal_x.into(), + set_x, + y: internal_y.into(), + set_y, is_scrolling: is_scrolling.into(), arrived_state: arrived_state.into(), directions: directions.into(), @@ -50,39 +103,61 @@ where } /// Options for [`use_scroll`]. -#[derive(Default)] pub struct UseScrollOptions { /// 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. /// Defaults to 200. - pub idle: u32, + pub idle: f64, /// Threshold in pixels when we consider a side to have arrived (`UseScrollReturn::arrived_state`). pub offset: ScrollOffset, /// Callback when scrolling is happening. - pub on_scroll: Option>, + pub on_scroll: Box, /// Callback when scrolling stops (after `idle` + `throttle` milliseconds have passed). - pub on_stop: Option>, + pub on_stop: Box>, /// Options passed to the `addEventListener("scroll", ...)` call - pub event_listener_options: Option, + pub event_listener_options: web_sys::AddEventListenerOptions, /// When changing the `x` or `y` signals this specifies the scroll behaviour. /// Can be `Auto` (= not smooth) or `Smooth`. Defaults to `Auto`. 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 { #[default] Auto, Smooth, } +impl Into 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 x: Signal, pub set_x: Box, @@ -101,7 +176,7 @@ pub struct Directions { pub bottom: bool, } -#[derive(Default)] +#[derive(Default, Copy, Clone)] pub struct ScrollOffset { pub left: f64, pub top: f64, diff --git a/src/use_throttle_fn.rs b/src/use_throttle_fn.rs index 1bc5202..95ba9cf 100644 --- a/src/use_throttle_fn.rs +++ b/src/use_throttle_fn.rs @@ -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 std::cell::RefCell; use std::rc::Rc; @@ -41,10 +43,9 @@ pub use crate::utils::ThrottleOptions; /// ``` /// # use leptos::*; /// # use leptos_use::{ThrottleOptions, use_throttle_fn_with_options}; -/// /// # #[component] /// # 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 /// }, @@ -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`]. /// /// ## Recommended Reading @@ -69,7 +70,7 @@ pub fn use_throttle_fn( ms: impl Into>, ) -> impl FnMut() -> Rc>> where - F: FnMut() -> R + Clone + 'static, + F: FnOnce() -> R + Clone + 'static, R: 'static, { use_throttle_fn_with_options(func, ms, Default::default()) @@ -82,10 +83,10 @@ pub fn use_throttle_fn_with_options( options: ThrottleOptions, ) -> impl FnMut() -> Rc>> where - F: FnMut() -> R + Clone + 'static, + F: FnOnce() -> R + Clone + '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. @@ -94,8 +95,8 @@ pub fn use_throttle_fn_with_arg( ms: impl Into>, ) -> impl FnMut(Arg) -> Rc>> where - F: FnMut(Arg) -> R + Clone + 'static, - Arg: 'static, + F: FnOnce(Arg) -> R + Clone + 'static, + Arg: Clone + 'static, R: 'static, { use_throttle_fn_with_arg_and_options(func, ms, Default::default()) @@ -108,9 +109,9 @@ pub fn use_throttle_fn_with_arg_and_options( options: ThrottleOptions, ) -> impl FnMut(Arg) -> Rc>> where - F: FnMut(Arg) -> R + Clone + 'static, - Arg: 'static, + F: FnOnce(Arg) -> R + Clone + 'static, + Arg: Clone + '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) } diff --git a/src/utils/clonable_fn.rs b/src/utils/clonable_fn.rs new file mode 100644 index 0000000..45965df --- /dev/null +++ b/src/utils/clonable_fn.rs @@ -0,0 +1,101 @@ +pub trait CloneableFnWithReturn: FnOnce() -> R { + fn clone_box(&self) -> Box>; +} + +impl CloneableFnWithReturn for F +where + F: FnOnce() -> R + Clone + 'static, + R: 'static, +{ + fn clone_box(&self) -> Box> { + Box::new(self.clone()) + } +} + +impl Clone for Box> { + fn clone(&self) -> Self { + (**self).clone_box() + } +} + +impl Default for Box> { + fn default() -> Self { + Box::new(|| Default::default()) + } +} + +pub trait CloneableFnWithReturnAndArg: FnOnce(Arg) -> R { + fn clone_box(&self) -> Box>; +} + +impl CloneableFnWithReturnAndArg for F +where + F: FnMut(Arg) -> R + Clone + 'static, + R: 'static, +{ + fn clone_box(&self) -> Box> { + Box::new(self.clone()) + } +} + +impl Clone for Box> { + fn clone(&self) -> Self { + (**self).clone_box() + } +} + +impl Default for Box> { + fn default() -> Self { + Box::new(|_| Default::default()) + } +} + +pub trait CloneableFn: FnOnce() { + fn clone_box(&self) -> Box; +} + +impl CloneableFn for F +where + F: FnOnce() + Clone + 'static, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + (**self).clone_box() + } +} + +impl Default for Box { + fn default() -> Self { + Box::new(|| {}) + } +} + +pub trait CloneableFnWithArg: FnOnce(Arg) { + fn clone_box(&self) -> Box>; +} + +impl CloneableFnWithArg for F +where + F: FnMut(Arg) + Clone + 'static, +{ + fn clone_box(&self) -> Box> { + Box::new(self.clone()) + } +} + +impl Clone for Box> { + fn clone(&self) -> Self { + (**self).clone_box() + } +} + +impl Default for Box> { + fn default() -> Self { + Box::new(|_| {}) + } +} diff --git a/src/utils/demo.rs b/src/utils/demo.rs new file mode 100644 index 0000000..5ff446e --- /dev/null +++ b/src/utils/demo.rs @@ -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::()) + .unwrap_or(document().body().expect("body to exist")) +} diff --git a/src/utils/filters/debounce.rs b/src/utils/filters/debounce.rs new file mode 100644 index 0000000..e25d4de --- /dev/null +++ b/src/utils/filters/debounce.rs @@ -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>, +} + +pub fn debounce_filter( + ms: impl Into>, + options: DebounceOptions, +) -> impl FnMut(Box>) -> Rc>> { + let timer = Rc::new(Cell::new(None::)); + let max_timer = Rc::new(Cell::new(None::)); + + let clear_timeout = move |timer: &Rc>>| { + if let Some(handle) = timer.get() { + handle.clear(); + timer.set(None); + } + }; + + let ms = ms.into(); + + move |invoke: Box>| { + 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)) + } +} diff --git a/src/utils/filters/mod.rs b/src/utils/filters/mod.rs new file mode 100644 index 0000000..03cfc03 --- /dev/null +++ b/src/utils/filters/mod.rs @@ -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(mut filter: Filter, func: F) -> impl FnMut() +where + F: FnOnce() + Clone + 'static, + Filter: FnMut(Box>) -> Rc>>, +{ + move || { + filter(Box::new(func.clone())); + } +} + +pub fn create_filter_wrapper_with_arg( + mut filter: Filter, + func: F, +) -> impl FnMut(Arg) +where + F: FnOnce(Arg) + Clone + 'static, + Arg: Clone + 'static, + Filter: FnMut(Box>) -> Rc>>, +{ + move |arg: Arg| { + let mut func = func.clone(); + filter(Box::new(move || func(arg))); + } +} + +pub fn create_filter_wrapper_with_return( + mut filter: Filter, + func: F, +) -> impl FnMut() -> Rc>> +where + F: FnOnce() -> R + Clone + 'static, + R: 'static, + Filter: FnMut(Box>) -> Rc>>, +{ + move || filter(Box::new(func.clone())) +} + +pub fn create_filter_wrapper_with_return_and_arg( + mut filter: Filter, + func: F, +) -> impl FnMut(Arg) -> Rc>> +where + F: FnOnce(Arg) -> R + Clone + 'static, + R: 'static, + Arg: Clone + 'static, + Filter: FnMut(Box>) -> Rc>>, +{ + move |arg: Arg| { + let mut func = func.clone(); + filter(Box::new(move || func(arg))) + } +} diff --git a/src/utils/filters.rs b/src/utils/filters/throttle.rs similarity index 74% rename from src/utils/filters.rs rename to src/utils/filters/throttle.rs index 21eb34c..c011923 100644 --- a/src/utils/filters.rs +++ b/src/utils/filters/throttle.rs @@ -1,3 +1,4 @@ +use crate::utils::CloneableFnWithReturn; use js_sys::Date; use leptos::leptos_dom::helpers::TimeoutHandle; use leptos::{set_timeout_with_handle, MaybeSignal, SignalGetUntracked}; @@ -6,38 +7,6 @@ use std::cmp::max; use std::rc::Rc; use std::time::Duration; -pub fn create_filter_wrapper( - mut filter: Filter, - func: F, -) -> impl FnMut() -> Rc>> -where - F: FnMut() -> R + Clone + 'static, - R: 'static, - Filter: FnMut(Box R>) -> Rc>>, -{ - move || { - let wrapped_func = Box::new(func.clone()); - filter(wrapped_func) - } -} - -pub fn create_filter_wrapper_with_arg( - mut filter: Filter, - func: F, -) -> impl FnMut(Arg) -> Rc>> -where - F: FnMut(Arg) -> R + Clone + 'static, - R: 'static, - Arg: 'static, - Filter: FnMut(Box R>) -> Rc>>, -{ - move |arg: Arg| { - let mut func = func.clone(); - let wrapped_func = Box::new(move || func(arg)); - filter(wrapped_func) - } -} - #[derive(Copy, Clone)] pub struct ThrottleOptions { pub trailing: bool, @@ -56,7 +25,7 @@ impl Default for ThrottleOptions { pub fn throttle_filter( ms: impl Into>, options: ThrottleOptions, -) -> impl FnMut(Box R>) -> Rc>> +) -> impl FnMut(Box>) -> Rc>> where R: 'static, { @@ -75,7 +44,7 @@ where let ms = ms.into(); - move |mut _invoke: Box R>| { + move |mut _invoke: Box>| { let duration = ms.get_untracked(); let elapsed = Date::now() - last_exec.get(); diff --git a/src/utils/is.rs b/src/utils/is.rs deleted file mode 100644 index 55cc3c5..0000000 --- a/src/utils/is.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub fn noop() -> Box { - Box::new(|| {}) -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 77594b8..aadd367 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,7 @@ +mod clonable_fn; +mod demo; mod filters; -mod is; +pub use clonable_fn::*; +pub use demo::*; pub use filters::*; -pub use is::*;