rytm-webflow 2.2.7 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(git fetch:*)",
5
+ "mcp__metrum__get_task",
6
+ "Bash(git checkout:*)",
7
+ "mcp__metrum__set_subtask_status",
8
+ "Bash(npm run:*)"
9
+ ]
10
+ },
11
+ "enableAllProjectMcpServers": true,
12
+ "enabledMcpjsonServers": [
13
+ "metrum"
14
+ ]
15
+ }
@@ -0,0 +1,44 @@
1
+ # WWT3 — Remove Bootstrap dependency for default use
2
+
3
+ ## Summary
4
+ Bootstrap 5 helpers were hardcoded into the main `rytm-webflow` bundle, causing bundler errors in non-Bootstrap projects (e.g. Tailwind CSS). The helpers have been moved to an opt-in sub-path export (`'rytm-webflow/bootstrap'`) and the long-standing export name typo (`bootsrap5` → `bootstrap5`) has been corrected. The main import is now fully Bootstrap-free.
5
+
6
+ ## Completed
7
+ - Remove `bootsrap5` import and export from `scripts/index.js`
8
+ - Create `scripts/bootstrap/index.js` as a secondary entry point exporting `bootstrap5`
9
+ - Create `scripts/bootstrap/index.d.ts` with types for the sub-path export
10
+ - Remove `bootsrap5` from the default export shape in `scripts/index.d.ts`
11
+ - Add `exports` field to `package.json` mapping `'.'` and `'./bootstrap'` sub-paths (with `import` + `types` conditions)
12
+ - Rename `bootsrap5` → `bootstrap5` across all source files, types, `CLAUDE.md`, and `README.md`
13
+ - Remove the "Note: the export name has a typo" line from `CLAUDE.md`
14
+ - Update `README.md` Bootstrap integration example to use sub-path import
15
+ - Bump version `2.2.7` → `2.3.0` in `package.json`
16
+ - Create `docs/changelog/2.3.0.md` documenting both breaking changes with upgrade table
17
+
18
+ ## Remaining work
19
+ None — task complete.
20
+
21
+ ## Key decisions
22
+ - **Option B chosen** (sub-path export) over dynamic import or `window.bootstrap` fallback — cleanest separation, no async complexity, standard pattern for optional peer dependencies
23
+ - **Both breaking changes in one version bump** — the rename and the sub-path move are coupled; doing them together avoids an intermediate version that is still broken for Tailwind projects
24
+ - `bootstrap` is intentionally NOT added to `peerDependencies` in `package.json` — it remains the host project's responsibility, consistent with prior behaviour
25
+ - The old typo name `bootsrap5` is preserved in the changelog for upgrade searchability, but removed from all live code
26
+
27
+ ## Gotchas & notes
28
+ - `scripts/bootstrap/v5/Bootstrap5.js` already used the correctly-spelled internal variable `bootstrap5` — only the re-export names needed fixing
29
+ - The `exports` field in `package.json` takes precedence over `main`/`types` in modern bundlers; both are kept for backwards compatibility with older tooling
30
+ - Any legacy project using `RytmWebflow.bootsrap5` will get a runtime error after upgrading — direct them to `docs/changelog/2.3.0.md`
31
+
32
+ ## Key files
33
+
34
+ | File | Description |
35
+ |---|---|
36
+ | `scripts/index.js` | Main entry — Bootstrap import removed |
37
+ | `scripts/bootstrap/index.js` | New opt-in sub-path entry point |
38
+ | `scripts/bootstrap/index.d.ts` | Types for sub-path export |
39
+ | `scripts/index.d.ts` | `bootsrap5` removed from default export shape |
40
+ | `scripts/bootstrap/v5/Bootstrap5.js` | Unchanged — internal variable was already correct |
41
+ | `package.json` | Version bump to 2.3.0; `exports` field added |
42
+ | `docs/changelog/2.3.0.md` | Upgrade guide for legacy projects |
43
+ | `CLAUDE.md` | Bootstrap section updated to sub-path usage |
44
+ | `README.md` | Bootstrap integration example updated |
package/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "metrum": {
4
+ "command": "node",
5
+ "args": ["/Users/rk/Sites/metrum.rytm.org/mcp/server.js"]
6
+ }
7
+ }
8
+ }
package/CLAUDE.md CHANGED
@@ -19,7 +19,7 @@ import RytmWebflow from 'rytm-webflow'
19
19
  | `View` | Class | Base view — animation hooks |
20
20
  | `WebflowView` | Class | View with declarative animations via data attributes |
21
21
  | `WebflowListView` | Class | Staggered list animations with scroll triggers |
22
- | `bootsrap5` | Object | Bootstrap 5 modal/offcanvas close helpers |
22
+ | `bootstrap5` | Object | Bootstrap 5 modal/offcanvas close helpers (import from `'rytm-webflow/bootstrap'`) |
23
23
  | `scrollController` | Singleton | ScrollMagic controller manager |
24
24
  | `showUp` | Singleton | Standalone scroll-triggered animations (no ASwap needed) |
25
25
  | `getWebflowProps(str)` | Function | Parse webflow show/hide syntax |
@@ -331,12 +331,12 @@ const aswapEvents = {
331
331
  ## Bootstrap 5 helpers
332
332
 
333
333
  ```javascript
334
- RytmWebflow.bootsrap5.hideFlyovers() // Close all modals + offcanvases
335
- RytmWebflow.bootsrap5.hideModals() // Close modals only
336
- RytmWebflow.bootsrap5.hideOffcanvases() // Close offcanvases only
337
- ```
334
+ import bootstrap5 from 'rytm-webflow/bootstrap'
338
335
 
339
- Note: the export name has a typo (`bootsrap5`, not `bootstrap5`).
336
+ bootstrap5.hideFlyovers() // Close all modals + offcanvases
337
+ bootstrap5.hideModals() // Close modals only
338
+ bootstrap5.hideOffcanvases() // Close offcanvases only
339
+ ```
340
340
 
341
341
  ---
342
342
 
@@ -356,7 +356,7 @@ RytmWebflow.scrollController.destroy() // Clean up
356
356
  2. **Layout shifts from images** — use `ControllerImgLoad` for image-heavy pages
357
357
  3. **Nested view element leaks** — filter with `elementBelongsToView()` when querying elements
358
358
  4. **ScrollMagic memory leaks** — WebflowView handles cleanup automatically; manual scenes need explicit `destroy()`
359
- 5. **Bootstrap modals persist on navigation** — call `bootsrap5.hideFlyovers()` in `onViewHide`
359
+ 5. **Bootstrap modals persist on navigation** — call `bootstrap5.hideFlyovers()` in `onViewHide`
360
360
  6. **Event listener accumulation** — always clean up in `removeEventListeners()`
361
361
  7. **Stagger not working** — requires `data-webset="selector:..."` attribute
362
362
  8. **Easing not applied** — use GSAP v3 syntax: `e:power3.out` (not `e:Power3.easeOut`)
package/README.md CHANGED
@@ -210,14 +210,15 @@ RytmWebflow.aswap.init(params, {
210
210
 
211
211
  ### Bootstrap integration ###
212
212
  #### Bootstrap v5 ####
213
- A common situation is to hide all Bootstrap offcanvases or modals when an ASwap page transition is in made. To do so just call the `RytmWebflow.bootsrap5.hideFlyovers()` method. You can also hide offcanvases or modals separatly by calling the `RytmWebflow.bootsrap5.hideOffcanvases()` or `RytmWebflow.bootsrap5.hideModals()` methods.
213
+ A common situation is to hide all Bootstrap offcanvases or modals when an ASwap page transition is in made. To do so just call the `bootstrap5.hideFlyovers()` method. You can also hide offcanvases or modals separatly by calling the `bootstrap5.hideOffcanvases()` or `bootstrap5.hideModals()` methods.
214
214
  A basic usage example:
215
215
  ```js
216
216
  import RytmWebflow from 'rytm-webflow'
217
+ import bootstrap5 from 'rytm-webflow/bootstrap'
217
218
 
218
219
  RytmWebflow.aswap.init(params, {
219
220
  onViewHide: () => {
220
- RytmWebflow.bootsrap5.hideFlyovers()
221
+ bootstrap5.hideFlyovers()
221
222
  },
222
223
  })
223
224
  ```
@@ -0,0 +1,70 @@
1
+ # 2.3.0 — Bootstrap 5 helpers made optional
2
+
3
+ **Released:** 2026-03-31
4
+
5
+ ## Summary
6
+
7
+ The Bootstrap 5 helper has been moved out of the main bundle into a dedicated sub-path export, and its long-standing export name typo has been corrected. Projects that do not use Bootstrap (e.g. Tailwind CSS projects) no longer have a transitive dependency on the `bootstrap` package.
8
+
9
+ ## Breaking changes
10
+
11
+ ### 1. Renamed: `bootsrap5` → `bootstrap5`
12
+
13
+ The export name had a typo (missing `t`). It is now correctly spelled `bootstrap5`.
14
+
15
+ ### 2. Moved to a sub-path import
16
+
17
+ `bootstrap5` is no longer part of the default `'rytm-webflow'` export. It must be imported from `'rytm-webflow/bootstrap'`.
18
+
19
+ **Before (≤ 2.2.x):**
20
+ ```js
21
+ import RytmWebflow from 'rytm-webflow'
22
+
23
+ RytmWebflow.bootsrap5.hideFlyovers()
24
+ ```
25
+
26
+ **After (≥ 2.3.0):**
27
+ ```js
28
+ import bootstrap5 from 'rytm-webflow/bootstrap'
29
+
30
+ bootstrap5.hideFlyovers()
31
+ ```
32
+
33
+ ## Upgrade guide for legacy projects
34
+
35
+ 1. **Search your codebase** for the old typo name:
36
+ ```
37
+ bootsrap5
38
+ ```
39
+
40
+ 2. **Add the sub-path import** at the top of any file that uses the Bootstrap helpers:
41
+ ```js
42
+ import bootstrap5 from 'rytm-webflow/bootstrap'
43
+ ```
44
+
45
+ 3. **Replace** all `RytmWebflow.bootsrap5.xxx()` calls:
46
+ | Before | After |
47
+ |---|---|
48
+ | `RytmWebflow.bootsrap5.hideFlyovers()` | `bootstrap5.hideFlyovers()` |
49
+ | `RytmWebflow.bootsrap5.hideModals()` | `bootstrap5.hideModals()` |
50
+ | `RytmWebflow.bootsrap5.hideOffcanvases()` | `bootstrap5.hideOffcanvases()` |
51
+
52
+ 4. **Verify** `bootstrap` is listed in your project's own `dependencies` or is loaded globally — `rytm-webflow` no longer pulls it in transitively.
53
+
54
+ ## Available helpers (unchanged)
55
+
56
+ ```js
57
+ import bootstrap5 from 'rytm-webflow/bootstrap'
58
+
59
+ bootstrap5.hideFlyovers() // Close all modals + offcanvases
60
+ bootstrap5.hideModals() // Close modals only
61
+ bootstrap5.hideOffcanvases() // Close offcanvases only
62
+ ```
63
+
64
+ ## Non-Bootstrap projects
65
+
66
+ No action required. The main import is unchanged and carries no Bootstrap dependency:
67
+
68
+ ```js
69
+ import RytmWebflow from 'rytm-webflow'
70
+ ```
package/package.json CHANGED
@@ -1,9 +1,19 @@
1
1
  {
2
2
  "name": "rytm-webflow",
3
- "version": "2.2.7",
3
+ "version": "2.3.1",
4
4
  "description": "rytm webflow pack - ASwap, ShowUp",
5
5
  "main": "scripts/index.js",
6
6
  "types": "scripts/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./scripts/index.js",
10
+ "types": "./scripts/index.d.ts"
11
+ },
12
+ "./bootstrap": {
13
+ "import": "./scripts/bootstrap/index.js",
14
+ "types": "./scripts/bootstrap/index.d.ts"
15
+ }
16
+ },
7
17
  "scripts": {
8
18
  "update": "git pull",
9
19
  "push": "git add . && git commit -a -m \"$m\" && git push"
@@ -1,6 +1,10 @@
1
+ import ScrollMagic from 'scrollmagic';
1
2
  import gsap from 'gsap';
2
3
  import View from './View';
4
+ import scrollController from './../showup/ScrollController';
3
5
  import { getWebflowAnimationProps, parseProps } from './../lib/dataTweenParser';
6
+ import { getScrollMagicSceneProps } from './../lib/dataScrollMagicParser';
7
+ import { elementIsVisibleInViewport } from '../lib/helpers';
4
8
  import { CLASS_NAME_WEBSCROLL_FIRED } from './WebflowView';
5
9
 
6
10
  // data-webscroll-... (scroll magic)
@@ -14,6 +18,7 @@ class WebflowListView extends View {
14
18
 
15
19
  constructor(id) {
16
20
  super(id);
21
+ this.scenes = [];
17
22
  }
18
23
  /**
19
24
  * animate in (show)
@@ -25,8 +30,15 @@ class WebflowListView extends View {
25
30
  console.warn("Unknown selector for WebflowListView", this);
26
31
  return;
27
32
  }
28
- const list = [...container.querySelectorAll(this.webset.selector)].filter(this.elementBelongsToView);
29
- list.forEach(this.listElementShow.bind(this));
33
+ const items = [...container.querySelectorAll(this.webset.selector)].filter(this.elementBelongsToView);
34
+ // 1. Set initial state for ALL scroll elements (regardless of viewport)
35
+ this.hideAllScrollElements(items);
36
+ // 2. Stagger-animate in-viewport items (existing behavior)
37
+ items.forEach(this.listElementShow.bind(this));
38
+ // 3. Build ScrollMagic scenes for off-screen items
39
+ this.buildScrollScenesForOffscreenItems(items);
40
+ // 4. Listen for resize to refresh ScrollMagic
41
+ this.addEventListeners();
30
42
  }
31
43
  /**
32
44
  * animate out (hide)
@@ -40,6 +52,16 @@ class WebflowListView extends View {
40
52
  }
41
53
  const list = [...container.querySelectorAll(this.webset.selector)].filter(this.elementBelongsToView);
42
54
  list.forEach(this.listElementHide.bind(this));
55
+ // Also hide off-screen scroll elements that have ScrollMagic scenes
56
+ this.hideOffscreenScrollElements(container);
57
+ }
58
+ /**
59
+ * hidden (cleanup after hide animation)
60
+ **/
61
+ hidden(container) {
62
+ super.hidden(container);
63
+ this.removeEventListeners();
64
+ this.destroyScenes();
43
65
  }
44
66
  /**
45
67
  * ############
@@ -106,6 +128,122 @@ class WebflowListView extends View {
106
128
  });
107
129
  }
108
130
  }
131
+ /**
132
+ * ##################
133
+ * ### DOM events ###
134
+ * ##################
135
+ */
136
+ handleEvent(e) {
137
+ switch (e.type) {
138
+ case 'resize':
139
+ case 'DOMContentLoaded':
140
+ this.onWindowUpdate();
141
+ break;
142
+ }
143
+ }
144
+ addEventListeners() {
145
+ window.addEventListener('resize', this);
146
+ document.addEventListener('DOMContentLoaded', this);
147
+ }
148
+ removeEventListeners() {
149
+ window.removeEventListener('resize', this);
150
+ document.removeEventListener('DOMContentLoaded', this);
151
+ }
152
+ onWindowUpdate() {
153
+ if (this.scenes.length > 0) {
154
+ scrollController.refresh();
155
+ }
156
+ }
157
+ /**
158
+ * #####################
159
+ * ### SCROLL SCENES ###
160
+ * #####################
161
+ */
162
+ // set initial state for all scroll elements in list items
163
+ hideAllScrollElements(items) {
164
+ items.forEach((el) => {
165
+ const scrollEls = [...el.querySelectorAll('*[data-' + DATA_ATTR_WEBSCROLL_SHOW + ']')]
166
+ .filter(this.elementBelongsToView);
167
+ scrollEls.forEach((scrollEl) => {
168
+ const propsInitial = this.getTweenProps(scrollEl, DATA_ATTR_WEBSCROLL_INIT);
169
+ if (propsInitial) {
170
+ gsap.killTweensOf(scrollEl);
171
+ gsap.set(scrollEl, { ...propsInitial.tween });
172
+ }
173
+ });
174
+ });
175
+ }
176
+ // build ScrollMagic scenes for off-screen list items
177
+ buildScrollScenesForOffscreenItems(items) {
178
+ items.forEach((el) => {
179
+ if (this.isElementInViewport(el)) return;
180
+ const scrollEls = [...el.querySelectorAll('*[data-' + DATA_ATTR_WEBSCROLL_SHOW + ']')]
181
+ .filter(this.elementBelongsToView);
182
+ scrollEls.forEach((scrollEl) => {
183
+ this.buildScrollmagicScene(scrollEl);
184
+ });
185
+ });
186
+ }
187
+ // build a single ScrollMagic scene for a scroll element
188
+ buildScrollmagicScene(el) {
189
+ const propsInitial = this.getTweenProps(el, DATA_ATTR_WEBSCROLL_INIT);
190
+ const propsShow = this.getTweenProps(el, DATA_ATTR_WEBSCROLL_SHOW);
191
+ const setup = parseProps(el.dataset[DATA_ATTR_SETUP] || '');
192
+ if (propsInitial && propsShow) {
193
+ const smsp = getScrollMagicSceneProps(el, setup);
194
+ const scene = new ScrollMagic.Scene(smsp);
195
+ scene.on("start", (e) => {
196
+ if (el.classList.contains(CLASS_NAME_WEBSCROLL_FIRED)) return;
197
+ if (e.scrollDirection === "FORWARD") {
198
+ gsap.killTweensOf(el);
199
+ gsap.set(el, { ...propsInitial.tween });
200
+ gsap.to(el, {
201
+ duration: propsShow.time,
202
+ ...propsShow.tween,
203
+ onComplete: () => {
204
+ el.classList.add(CLASS_NAME_WEBSCROLL_FIRED);
205
+ }
206
+ });
207
+ }
208
+ });
209
+ scene.on("add", () => {
210
+ setTimeout(() => {
211
+ if (elementIsVisibleInViewport(el, true)) {
212
+ gsap.to(el, {
213
+ duration: propsShow.time,
214
+ ...propsShow.tween,
215
+ onComplete: () => {
216
+ el.classList.add(CLASS_NAME_WEBSCROLL_FIRED);
217
+ }
218
+ });
219
+ }
220
+ }, 10);
221
+ });
222
+ scene.addTo(scrollController.get());
223
+ this.scenes.push(scene);
224
+ }
225
+ }
226
+ // hide off-screen scroll elements during view hide
227
+ hideOffscreenScrollElements(container) {
228
+ const scrollEls = [...container.querySelectorAll('*[data-' + DATA_ATTR_WEBSCROLL_HIDE + ']')]
229
+ .filter(this.elementBelongsToView);
230
+ scrollEls.forEach((el) => {
231
+ if (this.isElementInViewport(el.closest(this.webset.selector))) return;
232
+ const propsHide = this.getTweenProps(el, DATA_ATTR_WEBSCROLL_HIDE);
233
+ if (propsHide) {
234
+ gsap.killTweensOf(el);
235
+ gsap.to(el, { duration: propsHide.time, ...propsHide.tween });
236
+ }
237
+ });
238
+ }
239
+ // destroy all ScrollMagic scenes
240
+ destroyScenes() {
241
+ this.scenes.forEach((scene) => {
242
+ scene.destroy();
243
+ scene = null;
244
+ });
245
+ this.scenes = [];
246
+ }
109
247
  /**
110
248
  * ###############
111
249
  * ### HELPERS ###
@@ -0,0 +1,8 @@
1
+ export interface Bootstrap5 {
2
+ hideFlyovers(): void;
3
+ hideModals(): void;
4
+ hideOffcanvases(): void;
5
+ }
6
+
7
+ export declare const bootstrap5: Bootstrap5;
8
+ export default bootstrap5;
@@ -0,0 +1,4 @@
1
+ import bootstrap5 from './v5/Bootstrap5'
2
+
3
+ export { bootstrap5 }
4
+ export default bootstrap5
@@ -201,8 +201,6 @@ declare const RytmWebflow: {
201
201
  View: typeof View;
202
202
  WebflowView: typeof WebflowView;
203
203
  WebflowListView: typeof WebflowListView;
204
- /** Note: "bootsrap5" is the original (typo) name in the library */
205
- bootsrap5: Bootstrap5;
206
204
  scrollController: ScrollController;
207
205
  showUp: ShowUp;
208
206
  getWebflowProps: typeof getWebflowProps;
package/scripts/index.js CHANGED
@@ -4,8 +4,6 @@ import ControllerImgLoad from './aswap/ControllerImgLoad'
4
4
  import View from './aswap/View'
5
5
  import WebflowView from './aswap/WebflowView'
6
6
  import WebflowListView from './aswap/WebflowListView'
7
- // Bootstrap v5 helper
8
- import bootsrap5 from './bootstrap/v5/Bootstrap5'
9
7
  // showup
10
8
  import scrollController from './showup/ScrollController'
11
9
  import showUp from './showup/ShowUp'
@@ -20,8 +18,6 @@ export default {
20
18
  View,
21
19
  WebflowView,
22
20
  WebflowListView,
23
- // boottstrap
24
- bootsrap5,
25
21
  // showup
26
22
  scrollController,
27
23
  showUp,