├── .editorconfig
├── .github
└── workflows
│ └── deploy.yml
├── .gitignore
├── .stackblitzrc
├── CNAME
├── README.md
├── benchmarks
├── view-rendering-2
│ ├── index.html
│ └── index.js
└── view-rendering
│ ├── index.html
│ └── index.js
├── docs
├── .gitignore
├── 404.html
├── CNAME
├── Gemfile
├── Gemfile.lock
├── _config.yml
├── _posts
│ └── _test.md.xxx
├── assets
│ ├── css
│ │ └── style.scss
│ ├── event-adapters.png
│ ├── github-hero-2.png
│ ├── how-rimmel-testing-works.png
│ ├── how-rimmel-works-3.png
│ ├── how-rimmel-works-4.png
│ ├── how-rimmel-works-5.png
│ ├── how-rimmel-works-6.png
│ ├── how-rimmel-works-7.png
│ ├── how-rimmel-works-9.png
│ ├── how-rimmel-works.png
│ ├── isolating-side-effects.png
│ ├── observable-types.png
│ ├── rimmel.png
│ └── try-it-button.png
├── docs
│ └── assets
├── favicon.png
└── index.md
├── examples
└── kitchen-sink
│ ├── flower.webm
│ ├── index.html
│ └── index.ts
├── favicon.svg
├── jest.config.js
├── package-lock.json
├── package.json
├── rollup.config.dts.js
├── rollup.config.js
├── server.sh
├── src
├── constants.ts
├── custom-element.ts
├── debug.ts
├── definitions
│ ├── boolean-attributes.ts
│ ├── enumerated-attributes.ts
│ └── non-bubbling-events.ts
├── index.ts
├── internal-state.ts
├── lib
│ ├── addListener.ts
│ ├── drain.ts
│ └── observature.ts
├── lifecycle
│ └── data-binding.ts
├── parser
│ ├── parser.test.ts
│ ├── parser.ts
│ ├── sink-map.ts
│ └── ssr.ts
├── schedulers
│ ├── adaptive-animation-frame.ts
│ ├── animation-frame-by-node.ts
│ ├── animation-frame.ts
│ └── ema-animation-frame.ts
├── sinks
│ ├── any-sink.ts
│ ├── append-html-sink.test.ts
│ ├── append-html-sink.ts
│ ├── attribute-sink.test.ts
│ ├── attribute-sink.ts
│ ├── blur-sink.test-d.ts
│ ├── blur-sink.test.ts
│ ├── blur-sink.ts
│ ├── checked-sink.test.ts
│ ├── checked-sink.ts
│ ├── class-sink.test.ts
│ ├── class-sink.ts
│ ├── closed-sink.ts
│ ├── content-sink.ts
│ ├── dataset-sink.test.ts
│ ├── dataset-sink.ts
│ ├── disabled-sink.test.ts
│ ├── disabled-sink.ts
│ ├── error-sink.test.ts
│ ├── error-sink.ts
│ ├── event-handler-sink.ts
│ ├── focus-sink.test.ts
│ ├── focus-sink.ts
│ ├── hidden-sink.ts
│ ├── index.ts
│ ├── inner-html-sink.test.ts
│ ├── inner-html-sink.ts
│ ├── inner-text-sink.test.ts
│ ├── inner-text-sink.ts
│ ├── json-dump-sink.test.ts
│ ├── json-dump-sink.ts
│ ├── mixin-sink.ts
│ ├── prepend-html-sink.test.ts
│ ├── prepend-html-sink.ts
│ ├── readonly-sink.test.ts
│ ├── readonly-sink.ts
│ ├── removed-sink.test.ts
│ ├── removed-sink.ts
│ ├── sanitize-html-sink.test.ts
│ ├── sanitize-html-sink.ts
│ ├── selected-index-sink.test.ts
│ ├── selected-index-sink.ts
│ ├── signal-sink.ts
│ ├── style-sink.test.ts
│ ├── style-sink.ts
│ ├── subtree-sink.ts
│ ├── suspense-sink.ts
│ ├── termination-sink.ts
│ ├── text-content-sink.test.ts
│ ├── text-content-sink.ts
│ ├── value-sink.test.ts
│ ├── value-sink.ts
│ └── writable-stream.ts
├── sources
│ ├── all-source.ts
│ ├── as-latest-from.ts
│ ├── autoform-source.ts
│ ├── checked-source.test.ts
│ ├── checked-source.ts
│ ├── client-xy-source.test.ts
│ ├── client-xy-source.ts
│ ├── cut-source.test.ts
│ ├── cut-source.ts
│ ├── dataset-source.test.ts
│ ├── dataset-source.ts
│ ├── event-data.ts
│ ├── event-listener-object-source.ts
│ ├── event-listener.test.ts
│ ├── event-listener.ts
│ ├── event-target.ts
│ ├── first-touch-xy-source.ts
│ ├── form-data-source.ts
│ ├── keyboard-source.ts
│ ├── last-touch-xy-source.ts
│ ├── modifiers
│ │ ├── active.ts
│ │ └── passive.ts
│ ├── numberset-source.ts
│ ├── object-source.test.ts
│ ├── object-source.ts
│ ├── observer-source.ts
│ ├── offset-xy-source.ts
│ ├── readable-stream.ts
│ ├── swap-source.ts
│ ├── value-source.test.ts
│ └── value-source.ts
├── ssr
│ ├── hydration.ts
│ └── index.ts
├── test-support.ts
├── types
│ ├── attribute.ts
│ ├── basic.ts
│ ├── class.ts
│ ├── constructs.ts
│ ├── content.ts
│ ├── coords.ts
│ ├── dataset.ts
│ ├── dom-observable.d.ts
│ ├── dom.ts
│ ├── event-listener.ts
│ ├── futures.ts
│ ├── index.ts
│ ├── internal.ts
│ ├── json.ts
│ ├── monkey-patched-observable.ts
│ ├── rml.ts
│ ├── schedulers.ts
│ ├── sink.ts
│ ├── source.ts
│ ├── style.ts
│ └── value.ts
└── utils
│ ├── auto-value.ts
│ ├── camelCase.ts
│ ├── curry.ts
│ ├── input-pipe.ts
│ ├── is-behavior.ts
│ ├── is-function.ts
│ ├── suspense.ts
│ ├── take-first-sync.ts
│ └── to-listener.ts
├── tsconfig.json
├── typedoc.json
└── vite.config.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | tab_width = 2
6 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy Handbook and Website
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build-and-deploy:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout repository
14 | uses: actions/checkout@v3
15 |
16 | - name: Set up Node.js
17 | uses: actions/setup-node@v3
18 | with:
19 | node-version: 22
20 |
21 | - name: Install dependencies
22 | run: npm ci # Or the appropriate command to install dependencies
23 |
24 | - name: Build the library
25 | run: npm run build
26 |
27 | - name: Build the site
28 | run: npm run website
29 |
30 | - name: Build the handbook
31 | run: npm run handbook
32 |
33 | - name: Deploy to GitHub Pages
34 | uses: peaceiris/actions-gh-pages@v3
35 | with:
36 | github_token: ${{ secrets.GITHUB_TOKEN }}
37 | publish_dir: ./dist
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | docs
3 | dist
4 |
5 | .vscode
6 | bundle-stats*
7 |
8 |
--------------------------------------------------------------------------------
/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "startCommand": "npm run kitchen-sink"
3 | }
4 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | rimmel.js.org
--------------------------------------------------------------------------------
/benchmarks/view-rendering-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/benchmarks/view-rendering-2/index.js:
--------------------------------------------------------------------------------
1 | import { rml } from '../../dist/rimmel.es.js';
2 | const { Subject, BehaviorSubject } = rxjs;
3 | const { map, scan } = rxjs.operators
4 |
5 | const view1 = () => {
6 | const counter = (new BehaviorSubject(0)).pipe(
7 | scan(a=>a+1)
8 | )
9 | return rml`
10 | You changed view
${renderCount} times.
56 | `
57 | }
58 |
59 | run()
60 |
61 |
--------------------------------------------------------------------------------
/benchmarks/view-rendering/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/benchmarks/view-rendering/index.js:
--------------------------------------------------------------------------------
1 | import { rml } from '../../dist/rimmel.es.js';
2 | const { Subject, BehaviorSubject } = rxjs;
3 | const { map, scan } = rxjs.operators
4 |
5 | const view1 = () => {
6 | const counter = (new BehaviorSubject(0)).pipe(
7 | scan(a=>a+1)
8 | )
9 | return rml`
10 |
View 1
11 |
This is view1. It does some stuff, etc
12 |
This is view1. It does some stuff, etc
13 |
This is view1. It does some stuff, etc
14 |
15 | You clicked the button
${counter} times.
16 | `
17 | }
18 |
19 | const view2 = () => {
20 | const counter = (new BehaviorSubject(0)).pipe(
21 | scan(a=>a+1)
22 | )
23 | return rml`
24 |
View 2
25 |
26 |
This is view2. It does some more stuff, etc
27 |
This is view2. It does , etc
28 |
This is view2. It does some more stuff, etc
29 | You clicked the button
${counter} times.
30 | `
31 | }
32 |
33 | function run() {
34 | const currentView = new Subject()
35 | const views = [view1, view2]
36 | const renderCount = currentView.pipe(
37 | map(x=>1),
38 | scan(a=>a+1),
39 | )
40 |
41 | let idx = 0
42 |
43 | const changeView = () => {
44 | currentView.next(views[idx])
45 | idx = (idx+1)%views.length
46 | requestAnimationFrame(changeView)
47 | }
48 |
49 | document.body.innerHTML = rml`
50 |
render view 1
51 |
render view 2
52 |
53 | ${[...Array(20).fill(0)].map((x,i)=>rml`
54 |
X ${renderCount}
55 | `)}
56 | ${[...Array(20).fill(0)].map((x,i)=>rml`
57 |
Y ${renderCount}
58 | `)}
59 |
60 |
61 | ${currentView}
62 |
63 |
You changed view
${renderCount} times.
64 | `
65 |
66 | changeView()
67 | }
68 |
69 | run()
70 |
71 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | _site
2 | .sass-cache
3 | .jekyll-cache
4 | .jekyll-metadata
5 | vendor
6 |
--------------------------------------------------------------------------------
/docs/404.html:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /404.html
3 | layout: default
4 | ---
5 |
6 |
19 |
20 |
21 |
404
22 |
23 |
Page not found :(
24 |
The requested page could not be found.
25 |
26 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | rimmel.js.org
--------------------------------------------------------------------------------
/docs/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | # Hello! This is where you manage which Jekyll version is used to run.
3 | # When you want to use a different version, change it below, save the
4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
5 | #
6 | # bundle exec jekyll serve
7 | #
8 | # This will help ensure the proper Jekyll version is running.
9 | # Happy Jekylling!
10 | # gem "jekyll", "~> 4.3.3"
11 |
12 | gem "github-pages", "~> 228", group: :jekyll_plugins
13 | gem 'jekyll-theme-leap-day', '~> 0.2.0'
14 |
15 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and
16 | # uncomment the line below. To upgrade, run `bundle update github-pages`.
17 | # gem "github-pages", group: :jekyll_plugins
18 | # If you have any plugins, put them here!
19 | group :jekyll_plugins do
20 | gem "jekyll-feed", "~> 0.12"
21 | end
22 |
23 | # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
24 | # and associated library.
25 | platforms :mingw, :x64_mingw, :mswin, :jruby do
26 | gem "tzinfo", ">= 1", "< 3"
27 | gem "tzinfo-data"
28 | end
29 |
30 | # Performance-booster for watching directories on Windows
31 | gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin]
32 |
33 | # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem
34 | # do not have a Java counterpart.
35 | gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby]
36 |
37 | gem "webrick", "~> 1.8"
38 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | # Welcome to Jekyll!
2 | #
3 | # This config file is meant for settings that affect your whole blog, values
4 | # which you are expected to set up once and rarely edit after that. If you find
5 | # yourself editing this file very often, consider using Jekyll's data files
6 | # feature for the data you need to update frequently.
7 | #
8 | # For technical reasons, this file is *NOT* reloaded automatically when you use
9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process.
10 | #
11 | # If you need help with YAML syntax, here are some quick references for you:
12 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml
13 | # https://learnxinyminutes.com/docs/yaml/
14 | #
15 | # Site settings
16 | # These are used to personalize your new site. If you look in the HTML files,
17 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
18 | # You can create any custom variable you would like, and they will be accessible
19 | # in the templates via {{ site.myvariable }}.
20 |
21 | title: Rimmel.js
22 | email: your-email@example.com
23 | description: A Streams-Oriented UI library for the Rx.Observable Universe
24 | baseUrl: "/" # the subpath of your site, e.g. /blog
25 | url: "https://rimmel.js.org" # the base hostname & protocol for your site, e.g. http://example.com
26 | #twitter_username: jekyllrb
27 | #github_username: jekyll
28 |
29 | # Build settings
30 | theme: jekyll-theme-leap-day
31 | plugins:
32 | # - jekyll-feed
33 |
34 | include:
35 | - handbook
36 |
37 | # Exclude from processing.
38 | # The following items will not be processed, by default.
39 | # Any item listed under the `exclude:` key here will be automatically added to
40 | # the internal "default list".
41 | #
42 | # Excluded items can be processed by explicitly listing the directories or
43 | # their entries' file path in the `include:` list.
44 | #
45 | # exclude:
46 | # - .sass-cache/
47 | # - .jekyll-cache/
48 | # - gemfiles/
49 | # - Gemfile
50 | # - Gemfile.lock
51 | # - node_modules/
52 | # - vendor/bundle/
53 | # - vendor/cache/
54 | # - vendor/gems/
55 | # - vendor/ruby/
56 |
--------------------------------------------------------------------------------
/docs/_posts/_test.md.xxx:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Welcome to Jekyll!"
4 | date: 2024-02-04 20:46:29 +0000
5 | categories: jekyll update
6 | ---
7 | You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated.
8 |
9 | Jekyll requires blog post files to be named according to the following format:
10 |
11 | `YEAR-MONTH-DAY-title.MARKUP`
12 |
13 | Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `MARKUP` is the file extension representing the format used in the file. After that, include the necessary front matter. Take a look at the source for this post to get an idea about how it works.
14 |
15 | Jekyll also offers powerful support for code snippets:
16 |
17 | {% highlight ruby %}
18 | def print_hi(name)
19 | puts "Hi, #{name}"
20 | end
21 | print_hi('Tom')
22 | #=> prints 'Hi, Tom' to STDOUT.
23 | {% endhighlight %}
24 |
25 | Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk].
26 |
27 | [jekyll-docs]: https://jekyllrb.com/docs/home
28 | [jekyll-gh]: https://github.com/jekyll/jekyll
29 | [jekyll-talk]: https://talk.jekyllrb.com/
30 |
--------------------------------------------------------------------------------
/docs/assets/css/style.scss:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | @import "{{ site.theme }}";
5 |
6 | body { font-size: 18px; line-height: 26px; }
7 |
8 | h1, h2, h3 {
9 | margin-top: 3rem;
10 | }
11 |
12 | header { background: #ac4142; }
13 |
14 | @media print, screen and (max-width: 720px) {
15 | header p {
16 | display: block;
17 | }
18 | }
19 |
20 | #banner {
21 | border-radius: 0px 2px .6rem 0px;
22 | }
23 |
24 | section {
25 | padding: 1rem;
26 | margin-top: 170px;
27 | }
28 |
29 | section img {
30 | display: block;
31 | margin-left: auto;
32 | margin-right: auto;
33 | max-width: 100%;
34 | }
35 |
36 |
37 | @media print, screen and (max-width: 720px) {
38 | #banner {
39 | top: 115px;
40 | }
41 |
42 | #banner .fork {
43 | float: none;
44 | display: inline-block;
45 | position: static;
46 | margin-left: 1rem;
47 | }
48 | }
49 |
50 | @media print, screen and (max-width: 480px) {
51 | #banner {
52 | position: static;
53 | display: block;
54 | }
55 |
56 | section {
57 | position: static;
58 | margin-top: 0px;
59 | }
60 | }
61 |
62 | #banner #logo {
63 | background: url(/assets/rimmel.png);
64 | background-size: cover;
65 | filter: saturate(0) brightness(2);
66 | }
67 |
68 | code { font-family: monospace; font-size: 12px; }
69 |
70 | code>* {
71 | margin: 0;
72 | padding: 0;
73 | line-height: normal;
74 | }
75 |
76 | ul{ list-style-type: disclosure-closed; list-style-image: none; }
77 | ::marker{ color: #ac4142; }
78 |
79 | footer {
80 | position: static;
81 | width: auto;
82 | margin-left: 0;
83 | }
84 |
85 | h1, h2, h3 {
86 | margin-block-start: 4rem;
87 | color: #aa0033ff;
88 | }
89 |
90 | .playground-link img {
91 | display: none;
92 | }
93 |
94 | .playground-link {
95 | margin-block: 3rem;
96 | margin-inline: 0 1rem;
97 | font-size: 125%;
98 | white-space: nowrap;
99 | }
100 |
101 | .playground-link::before {
102 | display: inline-block;
103 | margin-inline: 0 2rem;
104 | padding: .6rem 1rem;
105 | background: #aa0033ff;
106 | color: gold;
107 | border-top-right-radius: .4rem;
108 | border-bottom-right-radius: .4rem;
109 | border-spacing: 4px;
110 | content: "Try it ⮞"
111 | }
112 |
113 |
--------------------------------------------------------------------------------
/docs/assets/event-adapters.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/event-adapters.png
--------------------------------------------------------------------------------
/docs/assets/github-hero-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/github-hero-2.png
--------------------------------------------------------------------------------
/docs/assets/how-rimmel-testing-works.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/how-rimmel-testing-works.png
--------------------------------------------------------------------------------
/docs/assets/how-rimmel-works-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/how-rimmel-works-3.png
--------------------------------------------------------------------------------
/docs/assets/how-rimmel-works-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/how-rimmel-works-4.png
--------------------------------------------------------------------------------
/docs/assets/how-rimmel-works-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/how-rimmel-works-5.png
--------------------------------------------------------------------------------
/docs/assets/how-rimmel-works-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/how-rimmel-works-6.png
--------------------------------------------------------------------------------
/docs/assets/how-rimmel-works-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/how-rimmel-works-7.png
--------------------------------------------------------------------------------
/docs/assets/how-rimmel-works-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/how-rimmel-works-9.png
--------------------------------------------------------------------------------
/docs/assets/how-rimmel-works.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/how-rimmel-works.png
--------------------------------------------------------------------------------
/docs/assets/isolating-side-effects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/isolating-side-effects.png
--------------------------------------------------------------------------------
/docs/assets/observable-types.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/observable-types.png
--------------------------------------------------------------------------------
/docs/assets/rimmel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/rimmel.png
--------------------------------------------------------------------------------
/docs/assets/try-it-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/assets/try-it-button.png
--------------------------------------------------------------------------------
/docs/docs/assets:
--------------------------------------------------------------------------------
1 | ../assets
--------------------------------------------------------------------------------
/docs/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/docs/favicon.png
--------------------------------------------------------------------------------
/examples/kitchen-sink/flower.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactiveHTML/rimmel/511feb4c61f552edd8deac083dbed499ca03c08e/examples/kitchen-sink/flower.webm
--------------------------------------------------------------------------------
/examples/kitchen-sink/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/favicon.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const common = {
2 | verbose: true,
3 | testPathIgnorePatterns: ['
/node_modules/'],
4 | coveragePathIgnorePatterns: ['node_modules'],
5 | transform: { '^.+\\.(j|t)s(x)?$': 'esbuild-jest' },
6 | moduleFileExtensions: ['js'],
7 | };
8 |
9 | module.exports = {
10 | projects: [
11 | {
12 | ...common,
13 | displayName: 'dom',
14 | testEnvironment: 'jsdom',
15 | testRegex: '/src/.*\\.web\\.test\\.js$',
16 | },
17 | {
18 | ...common,
19 | displayName: 'node',
20 | testEnvironment: 'node',
21 | testRegex: '/src/.*\\.node\\.test\\.js$',
22 | },
23 | ],
24 | };
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rimmel",
3 | "version": "1.4.4",
4 | "description": "A Streams-Oriented UI library for the Rx.Observable Universe",
5 | "type": "module",
6 | "_main": "dist/cjs/index.cjs",
7 | "module": "dist/esm/index.js",
8 | "types": "dist/esm/types/index.d.ts",
9 | "exports": {
10 | ".": {
11 | "import": "./dist/esm/index.js",
12 | "types": "./dist/esm/types/index.d.ts"
13 | },
14 | "./ssr": {
15 | "import": "./dist/ssr/index.mjs",
16 | "require": "./dist/ssr/index.cjs"
17 | }
18 | },
19 | "files": [
20 | "dist/"
21 | ],
22 | "sideEffects": [
23 | "./src/lifecycle/data-binding.ts"
24 | ],
25 | "scripts": {
26 | "build": "rimraf dist && rollup --config rollup.config.js",
27 | "dev": "rimraf dist && rollup -w --config rollup.config.js",
28 | "handbook:dev": "npx typedoc --watch",
29 | "handbook": "npx typedoc",
30 | "kitchen-sink": "cd examples/kitchen-sink && vite",
31 | "website": "cd docs && bundle exec jekyll build",
32 | "website:local": "cd docs && bundle exec jekyll serve",
33 | "test": "bun test",
34 | "test:types": "tsd --files src/**/*.test-d.ts"
35 | },
36 | "repository": {
37 | "type": "git",
38 | "url": "git+https://github.com/reactivehtml/rimmel.git"
39 | },
40 | "keywords": [
41 | "rimmel",
42 | "rimmel.js",
43 | "rimmeljs",
44 | "rxjs",
45 | "rx",
46 | "html",
47 | "observables",
48 | "observable",
49 | "reactive",
50 | "reactive html",
51 | "reactive dom",
52 | "rml",
53 | "reactive-markup",
54 | "stream",
55 | "streams-oriented",
56 | "streams-oriented programming",
57 | "fp",
58 | "functional programming",
59 | "functional/reactive",
60 | "functional/reactive programming"
61 | ],
62 | "author": "Dario Mannu",
63 | "license": "MIT",
64 | "bugs": {
65 | "url": "https://github.com/reactivehtml/rimmel/issues"
66 | },
67 | "homepage": "https://rimmel.js.org",
68 | "devDependencies": {
69 | "@rollup/plugin-commonjs": "^25.0.7",
70 | "@rollup/plugin-json": "^6.1.0",
71 | "@rollup/plugin-node-resolve": "^15.2.3",
72 | "@rollup/plugin-typescript": "^11.1.6",
73 | "@types/jest": "^29.5.11",
74 | "@types/node": "^22.10.2",
75 | "esbuild": "^0.19.11",
76 | "esbuild-jest": "^0.5.0",
77 | "jest": "^27.2.0",
78 | "rimraf": "^3.0.2",
79 | "rollup": "^4.9.5",
80 | "rollup-plugin-visualizer": "^5.12.0",
81 | "rxjs": "^7.8.1",
82 | "ts-node": "^10.9.2",
83 | "tsd": "^0.31.2",
84 | "tslib": "^2.8.0",
85 | "typedoc": "^0.27.5",
86 | "typescript": "^5.7.2",
87 | "vite": "^5.4.9"
88 | },
89 | "peerDependencies": {
90 | "rxjs": ">=5.5.0 || >=6.0.0 || >=7.0.0 || =8.0.0-alpha.14"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/rollup.config.dts.js:
--------------------------------------------------------------------------------
1 | import dts from 'rollup-plugin-dts';
2 |
3 | export default {
4 | input: './dist/esm/types/index.d.ts', // The main entry for types
5 | output: {
6 | file: './dist/esm/rimmel.d.ts',
7 | format: 'es'
8 | },
9 | plugins: [dts()]
10 | };
11 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { nodeResolve } from '@rollup/plugin-node-resolve';
2 | import json from '@rollup/plugin-json';
3 | import { join } from 'path';
4 | import typescript from '@rollup/plugin-typescript';
5 | import { visualizer } from 'rollup-plugin-visualizer';
6 |
7 | const getTSConfig = async (path) => {
8 | const tsConfig = (await import('./tsconfig.json', { assert: { type: 'json' } })).default;
9 |
10 | tsConfig.compilerOptions.outDir = path;
11 | tsConfig.compilerOptions.declarationDir = join(path, 'types');
12 | return tsConfig.compilerOptions;
13 | };
14 |
15 | export default [
16 | { // Global JS
17 | external: ['rxjs'],
18 | input: './src/index.ts',
19 | treeshake: {
20 | propertyReadSideEffects: false, // Optimise property access side effects
21 | },
22 | plugins: [
23 | nodeResolve({ preferBuiltins: true }),
24 | // json(),
25 | typescript({
26 | ...await getTSConfig('dist/globaljs'),
27 | sourceMap: true,
28 | outDir: 'dist/globaljs',
29 | declaration: false,
30 | declarationDir: undefined, // Explicitly unset
31 | declarationMap: false, // Explicitly disable
32 | }),
33 | visualizer({ filename: 'bundle-stats-globaljs.html' }),
34 | ],
35 | output: [{
36 | exports: 'named',
37 | externalLiveBindings: false,
38 | dir: './dist/globaljs',
39 | entryFileNames: '[name].mjs',
40 | freeze: true,
41 | generatedCode: 'es2015',
42 | format: 'iife',
43 | name: 'rml',
44 | globals: {
45 | 'rxjs': 'rxjs',
46 | },
47 | sourcemap: true,
48 | }],
49 | },
50 |
51 | { // ESM
52 | external: ['rxjs'],
53 | input: './src/index.ts',
54 | treeshake: {
55 | propertyReadSideEffects: false, // Optimise property access side effects
56 | },
57 | plugins: [
58 | nodeResolve({ preferBuiltins: true }),
59 | json(),
60 | typescript({
61 | ...await getTSConfig('dist/esm'),
62 | }),
63 | visualizer({ filename: 'bundle-stats-esm.html' }),
64 | ],
65 | output: [
66 | {
67 | exports: 'named',
68 | externalLiveBindings: false,
69 | freeze: false,
70 | sourcemap: true,
71 | entryFileNames: '[name].js',
72 | format: 'es',
73 | dir: './dist/esm',
74 | preserveModules: true,
75 | }
76 | ],
77 | },
78 |
79 | { // SSR
80 | external: ['rxjs'],
81 | input: './src/ssr/index.ts',
82 | treeshake: {
83 | moduleSideEffects: 'no-external', // Only shake internal code
84 | propertyReadSideEffects: false, // Optimise property access side effects
85 | },
86 | plugins: [
87 | nodeResolve({ preferBuiltins: true }),
88 | json(),
89 | typescript({
90 | ...await getTSConfig('dist/ssr'),
91 | sourceMap: true,
92 | outDir: 'dist/ssr',
93 | declaration: true,
94 | }),
95 | visualizer({ filename: 'bundle-stats-ssr.html' }),
96 | ],
97 | output: [
98 | {
99 | exports: 'named',
100 | externalLiveBindings: false,
101 | dir: './dist/ssr',
102 | entryFileNames: '[name].mjs',
103 | format: 'es',
104 | freeze: false,
105 | sourcemap: true,
106 | },
107 | {
108 | exports: 'named',
109 | externalLiveBindings: false,
110 | dir: './dist/ssr',
111 | entryFileNames: '[name].cjs',
112 | format: 'cjs',
113 | freeze: false,
114 | sourcemap: true,
115 | }
116 | ],
117 | },
118 | ];
119 |
120 |
--------------------------------------------------------------------------------
/server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | http-server dist/esm -c-1 -t300 -p8000 --cors
4 | # http-server . -c-1 -t300 -p8000 --cors
5 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | import type { RenderingScheduler } from './types/schedulers';
2 |
3 | declare global {
4 | interface Window {
5 | RMLREF: string;
6 | }
7 | }
8 | self.RMLREF='';
9 |
10 | export const REF_TAG: string = 'RMLREF+';
11 | export const REF_REGEXP: RegExp = /^RMLREF+\d+$/;
12 |
13 | // custom attribute and corresponding selector to find just-mounted elements
14 | // that need any data binding
15 | export const RESOLVE_ATTRIBUTE: string = 'resolve'; // keep lowercase for SVG
16 | export const RESOLVE_SELECTOR: string = `[${RESOLVE_ATTRIBUTE}]`;
17 |
18 | // An equivalent of the "debugger;" JavaScript expression, for templates
19 | export const RML_DEBUG: string = 'rml:debugger';
20 |
21 | // Special, non-printable Unicode characters to wrap interactive text nodes
22 | // letting Rimmel know they'll need to be rendered as Text Nodes in the DOM, for updates
23 | export const INTERACTIVE_NODE_START = '\u200B';
24 | export const INTERACTIVE_NODE_END = '\u200C'; // FIXME: can't use this
25 |
26 | export const SOURCE_TAG: string = 'source';
27 | export const SINK_TAG: string = 'sink';
28 |
29 | // Use the new native Web Platform Observables instead of addEventListener when available
30 | export var USE_DOM_OBSERVABLES: boolean = false;
31 | export const set_USE_DOM_OBSERVABLES = ((x: boolean) => USE_DOM_OBSERVABLES = x);
32 |
33 | export const SymbolObservature = Symbol.for('observature');
34 |
35 | // export var renderingScheduler = '../schedulers/ema-animation-frame';
36 | export var renderingScheduler: RenderingScheduler | null = null;
37 | export const setRenderingScheduler = (scheduler: RenderingScheduler) => renderingScheduler = scheduler;
38 |
39 | // export const configure = () => {
40 | // return rml
41 | // }
42 |
--------------------------------------------------------------------------------
/src/debug.ts:
--------------------------------------------------------------------------------
1 | // Show stack traces for Sources and Sinks
2 | export const tracing: boolean = true;
3 |
4 |
--------------------------------------------------------------------------------
/src/definitions/boolean-attributes.ts:
--------------------------------------------------------------------------------
1 | // List of HTML boolean attributes
2 | // https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML
3 | // These enable a certain functionality by their mere presence in a tag.
4 | // E.G.: is still disabled, which is unintuitive.
5 | // should really set or unset the disabled state depending on the stream's last emitted value!
6 | // If you don't like this behaviour, we have a "rml" prefixed set of such attributes, that actually behave like booleans
7 |
8 | // TODO: review, see if we can convert to a type... don't want all these in the bundles
9 | export const BOOLEAN_ATTRIBUTES = new Set([
10 | 'async',
11 | 'autofocus',
12 | 'autoplay',
13 | 'checked',
14 | 'controls',
15 | 'default',
16 | 'defer',
17 | 'disabled',
18 | 'formnovalidate',
19 | 'hidden',
20 | 'ismap',
21 | 'loop',
22 | 'multiple',
23 | 'muted',
24 | 'nomodule',
25 | 'novalidate',
26 | 'open',
27 | 'readonly',
28 | 'required',
29 | 'reversed',
30 | 'selected'
31 | ] as const);
32 |
33 | export type BooleanAttribute = (typeof BOOLEAN_ATTRIBUTES)['values'] extends () => Iterator ? T : never;
34 |
35 |
--------------------------------------------------------------------------------
/src/definitions/enumerated-attributes.ts:
--------------------------------------------------------------------------------
1 | // List of HTML enumerated attributes
2 | // https://developer.mozilla.org/en-US/docs/Glossary/Enumerated#aria_enumerated_attributes
3 | // TODO: convert to a type... don't need all these in the bundles
4 | export const ENUMERATED_ATTRIBUTES = new Set([
5 | 'autocomplete',
6 | 'capture',
7 | 'charset',
8 | 'contenteditable',
9 | 'crossorigin',
10 | 'decoding',
11 | 'dir',
12 | 'draggable',
13 | 'enctype',
14 | 'formmethod',
15 | 'formenctype',
16 | 'formtarget',
17 | 'inputmode',
18 | 'loading',
19 | 'method',
20 | 'preload',
21 | 'referrerpolicy',
22 | 'rel',
23 | 'scope',
24 | 'shape',
25 | 'spellcheck',
26 | 'translate',
27 | 'type',
28 | 'wrap',
29 | ]);
30 |
31 |
--------------------------------------------------------------------------------
/src/definitions/non-bubbling-events.ts:
--------------------------------------------------------------------------------
1 | import type {RMLEventName} from '../types/dom';
2 |
3 | // TODO: if we keep using this, maybe convert it to a type
4 | export const NON_BUBBLING_DOM_EVENTS: Set = new Set([
5 | 'abort',
6 | 'canplay',
7 | 'canplaythrough',
8 | 'durationchange',
9 | 'emptied',
10 | 'ended',
11 | 'error',
12 | 'load',
13 | 'loadeddata',
14 | 'loadedmetadata',
15 | 'pause',
16 | 'play',
17 | 'playing',
18 | 'ratechange',
19 | 'seeked',
20 | 'seeking',
21 | 'stalled',
22 | 'suspend',
23 | 'timeupdate',
24 | 'volumechange',
25 | 'waiting',
26 | 'rml:mount',
27 | ]);
28 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Rimmel_Mount } from './lifecycle/data-binding';
2 | // import { rml } from './parser/parser';
3 |
4 | export const init = (root = document.documentElement) => {
5 | const mo = new MutationObserver(Rimmel_Mount);
6 | mo.observe(root, { attributes: false, childList: true, subtree: true });
7 | };
8 |
9 | // import { Rimmel_Bind_Subtree } from './lifecycle/data-binding';
10 | // export const activate = Rimmel_Bind_Subtree;
11 |
12 | /*
13 | export const setRoot = (e: Element): RML => {
14 | const root = e;
15 | init(root);
16 | return rml;
17 | }
18 | */
19 |
20 | init();
21 |
22 | // Types
23 | export * from './types/attribute';
24 | export * from './types/coords';
25 | export * from './types/dom';
26 | export * from './types/event-listener';
27 | export * from './types/futures';
28 | export * from './types/internal';
29 | export * from './types/rml';
30 | export * from './types/sink';
31 | export * from './types/source';
32 | export * from './types/style';
33 | export * from './definitions/boolean-attributes';
34 | export * from './utils/input-pipe';
35 | export * from './sources/object-source';
36 |
37 | // Event Mapping Functions
38 | export { feed, feedIn, inputPipe, pipeIn } from './utils/input-pipe';
39 | export { curry } from './utils/curry';
40 |
41 | // Event Source Modifiers
42 | export { Active } from './sources/modifiers/active';
43 | export { Passive } from './sources/modifiers/passive';
44 |
45 | // Event Sources
46 | export { All, qsa } from './sources/all-source';
47 | export { AutoForm } from './sources/autoform-source';
48 | export { CheckedState } from './sources/checked-source';
49 | export { Cut, cut } from './sources/cut-source';
50 | export { Dataset, DatasetObject, datasetObject } from './sources/dataset-source';
51 | export { Numberset } from './sources/numberset-source';
52 | export { EventData, eventData } from './sources/event-data';
53 | export { EventTarget } from './sources/event-target';
54 | export { Form, form, AsFormData, asFormData } from './sources/form-data-source';
55 | export { Key, key } from './sources/keyboard-source';
56 | export { Update } from './sources/object-source';
57 | export { ClientXY } from './sources/client-xy-source';
58 | export { OffsetXY } from './sources/offset-xy-source';
59 | export { LastTouchXY } from './sources/last-touch-xy-source';
60 | export { Swap } from './sources/swap-source';
61 | export { AsLatestFrom } from './sources/as-latest-from';
62 | export { Value, ValueAsDate, ValueAsNumber, value, valueAsString, valueAsDate, valueAsNumber } from './sources/value-source';
63 |
64 | // Data Sinks
65 | export { AnyContentSink } from "./sinks/content-sink";
66 | export { AttributeObjectSink } from "./sinks/attribute-sink";
67 | // Data Sinks
68 | export { AppendHTML } from './sinks/append-html-sink';
69 | export { Blur } from './sinks/blur-sink';
70 | export { Checked } from './sinks/checked-sink';
71 | export { ClassName, ToggleClass } from './sinks/class-sink';
72 | export { Closed } from './sinks/closed-sink';
73 | export { Disabled } from './sinks/disabled-sink';
74 | export { Catch } from './sinks/error-sink';
75 | export { Focus } from './sinks/focus-sink';
76 | export { Hidden } from './sinks/hidden-sink';
77 | export { InnerHTML } from './sinks/inner-html-sink';
78 | export { InnerText } from './sinks/inner-text-sink';
79 | export { Mixin } from './sinks/mixin-sink';
80 | export { JSONDump } from './sinks/json-dump-sink';
81 | export { PrependHTML } from './sinks/prepend-html-sink';
82 | //export { Readonly } from './sinks/readonly-sink';
83 | export { Removed } from './sinks/removed-sink';
84 | export { Sanitize } from './sinks/sanitize-html-sink';
85 | export { Suspend, Suspender } from './sinks/suspense-sink';
86 | export { TextContent } from './sinks/text-content-sink';
87 |
88 | // Plumbing
89 | export { asap } from './lib/drain';
90 |
91 | // Experimental Web Component support
92 | export { RegisterElement } from './custom-element';
93 |
94 | // Utilities (Will take them out to the framework)
95 | export type { Component } from './types/constructs';
96 | export type { RimmelComponent, RMLTemplateExpressions } from './types/internal';
97 |
98 | export { source, sink } from './utils/input-pipe';
99 |
100 | // Other Low-Level Utilities
101 | export { Rimmel_Bind_Subtree, Rimmel_Mount } from './lifecycle/data-binding';
102 | export { RESOLVE_SELECTOR, RML_DEBUG, SINK_TAG } from './constants';
103 | export { set_USE_DOM_OBSERVABLES } from './constants';
104 |
105 | // Main entries
106 | export { rml } from './parser/parser';
107 | export { rml as html } from './parser/parser'; // Shall we?
108 |
109 | // Experimental stuff
110 | // export { rml } from 'rml/scandal';
111 | export { Observature } from './lib/observature';
112 |
113 |
--------------------------------------------------------------------------------
/src/internal-state.ts:
--------------------------------------------------------------------------------
1 | import type { BindingConfiguration, SourceBindingConfiguration } from "./types/internal";
2 | import type { RMLEventName } from "./types/dom";
3 | import type { Subscription } from "./types/futures";
4 |
5 | import { REF_TAG } from './constants';
6 |
7 | export const waitingElementHanlders =