@lisergia/core 23.0.1 → 25.0.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/dist/App.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ import { type IArrayDidChange, type IValueDidChange } from 'mobx';
2
+ import { Component, type ComponentParameters } from './Component.js';
3
+ import { Links } from './Links.js';
4
+ import type { Page, PageParameters } from './Page.js';
5
+ export interface ApplicationComponentData {
6
+ component: new (params?: ComponentParameters) => Component;
7
+ }
8
+ export interface ApplicationComponentDatasetData extends ApplicationComponentData {
9
+ selector: string;
10
+ }
11
+ export interface ApplicationRoute {
12
+ component: new (params?: PageParameters) => Page;
13
+ template: string;
14
+ }
15
+ export declare class ApplicationManager extends Component {
16
+ element: HTMLElement;
17
+ template: string;
18
+ constructor();
19
+ onTitleUpdate(): void;
20
+ onTemplateUpdate(): void;
21
+ canvas?: Component;
22
+ components: Array<Component>;
23
+ transition?: Component & {
24
+ onTransition?: (application: ApplicationManager) => void | Promise<void>;
25
+ };
26
+ initComponents(components: Array<ApplicationComponentData>): void;
27
+ addComponent(component: Component): void;
28
+ removeComponent(component: Component): void;
29
+ onComponentChange(_event: IArrayDidChange<Component>): void;
30
+ datasets: Array<ApplicationComponentDatasetData>;
31
+ initDatasets(datasets: Array<ApplicationComponentDatasetData>): void;
32
+ pages: Map<string, new (args: PageParameters) => Page>;
33
+ initRoutes(routes: Array<ApplicationRoute>): void;
34
+ initSprites(url?: string): Promise<void>;
35
+ IS_LINKS_ENABLED: boolean;
36
+ links: Links;
37
+ initPage(): void;
38
+ createLinks(): void;
39
+ currentPage?: Page;
40
+ createPage(template?: string): void;
41
+ destroyPage(): void;
42
+ route: string;
43
+ routeHistory: Array<string>;
44
+ routeRequestEnabled: boolean;
45
+ routePushState: boolean;
46
+ onRouteChange({ newValue }: IValueDidChange<string>): void;
47
+ onRouteChangeRequest({ href, pushState }: {
48
+ href: string;
49
+ pushState: boolean;
50
+ }): Promise<void>;
51
+ nextPage: {
52
+ element?: HTMLElement;
53
+ template?: string;
54
+ title?: string;
55
+ };
56
+ onRequest({ href, response, pushState }: {
57
+ href: string;
58
+ response: string;
59
+ pushState: boolean;
60
+ }): Promise<void>;
61
+ onPopState(): void;
62
+ get scroll(): number;
63
+ addEventListeners(): void;
64
+ removeEventListeners(): void;
65
+ }
66
+ export declare const Application: ApplicationManager;
package/dist/App.js ADDED
@@ -0,0 +1,251 @@
1
+ import { autorun, computed, makeObservable, observable, observe, } from 'mobx';
2
+ import { Component } from './Component.js';
3
+ import { Links } from './Links.js';
4
+ export class ApplicationManager extends Component {
5
+ template = document.documentElement.dataset.template ?? '404';
6
+ constructor() {
7
+ super({
8
+ autoListeners: false,
9
+ element: '.app',
10
+ });
11
+ makeObservable(this, {
12
+ // Application DOM Element.
13
+ element: observable,
14
+ // Components.
15
+ canvas: observable,
16
+ components: observable,
17
+ transition: observable,
18
+ // Page Information.
19
+ currentPage: observable,
20
+ nextPage: observable,
21
+ // Route Information.
22
+ route: observable,
23
+ routeHistory: observable,
24
+ // Template Information.
25
+ template: observable,
26
+ // Page Scroll.
27
+ scroll: computed,
28
+ });
29
+ observe(this.components, this.onComponentChange);
30
+ observe(this, 'route', this.onRouteChange);
31
+ autorun(this.onTitleUpdate);
32
+ autorun(this.onTemplateUpdate);
33
+ this.addEventListeners();
34
+ }
35
+ onTitleUpdate() {
36
+ const title = this.nextPage?.title;
37
+ if (title) {
38
+ document.title = title;
39
+ }
40
+ }
41
+ onTemplateUpdate() {
42
+ const template = this.nextPage?.template;
43
+ if (template) {
44
+ document.documentElement.dataset.template = template;
45
+ this.template = template;
46
+ }
47
+ }
48
+ //
49
+ // Components.
50
+ //
51
+ canvas;
52
+ components = [];
53
+ transition;
54
+ initComponents(components) {
55
+ const classes = components.map(({ component: Component }) => new Component({
56
+ application: this,
57
+ }));
58
+ this.canvas = classes.find((component) => component.id === 'canvas');
59
+ this.transition = classes.find((component) => component.id === 'transition');
60
+ classes.forEach((component) => {
61
+ this.addComponent(component);
62
+ });
63
+ }
64
+ addComponent(component) {
65
+ this.components.push(component);
66
+ }
67
+ removeComponent(component) {
68
+ component.destroy();
69
+ const index = this.components.indexOf(component);
70
+ if (index !== -1) {
71
+ this.components.splice(index, 1);
72
+ }
73
+ }
74
+ onComponentChange(_event) { }
75
+ //
76
+ // Datasets.
77
+ //
78
+ datasets = [];
79
+ initDatasets(datasets) {
80
+ this.datasets = datasets;
81
+ }
82
+ //
83
+ // Routes.
84
+ //
85
+ pages = new Map();
86
+ initRoutes(routes) {
87
+ routes.forEach(({ component, template }) => {
88
+ this.pages.set(template, component);
89
+ });
90
+ }
91
+ //
92
+ // Sprites.
93
+ //
94
+ async initSprites(url = '/bundle.svg') {
95
+ const request = await window.fetch(url);
96
+ const response = await request.text();
97
+ const sprite = document.createElement('div');
98
+ sprite.innerHTML = response;
99
+ sprite.style.left = '-999999px';
100
+ sprite.style.opacity = '0';
101
+ sprite.style.position = 'absolute';
102
+ sprite.style.top = '0';
103
+ document.body.appendChild(sprite);
104
+ }
105
+ //
106
+ // Initialization.
107
+ //
108
+ IS_LINKS_ENABLED = true;
109
+ initPage() {
110
+ this.createPage();
111
+ this.createLinks();
112
+ }
113
+ //
114
+ // Links.
115
+ //
116
+ createLinks() {
117
+ if (!this.IS_LINKS_ENABLED) {
118
+ return;
119
+ }
120
+ this.links = new Links(this);
121
+ }
122
+ //
123
+ // Page.
124
+ //
125
+ currentPage = undefined;
126
+ createPage(template = this.template) {
127
+ const PageClass = this.pages.get(template);
128
+ const page = new PageClass({
129
+ application: this,
130
+ datasets: this.datasets,
131
+ });
132
+ this.currentPage = page;
133
+ this.currentPage.create();
134
+ }
135
+ destroyPage() {
136
+ if (this.currentPage) {
137
+ this.currentPage.destroy();
138
+ }
139
+ }
140
+ //
141
+ // Navigate.
142
+ //
143
+ route = `${window.location.pathname}${window.location.search}${window.location.hash}`;
144
+ routeHistory = [this.route];
145
+ routeRequestEnabled = true;
146
+ routePushState = true;
147
+ onRouteChange({ newValue }) {
148
+ if (!this.routeRequestEnabled) {
149
+ return;
150
+ }
151
+ const url = new URL(newValue, window.location.href);
152
+ if (url.origin !== window.location.origin) {
153
+ window.location.assign(url.href);
154
+ return;
155
+ }
156
+ const href = `${url.pathname}${url.search}${url.hash}`;
157
+ void this.onRouteChangeRequest({
158
+ href,
159
+ pushState: this.routePushState,
160
+ });
161
+ }
162
+ async onRouteChangeRequest({ href, pushState = true }) {
163
+ try {
164
+ const request = await window.fetch(href);
165
+ if (request.redirected) {
166
+ window.location.assign(request.url || href);
167
+ return;
168
+ }
169
+ const contentType = request.headers.get('content-type');
170
+ if (contentType && !contentType.toLowerCase().includes('text/html')) {
171
+ window.location.assign(href);
172
+ return;
173
+ }
174
+ const response = await request.text();
175
+ await this.onRequest({
176
+ href,
177
+ response,
178
+ pushState,
179
+ });
180
+ }
181
+ catch {
182
+ window.location.assign(href);
183
+ }
184
+ }
185
+ //
186
+ // Request.
187
+ //
188
+ nextPage = {};
189
+ async onRequest({ href, response, pushState }) {
190
+ const dom = new DOMParser().parseFromString(response, 'text/html');
191
+ const html = dom.documentElement;
192
+ const app = dom.querySelector('.app');
193
+ const page = app?.firstElementChild;
194
+ const template = html.dataset.template ?? this.template;
195
+ if (!app || !(page instanceof HTMLElement) || !this.pages.has(template)) {
196
+ window.location.assign(href);
197
+ return;
198
+ }
199
+ this.nextPage = {
200
+ element: app,
201
+ template,
202
+ title: dom.title || document.title,
203
+ };
204
+ if (this.transition) {
205
+ await this.transition.onTransition?.(this);
206
+ }
207
+ else {
208
+ this.currentPage.element.remove();
209
+ this.currentPage.destroy();
210
+ this.element.appendChild(this.nextPage.element.firstElementChild);
211
+ this.createPage(this.nextPage.template);
212
+ }
213
+ if (pushState) {
214
+ window.history.pushState({}, this.nextPage.title, href);
215
+ }
216
+ this.routeHistory.push(href);
217
+ }
218
+ //
219
+ // Pop State.
220
+ //
221
+ onPopState() {
222
+ const route = `${document.location.pathname}${document.location.search}${document.location.hash}`;
223
+ const currentUrl = new URL(this.route, window.location.origin);
224
+ const nextUrl = new URL(route, window.location.origin);
225
+ if (currentUrl.pathname === nextUrl.pathname && currentUrl.search === nextUrl.search) {
226
+ this.routeRequestEnabled = false;
227
+ this.route = route;
228
+ this.routeRequestEnabled = true;
229
+ return;
230
+ }
231
+ this.routePushState = false;
232
+ this.route = route;
233
+ this.routePushState = true;
234
+ }
235
+ //
236
+ // Scroll.
237
+ //
238
+ get scroll() {
239
+ return this.currentPage.scroll ?? 0;
240
+ }
241
+ //
242
+ // Listeners.
243
+ //
244
+ addEventListeners() {
245
+ window.addEventListener('popstate', this.onPopState);
246
+ }
247
+ removeEventListeners() {
248
+ window.removeEventListener('popstate', this.onPopState);
249
+ }
250
+ }
251
+ export const Application = new ApplicationManager();
@@ -0,0 +1,44 @@
1
+ import type { ApplicationManager } from './App.js';
2
+ import { EventEmitter } from './EventEmitter.js';
3
+ export interface ComponentClasses {
4
+ [key: string]: string;
5
+ }
6
+ export interface ComponentElements {
7
+ [key: string]: unknown[] | Element | Array<Element> | HTMLElement | Array<HTMLElement> | NodeList | Window | null;
8
+ }
9
+ export type ComponentSelector = string | HTMLElement;
10
+ export interface ComponentSelectors {
11
+ [key: string]: string | Element | Array<Element> | HTMLElement | Array<HTMLElement> | NodeList | Window;
12
+ }
13
+ export interface ComponentParameters {
14
+ application?: ApplicationManager;
15
+ autoListeners?: boolean;
16
+ autoMount?: boolean;
17
+ classes?: ComponentClasses;
18
+ element?: ComponentSelector;
19
+ elements?: ComponentSelectors;
20
+ id?: string;
21
+ }
22
+ export declare class Component extends EventEmitter {
23
+ application?: ApplicationManager;
24
+ autoListeners: boolean;
25
+ autoMount: boolean;
26
+ classes?: ComponentClasses;
27
+ disposers: Set<() => void>;
28
+ selector?: ComponentSelector;
29
+ selectors?: ComponentSelectors;
30
+ id?: string;
31
+ element?: HTMLElement;
32
+ elements: ComponentElements;
33
+ constructor({ application, autoListeners, autoMount, classes, element, elements, id, }: ComponentParameters);
34
+ create(): void;
35
+ initElement(selector: ComponentSelector): void;
36
+ destroyElement(): void;
37
+ initElements(selectors?: ComponentSelectors): void;
38
+ destroyElements(): void;
39
+ addDisposer(disposer: () => void): () => void;
40
+ destroyDisposers(): void;
41
+ addEventListeners(): void;
42
+ removeEventListeners(): void;
43
+ destroy(): void;
44
+ }
@@ -0,0 +1,108 @@
1
+ import { EventEmitter } from './EventEmitter.js';
2
+ export class Component extends EventEmitter {
3
+ application;
4
+ autoListeners;
5
+ autoMount;
6
+ classes;
7
+ disposers = new Set();
8
+ selector;
9
+ selectors;
10
+ id;
11
+ element;
12
+ elements = {};
13
+ constructor({ application, autoListeners = true, autoMount = true, classes, element, elements, id, }) {
14
+ super();
15
+ this.application = application;
16
+ this.autoListeners = autoListeners;
17
+ this.autoMount = autoMount;
18
+ this.classes = classes;
19
+ this.selector = element;
20
+ this.selectors = elements;
21
+ this.id = id;
22
+ if (this.autoMount) {
23
+ this.create();
24
+ }
25
+ if (this.autoListeners) {
26
+ this.addEventListeners();
27
+ }
28
+ }
29
+ create() {
30
+ if (this.selector) {
31
+ this.initElement(this.selector);
32
+ }
33
+ if (this.selectors) {
34
+ this.initElements(this.selectors);
35
+ }
36
+ }
37
+ initElement(selector) {
38
+ if (selector instanceof HTMLElement) {
39
+ this.element = selector;
40
+ }
41
+ else {
42
+ this.element = document.querySelector(selector);
43
+ }
44
+ }
45
+ destroyElement() {
46
+ this.element = undefined;
47
+ }
48
+ initElements(selectors) {
49
+ for (const key in selectors) {
50
+ const selector = selectors[key];
51
+ if (selector === window) {
52
+ this.elements[key] = window;
53
+ }
54
+ else if (selector instanceof HTMLElement) {
55
+ this.elements[key] = selector;
56
+ }
57
+ else if (selector instanceof NodeList) {
58
+ this.elements[key] = selector;
59
+ }
60
+ else if (Array.isArray(selector)) {
61
+ this.elements[key] = selector;
62
+ }
63
+ else {
64
+ const elements = this.element.querySelectorAll(selector);
65
+ if (elements.length === 0) {
66
+ const elements = document.querySelectorAll(selector);
67
+ if (elements.length === 0) {
68
+ this.elements[key] = null;
69
+ }
70
+ else if (elements.length === 1) {
71
+ this.elements[key] = elements[0];
72
+ }
73
+ else {
74
+ this.elements[key] = elements;
75
+ }
76
+ }
77
+ else if (elements.length === 1) {
78
+ this.elements[key] = elements[0];
79
+ }
80
+ else {
81
+ this.elements[key] = elements;
82
+ }
83
+ }
84
+ }
85
+ }
86
+ destroyElements() {
87
+ this.elements = {};
88
+ }
89
+ addDisposer(disposer) {
90
+ this.disposers.add(disposer);
91
+ return disposer;
92
+ }
93
+ destroyDisposers() {
94
+ this.disposers.forEach((dispose) => {
95
+ dispose();
96
+ });
97
+ this.disposers.clear();
98
+ }
99
+ addEventListeners() { }
100
+ removeEventListeners() { }
101
+ destroy() {
102
+ this.destroyDisposers();
103
+ super.destroy();
104
+ this.removeEventListeners();
105
+ this.destroyElements();
106
+ this.destroyElement();
107
+ }
108
+ }
@@ -0,0 +1,10 @@
1
+ import { type Emitter, type Unsubscribe } from 'nanoevents';
2
+ export declare class EventEmitter {
3
+ emitter: Emitter;
4
+ entries: Map<unknown, Unsubscribe>;
5
+ constructor();
6
+ on<Arguments extends unknown[]>(event: string, callback: (...args: Arguments) => void): void | Unsubscribe;
7
+ off<Arguments extends unknown[]>(_event: string, callback: (...args: Arguments) => void): void;
8
+ fire(event: string, ...args: unknown[]): void;
9
+ destroy(): void;
10
+ }
@@ -0,0 +1,34 @@
1
+ import AutoBind from 'auto-bind';
2
+ import { createNanoEvents } from 'nanoevents';
3
+ export class EventEmitter {
4
+ emitter;
5
+ entries = new Map();
6
+ constructor() {
7
+ AutoBind(this);
8
+ this.emitter = createNanoEvents();
9
+ }
10
+ on(event, callback) {
11
+ if (!callback) {
12
+ return console.trace('No callback provided');
13
+ }
14
+ const emitter = this.emitter.on(event, callback);
15
+ this.entries.set(callback, emitter);
16
+ return emitter;
17
+ }
18
+ off(_event, callback) {
19
+ const unsubscribe = this.entries.get(callback);
20
+ if (unsubscribe) {
21
+ unsubscribe();
22
+ }
23
+ this.entries.delete(callback);
24
+ }
25
+ fire(event, ...args) {
26
+ this.emitter.emit(event, ...args);
27
+ }
28
+ destroy() {
29
+ this.entries.forEach((unsubscribe) => {
30
+ unsubscribe();
31
+ });
32
+ this.entries.clear();
33
+ }
34
+ }
package/dist/Link.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Component } from './Component.js';
2
+ export declare class Link extends Component {
3
+ element: HTMLAnchorElement;
4
+ constructor({ element }: {
5
+ element: HTMLAnchorElement;
6
+ });
7
+ onClick(event: MouseEvent): void;
8
+ shouldHandleClick(event: MouseEvent): boolean;
9
+ addEventListeners(): void;
10
+ removeEventListeners(): void;
11
+ }
package/dist/Link.js ADDED
@@ -0,0 +1,57 @@
1
+ import { Component } from './Component.js';
2
+ export class Link extends Component {
3
+ constructor({ element }) {
4
+ super({ element });
5
+ }
6
+ onClick(event) {
7
+ if (!this.shouldHandleClick(event)) {
8
+ return;
9
+ }
10
+ event.preventDefault();
11
+ this.fire('click', new URL(this.element.href, window.location.href).href);
12
+ }
13
+ shouldHandleClick(event) {
14
+ if (event.defaultPrevented ||
15
+ event.button !== 0 ||
16
+ event.metaKey ||
17
+ event.ctrlKey ||
18
+ event.shiftKey ||
19
+ event.altKey ||
20
+ this.element.hasAttribute('download')) {
21
+ return false;
22
+ }
23
+ const target = this.element.getAttribute('target');
24
+ if (target && target.toLowerCase() !== '_self') {
25
+ return false;
26
+ }
27
+ const url = new URL(this.element.href, window.location.href);
28
+ const isHttp = url.protocol === 'http:' || url.protocol === 'https:';
29
+ const isLocal = isHttp && url.origin === window.location.origin;
30
+ const hasHashReference = this.element.getAttribute('href')?.includes('#') ?? false;
31
+ const isSameDocumentHash = hasHashReference && url.pathname === window.location.pathname && url.search === window.location.search;
32
+ return isLocal && !isSameDocumentHash;
33
+ }
34
+ addEventListeners() {
35
+ const url = new URL(this.element.href, window.location.href);
36
+ const isHttp = url.protocol === 'http:' || url.protocol === 'https:';
37
+ const isLocal = isHttp && url.origin === window.location.origin;
38
+ if (isLocal) {
39
+ this.element.onclick = this.onClick;
40
+ }
41
+ else if (isHttp) {
42
+ if (!this.element.hasAttribute('target')) {
43
+ this.element.setAttribute('target', '_blank');
44
+ }
45
+ if (this.element.target === '_blank') {
46
+ const rel = new Set(this.element.rel.split(/\s+/).filter(Boolean));
47
+ rel.add('noopener');
48
+ this.element.rel = Array.from(rel).join(' ');
49
+ }
50
+ }
51
+ }
52
+ removeEventListeners() {
53
+ if (this.element.onclick === this.onClick) {
54
+ this.element.onclick = null;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,11 @@
1
+ import type { ApplicationManager } from './App.js';
2
+ import { EventEmitter } from './EventEmitter.js';
3
+ import { Link } from './Link.js';
4
+ export declare class Links extends EventEmitter {
5
+ application: ApplicationManager;
6
+ links: Array<Link>;
7
+ constructor(application: ApplicationManager);
8
+ addEventListeners(): void;
9
+ onLinkClick(href: string): void;
10
+ refresh(): void;
11
+ }
package/dist/Links.js ADDED
@@ -0,0 +1,30 @@
1
+ import { reaction } from 'mobx';
2
+ import { EventEmitter } from './EventEmitter.js';
3
+ import { Link } from './Link.js';
4
+ export class Links extends EventEmitter {
5
+ constructor(application) {
6
+ super();
7
+ this.application = application;
8
+ reaction(() => application.currentPage, () => this.refresh(), { fireImmediately: true });
9
+ }
10
+ addEventListeners() {
11
+ this.links?.forEach((link) => {
12
+ link.destroy();
13
+ });
14
+ const links = document.querySelectorAll('a');
15
+ this.links = Array.from(links).map((element) => {
16
+ const link = new Link({
17
+ element,
18
+ });
19
+ link.on('click', this.onLinkClick);
20
+ return link;
21
+ });
22
+ }
23
+ onLinkClick(href) {
24
+ const url = new URL(href, window.location.href);
25
+ this.application.route = `${url.pathname}${url.search}${url.hash}`;
26
+ }
27
+ refresh() {
28
+ this.addEventListeners();
29
+ }
30
+ }
package/dist/Page.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ import Lenis, { type LenisOptions } from 'lenis';
2
+ import { type TempusState } from 'tempus';
3
+ import type { ApplicationManager } from './App.js';
4
+ import { Component, type ComponentClasses, type ComponentSelectors } from './Component.js';
5
+ export interface PageParameters {
6
+ application: ApplicationManager;
7
+ classes?: ComponentClasses;
8
+ datasets: Array<{
9
+ component: typeof Component;
10
+ selector: string;
11
+ }>;
12
+ element?: HTMLElement | string;
13
+ elements?: ComponentSelectors;
14
+ scrollEnabled?: boolean;
15
+ scrollOptions?: LenisOptions;
16
+ }
17
+ export declare class Page extends Component {
18
+ element: HTMLElement;
19
+ elements: {
20
+ wrapper: HTMLElement;
21
+ };
22
+ application: ApplicationManager;
23
+ datasets: Array<{
24
+ component: typeof Component;
25
+ selector: string;
26
+ }>;
27
+ scroll: number;
28
+ scrollEnabled: boolean;
29
+ scrollOptions: LenisOptions;
30
+ unsubscribeRaf: (() => void) | undefined;
31
+ constructor({ application, classes, datasets, element, elements, scrollEnabled, scrollOptions, }: PageParameters);
32
+ create(): void;
33
+ resizeObserver: ResizeObserver;
34
+ createResizeObserver(): void;
35
+ destroyResizeObserver(): void;
36
+ components: Set<Component>;
37
+ createComponents(): void;
38
+ destroyComponents(): void;
39
+ lenis: Lenis;
40
+ createScroll(): void;
41
+ onLenisScroll(event: {
42
+ scroll: number;
43
+ }): void;
44
+ destroyScroll(): void;
45
+ onResize(): void;
46
+ onRAF({ time }: TempusState): void;
47
+ onScroll(scroll: number): void;
48
+ onScrollFallback(): void;
49
+ addEventListeners(): void;
50
+ removeEventListeners(): void;
51
+ destroy(): void;
52
+ }