runeforge 0.0.2 → 0.0.4
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/components/IconRenderer.svelte +2 -2
- package/dist/components/Modal.svelte +5 -10
- package/dist/components/common/Header.svelte +2 -2
- package/dist/components/crud/Field.svelte +12 -17
- package/dist/components/navigation/Breadcrumbs.svelte +38 -2
- package/dist/components/table/Paginator.svelte +54 -22
- package/dist/icons/sets/bootstrap.js +4 -1
- package/dist/icons/types.d.ts +1 -6
- package/dist/index.d.ts +1 -1
- package/package.json +80 -78
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
} = $props();
|
|
13
13
|
|
|
14
14
|
const icons = $derived(getIconSet());
|
|
15
|
-
const IconComponent = $derived(icons?.
|
|
15
|
+
const IconComponent = $derived(icons?.getByName?.(name) ?? null);
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
{#if IconComponent}
|
|
19
|
-
<IconComponent {
|
|
19
|
+
<IconComponent {size} class={className} />
|
|
20
20
|
{:else}
|
|
21
21
|
<span class={className} title={name}></span>
|
|
22
22
|
{/if}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
} = $props();
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
|
-
<
|
|
16
|
+
<dialog class="modal" open>
|
|
17
17
|
<div class="modal-box">
|
|
18
18
|
<div class="flex items-center justify-between gap-4">
|
|
19
19
|
<h3 class="text-lg font-bold">{title}</h3>
|
|
@@ -35,13 +35,8 @@
|
|
|
35
35
|
</div>
|
|
36
36
|
|
|
37
37
|
{#if onClose}
|
|
38
|
-
<div
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
role="button"
|
|
42
|
-
tabindex="0"
|
|
43
|
-
onclick={onClose}
|
|
44
|
-
onkeydown={(e) => e.key === 'Enter' && onClose?.()}
|
|
45
|
-
></div>
|
|
38
|
+
<div class="modal-backdrop">
|
|
39
|
+
<button onclick={onClose} aria-label="Cerrar"></button>
|
|
40
|
+
</div>
|
|
46
41
|
{/if}
|
|
47
|
-
</
|
|
42
|
+
</dialog>
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
20
|
-
<div class="flex flex-col gap-1">
|
|
20
|
+
<div class="flex flex-col gap-1 flex-1">
|
|
21
21
|
<h1>{title}</h1>
|
|
22
22
|
<Breadcrumbs items={breadcrumbs} {admin} />
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
25
|
{#if buttons}
|
|
26
|
-
<div class="flex items-center gap-2 pt-1">
|
|
26
|
+
<div class="flex shrink-0 items-center gap-2 sm:pt-1">
|
|
27
27
|
{@render buttons()}
|
|
28
28
|
</div>
|
|
29
29
|
{/if}
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
<div class="flex flex-col gap-1">
|
|
44
44
|
{#if field.type === 'file'}
|
|
45
45
|
<div class="flex justify-center">
|
|
46
|
-
<Avatar src={preview} text={avatarInitials} alt={labelText} class="w-
|
|
46
|
+
<Avatar src={preview} text={avatarInitials} alt={labelText} class="w-20 rounded-full" textClass="text-xl" />
|
|
47
47
|
</div>
|
|
48
48
|
{/if}
|
|
49
49
|
|
|
@@ -59,25 +59,20 @@
|
|
|
59
59
|
type="checkbox"
|
|
60
60
|
id={field.attribute}
|
|
61
61
|
{name}
|
|
62
|
-
class="toggle"
|
|
62
|
+
class="toggle toggle-primary"
|
|
63
63
|
checked={!!saved}
|
|
64
64
|
disabled={readonly}
|
|
65
65
|
/>
|
|
66
66
|
{:else if field.type === 'file'}
|
|
67
67
|
{#if !readonly}
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
/>
|
|
77
|
-
{#if field.placeholder}
|
|
78
|
-
<Label text={field.placeholder} for={field.attribute} class="label" />
|
|
79
|
-
{/if}
|
|
80
|
-
</fieldset>
|
|
68
|
+
<input
|
|
69
|
+
type="file"
|
|
70
|
+
id={field.attribute}
|
|
71
|
+
{name}
|
|
72
|
+
class="file-input file-input-bordered w-full"
|
|
73
|
+
class:file-input-error={!!error}
|
|
74
|
+
onchange={onFileChange}
|
|
75
|
+
/>
|
|
81
76
|
{/if}
|
|
82
77
|
<!-- read-only file value is shown as the avatar above -->
|
|
83
78
|
{:else if field.type === 'select'}
|
|
@@ -121,14 +116,14 @@
|
|
|
121
116
|
{/if}
|
|
122
117
|
{:else if field.type === 'textarea'}
|
|
123
118
|
{#if readonly}
|
|
124
|
-
<textarea id={field.attribute} class="textarea textarea-bordered w-full" value={displayValue} disabled></textarea>
|
|
119
|
+
<textarea id={field.attribute} class="textarea textarea-bordered bg-base-100 w-full" value={displayValue} disabled></textarea>
|
|
125
120
|
{:else}
|
|
126
121
|
<textarea
|
|
127
122
|
id={field.attribute}
|
|
128
123
|
{name}
|
|
129
124
|
placeholder={field.placeholder ?? ''}
|
|
130
125
|
bind:value={record[field.attribute]}
|
|
131
|
-
class="textarea textarea-bordered w-full"
|
|
126
|
+
class="textarea textarea-bordered bg-base-100 w-full"
|
|
132
127
|
class:textarea-error={!!error}
|
|
133
128
|
></textarea>
|
|
134
129
|
{/if}
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
{@const ItemIcon = item.icon}
|
|
35
35
|
<ItemIcon class="size-4" />
|
|
36
36
|
{/if}
|
|
37
|
-
<span
|
|
37
|
+
<span>{item.label}</span>
|
|
38
38
|
</a>
|
|
39
39
|
{:else}
|
|
40
40
|
<span class="inline-flex items-center gap-2">
|
|
@@ -42,10 +42,46 @@
|
|
|
42
42
|
{@const ItemIcon = item.icon}
|
|
43
43
|
<ItemIcon class="size-4" />
|
|
44
44
|
{/if}
|
|
45
|
-
<span
|
|
45
|
+
<span>{item.label}</span>
|
|
46
46
|
</span>
|
|
47
47
|
{/if}
|
|
48
48
|
</li>
|
|
49
49
|
{/each}
|
|
50
50
|
</ul>
|
|
51
51
|
</div>
|
|
52
|
+
|
|
53
|
+
<style>
|
|
54
|
+
/*
|
|
55
|
+
* DaisyUI breadcrumbs use @layer rules that Tailwind v4 doesn't propagate
|
|
56
|
+
* from node_modules. These unlayered rules override that gap.
|
|
57
|
+
*/
|
|
58
|
+
:global(.breadcrumbs > ul),
|
|
59
|
+
:global(.breadcrumbs > ol),
|
|
60
|
+
:global(.breadcrumbs > menu) {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
white-space: nowrap;
|
|
64
|
+
overflow-x: auto;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:global(.breadcrumbs > ul > li),
|
|
68
|
+
:global(.breadcrumbs > ol > li),
|
|
69
|
+
:global(.breadcrumbs > menu > li) {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
:global(.breadcrumbs > ul > li + li::before),
|
|
75
|
+
:global(.breadcrumbs > ol > li + li::before),
|
|
76
|
+
:global(.breadcrumbs > menu > li + li::before) {
|
|
77
|
+
content: '';
|
|
78
|
+
display: block;
|
|
79
|
+
opacity: 0.4;
|
|
80
|
+
border-top: 1px solid;
|
|
81
|
+
border-right: 1px solid;
|
|
82
|
+
width: 0.375rem;
|
|
83
|
+
height: 0.375rem;
|
|
84
|
+
margin-inline: 0.5rem 0.75rem;
|
|
85
|
+
rotate: 45deg;
|
|
86
|
+
}
|
|
87
|
+
</style>
|
|
@@ -24,6 +24,41 @@
|
|
|
24
24
|
function next() {
|
|
25
25
|
if (page < totalPages) page++;
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
function buildPages(current: number, total: number): number[] {
|
|
29
|
+
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
|
|
30
|
+
|
|
31
|
+
const visible = new Set<number>();
|
|
32
|
+
visible.add(1);
|
|
33
|
+
visible.add(total);
|
|
34
|
+
for (let i = Math.max(1, current - 2); i <= Math.min(total, current + 2); i++) {
|
|
35
|
+
visible.add(i);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const sorted = Array.from(visible).sort((a, b) => a - b);
|
|
39
|
+
const result: number[] = [];
|
|
40
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
41
|
+
if (i > 0 && sorted[i] - sorted[i - 1] > 1) result.push(0);
|
|
42
|
+
result.push(sorted[i]);
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const pages = $derived(buildPages(page, totalPages));
|
|
48
|
+
|
|
49
|
+
function handlePageInput(e: KeyboardEvent) {
|
|
50
|
+
if (e.key !== 'Enter') return;
|
|
51
|
+
const val = parseInt((e.currentTarget as HTMLInputElement).value);
|
|
52
|
+
if (!isNaN(val) && val >= 1 && val <= totalPages) {
|
|
53
|
+
page = val;
|
|
54
|
+
} else {
|
|
55
|
+
(e.currentTarget as HTMLInputElement).value = String(page);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resetInput(e: FocusEvent) {
|
|
60
|
+
(e.currentTarget as HTMLInputElement).value = String(page);
|
|
61
|
+
}
|
|
27
62
|
</script>
|
|
28
63
|
|
|
29
64
|
{#if totalPages > 1}
|
|
@@ -33,30 +68,27 @@
|
|
|
33
68
|
</span>
|
|
34
69
|
|
|
35
70
|
<div class="join">
|
|
36
|
-
<Button
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
71
|
+
<Button class="join-item btn-sm" disabled={page === 1} onclick={prev}>«</Button>
|
|
72
|
+
|
|
73
|
+
{#each pages as p, i (i)}
|
|
74
|
+
{#if p === 0}
|
|
75
|
+
<Button class="join-item btn-sm" disabled>…</Button>
|
|
76
|
+
{:else if p === page}
|
|
77
|
+
<input
|
|
78
|
+
type="number"
|
|
79
|
+
class="join-item btn btn-sm btn-active w-16 text-center appearance-none"
|
|
80
|
+
min="1"
|
|
81
|
+
max={totalPages}
|
|
82
|
+
value={page}
|
|
83
|
+
onkeydown={handlePageInput}
|
|
84
|
+
onblur={resetInput}
|
|
85
|
+
/>
|
|
86
|
+
{:else}
|
|
87
|
+
<Button class="join-item btn-sm" onclick={() => (page = p)}>{p}</Button>
|
|
88
|
+
{/if}
|
|
51
89
|
{/each}
|
|
52
90
|
|
|
53
|
-
<Button
|
|
54
|
-
class="join-item btn-sm"
|
|
55
|
-
disabled={page === totalPages}
|
|
56
|
-
onclick={next}
|
|
57
|
-
>
|
|
58
|
-
»
|
|
59
|
-
</Button>
|
|
91
|
+
<Button class="join-item btn-sm" disabled={page === totalPages} onclick={next}>»</Button>
|
|
60
92
|
</div>
|
|
61
93
|
</div>
|
|
62
94
|
{/if}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* import { bootstrapIconSet } from 'runeforge/icons/sets/bootstrap';
|
|
10
10
|
* setIconSet(bootstrapIconSet);
|
|
11
11
|
*/
|
|
12
|
-
import
|
|
12
|
+
import * as Icons from 'svelte-bootstrap-icons';
|
|
13
|
+
const { ChevronExpand, CaretUpFill, CaretDownFill, Funnel, FunnelFill, Plus, Eye, PencilSquare, Trash3, HouseDoor, Folder, EyeSlash, } = Icons;
|
|
13
14
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
15
|
function asIcon(c) { return c; }
|
|
15
16
|
export const bootstrapIconSet = {
|
|
@@ -26,4 +27,6 @@ export const bootstrapIconSet = {
|
|
|
26
27
|
folder: asIcon(Folder),
|
|
27
28
|
passwordShow: asIcon(Eye),
|
|
28
29
|
passwordHide: asIcon(EyeSlash),
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
getByName: (name) => asIcon(Icons[name]) ?? null,
|
|
29
32
|
};
|
package/dist/icons/types.d.ts
CHANGED
|
@@ -3,11 +3,6 @@ export type IconComponent = Component<{
|
|
|
3
3
|
size?: string | number;
|
|
4
4
|
class?: string;
|
|
5
5
|
}>;
|
|
6
|
-
export type IconByNameComponent = Component<{
|
|
7
|
-
name: string;
|
|
8
|
-
size?: string | number;
|
|
9
|
-
class?: string;
|
|
10
|
-
}>;
|
|
11
6
|
export interface CRUDIconSet {
|
|
12
7
|
sortNone: IconComponent;
|
|
13
8
|
sortAsc: IconComponent;
|
|
@@ -22,5 +17,5 @@ export interface CRUDIconSet {
|
|
|
22
17
|
folder: IconComponent;
|
|
23
18
|
passwordShow: IconComponent;
|
|
24
19
|
passwordHide: IconComponent;
|
|
25
|
-
|
|
20
|
+
getByName?: (name: string) => IconComponent | null;
|
|
26
21
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { AttributeType } from './types/attribute.js';
|
|
|
3
3
|
export type { AttributeMetadata, InterfaceMetadata, SelectOption, OptionsResolver, FormatterResolver } from './types/attribute.js';
|
|
4
4
|
export type { CellProps, CellComponent, CellFormatter, SortDirection, IndexedRow, DistinctEntry } from './types/table.js';
|
|
5
5
|
export type { ColumnDefinition, FieldDefinition, ActionConfiguration, CustomAction, RowAction } from './types/crud.js';
|
|
6
|
-
export type { CRUDIconSet, IconComponent
|
|
6
|
+
export type { CRUDIconSet, IconComponent } from './icons/types.js';
|
|
7
7
|
export { setIconSet, getIconSet } from './icons/context.js';
|
|
8
8
|
export { defaultIconSet } from './icons/sets/default.js';
|
|
9
9
|
export { bootstrapIconSet } from './icons/sets/bootstrap.js';
|
package/package.json
CHANGED
|
@@ -1,79 +1,81 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
2
|
+
"name": "runeforge",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "SvelteKit toolkit for building metadata-driven CRUD interfaces with tables, forms, and actions",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Ezequiel Puerta",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"svelte",
|
|
9
|
+
"sveltekit",
|
|
10
|
+
"crud",
|
|
11
|
+
"table",
|
|
12
|
+
"form",
|
|
13
|
+
"daisyui",
|
|
14
|
+
"tailwindcss",
|
|
15
|
+
"ui"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "vite dev",
|
|
19
|
+
"build": "vite build && npm run prepack",
|
|
20
|
+
"preview": "vite preview",
|
|
21
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
22
|
+
"prepack": "svelte-kit sync && svelte-package && publint",
|
|
23
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
24
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
25
|
+
"test": "pnpm run test:unit && pnpm run test:e2e",
|
|
26
|
+
"test:unit": "vitest run",
|
|
27
|
+
"test:unit:watch": "vitest",
|
|
28
|
+
"test:e2e": "playwright test",
|
|
29
|
+
"lint": "prettier --check . && eslint .",
|
|
30
|
+
"format": "prettier --write ."
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"!dist/**/*.test.*",
|
|
35
|
+
"!dist/**/*.spec.*"
|
|
36
|
+
],
|
|
37
|
+
"sideEffects": [
|
|
38
|
+
"**/*.css"
|
|
39
|
+
],
|
|
40
|
+
"svelte": "./dist/index.js",
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"type": "module",
|
|
43
|
+
"exports": {
|
|
44
|
+
".": {
|
|
45
|
+
"types": "./dist/index.d.ts",
|
|
46
|
+
"svelte": "./dist/index.js"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@sveltejs/kit": "^2.0.0",
|
|
51
|
+
"daisyui": "^5.0.0",
|
|
52
|
+
"svelte": "^5.0.0",
|
|
53
|
+
"tailwindcss": "^4.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@eslint/js": "^10.0.1",
|
|
57
|
+
"@playwright/test": "^1.60.0",
|
|
58
|
+
"@sveltejs/adapter-auto": "^7.0.1",
|
|
59
|
+
"@sveltejs/kit": "^2.63.0",
|
|
60
|
+
"@sveltejs/package": "^2.5.8",
|
|
61
|
+
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
62
|
+
"@tailwindcss/vite": "^4.3.1",
|
|
63
|
+
"@types/node": "^22",
|
|
64
|
+
"daisyui": "^5.5.23",
|
|
65
|
+
"eslint": "^10.4.1",
|
|
66
|
+
"eslint-config-prettier": "^10.1.8",
|
|
67
|
+
"eslint-plugin-svelte": "^3.19.0",
|
|
68
|
+
"globals": "^17.6.0",
|
|
69
|
+
"prettier": "^3.8.3",
|
|
70
|
+
"prettier-plugin-svelte": "^4.1.0",
|
|
71
|
+
"publint": "^0.3.21",
|
|
72
|
+
"svelte": "^5.56.1",
|
|
73
|
+
"svelte-bootstrap-icons": "^3.3.0",
|
|
74
|
+
"svelte-check": "^4.6.0",
|
|
75
|
+
"tailwindcss": "^4.3.1",
|
|
76
|
+
"typescript": "^6.0.3",
|
|
77
|
+
"typescript-eslint": "^8.60.1",
|
|
78
|
+
"vite": "^8.0.16",
|
|
79
|
+
"vitest": "^4.1.8"
|
|
80
|
+
}
|
|
81
|
+
}
|