@upsoftware_tech/svarium 1.0.8 → 1.0.9

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
@@ -5,7 +5,7 @@
5
5
  "email": "info@upSoftware.tech",
6
6
  "url": "https://upsoftware.tech"
7
7
  },
8
- "version": "1.0.8",
8
+ "version": "1.0.9",
9
9
  "type": "module",
10
10
  "files": [
11
11
  "dist",
@@ -65,6 +65,7 @@
65
65
  "vue-draggable-plus": "^0.6.1",
66
66
  "vue-input-otp": "^0.3.2",
67
67
  "vue-sonner": "^2.0.9",
68
+ "vue3-colorpicker": "^2.3.0",
68
69
  "ziggy-js": "^2.6.0"
69
70
  }
70
71
  }
@@ -5,6 +5,7 @@ type AppearanceLike = {
5
5
  fontSize?: unknown;
6
6
  textColor?: unknown;
7
7
  fontColor?: unknown;
8
+ textDecoration?: unknown;
8
9
  bgColor?: unknown;
9
10
  borderWidth?: unknown;
10
11
  borderRadius?: unknown;
@@ -81,6 +82,7 @@ const fontColorToClass: Record<string, string> = {
81
82
  fuchsia: 'text-fuchsia-500',
82
83
  pink: 'text-pink-500',
83
84
  rose: 'text-rose-500',
85
+ primary: 'text-primary',
84
86
  white: 'text-white',
85
87
  black: 'text-black',
86
88
  };
@@ -231,6 +233,44 @@ function appendTextColor(
231
233
  style.color = raw;
232
234
  }
233
235
 
236
+ function appendTextDecoration(
237
+ value: unknown,
238
+ classTokens: string[],
239
+ style: Record<string, string>,
240
+ ): void {
241
+ const raw = value !== undefined && value !== null ? String(value).trim() : '';
242
+ if (raw === '') {
243
+ return;
244
+ }
245
+
246
+ const normalized = raw.toLowerCase();
247
+ const tokens = normalized.split(/\s+/).map((token) => token.trim()).filter(Boolean);
248
+ const utilityTokens = new Set([
249
+ 'underline',
250
+ 'overline',
251
+ 'line-through',
252
+ 'no-underline',
253
+ 'decoration-solid',
254
+ 'decoration-double',
255
+ 'decoration-dotted',
256
+ 'decoration-dashed',
257
+ 'decoration-wavy',
258
+ 'decoration-auto',
259
+ 'decoration-from-font',
260
+ ]);
261
+
262
+ if (tokens.length > 0) {
263
+ const allUtilities = tokens.every((token) => utilityTokens.has(token) || token.startsWith('decoration-'));
264
+
265
+ if (allUtilities) {
266
+ classTokens.push(...tokens);
267
+ return;
268
+ }
269
+ }
270
+
271
+ style.textDecoration = raw;
272
+ }
273
+
234
274
  function appendBackgroundColor(
235
275
  value: unknown,
236
276
  classTokens: string[],
@@ -669,6 +709,7 @@ function normalizeAppearance(appearance: AppearanceLike): {
669
709
  const { light: lightTextColor, dark: darkTextColor } = getColorParts(textColorValue);
670
710
  appendTextColor(lightTextColor, classTokens, style);
671
711
  appendTextColor(darkTextColor, classTokens, style, true);
712
+ appendTextDecoration(appearance.textDecoration, classTokens, style);
672
713
 
673
714
  const { light: lightBgColor, dark: darkBgColor } = getColorParts(appearance.bgColor);
674
715
  appendBackgroundColor(lightBgColor, classTokens, style);
@@ -700,6 +741,7 @@ function normalizeAppearance(appearance: AppearanceLike): {
700
741
  delete nextAppearance.fontSize;
701
742
  delete nextAppearance.textColor;
702
743
  delete nextAppearance.fontColor;
744
+ delete nextAppearance.textDecoration;
703
745
  delete nextAppearance.bgColor;
704
746
  delete nextAppearance.borderWidth;
705
747
  delete nextAppearance.borderRadius;
@@ -740,6 +782,14 @@ export function normalizeNodeProps(nodeProps: Record<string, any>): Record<strin
740
782
  resolved.class = combinedClass.join(' ');
741
783
  }
742
784
 
785
+ const appearanceStyle = (normalized.appearance as AppearanceLike).style;
786
+ if (appearanceStyle !== undefined && appearanceStyle !== null && appearanceStyle !== '') {
787
+ resolved.style = mergeStyle(
788
+ resolved.style,
789
+ appearanceStyle as Record<string, string>,
790
+ );
791
+ }
792
+
743
793
  resolved.style = mergeStyle(resolved.style, normalized.style);
744
794
  resolved.appearance = normalized.appearance;
745
795
  }
@@ -1,5 +1,9 @@
1
1
  <template>
2
- <BlockFormLoginMethodVerification :verification-methods="verificationMethods" :session="session" />
2
+ <BlockFormLoginMethodVerification
3
+ :verification-methods="verificationMethods"
4
+ :session="session"
5
+ :type="type"
6
+ />
3
7
  </template>
4
8
 
5
9
  <script setup lang="ts">
@@ -9,11 +13,13 @@ import { AuthLayout, BlockFormLoginMethodVerification } from '@upsoftware_tech/s
9
13
  interface Props {
10
14
  verificationMethods?: VerificationMethod[] | Record<string, VerificationMethod>;
11
15
  session: string;
16
+ type?: string;
12
17
  }
13
18
 
14
19
  withDefaults(defineProps<Props>(), {
15
20
  verificationMethods: () => [],
16
21
  session: '',
22
+ type: 'login',
17
23
  });
18
24
 
19
25
  defineOptions({
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <component :is="resolvedLayout">
3
+ <BlockPageRegister v-bind="props" />
4
+ </component>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from 'vue';
9
+ import { AuthLayout, BlockPageRegister, PanelLayout } from '@upsoftware_tech/svarium';
10
+
11
+ interface RegisterFieldOption {
12
+ value: string | number | boolean;
13
+ label: string;
14
+ }
15
+
16
+ interface RegisterField {
17
+ name: string;
18
+ type?: string;
19
+ label?: string;
20
+ placeholder?: string;
21
+ hint?: string;
22
+ required?: boolean;
23
+ autocomplete?: string;
24
+ value?: string | number | boolean;
25
+ options?: RegisterFieldOption[];
26
+ }
27
+
28
+ interface Props {
29
+ title?: string;
30
+ subtitle?: string;
31
+ loginLabel?: string;
32
+ loginLinkLabel?: string;
33
+ loginLink?: string;
34
+ submitLabel?: string;
35
+ action?: string;
36
+ layout?: string;
37
+ schema?: Record<string, any>[];
38
+ fields?: RegisterField[];
39
+ }
40
+
41
+ const props = withDefaults(defineProps<Props>(), {
42
+ title: 'Create account',
43
+ subtitle: 'Fill in the form to create your account',
44
+ loginLabel: 'Already have an account?',
45
+ loginLinkLabel: 'Sign in',
46
+ loginLink: 'panel.auth.login',
47
+ submitLabel: 'Create account',
48
+ action: 'panel.auth.register.set',
49
+ layout: 'AuthLayout',
50
+ fields: () => [
51
+ { name: 'email', type: 'email', label: 'Email address', required: true, autocomplete: 'email' },
52
+ { name: 'password', type: 'password', label: 'Password', required: true, autocomplete: 'new-password' },
53
+ {
54
+ name: 'password_confirmation',
55
+ type: 'password',
56
+ label: 'Confirm password',
57
+ required: true,
58
+ autocomplete: 'new-password',
59
+ },
60
+ ],
61
+ });
62
+
63
+ const resolvedLayout = computed(() => {
64
+ const key = (props.layout ?? 'AuthLayout').trim();
65
+
66
+ if (key === 'PanelLayout') {
67
+ return PanelLayout;
68
+ }
69
+
70
+ if (key === 'AuthLayout') {
71
+ return AuthLayout;
72
+ }
73
+
74
+ return key || AuthLayout;
75
+ });
76
+ </script>
@@ -1,6 +1,10 @@
1
1
  <template>
2
+ <Head>
3
+ <link v-if="seo?.canonical" rel="canonical" :href="seo.canonical" />
4
+ <meta v-if="seo?.robots" name="robots" :content="seo.robots" />
5
+ </Head>
2
6
  <pre class="hidden">{{ tree }}</pre>
3
- <main class="flex bg-slate-100 dark:bg-slate-950 h-screen">
7
+ <main class="bg-slate-100 dark:bg-slate-950">
4
8
  <template v-if="Array.isArray(tree)">
5
9
  <component
6
10
  :is="renderNode(node)"
@@ -17,7 +21,7 @@
17
21
  </template>
18
22
 
19
23
  <script setup lang="ts">
20
- import { usePage } from '@inertiajs/vue3';
24
+ import { Head, usePage } from '@inertiajs/vue3';
21
25
  import { computed, h, isRef, ref, resolveComponent, watch } from 'vue';
22
26
  import { Toaster } from 'vue-sonner';
23
27
  import { toast } from 'vue-sonner';
@@ -64,11 +68,44 @@ function updateModelValue(modelContext: any, value: any): void {
64
68
  emit('update:modelValue', value);
65
69
  }
66
70
 
71
+ function shouldRenderVueIf(value: any): boolean {
72
+ if (typeof value === 'boolean') {
73
+ return value;
74
+ }
75
+
76
+ if (typeof value === 'number') {
77
+ return value !== 0;
78
+ }
79
+
80
+ if (typeof value === 'string') {
81
+ const normalized = value.trim().toLowerCase();
82
+
83
+ if (normalized === '' || normalized === 'false' || normalized === '0' || normalized === 'null' || normalized === 'undefined') {
84
+ return false;
85
+ }
86
+
87
+ return true;
88
+ }
89
+
90
+ if (value === null || value === undefined) {
91
+ return false;
92
+ }
93
+
94
+ return Boolean(value);
95
+ }
96
+
67
97
  function renderNode(node: any, modelContext?: any) {
68
98
  if (!node || typeof node !== 'object') {
69
99
  return null;
70
100
  }
71
101
 
102
+ const rawNodeProps = node.props && typeof node.props === 'object' ? node.props : {};
103
+ if (Object.prototype.hasOwnProperty.call(rawNodeProps, 'vIf')) {
104
+ if (!shouldRenderVueIf(rawNodeProps.vIf)) {
105
+ return null;
106
+ }
107
+ }
108
+
72
109
  const componentName = node.component ?? node.type;
73
110
 
74
111
  if (!componentName) {
@@ -78,10 +115,29 @@ function renderNode(node: any, modelContext?: any) {
78
115
 
79
116
  const component = resolveComponent(componentName);
80
117
  const slots: Record<string, (...args: any[]) => any> = {};
81
-
82
- if (Array.isArray(node.children) && node.children.length) {
83
- slots.default = () => node.children.map((child: any) => renderNode(child, modelContext));
84
- }
118
+ const nodeProps = { ...(node.props ?? {}) };
119
+ delete nodeProps.vIf;
120
+ const slotPlacementRaw =
121
+ nodeProps.__slotPlacement && typeof nodeProps.__slotPlacement === 'object'
122
+ ? nodeProps.__slotPlacement
123
+ : {};
124
+
125
+ delete nodeProps.__slotPlacement;
126
+
127
+ const resolveSlotContext = (slotProps: any = {}, fallbackModelContext: any = modelContext) => {
128
+ const hasSlotModel =
129
+ Object.prototype.hasOwnProperty.call(slotProps, 'modelValue') ||
130
+ Object.prototype.hasOwnProperty.call(slotProps, 'tab');
131
+
132
+ return hasSlotModel || typeof slotProps?.updateModelValue === 'function'
133
+ ? {
134
+ value: slotProps?.modelValue ?? slotProps?.tab ?? resolveModelValue(fallbackModelContext),
135
+ update: slotProps?.updateModelValue,
136
+ }
137
+ : (slotProps?.model ?? fallbackModelContext);
138
+ };
139
+
140
+ const slotContentMap: Record<string, any[]> = {};
85
141
 
86
142
  if (node.slots && !Array.isArray(node.slots)) {
87
143
  for (const [name, content] of Object.entries(node.slots)) {
@@ -89,28 +145,180 @@ function renderNode(node: any, modelContext?: any) {
89
145
  continue;
90
146
  }
91
147
 
148
+ slotContentMap[name] = content;
92
149
  slots[name] = (slotProps: any = {}) => {
93
- const hasSlotModel =
94
- Object.prototype.hasOwnProperty.call(slotProps, 'modelValue') ||
95
- Object.prototype.hasOwnProperty.call(slotProps, 'tab');
96
-
97
- const slotModelContext =
98
- hasSlotModel || typeof slotProps?.updateModelValue === 'function'
99
- ? {
100
- value: slotProps?.modelValue ?? slotProps?.tab ?? resolveModelValue(modelContext),
101
- update: slotProps?.updateModelValue,
102
- }
103
- : (slotProps?.model ?? modelContext);
104
-
150
+ const slotModelContext = resolveSlotContext(slotProps);
105
151
  return content.map((child: any) => renderNode(child, slotModelContext));
106
152
  };
107
153
  }
108
154
  }
109
155
 
156
+ const renderDefaultChildren = (slotProps: any = {}) => {
157
+ if (!Array.isArray(node.children) || node.children.length === 0) {
158
+ return [];
159
+ }
160
+
161
+ const slotModelContext = resolveSlotContext(slotProps);
162
+ return node.children.map((child: any) => renderNode(child, slotModelContext));
163
+ };
164
+
165
+ type PlacedSlot = {
166
+ slotName: string;
167
+ anchor: 'header' | 'footer';
168
+ position: 'before' | 'after';
169
+ priority: number | null;
170
+ order: number;
171
+ };
172
+
173
+ const placedSlots: PlacedSlot[] = [];
174
+ let placementFallbackOrder = 0;
175
+
176
+ for (const [slotName, config] of Object.entries(slotPlacementRaw as Record<string, any>)) {
177
+ if (!Array.isArray(slotContentMap[slotName])) {
178
+ continue;
179
+ }
180
+
181
+ const anchor = String(config?.anchor ?? 'header').toLowerCase();
182
+ const position = String(config?.position ?? 'after').toLowerCase();
183
+ const normalizedAnchor: 'header' | 'footer' = anchor === 'footer' ? 'footer' : 'header';
184
+ const normalizedPosition: 'before' | 'after' = position === 'before' ? 'before' : 'after';
185
+
186
+ let normalizedPriority: number | null = null;
187
+ const rawPriority = config?.priority;
188
+ if (typeof rawPriority === 'number' && Number.isFinite(rawPriority)) {
189
+ normalizedPriority = rawPriority;
190
+ } else if (typeof rawPriority === 'string' && rawPriority.trim() !== '' && Number.isFinite(Number(rawPriority))) {
191
+ normalizedPriority = Number(rawPriority);
192
+ }
193
+
194
+ const rawOrder = config?.order;
195
+ let normalizedOrder = placementFallbackOrder;
196
+ if (typeof rawOrder === 'number' && Number.isFinite(rawOrder)) {
197
+ normalizedOrder = rawOrder;
198
+ } else if (typeof rawOrder === 'string' && rawOrder.trim() !== '' && Number.isFinite(Number(rawOrder))) {
199
+ normalizedOrder = Number(rawOrder);
200
+ }
201
+ placementFallbackOrder += 1;
202
+
203
+ placedSlots.push({
204
+ slotName,
205
+ anchor: normalizedAnchor,
206
+ position: normalizedPosition,
207
+ priority: normalizedPriority,
208
+ order: normalizedOrder,
209
+ });
210
+ }
211
+
212
+ const sortPlacedSlots = (items: PlacedSlot[]): string[] => {
213
+ return [...items]
214
+ .sort((a, b) => {
215
+ const aHasPriority = a.priority !== null;
216
+ const bHasPriority = b.priority !== null;
217
+
218
+ if (aHasPriority && bHasPriority) {
219
+ if (a.priority !== b.priority) {
220
+ return (a.priority as number) - (b.priority as number);
221
+ }
222
+
223
+ return a.order - b.order;
224
+ }
225
+
226
+ if (aHasPriority !== bHasPriority) {
227
+ return aHasPriority ? -1 : 1;
228
+ }
229
+
230
+ return a.order - b.order;
231
+ })
232
+ .map((item) => item.slotName);
233
+ };
234
+
235
+ const beforeHeader = sortPlacedSlots(
236
+ placedSlots.filter((item) => item.anchor === 'header' && item.position === 'before'),
237
+ );
238
+ const afterHeader = sortPlacedSlots(
239
+ placedSlots.filter((item) => item.anchor === 'header' && item.position === 'after'),
240
+ );
241
+ const beforeFooter = sortPlacedSlots(
242
+ placedSlots.filter((item) => item.anchor === 'footer' && item.position === 'before'),
243
+ );
244
+ const afterFooter = sortPlacedSlots(
245
+ placedSlots.filter((item) => item.anchor === 'footer' && item.position === 'after'),
246
+ );
247
+
248
+ const placedSlotNames = new Set(placedSlots.map((item) => item.slotName));
249
+
250
+ if (placedSlotNames.size > 0) {
251
+ for (const slotName of placedSlotNames) {
252
+ delete slots[slotName];
253
+ }
254
+ }
255
+
256
+ const renderPlacedSlotNames = (slotNames: string[], slotProps: any = {}) => {
257
+ const slotModelContext = resolveSlotContext(slotProps);
258
+ const rendered: any[] = [];
259
+
260
+ for (const slotName of slotNames) {
261
+ const content = slotContentMap[slotName];
262
+ if (!Array.isArray(content) || content.length === 0) {
263
+ continue;
264
+ }
265
+
266
+ rendered.push(...content.map((child: any) => renderNode(child, slotModelContext)));
267
+ }
268
+
269
+ return rendered;
270
+ };
271
+
272
+ const hasMainHeader = !placedSlotNames.has('header') && Array.isArray(slotContentMap.header) && slotContentMap.header.length > 0;
273
+ const hasMainFooter = !placedSlotNames.has('footer') && Array.isArray(slotContentMap.footer) && slotContentMap.footer.length > 0;
274
+ const hasMainBody = !placedSlotNames.has('body') && Array.isArray(slotContentMap.body) && slotContentMap.body.length > 0;
275
+
276
+ const headerSlotOrder = [...beforeHeader, ...(hasMainHeader ? ['header'] : []), ...afterHeader];
277
+ const footerSlotOrder = [...beforeFooter, ...(hasMainFooter ? ['footer'] : []), ...afterFooter];
278
+
279
+ const hasStructuredSlots =
280
+ placedSlots.length > 0 || hasMainHeader || hasMainFooter || hasMainBody;
281
+
282
+ if (hasStructuredSlots) {
283
+ slots.header = (slotProps: any = {}) => renderPlacedSlotNames(headerSlotOrder, slotProps);
284
+ slots.footer = (slotProps: any = {}) => renderPlacedSlotNames(footerSlotOrder, slotProps);
285
+ slots.body = (slotProps: any = {}) => {
286
+ if (hasMainBody) {
287
+ return renderPlacedSlotNames(['body'], slotProps);
288
+ }
289
+
290
+ return renderDefaultChildren(slotProps);
291
+ };
292
+ if (placedSlots.length > 0) {
293
+ slots.default = (slotProps: any = {}) => {
294
+ const rendered: any[] = [];
295
+
296
+ rendered.push(...renderPlacedSlotNames(headerSlotOrder, slotProps));
297
+ if (hasMainBody) {
298
+ rendered.push(...renderPlacedSlotNames(['body'], slotProps));
299
+ } else {
300
+ rendered.push(...renderDefaultChildren(slotProps));
301
+ }
302
+ rendered.push(...renderPlacedSlotNames(footerSlotOrder, slotProps));
303
+
304
+ return rendered;
305
+ };
306
+ } else if (Array.isArray(node.children) && node.children.length) {
307
+ slots.default = (slotProps: any = {}) => renderDefaultChildren(slotProps);
308
+ }
309
+ } else if (Array.isArray(node.children) && node.children.length) {
310
+ slots.default = (slotProps: any = {}) => renderDefaultChildren(slotProps);
311
+ }
312
+
313
+ const normalizedProps = normalizeNodeProps(nodeProps);
314
+ if (Object.prototype.hasOwnProperty.call(normalizedProps, 'appearance')) {
315
+ delete normalizedProps.appearance;
316
+ }
317
+
110
318
  return h(
111
319
  component,
112
320
  {
113
- ...normalizeNodeProps(node.props ?? {}),
321
+ ...normalizedProps,
114
322
  modelValue: resolveModelValue(modelContext),
115
323
  'onUpdate:modelValue': (value: any) => {
116
324
  updateModelValue(modelContext, value);
@@ -130,6 +338,10 @@ type FlashMessages = {
130
338
 
131
339
  const page = usePage();
132
340
  const lastFlashFingerprint = ref('');
341
+ const seo = computed(() => {
342
+ const currentPage = page as unknown as { props?: { seo?: Record<string, any> } };
343
+ return currentPage.props?.seo ?? {};
344
+ });
133
345
 
134
346
  const flash = computed<FlashMessages>(() => {
135
347
  const currentPage = page as unknown as { flash?: unknown; props?: { flash?: unknown } };