@axium/client 0.13.3 → 0.14.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/assets/styles.css CHANGED
@@ -221,6 +221,20 @@ input.error {
221
221
  gap: 0.1em;
222
222
  }
223
223
 
224
+ [popover] .menu-item {
225
+ display: inline-flex;
226
+ align-items: center;
227
+ padding: 0.5em 0.75em;
228
+ gap: 1em;
229
+ border-radius: 0.5em;
230
+ user-select: none;
231
+
232
+ &:hover {
233
+ background-color: var(--bg-strong);
234
+ cursor: pointer;
235
+ }
236
+ }
237
+
224
238
  .icon-text {
225
239
  display: inline-flex;
226
240
  align-items: center;
@@ -65,13 +65,12 @@
65
65
  <Icon i="at" />
66
66
  <span>{control.role}</span>
67
67
  </span>
68
- <!-- {:else if control.tag} -->
69
68
  {:else}
70
69
  <i>Unknown</i>
71
70
  {/if}
72
71
  <div class="permissions">
73
72
  {#each Object.entries(pickPermissions(control) as Record<string, boolean>) as [key, value]}
74
- {@const id = `${getTarget(control)}.${key}`}
73
+ {@const id = `${item.id}.${getTarget(control)}.${key}`}
75
74
  <span class="icon-text">
76
75
  {#if editable}
77
76
  <input {id} type="checkbox" onchange={update(key)} />
@@ -11,8 +11,6 @@
11
11
  }
12
12
  </script>
13
13
 
14
- <!-- svelte-ignore a11y_click_events_have_key_events -->
15
- <!-- svelte-ignore a11y_no_static_element_interactions -->
16
14
  <div {onclick}>
17
15
  {#if toggle}
18
16
  {@render toggle()}
@@ -55,17 +53,4 @@
55
53
  left: anchor(left);
56
54
  top: anchor(bottom);
57
55
  }
58
-
59
- [popover] :global(.menu-item) {
60
- display: inline-flex;
61
- align-items: center;
62
- padding: 0.5em 0.75em;
63
- gap: 1em;
64
- border-radius: 0.5em;
65
-
66
- &:hover {
67
- background-color: var(--bg-strong);
68
- cursor: pointer;
69
- }
70
- }
71
56
  </style>
package/lib/Upload.svelte CHANGED
@@ -5,7 +5,6 @@
5
5
 
6
6
  let {
7
7
  name = 'files',
8
- input = $bindable(),
9
8
  files = $bindable(),
10
9
  progress = $bindable([]),
11
10
  ...rest
@@ -27,7 +26,7 @@
27
26
  e.preventDefault();
28
27
  const dt = new DataTransfer();
29
28
  for (let f of files!) if (file !== f) dt.items.add(f);
30
- input!.files = files = dt.files;
29
+ files = dt.files;
31
30
  }}
32
31
  style:display="contents"
33
32
  >
@@ -50,7 +49,7 @@
50
49
  {/each}
51
50
  </label>
52
51
 
53
- <input bind:this={input} bind:files {name} {id} type="file" {...rest} />
52
+ <input bind:files {name} {id} type="file" {...rest} />
54
53
  </div>
55
54
 
56
55
  <style>
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Svelte attachments
3
+ * @see https://svelte.dev/docs/svelte/@attach
4
+ */
5
+ import { mount, unmount } from 'svelte';
6
+ import Icon from './Icon.svelte';
7
+ import type { Attachment } from 'svelte/attachments';
8
+
9
+ export interface ContextMenuItem {
10
+ /** Icon name */
11
+ i?: string;
12
+ text: string;
13
+ danger?: boolean;
14
+ action(): unknown;
15
+ }
16
+
17
+ /**
18
+ * Attach a context menu to an element with the given actions
19
+ */
20
+ export function contextMenu(...menuItems: ContextMenuItem[]) {
21
+ function _attachContextMenu(element: HTMLElement) {
22
+ const menu = document.createElement('div');
23
+ menu.popover = 'auto';
24
+ menu.className = 'context-menu';
25
+
26
+ const mountedIcons = new Set<Record<string, any>>();
27
+
28
+ for (const item of menuItems) {
29
+ const div = document.createElement('div');
30
+ div.classList.add('icon-text', 'menu-item');
31
+ if (item.danger) div.classList.add('danger');
32
+
33
+ div.onclick = e => {
34
+ e.stopPropagation();
35
+ menu.hidePopover();
36
+ item.action();
37
+ };
38
+
39
+ if (item.i) mountedIcons.add(mount<any, {}>(Icon, { target: div, props: { i: item.i } }));
40
+
41
+ div.appendChild(document.createTextNode(item.text));
42
+
43
+ menu.appendChild(div);
44
+ }
45
+
46
+ document.body.appendChild(menu);
47
+
48
+ let _forcePopover = false;
49
+
50
+ element.oncontextmenu = (e: MouseEvent) => {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+
54
+ menu.showPopover();
55
+ _forcePopover = true;
56
+
57
+ const x = e.clientX;
58
+ const y = e.clientY;
59
+
60
+ menu.style.left = x + 'px';
61
+ menu.style.top = y + 'px';
62
+
63
+ const rect = menu.getBoundingClientRect();
64
+ if (rect.right > window.innerWidth) {
65
+ menu.style.left = '';
66
+ menu.style.right = window.innerWidth - x + 'px';
67
+ }
68
+ if (rect.bottom > window.innerHeight) {
69
+ menu.style.top = '';
70
+ menu.style.bottom = window.innerHeight - y + 'px';
71
+ }
72
+ };
73
+
74
+ /**
75
+ * Workaround for https://github.com/whatwg/html/issues/10905
76
+ * @todo Remove when the problem is fixed.
77
+ */
78
+ element.onpointerup = (e: PointerEvent) => {
79
+ if (!_forcePopover) return;
80
+ e.stopPropagation();
81
+ e.preventDefault();
82
+ menu.togglePopover();
83
+ _forcePopover = false;
84
+ };
85
+
86
+ return function _disposeContextMenu() {
87
+ for (const icon of mountedIcons) unmount(icon);
88
+ menu.remove();
89
+ };
90
+ }
91
+ return _attachContextMenu satisfies Attachment<HTMLElement>;
92
+ }
package/lib/index.ts CHANGED
@@ -16,6 +16,5 @@ export { default as URLText } from './URLText.svelte';
16
16
  export { default as UserCard } from './UserCard.svelte';
17
17
  export { default as UserMenu } from './UserMenu.svelte';
18
18
  export { default as Version } from './Version.svelte';
19
- export { default as WithContextMenu } from './WithContextMenu.svelte';
20
19
  export { default as ZodForm } from './ZodForm.svelte';
21
20
  export { default as ZodInput } from './ZodInput.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/client",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
4
4
  "author": "James Prevett <jp@jamespre.dev>",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -34,6 +34,7 @@
34
34
  "./*": "./dist/*.js",
35
35
  "./components": "./lib/index.js",
36
36
  "./components/*": "./lib/*.svelte",
37
+ "./attachments": "./lib/attachments.js",
37
38
  "./styles/*": "./styles/*.css"
38
39
  },
39
40
  "scripts": {
@@ -1,73 +0,0 @@
1
- <script lang="ts">
2
- import type { HTMLAttributes } from 'svelte/elements';
3
- import Icon from './Icon.svelte';
4
-
5
- interface Props extends HTMLAttributes<HTMLDivElement> {
6
- children(): any;
7
- menu(
8
- /**
9
- * Shortcut to quickly create a generic action in the context menu.
10
- */
11
- action: (icon: string, text: string, action: (event: MouseEvent) => void) => any
12
- ): any;
13
- actions: Record<string, () => void>;
14
- }
15
-
16
- let { children, menu, actions, ...rest }: Props = $props();
17
-
18
- let popover = $state<HTMLDivElement>();
19
-
20
- function oncontextmenu(e: MouseEvent) {
21
- e.preventDefault();
22
- e.stopPropagation();
23
- popover!.togglePopover();
24
- _forcePopover = true;
25
- }
26
-
27
- let _forcePopover = false;
28
-
29
- /**
30
- * Workaround for https://github.com/whatwg/html/issues/10905
31
- * @todo Remove when the problem is fixed.
32
- */
33
- function onpointerup(e: PointerEvent) {
34
- if (!_forcePopover) return;
35
- e.stopPropagation();
36
- e.preventDefault();
37
- popover!.togglePopover();
38
- _forcePopover = false;
39
- }
40
- </script>
41
-
42
- {#snippet action(i: string, text: string, action: (event: MouseEvent) => void)}
43
- <div
44
- onclick={e => {
45
- e.stopPropagation();
46
- e.preventDefault();
47
- action(e);
48
- }}
49
- class="action"
50
- >
51
- {#if i}<Icon {i} --size="14px" />{/if}
52
- {text}
53
- </div>
54
- {/snippet}
55
-
56
- <div data-axium-context-menu {oncontextmenu} {onpointerup} {...rest}>
57
- {@render children()}
58
- <div popover bind:this={popover}>
59
- {@render menu(action)}
60
- </div>
61
- </div>
62
-
63
- <style>
64
- [data-axium-context-menu] {
65
- display: contents;
66
- }
67
-
68
- div.action:hover {
69
- cursor: pointer;
70
- background-color: var(--bg-strong);
71
- border-radius: 0.25em;
72
- }
73
- </style>