@lilaquadrat/design-core 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lilaquadrat/design-core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "framework layer shared by every lilaquadrat STUDIO design: app bootstrap, router, stores, plugins and build tooling",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,6 +10,10 @@
10
10
  "url": "https://lilaquadrat.de"
11
11
  },
12
12
  "homepage": "https://lilaquadrat.de",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+ssh://git@ssh.dev.azure.com/v3/lilaquadrat/studio-packages/studio-design-core"
16
+ },
13
17
  "engines": {
14
18
  "node": ">=18"
15
19
  },
@@ -53,13 +57,14 @@
53
57
  "scripts": {
54
58
  "type-check": "vue-tsc --noEmit",
55
59
  "lint": "eslint . --fix",
60
+ "commit": "cz",
56
61
  "release": "standard-version"
57
62
  },
58
63
  "peerDependencies": {
59
64
  "@auth0/auth0-spa-js": "^2.1.3",
60
- "@lilaquadrat/interfaces": ">=1.31.0",
65
+ "@lilaquadrat/frontend": ">=0.2.0",
66
+ "@lilaquadrat/interfaces": ">=1.48.0",
61
67
  "@lilaquadrat/sdk": "^0.1.2",
62
- "@lilaquadrat/studio": "^9.14.3",
63
68
  "axios": "^1.9.0",
64
69
  "cypress": "^15.0.0",
65
70
  "dayjs": "^1.11.13",
@@ -99,9 +104,9 @@
99
104
  "devDependencies": {
100
105
  "@auth0/auth0-spa-js": "2.1.3",
101
106
  "@eslint/js": "^9.25.1",
102
- "@lilaquadrat/interfaces": "1.31.0",
107
+ "@lilaquadrat/frontend": "^0.2.0",
108
+ "@lilaquadrat/interfaces": "1.48.0",
103
109
  "@lilaquadrat/sdk": "^0.1.2",
104
- "@lilaquadrat/studio": "9.15.4",
105
110
  "@types/node": "^22.15.3",
106
111
  "@types/object-path": "^0.11.4",
107
112
  "@types/qrcode": "^1.5.5",
@@ -140,9 +145,5 @@
140
145
  "commitizen": {
141
146
  "path": "./node_modules/cz-conventional-changelog"
142
147
  }
143
- },
144
- "resolutions": {
145
- "@lilaquadrat/sdk/@lilaquadrat/interfaces": "1.31.0",
146
- "@lilaquadrat/studio/@lilaquadrat/interfaces": "1.31.0"
147
148
  }
148
149
  }
@@ -2,7 +2,7 @@
2
2
  import useMainStore from '../../stores/main.store';
3
3
  import type { BasicData, Content } from '@lilaquadrat/interfaces';
4
4
  import type { SDKResponse } from '@lilaquadrat/sdk';
5
- import { prepareContent } from '@lilaquadrat/studio/lib/esm/frontend';
5
+ import { prepareContent } from '@lilaquadrat/frontend/content';
6
6
  import { watch } from 'vue';
7
7
  import { onBeforeMount } from 'vue';
8
8
  import { ref, computed } from 'vue';
@@ -5,7 +5,7 @@ import useMainStore from '../../stores/main.store';
5
5
  import useUserStore from '../../stores/user.store';
6
6
  import type { BasicData, Content, ContentWithPositions, GenericData } from '@lilaquadrat/interfaces';
7
7
  import type { SDKResponse } from '@lilaquadrat/sdk';
8
- import { distributeGenericData, generateDataWithContent, hardCopy, prepareContent } from '@lilaquadrat/studio';
8
+ import { distributeGenericData, generateDataWithContent, hardCopy, prepareContent } from '@lilaquadrat/frontend/content';
9
9
  import { ref, computed, watch, onServerPrefetch, onBeforeMount } from 'vue';
10
10
  import { useRoute } from 'vue-router';
11
11
 
@@ -1,5 +1,5 @@
1
1
  import { v4 as uuid } from 'uuid';
2
- import { hardCopy } from '@lilaquadrat/studio/lib/esm/frontend';
2
+ import { hardCopy } from '@lilaquadrat/frontend/content';
3
3
 
4
4
  type DeclarationTypes = 'string' | 'number' | 'boolean' | 'object';
5
5
  type SingleDeclaration = { type: 'string', default?: string } |
package/src/main.ts CHANGED
@@ -3,7 +3,11 @@ import { createPinia, type StateTree } from 'pinia';
3
3
  import createRouter from './mixins/createRouter';
4
4
  import { loadViaDeclarationSync } from './mixins/loadComponents';
5
5
  import i18n from './plugins/translations';
6
- import resizePlugin from './plugins/resize';
6
+ /**
7
+ * side effect import: the resize singleton in @lilaquadrat/frontend attaches its
8
+ * listeners on import, there is no plugin to install
9
+ */
10
+ import './plugins/resize';
7
11
  import HelpersPlugin from './plugins/filters';
8
12
  import youtubePlugin from './plugins/youtube';
9
13
  import traceablePlugin from './plugins/traceable';
@@ -70,7 +74,6 @@ export function getAppInstance (context: any, routes: readonly RouteRecordRaw[],
70
74
 
71
75
  app.use(i18n);
72
76
  app.use(HelpersPlugin);
73
- app.use(resizePlugin);
74
77
  app.use(youtubePlugin);
75
78
  app.use(traceablePlugin);
76
79
  app.use(authPlugin);
@@ -1,351 +1,9 @@
1
- import { computed, watch } from 'vue';
2
- import { ref } from 'vue';
3
- import { type Ref } from 'vue';
4
-
5
- class Inview {
6
-
7
- timeout!: NodeJS.Timeout | undefined;
8
-
9
- lastScrollingAdjustment = 0;
10
-
11
- debounceTime: number = 50;
12
-
13
- isTop = ref<boolean>(false);
14
-
15
- scrolledEvent: Event;
16
-
17
- scrolled = ref<number>(0);
18
-
19
- moduleAdded = ref<number>(0);
20
-
21
- scrollDirection = ref<'up' | 'down'>();
22
-
23
- parentElement = ref<HTMLElement>();
24
-
25
- lastScrollY = 0;
26
- lastTimestamp = 0;
27
- scrollVelocity = 0;
28
-
29
- /**
30
- * if true we fire an inital scroll event to indicate that scrolling has started without waiting for the debounce
31
- */
32
- triggerStart = ref<boolean>(true);
33
-
34
- safeWindow = computed(() => {
35
-
36
- if (typeof window === 'undefined') return null;
37
- if (this.parentElement.value) return this.parentElement.value;
38
- return window;
39
-
40
- });
41
-
42
- scrollY () {
43
-
44
- if (this.safeWindow.value instanceof Window) {
45
-
46
- return this.safeWindow.value.scrollY;
47
-
48
- }
49
-
50
- if (this.parentElement.value) {
51
-
52
- return this.parentElement.value.scrollTop;
53
- }
54
-
55
- return 0
56
-
57
- }
58
-
59
- height () {
60
-
61
- if (this.safeWindow.value instanceof Window) {
62
-
63
- return this.safeWindow.value.outerHeight;
64
-
65
- }
66
-
67
- if (this.parentElement.value) {
68
-
69
- return this.parentElement.value.offsetHeight;
70
- }
71
-
72
- return 0
73
-
74
- }
75
-
76
- constructor () {
77
-
78
- this.scrolledEvent = new Event('scrolled');
79
- this.checkIsTop();
80
-
81
- if (this.safeWindow.value) {
82
-
83
- this.safeWindow.value?.addEventListener('scroll', () => {
84
-
85
- this.debounce();
86
-
87
- });
88
-
89
- }
90
-
91
- }
92
-
93
- setParentElement (element: HTMLElement) {
94
-
95
- this.parentElement.value = element;
96
- this.addScrollListener(element);
97
-
98
- }
99
-
100
- addScrollListener (element: Element) {
101
-
102
- element.addEventListener('scroll', () => {
103
-
104
- const currentTimestamp = performance.now();
105
- const deltaY = this.scrollY() - this.lastScrollY;
106
- const deltaTime = currentTimestamp - this.lastTimestamp;
107
-
108
- // Calculate velocity (pixels per millisecond)
109
- this.scrollVelocity = Math.abs(deltaY / deltaTime);
110
-
111
- this.lastScrollY = this.scrollY();
112
- this.lastTimestamp = currentTimestamp;
113
-
114
- // console.log(`Scroll Velocity: ${this.scrollVelocity.toFixed(2)} pixels/ms`);
115
-
116
- this.debounce();
117
-
118
- });
119
-
120
- }
121
-
122
- trigger (isStart?: boolean) {
123
-
124
- if (!this.safeWindow.value) return;
125
-
126
- this.checkIsTop();
127
- this.scrollDirection.value = this.scrolled.value > this.scrollY()
128
- ? 'down'
129
- : 'up'
130
- this.scrolled.value = this.scrollY();
131
-
132
- this.safeWindow.value?.dispatchEvent(this.scrolledEvent);
133
- if (!isStart) this.triggerStart.value = true;
134
-
135
- }
136
-
137
- checkIsTop () {
138
-
139
- if (!this.safeWindow.value) return;
140
-
141
- this.isTop.value = this.scrollY() <= 0
142
-
143
- }
144
-
145
- debounce () {
146
-
147
- /**
148
- * indicates that the scrolling has begun and fire an inital scrolling event without waiting for the debounce
149
- */
150
- if (this.triggerStart.value) {
151
-
152
- this.triggerStart.value = false;
153
- this.trigger(true);
154
-
155
- }
156
-
157
- clearTimeout(this.timeout);
158
- this.timeout = setTimeout(() => {
159
-
160
- this.trigger();
161
-
162
- }, this.debounceTime);
163
-
164
- }
165
-
166
- check (component: HTMLElement, state: Ref<string[]>, options?: { align?: boolean }) {
167
- const element = component;
168
- const newState: string[] = [];
169
-
170
- if (typeof element?.getBoundingClientRect !== 'function') return;
171
- if (!this.safeWindow.value) return;
172
-
173
- const viewportHeight = this.height();
174
- const height = viewportHeight / 2;
175
- const rect = element.getBoundingClientRect();
176
- let top = rect.top - height + 10;
177
- let bottom = rect.bottom - height + 10;
178
- let visibleHeight = 0;
179
- let marginToTop = 0;
180
-
181
- if (this.parentElement.value) {
182
-
183
- const parentRect = this.parentElement.value.getBoundingClientRect();
184
- // Adjust top and bottom based on parent element's top and bottom
185
- const parentTop = parentRect.top;
186
- const parentBottom = parentRect.bottom;
187
-
188
- top = rect.top - parentTop - height + 10;
189
- bottom = rect.bottom - parentTop - height + 10;
190
-
191
- marginToTop = Math.abs(rect.top - parentTop);
192
-
193
- // Adjust visibleTop and visibleBottom for parent element
194
- const visibleTop = Math.max(parentTop, rect.top);
195
- const visibleBottom = Math.min(parentBottom, rect.bottom);
196
-
197
- visibleHeight = Math.max(0, visibleBottom - visibleTop);
198
-
199
- } else {
200
-
201
- // Original calculations if no parent element is provided
202
- const visibleTop = Math.max(0, rect.top);
203
- const visibleBottom = Math.min(rect.bottom, viewportHeight);
204
-
205
- marginToTop = Math.abs(rect.top);
206
-
207
- visibleHeight = Math.max(0, visibleBottom - visibleTop);
208
-
209
- }
210
-
211
- if (top > 0) {
212
- newState.push('inview-after');
213
- }
214
-
215
- if (top <= 0 && bottom >= 0) {
216
- newState.push('inview-current');
217
- }
218
-
219
- if (bottom < 0) {
220
- newState.push('inview-before');
221
- }
222
-
223
- // Check if more than 30% of the element is visible
224
- if (visibleHeight / rect.height >= 0.3) {
225
- newState.push('inview-visible');
226
- }
227
-
228
- if (newState.includes('inview-current') && options?.align) {
229
-
230
- if(!this.lastScrollingAdjustment || Date.now() - this.lastScrollingAdjustment > 1500) {
231
-
232
- requestAnimationFrame(() => this.adjustScrolling(element, marginToTop));
233
-
234
- }
235
-
236
- }
237
-
238
- state.value.length = 0;
239
- state.value.push(...newState);
240
- }
241
-
242
- checkPreload (component: HTMLElement, state: Ref<boolean>) {
243
-
244
- const preloadRange = window?.outerHeight * 2;
245
- const rect = component.getBoundingClientRect();
246
-
247
- if (rect.bottom > -preloadRange && rect.top < preloadRange) {
248
-
249
- state.value = true;
250
-
251
- }
252
-
253
- }
254
-
255
- adjustScrolling (component: HTMLElement, top: number) {
256
-
257
- const offset = this.height() / 10;
258
-
259
- if (top < offset && top > (offset * -1)) {
260
-
261
- this.lastScrollingAdjustment = Date.now();
262
-
263
- component.scrollIntoView({
264
- behavior: 'smooth',
265
- });
266
-
267
- }
268
-
269
- }
270
-
271
- moduleRendered () {
272
-
273
- this.moduleAdded.value++;
274
-
275
- }
276
-
277
- }
278
-
279
- const inview = new Inview();
280
-
281
1
  /**
282
- * the preload option is used to check if the element is in range of two times of the viewport and changes the preload value accordingly
2
+ * Inview lives in @lilaquadrat/frontend - it is shared with the internal apps,
3
+ * and that copy is the truth. This path stays so a design keeps importing
4
+ * '@/plugins/inview'.
283
5
  *
284
- * without the preload options the inviewState array holds the following values
285
- ** inview-before - the element is before the viewport
286
- ** inview-current - the element is centered in the viewport
287
- ** inview-after - the element is after the viewport
288
- ** inview-visible - more than 30% of the element are visible in the current viewport, regardless if it is centered or nor
6
+ * Importing the module starts the scroll listener.
289
7
  */
290
-
291
- export function useInview (element?: Ref<HTMLElement | undefined>, options?: { align?: boolean, preload?: boolean }) {
292
-
293
- const state = ref<string[]>([]);
294
- const preload = ref<boolean>(false);
295
-
296
- if (element) {
297
-
298
- watch(element, () => {
299
-
300
- if (element.value) {
301
-
302
- inview.moduleRendered();
303
-
304
- if (options?.preload) {
305
-
306
- inview.checkPreload(element.value, preload);
307
-
308
- const unwatchPreload = watch(inview.scrolled, () => {
309
-
310
- if (!element.value) return;
311
-
312
- inview.checkPreload(element.value as HTMLElement, preload);
313
-
314
- //unwatch when the preload value is true
315
- if (preload.value) unwatchPreload();
316
-
317
- })
318
-
319
- } else {
320
-
321
- inview.check(element.value, state, options);
322
-
323
- watch(inview.scrolled, () => {
324
-
325
- inview.check(element.value as HTMLElement, state, options)
326
-
327
- })
328
-
329
- watch(inview.moduleAdded, () => {
330
-
331
- requestAnimationFrame(() => {
332
-
333
- inview.check(element.value as HTMLElement, state, options);
334
-
335
- })
336
-
337
- })
338
-
339
- }
340
-
341
- }
342
-
343
- })
344
-
345
- }
346
-
347
- return { inviewState: state, scrolled: inview.scrolled, scrollDirection: inview.scrollDirection, preload };
348
-
349
- }
350
-
351
- export default inview;
8
+ export * from '@lilaquadrat/frontend/inview';
9
+ export { default } from '@lilaquadrat/frontend/inview';
@@ -1,128 +1,7 @@
1
- import logger from '../mixins/logger';
2
- import { ref, computed } from 'vue';
3
-
4
- class Resize {
5
-
6
- timeout: any;
7
-
8
- debounceTime: number = 50;
9
-
10
- resizedEvent: Event;
11
-
12
- mediaEvent: Event;
13
-
14
- media = ref<string>('mobile');
15
-
16
- resized = ref<number>(Date.now());
17
-
18
- realHeight = ref<number>(0);
19
-
20
- /**
21
- * if true we fire an inital resized event to indicate that resizing has started without waiting for the debounce
22
- */
23
- triggerStart = ref<boolean>(true);
24
-
25
- safeWindow = computed(() => {
26
-
27
- if(typeof window === 'undefined') return null;
28
- return window;
29
-
30
- });
31
-
32
- constructor () {
33
-
34
- this.resizedEvent = new Event('resized');
35
- this.mediaEvent = new Event('media');
36
- this.realHeight.value = this.safeWindow.value?.innerHeight || 0;
37
-
38
- this.getMediaQuery();
39
-
40
- this.safeWindow.value?.addEventListener('resize', () => this.debounce());
41
-
42
- }
43
-
44
- trigger (isStart?: boolean) {
45
-
46
- this.getMediaQuery();
47
-
48
- if(this.safeWindow.value) {
49
-
50
- this.realHeight.value = this.safeWindow.value?.innerHeight;
51
- }
52
-
53
- this.resized.value = Date.now();
54
- this.safeWindow.value?.dispatchEvent(this.resizedEvent);
55
-
56
- if(!isStart) this.triggerStart.value = true;
57
-
58
- }
59
-
60
- getMediaQuery (): void {
61
-
62
- if(typeof document === 'undefined') return;
63
-
64
- const element = document.getElementById('mediadetection');
65
-
66
- if (!element) return;
67
-
68
- const child = Array.from(element.children).find(child => this.safeWindow.value?.getComputedStyle(child).display === 'block');
69
-
70
- if (child) {
71
- const childClass = child.getAttribute('class') as string;
72
-
73
- if (this.media.value !== childClass) {
74
-
75
- this.media.value = childClass;
76
- this.safeWindow.value?.dispatchEvent(this.mediaEvent);
77
-
78
- } else {
79
-
80
- this.media.value = childClass;
81
-
82
- }
83
- }
84
- }
85
-
86
- debounce () {
87
-
88
- /**
89
- * indicates that the scrolling has begun and fire an inital scrolling event without waiting for the debounce
90
- */
91
- if(this.triggerStart.value) {
92
-
93
- this.triggerStart.value = false;
94
- this.trigger(true);
95
-
96
- }
97
-
98
- clearTimeout(this.timeout);
99
- this.timeout = setTimeout(() => {
100
-
101
- this.trigger();
102
-
103
- }, this.debounceTime);
104
-
105
- }
106
-
107
- }
108
-
109
- const resize = new Resize();
110
- const plugin = {
111
- install: (): void => {
112
- // this exists only for initialization purposes
113
- logger.plugins('resize installed');
114
- },
115
- };
116
-
117
- function useResize () {
118
-
119
- return {realHeight: resize.realHeight, media: resize.media, resized: resize.resized, plugin: resize};
120
-
121
- }
122
-
123
- export default plugin;
124
-
125
- export {
126
- useResize,
127
- resize
128
- };
1
+ /**
2
+ * Resize lives in @lilaquadrat/frontend - it is shared with the internal apps,
3
+ * and that copy is the truth. This path stays so a design keeps importing
4
+ * '@/plugins/resize'.
5
+ */
6
+ export * from '@lilaquadrat/frontend/resize';
7
+ export { default } from '@lilaquadrat/frontend/resize';
@@ -7,7 +7,7 @@ import { useAuth } from '../plugins/auth';
7
7
  import type FrontendConfig from '../interfaces/FrontendConfig.interface';
8
8
  import type { AxiosError } from 'axios';
9
9
  import { computed } from 'vue';
10
- import { hardCopy } from '@lilaquadrat/studio/lib/esm/frontend';
10
+ import { hardCopy } from '@lilaquadrat/frontend/content';
11
11
 
12
12
  export const useMainStore = defineStore('main', () => {
13
13
 
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, watch } from 'vue';
3
- import { distributeGenericData, generateDataWithContent, hardCopy, prepareContent } from '@lilaquadrat/studio';
3
+ import { distributeGenericData, generateDataWithContent, hardCopy, prepareContent } from '@lilaquadrat/frontend/content';
4
4
  import type { BasicData, Content, ContentWithPositions, GenericData } from '@lilaquadrat/interfaces';
5
5
  import useMainStore from '../stores/main.store';
6
6
  import { useRoute } from 'vue-router';
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import useEditorStore from '../stores/editor.store';
3
- import { hardCopy, prepareContent } from '@lilaquadrat/studio/lib/esm/frontend';
3
+ import { hardCopy, prepareContent } from '@lilaquadrat/frontend/content';
4
4
  import { getCurrentInstance, nextTick, onMounted, ref } from 'vue';
5
5
  import { type Content, type ContentWithPositions, type EditorActiveModule, type StudioIframeMessage } from '@lilaquadrat/interfaces';
6
6
  import { onBeforeUnmount } from 'vue';
@@ -2,7 +2,7 @@
2
2
  import { auth } from '../plugins/auth';
3
3
  import useMainStore from '../stores/main.store';
4
4
  import useUserStore from '../stores/user.store';
5
- import { prepareContent } from '@lilaquadrat/studio/lib/esm/frontend';
5
+ import { prepareContent } from '@lilaquadrat/frontend/content';
6
6
  import type { ContentWithPositions } from '@lilaquadrat/interfaces';
7
7
  import { computed, onBeforeMount } from 'vue';
8
8
  import { useRoute, useRouter } from 'vue-router';
package/tooling/vite.ts CHANGED
@@ -110,9 +110,14 @@ export function createViteConfig (options: ViteFactoryOptions) {
110
110
  host: '0.0.0.0',
111
111
  },
112
112
  optimizeDeps: {
113
- // keep both packages in source form: their less needs the project
114
- // variables injected below, which a prebundle would not see
115
- exclude: ['@lilaquadrat/studio', '@lilaquadrat/design-core']
113
+ // design-core ships source, and its less needs the project variables
114
+ // injected below - a prebundle would not see them.
115
+ //
116
+ // @lilaquadrat/frontend rides along because design-core imports it:
117
+ // prebundling it there and serving it raw here would give the resize and
118
+ // inview singletons two module identities, and a second scroll listener
119
+ // with it
120
+ exclude: ['@lilaquadrat/design-core', '@lilaquadrat/frontend']
116
121
  },
117
122
  build: {
118
123
  cssCodeSplit : false,
@@ -173,13 +178,22 @@ export function createViteConfig (options: ViteFactoryOptions) {
173
178
  'vue-router',
174
179
  'vue-i18n',
175
180
  'pinia',
176
- '@lilaquadrat/interfaces',
181
+ // holds a static request cache, so a second copy would cache twice
177
182
  '@lilaquadrat/sdk',
178
- '@lilaquadrat/studio',
183
+ // resize and inview are singletons: a second copy means a second
184
+ // scroll listener and a second set of refs nobody reads
185
+ '@lilaquadrat/frontend',
179
186
  '@auth0/auth0-spa-js',
180
187
  'axios',
181
188
  'dayjs',
182
189
  ],
190
+ /**
191
+ * @lilaquadrat/studio is deliberately NOT deduped: it is the backend
192
+ * package and a project may still hold a copy for other reasons, while
193
+ * a second one costs nothing here.
194
+ *
195
+ * @lilaquadrat/interfaces is types only, so it has nothing to dedupe.
196
+ */
183
197
  },
184
198
  define: {
185
199
  '__FRONTEND_CONFIG__' : JSON.stringify(config),