Match-id-12cb1d500878baa753bd9f05655f9af416af5b38

This commit is contained in:
* 2024-03-13 19:37:35 +08:00
parent e0cfae4195
commit aa5ece9589
14 changed files with 1530 additions and 0 deletions

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
'use strict';
module.exports = {
printWidth: 120, // 一行120字符数如果超过会进行换行
tabWidth: 2, // tab等2个空格
useTabs: false, // 用空格缩进行
semi: true, // 行尾使用分号
singleQuote: true, // 字符串使用单引号
quoteProps: 'as-needed', // 仅在需要时在对象属性添加引号
jsxSingleQuote: false, // 在JSX中使用双引号
trailingComma: 'es5', // 使用尾逗号(对象、数组等)
bracketSpacing: true, // 对象的括号间增加空格
bracketSameLine: false, // 将多行JSX元素的>放在最后一行的末尾
arrowParens: 'avoid', // 在唯一的arrow函数参数周围省略括号
vueIndentScriptAndStyle: false, // 不缩进Vue文件中的<script>和<style>标记内的代码
endOfLine: 'lf', // 仅限换行(\n
};

View File

@ -0,0 +1,21 @@
{
"name": "@inula/pinia-adapter",
"version": "0.0.1",
"description": "pinia adapter",
"main": "./src/index.ts",
"scripts": {
"test": "vitest --ui"
},
"dependencies": {
"@babel/cli": "^7.23.9",
"@babel/core": "^7.23.9",
"@babel/plugin-syntax-typescript": "^7.23.3",
"@cloudsop/horizon": "workspace:*"
},
"devDependencies": {
"@testing-library/user-event": "^12.1.10",
"@vitest/ui": "^0.34.5",
"jsdom": "^24.0.0",
"vitest": "^0.34.5"
}
}

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
export * from './pinia';

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { createStore } from '@cloudsop/horizon';
import { ref } from './ref';
const storeMap = new Map();
export function defineStore(id, cbOrDef) {
if (!cbOrDef && typeof id === 'object') {
cbOrDef = id;
id = cbOrDef.id;
}
if (typeof cbOrDef === 'object') {
return defineOptionsStore(id, cbOrDef);
}
return () => {
const data = cbOrDef();
if (storeMap.has(cbOrDef)) {
return storeMap.get(cbOrDef)();
}
const entries = Object.entries(data);
const state = Object.fromEntries(entries.filter(([key, val]) => val.isRef).map(([key, val]) => [key, val.value]));
const computed = Object.fromEntries(
entries.filter(([key, val]) => val.isComputed).map(([key, val]) => [key, val.raw])
);
const actions = Object.fromEntries(entries.filter(([key, val]) => !val.isRef && !val.isComputed));
const useStore = createStore({
id,
state,
computed,
actions: enhanceActions(actions),
});
Object.entries(data)
.filter(([key, val]) => val.isRef)
.forEach(([key, val]) =>
val.watch(newVal => {
useStore().$s[key] = newVal;
})
);
storeMap.set(cbOrDef, useStore);
return useStore();
};
}
function enhanceActions(actions) {
return Object.fromEntries(
Object.entries(actions).map(([key, value]) => {
return [
key,
function (store, ...args) {
return value.bind(this)(...args);
},
];
})
);
}
function defineOptionsStore(id, { state, actions, getters }) {
if (typeof state === 'function') {
state = state();
}
let useStore = null;
return () => {
if (!useStore) {
useStore = createStore({
id,
state,
actions: enhanceActions(actions),
computed: getters,
});
}
return useStore();
};
}
export function mapStores(...stores) {
const result = {};
stores.forEach(store => {
const expandedStore = store();
result[`${expandedStore.id}Store`] = () => expandedStore;
});
return result;
}
export function storeToRefs(store) {
return Object.fromEntries(
Object.entries(store.$s).map(([key, value]) => {
return [key, ref(value)];
})
);
}
export function createPinia() {
const result = {
install: app => {}, // do something?
use: plugin => result,
state: {}, //
};
return result;
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
export function ref(initialValue) {
let [b, r] = window.horizon.useState(false);
let state = window.horizon.useRef(initialValue);
let listeners = new Set();
return new Proxy(
{ value: initialValue },
{
get: (target, name) => {
if (name === 'value' || name === 'current') {
return state.current;
}
if (name === Symbol.toPrimitive) {
return () => state.current;
}
if (name === 'isRef') return true;
if (name === 'watch')
return cb => {
listeners.add(cb);
};
if (name === 'raw') return initialValue;
},
set: (target, name, value) => {
if (name === 'value') {
if (state.current === value) return true;
state.current = value;
r(!b);
Array.from(listeners.values()).forEach(listener => {
listener(value);
});
return true;
} else if (name === 'current') {
if (state.current === value) return true;
state.current = value;
return true;
}
},
}
);
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck For the compiled code.
import { beforeEach, describe, it, expect } from 'vitest'
import {createPinia, defineStore} from '../src/pinia';
describe('pinia state', () => {
beforeEach(() => {
});
const useStore = defineStore('main', {
state: () => ({
name: 'Eduardo',
counter: 0,
nested: {n: 0},
}),
});
it('can directly access state at the store level', () => {
const store = useStore();
expect(store.name).toBe('Eduardo');
store.name = 'Ed';
expect(store.name).toBe('Ed');
});
});

View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true
},
"ts-node": {
"esm": true
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
export default {
test: {
environment: 'jsdom',
},
}

View File

@ -0,0 +1,32 @@
export default function Lazy({ loadingComponent, children }) {
const content = window.horizon.useRef(null);
const [b, r] = window.horizon.useState(false);
const reload = window.horizon.useRef(() => {
r(!b);
});
window.horizon.useEffect(() => {
reload.current = () => {
r(!b);
};
});
if (content.current) {
return typeof content.current === 'function' ? <component is={content.current} />:content.current
} else {
setTimeout(()=>{
try {
const lazy = loadingComponent();
lazy.then(function (resolved) {
content.current = resolved.default;
reload.current();
});
} catch(err){
content.current = loadingComponent;
reload.current();
}
},1)
return children;
}
}

View File

@ -0,0 +1,72 @@
import { useInjection } from './vue-horizon';
let counter = 0;
export function useLocalStore({ name, data, methods, computed, created, inject }) {
const newStore = window.horizon.useRef(true);
inject?.forEach(key => {
computed[key] = () => {
useInjection(key);
};
});
const store = window.horizon.useRef(
window.horizon.createStore({
id: `${name || 'unknown'}-${Date.now()}-${counter++}`,
state: (typeof data === 'function' ? data() : data) || {},
actions: {
...Object.fromEntries(
Object.entries(methods || {}).map(([key, method]) => {
return [
key,
function (state, ...args) {
return method.bind(this)(...args);
},
];
})
),
_setValue: function (state, key, value) {
state[key.replace(/^this\./, '')] = value;
},
},
computed: computed || {},
})
);
if (newStore.current) {
requestAnimationFrame(() => {
if (created) {
created.bind(store.current())();
}
});
}
newStore.current = false;
const currentStore = store.current();
const mix = {
...currentStore,
...currentStore.$s,
...Object.fromEntries(
Object.entries(currentStore.$a).map(([key, action]) => {
return [
key,
(...args) => {
return action.bind(currentStore)(...args);
},
];
})
),
};
Object.entries(currentStore.$c).forEach(([key, computed]) => {
const getter = (none, ...args) => {
return computed.bind(currentStore)(...args);
};
Object.defineProperty(mix, key, {
get: getter,
enumerable: true,
});
});
return mix;
}

View File

@ -0,0 +1,3 @@
export function forwardRef(render){
return window.horizon.forwardRef(render);
}

View File

@ -0,0 +1,110 @@
import { ref } from './vue-horizon';
const storeMap = new Map();
export function defineStore(id, cbOrDef) {
if (!cbOrDef && typeof id === 'object') {
cbOrDef = id;
id = cbOrDef.id;
}
if (typeof cbOrDef === 'object') {
return defineOptionsStore(id, cbOrDef);
}
return () => {
const data = cbOrDef();
if (storeMap.has(cbOrDef)) {
return storeMap.get(cbOrDef)();
}
const entries = Object.entries(data);
const state = Object.fromEntries(entries.filter(([key, val]) => val.isRef).map(([key, val]) => [key, val.value]));
const computed = Object.fromEntries(
entries.filter(([key, val]) => val.isComputed).map(([key, val]) => [key, val.raw])
);
const actions = Object.fromEntries(entries.filter(([key, val]) => !val.isRef && !val.isComputed));
const useStore = window.horizon.createStore({
id,
state,
computed,
actions: enhanceActions(actions),
});
Object.entries(data)
.filter(([key, val]) => val.isRef)
.forEach(([key, val]) =>
val.watch(newVal => {
useStore().$s[key] = newVal;
})
);
storeMap.set(cbOrDef, useStore);
return useStore();
};
}
function enhanceActions(actions) {
return Object.fromEntries(
Object.entries(actions).map(([key, value]) => {
return [
key,
function (store, ...args) {
return value.bind(this)(...args);
},
];
})
);
}
function defineOptionsStore(id, { state, actions, getters }) {
if (typeof state === 'function') {
state = state();
}
let useStore = null;
return () => {
if (!useStore) {
useStore = window.horizon.createStore({
id,
state,
actions: enhanceActions(actions),
computed: getters,
});
}
return useStore();
};
}
export function mapStores(...stores) {
const result = {};
stores.forEach(store => {
const expandedStore = store();
result[`${expandedStore.id}Store`] = () => expandedStore;
});
return result;
}
export function storeToRefs(store) {
return Object.fromEntries(
Object.entries(store.$s).map(([key, value]) => {
return [key, ref(value)];
})
);
}
export function createPinia() {
const result = {
install: app => {}, // do something?
use: plugin => result,
state: {}, //
};
return result;
}

View File

@ -0,0 +1,688 @@
import LazyComponent from './LazyComponent';
import { computed } from './vue-horizon';
const EXACT = 'exact';
const DYNAMIC = 'dynamic';
export let useRouter = () => {
throw Error('no router found');
};
export let useRoute = () => {
throw Error('no router found');
};
function parseRouteDescriptor(route) {
const parts = route.split('/');
return parts
.filter(part => part.length)
.map(part => {
const result = {};
result.raw = part;
if (part.startsWith(':')) {
const lastChar = part[part.length - 1];
result.type = DYNAMIC;
if (lastChar === '?') {
result.optional = true;
part = part.substring(0, part.length - 1);
}
if (lastChar === '*') {
result.optional = true;
result.repeat = true;
part = part.substring(0, part.length - 1);
}
if (lastChar === '+') {
result.repeat = true;
part = part.substring(0, part.length - 1);
}
result.match = part.match(/\((.*)\)$/)?.[1] || '.*';
result.name = part.match(/^:?(\w*)/)?.[1];
} else {
result.type = EXACT;
if (part[part.length - 1] === '?') {
result.optional = true;
part = part.substring(0, part.length - 1);
}
result.match = part;
}
return result;
});
}
function parseRoute(route) {
const rawRoute = route;
const queryRegex = /(?:\?|&)([^=^&]+)(=([^&]+))?/g;
const hash = route.match(/#(.+)/)?.[1] || '';
route = route.replace('#' + hash, '');
const queryParams = {};
let queryMatch;
while ((queryMatch = queryRegex.exec(route))) {
value = queryMatch[3];
queryParams[queryMatch[1]] = value === undefined ? true : value;
}
const parts = route
.replace(/\?.*/g, '')
.split('/')
.filter(part => part.length);
return { queryParams, hash, parts, route: rawRoute, path: route };
}
function matchRoute(route, descriptor) {
const parsedRoute = parseRoute(route);
let routeIndex = 0;
let descriptorIndex = 0;
let finished = false;
let params = {};
while (!finished) {
const dPart = descriptor[descriptorIndex];
const pPart = parsedRoute.parts[routeIndex];
if (!dPart) {
if (pPart) {
return null;
}
finished = true;
continue;
}
if (!pPart) {
if (dPart.optional) {
if (dPart.repeat) {
params[dPart.name] = [];
}
descriptorIndex++;
continue;
} else {
return null;
}
}
if (dPart.type === EXACT) {
if (dPart.match !== pPart) {
if (dPart.optional) {
descriptorIndex++;
continue;
} else return null;
}
} else {
if (dPart.repeat) {
const arr = [];
if (dPart.match && !pPart.match(new RegExp(dPart.match))) {
if (dPart.optional) {
descriptorIndex++;
params[dPart.name] = [];
continue;
}
return null;
}
let currentPpart = parsedRoute.parts[routeIndex];
const regexp = dPart.match && new RegExp(dPart.match);
while (currentPpart) {
if (regexp && currentPpart.match(regexp)) {
arr.push(currentPpart);
routeIndex++;
currentPpart = parsedRoute.parts[routeIndex];
} else if (regexp) {
currentPpart = null;
} else {
arr.push(currentPpart);
routeIndex++;
currentPpart = parsedRoute.parts[routeIndex];
}
}
params[dPart.name] = arr.map(item => decodeURIComponent(item));
descriptorIndex++;
continue;
}
if (dPart.match && !pPart.match(new RegExp(dPart.match))) {
if (dPart.optional) {
descriptorIndex++;
continue;
}
return null;
}
params[dPart.name] = decodeURIComponent(pPart);
}
descriptorIndex++;
routeIndex++;
}
if (descriptor[descriptorIndex] && !descriptor[descriptorIndex].optional) {
return null;
}
return {
query: parsedRoute.queryParams,
hash: parsedRoute.hash,
route: parsedRoute.route,
params,
};
}
// {
// path: "/user/:id",
// children: [
// {
// path: "/settings",
// name: "user-settings",
// component: ($route) =>
// `Displaying settings for User no.${$route.params.id}`,
// },
// {
// path: "",
// name: "user-home",
// component: ($route) => `User no.${$route.params.id}`,
// },
// {
// path: "/posts",
// name: "user-posts",
// component: ($route) =>
// `Displaying posts by User no.${$route.params.id}`,
// },
// ],
// },
// {
// path: "",
// name: "home",
// component: ($route) => "Displaying home",
// },
// {
// path: "/posts",
// name: "posts",
// component: ($route) => "Displaying posts",
// children: [
// {
// path: "/:postId(\\d+)",
// name: "post",
// component: ($route) => `Displaing post no.${$route.params.postId}`,
// children: [
// {
// path: "edit",
// name: "post-edit",
// component: ($route) => `Editing post no.${$route.params.postId}`,
// },
// ],
// },
// {
// path: "new",
// name: "post-add",
// component: () => "Adding new post",
// },
// ],
// },
// {
// path: "/:dirpath(.*)*",
// component: ($route) =>
// `error resolving path at /${$route.params.dirpath.join("/")}`,
// },
// ];
export function createMemoryHistory() {
let history = ['/'];
let pointer = 0;
function push(path, flags = {}) {
const { replace } = flags;
if (replace && history.length) {
history[pointer] = path;
history.length = pointer + 1;
} else if (typeof path === 'string') {
history.length = pointer + 1;
history[pointer + 1] = path;
pointer++;
history.length = pointer + 1;
}
}
function replace(path, flags = {}) {
flags.replace = true;
push(path, flags);
}
function go(steps) {
if (steps > 0) {
let i = 0;
while (i < steps) {
if (pointer + 1 < history.length) {
pointer++;
}
i++;
}
} else if (steps < 0) {
let i = 0;
while (i > steps) {
if (pointer > 0) {
pointer--;
}
i--;
}
}
}
function get() {
return history[pointer];
}
return {
push,
replace,
go,
get,
addListener: () => {},
};
}
export function createWebHashHistory() {
const listeners = [];
window.addEventListener('popstate', event => {
listeners.forEach(listener => listener(window.location.hash.substring(1)));
});
function push(path, flags = {}) {
if (flags.replace) {
window.history.replaceState(null, null, `#${path}`);
window.location.hash = path;
} else {
window.history.pushState(null, null, `#${path}`);
window.location.hash = path;
}
}
function replace(path, flags = {}) {
window.history.replaceState(null, null, `#${path}`);
window.location.hash = path;
}
function go(steps) {
window.history.go(steps);
}
function get() {
return window.location.hash.substring(1);
}
function addListener(listener) {
listeners.push(listener);
}
return {
push,
replace,
go,
get,
addListener,
};
}
export function createWebHistory() {
const host = `${window.location.protocol}//${window.location.host}`;
const listeners = [];
window.addEventListener('popstate', event => {
event.preventDefault();
listeners.forEach(listener => listener(window.location.pathname));
});
function push(path, flags = {}) {
if (flags.replace) {
window.history.replaceState(null, null, path);
} else {
window.history.pushState(null, null, path);
}
}
function replace(path, flags = {}) {
window.history.replaceState(null, null, path);
}
function go(steps) {
window.history.go(steps);
}
function get() {
return window.location.href.replace(host, '');
}
function addListener(listener) {
listeners.push(listener);
}
return {
push,
replace,
go,
get,
addListener,
};
}
export function createRouter({ routes, history }) {
let counter = 0;
const directRoutes = {};
let DepthContext;
function Redirect({ fallback, from, to, ...props }) {
if (match(history.get()) === match(from) && history.get() !== to) {
setTimeout(() => {
// rendered.current = false;
history.replace(to);
triggerListeners();
}, 1);
return null;
}
const child = <LazyComponent loadingComponent={fallback}>Loading...</LazyComponent>;
return child;
}
function processRoutes(routes, base = '/') {
routes.forEach(route => {
const absolutePath = base + '/' + route.path;
const name = route.name || `_unnamedRoute${counter++}`;
if (route.component) {
directRoutes[name] = {
...route,
component: Array.isArray(route.component)
? route.component
: [
function LazyWrapper() {
return <LazyComponent loadingComponent={route.component}>Loading...</LazyComponent>;
},
],
path: absolutePath.replaceAll(/\/+/g, '/'),
descriptor: parseRouteDescriptor(absolutePath),
meta: route.meta,
};
}
if (route.children) {
processChildRoutes(route.children, [directRoutes[name]], base);
}
if (route.redirect) {
directRoutes[name] = {
...route,
component: [
function RedirectWrapper() {
return <Redirect from={route.path} to={route.redirect} fallback={route.component} />;
},
],
path: absolutePath.replaceAll(/\/+/g, '/'),
descriptor: parseRouteDescriptor(absolutePath),
meta: route.meta,
};
}
});
}
function processChildRoutes(routes, parents, base) {
processRoutes(
routes.map(route => {
const processed = { ...route };
const lastParent = parents[parents.length - 1];
if (!route.path.startsWith(lastParent.path)) {
processed.path = /*lastParent +*/ route.path;
}
if (route.redirect) {
processed.component = (lastParent.component || []).concat([
function RedirectWrapperChild() {
return <Redirect from={route.path} to={route.redirect} fallback={route.component} />;
},
]);
} else if (route.component) {
processed.component = (lastParent.component || []).concat([
function LazyWrapperChild() {
return <LazyComponent loadingComponent={route.component}>Loading...</LazyComponent>;
},
]);
}
return processed;
})
);
}
processRoutes(routes);
function match(path = '') {
let matched;
Object.entries(directRoutes)
.filter(([name, route]) => {
const match = matchRoute(path, route.descriptor);
if (match) {
matched = name;
}
return match;
})
.sort((a, b) => {
return b[1].descriptor.length - a[1].descriptor.length;
})[0];
return matched;
}
function resolve({ path, query, params, hash, name }) {
let descriptor;
if (path) {
descriptor = parseRouteDescriptor(path);
} else if (name) {
descriptor = directRoutes[name].descriptor;
}
let result = '';
descriptor.forEach(part => {
if (part.type === EXACT) {
result += '/' + part.match;
} else if (part.type === DYNAMIC) {
result += '/' + params[part.name];
}
});
if (query) {
result +=
'?' +
Object.entries(query)
.map(([key, value]) => `${key}=${value}`)
.join('&');
}
if (hash) {
result += `#${hash}`;
}
return result;
}
function push(path, flags) {
if (typeof path === 'string') {
history.push(path, flags);
} else {
history.push(resolve(path), flags);
}
triggerListeners();
}
function replace(path, flags) {
history.push(path, flags);
triggerListeners();
}
function go(steps) {
history.go(steps);
triggerListeners();
}
function get() {
return {
view: match(history.get()),
route: parseRoute(history.get()),
descriptor: directRoutes[match(history.get())].descriptor,
match: matchRoute(history.get(), directRoutes[match(history.get())].descriptor),
component: [directRoutes[match(history.get())].component],
};
}
let listenerId = 0;
const listeners = new Map();
const hookListeners = new Set();
function addHookListener(guard) {
hookListeners.add(guard);
return () => {
hookListeners.delete(guard);
};
}
const afterEachListeners = new Set();
const beforeEachListeners = new Set();
const beforeResolveListeners = new Set();
const errorListeners = new Set();
function triggerListeners() {
listeners.forEach(listener => listener());
Array.from(hookListeners.values()).forEach(listener => listener());
}
history.addListener(triggerListeners);
useRoute = () => {
const current = get();
const routeName = match(history.get());
const [b, r] = window.horizon.useState(false);
window.horizon.useEffect(() => {
return addHookListener(() => {
r(!b);
});
});
return {
fullPath: window.location.href,
hash: window.location.hash,
matched: current,
name: routeName,
params: current.match.params,
path: window.location.pathname,
query: current.match.query,
};
};
useRouter = () => ({
get currentRoute() {
return computed(() => {
return get().route;
});
},
listening: true, // TODO: implement
get options() {
throw Error('not implemented'); // TODO: implement
},
addRoute(parentName, route) {
throw Error('not implemented'); // TODO: implement
},
afterEach(guard) {
afterEachListeners.add(guard);
return () => {
afterEachListeners.delete(guard);
};
},
back() {
go(-1);
},
beforeEach(guard) {
beforeEachListeners.add(guard);
return () => {
beforeEachListeners.delete(guard);
};
},
beforeResolve(guard) {
beforeResolveListeners.add(guard);
return () => {
beforeResolveListeners.delete(guard);
};
},
forward() {
go(1);
},
getRoutes() {
return directRoutes;
},
go,
hasRoute(name) {
return !!routes[name];
},
isReady() {
return true; // NOTE: without server side rendering router is always ready
},
onError(guard) {
errorListeners.add(guard);
return () => {
errorListeners.delete(guard);
};
},
push,
removeRoute(name) {
delete directRoutes[name];
},
replace(to) {
replace(to);
},
resolve,
match,
});
function RouterLink({ replace, to, children }) {
return (
<a
style={{
cursor: 'pointer',
}}
onClick={() => {
push(to, { replace });
}}
>
{children}
</a>
);
}
function RouterView(props) {
console.log('router view', { props });
if (!DepthContext) {
DepthContext = window.horizon.createContext(0);
}
let depth = window.horizon.useContext(DepthContext);
const [boo, re] = window.horizon.useState(true);
window.horizon.useEffect(() => {
const id = listenerId++;
listeners.set(id, () => {
re(!boo);
});
return () => {
listeners.delete(id);
};
});
const result = directRoutes[match(history.get())];
const View = () => {
console.log('View', { result });
return window.horizon.createElement(
result.component[depth],
result.props ? { ...result.route } : { $route: result.route }
);
};
const finalView = (
<DepthContext.Provider value={depth + 1}>
<View />
</DepthContext.Provider>
);
return finalView;
}
return { RouterLink, RouterView };
}

View File

@ -0,0 +1,302 @@
export function nextTick(callback) {
return new Promise(function (resolve) {
setTimeout(() => {
callback();
resolve();
}, 0);
});
}
export function onMounted(callback) {
window.horizon.useEffect(() => {
callback();
});
}
export function onBeforeUnmount(callback) {
window.horizon.useEffect(() => {
return () => {
callback();
};
});
}
export function onUnmounted(callback) {
window.horizon.useEffect(() => {
return () => {
callback();
};
});
}
export function toRef(val) {
if (typeof val === 'function') {
return computed(val);
}
if (val.toRef) {
return val;
}
return ref(val);
}
export function unref(val) {
if (val.isRef) {
return val.value;
}
return val;
}
export function toRefs(obj) {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, toRef(value)]));
}
export function ref(initialValue) {
let [b, r] = window.horizon.useState(false);
let state = window.horizon.useRef(initialValue);
let listeners = new Set();
return new Proxy(
{ value: initialValue },
{
get: (target, name) => {
if (name === 'value' || name === 'current') {
return state.current;
}
if (name === Symbol.toPrimitive) {
return () => state.current;
}
if (name === 'isRef') return true;
if (name === 'watch')
return cb => {
listeners.add(cb);
};
if (name === 'raw') return initialValue;
},
set: (target, name, value) => {
if (name === 'value') {
if (state.current === value) return true;
state.current = value;
r(!b);
Array.from(listeners.values()).forEach(listener => {
listener(value);
});
return true;
} else if (name === 'current') {
if (state.current === value) return true;
state.current = value;
return true;
}
},
}
);
}
export function reactive(initialValue) {
let [b, r] = window.horizon.useState(false);
const data = window.horizon.useRef(initialValue);
const listeners = new Set();
return new Proxy(initialValue, {
set: (target, name, value) => {
r(!b);
Array.from(listeners).forEach(listener => listener(value, data.current[name]));
data.current[name] = value;
return true;
},
get: (target, name) => {
if (name === 'isRef') return true;
if (name === 'watch')
return listener => {
listeners.add(listener);
};
return data.current[name];
},
});
}
export function computed(foo) {
if (typeof foo === 'object') return writtableComputed(foo);
let listeners = new Set();
let previousValue = foo();
return new Proxy(
{},
{
get: (target, name) => {
if (name === 'value') {
const newValue = foo();
if (JSON.stringify(newValue) !== JSON.stringify(previousValue)) {
previousValue = newValue;
Array.from(listeners.values()).forEach(listener => {
listener(newValue);
});
}
return previousValue;
}
if (name === 'isComputed') return true;
if (name === 'raw') return () => foo;
if (name === 'watch')
return cb => {
listeners.add(cb);
};
},
}
);
}
function writtableComputed(methods) {
let listeners = new Set();
return new Proxy(
{},
{
get: (target, name) => {
if (name === 'value') return methods.get();
if (name === 'isComputed') return true;
if (name === 'raw') return () => foo;
if (name === 'watch')
return cb => {
listeners.add(cb);
};
},
set: (target, name, value) => {
methods.set(value);
Array.from(listeners.values()).forEach(listener => {
listener(value);
});
return true;
},
}
);
}
const globals = new Map();
export function provide(key, value) {
globals.set(key, value);
}
export function inject(key) {
return globals.get(key);
}
export const useInjection = inject;
export function Teleport({ to, children }) {
const container = window.horizon.useState(() => {
document.createElement('div');
});
window.horizon.useEffect(() => {
to.appendChild(container);
return () => {
to.removeChild(container);
};
}, [to, container]);
return window.horizon.createPortal(children, container);
}
export function defineProps(props) {
console.log('define props', props);
}
export function getCurrentInstance() {
return window.horizon.getProcessingVNode();
}
export function toRaw(maybeRef) {
if (maybeRef.isRef || maybeRef.isComputed) return maybeRef.raw;
else return maybeRef;
}
export function watch(data, cb, options) {
if (data.isRef || data.isComputed) {
data.watch(cb);
} else if (typeof data === 'function') {
watch(computed(data), cb, options);
} else {
throw Error('Watch method not implemented');
}
}
export function useWindowSize(options = {}) {
const {
initialWidth = Number.POSITIVE_INFINITY,
initialHeight = Number.POSITIVE_INFINITY,
listenOrientation = true,
includeScrollbar = true,
} = options;
const width = ref(initialWidth);
const height = ref(initialHeight);
function update() {
if (window) {
if (includeScrollbar) {
width.value = window.innerWidth;
height.value = window.innerHeight;
} else {
width.value = window.document.documentElement.clientWidth;
height.value = window.document.documentElement.clientHeight;
}
}
}
update();
window.horizon.useEffect(() => {
window.addEventListener('resize', update);
if (listenOrientation) window.addEventListener('orientationchange', update);
return () => {
window.removeEventListener('resize', update);
if (listenOrientation) window.removeEventListener('orientationchange', update);
};
});
return { width: width.value, height: height.value };
}
export function defineComponent(data) {
console.log('define component', data);
return {
data() {
return {};
},
};
}
export function defineEmits(props, emitKeys) {
const listeners = {};
const eventprops = Object.entries(props)
.filter(([propname, listener]) => propname.startsWith('on'))
.map(([propname, listener]) => [propname.substring(2).toLowerCase(), listener]);
emitKeys.forEach(eventName => {
const key = eventName.toLowerCase();
listeners[key] = eventprops.find(evt => evt[0] === key)?.[1] || (() => {});
});
return (event, ...data) => {
if (listeners[event.toLowerCase()]) {
listeners[event.toLowerCase()](...data);
}
};
}
export function defineExpose(props, expose) {
props.expose = expose;
}
export function createVNode(component, props) {
return window.horizon.createElement(component, props, props.children);
}
export function render(vnode, target) {
window.horizon.render(vnode, target);
}