motimeline 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,7 +19,7 @@ Responsive two-column timeline layout library — plain JavaScript, zero depende
19
19
  - **Badges & arrows** — numbered badges on the center line, directional arrows
20
20
  - **Optional theme** — built-in card theme with image banners and overlapping avatars
21
21
  - **CSS custom properties** — override colors and sizes with one line of CSS
22
- - **Dynamic items** — append new `<li>` elements at any time via `initNewItems()`
22
+ - **Dynamic items** — append, insert, or inject `<li>` elements at any time via `initNewItems()`, `addItems()`, or `insertItem()`
23
23
  - **Bootstrap compatible** — wrap the `<ul>` in a Bootstrap `.container`, no config needed
24
24
  - **ESM · CJS · UMD** — works with any bundler or as a plain `<script>` tag
25
25
 
@@ -109,6 +109,7 @@ import 'motimeline/dist/moTimeline.css';
109
109
  | `cardMarginInverted` | string | `'0.5rem 0.5rem 0.5rem 1.25rem'` | Margin of right-column (inverted) themed cards. The larger left value creates space toward the center line. Sets `--mo-card-margin-inverted` on the container. |
110
110
  | `cardMarginFullWidth` | string | `'0.5rem'` | Margin of full-width themed cards. Sets `--mo-card-margin-fullwidth` on the container. |
111
111
  | `randomFullWidth` | number \| boolean | `0` | `0`/`false` = off. A number `0–1` sets the probability that each item is randomly promoted to full-width during init. `true` = 33% chance. Items can also be set manually by adding the `mo-fullwidth` class to the `<li>`. |
112
+ | `animate` | string \| boolean | `false` | Animate items as they scroll into view using `IntersectionObserver`. `'fade'` — items fade in. `'slide'` — left-column items slide in from the left, right-column items from the right. `true` = `'fade'`. Disable for individual items by adding `mo-visible` manually. Control speed via `--mo-animate-duration`. |
112
113
 
113
114
  ---
114
115
 
@@ -148,12 +149,28 @@ import 'motimeline/dist/moTimeline.css';
148
149
  ```js
149
150
  const tl = new MoTimeline(elementOrSelector, options);
150
151
 
151
- tl.refresh(); // re-layout all items (called automatically on resize)
152
- tl.initNewItems(); // pick up manually appended <li> elements
153
- tl.addItems(items); // create and append <li> from an array of item objects (or JSON string)
154
- tl.destroy(); // remove listeners and reset DOM classes
152
+ tl.refresh(); // re-layout all items (called automatically on resize)
153
+ tl.initNewItems(); // pick up manually appended <li> elements
154
+ tl.addItems(items); // create and append <li> from an array of item objects (or JSON string)
155
+ tl.insertItem(item, index); // insert a single item at a specific index (or random if omitted)
156
+ tl.destroy(); // remove listeners and reset DOM classes
155
157
  ```
156
158
 
159
+ ### insertItem
160
+
161
+ ```js
162
+ // Insert at a specific 0-based index:
163
+ tl.insertItem({ title: 'Breaking news', meta: 'Now', text: '...' }, 2);
164
+
165
+ // Insert at a random position (omit the index):
166
+ tl.insertItem({ title: 'Surprise!', meta: 'Now', text: '...' });
167
+
168
+ // Insert as a full-width item:
169
+ tl.insertItem({ title: 'Featured', meta: 'Now', text: '...', fullWidth: true }, 0);
170
+ ```
171
+
172
+ Badge numbers are automatically re-sequenced after insertion. Returns the inserted `<li>` element.
173
+
157
174
  ### addItems — item schema
158
175
 
159
176
  ```js
@@ -335,6 +352,7 @@ async function fetchPage(page) {
335
352
  --mo-card-margin: 0.5rem 1.25rem 0.5rem 0.5rem;
336
353
  --mo-card-margin-inverted: 0.5rem 0.5rem 0.5rem 1.25rem;
337
354
  --mo-card-margin-fullwidth: 0.5rem;
355
+ --mo-animate-duration: 0.5s;
338
356
  }
339
357
  ```
340
358
 
@@ -364,6 +382,12 @@ No framework option needed. Wrap the `<ul>` inside a Bootstrap `.container`:
364
382
 
365
383
  ## Changelog
366
384
 
385
+ ### v2.9.0
386
+ - New option `animate` — scroll-triggered animations via `IntersectionObserver`. `'fade'` fades items in as they enter the viewport; `'slide'` slides left-column items from the left and right-column items from the right. `true` defaults to `'fade'`. Speed controlled via `--mo-animate-duration` (default `0.5s`). Works for initial load, `addItems()`, and `insertItem()`.
387
+
388
+ ### v2.8.1
389
+ - New method `insertItem(item, index)` — inserts a single item at a specific 0-based index, or at a random position when index is omitted. Badge numbers are re-sequenced automatically.
390
+
367
391
  ### v2.8.0
368
392
  - New: full-width items — add `mo-fullwidth` class to any `<li>` to make it span both columns (two-column mode only). Badge and arrow are hidden automatically; card margin collapses to equal sides via `--mo-card-margin-fullwidth`
369
393
  - New option `randomFullWidth` (number 0–1 or boolean) — randomly promotes items to full-width during init (`true` = 33% probability)
@@ -1,6 +1,6 @@
1
- "use strict";var E=Object.defineProperty;var _=(n,e,t)=>e in n?E(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var p=(n,e,t)=>_(n,typeof e!="symbol"?e+"":e,t);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});/*!
2
- * moTimeline v2.8.0
1
+ "use strict";var _=Object.defineProperty;var b=(a,e,t)=>e in a?_(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var p=(a,e,t)=>b(a,typeof e!="symbol"?e+"":e,t);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});/*!
2
+ * moTimeline v2.9.0
3
3
  * Responsive two-column timeline layout library
4
4
  * https://github.com/MattOpen/moTimeline
5
5
  * MIT License
6
- */const l=new WeakMap,v={columnCount:{xs:1,sm:2,md:2,lg:2},showBadge:!1,showArrow:!1,theme:!1,showCounterStyle:"counter",cardBorderRadius:"8px",avatarSize:"50px",cardMargin:"0.5rem 1.25rem 0.5rem 0.5rem",cardMarginInverted:"0.5rem 0.5rem 0.5rem 1.25rem",cardMarginFullWidth:"0.5rem",randomFullWidth:0},b="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='11' fill='%234f46e5'/><circle cx='12' cy='12' r='4.5' fill='white'/></svg>";function w(){const n=window.innerWidth;return n<600?"xs":n<992?"sm":n<1200?"md":"lg"}function C(n,e=100){let t;return(...s)=>{clearTimeout(t),t=setTimeout(()=>n(...s),e)}}function m(n){return n?{o:n.offsetTop,h:n.offsetHeight,gppu:n.offsetTop+n.offsetHeight}:{o:0,h:0,gppu:0}}function y(n,e){const t=[];let s=n.previousElementSibling;for(;s;)(!e||s.matches(e))&&t.push(s),s=s.previousElementSibling;return t}const c=class c{constructor(e,t={}){if(typeof e=="string"&&(e=document.querySelector(e)),!e)throw new Error("moTimeline: element not found");this.element=e,this.settings=Object.assign({},v,t),this.settings.columnCount=Object.assign({},v.columnCount,t.columnCount),this._resizeHandler=C(()=>this.refresh(),100),this._initialized=!1,this.init()}init(){const e=this.element;if(l.has(e)){this.refresh();return}const t=Object.assign({},this.settings,{lastItemIdx:0});l.set(e,t),c.instances.add(this),e.classList.add("mo-timeline"),t.theme&&e.classList.add("mo-theme"),e.style.setProperty("--mo-card-border-radius",t.cardBorderRadius),e.style.setProperty("--mo-avatar-size",t.avatarSize),e.style.setProperty("--mo-card-margin",t.cardMargin),e.style.setProperty("--mo-card-margin-inverted",t.cardMarginInverted),e.style.setProperty("--mo-card-margin-fullwidth",t.cardMarginFullWidth),this._initialized=!0,window.addEventListener("resize",this._resizeHandler),Array.from(e.children).length>0&&this._initItems()}refresh(){c.instances.forEach(e=>{const t=e.element,s=l.get(t);s&&(s.col=s.columnCount[w()],e._setDivider(),Array.from(t.children).forEach(o=>{e._setPostPosition(o)}))})}initNewItems(){this._initItems()}addItems(e){typeof e=="string"&&(e=JSON.parse(e)),e.forEach(t=>this.element.appendChild(this._createItemElement(t))),this._initItems()}destroy(){window.removeEventListener("resize",this._resizeHandler),l.delete(this.element),c.instances.delete(this),this.element.style.removeProperty("--mo-card-border-radius"),this.element.style.removeProperty("--mo-avatar-size"),this.element.style.removeProperty("--mo-card-margin"),this.element.style.removeProperty("--mo-card-margin-inverted"),this.element.style.removeProperty("--mo-card-margin-fullwidth"),this.element.classList.remove("mo-timeline","mo-theme","mo-twocol"),Array.from(this.element.children).forEach(e=>{e.classList.remove("mo-item","js-mo-item","mo-inverted","js-mo-inverted","mo-offset","mo-fullwidth","js-mo-fullwidth"),e.querySelectorAll(".js-mo-badge, .js-mo-arrow").forEach(t=>t.remove())})}_getData(){return l.get(this.element)}_setDivider(){const e=this._getData();e&&(e.col=e.columnCount[w()],this.element.classList.toggle("mo-twocol",e.col>1))}_initItems(){const e=this.element,t=this._getData();if(!t)return;const s=t.lastItemIdx,o=Array.from(e.children),r=o.slice(s);if(r.length!==0){if(r.forEach((i,a)=>{i.id||(i.id="moT"+crypto.randomUUID()+"_"+(a+s)),i.classList.add("mo-item","js-mo-item")}),this._setDivider(),t.randomFullWidth){const i=t.randomFullWidth===!0?.33:t.randomFullWidth;r.forEach(a=>{!a.classList.contains("mo-fullwidth")&&Math.random()<i&&a.classList.add("mo-fullwidth","js-mo-fullwidth")})}r.forEach((i,a)=>{t.showBadge&&this._createBadge(i,a+s+1),t.showArrow&&this._createArrow(i)}),t.lastItemIdx=o.length,l.set(e,t),this.refresh(),r.forEach(i=>{i.querySelectorAll("img").forEach(a=>{a.complete||a.addEventListener("load",this._resizeHandler,{once:!0})})})}}_setPostPosition(e){if(e.classList.contains("mo-fullwidth")){e.classList.remove("mo-inverted","js-mo-inverted","mo-offset");return}const t=this._getLeftOrRight(e);t&&(e.classList.toggle("mo-inverted",t.lr>0),e.classList.toggle("js-mo-inverted",t.lr>0),e.classList.toggle("mo-offset",t.badge_offset>0))}_getLeftOrRight(e){if(!e)return null;const t=this._getData();if(!t)return null;const s=t.col,o=y(e,".js-mo-inverted")[0]||null,r=y(e,".js-mo-item:not(.js-mo-inverted)")[0]||null,i=m(r),a=m(o),u=m(e);let h=0,f=0;if(s>1){i.gppu>u.o+1&&(h=1),a.gppu>i.gppu&&(h=0);const g=e.previousElementSibling;g&&Math.abs(u.o-m(g).o)<40&&(f=1)}return{lr:h,badge_offset:f}}_createBadge(e,t){const s=this._getData(),o=document.createElement("span");if(o.className="mo-badge js-mo-badge",s.showCounterStyle==="none")o.style.opacity="0";else if(s.showCounterStyle==="image"){const r=document.createElement("img");r.className="mo-badge-icon",r.alt="",r.src=e.dataset.moIcon||b,o.appendChild(r)}else o.textContent=t;e.prepend(o)}_createItemElement(e){const t=document.createElement("li");e.icon&&(t.dataset.moIcon=e.icon);const s=document.createElement("div");if(s.className="mo-card",e.banner){const r=document.createElement("div");r.className="mo-card-image";const i=document.createElement("img");if(i.className="mo-banner",i.src=e.banner,i.alt="",r.appendChild(i),e.avatar){const a=document.createElement("img");a.className="mo-avatar",a.src=e.avatar,a.alt="",r.appendChild(a)}s.appendChild(r)}const o=document.createElement("div");if(o.className="mo-card-body",e.title){const r=document.createElement("h3");r.textContent=e.title,o.appendChild(r)}if(e.meta){const r=document.createElement("p");r.className="mo-meta",r.textContent=e.meta,o.appendChild(r)}if(e.text){const r=document.createElement("p");r.textContent=e.text,o.appendChild(r)}return s.appendChild(o),t.appendChild(s),t}_createArrow(e){const t=document.createElement("span");t.className="mo-arrow js-mo-arrow",e.prepend(t)}};p(c,"instances",new Set);let d=c;exports.MoTimeline=d;exports.default=d;
6
+ */const m=new WeakMap,v={columnCount:{xs:1,sm:2,md:2,lg:2},showBadge:!1,showArrow:!1,theme:!1,showCounterStyle:"counter",cardBorderRadius:"8px",avatarSize:"50px",cardMargin:"0.5rem 1.25rem 0.5rem 0.5rem",cardMarginInverted:"0.5rem 0.5rem 0.5rem 1.25rem",cardMarginFullWidth:"0.5rem",randomFullWidth:0,animate:!1},E="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='11' fill='%234f46e5'/><circle cx='12' cy='12' r='4.5' fill='white'/></svg>";function w(){const a=window.innerWidth;return a<600?"xs":a<992?"sm":a<1200?"md":"lg"}function I(a,e=100){let t;return(...r)=>{clearTimeout(t),t=setTimeout(()=>a(...r),e)}}function f(a){return a?{o:a.offsetTop,h:a.offsetHeight,gppu:a.offsetTop+a.offsetHeight}:{o:0,h:0,gppu:0}}function y(a,e){const t=[];let r=a.previousElementSibling;for(;r;)(!e||r.matches(e))&&t.push(r),r=r.previousElementSibling;return t}const c=class c{constructor(e,t={}){if(typeof e=="string"&&(e=document.querySelector(e)),!e)throw new Error("moTimeline: element not found");this.element=e,this.settings=Object.assign({},v,t),this.settings.columnCount=Object.assign({},v.columnCount,t.columnCount),this._resizeHandler=I(()=>this.refresh(),100),this._initialized=!1,this.init()}init(){const e=this.element;if(m.has(e)){this.refresh();return}const t=Object.assign({},this.settings,{lastItemIdx:0});if(m.set(e,t),c.instances.add(this),e.classList.add("mo-timeline"),t.theme&&e.classList.add("mo-theme"),e.style.setProperty("--mo-card-border-radius",t.cardBorderRadius),e.style.setProperty("--mo-avatar-size",t.avatarSize),e.style.setProperty("--mo-card-margin",t.cardMargin),e.style.setProperty("--mo-card-margin-inverted",t.cardMarginInverted),e.style.setProperty("--mo-card-margin-fullwidth",t.cardMarginFullWidth),t.animate){const r=t.animate===!0?"fade":t.animate;e.classList.add("mo-animate",`mo-animate-${r}`),this._observer=new IntersectionObserver(o=>{o.forEach(s=>{s.isIntersecting&&(s.target.classList.add("mo-visible"),this._observer.unobserve(s.target))})},{threshold:.1})}this._initialized=!0,window.addEventListener("resize",this._resizeHandler),Array.from(e.children).length>0&&this._initItems()}refresh(){c.instances.forEach(e=>{const t=e.element,r=m.get(t);r&&(r.col=r.columnCount[w()],e._setDivider(),Array.from(t.children).forEach(o=>{e._setPostPosition(o)}))})}initNewItems(){this._initItems()}addItems(e){typeof e=="string"&&(e=JSON.parse(e)),e.forEach(t=>this.element.appendChild(this._createItemElement(t))),this._initItems()}insertItem(e,t){const r=this.element,o=this._getData();if(!o)return;const s=this._createItemElement(e),i=Array.from(r.children).filter(l=>l.classList.contains("js-mo-item")),n=t==null?Math.floor(Math.random()*(i.length+1)):Math.max(0,Math.min(t,i.length));if(n>=i.length?r.appendChild(s):r.insertBefore(s,i[n]),s.id||(s.id="moT"+crypto.randomUUID()+"_"+n),s.classList.add("mo-item","js-mo-item"),e.fullWidth)s.classList.add("mo-fullwidth","js-mo-fullwidth");else if(o.randomFullWidth){const l=o.randomFullWidth===!0?.33:o.randomFullWidth;Math.random()<l&&s.classList.add("mo-fullwidth","js-mo-fullwidth")}return o.showBadge&&this._createBadge(s,n+1),o.showArrow&&this._createArrow(s),o.showBadge&&o.showCounterStyle==="counter"&&Array.from(r.querySelectorAll(".js-mo-item")).forEach((l,d)=>{const h=l.querySelector(".js-mo-badge");h&&(h.textContent=d+1)}),o.lastItemIdx=Array.from(r.children).length,m.set(r,o),this.refresh(),this._observeItems([s]),s.querySelectorAll("img").forEach(l=>{l.complete||l.addEventListener("load",this._resizeHandler,{once:!0})}),s}destroy(){window.removeEventListener("resize",this._resizeHandler),this._observer&&(this._observer.disconnect(),this._observer=null),m.delete(this.element),c.instances.delete(this),this.element.style.removeProperty("--mo-card-border-radius"),this.element.style.removeProperty("--mo-avatar-size"),this.element.style.removeProperty("--mo-card-margin"),this.element.style.removeProperty("--mo-card-margin-inverted"),this.element.style.removeProperty("--mo-card-margin-fullwidth"),this.element.classList.remove("mo-timeline","mo-theme","mo-twocol","mo-animate","mo-animate-fade","mo-animate-slide"),Array.from(this.element.children).forEach(e=>{e.classList.remove("mo-item","js-mo-item","mo-inverted","js-mo-inverted","mo-offset","mo-fullwidth","js-mo-fullwidth","mo-visible"),e.querySelectorAll(".js-mo-badge, .js-mo-arrow").forEach(t=>t.remove())})}_getData(){return m.get(this.element)}_setDivider(){const e=this._getData();e&&(e.col=e.columnCount[w()],this.element.classList.toggle("mo-twocol",e.col>1))}_initItems(){const e=this.element,t=this._getData();if(!t)return;const r=t.lastItemIdx,o=Array.from(e.children),s=o.slice(r);if(s.length!==0){if(s.forEach((i,n)=>{i.id||(i.id="moT"+crypto.randomUUID()+"_"+(n+r)),i.classList.add("mo-item","js-mo-item")}),this._setDivider(),t.randomFullWidth){const i=t.randomFullWidth===!0?.33:t.randomFullWidth;s.forEach(n=>{!n.classList.contains("mo-fullwidth")&&Math.random()<i&&n.classList.add("mo-fullwidth","js-mo-fullwidth")})}s.forEach((i,n)=>{t.showBadge&&this._createBadge(i,n+r+1),t.showArrow&&this._createArrow(i)}),t.lastItemIdx=o.length,m.set(e,t),this.refresh(),this._observeItems(s),s.forEach(i=>{i.querySelectorAll("img").forEach(n=>{n.complete||n.addEventListener("load",this._resizeHandler,{once:!0})})})}}_setPostPosition(e){if(e.classList.contains("mo-fullwidth")){e.classList.remove("mo-inverted","js-mo-inverted","mo-offset");return}const t=this._getLeftOrRight(e);t&&(e.classList.toggle("mo-inverted",t.lr>0),e.classList.toggle("js-mo-inverted",t.lr>0),e.classList.toggle("mo-offset",t.badge_offset>0))}_getLeftOrRight(e){if(!e)return null;const t=this._getData();if(!t)return null;const r=t.col,o=y(e,".js-mo-inverted")[0]||null,s=y(e,".js-mo-item:not(.js-mo-inverted)")[0]||null,i=f(s),n=f(o),l=f(e);let d=0,h=0;if(r>1){i.gppu>l.o+1&&(d=1),n.gppu>i.gppu&&(d=0);const g=e.previousElementSibling;g&&Math.abs(l.o-f(g).o)<40&&(h=1)}return{lr:d,badge_offset:h}}_createBadge(e,t){const r=this._getData(),o=document.createElement("span");if(o.className="mo-badge js-mo-badge",r.showCounterStyle==="none")o.style.opacity="0";else if(r.showCounterStyle==="image"){const s=document.createElement("img");s.className="mo-badge-icon",s.alt="",s.src=e.dataset.moIcon||E,o.appendChild(s)}else o.textContent=t;e.prepend(o)}_createItemElement(e){const t=document.createElement("li");e.icon&&(t.dataset.moIcon=e.icon);const r=document.createElement("div");if(r.className="mo-card",e.banner){const s=document.createElement("div");s.className="mo-card-image";const i=document.createElement("img");if(i.className="mo-banner",i.src=e.banner,i.alt="",s.appendChild(i),e.avatar){const n=document.createElement("img");n.className="mo-avatar",n.src=e.avatar,n.alt="",s.appendChild(n)}r.appendChild(s)}const o=document.createElement("div");if(o.className="mo-card-body",e.title){const s=document.createElement("h3");s.textContent=e.title,o.appendChild(s)}if(e.meta){const s=document.createElement("p");s.className="mo-meta",s.textContent=e.meta,o.appendChild(s)}if(e.text){const s=document.createElement("p");s.textContent=e.text,o.appendChild(s)}return r.appendChild(o),t.appendChild(r),t}_createArrow(e){const t=document.createElement("span");t.className="mo-arrow js-mo-arrow",e.prepend(t)}_observeItems(e){this._observer&&e.forEach(t=>{t.classList.contains("mo-visible")||this._observer.observe(t)})}};p(c,"instances",new Set);let u=c;exports.MoTimeline=u;exports.default=u;
@@ -1,3 +1,3 @@
1
1
  /*!
2
- * moTimeline v2.8.0 — CSS
3
- */:root{--mo-line-color: #dde1e7;--mo-badge-bg: #4f46e5;--mo-badge-color: #fff;--mo-badge-size: 26px;--mo-badge-font-size: 12px;--mo-arrow-color: #dde1e7}.mo-timeline{display:block;list-style:none;margin:0;padding:0;position:relative;width:100%}.mo-timeline:after{content:"";display:table;clear:both}.mo-timeline.mo-twocol:before{background-color:var(--mo-line-color);bottom:0;content:"";left:50%;margin-left:-1.5px;position:absolute;top:0;width:3px;z-index:0}.mo-timeline>.mo-item{box-sizing:border-box;display:block;float:left;position:relative;width:50%}.mo-timeline:not(.mo-twocol)>.mo-item{float:none;width:100%}.mo-timeline>.mo-item.mo-inverted{float:right}.mo-timeline.mo-twocol>.mo-item.mo-fullwidth{clear:both;float:none;width:100%}.mo-theme>.mo-item.mo-fullwidth .mo-card{margin:var(--mo-card-margin-fullwidth, .5rem)}.mo-timeline.mo-twocol>.mo-item.mo-fullwidth .mo-badge,.mo-timeline.mo-twocol>.mo-item.mo-fullwidth .mo-arrow{display:none}.mo-badge{align-items:center;background:var(--mo-badge-bg);border-radius:50%;color:var(--mo-badge-color);display:flex;font-size:var(--mo-badge-font-size);font-weight:700;height:var(--mo-badge-size);justify-content:center;min-width:var(--mo-badge-size);overflow:hidden;position:absolute;top:18px;z-index:2}.mo-badge .mo-badge-icon{border-radius:50%;height:100%;object-fit:cover;width:100%}.mo-timeline.mo-twocol>.mo-item:not(.mo-inverted) .mo-badge{left:auto;right:calc(var(--mo-badge-size) / -2)}.mo-timeline.mo-twocol>.mo-item.mo-inverted .mo-badge{left:calc(var(--mo-badge-size) / -2);right:auto}.mo-timeline.mo-twocol>.mo-item.mo-offset .mo-badge{top:calc(18px + var(--mo-badge-size) + 10px)}.mo-timeline.mo-twocol>.mo-item.mo-offset .mo-arrow{top:calc(26px + var(--mo-badge-size) + 10px)}.mo-timeline:not(.mo-twocol)>.mo-item .mo-badge{display:none}.mo-arrow{border:8px solid transparent;display:block;height:0;position:absolute;top:26px;width:0;z-index:1}.mo-timeline.mo-twocol>.mo-item:not(.mo-inverted) .mo-arrow{border-left:8px solid var(--mo-arrow-color);border-right:none;left:auto;right:0}.mo-timeline.mo-twocol>.mo-item.mo-inverted .mo-arrow{border-right:8px solid var(--mo-arrow-color);border-left:none;left:0;right:auto}.mo-timeline:not(.mo-twocol)>.mo-item .mo-arrow{display:none}.mo-theme{--mo-arrow-color: #fff}.mo-theme>.mo-item .mo-card{background:#fff;border-radius:var(--mo-card-border-radius, 8px);box-shadow:0 2px 14px #0000001a;margin:var(--mo-card-margin, .5rem 1.25rem .5rem .5rem);position:relative}.mo-theme>.mo-item.mo-inverted .mo-card{margin:var(--mo-card-margin-inverted, .5rem .5rem .5rem 1.25rem)}.mo-theme>.mo-item .mo-banner{border-radius:var(--mo-card-border-radius, 8px) var(--mo-card-border-radius, 8px) 0 0;display:block;max-height:240px;object-fit:cover;width:100%}.mo-theme>.mo-item .mo-card-image{overflow:visible;position:relative}.mo-theme>.mo-item .mo-avatar{border:3px solid #fff;border-radius:50%;bottom:calc(var(--mo-avatar-size, 50px) * -.44);box-shadow:0 2px 8px #0000002e;height:var(--mo-avatar-size, 50px);object-fit:cover;position:absolute;right:14px;width:var(--mo-avatar-size, 50px);z-index:1}.mo-theme>.mo-item .mo-card-body{padding:1.75rem 1rem 1rem}.mo-theme>.mo-item .mo-card-body h3{font-size:1rem;font-weight:700;margin:0 0 .3rem}.mo-theme>.mo-item .mo-card-body .mo-meta{color:#9ca3af;font-size:.75rem;margin-bottom:.5rem}.mo-theme>.mo-item .mo-card-body p{color:#6b7280;font-size:.875rem;line-height:1.55;margin:0}.mo-theme.mo-twocol>.mo-item:not(.mo-inverted) .mo-arrow{border-left-color:var(--mo-arrow-color);right:12px}.mo-theme.mo-twocol>.mo-item.mo-inverted .mo-arrow{border-right-color:var(--mo-arrow-color);left:12px}.mo-theme .mo-badge{background:#fff;border:2px solid var(--mo-line-color);box-shadow:0 2px 6px #0000001a;color:#374151}
2
+ * moTimeline v2.9.0 — CSS
3
+ */:root{--mo-line-color: #dde1e7;--mo-badge-bg: #4f46e5;--mo-badge-color: #fff;--mo-badge-size: 26px;--mo-badge-font-size: 12px;--mo-arrow-color: #dde1e7}.mo-timeline{display:block;list-style:none;margin:0;padding:0;position:relative;width:100%}.mo-timeline:after{content:"";display:table;clear:both}.mo-timeline.mo-twocol:before{background-color:var(--mo-line-color);bottom:0;content:"";left:50%;margin-left:-1.5px;position:absolute;top:0;width:3px;z-index:0}.mo-timeline>.mo-item{box-sizing:border-box;display:block;float:left;position:relative;width:50%}.mo-timeline:not(.mo-twocol)>.mo-item{float:none;width:100%}.mo-timeline>.mo-item.mo-inverted{float:right}.mo-timeline.mo-twocol>.mo-item.mo-fullwidth{clear:both;float:none;width:100%}.mo-theme>.mo-item.mo-fullwidth .mo-card{margin:var(--mo-card-margin-fullwidth, .5rem)}.mo-timeline.mo-twocol>.mo-item.mo-fullwidth .mo-badge,.mo-timeline.mo-twocol>.mo-item.mo-fullwidth .mo-arrow{display:none}.mo-badge{align-items:center;background:var(--mo-badge-bg);border-radius:50%;color:var(--mo-badge-color);display:flex;font-size:var(--mo-badge-font-size);font-weight:700;height:var(--mo-badge-size);justify-content:center;min-width:var(--mo-badge-size);overflow:hidden;position:absolute;top:18px;z-index:2}.mo-badge .mo-badge-icon{border-radius:50%;height:100%;object-fit:cover;width:100%}.mo-timeline.mo-twocol>.mo-item:not(.mo-inverted) .mo-badge{left:auto;right:calc(var(--mo-badge-size) / -2)}.mo-timeline.mo-twocol>.mo-item.mo-inverted .mo-badge{left:calc(var(--mo-badge-size) / -2);right:auto}.mo-timeline.mo-twocol>.mo-item.mo-offset .mo-badge{top:calc(18px + var(--mo-badge-size) + 10px)}.mo-timeline.mo-twocol>.mo-item.mo-offset .mo-arrow{top:calc(26px + var(--mo-badge-size) + 10px)}.mo-timeline:not(.mo-twocol)>.mo-item .mo-badge{display:none}.mo-arrow{border:8px solid transparent;display:block;height:0;position:absolute;top:26px;width:0;z-index:1}.mo-timeline.mo-twocol>.mo-item:not(.mo-inverted) .mo-arrow{border-left:8px solid var(--mo-arrow-color);border-right:none;left:auto;right:0}.mo-timeline.mo-twocol>.mo-item.mo-inverted .mo-arrow{border-right:8px solid var(--mo-arrow-color);border-left:none;left:0;right:auto}.mo-timeline:not(.mo-twocol)>.mo-item .mo-arrow{display:none}.mo-theme{--mo-arrow-color: #fff}.mo-theme>.mo-item .mo-card{background:#fff;border-radius:var(--mo-card-border-radius, 8px);box-shadow:0 2px 14px #0000001a;margin:var(--mo-card-margin, .5rem 1.25rem .5rem .5rem);position:relative}.mo-theme>.mo-item.mo-inverted .mo-card{margin:var(--mo-card-margin-inverted, .5rem .5rem .5rem 1.25rem)}.mo-theme>.mo-item .mo-banner{border-radius:var(--mo-card-border-radius, 8px) var(--mo-card-border-radius, 8px) 0 0;display:block;max-height:240px;object-fit:cover;width:100%}.mo-theme>.mo-item .mo-card-image{overflow:visible;position:relative}.mo-theme>.mo-item .mo-avatar{border:3px solid #fff;border-radius:50%;bottom:calc(var(--mo-avatar-size, 50px) * -.44);box-shadow:0 2px 8px #0000002e;height:var(--mo-avatar-size, 50px);object-fit:cover;position:absolute;right:14px;width:var(--mo-avatar-size, 50px);z-index:1}.mo-theme>.mo-item .mo-card-body{padding:1.75rem 1rem 1rem}.mo-theme>.mo-item .mo-card-body h3{font-size:1rem;font-weight:700;margin:0 0 .3rem}.mo-theme>.mo-item .mo-card-body .mo-meta{color:#9ca3af;font-size:.75rem;margin-bottom:.5rem}.mo-theme>.mo-item .mo-card-body p{color:#6b7280;font-size:.875rem;line-height:1.55;margin:0}.mo-theme.mo-twocol>.mo-item:not(.mo-inverted) .mo-arrow{border-left-color:var(--mo-arrow-color);right:12px}.mo-theme.mo-twocol>.mo-item.mo-inverted .mo-arrow{border-right-color:var(--mo-arrow-color);left:12px}.mo-theme .mo-badge{background:#fff;border:2px solid var(--mo-line-color);box-shadow:0 2px 6px #0000001a;color:#374151}.mo-timeline.mo-animate>.mo-item{transition:opacity var(--mo-animate-duration, .5s) ease,transform var(--mo-animate-duration, .5s) ease}.mo-timeline.mo-animate-fade>.mo-item{opacity:0}.mo-timeline.mo-animate-slide>.mo-item:not(.mo-inverted){opacity:0;transform:translate(-40px)}.mo-timeline.mo-animate-slide>.mo-item.mo-inverted{opacity:0;transform:translate(40px)}.mo-timeline:not(.mo-twocol).mo-animate-slide>.mo-item{transform:translate(-40px)}.mo-timeline.mo-animate>.mo-item.mo-visible{opacity:1;transform:translate(0)}
@@ -1,13 +1,13 @@
1
- var E = Object.defineProperty;
2
- var _ = (n, e, t) => e in n ? E(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t;
3
- var g = (n, e, t) => _(n, typeof e != "symbol" ? e + "" : e, t);
1
+ var _ = Object.defineProperty;
2
+ var b = (a, e, t) => e in a ? _(a, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[e] = t;
3
+ var g = (a, e, t) => b(a, typeof e != "symbol" ? e + "" : e, t);
4
4
  /*!
5
- * moTimeline v2.8.0
5
+ * moTimeline v2.9.0
6
6
  * Responsive two-column timeline layout library
7
7
  * https://github.com/MattOpen/moTimeline
8
8
  * MIT License
9
9
  */
10
- const l = /* @__PURE__ */ new WeakMap(), p = {
10
+ const m = /* @__PURE__ */ new WeakMap(), p = {
11
11
  columnCount: { xs: 1, sm: 2, md: 2, lg: 2 },
12
12
  showBadge: !1,
13
13
  showArrow: !1,
@@ -19,51 +19,61 @@ const l = /* @__PURE__ */ new WeakMap(), p = {
19
19
  cardMargin: "0.5rem 1.25rem 0.5rem 0.5rem",
20
20
  cardMarginInverted: "0.5rem 0.5rem 0.5rem 1.25rem",
21
21
  cardMarginFullWidth: "0.5rem",
22
- randomFullWidth: 0
22
+ randomFullWidth: 0,
23
23
  // 0 = off; 0–1 = probability per item; true = 0.33
24
- }, b = "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='11' fill='%234f46e5'/><circle cx='12' cy='12' r='4.5' fill='white'/></svg>";
24
+ animate: !1
25
+ // false | 'fade' | 'slide'
26
+ }, E = "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='11' fill='%234f46e5'/><circle cx='12' cy='12' r='4.5' fill='white'/></svg>";
25
27
  function v() {
26
- const n = window.innerWidth;
27
- return n < 600 ? "xs" : n < 992 ? "sm" : n < 1200 ? "md" : "lg";
28
+ const a = window.innerWidth;
29
+ return a < 600 ? "xs" : a < 992 ? "sm" : a < 1200 ? "md" : "lg";
28
30
  }
29
- function C(n, e = 100) {
31
+ function I(a, e = 100) {
30
32
  let t;
31
- return (...s) => {
32
- clearTimeout(t), t = setTimeout(() => n(...s), e);
33
+ return (...r) => {
34
+ clearTimeout(t), t = setTimeout(() => a(...r), e);
33
35
  };
34
36
  }
35
- function m(n) {
36
- return n ? {
37
- o: n.offsetTop,
38
- h: n.offsetHeight,
39
- gppu: n.offsetTop + n.offsetHeight
37
+ function f(a) {
38
+ return a ? {
39
+ o: a.offsetTop,
40
+ h: a.offsetHeight,
41
+ gppu: a.offsetTop + a.offsetHeight
40
42
  } : { o: 0, h: 0, gppu: 0 };
41
43
  }
42
- function w(n, e) {
44
+ function w(a, e) {
43
45
  const t = [];
44
- let s = n.previousElementSibling;
45
- for (; s; )
46
- (!e || s.matches(e)) && t.push(s), s = s.previousElementSibling;
46
+ let r = a.previousElementSibling;
47
+ for (; r; )
48
+ (!e || r.matches(e)) && t.push(r), r = r.previousElementSibling;
47
49
  return t;
48
50
  }
49
51
  const c = class c {
50
52
  constructor(e, t = {}) {
51
53
  if (typeof e == "string" && (e = document.querySelector(e)), !e) throw new Error("moTimeline: element not found");
52
- this.element = e, this.settings = Object.assign({}, p, t), this.settings.columnCount = Object.assign({}, p.columnCount, t.columnCount), this._resizeHandler = C(() => this.refresh(), 100), this._initialized = !1, this.init();
54
+ this.element = e, this.settings = Object.assign({}, p, t), this.settings.columnCount = Object.assign({}, p.columnCount, t.columnCount), this._resizeHandler = I(() => this.refresh(), 100), this._initialized = !1, this.init();
53
55
  }
54
56
  init() {
55
57
  const e = this.element;
56
- if (l.has(e)) {
58
+ if (m.has(e)) {
57
59
  this.refresh();
58
60
  return;
59
61
  }
60
62
  const t = Object.assign({}, this.settings, { lastItemIdx: 0 });
61
- l.set(e, t), c.instances.add(this), e.classList.add("mo-timeline"), t.theme && e.classList.add("mo-theme"), e.style.setProperty("--mo-card-border-radius", t.cardBorderRadius), e.style.setProperty("--mo-avatar-size", t.avatarSize), e.style.setProperty("--mo-card-margin", t.cardMargin), e.style.setProperty("--mo-card-margin-inverted", t.cardMarginInverted), e.style.setProperty("--mo-card-margin-fullwidth", t.cardMarginFullWidth), this._initialized = !0, window.addEventListener("resize", this._resizeHandler), Array.from(e.children).length > 0 && this._initItems();
63
+ if (m.set(e, t), c.instances.add(this), e.classList.add("mo-timeline"), t.theme && e.classList.add("mo-theme"), e.style.setProperty("--mo-card-border-radius", t.cardBorderRadius), e.style.setProperty("--mo-avatar-size", t.avatarSize), e.style.setProperty("--mo-card-margin", t.cardMargin), e.style.setProperty("--mo-card-margin-inverted", t.cardMarginInverted), e.style.setProperty("--mo-card-margin-fullwidth", t.cardMarginFullWidth), t.animate) {
64
+ const r = t.animate === !0 ? "fade" : t.animate;
65
+ e.classList.add("mo-animate", `mo-animate-${r}`), this._observer = new IntersectionObserver((o) => {
66
+ o.forEach((s) => {
67
+ s.isIntersecting && (s.target.classList.add("mo-visible"), this._observer.unobserve(s.target));
68
+ });
69
+ }, { threshold: 0.1 });
70
+ }
71
+ this._initialized = !0, window.addEventListener("resize", this._resizeHandler), Array.from(e.children).length > 0 && this._initItems();
62
72
  }
63
73
  refresh() {
64
74
  c.instances.forEach((e) => {
65
- const t = e.element, s = l.get(t);
66
- s && (s.col = s.columnCount[v()], e._setDivider(), Array.from(t.children).forEach((o) => {
75
+ const t = e.element, r = m.get(t);
76
+ r && (r.col = r.columnCount[v()], e._setDivider(), Array.from(t.children).forEach((o) => {
67
77
  e._setPostPosition(o);
68
78
  }));
69
79
  });
@@ -83,14 +93,38 @@ const c = class c {
83
93
  addItems(e) {
84
94
  typeof e == "string" && (e = JSON.parse(e)), e.forEach((t) => this.element.appendChild(this._createItemElement(t))), this._initItems();
85
95
  }
96
+ /**
97
+ * Insert a single item at a specific position or at a random position.
98
+ *
99
+ * @param {Object} item — same shape as addItems(): { title, meta, text, banner, avatar, icon }
100
+ * @param {number} [index] — 0-based insertion index. Omit (or pass undefined) for a random position.
101
+ * @returns {HTMLElement} the inserted <li> element
102
+ */
103
+ insertItem(e, t) {
104
+ const r = this.element, o = this._getData();
105
+ if (!o) return;
106
+ const s = this._createItemElement(e), i = Array.from(r.children).filter((l) => l.classList.contains("js-mo-item")), n = t == null ? Math.floor(Math.random() * (i.length + 1)) : Math.max(0, Math.min(t, i.length));
107
+ if (n >= i.length ? r.appendChild(s) : r.insertBefore(s, i[n]), s.id || (s.id = "moT" + crypto.randomUUID() + "_" + n), s.classList.add("mo-item", "js-mo-item"), e.fullWidth)
108
+ s.classList.add("mo-fullwidth", "js-mo-fullwidth");
109
+ else if (o.randomFullWidth) {
110
+ const l = o.randomFullWidth === !0 ? 0.33 : o.randomFullWidth;
111
+ Math.random() < l && s.classList.add("mo-fullwidth", "js-mo-fullwidth");
112
+ }
113
+ return o.showBadge && this._createBadge(s, n + 1), o.showArrow && this._createArrow(s), o.showBadge && o.showCounterStyle === "counter" && Array.from(r.querySelectorAll(".js-mo-item")).forEach((l, d) => {
114
+ const h = l.querySelector(".js-mo-badge");
115
+ h && (h.textContent = d + 1);
116
+ }), o.lastItemIdx = Array.from(r.children).length, m.set(r, o), this.refresh(), this._observeItems([s]), s.querySelectorAll("img").forEach((l) => {
117
+ l.complete || l.addEventListener("load", this._resizeHandler, { once: !0 });
118
+ }), s;
119
+ }
86
120
  destroy() {
87
- window.removeEventListener("resize", this._resizeHandler), l.delete(this.element), c.instances.delete(this), this.element.style.removeProperty("--mo-card-border-radius"), this.element.style.removeProperty("--mo-avatar-size"), this.element.style.removeProperty("--mo-card-margin"), this.element.style.removeProperty("--mo-card-margin-inverted"), this.element.style.removeProperty("--mo-card-margin-fullwidth"), this.element.classList.remove("mo-timeline", "mo-theme", "mo-twocol"), Array.from(this.element.children).forEach((e) => {
88
- e.classList.remove("mo-item", "js-mo-item", "mo-inverted", "js-mo-inverted", "mo-offset", "mo-fullwidth", "js-mo-fullwidth"), e.querySelectorAll(".js-mo-badge, .js-mo-arrow").forEach((t) => t.remove());
121
+ window.removeEventListener("resize", this._resizeHandler), this._observer && (this._observer.disconnect(), this._observer = null), m.delete(this.element), c.instances.delete(this), this.element.style.removeProperty("--mo-card-border-radius"), this.element.style.removeProperty("--mo-avatar-size"), this.element.style.removeProperty("--mo-card-margin"), this.element.style.removeProperty("--mo-card-margin-inverted"), this.element.style.removeProperty("--mo-card-margin-fullwidth"), this.element.classList.remove("mo-timeline", "mo-theme", "mo-twocol", "mo-animate", "mo-animate-fade", "mo-animate-slide"), Array.from(this.element.children).forEach((e) => {
122
+ e.classList.remove("mo-item", "js-mo-item", "mo-inverted", "js-mo-inverted", "mo-offset", "mo-fullwidth", "js-mo-fullwidth", "mo-visible"), e.querySelectorAll(".js-mo-badge, .js-mo-arrow").forEach((t) => t.remove());
89
123
  });
90
124
  }
91
125
  // ─── Private ────────────────────────────────────────────────────────────────
92
126
  _getData() {
93
- return l.get(this.element);
127
+ return m.get(this.element);
94
128
  }
95
129
  _setDivider() {
96
130
  const e = this._getData();
@@ -99,21 +133,21 @@ const c = class c {
99
133
  _initItems() {
100
134
  const e = this.element, t = this._getData();
101
135
  if (!t) return;
102
- const s = t.lastItemIdx, o = Array.from(e.children), r = o.slice(s);
103
- if (r.length !== 0) {
104
- if (r.forEach((i, a) => {
105
- i.id || (i.id = "moT" + crypto.randomUUID() + "_" + (a + s)), i.classList.add("mo-item", "js-mo-item");
136
+ const r = t.lastItemIdx, o = Array.from(e.children), s = o.slice(r);
137
+ if (s.length !== 0) {
138
+ if (s.forEach((i, n) => {
139
+ i.id || (i.id = "moT" + crypto.randomUUID() + "_" + (n + r)), i.classList.add("mo-item", "js-mo-item");
106
140
  }), this._setDivider(), t.randomFullWidth) {
107
141
  const i = t.randomFullWidth === !0 ? 0.33 : t.randomFullWidth;
108
- r.forEach((a) => {
109
- !a.classList.contains("mo-fullwidth") && Math.random() < i && a.classList.add("mo-fullwidth", "js-mo-fullwidth");
142
+ s.forEach((n) => {
143
+ !n.classList.contains("mo-fullwidth") && Math.random() < i && n.classList.add("mo-fullwidth", "js-mo-fullwidth");
110
144
  });
111
145
  }
112
- r.forEach((i, a) => {
113
- t.showBadge && this._createBadge(i, a + s + 1), t.showArrow && this._createArrow(i);
114
- }), t.lastItemIdx = o.length, l.set(e, t), this.refresh(), r.forEach((i) => {
115
- i.querySelectorAll("img").forEach((a) => {
116
- a.complete || a.addEventListener("load", this._resizeHandler, { once: !0 });
146
+ s.forEach((i, n) => {
147
+ t.showBadge && this._createBadge(i, n + r + 1), t.showArrow && this._createArrow(i);
148
+ }), t.lastItemIdx = o.length, m.set(e, t), this.refresh(), this._observeItems(s), s.forEach((i) => {
149
+ i.querySelectorAll("img").forEach((n) => {
150
+ n.complete || n.addEventListener("load", this._resizeHandler, { once: !0 });
117
151
  });
118
152
  });
119
153
  }
@@ -130,22 +164,22 @@ const c = class c {
130
164
  if (!e) return null;
131
165
  const t = this._getData();
132
166
  if (!t) return null;
133
- const s = t.col, o = w(e, ".js-mo-inverted")[0] || null, r = w(e, ".js-mo-item:not(.js-mo-inverted)")[0] || null, i = m(r), a = m(o), h = m(e);
134
- let d = 0, f = 0;
135
- if (s > 1) {
136
- i.gppu > h.o + 1 && (d = 1), a.gppu > i.gppu && (d = 0);
167
+ const r = t.col, o = w(e, ".js-mo-inverted")[0] || null, s = w(e, ".js-mo-item:not(.js-mo-inverted)")[0] || null, i = f(s), n = f(o), l = f(e);
168
+ let d = 0, h = 0;
169
+ if (r > 1) {
170
+ i.gppu > l.o + 1 && (d = 1), n.gppu > i.gppu && (d = 0);
137
171
  const u = e.previousElementSibling;
138
- u && Math.abs(h.o - m(u).o) < 40 && (f = 1);
172
+ u && Math.abs(l.o - f(u).o) < 40 && (h = 1);
139
173
  }
140
- return { lr: d, badge_offset: f };
174
+ return { lr: d, badge_offset: h };
141
175
  }
142
176
  _createBadge(e, t) {
143
- const s = this._getData(), o = document.createElement("span");
144
- if (o.className = "mo-badge js-mo-badge", s.showCounterStyle === "none")
177
+ const r = this._getData(), o = document.createElement("span");
178
+ if (o.className = "mo-badge js-mo-badge", r.showCounterStyle === "none")
145
179
  o.style.opacity = "0";
146
- else if (s.showCounterStyle === "image") {
147
- const r = document.createElement("img");
148
- r.className = "mo-badge-icon", r.alt = "", r.src = e.dataset.moIcon || b, o.appendChild(r);
180
+ else if (r.showCounterStyle === "image") {
181
+ const s = document.createElement("img");
182
+ s.className = "mo-badge-icon", s.alt = "", s.src = e.dataset.moIcon || E, o.appendChild(s);
149
183
  } else
150
184
  o.textContent = t;
151
185
  e.prepend(o);
@@ -153,36 +187,41 @@ const c = class c {
153
187
  _createItemElement(e) {
154
188
  const t = document.createElement("li");
155
189
  e.icon && (t.dataset.moIcon = e.icon);
156
- const s = document.createElement("div");
157
- if (s.className = "mo-card", e.banner) {
158
- const r = document.createElement("div");
159
- r.className = "mo-card-image";
190
+ const r = document.createElement("div");
191
+ if (r.className = "mo-card", e.banner) {
192
+ const s = document.createElement("div");
193
+ s.className = "mo-card-image";
160
194
  const i = document.createElement("img");
161
- if (i.className = "mo-banner", i.src = e.banner, i.alt = "", r.appendChild(i), e.avatar) {
162
- const a = document.createElement("img");
163
- a.className = "mo-avatar", a.src = e.avatar, a.alt = "", r.appendChild(a);
195
+ if (i.className = "mo-banner", i.src = e.banner, i.alt = "", s.appendChild(i), e.avatar) {
196
+ const n = document.createElement("img");
197
+ n.className = "mo-avatar", n.src = e.avatar, n.alt = "", s.appendChild(n);
164
198
  }
165
- s.appendChild(r);
199
+ r.appendChild(s);
166
200
  }
167
201
  const o = document.createElement("div");
168
202
  if (o.className = "mo-card-body", e.title) {
169
- const r = document.createElement("h3");
170
- r.textContent = e.title, o.appendChild(r);
203
+ const s = document.createElement("h3");
204
+ s.textContent = e.title, o.appendChild(s);
171
205
  }
172
206
  if (e.meta) {
173
- const r = document.createElement("p");
174
- r.className = "mo-meta", r.textContent = e.meta, o.appendChild(r);
207
+ const s = document.createElement("p");
208
+ s.className = "mo-meta", s.textContent = e.meta, o.appendChild(s);
175
209
  }
176
210
  if (e.text) {
177
- const r = document.createElement("p");
178
- r.textContent = e.text, o.appendChild(r);
211
+ const s = document.createElement("p");
212
+ s.textContent = e.text, o.appendChild(s);
179
213
  }
180
- return s.appendChild(o), t.appendChild(s), t;
214
+ return r.appendChild(o), t.appendChild(r), t;
181
215
  }
182
216
  _createArrow(e) {
183
217
  const t = document.createElement("span");
184
218
  t.className = "mo-arrow js-mo-arrow", e.prepend(t);
185
219
  }
220
+ _observeItems(e) {
221
+ this._observer && e.forEach((t) => {
222
+ t.classList.contains("mo-visible") || this._observer.observe(t);
223
+ });
224
+ }
186
225
  };
187
226
  g(c, "instances", /* @__PURE__ */ new Set());
188
227
  let y = c;
@@ -1,6 +1,6 @@
1
- (function(l,n){typeof exports=="object"&&typeof module<"u"?n(exports):typeof define=="function"&&define.amd?define(["exports"],n):(l=typeof globalThis<"u"?globalThis:l||self,n(l.MoTimeline={}))})(this,function(l){"use strict";var C=Object.defineProperty;var I=(l,n,d)=>n in l?C(l,n,{enumerable:!0,configurable:!0,writable:!0,value:d}):l[n]=d;var E=(l,n,d)=>I(l,typeof n!="symbol"?n+"":n,d);/*!
2
- * moTimeline v2.8.0
1
+ (function(l,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(l=typeof globalThis<"u"?globalThis:l||self,a(l.MoTimeline={}))})(this,function(l){"use strict";var I=Object.defineProperty;var L=(l,a,c)=>a in l?I(l,a,{enumerable:!0,configurable:!0,writable:!0,value:c}):l[a]=c;var b=(l,a,c)=>L(l,typeof a!="symbol"?a+"":a,c);/*!
2
+ * moTimeline v2.9.0
3
3
  * Responsive two-column timeline layout library
4
4
  * https://github.com/MattOpen/moTimeline
5
5
  * MIT License
6
- */const n=new WeakMap,d={columnCount:{xs:1,sm:2,md:2,lg:2},showBadge:!1,showArrow:!1,theme:!1,showCounterStyle:"counter",cardBorderRadius:"8px",avatarSize:"50px",cardMargin:"0.5rem 1.25rem 0.5rem 0.5rem",cardMarginInverted:"0.5rem 0.5rem 0.5rem 1.25rem",cardMarginFullWidth:"0.5rem",randomFullWidth:0},_="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='11' fill='%234f46e5'/><circle cx='12' cy='12' r='4.5' fill='white'/></svg>";function g(){const c=window.innerWidth;return c<600?"xs":c<992?"sm":c<1200?"md":"lg"}function b(c,e=100){let t;return(...r)=>{clearTimeout(t),t=setTimeout(()=>c(...r),e)}}function h(c){return c?{o:c.offsetTop,h:c.offsetHeight,gppu:c.offsetTop+c.offsetHeight}:{o:0,h:0,gppu:0}}function p(c,e){const t=[];let r=c.previousElementSibling;for(;r;)(!e||r.matches(e))&&t.push(r),r=r.previousElementSibling;return t}const m=class m{constructor(e,t={}){if(typeof e=="string"&&(e=document.querySelector(e)),!e)throw new Error("moTimeline: element not found");this.element=e,this.settings=Object.assign({},d,t),this.settings.columnCount=Object.assign({},d.columnCount,t.columnCount),this._resizeHandler=b(()=>this.refresh(),100),this._initialized=!1,this.init()}init(){const e=this.element;if(n.has(e)){this.refresh();return}const t=Object.assign({},this.settings,{lastItemIdx:0});n.set(e,t),m.instances.add(this),e.classList.add("mo-timeline"),t.theme&&e.classList.add("mo-theme"),e.style.setProperty("--mo-card-border-radius",t.cardBorderRadius),e.style.setProperty("--mo-avatar-size",t.avatarSize),e.style.setProperty("--mo-card-margin",t.cardMargin),e.style.setProperty("--mo-card-margin-inverted",t.cardMarginInverted),e.style.setProperty("--mo-card-margin-fullwidth",t.cardMarginFullWidth),this._initialized=!0,window.addEventListener("resize",this._resizeHandler),Array.from(e.children).length>0&&this._initItems()}refresh(){m.instances.forEach(e=>{const t=e.element,r=n.get(t);r&&(r.col=r.columnCount[g()],e._setDivider(),Array.from(t.children).forEach(o=>{e._setPostPosition(o)}))})}initNewItems(){this._initItems()}addItems(e){typeof e=="string"&&(e=JSON.parse(e)),e.forEach(t=>this.element.appendChild(this._createItemElement(t))),this._initItems()}destroy(){window.removeEventListener("resize",this._resizeHandler),n.delete(this.element),m.instances.delete(this),this.element.style.removeProperty("--mo-card-border-radius"),this.element.style.removeProperty("--mo-avatar-size"),this.element.style.removeProperty("--mo-card-margin"),this.element.style.removeProperty("--mo-card-margin-inverted"),this.element.style.removeProperty("--mo-card-margin-fullwidth"),this.element.classList.remove("mo-timeline","mo-theme","mo-twocol"),Array.from(this.element.children).forEach(e=>{e.classList.remove("mo-item","js-mo-item","mo-inverted","js-mo-inverted","mo-offset","mo-fullwidth","js-mo-fullwidth"),e.querySelectorAll(".js-mo-badge, .js-mo-arrow").forEach(t=>t.remove())})}_getData(){return n.get(this.element)}_setDivider(){const e=this._getData();e&&(e.col=e.columnCount[g()],this.element.classList.toggle("mo-twocol",e.col>1))}_initItems(){const e=this.element,t=this._getData();if(!t)return;const r=t.lastItemIdx,o=Array.from(e.children),s=o.slice(r);if(s.length!==0){if(s.forEach((i,a)=>{i.id||(i.id="moT"+crypto.randomUUID()+"_"+(a+r)),i.classList.add("mo-item","js-mo-item")}),this._setDivider(),t.randomFullWidth){const i=t.randomFullWidth===!0?.33:t.randomFullWidth;s.forEach(a=>{!a.classList.contains("mo-fullwidth")&&Math.random()<i&&a.classList.add("mo-fullwidth","js-mo-fullwidth")})}s.forEach((i,a)=>{t.showBadge&&this._createBadge(i,a+r+1),t.showArrow&&this._createArrow(i)}),t.lastItemIdx=o.length,n.set(e,t),this.refresh(),s.forEach(i=>{i.querySelectorAll("img").forEach(a=>{a.complete||a.addEventListener("load",this._resizeHandler,{once:!0})})})}}_setPostPosition(e){if(e.classList.contains("mo-fullwidth")){e.classList.remove("mo-inverted","js-mo-inverted","mo-offset");return}const t=this._getLeftOrRight(e);t&&(e.classList.toggle("mo-inverted",t.lr>0),e.classList.toggle("js-mo-inverted",t.lr>0),e.classList.toggle("mo-offset",t.badge_offset>0))}_getLeftOrRight(e){if(!e)return null;const t=this._getData();if(!t)return null;const r=t.col,o=p(e,".js-mo-inverted")[0]||null,s=p(e,".js-mo-item:not(.js-mo-inverted)")[0]||null,i=h(s),a=h(o),v=h(e);let u=0,w=0;if(r>1){i.gppu>v.o+1&&(u=1),a.gppu>i.gppu&&(u=0);const y=e.previousElementSibling;y&&Math.abs(v.o-h(y).o)<40&&(w=1)}return{lr:u,badge_offset:w}}_createBadge(e,t){const r=this._getData(),o=document.createElement("span");if(o.className="mo-badge js-mo-badge",r.showCounterStyle==="none")o.style.opacity="0";else if(r.showCounterStyle==="image"){const s=document.createElement("img");s.className="mo-badge-icon",s.alt="",s.src=e.dataset.moIcon||_,o.appendChild(s)}else o.textContent=t;e.prepend(o)}_createItemElement(e){const t=document.createElement("li");e.icon&&(t.dataset.moIcon=e.icon);const r=document.createElement("div");if(r.className="mo-card",e.banner){const s=document.createElement("div");s.className="mo-card-image";const i=document.createElement("img");if(i.className="mo-banner",i.src=e.banner,i.alt="",s.appendChild(i),e.avatar){const a=document.createElement("img");a.className="mo-avatar",a.src=e.avatar,a.alt="",s.appendChild(a)}r.appendChild(s)}const o=document.createElement("div");if(o.className="mo-card-body",e.title){const s=document.createElement("h3");s.textContent=e.title,o.appendChild(s)}if(e.meta){const s=document.createElement("p");s.className="mo-meta",s.textContent=e.meta,o.appendChild(s)}if(e.text){const s=document.createElement("p");s.textContent=e.text,o.appendChild(s)}return r.appendChild(o),t.appendChild(r),t}_createArrow(e){const t=document.createElement("span");t.className="mo-arrow js-mo-arrow",e.prepend(t)}};E(m,"instances",new Set);let f=m;l.MoTimeline=f,l.default=f,Object.defineProperties(l,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
6
+ */const a=new WeakMap,c={columnCount:{xs:1,sm:2,md:2,lg:2},showBadge:!1,showArrow:!1,theme:!1,showCounterStyle:"counter",cardBorderRadius:"8px",avatarSize:"50px",cardMargin:"0.5rem 1.25rem 0.5rem 0.5rem",cardMarginInverted:"0.5rem 0.5rem 0.5rem 1.25rem",cardMarginFullWidth:"0.5rem",randomFullWidth:0,animate:!1},_="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='11' fill='%234f46e5'/><circle cx='12' cy='12' r='4.5' fill='white'/></svg>";function v(){const m=window.innerWidth;return m<600?"xs":m<992?"sm":m<1200?"md":"lg"}function E(m,e=100){let t;return(...r)=>{clearTimeout(t),t=setTimeout(()=>m(...r),e)}}function g(m){return m?{o:m.offsetTop,h:m.offsetHeight,gppu:m.offsetTop+m.offsetHeight}:{o:0,h:0,gppu:0}}function w(m,e){const t=[];let r=m.previousElementSibling;for(;r;)(!e||r.matches(e))&&t.push(r),r=r.previousElementSibling;return t}const h=class h{constructor(e,t={}){if(typeof e=="string"&&(e=document.querySelector(e)),!e)throw new Error("moTimeline: element not found");this.element=e,this.settings=Object.assign({},c,t),this.settings.columnCount=Object.assign({},c.columnCount,t.columnCount),this._resizeHandler=E(()=>this.refresh(),100),this._initialized=!1,this.init()}init(){const e=this.element;if(a.has(e)){this.refresh();return}const t=Object.assign({},this.settings,{lastItemIdx:0});if(a.set(e,t),h.instances.add(this),e.classList.add("mo-timeline"),t.theme&&e.classList.add("mo-theme"),e.style.setProperty("--mo-card-border-radius",t.cardBorderRadius),e.style.setProperty("--mo-avatar-size",t.avatarSize),e.style.setProperty("--mo-card-margin",t.cardMargin),e.style.setProperty("--mo-card-margin-inverted",t.cardMarginInverted),e.style.setProperty("--mo-card-margin-fullwidth",t.cardMarginFullWidth),t.animate){const r=t.animate===!0?"fade":t.animate;e.classList.add("mo-animate",`mo-animate-${r}`),this._observer=new IntersectionObserver(o=>{o.forEach(s=>{s.isIntersecting&&(s.target.classList.add("mo-visible"),this._observer.unobserve(s.target))})},{threshold:.1})}this._initialized=!0,window.addEventListener("resize",this._resizeHandler),Array.from(e.children).length>0&&this._initItems()}refresh(){h.instances.forEach(e=>{const t=e.element,r=a.get(t);r&&(r.col=r.columnCount[v()],e._setDivider(),Array.from(t.children).forEach(o=>{e._setPostPosition(o)}))})}initNewItems(){this._initItems()}addItems(e){typeof e=="string"&&(e=JSON.parse(e)),e.forEach(t=>this.element.appendChild(this._createItemElement(t))),this._initItems()}insertItem(e,t){const r=this.element,o=this._getData();if(!o)return;const s=this._createItemElement(e),i=Array.from(r.children).filter(d=>d.classList.contains("js-mo-item")),n=t==null?Math.floor(Math.random()*(i.length+1)):Math.max(0,Math.min(t,i.length));if(n>=i.length?r.appendChild(s):r.insertBefore(s,i[n]),s.id||(s.id="moT"+crypto.randomUUID()+"_"+n),s.classList.add("mo-item","js-mo-item"),e.fullWidth)s.classList.add("mo-fullwidth","js-mo-fullwidth");else if(o.randomFullWidth){const d=o.randomFullWidth===!0?.33:o.randomFullWidth;Math.random()<d&&s.classList.add("mo-fullwidth","js-mo-fullwidth")}return o.showBadge&&this._createBadge(s,n+1),o.showArrow&&this._createArrow(s),o.showBadge&&o.showCounterStyle==="counter"&&Array.from(r.querySelectorAll(".js-mo-item")).forEach((d,f)=>{const u=d.querySelector(".js-mo-badge");u&&(u.textContent=f+1)}),o.lastItemIdx=Array.from(r.children).length,a.set(r,o),this.refresh(),this._observeItems([s]),s.querySelectorAll("img").forEach(d=>{d.complete||d.addEventListener("load",this._resizeHandler,{once:!0})}),s}destroy(){window.removeEventListener("resize",this._resizeHandler),this._observer&&(this._observer.disconnect(),this._observer=null),a.delete(this.element),h.instances.delete(this),this.element.style.removeProperty("--mo-card-border-radius"),this.element.style.removeProperty("--mo-avatar-size"),this.element.style.removeProperty("--mo-card-margin"),this.element.style.removeProperty("--mo-card-margin-inverted"),this.element.style.removeProperty("--mo-card-margin-fullwidth"),this.element.classList.remove("mo-timeline","mo-theme","mo-twocol","mo-animate","mo-animate-fade","mo-animate-slide"),Array.from(this.element.children).forEach(e=>{e.classList.remove("mo-item","js-mo-item","mo-inverted","js-mo-inverted","mo-offset","mo-fullwidth","js-mo-fullwidth","mo-visible"),e.querySelectorAll(".js-mo-badge, .js-mo-arrow").forEach(t=>t.remove())})}_getData(){return a.get(this.element)}_setDivider(){const e=this._getData();e&&(e.col=e.columnCount[v()],this.element.classList.toggle("mo-twocol",e.col>1))}_initItems(){const e=this.element,t=this._getData();if(!t)return;const r=t.lastItemIdx,o=Array.from(e.children),s=o.slice(r);if(s.length!==0){if(s.forEach((i,n)=>{i.id||(i.id="moT"+crypto.randomUUID()+"_"+(n+r)),i.classList.add("mo-item","js-mo-item")}),this._setDivider(),t.randomFullWidth){const i=t.randomFullWidth===!0?.33:t.randomFullWidth;s.forEach(n=>{!n.classList.contains("mo-fullwidth")&&Math.random()<i&&n.classList.add("mo-fullwidth","js-mo-fullwidth")})}s.forEach((i,n)=>{t.showBadge&&this._createBadge(i,n+r+1),t.showArrow&&this._createArrow(i)}),t.lastItemIdx=o.length,a.set(e,t),this.refresh(),this._observeItems(s),s.forEach(i=>{i.querySelectorAll("img").forEach(n=>{n.complete||n.addEventListener("load",this._resizeHandler,{once:!0})})})}}_setPostPosition(e){if(e.classList.contains("mo-fullwidth")){e.classList.remove("mo-inverted","js-mo-inverted","mo-offset");return}const t=this._getLeftOrRight(e);t&&(e.classList.toggle("mo-inverted",t.lr>0),e.classList.toggle("js-mo-inverted",t.lr>0),e.classList.toggle("mo-offset",t.badge_offset>0))}_getLeftOrRight(e){if(!e)return null;const t=this._getData();if(!t)return null;const r=t.col,o=w(e,".js-mo-inverted")[0]||null,s=w(e,".js-mo-item:not(.js-mo-inverted)")[0]||null,i=g(s),n=g(o),d=g(e);let f=0,u=0;if(r>1){i.gppu>d.o+1&&(f=1),n.gppu>i.gppu&&(f=0);const y=e.previousElementSibling;y&&Math.abs(d.o-g(y).o)<40&&(u=1)}return{lr:f,badge_offset:u}}_createBadge(e,t){const r=this._getData(),o=document.createElement("span");if(o.className="mo-badge js-mo-badge",r.showCounterStyle==="none")o.style.opacity="0";else if(r.showCounterStyle==="image"){const s=document.createElement("img");s.className="mo-badge-icon",s.alt="",s.src=e.dataset.moIcon||_,o.appendChild(s)}else o.textContent=t;e.prepend(o)}_createItemElement(e){const t=document.createElement("li");e.icon&&(t.dataset.moIcon=e.icon);const r=document.createElement("div");if(r.className="mo-card",e.banner){const s=document.createElement("div");s.className="mo-card-image";const i=document.createElement("img");if(i.className="mo-banner",i.src=e.banner,i.alt="",s.appendChild(i),e.avatar){const n=document.createElement("img");n.className="mo-avatar",n.src=e.avatar,n.alt="",s.appendChild(n)}r.appendChild(s)}const o=document.createElement("div");if(o.className="mo-card-body",e.title){const s=document.createElement("h3");s.textContent=e.title,o.appendChild(s)}if(e.meta){const s=document.createElement("p");s.className="mo-meta",s.textContent=e.meta,o.appendChild(s)}if(e.text){const s=document.createElement("p");s.textContent=e.text,o.appendChild(s)}return r.appendChild(o),t.appendChild(r),t}_createArrow(e){const t=document.createElement("span");t.className="mo-arrow js-mo-arrow",e.prepend(t)}_observeItems(e){this._observer&&e.forEach(t=>{t.classList.contains("mo-visible")||this._observer.observe(t)})}};b(h,"instances",new Set);let p=h;l.MoTimeline=p,l.default=p,Object.defineProperties(l,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "motimeline",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "Responsive two-column timeline layout library. Plain JavaScript, no dependencies.",
5
5
  "main": "./dist/moTimeline.cjs",
6
6
  "module": "./dist/moTimeline.js",
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * moTimeline v2.8.0 — CSS
2
+ * moTimeline v2.9.0 — CSS
3
3
  */
4
4
 
5
5
  /* ── CSS custom properties (easy override) ─────────────────── */
@@ -263,3 +263,39 @@
263
263
  box-shadow: 0 2px 6px rgba(0, 0, 0, .10);
264
264
  color: #374151;
265
265
  }
266
+
267
+ /* ═══════════════════════════════════════════════════════════════
268
+ ANIMATION — enabled via animate: 'fade' | 'slide'
269
+ ═══════════════════════════════════════════════════════════════ */
270
+
271
+ .mo-timeline.mo-animate > .mo-item {
272
+ transition: opacity var(--mo-animate-duration, 0.5s) ease,
273
+ transform var(--mo-animate-duration, 0.5s) ease;
274
+ }
275
+
276
+ /* Fade: items start invisible */
277
+ .mo-timeline.mo-animate-fade > .mo-item {
278
+ opacity: 0;
279
+ }
280
+
281
+ /* Slide: left items come from left, right items from right */
282
+ .mo-timeline.mo-animate-slide > .mo-item:not(.mo-inverted) {
283
+ opacity: 0;
284
+ transform: translateX(-40px);
285
+ }
286
+
287
+ .mo-timeline.mo-animate-slide > .mo-item.mo-inverted {
288
+ opacity: 0;
289
+ transform: translateX(40px);
290
+ }
291
+
292
+ /* Single-column slide: always comes from left */
293
+ .mo-timeline:not(.mo-twocol).mo-animate-slide > .mo-item {
294
+ transform: translateX(-40px);
295
+ }
296
+
297
+ /* Visible state — added by IntersectionObserver */
298
+ .mo-timeline.mo-animate > .mo-item.mo-visible {
299
+ opacity: 1;
300
+ transform: translateX(0);
301
+ }
package/src/moTimeline.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * moTimeline v2.8.0
2
+ * moTimeline v2.9.0
3
3
  * Responsive two-column timeline layout library
4
4
  * https://github.com/MattOpen/moTimeline
5
5
  * MIT License
@@ -22,6 +22,7 @@ const DEFAULTS = {
22
22
  cardMarginInverted: '0.5rem 0.5rem 0.5rem 1.25rem',
23
23
  cardMarginFullWidth: '0.5rem',
24
24
  randomFullWidth: 0, // 0 = off; 0–1 = probability per item; true = 0.33
25
+ animate: false, // false | 'fade' | 'slide'
25
26
  };
26
27
 
27
28
  const DEFAULT_BADGE_ICON = "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='11' fill='%234f46e5'/><circle cx='12' cy='12' r='4.5' fill='white'/></svg>";
@@ -100,6 +101,19 @@ export class MoTimeline {
100
101
  el.style.setProperty('--mo-card-margin-inverted', data.cardMarginInverted);
101
102
  el.style.setProperty('--mo-card-margin-fullwidth', data.cardMarginFullWidth);
102
103
 
104
+ if (data.animate) {
105
+ const type = data.animate === true ? 'fade' : data.animate;
106
+ el.classList.add('mo-animate', `mo-animate-${type}`);
107
+ this._observer = new IntersectionObserver((entries) => {
108
+ entries.forEach((entry) => {
109
+ if (entry.isIntersecting) {
110
+ entry.target.classList.add('mo-visible');
111
+ this._observer.unobserve(entry.target);
112
+ }
113
+ });
114
+ }, { threshold: 0.1 });
115
+ }
116
+
103
117
  this._initialized = true;
104
118
  window.addEventListener('resize', this._resizeHandler);
105
119
 
@@ -142,8 +156,75 @@ export class MoTimeline {
142
156
  this._initItems();
143
157
  }
144
158
 
159
+ /**
160
+ * Insert a single item at a specific position or at a random position.
161
+ *
162
+ * @param {Object} item — same shape as addItems(): { title, meta, text, banner, avatar, icon }
163
+ * @param {number} [index] — 0-based insertion index. Omit (or pass undefined) for a random position.
164
+ * @returns {HTMLElement} the inserted <li> element
165
+ */
166
+ insertItem(item, index) {
167
+ const el = this.element;
168
+ const data = this._getData();
169
+ if (!data) return;
170
+
171
+ const newEl = this._createItemElement(item);
172
+
173
+ // All currently initialised items (to determine valid range)
174
+ const items = Array.from(el.children).filter((c) => c.classList.contains('js-mo-item'));
175
+ const pos = (index === undefined || index === null)
176
+ ? Math.floor(Math.random() * (items.length + 1))
177
+ : Math.max(0, Math.min(index, items.length));
178
+
179
+ // Insert into DOM (append when pos is at end)
180
+ if (pos >= items.length) {
181
+ el.appendChild(newEl);
182
+ } else {
183
+ el.insertBefore(newEl, items[pos]);
184
+ }
185
+
186
+ // Initialise the new element
187
+ if (!newEl.id) newEl.id = 'moT' + crypto.randomUUID() + '_' + pos;
188
+ newEl.classList.add('mo-item', 'js-mo-item');
189
+
190
+ // Explicit fullWidth on item data takes priority, then fall back to randomFullWidth
191
+ if (item.fullWidth) {
192
+ newEl.classList.add('mo-fullwidth', 'js-mo-fullwidth');
193
+ } else if (data.randomFullWidth) {
194
+ const prob = data.randomFullWidth === true ? 0.33 : data.randomFullWidth;
195
+ if (Math.random() < prob) newEl.classList.add('mo-fullwidth', 'js-mo-fullwidth');
196
+ }
197
+ if (data.showBadge) this._createBadge(newEl, pos + 1);
198
+ if (data.showArrow) this._createArrow(newEl);
199
+
200
+ // Re-number all counter badges so sequence stays correct after insertion
201
+ if (data.showBadge && data.showCounterStyle === 'counter') {
202
+ Array.from(el.querySelectorAll('.js-mo-item')).forEach((it, i) => {
203
+ const badge = it.querySelector('.js-mo-badge');
204
+ if (badge) badge.textContent = i + 1;
205
+ });
206
+ }
207
+
208
+ data.lastItemIdx = Array.from(el.children).length;
209
+ instanceData.set(el, data);
210
+
211
+ this.refresh();
212
+
213
+ this._observeItems([newEl]);
214
+
215
+ newEl.querySelectorAll('img').forEach((img) => {
216
+ if (!img.complete) img.addEventListener('load', this._resizeHandler, { once: true });
217
+ });
218
+
219
+ return newEl;
220
+ }
221
+
145
222
  destroy() {
146
223
  window.removeEventListener('resize', this._resizeHandler);
224
+ if (this._observer) {
225
+ this._observer.disconnect();
226
+ this._observer = null;
227
+ }
147
228
  instanceData.delete(this.element);
148
229
  MoTimeline.instances.delete(this);
149
230
  this.element.style.removeProperty('--mo-card-border-radius');
@@ -151,9 +232,9 @@ export class MoTimeline {
151
232
  this.element.style.removeProperty('--mo-card-margin');
152
233
  this.element.style.removeProperty('--mo-card-margin-inverted');
153
234
  this.element.style.removeProperty('--mo-card-margin-fullwidth');
154
- this.element.classList.remove('mo-timeline', 'mo-theme', 'mo-twocol');
235
+ this.element.classList.remove('mo-timeline', 'mo-theme', 'mo-twocol', 'mo-animate', 'mo-animate-fade', 'mo-animate-slide');
155
236
  Array.from(this.element.children).forEach((child) => {
156
- child.classList.remove('mo-item', 'js-mo-item', 'mo-inverted', 'js-mo-inverted', 'mo-offset', 'mo-fullwidth', 'js-mo-fullwidth');
237
+ child.classList.remove('mo-item', 'js-mo-item', 'mo-inverted', 'js-mo-inverted', 'mo-offset', 'mo-fullwidth', 'js-mo-fullwidth', 'mo-visible');
157
238
  child.querySelectorAll('.js-mo-badge, .js-mo-arrow').forEach((b) => b.remove());
158
239
  });
159
240
  }
@@ -217,6 +298,8 @@ export class MoTimeline {
217
298
 
218
299
  this.refresh();
219
300
 
301
+ this._observeItems(newItems);
302
+
220
303
  // Re-layout after any unloaded images finish, because offsetHeight is
221
304
  // based on text-only height until images are ready.
222
305
  newItems.forEach((item) => {
@@ -345,6 +428,15 @@ export class MoTimeline {
345
428
  span.className = 'mo-arrow js-mo-arrow';
346
429
  el.prepend(span);
347
430
  }
431
+
432
+ _observeItems(items) {
433
+ if (!this._observer) return;
434
+ items.forEach((item) => {
435
+ if (!item.classList.contains('mo-visible')) {
436
+ this._observer.observe(item);
437
+ }
438
+ });
439
+ }
348
440
  }
349
441
 
350
442
  export default MoTimeline;