bt-core-app 0.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 (53) hide show
  1. package/.vscode/extensions.json +3 -0
  2. package/README.md +45 -0
  3. package/index.html +13 -0
  4. package/package.json +39 -0
  5. package/public/vite.svg +1 -0
  6. package/src/assets/vue.svg +1 -0
  7. package/src/components/BT-Btn.vue +40 -0
  8. package/src/components/BT-Col.vue +36 -0
  9. package/src/components/BT-Span.vue +21 -0
  10. package/src/components/Dialog-Confirm.vue +47 -0
  11. package/src/components/Dialog-Select-Date.vue +89 -0
  12. package/src/components/Dialog-Select.vue +140 -0
  13. package/src/components/Dialog-Text.vue +59 -0
  14. package/src/composables/actions-tracker.ts +99 -0
  15. package/src/composables/actions.ts +354 -0
  16. package/src/composables/api.ts +471 -0
  17. package/src/composables/auth.ts +382 -0
  18. package/src/composables/cosmetics.ts +178 -0
  19. package/src/composables/csv.ts +198 -0
  20. package/src/composables/dates.ts +79 -0
  21. package/src/composables/demo.ts +25 -0
  22. package/src/composables/dialogs.ts +115 -0
  23. package/src/composables/document-meta.ts +40 -0
  24. package/src/composables/draggable.ts +189 -0
  25. package/src/composables/filters.ts +256 -0
  26. package/src/composables/forage.ts +49 -0
  27. package/src/composables/helpers.ts +694 -0
  28. package/src/composables/id.ts +20 -0
  29. package/src/composables/list.ts +601 -0
  30. package/src/composables/navigation.ts +214 -0
  31. package/src/composables/presets.ts +20 -0
  32. package/src/composables/pwa.ts +89 -0
  33. package/src/composables/resizable.ts +382 -0
  34. package/src/composables/rules.ts +40 -0
  35. package/src/composables/stores.ts +797 -0
  36. package/src/composables/track.ts +55 -0
  37. package/src/composables/urls.ts +11 -0
  38. package/src/core.ts +92 -0
  39. package/src/index.ts +16 -0
  40. package/src/types.ts +13 -0
  41. package/src/useApi.ts +68 -0
  42. package/src/vite-env.d.ts +1 -0
  43. package/test/api.test.ts +84 -0
  44. package/test/forage.test.ts +31 -0
  45. package/test/helpers.test.ts +231 -0
  46. package/test/navigation.test.ts +99 -0
  47. package/test/stores-last-update.test.ts +138 -0
  48. package/test/stores-session.test.ts +118 -0
  49. package/test/track.test.ts +29 -0
  50. package/test/utils.ts +15 -0
  51. package/tsconfig.json +33 -0
  52. package/tsconfig.node.json +11 -0
  53. package/vite.config.ts +19 -0
@@ -0,0 +1,198 @@
1
+ import { TableColumn } from "./list"
2
+ import { isLengthyArray, fromCamelCase, nestedValue } from "../index"
3
+
4
+ export interface CSVProps {
5
+ canExportCSV?: boolean
6
+ }
7
+
8
+ export const csvDefaults = {
9
+ canExportCSV: false
10
+ }
11
+
12
+ export interface UseCSVPropsReturn {
13
+ exportToCSV: Function
14
+ }
15
+
16
+ export interface CSVItem {
17
+ header: string
18
+ itemText?: string
19
+ value: any
20
+ }
21
+
22
+ declare global {
23
+ interface Navigator {
24
+ msSaveOrOpenBlob: (blob: Blob, fileName: string) => boolean
25
+ }
26
+ }
27
+
28
+ export function useCSV(): UseCSVPropsReturn {
29
+
30
+ function exportToCSV(
31
+ items: any[],
32
+ headers?: TableColumn[],
33
+ fileName: string = 'csvData.csv') {
34
+
35
+ if (!isLengthyArray(items)) {
36
+ return;
37
+ }
38
+
39
+ let dnaArray: CSVItem[] = []
40
+
41
+ if (headers != null) {
42
+ dnaArray = headers?.filter(y => (y.csv ?? y.csvText ?? y.csvFilter ?? y.csvArray) != null)
43
+ .map(z => {
44
+ return {
45
+ header: z.title ?? '',
46
+ itemText: z.itemText,
47
+ value: z.value
48
+ }
49
+ })
50
+ }
51
+ else {
52
+ dnaArray = Object.keys(items[0]).map(x => { return { header: fromCamelCase(x) ?? '', value: x }; });
53
+ }
54
+
55
+ dnaArray = dnaArray.filter(z => z.header.length > 0)
56
+
57
+ var lineArray: any[] = [];
58
+
59
+ // var increments = [];
60
+ // if (dnaArray.some(y => y.breakdown === true)) {
61
+ // try {
62
+ // increments = await BlitzIt.store.getAll('stock-increments');
63
+ // }
64
+ // catch (err) {
65
+ // console.log('generating csv file could not pull increments for breakdown');
66
+ // console.log(this.extractErrorDescription(err));
67
+ // }
68
+ // }
69
+
70
+ // if (docTitle != null) {
71
+ // lineArray.push(docTitle);
72
+ // }
73
+
74
+ // dnaArray = dnaArray.filter(x => x.header != null);
75
+
76
+ //print header row
77
+ // lineArray.push(dnaArray.map(x => x.header))
78
+
79
+ for (let i = 0; i < items.length; i++) {
80
+ const d = items[i];
81
+
82
+ var newItem: any = {};
83
+ var extraLines: any[] = [];
84
+
85
+ for (let ii = 0; ii < dnaArray.length; ii++) {
86
+ const dna = dnaArray[ii];
87
+ var v = null;
88
+ if (typeof(dna.value) == 'function') {
89
+ v = dna.value(d);
90
+ }
91
+ else if (typeof(dna.value) == 'string') {
92
+ v = nestedValue(d, dna.value);
93
+
94
+ // if (dna.navigation != null && v != null) {
95
+ // //search from local storage
96
+ // try {
97
+ // var res = await BlitzIt.store.get(dna.navigation, v, null, false, null, null, true);
98
+ // if (res != null) {
99
+ // v = res;
100
+ // }
101
+ // }
102
+ // catch (err) {
103
+ // console.log(err);
104
+ // }
105
+ // }
106
+ }
107
+
108
+ // if (v != null && dna.valueFilter != null) {
109
+ // console.log('aa');
110
+ // v = this.$options.filters[dna.valueFilter](v);
111
+ // console.log(v);
112
+ // }
113
+
114
+ // if (dna.csvArray) {
115
+ // if (this.isLengthyArray(v)) {
116
+ // v.forEach(w => {
117
+ // var otherNewItem = {};
118
+ // // otherNewItem[dna.header] = w.toString();
119
+ // // extraLines.push(otherNewItem);
120
+ // if (dna.breakdown && w.productID != null) {
121
+ // otherNewItem[dna.header] = `${getBreakdown(w.quantity, measurements, increments, w.productID)}, ${w.product?.productName}`;
122
+ // extraLines.push(otherNewItem);
123
+ // }
124
+ // else {
125
+ // otherNewItem[dna.header] = w.toString();
126
+ // extraLines.push(otherNewItem);
127
+ // }
128
+ // })
129
+ // }
130
+ // }
131
+ // else {
132
+ // if (dna.breakdown) {
133
+ // var prodProp = dna.csvProductIDProp || 'productID';
134
+ // newItem[dna.header] = getBreakdown(v, measurements, increments, d[prodProp]);
135
+ // }
136
+ if (dna.itemText != null) {
137
+ newItem[dna.header] = nestedValue(v, dna.itemText);
138
+ }
139
+ else {
140
+ newItem[dna.header] = v;
141
+ }
142
+ // }
143
+ }
144
+
145
+ lineArray.push(newItem);
146
+
147
+ if (isLengthyArray(extraLines)) {
148
+ extraLines.forEach(e => {
149
+ lineArray.push(e);
150
+ })
151
+ }
152
+
153
+ extraLines = [];
154
+ }
155
+
156
+ var resArray = [];
157
+
158
+ // if (docTitle != null) {
159
+ // resArray.push(docTitle);
160
+ // }
161
+
162
+ //print header row
163
+ resArray.push(dnaArray.map(x => x.header))
164
+
165
+ lineArray.forEach(obj => {
166
+ let propArray: any[] = [];
167
+ dnaArray.forEach(function(k) {
168
+ var v = obj[k.header];
169
+ propArray.push(v != null ? v : '');
170
+ });
171
+
172
+ resArray.push(propArray.join(","));
173
+ })
174
+
175
+ var csvContent = resArray.join("\n");
176
+ var file = new Blob([csvContent], { type: "text/plain;charset=utf-8" });
177
+
178
+ if (window.navigator.msSaveOrOpenBlob) {
179
+ window.navigator.msSaveOrOpenBlob(file, fileName);
180
+ }
181
+ else {
182
+ var a = document.createElement("a"),
183
+ url = URL.createObjectURL(file);
184
+ a.href = url;
185
+ a.download = fileName;
186
+ document.body.appendChild(a);
187
+ a.click();
188
+ setTimeout(function () {
189
+ document.body.removeChild(a);
190
+ window.URL.revokeObjectURL(url);
191
+ }, 0);
192
+ }
193
+ }
194
+
195
+ return {
196
+ exportToCSV
197
+ }
198
+ }
@@ -0,0 +1,79 @@
1
+ import { DateTime } from 'luxon'
2
+ // import { useTimeZone } from '@/composables/auth'
3
+
4
+ export interface BTDates {
5
+ getToday: () => string
6
+ getTomorrow: () => string
7
+ tzDate: (val?: string, fromFormat?: string) => DateTime
8
+ tzString: (val?: string, format?: string, fromFormat?: string) => string
9
+ utcDate: (val?: string, fromFormat?: string) => DateTime
10
+ utcString: (val?: string, format?: string, fromFormat?: string) => string
11
+ }
12
+
13
+ export interface CreateDatesOptions {
14
+ getTimeZone: () => string
15
+ }
16
+
17
+ export function createDates(options: CreateDatesOptions): BTDates {
18
+
19
+ function getToday(): string {
20
+ return tzDate()?.startOf('day').toUTC().toString() ?? '';
21
+ }
22
+
23
+ function getTomorrow(): string {
24
+ return tzDate()?.endOf('day').toUTC().toString() ?? '';
25
+ }
26
+
27
+ function tzDate(val?: string, fromFormat?: string): DateTime {
28
+ if (val == null) {
29
+ //create now
30
+ return DateTime.utc().setZone(options.getTimeZone())
31
+ }
32
+
33
+ return fromFormat ? DateTime.fromFormat(val, fromFormat, { zone: options.getTimeZone() }) : DateTime.fromISO(val, { zone: options.getTimeZone() })
34
+ }
35
+
36
+ function tzString(val?: string, format?: string, fromFormat?: string): string {
37
+ if (val == null) {
38
+ //create now
39
+ const d = DateTime.utc().setZone(options.getTimeZone())
40
+ return format ? d.toFormat(format) : d.toString()
41
+ }
42
+
43
+ if (val == 'Invalid DateTime') {
44
+ return ''
45
+ }
46
+
47
+ const d = fromFormat ? DateTime.fromFormat(val, fromFormat, { zone: options.getTimeZone() }) : DateTime.fromISO(val, { zone: options.getTimeZone() })
48
+
49
+ return format ? d.toFormat(format) : d?.toString()
50
+ }
51
+
52
+ function utcDate(val?: string, fromFormat?: string): DateTime {
53
+ if (val == null) {
54
+ return DateTime.utc()
55
+ }
56
+ else {
57
+ return fromFormat ? DateTime.fromFormat(val, fromFormat) : DateTime.fromISO(val)
58
+ }
59
+ }
60
+
61
+ function utcString(val?: string, format?: string, fromFormat?: string): string {
62
+ if (val == null) {
63
+ return format ? DateTime.utc().toFormat(format) : DateTime.utc().toString()
64
+ }
65
+ else {
66
+ const d = fromFormat ? DateTime.fromFormat(val, fromFormat) : DateTime.fromISO(val)
67
+ return format ? d.toFormat(format) : d.toString()
68
+ }
69
+ }
70
+
71
+ return {
72
+ getToday,
73
+ getTomorrow,
74
+ tzDate,
75
+ tzString,
76
+ utcDate,
77
+ utcString
78
+ }
79
+ }
@@ -0,0 +1,25 @@
1
+ import { ref, type Ref } from 'vue'
2
+
3
+ export interface BTDemo {
4
+ endDemo: () => void
5
+ isDemoing: Ref<boolean>
6
+ startDemo: () => void
7
+ }
8
+
9
+ export function createDemo(): BTDemo {
10
+ const isDemoing = ref(false)
11
+
12
+ function startDemo() {
13
+
14
+ }
15
+
16
+ function endDemo() {
17
+
18
+ }
19
+
20
+ return {
21
+ endDemo,
22
+ isDemoing,
23
+ startDemo
24
+ }
25
+ }
@@ -0,0 +1,115 @@
1
+ // import { openDialog } from 'vue3-promise-dialog'
2
+ import { createConfirmDialog } from 'vuejs-confirm-dialog'
3
+ import BTConfirmDialog from '@/components/Dialog-Confirm.vue'
4
+ import BTSelectDateDialog from '@/components/Dialog-Select-Date.vue'
5
+ import BTSelectDialog from '@/components/Dialog-Select.vue'
6
+ import BTTextDialog from '@/components/Dialog-Text.vue'
7
+ import { type ListProps } from '@/composables/list'
8
+
9
+ export interface ConfirmDialogProps {
10
+ cancelText?: string
11
+ cancelValue?: any
12
+ confirmText?: string
13
+ confirmValue?: any
14
+ msg?: string
15
+ maxWidth?: number
16
+ minWidth?: number
17
+ title?: string
18
+ }
19
+
20
+ export interface SelectDateProps {
21
+ cancelText?: string
22
+ cancelValue?: any
23
+ confirmText?: string
24
+ dateFrom?: string
25
+ dateRules?: Function | unknown[]
26
+ format?: string
27
+ fromNow?: boolean
28
+ height?: string
29
+ msg?: string
30
+ maxWidth?: number
31
+ minWidth?: number
32
+ range?: boolean
33
+ required?: boolean
34
+ requireTime?: boolean
35
+ title?: string
36
+ useTime?: boolean
37
+ }
38
+
39
+ export interface SelectDialogProps extends ListProps {
40
+ cancelText?: string
41
+ cancelValue?: any
42
+ canUnselect?: boolean
43
+ confirmText?: string
44
+ height?: string
45
+ itemSubtext?: string
46
+ itemText?: string
47
+ itemValue?: string
48
+ msg?: string
49
+ maxWidth?: number
50
+ minWidth?: number
51
+ multiple?: boolean
52
+ nav?: string
53
+ onFilter?: Function
54
+ required?: boolean
55
+ subtextFilter?: string
56
+ subtextFunction?: Function
57
+ textFilter?: string
58
+ textFunction?: Function
59
+ title?: string
60
+ }
61
+
62
+ export interface TextDialogProps extends ListProps {
63
+ cancelText?: string
64
+ confirmText?: string
65
+ height?: string
66
+ label?: string
67
+ msg?: string
68
+ maxWidth?: number
69
+ minWidth?: number
70
+ required?: boolean
71
+ title?: string
72
+ value?: any
73
+ }
74
+
75
+ const confirmDialog = createConfirmDialog(BTConfirmDialog as any)
76
+ const selectDateDialog = createConfirmDialog(BTSelectDateDialog as any)
77
+ const selectDialog = createConfirmDialog(BTSelectDialog as any)
78
+ const textDialog = createConfirmDialog(BTTextDialog as any)
79
+
80
+ export function useRequireConfirmation(action: any, props: ConfirmDialogProps, requireConfirm: boolean) {
81
+ if (requireConfirm) {
82
+ const { reveal, onConfirm } = createConfirmDialog(BTConfirmDialog as any, props)
83
+ onConfirm(action)
84
+ reveal()
85
+ }
86
+ else {
87
+ action()
88
+ }
89
+ }
90
+
91
+ export async function useConfirmAsync(text: string) {
92
+ const { isCanceled } = await confirmDialog.reveal({ msg: text })
93
+ return !isCanceled
94
+ }
95
+
96
+ /**
97
+ * Returns undefined if cancelled
98
+ * [] if multiple
99
+ * Null | Obj if single
100
+ * @param opts
101
+ */
102
+ export async function useSelectDialog(opts?: SelectDialogProps) {
103
+ const { data } = await selectDialog.reveal(opts)
104
+ return data
105
+ }
106
+
107
+ export async function useSelectDate(opts?: SelectDateProps) {
108
+ const { data } = await selectDateDialog.reveal(opts)
109
+ return data
110
+ }
111
+
112
+ export async function useTextDialog(opts?: TextDialogProps) {
113
+ const { data } = await textDialog.reveal(opts)
114
+ return data
115
+ }
@@ -0,0 +1,40 @@
1
+ import { type RouteLocationNormalized } from 'vue-router'
2
+ import { BTDemo } from '@/composables/demo'
3
+ import { type Environment } from '@/composables/urls'
4
+
5
+ export interface UseDocumentMetaOptions {
6
+ demo?: BTDemo
7
+ }
8
+
9
+ /**routes with meta object */
10
+ export function useDocumentMeta(options?: UseDocumentMetaOptions) {
11
+
12
+ function updateMeta(to: RouteLocationNormalized) {
13
+ const nearestWithTitle = to.matched.slice().reverse().find(r => r.meta && r.meta.title);
14
+
15
+ if(nearestWithTitle) {
16
+ document.title = nearestWithTitle.meta.title as string
17
+ }
18
+ else {
19
+ const env = import.meta.env.MODE as Environment
20
+ let title = ''
21
+
22
+ if (env == 'development')
23
+ title = 'BWeb Dev'
24
+ else if (env == 'staging')
25
+ title = 'BlitzIt Sandpit'
26
+ else {
27
+ title = 'BlitzIt Web | Cloud-Based Wholesale Logistics Platform'
28
+ }
29
+
30
+ if (options?.demo?.isDemoing.value == true)
31
+ title = `Training: ${title}`
32
+
33
+ document.title = title
34
+ }
35
+ }
36
+
37
+ return {
38
+ updateMeta
39
+ }
40
+ }
@@ -0,0 +1,189 @@
1
+ import { type ComponentPublicInstance, type MaybeRefOrGetter, type Ref, ref, toValue } from 'vue'
2
+ import { type Position, useEventListener } from '@vueuse/core'
3
+ import { type PointerOrTouchEvent } from './resizable'
4
+
5
+ export interface UseDraggableOptions {
6
+ /**
7
+ * Only start the dragging when click on the element directly
8
+ *
9
+ * @default false
10
+ */
11
+ // exact?: MaybeRefOrGetter<boolean>
12
+
13
+ preventDefault?: MaybeRefOrGetter<boolean>
14
+ stopPropagation?: MaybeRefOrGetter<boolean>
15
+
16
+ /**
17
+ * Whether dispatch events in capturing phase
18
+ *
19
+ * @default true
20
+ */
21
+ capture?: boolean
22
+
23
+ /**
24
+ * Element to attach `pointermove` and `pointerup` events to.
25
+ *
26
+ * @default window
27
+ */
28
+ draggingElement?: MaybeRefOrGetter<HTMLElement | SVGElement | Window | Document | null | undefined>
29
+
30
+ /**
31
+ * Element for calculating bounds (If not set, it will use the event's target).
32
+ *
33
+ * @default undefined
34
+ */
35
+ // containerElement?: MaybeRefOrGetter<HTMLElement | SVGElement | null | undefined>
36
+
37
+ /**
38
+ * Handle that triggers the drag event
39
+ *
40
+ * @default target
41
+ */
42
+ handle?: MaybeRefOrGetter<HTMLElement | SVGElement | null | undefined>
43
+
44
+ /**
45
+ * Initial position of the element.
46
+ *
47
+ * @default { x: 0, y: 0 }
48
+ */
49
+ initialValue?: MaybeRefOrGetter<Position>
50
+
51
+ onStart?: (position: Position, event: PointerEvent) => void | false
52
+ onMove?: (position: Position, event: PointerEvent) => void
53
+ onEnd?: (position: Position, event: PointerEvent) => void
54
+
55
+ /**
56
+ * Axis to drag on.
57
+ *
58
+ * @default 'both'
59
+ */
60
+ axis?: 'x' | 'y' | 'both'
61
+ }
62
+
63
+ export function useDraggable(
64
+ target: MaybeRefOrGetter<ComponentPublicInstance | null>, //HTMLElement | SVGElement | null | undefined>,
65
+ handle: MaybeRefOrGetter<ComponentPublicInstance | null>,
66
+ options: UseDraggableOptions = {},
67
+ ) {
68
+ const {
69
+ preventDefault = false,
70
+ stopPropagation = false,
71
+ axis = 'both',
72
+ } = options
73
+
74
+ const config = { capture: options.capture ?? true }
75
+ let currentElementPosition = { x: 0, y: 0 }
76
+ let startingElementPosition = { x: 0, y: 0 }
77
+ let startingPointerPosition = { x: 0, y: 0 }
78
+
79
+ let listeners: Function[] = []
80
+ let moveListeners: Function[] = []
81
+ let draggingIsOn: Ref<boolean> = ref(false)
82
+
83
+ function handleEvent(e: PointerEvent) {
84
+ if (toValue(preventDefault))
85
+ e.preventDefault()
86
+ if (toValue(stopPropagation))
87
+ e.stopPropagation()
88
+ }
89
+
90
+ function start(e: PointerOrTouchEvent) {
91
+ const t = toValue(target)
92
+ if (!t) return
93
+ const el = t.$el
94
+ const isTouch = e.type === 'touchstart' && e.touches.length > 0
95
+ const evtData = isTouch ? e.touches[0] : e
96
+
97
+ startingPointerPosition = { x: evtData.clientX, y: evtData.clientY }
98
+
99
+ startingElementPosition = {
100
+ x: el.offsetLeft,
101
+ y: el.offsetTop
102
+ }
103
+
104
+ currentElementPosition = {
105
+ x: el.offsetLeft,
106
+ y: el.offsetTop
107
+ }
108
+
109
+ if (options.onStart?.(startingPointerPosition, e) === false) return
110
+
111
+ moveListeners.push(useEventListener('mousemove', move))
112
+ moveListeners.push(useEventListener('touchmove', move))
113
+ moveListeners.push(useEventListener('mouseup', end))
114
+ moveListeners.push(useEventListener('touchend', end))
115
+
116
+ handleEvent(e)
117
+ }
118
+
119
+ function move(e: any) {
120
+ if (!startingPointerPosition) return
121
+
122
+ const t = toValue(target)
123
+ if (!t) return
124
+ // const el = t.$el
125
+ const isTouch = e.type === 'touchmove' && e.touches.length > 0
126
+ const evtData = isTouch ? e.touches[0] : e
127
+ let dx = evtData.clientX - startingPointerPosition.x
128
+ let dy = evtData.clientY - startingPointerPosition.y
129
+
130
+ if (axis === 'x' || axis === 'both')
131
+ currentElementPosition.x = startingElementPosition.x + dx
132
+ if (axis === 'y' || axis === 'both')
133
+ currentElementPosition.y = startingElementPosition.y + dy
134
+
135
+ options.onMove?.(currentElementPosition, e)
136
+
137
+ handleEvent(e)
138
+
139
+ applyToElementStyle(currentElementPosition)
140
+ }
141
+
142
+ function end(e: any) {
143
+ document.documentElement.style.cursor = ''
144
+ moveListeners.forEach(m => { m() })
145
+ moveListeners.length = 0
146
+ options.onEnd?.(currentElementPosition, e)
147
+ handleEvent(e)
148
+ }
149
+
150
+ function applyToElementStyle(s: Position) {
151
+ const t = toValue(target)
152
+ if (!t) return
153
+ const el = t.$el
154
+
155
+ el.style.left = `${s.x}px`
156
+ el.style.top = `${s.y}px`
157
+ }
158
+
159
+ function turnDraggableOn() {
160
+ if (toValue(draggingIsOn)) return
161
+ const handleT = toValue(handle)
162
+ if (!handleT) return
163
+ const handleEl = handleT.$el
164
+ handleEl.style.cursor = 'move'
165
+
166
+ listeners.push(useEventListener(handleEl, 'mousedown', start, config))
167
+ listeners.push(useEventListener(handleEl, 'touchstart', start, config))
168
+ draggingIsOn.value = true
169
+ }
170
+
171
+ function turnDraggableOff() {
172
+ if (!toValue(draggingIsOn)) return
173
+ const handleT = toValue(handle)
174
+ if (!handleT) return
175
+ const handleEl = handleT.$el
176
+ handleEl.style.cursor = ''
177
+
178
+ //provide new position and size?
179
+ listeners.forEach(l => { l() })
180
+ listeners.length = 0
181
+ draggingIsOn.value = false
182
+ }
183
+
184
+ return {
185
+ draggingIsOn,
186
+ turnDraggableOff,
187
+ turnDraggableOn
188
+ }
189
+ }