native-document 1.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.
Files changed (47) hide show
  1. package/dist/native-document.dev.js +2346 -0
  2. package/dist/native-document.min.js +1 -0
  3. package/elements.js +2 -0
  4. package/index.js +11 -0
  5. package/package.json +16 -0
  6. package/readme.md +495 -0
  7. package/rollup.config.js +29 -0
  8. package/router.js +9 -0
  9. package/src/data/MemoryManager.js +60 -0
  10. package/src/data/Observable.js +162 -0
  11. package/src/data/ObservableChecker.js +24 -0
  12. package/src/data/ObservableItem.js +101 -0
  13. package/src/data/Store.js +74 -0
  14. package/src/elements/content-formatter.js +32 -0
  15. package/src/elements/control/for-each.js +110 -0
  16. package/src/elements/control/show-if.js +86 -0
  17. package/src/elements/control/switch.js +88 -0
  18. package/src/elements/description-list.js +5 -0
  19. package/src/elements/form.js +71 -0
  20. package/src/elements/html5-semantics.js +12 -0
  21. package/src/elements/img.js +45 -0
  22. package/src/elements/index.js +21 -0
  23. package/src/elements/interactive.js +7 -0
  24. package/src/elements/list.js +6 -0
  25. package/src/elements/medias.js +8 -0
  26. package/src/elements/meta-data.js +9 -0
  27. package/src/elements/table.js +14 -0
  28. package/src/errors/ArgTypesError.js +7 -0
  29. package/src/errors/NativeDocumentError.js +8 -0
  30. package/src/errors/RouterError.js +9 -0
  31. package/src/router/Route.js +102 -0
  32. package/src/router/RouteGroupHelper.js +52 -0
  33. package/src/router/Router.js +232 -0
  34. package/src/router/RouterComponent.js +37 -0
  35. package/src/router/link.js +27 -0
  36. package/src/router/modes/HashRouter.js +83 -0
  37. package/src/router/modes/HistoryRouter.js +66 -0
  38. package/src/router/modes/MemoryRouter.js +71 -0
  39. package/src/utils/args-types.js +100 -0
  40. package/src/utils/debug-manager.js +34 -0
  41. package/src/utils/helpers.js +37 -0
  42. package/src/utils/prototypes.js +16 -0
  43. package/src/utils/validator.js +96 -0
  44. package/src/wrappers/AttributesWrapper.js +94 -0
  45. package/src/wrappers/DocumentObserver.js +51 -0
  46. package/src/wrappers/HtmlElementEventsWrapper.js +77 -0
  47. package/src/wrappers/HtmlElementWrapper.js +174 -0
@@ -0,0 +1,71 @@
1
+ import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
2
+
3
+
4
+ export const Form = HtmlElementWrapper('form', function(el) {
5
+
6
+ el.submit = function(action) {
7
+ if(typeof action === 'function') {
8
+ el.on.submit((e) => {
9
+ e.preventDefault();
10
+ action(e);
11
+ });
12
+ return el;
13
+ }
14
+ this.setAttribute('action', action);
15
+ return el;
16
+ };
17
+ el.multipartFormData = function() {
18
+ this.setAttribute('enctype', 'multipart/form-data');
19
+ return el;
20
+ }
21
+ el.post = function(action) {
22
+ this.setAttribute('method', 'post');
23
+ this.setAttribute('action', action);
24
+ return el;
25
+ };
26
+ el.get = function(action) {
27
+ this.setAttribute('method', 'get');
28
+ this.setAttribute('action', action);
29
+ };
30
+ return el;
31
+ });
32
+
33
+ export const Input = HtmlElementWrapper('input');
34
+
35
+ export const TextArea = HtmlElementWrapper('textarea');
36
+ export const TextInput = TextArea;
37
+
38
+ export const Select = HtmlElementWrapper('select');
39
+ export const FieldSet = HtmlElementWrapper('fieldset', );
40
+ export const Option = HtmlElementWrapper('option');
41
+ export const Legend = HtmlElementWrapper('legend');
42
+ export const Datalist = HtmlElementWrapper('datalist');
43
+ export const Output = HtmlElementWrapper('output');
44
+ export const Progress = HtmlElementWrapper('progress');
45
+ export const Meter = HtmlElementWrapper('meter');
46
+
47
+ export const ReadonlyInput = (attributes) => Input({ readonly: true, ...attributes });
48
+ export const HiddenInput = (attributes) => Input({type: 'hidden', ...attributes });
49
+ export const FileInput = (attributes) => Input({ type: 'file', ...attributes });
50
+ export const PasswordInput = (attributes) => Input({ type: 'password', ...attributes });
51
+ export const Checkbox = (attributes) => Input({ type: 'checkbox', ...attributes });
52
+ export const Radio = (attributes) => Input({ type: 'radio', ...attributes });
53
+
54
+ export const RangeInput = (attributes) => Input({ type: 'range', ...attributes });
55
+ export const ColorInput = (attributes) => Input({ type: 'color', ...attributes });
56
+ export const DateInput = (attributes) => Input({ type: 'date', ...attributes });
57
+ export const TimeInput = (attributes) => Input({ type: 'time', ...attributes });
58
+ export const DateTimeInput = (attributes) => Input({ type: 'datetime-local', ...attributes });
59
+ export const WeekInput = (attributes) => Input({ type: 'week', ...attributes });
60
+ export const MonthInput = (attributes) => Input({ type: 'month', ...attributes });
61
+ export const SearchInput = (attributes) => Input({ type: 'search', ...attributes });
62
+ export const TelInput = (attributes) => Input({ type: 'tel', ...attributes });
63
+ export const UrlInput = (attributes) => Input({ type: 'url', ...attributes });
64
+ export const EmailInput = (attributes) => Input({ type: 'email', ...attributes });
65
+ export const NumberInput = (attributes) => Input({ type: 'number', ...attributes });
66
+
67
+
68
+ export const Button = HtmlElementWrapper('button');
69
+ export const SimpleButton = (child, attributes) => Button(child, { type: 'button', ...attributes });
70
+ export const SubmitButton = (child, attributes) => Button(child, { type: 'submit', ...attributes });
71
+
@@ -0,0 +1,12 @@
1
+ import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
2
+
3
+ export const Main = HtmlElementWrapper('main');
4
+ export const Section = HtmlElementWrapper('section');
5
+ export const Article = HtmlElementWrapper('article');
6
+ export const Aside = HtmlElementWrapper('aside');
7
+ export const Nav = HtmlElementWrapper('nav');
8
+ export const Figure = HtmlElementWrapper('figure');
9
+ export const FigCaption = HtmlElementWrapper('figcaption');
10
+
11
+ export const Header = HtmlElementWrapper('header');
12
+ export const Footer = HtmlElementWrapper('footer');
@@ -0,0 +1,45 @@
1
+ import HtmlElementWrapper from "../wrappers/HtmlElementWrapper"
2
+ import Validator from "../utils/validator";
3
+ import NativeDocumentError from "../errors/NativeDocumentError";
4
+
5
+ export const BaseImage = HtmlElementWrapper('img');
6
+ export const Img = function(src, attributes) {
7
+ return BaseImage({ src, ...attributes });
8
+ };
9
+
10
+ /**
11
+ *
12
+ * @param {string} src
13
+ * @param {string|null} defaultImage
14
+ * @param {Object} attributes
15
+ * @param {?Function} callback
16
+ * @returns {Image}
17
+ */
18
+ export const AsyncImg = function(src, defaultImage, attributes, callback) {
19
+ const image = Img(defaultImage || src, attributes);
20
+ const img = new Image();
21
+ img.onload = () => {
22
+ Validator.isFunction(callback) && callback(null, image);
23
+ image.src = src;
24
+ };
25
+ img.onerror = () => {
26
+ Validator.isFunction(callback) && callback(new NativeDocumentError('Image not found'));
27
+ };
28
+ if(Validator.isObservable(src)) {
29
+ src.subscribe(newSrc => {
30
+ img.src = newSrc;
31
+ });
32
+ }
33
+ img.src = src;
34
+ return image;
35
+ };
36
+
37
+ /**
38
+ *
39
+ * @param {string} src
40
+ * @param {Object} attributes
41
+ * @returns {Image}
42
+ */
43
+ export const LazyImg = function(src, attributes) {
44
+ return Img(src, { ...attributes, loading: 'lazy' });
45
+ };
@@ -0,0 +1,21 @@
1
+ import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
2
+
3
+ export * from './control/for-each';
4
+ export * from './control/show-if';
5
+ export * from './control/switch';
6
+ export * from './content-formatter';
7
+ export * from './description-list';
8
+ export * from './form';
9
+ export * from './html5-semantics';
10
+ export * from './img';
11
+ export * from './interactive';
12
+ export * from './list';
13
+ export * from './medias';
14
+ export * from './meta-data';
15
+ export * from './table';
16
+
17
+ export const Fragment = HtmlElementWrapper('');
18
+
19
+
20
+
21
+
@@ -0,0 +1,7 @@
1
+ import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
2
+
3
+
4
+ export const Details = HtmlElementWrapper('details');
5
+ export const Summary = HtmlElementWrapper('summary');
6
+ export const Dialog = HtmlElementWrapper('dialog');
7
+ export const Menu = HtmlElementWrapper('menu');
@@ -0,0 +1,6 @@
1
+ import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
2
+
3
+ export const OrderedList = HtmlElementWrapper('ol');
4
+ export const UnorderedList = HtmlElementWrapper('ul');
5
+ export const ListItem = HtmlElementWrapper('li');
6
+
@@ -0,0 +1,8 @@
1
+ import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
2
+
3
+ export const Audio = HtmlElementWrapper('audio');
4
+ export const Video = HtmlElementWrapper('video');
5
+ export const Source = HtmlElementWrapper('source');
6
+ export const Track = HtmlElementWrapper('track');
7
+ export const Canvas = HtmlElementWrapper('canvas');
8
+ export const Svg = HtmlElementWrapper('svg');
@@ -0,0 +1,9 @@
1
+ import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
2
+
3
+ export const Time = HtmlElementWrapper('time');
4
+ export const Data = HtmlElementWrapper('data');
5
+ export const Address = HtmlElementWrapper('address');
6
+ export const Kbd = HtmlElementWrapper('kbd');
7
+ export const Samp = HtmlElementWrapper('samp');
8
+ export const Var = HtmlElementWrapper('var');
9
+ export const Wbr = HtmlElementWrapper('wbr');
@@ -0,0 +1,14 @@
1
+ import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
2
+
3
+ export const Caption = HtmlElementWrapper('caption');
4
+ export const Table = HtmlElementWrapper('table');
5
+ export const THead = HtmlElementWrapper('thead');
6
+ export const TFoot = HtmlElementWrapper('tfoot');
7
+ export const TBody = HtmlElementWrapper('tbody');
8
+ export const Tr = HtmlElementWrapper('tr');
9
+ export const TRow = Tr;
10
+ export const Th = HtmlElementWrapper('th');
11
+ export const THeadCell = Th;
12
+ export const TFootCell = Th;
13
+ export const Td = HtmlElementWrapper('td');
14
+ export const TBodyCell = Td;
@@ -0,0 +1,7 @@
1
+
2
+
3
+ export default class ArgTypesError extends Error {
4
+ constructor(message, errors) {
5
+ super(`${message}\n\n${errors.join("\n")}\n\n`);
6
+ }
7
+ }
@@ -0,0 +1,8 @@
1
+ export default class NativeDocumentError extends Error {
2
+ constructor(message, context = {}) {
3
+ super(message);
4
+ this.name = 'NativeDocumentError';
5
+ this.context = context;
6
+ this.timestamp = new Date().toISOString();
7
+ }
8
+ }
@@ -0,0 +1,9 @@
1
+
2
+
3
+ export default class RouterError extends Error {
4
+ constructor(message, context) {
5
+ super(message);
6
+ this.context = context;
7
+ }
8
+
9
+ }
@@ -0,0 +1,102 @@
1
+ import {trim} from "../utils/helpers.js";
2
+
3
+ export const RouteParamPatterns = {
4
+
5
+ };
6
+
7
+ /**
8
+ *
9
+ * @param {string} $path
10
+ * @param {Function} $component
11
+ * @param {{name:?string, middlewares:Function[], shouldRebuild:Boolean, with: Object }}$options
12
+ * @class
13
+ */
14
+ export function Route($path, $component, $options = {}) {
15
+
16
+ $path = '/'+trim($path, '/');
17
+
18
+ let $pattern = null;
19
+ let $name = $options.name || null;
20
+
21
+ const $middlewares = $options.middlewares || [];
22
+ const $shouldRebuild = $options.shouldRebuild || false;
23
+ const $paramsValidators = $options.with || {};
24
+
25
+ const $params = {};
26
+ const $paramsNames = [];
27
+
28
+
29
+ const paramsExtractor = (description) => {
30
+ if(!description) return null;
31
+ const [name, type] = description.split(':');
32
+
33
+ let pattern = $paramsValidators[name];
34
+ if(!pattern && type) {
35
+ pattern = RouteParamPatterns[type];
36
+ }
37
+ if(!pattern) {
38
+ pattern = '[^/]+';
39
+ }
40
+
41
+ pattern = pattern.replace('(', '(?:');
42
+
43
+ return { name, pattern: `(${pattern})` };
44
+ };
45
+
46
+ const getPattern = () => {
47
+ if($pattern) {
48
+ return $pattern;
49
+ }
50
+
51
+ const patternDescription = $path.replace(/\{(.*?)}/ig, (block, definition) => {
52
+ const description = paramsExtractor(definition);
53
+ if(!description || !description.pattern) return block;
54
+ $params[description.name] = description.pattern;
55
+ $paramsNames.push(description.name);
56
+ return description.pattern;
57
+ });
58
+
59
+ $pattern = new RegExp('^'+patternDescription+'$');
60
+ return $pattern;
61
+ };
62
+
63
+ this.name = () => $name;
64
+ this.component = () => $component;
65
+ this.middlewares = () => $middlewares;
66
+ this.shouldRebuild = () => $shouldRebuild;
67
+ this.path = () => $path;
68
+
69
+ /**
70
+ *
71
+ * @param {string} path
72
+ */
73
+ this.match = function(path) {
74
+ path = '/'+trim(path, '/');
75
+ const match = getPattern().exec(path);
76
+ if(!match) return false;
77
+ const params = {};
78
+
79
+ getPattern().exec(path).forEach((value, index) => {
80
+ if(index < 1) return;
81
+ const name = $paramsNames[index - 1];
82
+ params[name] = value;
83
+ });
84
+
85
+ return params;
86
+ };
87
+ /**
88
+ * @param {{params: ?Object, query: ?Object, basePath: ?string}} configs
89
+ */
90
+ this.url = function(configs) {
91
+ const path = $path.replace(/\{(.*?)}/ig, (block, definition) => {
92
+ const description = paramsExtractor(definition);
93
+ if(configs.params && configs.params[description.name]) {
94
+ return configs.params[description.name];
95
+ }
96
+ throw new Error(`Missing parameter '${description.name}'`);
97
+ });
98
+
99
+ const queryString = (typeof configs.query === 'object') ? (new URLSearchParams(configs.query)).toString() : null;
100
+ return (configs.basePath ? configs.basePath : '') + (queryString ? `${path}?${queryString}` : path);
101
+ }
102
+ }
@@ -0,0 +1,52 @@
1
+ import {trim} from "../utils/helpers.js";
2
+
3
+ export const RouteGroupHelper = {
4
+ /**
5
+ *
6
+ * @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
7
+ * @param {string} path
8
+ * @returns {string}
9
+ */
10
+ fullPath: ($groupTree, path) => {
11
+ const fullPath = [];
12
+ $groupTree.forEach(group => {
13
+ fullPath.push(trim(group.suffix, '/'));
14
+ });
15
+ fullPath.push(trim(path, '/'));
16
+ return fullPath.join('/');
17
+ },
18
+ /**
19
+ *
20
+ * @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
21
+ * @param {Function[]} middlewares
22
+ * @returns {Function[]}
23
+ */
24
+ fullMiddlewares: ($groupTree, middlewares) => {
25
+ const fullMiddlewares = [];
26
+ $groupTree.forEach(group => {
27
+ if(group.options.middlewares) {
28
+ fullMiddlewares.push(...group.options.middlewares);
29
+ }
30
+ });
31
+ if(middlewares) {
32
+ fullMiddlewares.push(...middlewares);
33
+ }
34
+ return fullMiddlewares;
35
+ },
36
+ /**
37
+ *
38
+ * @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
39
+ * @param {string} name
40
+ * @returns {string}
41
+ */
42
+ fullName: ($groupTree, name) => {
43
+ const fullName = [];
44
+ $groupTree.forEach(group => {
45
+ if(group.options?.name) {
46
+ fullName.push(group.options.name);
47
+ }
48
+ });
49
+ name && fullName.push(name);
50
+ return fullName.join('.');
51
+ }
52
+ };
@@ -0,0 +1,232 @@
1
+ import {Route} from "./Route.js";
2
+ import Validator from "../utils/validator.js";
3
+ import RouterError from "../errors/RouterError.js";
4
+ import {RouteGroupHelper} from "./RouteGroupHelper.js";
5
+ import {trim} from "../utils/helpers.js";
6
+ import HashRouter from "./modes/HashRouter.js";
7
+ import HistoryRouter from "./modes/HistoryRouter.js";
8
+ import MemoryRouter from "./modes/MemoryRouter.js";
9
+ import DebugManager from "../utils/debug-manager.js";
10
+ import {RouterComponent} from "./RouterComponent.js";
11
+
12
+ const DEFAULT_ROUTER_NAME = 'default';
13
+
14
+ /**
15
+ *
16
+ * @param {{mode: 'memory'|'history'|'hash'}} $options
17
+ * @constructor
18
+ */
19
+ export default function Router($options = {}) {
20
+
21
+ /** @type {Route[]} */
22
+ const $routes = [];
23
+ /** @type {{[string]: Route}} */
24
+ const $routesByName = {};
25
+ const $groupTree = [];
26
+ const $listeners = [];
27
+ const $currentState = { route: null, params: null, query: null, path: null, hash: null };
28
+
29
+ if($options.mode === 'hash') {
30
+ HashRouter.apply(this, []);
31
+ } else if($options.mode === 'history') {
32
+ HistoryRouter.apply(this, []);
33
+ } else if($options.mode === 'memory') {
34
+ MemoryRouter.apply(this, []);
35
+ } else {
36
+ throw new RouterError('Invalid router mode '+$options.mode);
37
+ }
38
+
39
+ const trigger = function(request, next) {
40
+ for(const listener of $listeners) {
41
+ try {
42
+ listener(request);
43
+ next && next(request);
44
+ } catch (e) {
45
+ DebugManager.warn('Route Listener', 'Error in listener:', e);
46
+ }
47
+ }
48
+ }
49
+
50
+ this.routes = () => [...$routes];
51
+ this.currentState = () => ({ ...$currentState });
52
+
53
+ /**
54
+ *
55
+ * @param {string} path
56
+ * @param {Function} component
57
+ * @param {{name:?string, middlewares:Function[], shouldRebuild:Boolean, with: Object }} options
58
+ * @returns {this}
59
+ */
60
+ this.add = function(path, component, options) {
61
+ const route = new Route(RouteGroupHelper.fullPath($groupTree, path), component, {
62
+ ...options,
63
+ middlewares: RouteGroupHelper.fullMiddlewares($groupTree, options?.middlewares || []),
64
+ name: options?.name ? RouteGroupHelper.fullName($groupTree, options.name) : null,
65
+ });
66
+ $routes.push(route);
67
+ if(route.name()) {
68
+ $routesByName[route.name()] = route;
69
+ }
70
+ return this;
71
+ };
72
+
73
+ /**
74
+ *
75
+ * @param {string} suffix
76
+ * @param {{ middlewares: Function[], name: string}} options
77
+ * @param {Function} callback
78
+ * @returns {this}
79
+ */
80
+ this.group = function(suffix, options, callback) {
81
+ if(!Validator.isFunction(callback)) {
82
+ throw new RouterError('Callback must be a function');
83
+ }
84
+ $groupTree.push({suffix, options});
85
+ callback();
86
+ $groupTree.pop();
87
+ return this;
88
+ };
89
+
90
+ /**
91
+ *
92
+ * @param {string} name
93
+ * @param {Object}params
94
+ * @param {Object} query
95
+ * @returns {*}
96
+ */
97
+ this.generateUrl = function(name, params = {}, query = {}) {
98
+ const route = $routesByName[name];
99
+ if(!route) {
100
+ throw new RouterError(`Route not found for name: ${name}`);
101
+ }
102
+ return route.url({ params, query });
103
+ };
104
+
105
+ /**
106
+ *
107
+ * @param {string|{name:string,params?:Object, query?:Object }} target
108
+ * @returns {{route:Route, params:Object, query:Object, path:string}}
109
+ */
110
+ this.resolve = function(target) {
111
+ if(Validator.isJson(target)) {
112
+ const route = $routesByName[target.name];
113
+ if(!route) {
114
+ throw new RouterError(`Route not found for name: ${target.name}`);
115
+ }
116
+ return {
117
+ route,
118
+ params: target.params,
119
+ query: target.query,
120
+ path: route.url({ ...target })
121
+ };
122
+ }
123
+
124
+ const [urlPath, urlQuery] = target.split('?');
125
+ const path = '/'+trim(urlPath, '/');
126
+ let routeFound = null, params;
127
+
128
+ for(const route of $routes) {
129
+ params = route.match(path);
130
+ if(params) {
131
+ routeFound = route;
132
+ break;
133
+ }
134
+ }
135
+ if(!routeFound) {
136
+ throw new RouterError(`Route not found for url: ${urlPath}`);
137
+ }
138
+ const queryParams = {};
139
+ if(urlQuery) {
140
+ const queries = new URLSearchParams(urlQuery).entries();
141
+ for (const [key, value] of queries) {
142
+ queryParams[key] = value;
143
+ }
144
+ }
145
+
146
+ return { route: routeFound, params, query: queryParams, path: target };
147
+ };
148
+
149
+ /**
150
+ *
151
+ * @param {Function} listener
152
+ * @returns {(function(): void)|*}
153
+ */
154
+ this.subscribe = function(listener) {
155
+ if(!Validator.isFunction(listener)) {
156
+ throw new RouterError('Listener must be a function');
157
+ }
158
+ $listeners.push(listener);
159
+ return () => {
160
+ $listeners.splice($listeners.indexOf(listener), 1);
161
+ };
162
+ };
163
+
164
+ /**
165
+ *
166
+ * @param {Route} route
167
+ * @param {Object} params
168
+ * @param {Object} query
169
+ * @param {string} path
170
+ */
171
+ this.handleRouteChange = function(route, params, query, path) {
172
+ $currentState.route = route;
173
+ $currentState.params = params;
174
+ $currentState.query = query;
175
+ $currentState.path = path;
176
+
177
+ console.log($currentState.query)
178
+ const middlewares = [...route.middlewares(), trigger];
179
+ let currentIndex = 0;
180
+ const request = { ...$currentState };
181
+
182
+ const next = (editableRequest) => {
183
+ currentIndex++;
184
+ if(currentIndex >= middlewares.length) {
185
+ return;
186
+ }
187
+ return middlewares[currentIndex](editableRequest || request, next);
188
+ };
189
+ return middlewares[currentIndex](request, next);
190
+ };
191
+
192
+ }
193
+
194
+ Router.routers = {};
195
+
196
+ /**
197
+ *
198
+ * @param {{mode: 'memory'|'history'|'hash', name:string, entry: string}} options
199
+ * @param {Function} callback
200
+ * @param {Element} container
201
+ */
202
+ Router.create = function(options, callback) {
203
+ if(!Validator.isFunction(callback)) {
204
+ DebugManager.error('Router', 'Callback must be a function', e);
205
+ throw new RouterError('Callback must be a function');
206
+ }
207
+ const router = new Router(options);
208
+ Router.routers[options.name || DEFAULT_ROUTER_NAME] = router;
209
+ callback(router);
210
+
211
+ router.init(options.entry);
212
+
213
+ return {
214
+ mount: (container) => {
215
+ if(Validator.isString(container)) {
216
+ const mountContainer = document.querySelector(container);
217
+ if(!mountContainer) {
218
+ throw new RouterError(`Container not found for selector: ${container}`);
219
+ }
220
+ container = mountContainer;
221
+ } else if(!Validator.isElement(container)) {
222
+ throw new RouterError('Container must be a string or an Element');
223
+ }
224
+
225
+ RouterComponent(router, container);
226
+ }
227
+ };
228
+ };
229
+
230
+ Router.get = function(name) {
231
+ return Router.routers[name || DEFAULT_ROUTER_NAME];
232
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ *
3
+ * @param {Router} router
4
+ * @param {?HTMLElement} container
5
+ */
6
+ export function RouterComponent(router, container) {
7
+
8
+ const $cache = new Map();
9
+
10
+ const updateContainer = function(node) {
11
+ container.innerHTML = '';
12
+ container.appendChild(node);
13
+ };
14
+
15
+ const handleCurrentRouterState = function(state) {
16
+ if(!state.route) {
17
+ return;
18
+ }
19
+ const { route, params, query, path } = state;
20
+ if($cache.has(path)) {
21
+ const cacheNode = $cache.get(path);
22
+ console.log(cacheNode);
23
+ updateContainer(cacheNode);
24
+ return;
25
+ }
26
+ const Component = route.component()
27
+ console.log({ params, query })
28
+ const node = Component({ params, query });
29
+ $cache.set(path, node);
30
+ updateContainer(node);
31
+ };
32
+
33
+ router.subscribe(handleCurrentRouterState);
34
+
35
+ handleCurrentRouterState(router.currentState());
36
+ return container;
37
+ }
@@ -0,0 +1,27 @@
1
+ import Validator from "../utils/validator.js";
2
+ import {Link as NativeLink} from "../../elements.js";
3
+ import Router from "./Router.js";
4
+ import RouterError from "../errors/RouterError.js";
5
+
6
+
7
+ export function Link(attributes, children){
8
+ const target = attributes.to || attributes.href;
9
+ if(Validator.isString(target)) {
10
+ const router = Router.get();
11
+ return NativeLink({ ...attributes, href: target}, children).nd.on.prevent.click(() => {
12
+ router.push(target);
13
+ });
14
+ }
15
+ const router = Router.get(target.router);
16
+ if(!router) {
17
+ throw new RouterError('Router not found "'+target.router+'" for link "'+target.name+'"');
18
+ }
19
+ const url = router.generateUrl(target.name, target.params, target.query);
20
+ return NativeLink({ ...attributes, href: url }, children).nd.on.prevent.click(() => {
21
+ router.push(url);
22
+ });
23
+ }
24
+
25
+ Link.blank = function(attributes, children){
26
+ return NativeLink({ ...attributes, target: '_blank'}, children);
27
+ };