motimeline 2.4.0 → 2.6.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
@@ -4,6 +4,7 @@ Responsive two-column timeline layout library — plain JavaScript, zero depende
4
4
 
5
5
  **[Live demo & docs → mattopen.github.io/moTimeline](https://mattopen.github.io/moTimeline/)**
6
6
 
7
+ [![npm](https://img.shields.io/npm/v/motimeline.svg)](https://www.npmjs.com/package/motimeline)
7
8
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
9
 
9
10
  [![Preview](images/preview1.PNG)](https://mattopen.github.io/moTimeline/)
@@ -66,8 +67,8 @@ import 'motimeline/dist/moTimeline.css';
66
67
  import MoTimeline from 'motimeline';
67
68
 
68
69
  const tl = new MoTimeline('#my-timeline', {
69
- badgeShow: true,
70
- arrowShow: true,
70
+ showBadge: true,
71
+ showArrow: true,
71
72
  theme: true,
72
73
  });
73
74
  </script>
@@ -97,12 +98,42 @@ import 'motimeline/dist/moTimeline.css';
97
98
 
98
99
  | Option | Type | Default | Description |
99
100
  |---|---|---|---|
100
- | `columnCount` | object | `{xs:1, sm:2, md:2, lg:2}` | Columns at each breakpoint (xs <600 px, sm <992 px, md <1200 px, lg ≥1200 px) |
101
- | `badgeShow` | boolean | `false` | Show numbered badges on the center line |
102
- | `arrowShow` | boolean | `false` | Show triangle arrows pointing from each card toward the center line |
103
- | `theme` | boolean | `false` | Enable the built-in card theme (banners, avatars, styled badges) |
104
- | `showCounter` | boolean | `true` | Render the badge number. `false` keeps the badge for layout but sets it transparent |
105
- | `showCounterStyle` | string | `'counter'` | `'counter'` shows the item number; `'image'` shows the icon from `data-mo-icon` on the `<li>`, or a built-in SVG fallback |
101
+ | `columnCount` | object | `{xs:1, sm:2, md:2, lg:2}` | Columns at each responsive breakpoint: `xs` < 600 px · `sm` < 992 px · `md` < 1 200 px · `lg`1 200 px. Set any key to `1` to force single-column at that width. The center line, badges, and arrows are only visible in two-column mode. |
102
+ | `showBadge` | boolean | `false` | Render a circular badge on the center line for every item, numbered sequentially. Badges are automatically hidden when single-column mode is active. |
103
+ | `showArrow` | boolean | `false` | Render a triangle arrow pointing from each card toward the center line. Automatically hidden in single-column mode. |
104
+ | `theme` | boolean | `false` | Enable the built-in card theme: white cards with drop shadow, full-width image banners (160 px), overlapping circular avatars, and styled badges. Adds `mo-theme` to the container — can also be set manually in HTML. |
105
+ | `showCounterStyle` | string | `'counter'` | `'counter'` sequential item number (1, 2, 3…). `'image'` image from `data-mo-icon` on the `<li>`; falls back to a built-in flat SVG dot if the attribute is absent. `'none'` — badge element is created (preserving center-line spacing) but rendered with `opacity: 0`. |
106
+
107
+ ---
108
+
109
+ ## Data attributes
110
+
111
+ | Attribute | Element | Description |
112
+ |---|---|---|
113
+ | `data-mo-icon` | `<li>` | URL of the image shown inside the badge when `showCounterStyle: 'image'`. Accepts any web-safe format including inline SVG data URIs. Falls back to a built-in SVG icon if absent. Also set automatically by `addItems()` when an `icon` field is provided. |
114
+
115
+ ---
116
+
117
+ ## CSS classes reference
118
+
119
+ | Class | Applied to | Description |
120
+ |---|---|---|
121
+ | `mo-timeline` | container `<ul>` | Core layout class. Added automatically on init; safe to add in HTML before init. |
122
+ | `mo-twocol` | container | Present when two-column mode is active. Triggers the center vertical line and badge/arrow positioning. |
123
+ | `mo-theme` | container | Activates the built-in card theme. Added by `theme: true` or set manually. |
124
+ | `mo-item` | `<li>` | Applied to every timeline item. Controls 50 % width and float direction. |
125
+ | `mo-inverted` | `<li>` | Added to right-column items. Flips float, badge, arrow, and avatar positions. |
126
+ | `mo-offset` | `<li>` | Added when a badge would overlap the previous badge — nudges badge and arrow down to avoid collision. |
127
+ | `mo-badge` | `<span>` | Badge circle on the center line. Style via CSS custom properties. |
128
+ | `mo-badge-icon` | `<img>` inside badge | Image inside the badge when `showCounterStyle: 'image'`. |
129
+ | `mo-arrow` | `<span>` | Triangle arrow pointing from the card toward the center line. |
130
+ | `mo-card` | `<div>` | Card wrapper. Shadow, border-radius, and margins when `mo-theme` is active. |
131
+ | `mo-card-image` | `<div>` | Optional image container inside a card. Required for the avatar-over-banner overlap. |
132
+ | `mo-banner` | `<img>` | Full-width banner image at the top of a themed card. |
133
+ | `mo-avatar` | `<img>` | Circular avatar overlapping the bottom of the banner. Mirrors position on right-column items. |
134
+ | `mo-card-body` | `<div>` | Text content area. Padding and typography when `mo-theme` is active. |
135
+ | `mo-meta` | `<p>` | Date / subtitle line inside a card body. Muted colour, smaller font. |
136
+ | `js-mo-item` · `js-mo-inverted` | `<li>` | JS-only selector mirrors of `mo-item` / `mo-inverted`. Use in your own JS queries to avoid coupling to styling class names. |
106
137
 
107
138
  ---
108
139
 
@@ -137,6 +168,152 @@ tl.addItems('[{"title":"From JSON","meta":"Today","text":"Parsed automatically."
137
168
 
138
169
  ---
139
170
 
171
+ ## React
172
+
173
+ moTimeline manipulates the DOM directly, so use a `useRef` + `useEffect` wrapper to bridge it with React's rendering. Save the snippet below as `Timeline.jsx`:
174
+
175
+ ```jsx
176
+ import { useEffect, useRef } from 'react';
177
+ import MoTimeline from 'motimeline';
178
+ import 'motimeline/dist/moTimeline.css';
179
+
180
+ /**
181
+ * items shape: [{ id, title, meta, text, banner, avatar, icon }]
182
+ * All item fields are optional except a stable `id` for React keys.
183
+ */
184
+ export default function Timeline({ items = [], options = {} }) {
185
+ const ulRef = useRef(null);
186
+ const tlRef = useRef(null);
187
+ const lenRef = useRef(0);
188
+
189
+ // Initialise once on mount
190
+ useEffect(() => {
191
+ tlRef.current = new MoTimeline(ulRef.current, options);
192
+ lenRef.current = items.length;
193
+ return () => tlRef.current?.destroy();
194
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
195
+
196
+ // When items array grows, pick up the new <li> elements React just rendered
197
+ useEffect(() => {
198
+ if (!tlRef.current) return;
199
+ if (items.length > lenRef.current) {
200
+ tlRef.current.initNewItems();
201
+ } else {
202
+ // Items removed or reordered — full reinit
203
+ tlRef.current.destroy();
204
+ tlRef.current = new MoTimeline(ulRef.current, options);
205
+ }
206
+ lenRef.current = items.length;
207
+ }, [items]); // eslint-disable-line react-hooks/exhaustive-deps
208
+
209
+ return (
210
+ <ul ref={ulRef}>
211
+ {items.map((item) => (
212
+ <li key={item.id} {...(item.icon && { 'data-mo-icon': item.icon })}>
213
+ <div className="mo-card">
214
+ {item.banner && (
215
+ <div className="mo-card-image">
216
+ <img className="mo-banner" src={item.banner} alt="" />
217
+ {item.avatar && <img className="mo-avatar" src={item.avatar} alt="" />}
218
+ </div>
219
+ )}
220
+ <div className="mo-card-body">
221
+ {item.title && <h3>{item.title}</h3>}
222
+ {item.meta && <p className="mo-meta">{item.meta}</p>}
223
+ {item.text && <p>{item.text}</p>}
224
+ </div>
225
+ </div>
226
+ </li>
227
+ ))}
228
+ </ul>
229
+ );
230
+ }
231
+ ```
232
+
233
+ ### Usage
234
+
235
+ ```jsx
236
+ import { useState } from 'react';
237
+ import Timeline from './Timeline';
238
+
239
+ export default function App() {
240
+ const [items, setItems] = useState([
241
+ { id: '1', title: 'Project kickoff', meta: 'Jan 2024', text: 'Team aligned on goals.' },
242
+ { id: '2', title: 'Design system', meta: 'Feb 2024', text: 'Component library shipped.',
243
+ banner: 'banner.jpg', avatar: 'avatar.jpg' },
244
+ ]);
245
+
246
+ const addItem = () =>
247
+ setItems(prev => [...prev, {
248
+ id: String(Date.now()),
249
+ title: 'New event',
250
+ meta: 'Just now',
251
+ text: 'Added dynamically from React state.',
252
+ }]);
253
+
254
+ return (
255
+ <>
256
+ <Timeline
257
+ items={items}
258
+ options={{ showBadge: true, showArrow: true, theme: true }}
259
+ />
260
+ <button onClick={addItem}>Add item</button>
261
+ </>
262
+ );
263
+ }
264
+ ```
265
+
266
+ > **How it works:** React renders the `<li>` elements. moTimeline initialises once on mount and reads the DOM. When the `items` array grows, `initNewItems()` picks up the new `<li>` nodes React just appended. When items are removed or reordered React re-renders the list and the instance is fully reinitialised.
267
+
268
+ ---
269
+
270
+ ## Infinite scroll recipe
271
+
272
+ moTimeline handles the layout — you own the data fetching. Wire an `IntersectionObserver` to a sentinel element below the list and call `addItems()` when it comes into view.
273
+
274
+ ```html
275
+ <!-- Place a sentinel element right after the <ul> -->
276
+ <ul id="my-timeline"></ul>
277
+ <div id="sentinel"></div>
278
+ ```
279
+
280
+ ```js
281
+ const tl = new MoTimeline('#my-timeline', { theme: true, showBadge: true });
282
+ const sentinel = document.getElementById('sentinel');
283
+ let loading = false;
284
+ let page = 1;
285
+ let exhausted = false;
286
+
287
+ const observer = new IntersectionObserver(async (entries) => {
288
+ if (!entries[0].isIntersecting || loading || exhausted) return;
289
+
290
+ loading = true;
291
+ const items = await fetchPage(page); // your own async data fetch
292
+
293
+ if (items.length === 0) {
294
+ exhausted = true;
295
+ observer.disconnect();
296
+ } else {
297
+ tl.addItems(items); // moTimeline creates <li> and lays out
298
+ page++;
299
+ }
300
+ loading = false;
301
+ });
302
+
303
+ observer.observe(sentinel);
304
+
305
+ // Example fetch — replace with your real API call
306
+ async function fetchPage(page) {
307
+ const res = await fetch(`/api/events?page=${page}`);
308
+ const data = await res.json();
309
+ return data.items; // [{ title, meta, text, banner, avatar }, …]
310
+ }
311
+ ```
312
+
313
+ > `IntersectionObserver` is supported in all modern browsers with no polyfill needed. The `loading` flag prevents duplicate requests if the sentinel stays visible while a fetch is in flight. Set `exhausted = true` and disconnect when your API returns an empty page.
314
+
315
+ ---
316
+
140
317
  ## CSS custom properties
141
318
 
142
319
  ```css
@@ -176,12 +353,19 @@ No framework option needed. Wrap the `<ul>` inside a Bootstrap `.container`:
176
353
 
177
354
  ## Changelog
178
355
 
356
+ ### v2.6.0
357
+ - **Breaking:** `badgeShow` renamed to `showBadge`; `arrowShow` renamed to `showArrow` — consistent `show*` naming alongside `showCounterStyle`
358
+
359
+ ### v2.5.0
360
+ - **Breaking:** `showCounter` (boolean) removed — replaced by `showCounterStyle: 'none'`, which preserves center-line spacing with an invisible badge
361
+ - `showCounterStyle` now accepts three values: `'counter'` · `'image'` · `'none'`
362
+
179
363
  ### v2.4.0
180
364
  - Added `addItems(items)` — creates and appends `<li>` elements from an array of item objects or a JSON string, then initializes them in one batch
181
365
  - Badges and arrows now hidden in single-column mode (center-line elements have no meaning without a center line)
182
366
 
183
367
  ### v2.3.0
184
- - Added `showCounter` (opacity toggle) and `showCounterStyle` (`'counter'` | `'image'`) badge options
368
+ - Added `showCounterStyle` (`'counter'` | `'image'`) and `showCounter` opacity toggle (consolidated into `showCounterStyle: 'none'` in v2.5.0)
185
369
  - `data-mo-icon` attribute on `<li>` sets a custom icon in image mode; built-in flat SVG used as fallback
186
370
 
187
371
  ### v2.2.0
@@ -1,6 +1,6 @@
1
- "use strict";var _=Object.defineProperty;var b=(o,e,t)=>e in o?_(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var p=(o,e,t)=>b(o,typeof e!="symbol"?e+"":e,t);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});/*!
2
- * moTimeline v2.4.0
1
+ "use strict";var _=Object.defineProperty;var C=(o,e,t)=>e in o?_(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var p=(o,e,t)=>C(o,typeof e!="symbol"?e+"":e,t);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});/*!
2
+ * moTimeline v2.6.0
3
3
  * Responsive two-column timeline layout library
4
4
  * https://github.com/MattOpen/moTimeline
5
5
  * MIT License
6
- */const c=new WeakMap,w={columnCount:{xs:1,sm:2,md:2,lg:2},badgeShow:!1,arrowShow:!1,theme:!1,showCounter:!0,showCounterStyle:"counter"},C="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 o=window.innerWidth;return o<600?"xs":o<992?"sm":o<1200?"md":"lg"}function I(o,e=100){let t;return(...s)=>{clearTimeout(t),t=setTimeout(()=>o(...s),e)}}function m(o){return o?{o:o.offsetTop,h:o.offsetHeight,gppu:o.offsetTop+o.offsetHeight}:{o:0,h:0,gppu:0}}function E(o,e){const t=[];let s=o.previousElementSibling;for(;s;)(!e||s.matches(e))&&t.push(s),s=s.previousElementSibling;return t}const l=class l{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({},w,t),this.settings.columnCount=Object.assign({},w.columnCount,t.columnCount),this._resizeHandler=I(()=>this.refresh(),100),this._initialized=!1,this.init()}init(){const e=this.element;if(c.has(e)){this.refresh();return}const t=Object.assign({},this.settings,{lastItemIdx:0});c.set(e,t),l.instances.add(this),e.classList.add("mo-timeline"),t.theme&&e.classList.add("mo-theme"),Array.from(e.children).length!==0&&(this._initItems(),this._initialized=!0,window.addEventListener("resize",this._resizeHandler))}refresh(){l.instances.forEach(e=>{const t=e.element,s=c.get(t);s&&(s.col=s.columnCount[v()],e._setDivider(),Array.from(t.children).forEach(i=>{e._setPostPosition(i)}))})}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),c.delete(this.element),l.instances.delete(this),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"),e.querySelectorAll(".js-mo-badge, .js-mo-arrow").forEach(t=>t.remove())})}_getData(){return c.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 s=t.lastItemIdx,i=Array.from(e.children),n=i.slice(s);n.length!==0&&(n.forEach((r,a)=>{r.id||(r.id="moT"+crypto.randomUUID()+"_"+(a+s)),r.classList.add("mo-item","js-mo-item")}),this._setDivider(),n.forEach((r,a)=>{t.badgeShow&&this._createBadge(r,a+s+1),t.arrowShow&&this._createArrow(r)}),t.lastItemIdx=i.length,c.set(e,t),this.refresh())}_setPostPosition(e){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,i=E(e,".js-mo-inverted")[0]||null,n=E(e,".js-mo-item:not(.js-mo-inverted)")[0]||null,r=m(n),a=m(i),u=m(e);let h=0,f=0;if(s>1){r.gppu>u.o&&(h=1),a.gppu>r.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(),i=document.createElement("span");if(i.className="mo-badge js-mo-badge",s.showCounter||(i.style.opacity="0"),s.showCounterStyle==="image"){const n=document.createElement("img");n.className="mo-badge-icon",n.alt="",n.src=e.dataset.moIcon||C,i.appendChild(n)}else i.textContent=t;e.prepend(i)}_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 n=document.createElement("div");n.className="mo-card-image";const r=document.createElement("img");if(r.className="mo-banner",r.src=e.banner,r.alt="",n.appendChild(r),e.avatar){const a=document.createElement("img");a.className="mo-avatar",a.src=e.avatar,a.alt="",n.appendChild(a)}s.appendChild(n)}const i=document.createElement("div");if(i.className="mo-card-body",e.title){const n=document.createElement("h3");n.textContent=e.title,i.appendChild(n)}if(e.meta){const n=document.createElement("p");n.className="mo-meta",n.textContent=e.meta,i.appendChild(n)}if(e.text){const n=document.createElement("p");n.textContent=e.text,i.appendChild(n)}return s.appendChild(i),t.appendChild(s),t}_createArrow(e){const t=document.createElement("span");t.className="mo-arrow js-mo-arrow",e.prepend(t)}};p(l,"instances",new Set);let d=l;exports.MoTimeline=d;exports.default=d;
6
+ */const c=new WeakMap,v={columnCount:{xs:1,sm:2,md:2,lg:2},showBadge:!1,showArrow:!1,theme:!1,showCounterStyle:"counter"},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 o=window.innerWidth;return o<600?"xs":o<992?"sm":o<1200?"md":"lg"}function I(o,e=100){let t;return(...s)=>{clearTimeout(t),t=setTimeout(()=>o(...s),e)}}function m(o){return o?{o:o.offsetTop,h:o.offsetHeight,gppu:o.offsetTop+o.offsetHeight}:{o:0,h:0,gppu:0}}function E(o,e){const t=[];let s=o.previousElementSibling;for(;s;)(!e||s.matches(e))&&t.push(s),s=s.previousElementSibling;return t}const l=class l{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(c.has(e)){this.refresh();return}const t=Object.assign({},this.settings,{lastItemIdx:0});c.set(e,t),l.instances.add(this),e.classList.add("mo-timeline"),t.theme&&e.classList.add("mo-theme"),Array.from(e.children).length!==0&&(this._initItems(),this._initialized=!0,window.addEventListener("resize",this._resizeHandler))}refresh(){l.instances.forEach(e=>{const t=e.element,s=c.get(t);s&&(s.col=s.columnCount[w()],e._setDivider(),Array.from(t.children).forEach(i=>{e._setPostPosition(i)}))})}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),c.delete(this.element),l.instances.delete(this),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"),e.querySelectorAll(".js-mo-badge, .js-mo-arrow").forEach(t=>t.remove())})}_getData(){return c.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,i=Array.from(e.children),n=i.slice(s);n.length!==0&&(n.forEach((r,a)=>{r.id||(r.id="moT"+crypto.randomUUID()+"_"+(a+s)),r.classList.add("mo-item","js-mo-item")}),this._setDivider(),n.forEach((r,a)=>{t.showBadge&&this._createBadge(r,a+s+1),t.showArrow&&this._createArrow(r)}),t.lastItemIdx=i.length,c.set(e,t),this.refresh())}_setPostPosition(e){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,i=E(e,".js-mo-inverted")[0]||null,n=E(e,".js-mo-item:not(.js-mo-inverted)")[0]||null,r=m(n),a=m(i),f=m(e);let h=0,u=0;if(s>1){r.gppu>f.o&&(h=1),a.gppu>r.gppu&&(h=0);const g=e.previousElementSibling;g&&Math.abs(f.o-m(g).o)<40&&(u=1)}return{lr:h,badge_offset:u}}_createBadge(e,t){const s=this._getData(),i=document.createElement("span");if(i.className="mo-badge js-mo-badge",s.showCounterStyle==="none")i.style.opacity="0";else if(s.showCounterStyle==="image"){const n=document.createElement("img");n.className="mo-badge-icon",n.alt="",n.src=e.dataset.moIcon||b,i.appendChild(n)}else i.textContent=t;e.prepend(i)}_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 n=document.createElement("div");n.className="mo-card-image";const r=document.createElement("img");if(r.className="mo-banner",r.src=e.banner,r.alt="",n.appendChild(r),e.avatar){const a=document.createElement("img");a.className="mo-avatar",a.src=e.avatar,a.alt="",n.appendChild(a)}s.appendChild(n)}const i=document.createElement("div");if(i.className="mo-card-body",e.title){const n=document.createElement("h3");n.textContent=e.title,i.appendChild(n)}if(e.meta){const n=document.createElement("p");n.className="mo-meta",n.textContent=e.meta,i.appendChild(n)}if(e.text){const n=document.createElement("p");n.textContent=e.text,i.appendChild(n)}return s.appendChild(i),t.appendChild(s),t}_createArrow(e){const t=document.createElement("span");t.className="mo-arrow js-mo-arrow",e.prepend(t)}};p(l,"instances",new Set);let d=l;exports.MoTimeline=d;exports.default=d;
@@ -2,19 +2,18 @@ var _ = Object.defineProperty;
2
2
  var C = (o, e, t) => e in o ? _(o, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : o[e] = t;
3
3
  var g = (o, e, t) => C(o, typeof e != "symbol" ? e + "" : e, t);
4
4
  /*!
5
- * moTimeline v2.4.0
5
+ * moTimeline v2.6.0
6
6
  * Responsive two-column timeline layout library
7
7
  * https://github.com/MattOpen/moTimeline
8
8
  * MIT License
9
9
  */
10
10
  const c = /* @__PURE__ */ new WeakMap(), p = {
11
11
  columnCount: { xs: 1, sm: 2, md: 2, lg: 2 },
12
- badgeShow: !1,
13
- arrowShow: !1,
12
+ showBadge: !1,
13
+ showArrow: !1,
14
14
  theme: !1,
15
- showCounter: !0,
16
15
  showCounterStyle: "counter"
17
- // 'counter' | 'image'
16
+ // 'counter' | 'image' | 'none'
18
17
  }, 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>";
19
18
  function w() {
20
19
  const o = window.innerWidth;
@@ -97,7 +96,7 @@ const l = class l {
97
96
  n.length !== 0 && (n.forEach((r, a) => {
98
97
  r.id || (r.id = "moT" + crypto.randomUUID() + "_" + (a + s)), r.classList.add("mo-item", "js-mo-item");
99
98
  }), this._setDivider(), n.forEach((r, a) => {
100
- t.badgeShow && this._createBadge(r, a + s + 1), t.arrowShow && this._createArrow(r);
99
+ t.showBadge && this._createBadge(r, a + s + 1), t.showArrow && this._createArrow(r);
101
100
  }), t.lastItemIdx = i.length, c.set(e, t), this.refresh());
102
101
  }
103
102
  _setPostPosition(e) {
@@ -119,7 +118,9 @@ const l = class l {
119
118
  }
120
119
  _createBadge(e, t) {
121
120
  const s = this._getData(), i = document.createElement("span");
122
- if (i.className = "mo-badge js-mo-badge", s.showCounter || (i.style.opacity = "0"), s.showCounterStyle === "image") {
121
+ if (i.className = "mo-badge js-mo-badge", s.showCounterStyle === "none")
122
+ i.style.opacity = "0";
123
+ else if (s.showCounterStyle === "image") {
123
124
  const n = document.createElement("img");
124
125
  n.className = "mo-badge-icon", n.alt = "", n.src = e.dataset.moIcon || b, i.appendChild(n);
125
126
  } else
@@ -1,6 +1,6 @@
1
- (function(a,o){typeof exports=="object"&&typeof module<"u"?o(exports):typeof define=="function"&&define.amd?define(["exports"],o):(a=typeof globalThis<"u"?globalThis:a||self,o(a.MoTimeline={}))})(this,function(a){"use strict";var I=Object.defineProperty;var y=(a,o,m)=>o in a?I(a,o,{enumerable:!0,configurable:!0,writable:!0,value:m}):a[o]=m;var _=(a,o,m)=>y(a,typeof o!="symbol"?o+"":o,m);/*!
2
- * moTimeline v2.4.0
1
+ (function(a,o){typeof exports=="object"&&typeof module<"u"?o(exports):typeof define=="function"&&define.amd?define(["exports"],o):(a=typeof globalThis<"u"?globalThis:a||self,o(a.MoTimeline={}))})(this,function(a){"use strict";var y=Object.defineProperty;var I=(a,o,m)=>o in a?y(a,o,{enumerable:!0,configurable:!0,writable:!0,value:m}):a[o]=m;var _=(a,o,m)=>I(a,typeof o!="symbol"?o+"":o,m);/*!
2
+ * moTimeline v2.6.0
3
3
  * Responsive two-column timeline layout library
4
4
  * https://github.com/MattOpen/moTimeline
5
5
  * MIT License
6
- */const o=new WeakMap,m={columnCount:{xs:1,sm:2,md:2,lg:2},badgeShow:!1,arrowShow:!1,theme:!1,showCounter:!0,showCounterStyle:"counter"},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 g(){const c=window.innerWidth;return c<600?"xs":c<992?"sm":c<1200?"md":"lg"}function C(c,e=100){let t;return(...n)=>{clearTimeout(t),t=setTimeout(()=>c(...n),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 n=c.previousElementSibling;for(;n;)(!e||n.matches(e))&&t.push(n),n=n.previousElementSibling;return t}const d=class d{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({},m,t),this.settings.columnCount=Object.assign({},m.columnCount,t.columnCount),this._resizeHandler=C(()=>this.refresh(),100),this._initialized=!1,this.init()}init(){const e=this.element;if(o.has(e)){this.refresh();return}const t=Object.assign({},this.settings,{lastItemIdx:0});o.set(e,t),d.instances.add(this),e.classList.add("mo-timeline"),t.theme&&e.classList.add("mo-theme"),Array.from(e.children).length!==0&&(this._initItems(),this._initialized=!0,window.addEventListener("resize",this._resizeHandler))}refresh(){d.instances.forEach(e=>{const t=e.element,n=o.get(t);n&&(n.col=n.columnCount[g()],e._setDivider(),Array.from(t.children).forEach(i=>{e._setPostPosition(i)}))})}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),o.delete(this.element),d.instances.delete(this),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"),e.querySelectorAll(".js-mo-badge, .js-mo-arrow").forEach(t=>t.remove())})}_getData(){return o.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 n=t.lastItemIdx,i=Array.from(e.children),s=i.slice(n);s.length!==0&&(s.forEach((r,l)=>{r.id||(r.id="moT"+crypto.randomUUID()+"_"+(l+n)),r.classList.add("mo-item","js-mo-item")}),this._setDivider(),s.forEach((r,l)=>{t.badgeShow&&this._createBadge(r,l+n+1),t.arrowShow&&this._createArrow(r)}),t.lastItemIdx=i.length,o.set(e,t),this.refresh())}_setPostPosition(e){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 n=t.col,i=p(e,".js-mo-inverted")[0]||null,s=p(e,".js-mo-item:not(.js-mo-inverted)")[0]||null,r=h(s),l=h(i),w=h(e);let u=0,v=0;if(n>1){r.gppu>w.o&&(u=1),l.gppu>r.gppu&&(u=0);const E=e.previousElementSibling;E&&Math.abs(w.o-h(E).o)<40&&(v=1)}return{lr:u,badge_offset:v}}_createBadge(e,t){const n=this._getData(),i=document.createElement("span");if(i.className="mo-badge js-mo-badge",n.showCounter||(i.style.opacity="0"),n.showCounterStyle==="image"){const s=document.createElement("img");s.className="mo-badge-icon",s.alt="",s.src=e.dataset.moIcon||b,i.appendChild(s)}else i.textContent=t;e.prepend(i)}_createItemElement(e){const t=document.createElement("li");e.icon&&(t.dataset.moIcon=e.icon);const n=document.createElement("div");if(n.className="mo-card",e.banner){const s=document.createElement("div");s.className="mo-card-image";const r=document.createElement("img");if(r.className="mo-banner",r.src=e.banner,r.alt="",s.appendChild(r),e.avatar){const l=document.createElement("img");l.className="mo-avatar",l.src=e.avatar,l.alt="",s.appendChild(l)}n.appendChild(s)}const i=document.createElement("div");if(i.className="mo-card-body",e.title){const s=document.createElement("h3");s.textContent=e.title,i.appendChild(s)}if(e.meta){const s=document.createElement("p");s.className="mo-meta",s.textContent=e.meta,i.appendChild(s)}if(e.text){const s=document.createElement("p");s.textContent=e.text,i.appendChild(s)}return n.appendChild(i),t.appendChild(n),t}_createArrow(e){const t=document.createElement("span");t.className="mo-arrow js-mo-arrow",e.prepend(t)}};_(d,"instances",new Set);let f=d;a.MoTimeline=f,a.default=f,Object.defineProperties(a,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
6
+ */const o=new WeakMap,m={columnCount:{xs:1,sm:2,md:2,lg:2},showBadge:!1,showArrow:!1,theme:!1,showCounterStyle:"counter"},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 g(){const c=window.innerWidth;return c<600?"xs":c<992?"sm":c<1200?"md":"lg"}function C(c,e=100){let t;return(...n)=>{clearTimeout(t),t=setTimeout(()=>c(...n),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 n=c.previousElementSibling;for(;n;)(!e||n.matches(e))&&t.push(n),n=n.previousElementSibling;return t}const d=class d{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({},m,t),this.settings.columnCount=Object.assign({},m.columnCount,t.columnCount),this._resizeHandler=C(()=>this.refresh(),100),this._initialized=!1,this.init()}init(){const e=this.element;if(o.has(e)){this.refresh();return}const t=Object.assign({},this.settings,{lastItemIdx:0});o.set(e,t),d.instances.add(this),e.classList.add("mo-timeline"),t.theme&&e.classList.add("mo-theme"),Array.from(e.children).length!==0&&(this._initItems(),this._initialized=!0,window.addEventListener("resize",this._resizeHandler))}refresh(){d.instances.forEach(e=>{const t=e.element,n=o.get(t);n&&(n.col=n.columnCount[g()],e._setDivider(),Array.from(t.children).forEach(i=>{e._setPostPosition(i)}))})}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),o.delete(this.element),d.instances.delete(this),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"),e.querySelectorAll(".js-mo-badge, .js-mo-arrow").forEach(t=>t.remove())})}_getData(){return o.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 n=t.lastItemIdx,i=Array.from(e.children),s=i.slice(n);s.length!==0&&(s.forEach((r,l)=>{r.id||(r.id="moT"+crypto.randomUUID()+"_"+(l+n)),r.classList.add("mo-item","js-mo-item")}),this._setDivider(),s.forEach((r,l)=>{t.showBadge&&this._createBadge(r,l+n+1),t.showArrow&&this._createArrow(r)}),t.lastItemIdx=i.length,o.set(e,t),this.refresh())}_setPostPosition(e){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 n=t.col,i=p(e,".js-mo-inverted")[0]||null,s=p(e,".js-mo-item:not(.js-mo-inverted)")[0]||null,r=h(s),l=h(i),v=h(e);let u=0,w=0;if(n>1){r.gppu>v.o&&(u=1),l.gppu>r.gppu&&(u=0);const E=e.previousElementSibling;E&&Math.abs(v.o-h(E).o)<40&&(w=1)}return{lr:u,badge_offset:w}}_createBadge(e,t){const n=this._getData(),i=document.createElement("span");if(i.className="mo-badge js-mo-badge",n.showCounterStyle==="none")i.style.opacity="0";else if(n.showCounterStyle==="image"){const s=document.createElement("img");s.className="mo-badge-icon",s.alt="",s.src=e.dataset.moIcon||b,i.appendChild(s)}else i.textContent=t;e.prepend(i)}_createItemElement(e){const t=document.createElement("li");e.icon&&(t.dataset.moIcon=e.icon);const n=document.createElement("div");if(n.className="mo-card",e.banner){const s=document.createElement("div");s.className="mo-card-image";const r=document.createElement("img");if(r.className="mo-banner",r.src=e.banner,r.alt="",s.appendChild(r),e.avatar){const l=document.createElement("img");l.className="mo-avatar",l.src=e.avatar,l.alt="",s.appendChild(l)}n.appendChild(s)}const i=document.createElement("div");if(i.className="mo-card-body",e.title){const s=document.createElement("h3");s.textContent=e.title,i.appendChild(s)}if(e.meta){const s=document.createElement("p");s.className="mo-meta",s.textContent=e.meta,i.appendChild(s)}if(e.text){const s=document.createElement("p");s.textContent=e.text,i.appendChild(s)}return n.appendChild(i),t.appendChild(n),t}_createArrow(e){const t=document.createElement("span");t.className="mo-arrow js-mo-arrow",e.prepend(t)}};_(d,"instances",new Set);let f=d;a.MoTimeline=f,a.default=f,Object.defineProperties(a,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "motimeline",
3
- "version": "2.4.0",
3
+ "version": "2.6.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",
package/src/moTimeline.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * moTimeline v2.4.0
2
+ * moTimeline v2.6.0
3
3
  * Responsive two-column timeline layout library
4
4
  * https://github.com/MattOpen/moTimeline
5
5
  * MIT License
@@ -18,11 +18,10 @@ const BREAKPOINTS = {
18
18
 
19
19
  const DEFAULTS = {
20
20
  columnCount: { xs: 1, sm: 2, md: 2, lg: 2 },
21
- badgeShow: false,
22
- arrowShow: false,
21
+ showBadge: false,
22
+ showArrow: false,
23
23
  theme: false,
24
- showCounter: true,
25
- showCounterStyle: 'counter', // 'counter' | 'image'
24
+ showCounterStyle: 'counter', // 'counter' | 'image' | 'none'
26
25
  };
27
26
 
28
27
  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>";
@@ -186,10 +185,10 @@ export class MoTimeline {
186
185
 
187
186
  // Badges / arrows
188
187
  newItems.forEach((item, i) => {
189
- if (data.badgeShow) {
188
+ if (data.showBadge) {
190
189
  this._createBadge(item, i + lastItemIdx + 1);
191
190
  }
192
- if (data.arrowShow) {
191
+ if (data.showArrow) {
193
192
  this._createArrow(item);
194
193
  }
195
194
  });
@@ -246,11 +245,9 @@ export class MoTimeline {
246
245
  const span = document.createElement('span');
247
246
  span.className = 'mo-badge js-mo-badge';
248
247
 
249
- if (!data.showCounter) {
248
+ if (data.showCounterStyle === 'none') {
250
249
  span.style.opacity = '0';
251
- }
252
-
253
- if (data.showCounterStyle === 'image') {
250
+ } else if (data.showCounterStyle === 'image') {
254
251
  const img = document.createElement('img');
255
252
  img.className = 'mo-badge-icon';
256
253
  img.alt = '';