@hotstaq/admin-panel 0.4.0 → 0.4.2

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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hotstaq/admin-panel",
3
3
  "description": "",
4
- "version": "0.4.0",
4
+ "version": "0.4.2",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
7
7
  "start": "hotstaq --hotsite ./HotSite.json --env-file ./.env run --server-type web-api",
@@ -1,42 +1,43 @@
1
1
  import { HotStaq, Hot, HotAPI, HotComponent, HotComponentOutput } from "hotstaq";
2
2
 
3
3
  /**
4
- * Inline collapsible "Add" panel. Replaces the modal opened by
5
- * <admin-edit hot-type="add">. Built on Bootstrap's collapse so it
6
- * slides down below a trigger button without overlaying the page.
4
+ * Self-contained inline add form. Replaces the modal opened by
5
+ * <admin-edit hot-type="add"> with a Bootstrap-collapse card that
6
+ * lives directly on the page header carries the "+ Add" toggle,
7
+ * body holds the form fields. No cross-component DOM injection.
7
8
  *
8
- * Pairs with <admin-card-table>: the table's "+ Add" button toggles
9
- * this panel via Bootstrap's data-bs-toggle="collapse" data-bs-target.
10
- * Slot for form fields lives at `hot-place-here name="panelBody"`.
9
+ * Place this above an <admin-card-table>; when the user clicks the
10
+ * header toggle, the form panel slides down. On Save, the paired
11
+ * table is asked to refresh via `attached_list`.
11
12
  *
12
13
  * Usage:
13
- * <admin-add-panel name="bankAccountsAdd" hot-title="Add bank account"
14
- * hot-onsave="<(values) => {
15
- * const r = await Hot.jsonRequest(`${config.baseUrl}/v1/bank_accounts/create`,
16
- * { bankAccount: values }, '${jwtToken}');
17
- * if (r && r.error) { alertError(r.error); return false; }
18
- * }Ra>"
19
- * hot-attached_list="bankAccountsList">
14
+ * <admin-add-panel name="bankAccountsAdd"
15
+ * hot-title="Add a bank account"
16
+ * hot-attached_list="bankAccountsList"
17
+ * hot-add_text="+ Add bank account"
18
+ * hot-button_title="Create"
19
+ * hot-onsave="<(values) => {...}Ra>">
20
20
  * <admin-form-field hot-field="name" hot-label="Name" hot-required="1"
21
21
  * hot-col="col-md-6"></admin-form-field>
22
- * ...
22
+ * <admin-form-field hot-field="bankSyncAPIType" hot-label="Sync type"
23
+ * hot-control="select"
24
+ * hot-options="paypal_webhooks:PayPal Webhooks"
25
+ * hot-col="col-md-6"></admin-form-field>
23
26
  * </admin-add-panel>
24
27
  */
25
28
  export class AdminAddPanel extends HotComponent
26
29
  {
27
- /** Title shown at the top of the panel. */
30
+ /** Title shown in the card header (next to the toggle button). */
28
31
  title: string;
29
32
  /** Submit button label. */
30
33
  button_title: string;
31
- /** Cancel button label. Empty disables the cancel button. */
34
+ /** Cancel button label. Empty hides the cancel button. */
32
35
  cancel_text: string;
33
- /** Optional id of the related <admin-card-table>; the toggle button gets injected into its header. */
36
+ /** Optional id of the partner <admin-card-table>; refreshList() is called after a successful save. */
34
37
  attached_list: string;
35
- /** Where to render the toggle button (a hot-place-here name on the page). Leave blank when attached_list is set. */
36
- add_place_here: string;
37
- /** Text shown on the toggle button. */
38
+ /** Text shown on the header toggle button. */
38
39
  add_text: string;
39
- /** When set to "1" / "true", panel starts expanded. */
40
+ /** "1" / "true" panel starts expanded. */
40
41
  start_open: string;
41
42
  /** What to run when the user clicks Save. Receives a values object built from hot-field inputs. Return false to keep the panel open. */
42
43
  onsave: (values: any) => Promise<boolean | void>;
@@ -53,33 +54,52 @@ export class AdminAddPanel extends HotComponent
53
54
  this.button_title = "Save";
54
55
  this.cancel_text = "Cancel";
55
56
  this.attached_list = "";
56
- this.add_place_here = "";
57
57
  this.add_text = "+ Add";
58
58
  this.start_open = "0";
59
59
  this.onsave = null;
60
60
  }
61
61
 
62
62
  /**
63
- * Wires the submit + cancel handlers after the DOM is in place.
64
- * Browsers handle the collapse open/close via Bootstrap data-attrs
65
- * on the toggle button — we don't need to manage that ourselves.
63
+ * Wire submit + cancel handlers after the DOM is in place.
64
+ * Bootstrap's data-bs-toggle handles open/close automatically.
65
+ *
66
+ * Also relocates any child elements (admin-form-fields, etc.) that
67
+ * the framework appended to the card root into the collapse panel's
68
+ * form-row. Without this, children would render OUTSIDE the
69
+ * collapsible body (siblings of the card-header and the collapse
70
+ * div), visible on the page even when the panel is collapsed.
66
71
  */
67
72
  onPostPlace (parentHtmlElement: HTMLElement, htmlElement: HTMLElement): HTMLElement
68
73
  {
69
74
  const self = this;
70
- const panel = document.getElementById (this.panelId);
71
- if (panel == null)
75
+ const root = document.getElementById (this.name);
76
+ if (root == null)
72
77
  return (null);
73
78
 
74
- const submitBtn = panel.querySelector (`.fl-add-panel-submit`) as HTMLButtonElement | null;
75
- const cancelBtn = panel.querySelector (`.fl-add-panel-cancel`) as HTMLButtonElement | null;
79
+ const submitBtn = root.querySelector (".fl-add-panel-submit") as HTMLButtonElement | null;
80
+ const cancelBtn = root.querySelector (".fl-add-panel-cancel") as HTMLButtonElement | null;
81
+
82
+ // Auto-relocate stray children into the form-row. The framework
83
+ // appends children that lack `hot-place-parent` directly under
84
+ // compHtmlElement2 (the card root). Move them inside the form.
85
+ const row = root.querySelector (".fl-add-panel-form .row") as HTMLElement | null;
86
+ const collapse = document.getElementById (this.panelId);
87
+ const header = root.querySelector (":scope > .card-header") as HTMLElement | null;
88
+ if (row != null)
89
+ {
90
+ Array.from (root.children).forEach ((child) =>
91
+ {
92
+ if (child === header || child === collapse) return;
93
+ row.appendChild (child as Element);
94
+ });
95
+ }
76
96
 
77
97
  if (submitBtn != null)
78
98
  {
79
99
  submitBtn.addEventListener ("click", async (e) =>
80
100
  {
81
101
  e.preventDefault ();
82
- const values = self.collectFieldValues (panel);
102
+ const values = self.collectFieldValues (root);
83
103
  submitBtn.disabled = true;
84
104
  try
85
105
  {
@@ -89,7 +109,7 @@ export class AdminAddPanel extends HotComponent
89
109
 
90
110
  if (keepOpen === false || keepOpen == null)
91
111
  {
92
- self.resetFields (panel);
112
+ self.resetFields (root);
93
113
  self.collapsePanel ();
94
114
  self.refreshAttachedList ();
95
115
  }
@@ -110,7 +130,7 @@ export class AdminAddPanel extends HotComponent
110
130
  cancelBtn.addEventListener ("click", (e) =>
111
131
  {
112
132
  e.preventDefault ();
113
- self.resetFields (panel);
133
+ self.resetFields (root);
114
134
  self.collapsePanel ();
115
135
  });
116
136
  }
@@ -118,11 +138,10 @@ export class AdminAddPanel extends HotComponent
118
138
  return (null);
119
139
  }
120
140
 
121
- /** Read every hot-field-marked input inside the panel into a plain object. */
122
- protected collectFieldValues (panel: HTMLElement): any
141
+ protected collectFieldValues (root: HTMLElement): any
123
142
  {
124
143
  const out: any = {};
125
- const nodes = panel.querySelectorAll ("[hot-field]");
144
+ const nodes = root.querySelectorAll ("[hot-field]");
126
145
  for (let i = 0; i < nodes.length; i++)
127
146
  {
128
147
  const el = nodes[i] as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
@@ -139,9 +158,9 @@ export class AdminAddPanel extends HotComponent
139
158
  return (out);
140
159
  }
141
160
 
142
- protected resetFields (panel: HTMLElement): void
161
+ protected resetFields (root: HTMLElement): void
143
162
  {
144
- const nodes = panel.querySelectorAll ("[hot-field]");
163
+ const nodes = root.querySelectorAll ("[hot-field]");
145
164
  for (let i = 0; i < nodes.length; i++)
146
165
  {
147
166
  const el = nodes[i] as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
@@ -155,19 +174,11 @@ export class AdminAddPanel extends HotComponent
155
174
  {
156
175
  const panel = document.getElementById (this.panelId);
157
176
  if (panel == null) return;
158
- // Bootstrap collapse hide — works without importing Bootstrap JS
159
- // directly by toggling the .show class and aria-expanded on any
160
- // trigger pointed at us.
161
177
  panel.classList.remove ("show");
162
178
  const triggers = document.querySelectorAll (`[data-bs-target="#${this.panelId}"]`);
163
179
  triggers.forEach (t => t.setAttribute ("aria-expanded", "false"));
164
180
  }
165
181
 
166
- /**
167
- * Best-effort refresh of the paired <admin-card-table>. The table
168
- * exposes a `refreshList` method on the rendered element when its
169
- * data is loaded; calling it re-fetches without a page reload.
170
- */
171
182
  protected refreshAttachedList (): void
172
183
  {
173
184
  if (this.attached_list === "") return;
@@ -185,48 +196,36 @@ export class AdminAddPanel extends HotComponent
185
196
  this.formId = `${this.name}Form`;
186
197
 
187
198
  const showClass = (this.start_open === "1" || this.start_open === "true") ? " show" : "";
188
- const titleHtml = this.title ? `<h2 class="h6 fl-add-panel-title mb-3">${this.title}</h2>` : "";
199
+ const ariaExp = showClass ? "true" : "false";
200
+ const titleHtml = this.title ? `<strong class="fl-add-panel-title">${this.title}</strong>` : `<span></span>`;
189
201
  const cancelHtml = this.cancel_text
190
202
  ? `<button type="button" class="btn btn-sm btn-link text-muted fl-add-panel-cancel">${this.cancel_text}</button>`
191
203
  : "";
192
204
 
193
- const panelHtml = `
194
- <div id="${this.panelId}" class="collapse fl-add-panel${showClass}">
195
- <div class="card-body border-top bg-body-tertiary fl-add-panel-body">
205
+ // Single self-contained card. Header has the toggle, body is the
206
+ // collapse panel containing the form. Bootstrap's data-bs-toggle
207
+ // drives the open/close — no JS wiring needed for that.
208
+ return (`
209
+ <div id="${this.name}" class="card fl-add-panel mb-3">
210
+ <div class="card-header d-flex justify-content-between align-items-center">
196
211
  ${titleHtml}
197
- <form id="${this.formId}" class="fl-add-panel-form">
198
- <div class="row g-2 align-items-end">
199
- <hot-place-here name="panelBody"></hot-place-here>
200
- </div>
201
- <div class="d-flex justify-content-end gap-2 mt-3">
202
- ${cancelHtml}
203
- <button type="submit" class="btn btn-sm btn-success fl-add-panel-submit">${this.button_title}</button>
204
- </div>
205
- </form>
212
+ <button type="button" class="btn btn-sm btn-primary fl-add-panel-toggle"
213
+ data-bs-toggle="collapse" data-bs-target="#${this.panelId}"
214
+ aria-expanded="${ariaExp}" aria-controls="${this.panelId}">${this.add_text}</button>
206
215
  </div>
207
- </div>`;
208
-
209
- const outputs: HotComponentOutput[] = [{ html: panelHtml, documentSelector: "body" }];
210
-
211
- // If a partner card-table is named, inject the toggle button into
212
- // its header slot. Otherwise honour an explicit add_place_here.
213
- const toggleBtn = `<button type="button" class="btn btn-sm btn-primary fl-add-panel-toggle" data-bs-toggle="collapse" data-bs-target="#${this.panelId}" aria-expanded="${showClass ? "true" : "false"}" aria-controls="${this.panelId}">${this.add_text}</button>`;
214
-
215
- if (this.attached_list !== "")
216
- {
217
- outputs.push ({
218
- html: toggleBtn,
219
- documentSelector: `[data-card-table-add-slot="${this.attached_list}"]`
220
- });
221
- }
222
- else if (this.add_place_here !== "")
223
- {
224
- outputs.push ({
225
- html: toggleBtn,
226
- documentSelector: `hot-place-here[name="${this.add_place_here}"]`
227
- });
228
- }
229
-
230
- return (outputs);
216
+ <div id="${this.panelId}" class="collapse fl-add-panel-body${showClass}">
217
+ <div class="card-body border-top">
218
+ <form id="${this.formId}" class="fl-add-panel-form">
219
+ <div class="row g-2 align-items-end">
220
+ <hot-place-here name="panelBody"></hot-place-here>
221
+ </div>
222
+ <div class="d-flex justify-content-end gap-2 mt-3">
223
+ ${cancelHtml}
224
+ <button type="submit" class="btn btn-sm btn-success fl-add-panel-submit">${this.button_title}</button>
225
+ </div>
226
+ </form>
227
+ </div>
228
+ </div>
229
+ </div>`);
231
230
  }
232
231
  }