simplyview 2.1.0 → 3.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/simply.app.js +1120 -0
- package/dist/simply.app.js.map +7 -0
- package/dist/simply.app.min.js +2 -0
- package/dist/simply.app.min.js.map +7 -0
- package/dist/simply.everything.js +1583 -2025
- package/dist/simply.everything.js.map +7 -0
- package/dist/simply.everything.min.js +2 -0
- package/dist/simply.everything.min.js.map +7 -0
- package/package.json +8 -3
- package/src/action.mjs +12 -0
- package/src/activate.mjs +63 -0
- package/src/app.mjs +40 -0
- package/src/bind.mjs +572 -0
- package/src/command.mjs +125 -0
- package/src/everything.mjs +27 -0
- package/src/include.mjs +191 -0
- package/src/key.mjs +55 -0
- package/src/model.mjs +151 -0
- package/src/route.mjs +222 -0
- package/src/state.mjs +536 -0
- package/js/.eslintrc.json +0 -29
- package/js/simply.action.js +0 -115
- package/js/simply.activate.js +0 -79
- package/js/simply.api.js +0 -228
- package/js/simply.app.js +0 -63
- package/js/simply.collect.js +0 -72
- package/js/simply.command.js +0 -196
- package/js/simply.include.js +0 -226
- package/js/simply.keyboard.js +0 -62
- package/js/simply.modules.js +0 -22
- package/js/simply.observe.js +0 -349
- package/js/simply.path.js +0 -48
- package/js/simply.render.js +0 -125
- package/js/simply.resize.js +0 -80
- package/js/simply.route.js +0 -234
- package/js/simply.view.js +0 -35
- package/js/simply.viewmodel.js +0 -189
- /package/{js/simply.include.next.js → src/include.next.js} +0 -0
package/src/command.mjs
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
class SimplyCommands {
|
|
2
|
+
constructor(options={}) {
|
|
3
|
+
if (!options.app) {
|
|
4
|
+
options.app = {}
|
|
5
|
+
}
|
|
6
|
+
if (!options.app.container) {
|
|
7
|
+
options.app.container = document.body
|
|
8
|
+
}
|
|
9
|
+
this.$handlers = options.handlers || defaultHandlers
|
|
10
|
+
if (options.commands) {
|
|
11
|
+
Object.assign(this, options.commands)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const commandHandler = (evt) => {
|
|
15
|
+
const command = getCommand(evt, this.$handlers)
|
|
16
|
+
if (!command) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
if (!this[command.name]) {
|
|
20
|
+
console.error('simply.command: undefined command '+command.name, command.source);
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
const shouldContinue = this[command.name].call(options.app, command.source, command.value)
|
|
24
|
+
if (shouldContinue===false) {
|
|
25
|
+
evt.preventDefault()
|
|
26
|
+
evt.stopPropagation()
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
options.app.container.addEventListener('click', commandHandler)
|
|
32
|
+
options.app.container.addEventListener('submit', commandHandler)
|
|
33
|
+
options.app.container.addEventListener('change', commandHandler)
|
|
34
|
+
options.app.container.addEventListener('input', commandHandler)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function commands(options={}) {
|
|
39
|
+
return new SimplyCommands(options)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getCommand(evt, handlers) {
|
|
43
|
+
var el = evt.target.closest('[data-simply-command]')
|
|
44
|
+
if (el) {
|
|
45
|
+
for (let handler of handlers) {
|
|
46
|
+
if (el.matches(handler.match)) {
|
|
47
|
+
if (handler.check(el, evt)) {
|
|
48
|
+
return {
|
|
49
|
+
name: el.dataset.simplyCommand,
|
|
50
|
+
source: el,
|
|
51
|
+
value: handler.get(el)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const defaultHandlers = [
|
|
62
|
+
{
|
|
63
|
+
match: 'input,select,textarea',
|
|
64
|
+
get: function(el) {
|
|
65
|
+
if (el.tagName==='SELECT' && el.multiple) {
|
|
66
|
+
let values = []
|
|
67
|
+
for (let option of el.options) {
|
|
68
|
+
if (option.selected) {
|
|
69
|
+
values.push(option.value)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return values
|
|
73
|
+
}
|
|
74
|
+
return el.dataset.simplyValue || el.value
|
|
75
|
+
},
|
|
76
|
+
check: function(el, evt) {
|
|
77
|
+
return evt.type=='change' || (el.dataset.simplyImmediate && evt.type=='input')
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
match: 'a,button',
|
|
82
|
+
get: function(el) {
|
|
83
|
+
return el.dataset.simplyValue || el.href || el.value
|
|
84
|
+
},
|
|
85
|
+
check: function(el,evt) {
|
|
86
|
+
return evt.type=='click' && evt.ctrlKey==false && evt.button==0
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
match: 'form',
|
|
91
|
+
get: function(el) {
|
|
92
|
+
let data = {}
|
|
93
|
+
for (let input of Array.from(el.elements)) {
|
|
94
|
+
if (input.tagName=='INPUT'
|
|
95
|
+
&& (input.type=='checkbox' || input.type=='radio')
|
|
96
|
+
) {
|
|
97
|
+
if (!input.checked) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (data[input.name] && !Array.isArray(data[input.name])) {
|
|
102
|
+
data[input.name] = [data[input.name]]
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(data[input.name])) {
|
|
105
|
+
data[input.name].push(input.value)
|
|
106
|
+
} else {
|
|
107
|
+
data[input.name] = input.value
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return data
|
|
111
|
+
},
|
|
112
|
+
check: function(el,evt) {
|
|
113
|
+
return evt.type=='submit'
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
match: '*',
|
|
118
|
+
get: function(el) {
|
|
119
|
+
return el.dataset.simplyValue
|
|
120
|
+
},
|
|
121
|
+
check: function(el, evt) {
|
|
122
|
+
return evt.type=='click' && evt.ctrlKey==false && evt.button==0
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { activate } from './activate.mjs'
|
|
2
|
+
import * as action from './action.mjs'
|
|
3
|
+
import { app } from './app.mjs'
|
|
4
|
+
import { bind } from './bind.mjs'
|
|
5
|
+
import * as command from './command.mjs'
|
|
6
|
+
import { include } from './include.mjs'
|
|
7
|
+
import * as key from './key.mjs'
|
|
8
|
+
import * as model from './model.mjs'
|
|
9
|
+
import * as route from './route.mjs'
|
|
10
|
+
import * as state from './state.mjs'
|
|
11
|
+
|
|
12
|
+
const simply = {
|
|
13
|
+
activate,
|
|
14
|
+
action,
|
|
15
|
+
app,
|
|
16
|
+
bind,
|
|
17
|
+
command,
|
|
18
|
+
include,
|
|
19
|
+
key,
|
|
20
|
+
model,
|
|
21
|
+
route,
|
|
22
|
+
state
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
window.simply = simply
|
|
26
|
+
|
|
27
|
+
export default simply
|
package/src/include.mjs
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
function throttle( callbackFunction, intervalTime ) {
|
|
2
|
+
let eventId = 0
|
|
3
|
+
return () => {
|
|
4
|
+
const myArguments = arguments
|
|
5
|
+
if ( eventId ) {
|
|
6
|
+
return
|
|
7
|
+
} else {
|
|
8
|
+
eventId = globalThis.setTimeout( () => {
|
|
9
|
+
callbackFunction.apply(this, myArguments)
|
|
10
|
+
eventId = 0
|
|
11
|
+
}, intervalTime )
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const runWhenIdle = (() => {
|
|
17
|
+
if (globalThis.requestIdleCallback) {
|
|
18
|
+
return (callback) => {
|
|
19
|
+
globalThis.requestIdleCallback(callback, {timeout: 500})
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return globalThis.requestAnimationFrame
|
|
23
|
+
})()
|
|
24
|
+
|
|
25
|
+
function rebaseHref(relative, base) {
|
|
26
|
+
let url = new URL(relative, base)
|
|
27
|
+
if (include.cacheBuster) {
|
|
28
|
+
url.searchParams.set('cb',include.cacheBuster)
|
|
29
|
+
}
|
|
30
|
+
return url.href
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let observer, loaded = {}
|
|
34
|
+
let head = globalThis.document.querySelector('head')
|
|
35
|
+
let currentScript = globalThis.document.currentScript
|
|
36
|
+
let getScriptURL, currentScriptURL
|
|
37
|
+
if (!currentScript) {
|
|
38
|
+
getScriptURL = (() => {
|
|
39
|
+
var scripts = document.getElementsByTagName('script')
|
|
40
|
+
var index = scripts.length - 1
|
|
41
|
+
var myScript = scripts[index]
|
|
42
|
+
return () => myScript.src
|
|
43
|
+
})()
|
|
44
|
+
currentScriptURL = getScriptURL()
|
|
45
|
+
} else {
|
|
46
|
+
currentScriptURL = currentScript.src
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const waitForPreviousScripts = async () => {
|
|
50
|
+
// because of the async=false attribute, this script will run after
|
|
51
|
+
// the previous scripts have been loaded and run
|
|
52
|
+
// simply.include.next.js only fires the simply-next-script event
|
|
53
|
+
// that triggers the Promise.resolve method
|
|
54
|
+
return new Promise(function(resolve) {
|
|
55
|
+
var next = globalThis.document.createElement('script')
|
|
56
|
+
next.src = rebaseHref('simply.include.next.js', currentScriptURL)
|
|
57
|
+
next.async = false
|
|
58
|
+
globalThis.document.addEventListener('simply-include-next', () => {
|
|
59
|
+
head.removeChild(next)
|
|
60
|
+
resolve()
|
|
61
|
+
}, { once: true, passive: true})
|
|
62
|
+
head.appendChild(next)
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let scriptLocations = []
|
|
67
|
+
|
|
68
|
+
export const include = {
|
|
69
|
+
cacheBuster: null,
|
|
70
|
+
scripts: (scripts, base) => {
|
|
71
|
+
let arr = scripts.slice()
|
|
72
|
+
const importScript = () => {
|
|
73
|
+
const script = arr.shift()
|
|
74
|
+
if (!script) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
const attrs = [].map.call(script.attributes, (attr) => {
|
|
78
|
+
return attr.name
|
|
79
|
+
})
|
|
80
|
+
let clone = globalThis.document.createElement('script')
|
|
81
|
+
for (const attr of attrs) {
|
|
82
|
+
clone.setAttribute(attr, script.getAttribute(attr))
|
|
83
|
+
}
|
|
84
|
+
clone.removeAttribute('data-simply-location')
|
|
85
|
+
if (!clone.src) {
|
|
86
|
+
// this is an inline script, so copy the content and wait for previous scripts to run
|
|
87
|
+
clone.innerHTML = script.innerHTML
|
|
88
|
+
waitForPreviousScripts()
|
|
89
|
+
.then(() => {
|
|
90
|
+
const node = scriptLocations[script.dataset.simplyLocation]
|
|
91
|
+
node.parentNode.insertBefore(clone, node)
|
|
92
|
+
node.parentNode.removeChild(node)
|
|
93
|
+
importScript()
|
|
94
|
+
})
|
|
95
|
+
} else {
|
|
96
|
+
clone.src = rebaseHref(clone.src, base)
|
|
97
|
+
if (!clone.hasAttribute('async') && !clone.hasAttribute('defer')) {
|
|
98
|
+
clone.async = false //important! do not use clone.setAttribute('async', false) - it has no effect
|
|
99
|
+
}
|
|
100
|
+
const node = scriptLocations[script.dataset.simplyLocation]
|
|
101
|
+
node.parentNode.insertBefore(clone, node)
|
|
102
|
+
node.parentNode.removeChild(node)
|
|
103
|
+
loaded[clone.src]=true
|
|
104
|
+
importScript()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (arr.length) {
|
|
108
|
+
importScript()
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
html: (html, link) => {
|
|
112
|
+
let fragment = globalThis.document.createRange().createContextualFragment(html)
|
|
113
|
+
const stylesheets = fragment.querySelectorAll('link[rel="stylesheet"],style')
|
|
114
|
+
// add all stylesheets to head
|
|
115
|
+
for (let stylesheet of stylesheets) {
|
|
116
|
+
if (stylesheet.href) {
|
|
117
|
+
stylesheet.href = rebaseHref(stylesheet.href, link.href)
|
|
118
|
+
}
|
|
119
|
+
head.appendChild(stylesheet)
|
|
120
|
+
}
|
|
121
|
+
// remove the scripts from the fragment, as they will not run in the
|
|
122
|
+
// order in which they are defined
|
|
123
|
+
let scriptsFragment = globalThis.document.createDocumentFragment()
|
|
124
|
+
const scripts = fragment.querySelectorAll('script')
|
|
125
|
+
for (let script of scripts) {
|
|
126
|
+
let placeholder = globalThis.document.createComment(script.src || 'inline script')
|
|
127
|
+
script.parentNode.insertBefore(placeholder, script)
|
|
128
|
+
script.dataset.simplyLocation = scriptLocations.length
|
|
129
|
+
scriptLocations.push(placeholder)
|
|
130
|
+
scriptsFragment.appendChild(script)
|
|
131
|
+
}
|
|
132
|
+
// add the remainder before the include link
|
|
133
|
+
link.parentNode.insertBefore(fragment, link ? link : null)
|
|
134
|
+
globalThis.setTimeout(function() {
|
|
135
|
+
include.scripts(scriptsFragment.childNodes, link ? link.href : globalThis.location.href )
|
|
136
|
+
}, 10)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let included = {}
|
|
141
|
+
const includeLinks = async (links) => {
|
|
142
|
+
// mark them as in progress, so handleChanges doesn't find them again
|
|
143
|
+
let remainingLinks = [].reduce.call(links, (remainder, link) => {
|
|
144
|
+
if (link.rel=='simply-include-once' && included[link.href]) {
|
|
145
|
+
link.parentNode.removeChild(link)
|
|
146
|
+
} else {
|
|
147
|
+
included[link.href]=true
|
|
148
|
+
link.rel = 'simply-include-loading'
|
|
149
|
+
remainder.push(link)
|
|
150
|
+
}
|
|
151
|
+
return remainder
|
|
152
|
+
}, [])
|
|
153
|
+
|
|
154
|
+
for (let link of remainingLinks) {
|
|
155
|
+
if (!link.href) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
// fetch the html
|
|
159
|
+
const response = await fetch(link.href)
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
console.log('simply-include: failed to load '+link.href);
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
console.log('simply-include: loaded '+link.href);
|
|
165
|
+
const html = await response.text()
|
|
166
|
+
// if succesfull import the html
|
|
167
|
+
include.html(html, link)
|
|
168
|
+
// remove the include link
|
|
169
|
+
link.parentNode.removeChild(link)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const handleChanges = throttle(() => {
|
|
174
|
+
runWhenIdle(() => {
|
|
175
|
+
var links = globalThis.document.querySelectorAll('link[rel="simply-include"],link[rel="simply-include-once"]')
|
|
176
|
+
if (links.length) {
|
|
177
|
+
includeLinks(links)
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const observe = () => {
|
|
183
|
+
observer = new MutationObserver(handleChanges)
|
|
184
|
+
observer.observe(globalThis.document, {
|
|
185
|
+
subtree: true,
|
|
186
|
+
childList: true,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
observe()
|
|
191
|
+
handleChanges() // check if there are include links in the dom already
|
package/src/key.mjs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
class SimplyKeys {
|
|
2
|
+
constructor(options = {}) {
|
|
3
|
+
if (!options.app) {
|
|
4
|
+
options.app = {}
|
|
5
|
+
}
|
|
6
|
+
if (!options.app.container) {
|
|
7
|
+
options.app.container = document.body
|
|
8
|
+
}
|
|
9
|
+
Object.assign(this, options.keys)
|
|
10
|
+
|
|
11
|
+
const keyHandler = (e) => {
|
|
12
|
+
if (e.isComposing || e.keyCode === 229) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (e.defaultPrevented) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!e.target) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let selectedKeyboard = 'default';
|
|
23
|
+
if (e.target.closest('[data-simply-keyboard]')) {
|
|
24
|
+
selectedKeyboard = e.target.closest('[data-simply-keyboard]').dataset.simplyKeyboard;
|
|
25
|
+
}
|
|
26
|
+
let key = '';
|
|
27
|
+
if (e.ctrlKey && e.keyCode!=17) {
|
|
28
|
+
key+='Control+';
|
|
29
|
+
}
|
|
30
|
+
if (e.metaKey && e.keyCode!=224) {
|
|
31
|
+
key+='Meta+';
|
|
32
|
+
}
|
|
33
|
+
if (e.altKey && e.keyCode!=18) {
|
|
34
|
+
key+='Alt+';
|
|
35
|
+
}
|
|
36
|
+
if (e.shiftKey && e.keyCode!=16) {
|
|
37
|
+
key+='Shift+';
|
|
38
|
+
}
|
|
39
|
+
key+=e.key;
|
|
40
|
+
|
|
41
|
+
if (this[selectedKeyboard] && this[selectedKeyboard][key]) {
|
|
42
|
+
let keyboard = this[selectedKeyboard]
|
|
43
|
+
keyboard[key].call(options.app,e);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
options.app.container.addEventListener('keydown', keyHandler)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function keys(options={}) {
|
|
53
|
+
return new SimplyKeys(options)
|
|
54
|
+
}
|
|
55
|
+
|
package/src/model.mjs
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {signal, effect, batch} from './state.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This class implements a pluggable data model, where you can
|
|
5
|
+
* add effects that are run only when either an option for that
|
|
6
|
+
* effect changes, or when an effect earlier in the chain of
|
|
7
|
+
* effects changes.
|
|
8
|
+
*/
|
|
9
|
+
class SimplyModel {
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new datamodel, with a state property that contains
|
|
13
|
+
* all the data passed to this constructor
|
|
14
|
+
* @param state Object with all the data for this model
|
|
15
|
+
*/
|
|
16
|
+
constructor(state) {
|
|
17
|
+
this.state = signal(state)
|
|
18
|
+
if (!this.state.options) {
|
|
19
|
+
this.state.options = {}
|
|
20
|
+
}
|
|
21
|
+
this.effects = [{current:state.data}]
|
|
22
|
+
this.view = signal(state.data)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Adds an effect to run whenever a signal it depends on
|
|
27
|
+
* changes. this.state is the usual signal.
|
|
28
|
+
* The `fn` function param is not itself an effect, but must return
|
|
29
|
+
* and effect function. `fn` takes one param, which is the data signal.
|
|
30
|
+
* This signal will always have at least a `current` property.
|
|
31
|
+
* The result of the effect function is pushed on to the this.effects
|
|
32
|
+
* list. And the last effect added is set as this.view
|
|
33
|
+
*/
|
|
34
|
+
addEffect(fn) {
|
|
35
|
+
const dataSignal = this.effects[this.effects.length-1]
|
|
36
|
+
this.view = fn.call(this, dataSignal)
|
|
37
|
+
this.effects.push(this.view)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function model(options) {
|
|
42
|
+
return new SimplyModel(options)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function sort(options={}) {
|
|
46
|
+
return function(data) {
|
|
47
|
+
// initialize the sort options, only gets called once
|
|
48
|
+
this.state.options.sort = Object.assign({
|
|
49
|
+
direction: 'asc',
|
|
50
|
+
sortBy: null,
|
|
51
|
+
sortFn: ((a,b) => {
|
|
52
|
+
const sort = this.state.options.sort
|
|
53
|
+
const sortBy = sort.sortBy
|
|
54
|
+
if (!sort.sortBy) {
|
|
55
|
+
return 0
|
|
56
|
+
}
|
|
57
|
+
const larger = sort.direction == 'asc' ? 1 : -1
|
|
58
|
+
const smaller = sort.direction == 'asc' ? -1 : 1
|
|
59
|
+
if (typeof a?.[sortBy] === 'undefined') {
|
|
60
|
+
if (typeof b?.[sortBy] === 'undefined') {
|
|
61
|
+
return 0
|
|
62
|
+
}
|
|
63
|
+
return larger
|
|
64
|
+
}
|
|
65
|
+
if (typeof b?.[sortBy] === 'undefined') {
|
|
66
|
+
return smaller
|
|
67
|
+
}
|
|
68
|
+
if (a[sortBy]<b[sortBy]) {
|
|
69
|
+
return smaller
|
|
70
|
+
} else if (a[sortBy]>b[sortBy]) {
|
|
71
|
+
return larger
|
|
72
|
+
} else {
|
|
73
|
+
return 0
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}, options);
|
|
77
|
+
// then return the effect, which is called when
|
|
78
|
+
// either the data or the sort options change
|
|
79
|
+
return effect(() => {
|
|
80
|
+
const sort = this.state.options.sort
|
|
81
|
+
if (sort?.sortBy && sort?.direction) {
|
|
82
|
+
return data.current.toSorted(sort?.sortFn)
|
|
83
|
+
}
|
|
84
|
+
return data.current
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function paging(options={}) {
|
|
90
|
+
return function(data) {
|
|
91
|
+
// initialize the paging options
|
|
92
|
+
this.state.options.paging = Object.assign({
|
|
93
|
+
page: 1,
|
|
94
|
+
pageSize: 20,
|
|
95
|
+
max: 1
|
|
96
|
+
}, options)
|
|
97
|
+
return effect(() => {
|
|
98
|
+
return batch(() => {
|
|
99
|
+
const paging = this.state.options.paging
|
|
100
|
+
if (!paging.pageSize) {
|
|
101
|
+
paging.pageSize = 20
|
|
102
|
+
}
|
|
103
|
+
paging.max = Math.ceil(this.state.data.length / paging.pageSize)
|
|
104
|
+
paging.page = Math.max(1, Math.min(paging.max, paging.page))
|
|
105
|
+
|
|
106
|
+
const start = (paging.page-1) * paging.pageSize
|
|
107
|
+
const end = start + paging.pageSize
|
|
108
|
+
return data.current.slice(start, end)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function filter(options) {
|
|
115
|
+
if (!options?.name || typeof options.name!=='string') {
|
|
116
|
+
throw new Error('filter requires options.name to be a string')
|
|
117
|
+
}
|
|
118
|
+
if (!options.matches || typeof options.matches!=='function') {
|
|
119
|
+
throw new Error('filter requires options.matches to be a function')
|
|
120
|
+
}
|
|
121
|
+
return function(data) {
|
|
122
|
+
this.state.options[options.name] = options
|
|
123
|
+
return effect(() => {
|
|
124
|
+
if (this.state.options[options.name].enabled) {
|
|
125
|
+
return data.filter(this.state.options.matches)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function columns(options={}) {
|
|
132
|
+
if (!options
|
|
133
|
+
|| typeof options!=='object'
|
|
134
|
+
|| Object.keys(options).length===0) {
|
|
135
|
+
throw new Error('columns requires options to be an object with at least one property')
|
|
136
|
+
}
|
|
137
|
+
return function(data) {
|
|
138
|
+
this.state.options.columns = options
|
|
139
|
+
return effect(() => {
|
|
140
|
+
return data.current.map(input => {
|
|
141
|
+
let result = {}
|
|
142
|
+
for (let key of Object.keys(this.state.options.columns)) {
|
|
143
|
+
if (!this.state.options.columns[key].hidden) {
|
|
144
|
+
result[key] = input[key]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return result
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
}
|