@humandialog/forms.svelte 0.5.10 → 0.5.12

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.
@@ -12,7 +12,10 @@ $:
12
12
  </script>
13
13
 
14
14
  <!-- svelte-ignore a11y-click-events-have-key-events -->
15
- <div id={id} class="font-medium m-0 p-0 text-sm w-full text-left flex flew-row cursor-context-menu {active_class} {cl}" on:click on:mousemove>
15
+ <div id={id} class="font-medium m-0 p-0 text-sm w-full text-left flex flew-row cursor-context-menu {active_class} {cl}"
16
+ on:click
17
+ on:mousemove
18
+ on:mousedown>
16
19
  <div class="flex items-center justify-center" style:width={`${icon_placeholder_size*0.25}rem`}>
17
20
  {#if cmd.icon}
18
21
  <Icon size={icon_size} component={cmd.icon}/>
@@ -11,6 +11,7 @@ declare const __propDef: {
11
11
  events: {
12
12
  click: MouseEvent;
13
13
  mousemove: MouseEvent;
14
+ mousedown: MouseEvent;
14
15
  } & {
15
16
  [evt: string]: CustomEvent<any>;
16
17
  };
@@ -2,6 +2,8 @@
2
2
  <script>import { tick } from "svelte";
3
3
  import Pallete_row from "./palette.row.svelte";
4
4
  import { createEventDispatcher } from "svelte";
5
+ import Icon from "../../icon.svelte";
6
+ import { isDeviceSmallerThan } from "../../../utils.js";
5
7
  export let commands;
6
8
  export let width_px = 400;
7
9
  export let max_height_px = 500;
@@ -9,8 +11,22 @@ let visible = false;
9
11
  let css_style = "";
10
12
  let filtered_commands = [...commands];
11
13
  let current_command = filtered_commands.length ? filtered_commands[0] : null;
14
+ let isToolbox = false;
12
15
  const dispatch = createEventDispatcher();
16
+ let toolboxX;
17
+ let toolboxY;
18
+ export function showAsToolbox(rect) {
19
+ isToolbox = true;
20
+ visible = true;
21
+ const margin = 15;
22
+ const windowWidth = window.innerWidth - 2 * margin;
23
+ toolboxX = margin;
24
+ toolboxY = rect.bottom + margin;
25
+ css_style = `position: fixed; left:${toolboxX}px; top:${toolboxY}px;`;
26
+ dispatch("palette_shown");
27
+ }
13
28
  export function show(x, y, up = false) {
29
+ isToolbox = false;
14
30
  css_style = `width: ${width_px}px; max-height:${max_height_px}px; position: fixed; left:${x}px; top:${y}px;`;
15
31
  if (up)
16
32
  css_style += " transform: translate(0, -100%);";
@@ -18,6 +34,7 @@ export function show(x, y, up = false) {
18
34
  dispatch("palette_shown");
19
35
  }
20
36
  export function show_fullscreen(_width_px, _height_px) {
37
+ isToolbox = false;
21
38
  width_px = _width_px;
22
39
  max_height_px = _height_px;
23
40
  css_style = `position: fixed; left: 0px; top: 0px; width: ${_width_px}px; height: ${_height_px}px; z-index: 1055;`;
@@ -26,6 +43,7 @@ export function show_fullscreen(_width_px, _height_px) {
26
43
  }
27
44
  export function hide() {
28
45
  visible = false;
46
+ isToolbox = false;
29
47
  dispatch("palette_hidden");
30
48
  }
31
49
  export function execute_selected_command() {
@@ -84,7 +102,6 @@ async function execute_mouse_click(on_choice) {
84
102
  if (!visible)
85
103
  return;
86
104
  dispatch("palette_mouse_click");
87
- await tick();
88
105
  hide();
89
106
  on_choice();
90
107
  }
@@ -100,24 +117,87 @@ function update_current_command(cmd) {
100
117
  rows.find((r) => r.cmd == cmd).is_highlighted = true;
101
118
  current_command = cmd;
102
119
  }
120
+ function buttonMousedown(e) {
121
+ e.preventDefault();
122
+ }
123
+ let isMoving = false;
124
+ let beforeTrackingClient = null;
125
+ let beforeTrackingPos = null;
126
+ function mousedown(e) {
127
+ if (e.touches.length != 1)
128
+ return;
129
+ const touch = e.touches.item(0);
130
+ beforeTrackingClient = new DOMPoint(touch.clientX, touch.clientY);
131
+ beforeTrackingPos = new DOMPoint(toolboxX, toolboxY);
132
+ isMoving = true;
133
+ }
134
+ function mousemove(e) {
135
+ if (isMoving && beforeTrackingClient && beforeTrackingPos) {
136
+ if (e.touches.length != 1)
137
+ return;
138
+ const touch = e.touches.item(0);
139
+ const trackDelta = new DOMPoint(touch.clientX - beforeTrackingClient.x, touch.clientY - beforeTrackingClient.y);
140
+ toolboxX = beforeTrackingPos.x + trackDelta.x;
141
+ css_style = `position: fixed; left:${toolboxX}px; top:${toolboxY}px;`;
142
+ }
143
+ }
144
+ function mouseup(e) {
145
+ isMoving = false;
146
+ beforeTrackingClient = null;
147
+ beforeTrackingPos = null;
148
+ }
103
149
  </script>
104
150
 
105
- <div class="not-prose bg-white dark:bg-stone-800 text-stone-500 dark:text-stone-400 rounded-lg border border-stone-200 dark:border-stone-700 shadow-md z-30"
106
- hidden={!visible}
107
- style={css_style}>
108
- {#if filtered_commands && filtered_commands.length}
109
- {#each filtered_commands as cmd, idx (cmd.caption)}
110
- {@const id = "cpi_" + idx}
111
- <Pallete_row {id}
112
- cmd={cmd}
113
- is_highlighted={cmd == current_command}
114
- on:click={ () => { execute_mouse_click(cmd.on_choice); }}
115
- on:mousemove={ () => { on_mouse_over(cmd); }}
116
- bind:this={rows[idx]}
117
- />
118
- {/each}
119
- {:else}
120
- <p class="text-sm text-stone-500">No results</p>
121
- {/if}
151
+ {#if isToolbox}
152
+ <menu class=" not-prose bg-white dark:bg-stone-800 text-stone-500 dark:text-stone-400 rounded-lg border border-stone-200 dark:border-stone-700 shadow-md
153
+ z-30
154
+ flex flex-row flex-nowrap"
155
+ style={css_style}
156
+ hidden={!visible}
157
+ on:touchstart={mousedown}
158
+ on:touchmove={mousemove}
159
+ on:touchend={mouseup}>
160
+ {#if filtered_commands && filtered_commands.length}
161
+ {#each filtered_commands as cmd, idx (cmd.caption)}
162
+ {@const id = "cpi_" + idx}
163
+ {@const mobile = isDeviceSmallerThan("sm")}
164
+ {@const icon_placeholder_size = mobile ? 12 : 10}
165
+ <button class="font-medium m-0 py-2 pr-4 text-lg sm:text-sm w-full text-left flex flex-row cursor-context-menu focus:outline-none"
166
+ {id}
167
+ bind:this={rows[idx]}
168
+ on:click={ () => { execute_mouse_click(cmd.on_choice); }}
169
+ on:mousedown={buttonMousedown}>
170
+
171
+ <div class="flex items-center justify-center mt-1 sm:mt-0.5" style:width={`${icon_placeholder_size*0.25}rem`}>
172
+ {#if cmd.icon}
173
+ {@const cc = mobile ? 7 : 6}
174
+ {@const icon_size = icon_placeholder_size - cc}
175
+ <Icon size={icon_size} component={cmd.icon}/>
176
+ {/if}
177
+ </div>
178
+ </button>
179
+ {/each}
180
+ {/if}
181
+ </menu>
182
+ {:else}
183
+ <div class="not-prose bg-white dark:bg-stone-800 text-stone-500 dark:text-stone-400 rounded-lg border border-stone-200 dark:border-stone-700 shadow-md z-30"
184
+ hidden={!visible}
185
+ style={css_style}>
186
+ {#if filtered_commands && filtered_commands.length}
187
+ {#each filtered_commands as cmd, idx (cmd.caption)}
188
+ {@const id = "cpi_" + idx}
189
+ <Pallete_row {id}
190
+ cmd={cmd}
191
+ is_highlighted={cmd == current_command}
192
+ on:click={ () => { execute_mouse_click(cmd.on_choice); }}
193
+ on:mousemove={ () => { on_mouse_over(cmd); }}
194
+ on:mousedown={buttonMousedown}
195
+ bind:this={rows[idx]}
196
+ />
197
+ {/each}
198
+ {:else}
199
+ <p class="text-sm text-stone-500">No results</p>
200
+ {/if}
122
201
 
123
- </div>
202
+ </div>
203
+ {/if}
@@ -5,6 +5,7 @@ declare const __propDef: {
5
5
  commands: Document_command[];
6
6
  width_px?: number | undefined;
7
7
  max_height_px?: number | undefined;
8
+ showAsToolbox?: ((rect: DOMRect) => void) | undefined;
8
9
  show?: ((x: number, y: number, up?: boolean) => void) | undefined;
9
10
  show_fullscreen?: ((_width_px: number, _height_px: number) => void) | undefined;
10
11
  hide?: (() => void) | undefined;
@@ -20,12 +21,13 @@ declare const __propDef: {
20
21
  } & {
21
22
  [evt: string]: CustomEvent<any>;
22
23
  };
23
- slots: {}; /**accessor*/
24
+ slots: {};
24
25
  };
25
26
  export type PaletteProps = typeof __propDef.props;
26
27
  export type PaletteEvents = typeof __propDef.events;
27
28
  export type PaletteSlots = typeof __propDef.slots;
28
29
  export default class Palette extends SvelteComponentTyped<PaletteProps, PaletteEvents, PaletteSlots> {
30
+ get showAsToolbox(): (rect: DOMRect) => void;
29
31
  get show(): (x: number, y: number, up?: boolean) => void;
30
32
  get show_fullscreen(): (_width_px: number, _height_px: number) => void;
31
33
  get hide(): () => void;
@@ -63,5 +65,8 @@ export default class Palette extends SvelteComponentTyped<PaletteProps, PaletteE
63
65
  get undefined(): any;
64
66
  /**accessor*/
65
67
  set undefined(_: any);
68
+ get undefined(): any;
69
+ /**accessor*/
70
+ set undefined(_: any);
66
71
  }
67
72
  export {};
@@ -1,9 +1,9 @@
1
1
  <script>import { Selection_helper } from "./internal/Selection_helper";
2
- import { getContext, onDestroy, onMount } from "svelte";
2
+ import { afterUpdate, getContext, onDestroy, onMount } from "svelte";
3
3
  import { Selection_range, Selection_edge } from "./internal/Selection_range";
4
4
  import { data_tick_store, contextItemsStore, contextTypesStore } from "../../stores.js";
5
5
  import { informModification, pushChanges } from "../../updates.js";
6
- import { parseWidthDirective } from "../../utils.js";
6
+ import { isDeviceSmallerThan, parseWidthDirective } from "../../utils.js";
7
7
  import Palette from "./internal/palette.svelte";
8
8
  import FaFont from "svelte-icons/fa/FaFont.svelte";
9
9
  import FaRemoveFormat from "svelte-icons/fa/FaRemoveFormat.svelte";
@@ -13,6 +13,7 @@ import FaComments from "svelte-icons/fa/FaComment.svelte";
13
13
  import FaQuote from "svelte-icons/fa/FaQuoteRight.svelte";
14
14
  import FaWarn from "svelte-icons/fa/FaExclamationTriangle.svelte";
15
15
  import FaInfo from "svelte-icons/fa/FaInfo.svelte";
16
+ import { showMenu } from "../menu";
16
17
  export let value = "";
17
18
  export let placeholder = "";
18
19
  export let self = null;
@@ -62,8 +63,12 @@ else
62
63
  px-2.5`;
63
64
  let last_tick = -1;
64
65
  $: {
65
- if (last_tick < $data_tick_store)
66
- setup_source();
66
+ if (last_tick < $data_tick_store) {
67
+ if (has_changed_value)
68
+ saveData();
69
+ else
70
+ setup_source();
71
+ }
67
72
  }
68
73
  function setup_source() {
69
74
  last_tick = $data_tick_store;
@@ -80,6 +85,8 @@ function setup_source() {
80
85
  if (!value)
81
86
  value = "<p>\u200B</p>";
82
87
  has_changed_value = false;
88
+ if (stored_selection)
89
+ set_selection(stored_selection);
83
90
  }
84
91
  onMount(() => {
85
92
  if (!editable_div)
@@ -95,6 +102,9 @@ onMount(() => {
95
102
  subtree: true
96
103
  });
97
104
  }
105
+ return () => {
106
+ on_blur();
107
+ };
98
108
  });
99
109
  onDestroy(() => {
100
110
  if (!editable_div)
@@ -166,9 +176,12 @@ const on_keydown = (event) => {
166
176
  } else {
167
177
  event.cancelBubble = true;
168
178
  event.preventDefault();
169
- if (is_multiline())
170
- insert_new_line();
171
- else
179
+ if (is_multiline()) {
180
+ if (event.shiftKey)
181
+ insert_character_at_caret_position("\n");
182
+ else
183
+ insert_new_line();
184
+ } else
172
185
  move_cursor_to_next_editable_element();
173
186
  }
174
187
  break;
@@ -176,6 +189,11 @@ const on_keydown = (event) => {
176
189
  store_node_text_and_position();
177
190
  show_command_palette();
178
191
  break;
192
+ case "Tab":
193
+ event.cancelBubble = true;
194
+ event.preventDefault();
195
+ insert_character_at_caret_position(" ");
196
+ break;
179
197
  case "Backspace":
180
198
  if (is_range_selected()) {
181
199
  if (is_multi_range_selection()) {
@@ -715,7 +733,7 @@ function set_tag_and_class_for_paragraph(node, tag, css_class) {
715
733
  while (node && node.nodeType !== Node.ELEMENT_NODE)
716
734
  node = node.parentNode;
717
735
  if (!node)
718
- return;
736
+ return false;
719
737
  let element = node;
720
738
  if (element.tagName == tag.toUpperCase()) {
721
739
  element.className = css_class;
@@ -737,7 +755,7 @@ function set_tag_and_class_for_paragraph(node, tag, css_class) {
737
755
  function do_format(tag, css_class) {
738
756
  const elem = editable_div;
739
757
  const editableElem = editable_div;
740
- let stored_selection = Selection_helper.get_selection(elem);
758
+ let stored_selection2 = Selection_helper.get_selection(elem);
741
759
  let sel = window.getSelection();
742
760
  let should_restore_selection = false;
743
761
  if (sel.isCollapsed || sel.focusNode === sel.anchorNode) {
@@ -766,7 +784,28 @@ function do_format(tag, css_class) {
766
784
  } while (node);
767
785
  }
768
786
  if (should_restore_selection) {
769
- const range = Selection_helper.create_range(editableElem, stored_selection.begin.absolute_index, stored_selection.end.absolute_index);
787
+ const range = Selection_helper.create_range(editableElem, stored_selection2.begin.absolute_index, stored_selection2.end.absolute_index);
788
+ set_selection(range);
789
+ }
790
+ }
791
+ function insert_character_at_caret_position(what_to_insert) {
792
+ if (is_range_selected()) {
793
+ console.log("Unsupported");
794
+ return false;
795
+ } else {
796
+ let sel;
797
+ sel = window.getSelection();
798
+ const focusNode = sel.focusNode;
799
+ const wholeText = focusNode.textContent;
800
+ const carretPos = sel.focusOffset;
801
+ let leftPart = wholeText.substring(0, carretPos);
802
+ let rightPart = wholeText.substring(carretPos);
803
+ focusNode.textContent = leftPart + what_to_insert + rightPart;
804
+ let pos = carretPos + 1;
805
+ let range = new Range();
806
+ range.collapse(true);
807
+ range.setStart(focusNode, pos);
808
+ range.setEnd(focusNode, pos);
770
809
  set_selection(range);
771
810
  }
772
811
  }
@@ -879,7 +918,10 @@ function show_command_palette() {
879
918
  y = rect.y - (preferred_palette_height - bottom_space);
880
919
  } else
881
920
  show_fullscreen = true;
882
- if (show_fullscreen)
921
+ const isSmallDevice = isDeviceSmallerThan("sm");
922
+ if (isSmallDevice) {
923
+ palette.showAsToolbox(rect);
924
+ } else if (show_fullscreen)
883
925
  palette.show_fullscreen(client_rect.width, client_rect.height);
884
926
  else
885
927
  palette.show(x, y, show_above);
@@ -899,25 +941,18 @@ function navigate_command_palette(key) {
899
941
  else if (key == "ArrowUp")
900
942
  palette.navigate_up();
901
943
  }
944
+ let stored_selection = void 0;
902
945
  function on_selection_changed() {
946
+ let active_range = Selection_helper.get_selection(editable_div);
947
+ stored_selection = window.getSelection()?.getRangeAt(0);
903
948
  }
904
949
  let intervalId = 0;
905
950
  function on_focus() {
906
- if (pushChangesImmediately) {
907
- intervalId = setInterval(
908
- () => {
909
- saveData();
910
- },
911
- 2e3
912
- );
913
- }
914
951
  }
915
952
  function on_blur() {
916
953
  let active_range = Selection_helper.get_selection(editable_div);
917
- if (intervalId) {
918
- clearInterval(intervalId);
919
- intervalId = 0;
920
- }
954
+ console.log("rich.edit: on_blur", active_range?.begin?.absolute_index);
955
+ stored_selection = void 0;
921
956
  if (onBlur) {
922
957
  onBlur();
923
958
  onBlur = void 0;
@@ -927,6 +962,12 @@ function on_blur() {
927
962
  $data_tick_store = last_tick;
928
963
  }
929
964
  }
965
+ export function save() {
966
+ if (saveData()) {
967
+ last_tick = $data_tick_store + 1;
968
+ $data_tick_store = last_tick;
969
+ }
970
+ }
930
971
  function saveData() {
931
972
  if (item && a && has_changed_value) {
932
973
  item[a] = changed_value;
@@ -977,7 +1018,7 @@ let commands = [
977
1018
  on:keydown={on_keydown}
978
1019
  on:mouseup={on_mouseup}
979
1020
  class="{cs} {appearance_class}
980
- prose prose-base sm:prose-base dark:prose-invert {additional_class} overflow-y-auto"
1021
+ prose prose-base sm:prose-base dark:prose-invert {additional_class} overflow-y-auto whitespace-pre-wrap"
981
1022
  on:blur={on_blur}
982
1023
  on:focus={on_focus}
983
1024
  on:focus
@@ -13,6 +13,7 @@ declare const __propDef: {
13
13
  pushChangesImmediately?: boolean | undefined;
14
14
  run?: ((onStop?: undefined) => void) | undefined;
15
15
  getFormattingOperations?: ((withCaptions?: boolean) => any[]) | undefined;
16
+ save?: (() => void) | undefined;
16
17
  };
17
18
  events: {
18
19
  focus: FocusEvent;
@@ -28,5 +29,6 @@ export type RichSlots = typeof __propDef.slots;
28
29
  export default class Rich extends SvelteComponentTyped<RichProps, RichEvents, RichSlots> {
29
30
  get run(): (onStop?: undefined) => void;
30
31
  get getFormattingOperations(): (withCaptions?: boolean) => any[];
32
+ get save(): () => void;
31
33
  }
32
34
  export {};
@@ -16,6 +16,8 @@ export declare class rKanban_definition {
16
16
  titleAttrib: string;
17
17
  titleOnChange: Function | undefined;
18
18
  titleReadOnly: boolean;
19
+ titleHref: string | undefined;
20
+ titleHrefFunc: Function | undefined;
19
21
  summaryAttrib: string;
20
22
  summaryOnChange: Function | undefined;
21
23
  summaryReadOnly: boolean;
@@ -15,6 +15,8 @@ export class rKanban_definition {
15
15
  titleAttrib = '';
16
16
  titleOnChange = undefined;
17
17
  titleReadOnly = false;
18
+ titleHref = undefined;
19
+ titleHrefFunc = undefined;
18
20
  summaryAttrib = '';
19
21
  summaryOnChange = undefined;
20
22
  summaryReadOnly = false;
@@ -1,4 +1,5 @@
1
1
  <script>import { getContext, tick } from "svelte";
2
+ import { push } from "svelte-spa-router";
2
3
  import {
3
4
  contextItemsStore,
4
5
  isActive,
@@ -8,7 +9,8 @@ import {
8
9
  editable,
9
10
  showFloatingToolbar,
10
11
  informModification,
11
- pushChanges
12
+ pushChanges,
13
+ startEditing
12
14
  } from "../../..";
13
15
  import { FaArrowsAlt, FaTrash, FaPlus, FaExternalLinkAlt } from "svelte-icons/fa";
14
16
  import MoveOperations from "./kanban.move.menu.svelte";
@@ -24,6 +26,8 @@ $:
24
26
  selectedClass = isCardSelected ? "!border-blue-300" : "";
25
27
  $:
26
28
  focusedClass = isCardActive ? "bg-stone-100 dark:bg-stone-700" : "";
29
+ $:
30
+ isLinkLike = isCardActive && (!!definition.titleHref || !!definition.titleHrefFunc);
27
31
  function calculate_active(...args) {
28
32
  return isActive("props", item);
29
33
  }
@@ -96,9 +100,13 @@ let summaryElement;
96
100
  let summaryPlaceholder = false;
97
101
  export async function editProperty(field) {
98
102
  if (field == "Title") {
99
- titleElement.focus();
100
- await tick();
101
- setSelectionAtEnd(titleElement);
103
+ if (isLinkLike) {
104
+ startEditing(titleElement);
105
+ } else {
106
+ titleElement.focus();
107
+ await tick();
108
+ setSelectionAtEnd(titleElement);
109
+ }
102
110
  } else if (field == "Summary") {
103
111
  if (!!summaryElement) {
104
112
  summaryElement.focus();
@@ -127,6 +135,19 @@ export async function editProperty(field) {
127
135
  }
128
136
  }
129
137
  }
138
+ function followDefinedHRef() {
139
+ let link = getHRef();
140
+ if (link)
141
+ push(link);
142
+ }
143
+ function getHRef() {
144
+ if (definition.titleHref)
145
+ return definition.titleHref;
146
+ else if (definition.titleHrefFunc)
147
+ return definition.titleHrefFunc(item);
148
+ else
149
+ return "";
150
+ }
130
151
  </script>
131
152
 
132
153
  <!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -162,25 +183,43 @@ export async function editProperty(field) {
162
183
 
163
184
 
164
185
  {#if isCardActive}
165
- <h3 class=" text-lg font-semibold min-h-[1.75rem]
166
- sm:text-sm sm:font-semibold sm:min-h-[1.25rem]
167
- whitespace-nowrap overflow-clip truncate w-full sm:flex-none
168
- relative"
169
- use:editable={{
170
- action: (text) => onTitleChanged(text),
171
- active: true,
172
- readonly: definition.titleReadOnly,
173
- onFinish: (d) => {titleElement.blur()}}}
174
- bind:this={titleElement}>
175
- {item[definition.titleAttrib]}
186
+ {#if isLinkLike}
187
+ <h3 class=" text-lg font-semibold min-h-[1.75rem]
188
+ sm:text-sm sm:font-semibold sm:min-h-[1.25rem]
189
+ whitespace-nowrap overflow-clip truncate w-full sm:flex-none
190
+ relative
191
+ sm:hover:cursor-pointer underline"
192
+ on:click|stopPropagation={followDefinedHRef}
193
+ use:editable={{
194
+ action: (text) => onTitleChanged(text),
195
+ active: false,
196
+ readonly: definition.titleReadOnly,
197
+ onFinish: (d) => {titleElement.blur()}}}
198
+ bind:this={titleElement}>
199
+ {item[definition.titleAttrib]}
200
+ </h3>
201
+ {:else}
202
+ <h3 class=" text-lg font-semibold min-h-[1.75rem]
203
+ sm:text-sm sm:font-semibold sm:min-h-[1.25rem]
204
+ whitespace-nowrap overflow-clip truncate w-full sm:flex-none
205
+ relative"
206
+ use:editable={{
207
+ action: (text) => onTitleChanged(text),
208
+ active: true,
209
+ readonly: definition.titleReadOnly,
210
+ onFinish: (d) => {titleElement.blur()}}}
211
+ bind:this={titleElement}>
212
+ {item[definition.titleAttrib]}
176
213
 
177
- {#if definition.onOpen}
178
- <button class="absolute top-1 right-0 w-5 h-5 sm:w-3 sm:h-3"
179
- on:click={(e) => definition.onOpen(item)}>
180
- <FaExternalLinkAlt/>
181
- </button>
214
+ {#if definition.onOpen}
215
+ <button class="absolute top-1 right-0 w-5 h-5 sm:w-3 sm:h-3"
216
+ on:click={(e) => definition.onOpen(item)}>
217
+ <FaExternalLinkAlt/>
218
+ </button>
219
+ {/if}
220
+ </h3>
182
221
  {/if}
183
- </h3>
222
+
184
223
  {:else}
185
224
  <h3 class=" text-lg font-semibold min-h-[1.75rem]
186
225
  sm:text-sm sm:font-semibold sm:min-h-[1.25rem]
@@ -2,9 +2,13 @@
2
2
  export let a;
3
3
  export let onChange = void 0;
4
4
  export let readOnly = false;
5
+ export let href = void 0;
6
+ export let hrefFunc = void 0;
5
7
  let definition = getContext("rKanban-definition");
6
8
  definition.titleAttrib = a;
7
9
  definition.titleOnChange = onChange;
8
10
  definition.titleReadOnly = readOnly;
11
+ definition.titleHref = href;
12
+ definition.titleHrefFunc = hrefFunc;
9
13
  </script>
10
14
 
@@ -4,6 +4,8 @@ declare const __propDef: {
4
4
  a: string;
5
5
  onChange?: Function | undefined;
6
6
  readOnly?: boolean | undefined;
7
+ href?: string | undefined;
8
+ hrefFunc?: Function | undefined;
7
9
  };
8
10
  events: {
9
11
  [evt: string]: CustomEvent<any>;
@@ -36,7 +36,7 @@ $:
36
36
  $:
37
37
  focused_class = is_row_active ? "bg-stone-200 dark:bg-stone-700" : "";
38
38
  $:
39
- is_link_like = is_row_selected && (!!definition.title_href || !!definition.title_href_func);
39
+ is_link_like = is_row_active && (!!definition.title_href || !!definition.title_href_func);
40
40
  if (!typename) {
41
41
  if (item.$type)
42
42
  typename = item.$type;
@@ -117,18 +117,24 @@ function on_active_row_clicked(e, part) {
117
117
  }
118
118
  } else if (click_on_empty_space) {
119
119
  if (definition.title_href || definition.title_href_func) {
120
- let link = "";
121
- if (definition.title_href)
122
- link = definition.title_href;
123
- else if (definition.title_href_func)
124
- link = definition.title_href_func(item);
125
- if (link)
126
- push(link);
127
120
  } else {
128
121
  }
129
122
  } else {
130
123
  }
131
124
  }
125
+ function followDefinedHRef() {
126
+ let link = getHRef();
127
+ if (link)
128
+ push(link);
129
+ }
130
+ function getHRef() {
131
+ if (definition.title_href)
132
+ return definition.title_href;
133
+ else if (definition.title_href_func)
134
+ return definition.title_href_func(item);
135
+ else
136
+ return "";
137
+ }
132
138
  function activate_row(e, item2) {
133
139
  activateItem("props", item2, toolbarOperations(item2));
134
140
  if (e)
@@ -174,8 +180,19 @@ async function force_editing(field) {
174
180
  if (!element_node.classList.contains("editable")) {
175
181
  return;
176
182
  }
177
- element_node.focus();
178
- setSelectionAtEnd(element_node);
183
+ if (field == "Title") {
184
+ if (is_link_like) {
185
+ startEditing(element_node, () => {
186
+ placeholder = "";
187
+ });
188
+ } else {
189
+ element_node.focus();
190
+ setSelectionAtEnd(element_node);
191
+ }
192
+ } else {
193
+ element_node.focus();
194
+ setSelectionAtEnd(element_node);
195
+ }
179
196
  }
180
197
  function setSelectionAtEnd(element) {
181
198
  const textNode = element.childNodes[0];
@@ -214,43 +231,58 @@ export function scrollToView() {
214
231
  <slot name="left" element={item}/>
215
232
 
216
233
  <div class="ml-3 w-full py-1"
217
- class:sm:hover:cursor-pointer={is_link_like}
218
234
  use:selectable={item}
219
235
  on:click={(e) => {activate_row(e, item)}}
220
236
  role="row"
221
237
  tabindex="0">
222
238
  <div class="block sm:flex sm:flex-row" on:click={(e) => on_active_row_clicked(e, 'top')}>
223
239
 
224
- {#if is_row_active}
225
- {#key item[title]} <!-- Wymusza pełne wyrenderowanie zwłasza po zmiane z pustego na tekst -->
226
- <p class=" text-lg font-semibold min-h-[1.75rem]
227
- sm:text-sm sm:font-semibold sm:min-h-[1.25rem]
228
- whitespace-nowrap overflow-clip w-full sm:flex-none sm:w-2/3"
229
- id="__hd_list_ctrl_{item[item_key]}_Title"
230
- use:editable={{
231
- action: (text) => {change_name(text)},
232
- active: true,
233
- readonly: definition.title_readonly,
234
- }}>
240
+ {#if is_row_active}
241
+ {#key item[title]} <!-- Wymusza pełne wyrenderowanie zwłasza po zmiane z pustego na tekst -->
242
+ {#if is_link_like}
243
+ <p class=" text-lg font-semibold min-h-[1.75rem]
244
+ sm:text-sm sm:font-semibold sm:min-h-[1.25rem]
245
+ whitespace-nowrap overflow-clip w-full sm:flex-none sm:w-2/3
246
+ sm:hover:cursor-pointer underline"
247
+ id="__hd_list_ctrl_{item[item_key]}_Title"
248
+ on:click|stopPropagation={followDefinedHRef}
249
+ use:editable={{
250
+ action: (text) => {change_name(text)},
251
+ active: false,
252
+ readonly: definition.title_readonly,
253
+ }}
254
+ >
235
255
  {element_title}
256
+ </p>
257
+ {:else}
258
+ <p class=" text-lg font-semibold min-h-[1.75rem]
259
+ sm:text-sm sm:font-semibold sm:min-h-[1.25rem]
260
+ whitespace-nowrap overflow-clip w-full sm:flex-none sm:w-2/3"
261
+ id="__hd_list_ctrl_{item[item_key]}_Title"
262
+ use:editable={{
263
+ action: (text) => {change_name(text)},
264
+ active: true,
265
+ readonly: definition.title_readonly,
266
+ }}>
267
+ {element_title}
236
268
 
237
- {#if definition.onOpen}
238
- <button class="ml-3 w-5 h-5 sm:w-3 sm:h-3"
239
- on:click={(e) => definition.onOpen(item)}>
240
- <FaExternalLinkAlt/>
241
- </button>
242
- {/if}
243
- </p>
269
+ {#if definition.onOpen}
270
+ <button class="ml-3 w-5 h-5 sm:w-3 sm:h-3"
271
+ on:click={(e) => definition.onOpen(item)}>
272
+ <FaExternalLinkAlt/>
273
+ </button>
274
+ {/if}
275
+ </p>
276
+ {/if}
244
277
  {/key}
245
- {:else}
278
+ {:else}
246
279
  <p class=" text-lg font-semibold min-h-[1.75rem]
247
280
  sm:text-sm sm:font-semibold sm:min-h-[1.25rem]
248
281
  whitespace-nowrap overflow-clip w-full sm:flex-none sm:w-2/3"
249
282
  id="__hd_list_ctrl_{item[item_key]}_Title">
250
283
  {element_title}
251
284
  </p>
252
- {/if}
253
-
285
+ {/if}
254
286
 
255
287
 
256
288
  <section class="hidden sm:block w-full sm:flex-none sm:w-1/3">
@@ -130,11 +130,18 @@
130
130
  if(config.customOperations && Array.isArray(config.customOperations) && config.customOperations.length > 0)
131
131
  {
132
132
  config.customOperations.forEach( o => {
133
- options.push({
134
- caption: o.caption,
135
- icon: o.icon,
136
- action: o.action
137
- })
133
+ let add = true;
134
+ if(o.condition)
135
+ add = o.condition();
136
+
137
+ if(add)
138
+ {
139
+ options.push({
140
+ caption: o.caption,
141
+ icon: o.icon,
142
+ action: o.action
143
+ })
144
+ }
138
145
  })
139
146
  }
140
147
 
package/index.d.ts CHANGED
@@ -51,7 +51,7 @@ export { default as KanbanComboProperty } from './components/kanban/kanban.combo
51
51
  export { default as KanbanTagsProperty } from './components/kanban/kanban.tags.svelte';
52
52
  export { default as KanbanCallbacks } from './components/kanban/kanban.callbacks.svelte';
53
53
  export { KanbanColumnTop, KanbanColumnBottom } from './components/kanban/Kanban';
54
- export { selectItem, activateItem, clearActiveItem, isActive, isSelected, getActive, editable, startEditing, selectable, handleSelect, isDeviceSmallerThan } from './utils';
54
+ export { selectItem, activateItem, clearActiveItem, isActive, isSelected, getActive, editable, startEditing, saveCurrentEditable, selectable, handleSelect, isDeviceSmallerThan } from './utils';
55
55
  export { mainViewReloader, reloadMainView } from './stores.js';
56
56
  export { data_tick_store, // tmp
57
57
  hasSelectedItem, hasDataItem, setNavigatorTitle } from "./stores";
package/index.js CHANGED
@@ -57,7 +57,7 @@ export { default as KanbanComboProperty } from './components/kanban/kanban.combo
57
57
  export { default as KanbanTagsProperty } from './components/kanban/kanban.tags.svelte';
58
58
  export { default as KanbanCallbacks } from './components/kanban/kanban.callbacks.svelte';
59
59
  export { KanbanColumnTop, KanbanColumnBottom } from './components/kanban/Kanban';
60
- export { selectItem, activateItem, clearActiveItem, isActive, isSelected, getActive, editable, startEditing, selectable, handleSelect, isDeviceSmallerThan } from './utils';
60
+ export { selectItem, activateItem, clearActiveItem, isActive, isSelected, getActive, editable, startEditing, saveCurrentEditable, selectable, handleSelect, isDeviceSmallerThan } from './utils';
61
61
  export { mainViewReloader, reloadMainView } from './stores.js';
62
62
  export { data_tick_store, // tmp
63
63
  hasSelectedItem, hasDataItem, setNavigatorTitle } from "./stores";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humandialog/forms.svelte",
3
- "version": "0.5.10",
3
+ "version": "0.5.12",
4
4
  "description": "Basic Svelte UI components for Object Reef applications",
5
5
  "devDependencies": {
6
6
  "@playwright/test": "^1.28.1",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "type": "module",
28
28
  "dependencies": {
29
- "@humandialog/auth.svelte": "^1.4.0",
29
+ "@humandialog/auth.svelte": "^1.5.1",
30
30
  "flowbite-svelte": "^0.29.13",
31
31
  "svelte-icons": "^2.1.0",
32
32
  "svelte-spa-router": "^3.3.0"
@@ -442,9 +442,9 @@
442
442
 
443
443
  <ListComboProperty name='Privileges' a='auth_group' onSelect={on_change_privileges}>
444
444
  <ComboItem name='None' key={0} />
445
- <ComboItem name='Can See' key={1} />
446
- <ComboItem name='Can Invite' key={3} />
447
- <ComboItem name='Maintainer' key={7} />
445
+ <ComboItem name='Can see' key={1} />
446
+ <ComboItem name='Can invite' key={3} />
447
+ <ComboItem name='Can fully manage' key={7} />
448
448
  </ListComboProperty>
449
449
 
450
450
  {#if showFiles}
package/utils.d.ts CHANGED
@@ -9,6 +9,7 @@ export function editable(node: any, params: any): {
9
9
  destroy(): void;
10
10
  } | undefined;
11
11
  export function startEditing(element: any, finish_callback: any): void;
12
+ export function saveCurrentEditable(): void;
12
13
  export function selectable(node: any, itm: any): {
13
14
  destroy(): void;
14
15
  };
@@ -33,3 +34,4 @@ export namespace SCREEN_SIZES {
33
34
  const lg: number;
34
35
  const xl: number;
35
36
  }
37
+ export let currentEditable: null;
package/utils.js CHANGED
@@ -97,6 +97,8 @@ export function getActive(context_level)
97
97
  return null;
98
98
  }
99
99
 
100
+ export let currentEditable = null;
101
+
100
102
  export function editable(node, params)
101
103
  {
102
104
  let action;
@@ -122,6 +124,9 @@ export function editable(node, params)
122
124
  const org_text = node.textContent;
123
125
  const blur_listener = async (e) =>
124
126
  {
127
+ if(currentEditable == node)
128
+ currentEditable = null;
129
+
125
130
  let cancel = !node.textContent
126
131
  if(observer)
127
132
  observer.disconnect();
@@ -170,6 +175,7 @@ export function editable(node, params)
170
175
  {
171
176
  node.removeEventListener("blur", blur_listener);
172
177
  node.removeEventListener("keydown", key_listener);
178
+ node.removeEventListener("save", save_listener);
173
179
  node.contentEditable = "false"
174
180
 
175
181
  let sel = window.getSelection();
@@ -206,11 +212,20 @@ export function editable(node, params)
206
212
  node.removeEventListener("finish", (e) => {});
207
213
  }
208
214
 
215
+ const save_listener = async (e) =>
216
+ {
217
+ if(has_changed)
218
+ await action(node.textContent)
219
+ }
220
+
209
221
  const edit_listener = async (e) =>
210
222
  {
211
223
  node.contentEditable = "true"
212
224
  node.addEventListener("blur", blur_listener);
213
225
  node.addEventListener("keydown", key_listener);
226
+
227
+ currentEditable = node;
228
+ node.addEventListener("save", save_listener)
214
229
 
215
230
  node.focus();
216
231
 
@@ -235,6 +250,8 @@ export function editable(node, params)
235
250
  node.addEventListener("blur", blur_listener);
236
251
  node.addEventListener("keydown", key_listener);
237
252
  has_changed = false;
253
+ currentEditable = node;
254
+ node.addEventListener("save", save_listener)
238
255
 
239
256
  observer = new MutationObserver(() => { has_changed = true; });
240
257
  observer.observe( node, {
@@ -298,6 +315,12 @@ export function startEditing(element, finish_callback)
298
315
  }
299
316
  }
300
317
 
318
+ export function saveCurrentEditable()
319
+ {
320
+ if(currentEditable)
321
+ currentEditable.dispatchEvent(new Event("save"))
322
+ }
323
+
301
324
  export function selectable(node, itm)
302
325
  {
303
326
  const select_listener = (e) =>
@@ -98,11 +98,18 @@
98
98
  if(config.customOperations && Array.isArray(config.customOperations) && config.customOperations.length > 0)
99
99
  {
100
100
  config.customOperations.forEach( o => {
101
- options.push({
102
- caption: o.caption,
103
- icon: o.icon,
104
- action: o.action
105
- })
101
+ let add = true;
102
+ if(o.condition)
103
+ add = o.condition();
104
+
105
+ if(add)
106
+ {
107
+ options.push({
108
+ caption: o.caption,
109
+ icon: o.icon,
110
+ action: o.action
111
+ })
112
+ }
106
113
  })
107
114
  }
108
115