motimeline 2.3.0 → 2.5.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 +214 -10
- package/dist/moTimeline.cjs +3 -3
- package/dist/moTimeline.css +1 -1
- package/dist/moTimeline.js +115 -73
- package/dist/moTimeline.umd.js +3 -3
- package/package.json +1 -1
- package/src/moTimeline.css +2 -4
- package/src/moTimeline.js +67 -7
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/)
|
|
@@ -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
|
-
| `badgeShow` | boolean | `false` |
|
|
102
|
-
| `arrowShow` | boolean | `false` |
|
|
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
|
+
| `badgeShow` | 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
|
+
| `arrowShow` | 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
|
|
|
@@ -111,13 +142,178 @@ import 'motimeline/dist/moTimeline.css';
|
|
|
111
142
|
```js
|
|
112
143
|
const tl = new MoTimeline(elementOrSelector, options);
|
|
113
144
|
|
|
114
|
-
tl.refresh();
|
|
115
|
-
tl.initNewItems();
|
|
116
|
-
tl.
|
|
145
|
+
tl.refresh(); // re-layout all items (called automatically on resize)
|
|
146
|
+
tl.initNewItems(); // pick up manually appended <li> elements
|
|
147
|
+
tl.addItems(items); // create and append <li> from an array of item objects (or JSON string)
|
|
148
|
+
tl.destroy(); // remove listeners and reset DOM classes
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### addItems — item schema
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
tl.addItems([
|
|
155
|
+
{
|
|
156
|
+
title: "Project kickoff", // <h3> heading
|
|
157
|
+
meta: "January 2024", // date / subtitle line
|
|
158
|
+
text: "Kicked off the roadmap.", // body paragraph
|
|
159
|
+
banner: "images/banner.jpg", // img.mo-banner (optional)
|
|
160
|
+
avatar: "images/avatar.jpg", // img.mo-avatar (optional)
|
|
161
|
+
icon: "images/icon.svg" // data-mo-icon on <li>, used by showCounterStyle:'image'
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
// A JSON string is also accepted:
|
|
166
|
+
tl.addItems('[{"title":"From JSON","meta":"Today","text":"Parsed automatically."}]');
|
|
117
167
|
```
|
|
118
168
|
|
|
119
169
|
---
|
|
120
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={{ badgeShow: true, arrowShow: 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, badgeShow: 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
|
+
|
|
121
317
|
## CSS custom properties
|
|
122
318
|
|
|
123
319
|
```css
|
|
@@ -157,8 +353,16 @@ No framework option needed. Wrap the `<ul>` inside a Bootstrap `.container`:
|
|
|
157
353
|
|
|
158
354
|
## Changelog
|
|
159
355
|
|
|
356
|
+
### v2.5.0
|
|
357
|
+
- **Breaking:** `showCounter` (boolean) removed — replaced by `showCounterStyle: 'none'`, which preserves center-line spacing with an invisible badge
|
|
358
|
+
- `showCounterStyle` now accepts three values: `'counter'` · `'image'` · `'none'`
|
|
359
|
+
|
|
360
|
+
### v2.4.0
|
|
361
|
+
- Added `addItems(items)` — creates and appends `<li>` elements from an array of item objects or a JSON string, then initializes them in one batch
|
|
362
|
+
- Badges and arrows now hidden in single-column mode (center-line elements have no meaning without a center line)
|
|
363
|
+
|
|
160
364
|
### v2.3.0
|
|
161
|
-
- Added `
|
|
365
|
+
- Added `showCounterStyle` (`'counter'` | `'image'`) and `showCounter` opacity toggle (consolidated into `showCounterStyle: 'none'` in v2.5.0)
|
|
162
366
|
- `data-mo-icon` attribute on `<li>` sets a custom icon in image mode; built-in flat SVG used as fallback
|
|
163
367
|
|
|
164
368
|
### v2.2.0
|
package/dist/moTimeline.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
* moTimeline v2.
|
|
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.5.0
|
|
3
3
|
* Responsive two-column timeline layout library
|
|
4
4
|
* https://github.com/MattOpen/moTimeline
|
|
5
5
|
* MIT License
|
|
6
|
-
*/const
|
|
6
|
+
*/const c=new WeakMap,v={columnCount:{xs:1,sm:2,md:2,lg:2},badgeShow:!1,arrowShow:!1,theme:!1,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 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.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),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||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;
|
package/dist/moTimeline.css
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* moTimeline v2.3.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-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{
|
|
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-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-item .mo-card{background:#fff;border-radius:8px;box-shadow:0 2px 14px #0000001a;margin:.5rem 1.25rem .5rem .5rem;overflow:hidden;position:relative}.mo-theme>.mo-item.mo-inverted .mo-card{margin:.5rem .5rem .5rem 1.25rem}.mo-theme>.mo-item .mo-banner{display:block;height:160px;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:-22px;box-shadow:0 2px 8px #0000002e;height:50px;object-fit:cover;position:absolute;right:14px;width:50px;z-index:1}.mo-theme>.mo-item.mo-inverted .mo-avatar{left:14px;right:auto}.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:#e5e7eb;right:10px}.mo-theme.mo-twocol>.mo-item.mo-inverted .mo-arrow{border-right-color:#e5e7eb;left:10px}.mo-theme .mo-badge{background:#fff;border:2px solid var(--mo-line-color);box-shadow:0 2px 6px #0000001a;color:#374151}
|
package/dist/moTimeline.js
CHANGED
|
@@ -1,127 +1,169 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var g = (
|
|
1
|
+
var _ = Object.defineProperty;
|
|
2
|
+
var C = (o, e, t) => e in o ? _(o, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : o[e] = t;
|
|
3
|
+
var g = (o, e, t) => C(o, typeof e != "symbol" ? e + "" : e, t);
|
|
4
4
|
/*!
|
|
5
|
-
* moTimeline v2.
|
|
5
|
+
* moTimeline v2.5.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 c = /* @__PURE__ */ new WeakMap(), p = {
|
|
11
11
|
columnCount: { xs: 1, sm: 2, md: 2, lg: 2 },
|
|
12
12
|
badgeShow: !1,
|
|
13
13
|
arrowShow: !1,
|
|
14
14
|
theme: !1,
|
|
15
|
-
showCounter: !0,
|
|
16
15
|
showCounterStyle: "counter"
|
|
17
|
-
// 'counter' | 'image'
|
|
18
|
-
},
|
|
16
|
+
// 'counter' | 'image' | 'none'
|
|
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
|
-
const
|
|
21
|
-
return
|
|
19
|
+
const o = window.innerWidth;
|
|
20
|
+
return o < 600 ? "xs" : o < 992 ? "sm" : o < 1200 ? "md" : "lg";
|
|
22
21
|
}
|
|
23
|
-
function
|
|
24
|
-
let
|
|
22
|
+
function I(o, e = 100) {
|
|
23
|
+
let t;
|
|
25
24
|
return (...s) => {
|
|
26
|
-
clearTimeout(
|
|
25
|
+
clearTimeout(t), t = setTimeout(() => o(...s), e);
|
|
27
26
|
};
|
|
28
27
|
}
|
|
29
|
-
function m(
|
|
30
|
-
return
|
|
31
|
-
o:
|
|
32
|
-
h:
|
|
33
|
-
gppu:
|
|
28
|
+
function m(o) {
|
|
29
|
+
return o ? {
|
|
30
|
+
o: o.offsetTop,
|
|
31
|
+
h: o.offsetHeight,
|
|
32
|
+
gppu: o.offsetTop + o.offsetHeight
|
|
34
33
|
} : { o: 0, h: 0, gppu: 0 };
|
|
35
34
|
}
|
|
36
|
-
function v(
|
|
37
|
-
const
|
|
38
|
-
let s =
|
|
35
|
+
function v(o, e) {
|
|
36
|
+
const t = [];
|
|
37
|
+
let s = o.previousElementSibling;
|
|
39
38
|
for (; s; )
|
|
40
|
-
(!
|
|
41
|
-
return
|
|
39
|
+
(!e || s.matches(e)) && t.push(s), s = s.previousElementSibling;
|
|
40
|
+
return t;
|
|
42
41
|
}
|
|
43
|
-
const
|
|
44
|
-
constructor(
|
|
45
|
-
if (typeof
|
|
46
|
-
this.element =
|
|
42
|
+
const l = class l {
|
|
43
|
+
constructor(e, t = {}) {
|
|
44
|
+
if (typeof e == "string" && (e = document.querySelector(e)), !e) throw new Error("moTimeline: element not found");
|
|
45
|
+
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();
|
|
47
46
|
}
|
|
48
47
|
init() {
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
48
|
+
const e = this.element;
|
|
49
|
+
if (c.has(e)) {
|
|
51
50
|
this.refresh();
|
|
52
51
|
return;
|
|
53
52
|
}
|
|
54
|
-
const
|
|
55
|
-
|
|
53
|
+
const t = Object.assign({}, this.settings, { lastItemIdx: 0 });
|
|
54
|
+
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));
|
|
56
55
|
}
|
|
57
56
|
refresh() {
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
s && (s.col = s.columnCount[w()],
|
|
61
|
-
|
|
57
|
+
l.instances.forEach((e) => {
|
|
58
|
+
const t = e.element, s = c.get(t);
|
|
59
|
+
s && (s.col = s.columnCount[w()], e._setDivider(), Array.from(t.children).forEach((i) => {
|
|
60
|
+
e._setPostPosition(i);
|
|
62
61
|
}));
|
|
63
62
|
});
|
|
64
63
|
}
|
|
65
64
|
initNewItems() {
|
|
66
65
|
this._initItems();
|
|
67
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Create <li> elements from an array of item objects (or a JSON string) and
|
|
69
|
+
* append them to the timeline, then initialize them in one batch.
|
|
70
|
+
*
|
|
71
|
+
* Item shape:
|
|
72
|
+
* { title, meta, text, banner, avatar, icon }
|
|
73
|
+
* — banner / avatar / icon are all optional
|
|
74
|
+
* — icon sets data-mo-icon on the <li> for showCounterStyle:'image'
|
|
75
|
+
*/
|
|
76
|
+
addItems(e) {
|
|
77
|
+
typeof e == "string" && (e = JSON.parse(e)), e.forEach((t) => this.element.appendChild(this._createItemElement(t))), this._initItems();
|
|
78
|
+
}
|
|
68
79
|
destroy() {
|
|
69
|
-
window.removeEventListener("resize", this._resizeHandler),
|
|
70
|
-
|
|
80
|
+
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) => {
|
|
81
|
+
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());
|
|
71
82
|
});
|
|
72
83
|
}
|
|
73
84
|
// ─── Private ────────────────────────────────────────────────────────────────
|
|
74
85
|
_getData() {
|
|
75
|
-
return
|
|
86
|
+
return c.get(this.element);
|
|
76
87
|
}
|
|
77
88
|
_setDivider() {
|
|
78
|
-
const
|
|
79
|
-
|
|
89
|
+
const e = this._getData();
|
|
90
|
+
e && (e.col = e.columnCount[w()], this.element.classList.toggle("mo-twocol", e.col > 1));
|
|
80
91
|
}
|
|
81
92
|
_initItems() {
|
|
82
|
-
const
|
|
83
|
-
if (!
|
|
84
|
-
const s =
|
|
85
|
-
n.length !== 0 && (n.forEach((r,
|
|
86
|
-
r.id || (r.id = "moT" + crypto.randomUUID() + "_" + (
|
|
87
|
-
}), this._setDivider(), n.forEach((r,
|
|
88
|
-
|
|
89
|
-
}),
|
|
93
|
+
const e = this.element, t = this._getData();
|
|
94
|
+
if (!t) return;
|
|
95
|
+
const s = t.lastItemIdx, i = Array.from(e.children), n = i.slice(s);
|
|
96
|
+
n.length !== 0 && (n.forEach((r, a) => {
|
|
97
|
+
r.id || (r.id = "moT" + crypto.randomUUID() + "_" + (a + s)), r.classList.add("mo-item", "js-mo-item");
|
|
98
|
+
}), this._setDivider(), n.forEach((r, a) => {
|
|
99
|
+
t.badgeShow && this._createBadge(r, a + s + 1), t.arrowShow && this._createArrow(r);
|
|
100
|
+
}), t.lastItemIdx = i.length, c.set(e, t), this.refresh());
|
|
90
101
|
}
|
|
91
|
-
_setPostPosition(
|
|
92
|
-
const
|
|
93
|
-
|
|
102
|
+
_setPostPosition(e) {
|
|
103
|
+
const t = this._getLeftOrRight(e);
|
|
104
|
+
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));
|
|
94
105
|
}
|
|
95
|
-
_getLeftOrRight(
|
|
96
|
-
if (!t) return null;
|
|
97
|
-
const e = this._getData();
|
|
106
|
+
_getLeftOrRight(e) {
|
|
98
107
|
if (!e) return null;
|
|
99
|
-
const
|
|
100
|
-
|
|
108
|
+
const t = this._getData();
|
|
109
|
+
if (!t) return null;
|
|
110
|
+
const s = t.col, i = v(e, ".js-mo-inverted")[0] || null, n = v(e, ".js-mo-item:not(.js-mo-inverted)")[0] || null, r = m(n), a = m(i), h = m(e);
|
|
111
|
+
let d = 0, f = 0;
|
|
101
112
|
if (s > 1) {
|
|
102
|
-
r.gppu >
|
|
103
|
-
const u =
|
|
104
|
-
u && Math.abs(
|
|
113
|
+
r.gppu > h.o && (d = 1), a.gppu > r.gppu && (d = 0);
|
|
114
|
+
const u = e.previousElementSibling;
|
|
115
|
+
u && Math.abs(h.o - m(u).o) < 40 && (f = 1);
|
|
105
116
|
}
|
|
106
|
-
return { lr:
|
|
117
|
+
return { lr: d, badge_offset: f };
|
|
107
118
|
}
|
|
108
|
-
_createBadge(
|
|
109
|
-
const s = this._getData(),
|
|
110
|
-
if (
|
|
119
|
+
_createBadge(e, t) {
|
|
120
|
+
const s = this._getData(), i = document.createElement("span");
|
|
121
|
+
if (i.className = "mo-badge js-mo-badge", s.showCounterStyle === "none")
|
|
122
|
+
i.style.opacity = "0";
|
|
123
|
+
else if (s.showCounterStyle === "image") {
|
|
111
124
|
const n = document.createElement("img");
|
|
112
|
-
n.className = "mo-badge-icon", n.alt = "", n.src =
|
|
125
|
+
n.className = "mo-badge-icon", n.alt = "", n.src = e.dataset.moIcon || b, i.appendChild(n);
|
|
113
126
|
} else
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
i.textContent = t;
|
|
128
|
+
e.prepend(i);
|
|
129
|
+
}
|
|
130
|
+
_createItemElement(e) {
|
|
131
|
+
const t = document.createElement("li");
|
|
132
|
+
e.icon && (t.dataset.moIcon = e.icon);
|
|
133
|
+
const s = document.createElement("div");
|
|
134
|
+
if (s.className = "mo-card", e.banner) {
|
|
135
|
+
const n = document.createElement("div");
|
|
136
|
+
n.className = "mo-card-image";
|
|
137
|
+
const r = document.createElement("img");
|
|
138
|
+
if (r.className = "mo-banner", r.src = e.banner, r.alt = "", n.appendChild(r), e.avatar) {
|
|
139
|
+
const a = document.createElement("img");
|
|
140
|
+
a.className = "mo-avatar", a.src = e.avatar, a.alt = "", n.appendChild(a);
|
|
141
|
+
}
|
|
142
|
+
s.appendChild(n);
|
|
143
|
+
}
|
|
144
|
+
const i = document.createElement("div");
|
|
145
|
+
if (i.className = "mo-card-body", e.title) {
|
|
146
|
+
const n = document.createElement("h3");
|
|
147
|
+
n.textContent = e.title, i.appendChild(n);
|
|
148
|
+
}
|
|
149
|
+
if (e.meta) {
|
|
150
|
+
const n = document.createElement("p");
|
|
151
|
+
n.className = "mo-meta", n.textContent = e.meta, i.appendChild(n);
|
|
152
|
+
}
|
|
153
|
+
if (e.text) {
|
|
154
|
+
const n = document.createElement("p");
|
|
155
|
+
n.textContent = e.text, i.appendChild(n);
|
|
156
|
+
}
|
|
157
|
+
return s.appendChild(i), t.appendChild(s), t;
|
|
116
158
|
}
|
|
117
|
-
_createArrow(
|
|
118
|
-
const
|
|
119
|
-
|
|
159
|
+
_createArrow(e) {
|
|
160
|
+
const t = document.createElement("span");
|
|
161
|
+
t.className = "mo-arrow js-mo-arrow", e.prepend(t);
|
|
120
162
|
}
|
|
121
163
|
};
|
|
122
|
-
g(
|
|
123
|
-
let
|
|
164
|
+
g(l, "instances", /* @__PURE__ */ new Set());
|
|
165
|
+
let E = l;
|
|
124
166
|
export {
|
|
125
|
-
|
|
126
|
-
|
|
167
|
+
E as MoTimeline,
|
|
168
|
+
E as default
|
|
127
169
|
};
|
package/dist/moTimeline.umd.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
(function(
|
|
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.5.0
|
|
3
3
|
* Responsive two-column timeline layout library
|
|
4
4
|
* https://github.com/MattOpen/moTimeline
|
|
5
5
|
* MIT License
|
|
6
|
-
*/const
|
|
6
|
+
*/const o=new WeakMap,m={columnCount:{xs:1,sm:2,md:2,lg:2},badgeShow:!1,arrowShow:!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.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),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.css
CHANGED
|
@@ -113,11 +113,9 @@
|
|
|
113
113
|
top: calc(26px + var(--mo-badge-size) + 10px);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
/* Single column: badge
|
|
116
|
+
/* Single column: hide badge (center-line elements have no meaning without a center line) */
|
|
117
117
|
.mo-timeline:not(.mo-twocol) > .mo-item .mo-badge {
|
|
118
|
-
|
|
119
|
-
left: auto;
|
|
120
|
-
top: 12px;
|
|
118
|
+
display: none;
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
/* ── Arrow — triangle pointing FROM card TOWARD center line ─── */
|
package/src/moTimeline.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* moTimeline v2.
|
|
2
|
+
* moTimeline v2.5.0
|
|
3
3
|
* Responsive two-column timeline layout library
|
|
4
4
|
* https://github.com/MattOpen/moTimeline
|
|
5
5
|
* MIT License
|
|
@@ -21,8 +21,7 @@ const DEFAULTS = {
|
|
|
21
21
|
badgeShow: false,
|
|
22
22
|
arrowShow: 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>";
|
|
@@ -124,6 +123,21 @@ export class MoTimeline {
|
|
|
124
123
|
this._initItems();
|
|
125
124
|
}
|
|
126
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Create <li> elements from an array of item objects (or a JSON string) and
|
|
128
|
+
* append them to the timeline, then initialize them in one batch.
|
|
129
|
+
*
|
|
130
|
+
* Item shape:
|
|
131
|
+
* { title, meta, text, banner, avatar, icon }
|
|
132
|
+
* — banner / avatar / icon are all optional
|
|
133
|
+
* — icon sets data-mo-icon on the <li> for showCounterStyle:'image'
|
|
134
|
+
*/
|
|
135
|
+
addItems(items) {
|
|
136
|
+
if (typeof items === 'string') items = JSON.parse(items);
|
|
137
|
+
items.forEach((item) => this.element.appendChild(this._createItemElement(item)));
|
|
138
|
+
this._initItems();
|
|
139
|
+
}
|
|
140
|
+
|
|
127
141
|
destroy() {
|
|
128
142
|
window.removeEventListener('resize', this._resizeHandler);
|
|
129
143
|
instanceData.delete(this.element);
|
|
@@ -231,11 +245,9 @@ export class MoTimeline {
|
|
|
231
245
|
const span = document.createElement('span');
|
|
232
246
|
span.className = 'mo-badge js-mo-badge';
|
|
233
247
|
|
|
234
|
-
if (
|
|
248
|
+
if (data.showCounterStyle === 'none') {
|
|
235
249
|
span.style.opacity = '0';
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (data.showCounterStyle === 'image') {
|
|
250
|
+
} else if (data.showCounterStyle === 'image') {
|
|
239
251
|
const img = document.createElement('img');
|
|
240
252
|
img.className = 'mo-badge-icon';
|
|
241
253
|
img.alt = '';
|
|
@@ -248,6 +260,54 @@ export class MoTimeline {
|
|
|
248
260
|
el.prepend(span);
|
|
249
261
|
}
|
|
250
262
|
|
|
263
|
+
_createItemElement(item) {
|
|
264
|
+
const li = document.createElement('li');
|
|
265
|
+
if (item.icon) li.dataset.moIcon = item.icon;
|
|
266
|
+
|
|
267
|
+
const card = document.createElement('div');
|
|
268
|
+
card.className = 'mo-card';
|
|
269
|
+
|
|
270
|
+
if (item.banner) {
|
|
271
|
+
const wrap = document.createElement('div');
|
|
272
|
+
wrap.className = 'mo-card-image';
|
|
273
|
+
const banner = document.createElement('img');
|
|
274
|
+
banner.className = 'mo-banner';
|
|
275
|
+
banner.src = item.banner;
|
|
276
|
+
banner.alt = '';
|
|
277
|
+
wrap.appendChild(banner);
|
|
278
|
+
if (item.avatar) {
|
|
279
|
+
const avatar = document.createElement('img');
|
|
280
|
+
avatar.className = 'mo-avatar';
|
|
281
|
+
avatar.src = item.avatar;
|
|
282
|
+
avatar.alt = '';
|
|
283
|
+
wrap.appendChild(avatar);
|
|
284
|
+
}
|
|
285
|
+
card.appendChild(wrap);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const body = document.createElement('div');
|
|
289
|
+
body.className = 'mo-card-body';
|
|
290
|
+
if (item.title) {
|
|
291
|
+
const h = document.createElement('h3');
|
|
292
|
+
h.textContent = item.title;
|
|
293
|
+
body.appendChild(h);
|
|
294
|
+
}
|
|
295
|
+
if (item.meta) {
|
|
296
|
+
const m = document.createElement('p');
|
|
297
|
+
m.className = 'mo-meta';
|
|
298
|
+
m.textContent = item.meta;
|
|
299
|
+
body.appendChild(m);
|
|
300
|
+
}
|
|
301
|
+
if (item.text) {
|
|
302
|
+
const p = document.createElement('p');
|
|
303
|
+
p.textContent = item.text;
|
|
304
|
+
body.appendChild(p);
|
|
305
|
+
}
|
|
306
|
+
card.appendChild(body);
|
|
307
|
+
li.appendChild(card);
|
|
308
|
+
return li;
|
|
309
|
+
}
|
|
310
|
+
|
|
251
311
|
_createArrow(el) {
|
|
252
312
|
const span = document.createElement('span');
|
|
253
313
|
span.className = 'mo-arrow js-mo-arrow';
|