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 +193 -9
- package/dist/moTimeline.cjs +3 -3
- package/dist/moTimeline.js +8 -7
- package/dist/moTimeline.umd.js +3 -3
- package/package.json +1 -1
- package/src/moTimeline.js +8 -11
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
|
+
[](https://www.npmjs.com/package/motimeline)
|
|
7
8
|
[](LICENSE)
|
|
8
9
|
|
|
9
10
|
[](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
|
-
|
|
70
|
-
|
|
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
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `theme` | boolean | `false` | Enable the built-in card theme (
|
|
104
|
-
| `
|
|
105
|
-
|
|
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 `
|
|
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
|
package/dist/moTimeline.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";var _=Object.defineProperty;var
|
|
2
|
-
* moTimeline v2.
|
|
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,
|
|
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;
|
package/dist/moTimeline.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
13
|
-
|
|
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.
|
|
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.
|
|
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
|
package/dist/moTimeline.umd.js
CHANGED
|
@@ -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
|
|
2
|
-
* moTimeline v2.
|
|
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},
|
|
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
package/src/moTimeline.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* moTimeline v2.
|
|
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
|
-
|
|
22
|
-
|
|
21
|
+
showBadge: false,
|
|
22
|
+
showArrow: false,
|
|
23
23
|
theme: false,
|
|
24
|
-
|
|
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.
|
|
188
|
+
if (data.showBadge) {
|
|
190
189
|
this._createBadge(item, i + lastItemIdx + 1);
|
|
191
190
|
}
|
|
192
|
-
if (data.
|
|
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 (
|
|
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 = '';
|