@ibgib/web-gib 0.0.46 → 0.0.47

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.
@@ -17,9 +17,14 @@ const logalot = GLOBAL_LOG_A_LOT;
17
17
  * @param {boolean} [details.prompt] - If true, shows a text input for prompting the user.
18
18
  * @returns {Promise<string | null>} - A promise that resolves with the text entered by the user (if prompt is true) or null if canceled/dismissed.
19
19
  */
20
- export async function showFullscreenDialog(opts: {
21
- title: string,
22
- msg: string,
20
+ export interface FullscreenDialogController {
21
+ close: () => void;
22
+ update: (opts: { title?: string; msg?: string; animationEmoji?: string }) => void;
23
+ }
24
+
25
+ export interface ShowFullscreenDialogOpts {
26
+ title: string;
27
+ msg: string;
23
28
  /**
24
29
  * determines if to show the cancel button. does NOT control whether or not
25
30
  * we show the input control.
@@ -30,19 +35,36 @@ export async function showFullscreenDialog(opts: {
30
35
  *
31
36
  * @see {@link showInput}
32
37
  */
33
- prompt?: boolean,
38
+ prompt?: boolean;
34
39
  /**
35
40
  * this flag determines if the user input field is shown. If this is truthy,
36
41
  * then {@link prompt} will be ignored and always set to `true`.
37
42
  *
38
43
  * @see {@link prompt}
39
44
  */
40
- showInput?: boolean,
41
- defaultValue?: string,
42
- okButtonTitle?: string,
43
- cancelButtonTitle?: string,
44
- isPassword?: boolean,
45
- }): Promise<string | undefined> {
45
+ showInput?: boolean;
46
+ defaultValue?: string;
47
+ okButtonTitle?: string;
48
+ cancelButtonTitle?: string;
49
+ isPassword?: boolean;
50
+ isBusy?: boolean;
51
+ animationEmoji?: string;
52
+ }
53
+
54
+ export async function showFullscreenDialog(opts: ShowFullscreenDialogOpts & { isBusy: true }): Promise<FullscreenDialogController>;
55
+ export async function showFullscreenDialog(opts: ShowFullscreenDialogOpts & { isBusy?: false }): Promise<string | undefined>;
56
+ export async function showFullscreenDialog({
57
+ title,
58
+ msg,
59
+ prompt,
60
+ showInput,
61
+ defaultValue,
62
+ okButtonTitle,
63
+ cancelButtonTitle,
64
+ isPassword,
65
+ isBusy,
66
+ animationEmoji,
67
+ }: ShowFullscreenDialogOpts): Promise<string | undefined | FullscreenDialogController> {
46
68
  const lc = `[${showFullscreenDialog.name}]`;
47
69
  try {
48
70
  if (logalot) { console.log(`${lc} starting... (I: 037b228a2568181cd3eb25dba4e03225)`); }
@@ -67,10 +89,10 @@ export async function showFullscreenDialog(opts: {
67
89
  throw new Error(`(UNEXPECTED) One or more dialog elements not found in DOM (E: 578e37c0271cc57a99e95e780ad18a25)`);
68
90
  }
69
91
 
70
- titleElement.textContent = opts.title || '';
92
+ titleElement.textContent = title || '';
71
93
  [...messageElement.childNodes as any].forEach(child => messageElement.removeChild(child));
72
94
  // messageElement.innerHTML = '';
73
- let msgLines = (opts.msg || '').split('\n');
95
+ let msgLines = (msg || '').split('\n');
74
96
  msgLines.forEach(line => {
75
97
  const pLine = document.createElement('p');
76
98
  pLine.textContent = line;
@@ -78,11 +100,24 @@ export async function showFullscreenDialog(opts: {
78
100
  });
79
101
  // messageElement.textContent = opts.msg || '';
80
102
 
81
- // reset the input classList
103
+ // Add dynamic animation emoji if provided
104
+ if (animationEmoji) {
105
+ const animDiv = document.createElement('div');
106
+ animDiv.id = 'fullscreen-dialog-animation-emoji';
107
+ animDiv.textContent = animationEmoji;
108
+ animDiv.style.fontSize = '3.5rem';
109
+ animDiv.style.textAlign = 'center';
110
+ animDiv.style.margin = '1.5rem 0';
111
+ animDiv.style.animation = 'spin-bounce 5s infinite ease-in-out';
112
+ messageElement.appendChild(animDiv);
113
+ }
114
+
115
+ // reset the input classList and button displays
82
116
  promptInput.classList.remove('collapsed');
83
117
  cancelButton.classList.remove('collapsed');
84
118
  cancelButton.style.display = 'inline-block';
85
- okButton.textContent = opts.okButtonTitle || 'OK';
119
+ okButton.style.display = 'inline-block';
120
+ okButton.textContent = okButtonTitle || 'OK';
86
121
 
87
122
  // Prompt-specific setup
88
123
  const onKeypress = (ev: KeyboardEvent) => {
@@ -93,15 +128,19 @@ export async function showFullscreenDialog(opts: {
93
128
  if (isEnter && ev.ctrlKey) { okButton.click(); }
94
129
  };
95
130
 
96
- if (opts.prompt || opts.showInput) {
131
+ if (isBusy) {
132
+ promptInput.classList.add('collapsed');
133
+ cancelButton.style.display = 'none';
134
+ okButton.style.display = 'none';
135
+ } else if (prompt || showInput) {
97
136
  promptInput.addEventListener('keypress', onKeypress);
98
- if (opts.showInput) {
137
+ if (showInput) {
99
138
  promptInput.autofocus = true;
100
- promptInput.value = opts.defaultValue ?? '';
139
+ promptInput.value = defaultValue ?? '';
101
140
  promptInput.addEventListener('keypress', (ev) => {
102
141
  if (ev.key === 'Enter') { okButton.click(); }
103
142
  });
104
- if (opts.isPassword) {
143
+ if (isPassword) {
105
144
  promptInput.type = 'password';
106
145
  promptInput.autocomplete = 'off';
107
146
  promptInput.required = true;
@@ -115,18 +154,91 @@ export async function showFullscreenDialog(opts: {
115
154
  promptInput.autofocus = false;
116
155
  promptInput.classList.add('collapsed');
117
156
  }
118
- cancelButton.textContent = opts.cancelButtonTitle || 'Cancel';
157
+ cancelButton.textContent = cancelButtonTitle || 'Cancel';
119
158
  } else {
120
159
  promptInput.classList.add('collapsed'); // Hide prompt input
121
160
  cancelButton.style.display = 'none'; // Hide cancel button for alerts
122
161
  }
123
162
 
124
- return new Promise<string | undefined>((resolve) => {
163
+ return new Promise<any>((resolve) => {
164
+ if (isBusy) {
165
+ const onCancelEvent = (ev: Event) => {
166
+ ev.preventDefault();
167
+ };
168
+ dialog.addEventListener('cancel', onCancelEvent);
169
+
170
+ const close = () => {
171
+ dialog.removeEventListener('cancel', onCancelEvent);
172
+ dialog.close();
173
+ };
174
+
175
+ const update = ({
176
+ title: newTitle,
177
+ msg: newMsg,
178
+ animationEmoji: newEmoji,
179
+ }: {
180
+ title?: string;
181
+ msg?: string;
182
+ animationEmoji?: string;
183
+ }) => {
184
+ if (newTitle !== undefined) {
185
+ titleElement.textContent = newTitle;
186
+ }
187
+ if (newMsg !== undefined) {
188
+ // Clear non-emoji nodes
189
+ [...messageElement.childNodes as any].forEach(child => {
190
+ if (child.id !== 'fullscreen-dialog-animation-emoji') {
191
+ messageElement.removeChild(child);
192
+ }
193
+ });
194
+ // Insert new lines before the animation emoji
195
+ let newMsgLines = newMsg.split('\n');
196
+ const animDiv = document.getElementById('fullscreen-dialog-animation-emoji');
197
+ newMsgLines.forEach(line => {
198
+ const pLine = document.createElement('p');
199
+ pLine.textContent = line;
200
+ if (animDiv) {
201
+ messageElement.insertBefore(pLine, animDiv);
202
+ } else {
203
+ messageElement.appendChild(pLine);
204
+ }
205
+ });
206
+ }
207
+ if (newEmoji !== undefined) {
208
+ let animDiv = document.getElementById('fullscreen-dialog-animation-emoji');
209
+ if (newEmoji) {
210
+ if (!animDiv) {
211
+ animDiv = document.createElement('div');
212
+ animDiv.id = 'fullscreen-dialog-animation-emoji';
213
+ animDiv.style.fontSize = '3.5rem';
214
+ animDiv.style.textAlign = 'center';
215
+ animDiv.style.margin = '1.5rem 0';
216
+ animDiv.style.animation = 'spin-bounce 5s infinite ease-in-out';
217
+ messageElement.appendChild(animDiv);
218
+ }
219
+ animDiv.textContent = newEmoji;
220
+ } else if (animDiv) {
221
+ animDiv.remove();
222
+ }
223
+ }
224
+ };
225
+
226
+ if (!dialog.open) {
227
+ dialog.showModal();
228
+ }
229
+ delay(1000).then(() => {
230
+ dialogBody.scrollTo({ top: 0, behavior: 'smooth' });
231
+ });
232
+
233
+ resolve({ close, update });
234
+ return;
235
+ }
236
+
125
237
  // handlers
126
238
 
127
239
  /** helper used in other handlers */
128
240
  const removeEventListeners = () => {
129
- if (opts.prompt || opts.showInput) {
241
+ if (prompt || showInput) {
130
242
  promptInput.removeEventListener('keypress', onKeypress);
131
243
  }
132
244
  okButton.removeEventListener('click', onOK);
@@ -136,7 +248,7 @@ export async function showFullscreenDialog(opts: {
136
248
  /** user clicks OK or hits "ENTER" in input */
137
249
  const onOK = () => {
138
250
  removeEventListeners();
139
- let result = opts.showInput ?
251
+ let result = showInput ?
140
252
  promptInput.value ?? '' :
141
253
  true.toString();
142
254
  if (promptInput) { promptInput.value = ''; } // Clear input for next time
@@ -170,7 +282,9 @@ export async function showFullscreenDialog(opts: {
170
282
  dialog.addEventListener('close', onClose); // e.g. escape key
171
283
 
172
284
  // show it
173
- dialog.showModal();
285
+ if (!dialog.open) {
286
+ dialog.showModal();
287
+ }
174
288
  delay(1000).then(() => {
175
289
  dialogBody.scrollTo({ top: 0, behavior: 'smooth' });
176
290
  });
@@ -7,6 +7,8 @@ description: Scaffolds a new reusable ibgib UI component. Generates the componen
7
7
 
8
8
  This skill scaffolds a new UI component for an ibgib application. components are implemented as Web Components (Custom Elements) using the `ibgib-web-gib` framework, which provides deep integration with ibgib timelines and real-time updates.
9
9
 
10
+ For more documentation, refer to the ibgib component [README.md](libs\web-gib\src\ui\component\README.md)
11
+
10
12
  ## Goal
11
13
  To create a standard, reactive UI component that can be registered with the `ibgib-component-service` and rendered within an app shell.
12
14
 
@@ -20,14 +22,16 @@ Wait for the user to provide or confirm these tokens:
20
22
 
21
23
  ## Output Files
22
24
  The skill will generate:
23
- 1. `src/components/{{COMPONENT_NAME}}/{{COMPONENT_NAME}}.mts`
24
- 2. `src/components/{{COMPONENT_NAME}}/{{COMPONENT_NAME}}.html`
25
- 3. `src/components/{{COMPONENT_NAME}}/{{COMPONENT_NAME}}.css`
25
+ 1. `src/components/{{COMPONENT_NAME}}/IMPLEMENTATION.md` (Design spec, requirements, and checklist)
26
+ 2. `src/components/{{COMPONENT_NAME}}/{{COMPONENT_NAME}}.mts`
27
+ 3. `src/components/{{COMPONENT_NAME}}/{{COMPONENT_NAME}}.html`
28
+ 4. `src/components/{{COMPONENT_NAME}}/{{COMPONENT_NAME}}.css`
26
29
 
27
30
  ## Usage Instructions
28
31
  1. Ensure the `src/components/{{COMPONENT_NAME}}` directory exists.
29
- 2. Generate the files from templates.
30
- 3. Import and register the `{{COMPONENT_CLASS_PREFIX}}ComponentMeta` in your `ShellService`'s `registerComponents()` method.
32
+ 2. Before scaffolding the code, create the `IMPLEMENTATION.md` file detailing the core layout, settings, requirements, and an implementation checklist.
33
+ 3. Generate the implementation files (.mts, .html, .css) from templates matching the specifications in the implementation plan.
34
+ 4. Import and register the `{{COMPONENT_CLASS_PREFIX}}ComponentMeta` in your `ShellService`'s `registerComponents()` method.
31
35
 
32
36
  ## Component Lifecycle
33
37
  - `initialize(opts)`: Called when the component is first given an ibGib address.