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 +29 -5
- package/dist/moTimeline.cjs +3 -3
- package/dist/moTimeline.css +2 -2
- package/dist/moTimeline.js +105 -66
- package/dist/moTimeline.umd.js +3 -3
- package/package.json +1 -1
- package/src/moTimeline.css +37 -1
- package/src/moTimeline.js +95 -3
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
|
|
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();
|
|
152
|
-
tl.initNewItems();
|
|
153
|
-
tl.addItems(items);
|
|
154
|
-
tl.
|
|
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)
|
package/dist/moTimeline.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
* moTimeline v2.
|
|
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
|
|
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;
|
package/dist/moTimeline.css
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* moTimeline v2.
|
|
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)}
|
package/dist/moTimeline.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var g = (
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
27
|
-
return
|
|
28
|
+
const a = window.innerWidth;
|
|
29
|
+
return a < 600 ? "xs" : a < 992 ? "sm" : a < 1200 ? "md" : "lg";
|
|
28
30
|
}
|
|
29
|
-
function
|
|
31
|
+
function I(a, e = 100) {
|
|
30
32
|
let t;
|
|
31
|
-
return (...
|
|
32
|
-
clearTimeout(t), t = setTimeout(() =>
|
|
33
|
+
return (...r) => {
|
|
34
|
+
clearTimeout(t), t = setTimeout(() => a(...r), e);
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
|
-
function
|
|
36
|
-
return
|
|
37
|
-
o:
|
|
38
|
-
h:
|
|
39
|
-
gppu:
|
|
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(
|
|
44
|
+
function w(a, e) {
|
|
43
45
|
const t = [];
|
|
44
|
-
let
|
|
45
|
-
for (;
|
|
46
|
-
(!e ||
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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,
|
|
66
|
-
|
|
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),
|
|
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
|
|
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
|
|
103
|
-
if (
|
|
104
|
-
if (
|
|
105
|
-
i.id || (i.id = "moT" + crypto.randomUUID() + "_" + (
|
|
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
|
-
|
|
109
|
-
!
|
|
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
|
-
|
|
113
|
-
t.showBadge && this._createBadge(i,
|
|
114
|
-
}), t.lastItemIdx = o.length,
|
|
115
|
-
i.querySelectorAll("img").forEach((
|
|
116
|
-
|
|
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
|
|
134
|
-
let d = 0,
|
|
135
|
-
if (
|
|
136
|
-
i.gppu >
|
|
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(
|
|
172
|
+
u && Math.abs(l.o - f(u).o) < 40 && (h = 1);
|
|
139
173
|
}
|
|
140
|
-
return { lr: d, badge_offset:
|
|
174
|
+
return { lr: d, badge_offset: h };
|
|
141
175
|
}
|
|
142
176
|
_createBadge(e, t) {
|
|
143
|
-
const
|
|
144
|
-
if (o.className = "mo-badge js-mo-badge",
|
|
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 (
|
|
147
|
-
const
|
|
148
|
-
|
|
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
|
|
157
|
-
if (
|
|
158
|
-
const
|
|
159
|
-
|
|
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 = "",
|
|
162
|
-
const
|
|
163
|
-
|
|
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
|
-
|
|
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
|
|
170
|
-
|
|
203
|
+
const s = document.createElement("h3");
|
|
204
|
+
s.textContent = e.title, o.appendChild(s);
|
|
171
205
|
}
|
|
172
206
|
if (e.meta) {
|
|
173
|
-
const
|
|
174
|
-
|
|
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
|
|
178
|
-
|
|
211
|
+
const s = document.createElement("p");
|
|
212
|
+
s.textContent = e.text, o.appendChild(s);
|
|
179
213
|
}
|
|
180
|
-
return
|
|
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;
|
package/dist/moTimeline.umd.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
(function(l,
|
|
2
|
-
* moTimeline v2.
|
|
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
|
|
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
package/src/moTimeline.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* moTimeline v2.
|
|
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.
|
|
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;
|