@blorkfield/blork-tabs 0.4.0 → 0.5.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/dist/index.cjs CHANGED
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  createPanelElement: () => createPanelElement,
33
33
  createPanelState: () => createPanelState,
34
34
  createPresetAnchor: () => createPresetAnchor,
35
+ createTagButton: () => createTagButton,
35
36
  detachFromGroup: () => detachFromGroup,
36
37
  findSnapTarget: () => findSnapTarget,
37
38
  getConnectedGroup: () => getConnectedGroup,
@@ -1633,6 +1634,80 @@ var TabManager = class {
1633
1634
  this.debugPanelElements.clear();
1634
1635
  }
1635
1636
  };
1637
+
1638
+ // src/TagButton.ts
1639
+ function createTagButton(label, config = {}) {
1640
+ const { defaultActive = false, inputs = [], onChange } = config;
1641
+ const hasInputs = inputs.length > 0;
1642
+ const el = document.createElement(hasInputs ? "div" : "button");
1643
+ el.className = "blork-tabs-tag-btn";
1644
+ if (hasInputs) {
1645
+ el.setAttribute("role", "button");
1646
+ el.setAttribute("tabindex", "0");
1647
+ }
1648
+ el.appendChild(document.createTextNode(label));
1649
+ const inputElements = [];
1650
+ if (hasInputs) {
1651
+ const inputsContainer = document.createElement("span");
1652
+ inputsContainer.className = "blork-tabs-tag-inputs";
1653
+ for (const inputConfig of inputs) {
1654
+ if (inputConfig.label) {
1655
+ const labelSpan = document.createElement("span");
1656
+ labelSpan.textContent = inputConfig.label;
1657
+ inputsContainer.appendChild(labelSpan);
1658
+ }
1659
+ if (inputConfig.type === "select") {
1660
+ const select = document.createElement("select");
1661
+ select.className = "blork-tabs-tag-select";
1662
+ for (const opt of inputConfig.options ?? []) {
1663
+ const option = document.createElement("option");
1664
+ option.value = opt.value;
1665
+ option.textContent = opt.label;
1666
+ select.appendChild(option);
1667
+ }
1668
+ select.addEventListener("click", (e) => e.stopPropagation());
1669
+ select.addEventListener("mousedown", (e) => e.stopPropagation());
1670
+ inputsContainer.appendChild(select);
1671
+ inputElements.push(select);
1672
+ } else {
1673
+ const input = document.createElement("input");
1674
+ input.type = "number";
1675
+ input.className = "blork-tabs-tag-input";
1676
+ if (inputConfig.defaultValue !== void 0) input.value = String(inputConfig.defaultValue);
1677
+ if (inputConfig.step !== void 0) input.step = String(inputConfig.step);
1678
+ if (inputConfig.min !== void 0) input.min = String(inputConfig.min);
1679
+ if (inputConfig.max !== void 0) input.max = String(inputConfig.max);
1680
+ input.addEventListener("click", (e) => e.stopPropagation());
1681
+ inputsContainer.appendChild(input);
1682
+ inputElements.push(input);
1683
+ }
1684
+ }
1685
+ el.appendChild(inputsContainer);
1686
+ }
1687
+ let active = defaultActive;
1688
+ if (active) el.classList.add("active");
1689
+ const setActive = (value) => {
1690
+ active = value;
1691
+ el.classList.toggle("active", active);
1692
+ onChange?.(active);
1693
+ };
1694
+ el.addEventListener("click", () => setActive(!active));
1695
+ el.addEventListener("keydown", (e) => {
1696
+ const ke = e;
1697
+ if (ke.key === "Enter" || ke.key === " ") {
1698
+ ke.preventDefault();
1699
+ setActive(!active);
1700
+ }
1701
+ });
1702
+ return {
1703
+ element: el,
1704
+ isActive: () => active,
1705
+ setActive,
1706
+ toggle: () => setActive(!active),
1707
+ getValue: (index) => inputElements[index]?.value ?? "",
1708
+ getInput: (index) => inputElements[index]
1709
+ };
1710
+ }
1636
1711
  // Annotate the CommonJS export names for ESM import in node:
1637
1712
  0 && (module.exports = {
1638
1713
  AnchorManager,
@@ -1647,6 +1722,7 @@ var TabManager = class {
1647
1722
  createPanelElement,
1648
1723
  createPanelState,
1649
1724
  createPresetAnchor,
1725
+ createTagButton,
1650
1726
  detachFromGroup,
1651
1727
  findSnapTarget,
1652
1728
  getConnectedGroup,
package/dist/index.d.cts CHANGED
@@ -823,6 +823,67 @@ interface HoverEnlargeConfig {
823
823
  */
824
824
  declare function setupHoverEnlarge(config: HoverEnlargeConfig): void;
825
825
 
826
+ /**
827
+ * Tag button: a toggleable pill-shaped button that optionally reveals
828
+ * inline inputs (numbers, selects) when active.
829
+ *
830
+ * @example
831
+ * ```typescript
832
+ * // Simple toggle
833
+ * const staticBtn = createTagButton('static');
834
+ * container.appendChild(staticBtn.element);
835
+ *
836
+ * // Toggle with parameterized inputs
837
+ * const govBtn = createTagButton('gravity_override', {
838
+ * inputs: [
839
+ * { label: 'x', defaultValue: 0, step: 0.1 },
840
+ * { label: 'y', defaultValue: -1, step: 0.1 },
841
+ * ],
842
+ * });
843
+ * container.appendChild(govBtn.element);
844
+ *
845
+ * // Read state when needed
846
+ * if (govBtn.isActive()) {
847
+ * const gx = parseFloat(govBtn.getValue(0));
848
+ * const gy = parseFloat(govBtn.getValue(1));
849
+ * }
850
+ * ```
851
+ */
852
+ interface TagButtonNumberInputConfig {
853
+ type?: 'number';
854
+ /** Short label rendered before the input (e.g. 'x', 'y') */
855
+ label?: string;
856
+ defaultValue?: number;
857
+ step?: number;
858
+ min?: number;
859
+ max?: number;
860
+ }
861
+ interface TagButtonSelectInputConfig {
862
+ type: 'select';
863
+ label?: string;
864
+ options?: Array<{
865
+ value: string;
866
+ label: string;
867
+ }>;
868
+ }
869
+ type TagButtonInputConfig = TagButtonNumberInputConfig | TagButtonSelectInputConfig;
870
+ interface TagButtonConfig {
871
+ defaultActive?: boolean;
872
+ inputs?: TagButtonInputConfig[];
873
+ onChange?: (active: boolean) => void;
874
+ }
875
+ interface TagButton {
876
+ element: HTMLElement;
877
+ isActive(): boolean;
878
+ setActive(active: boolean): void;
879
+ toggle(): void;
880
+ /** Get the string value of the input at position index */
881
+ getValue(index: number): string;
882
+ /** Direct access to the underlying input or select element */
883
+ getInput(index: number): HTMLInputElement | HTMLSelectElement | undefined;
884
+ }
885
+ declare function createTagButton(label: string, config?: TagButtonConfig): TagButton;
886
+
826
887
  /**
827
888
  * @blorkfield/blork-tabs - Panel
828
889
  * Individual panel component with collapse/expand functionality
@@ -955,4 +1016,4 @@ declare function getMovingGroupRespectingPins(grabbedPanel: PanelState, panels:
955
1016
  */
956
1017
  declare function unsnap(leftPanel: PanelState, rightPanel: PanelState): void;
957
1018
 
958
- export { type AnchorConfig, AnchorManager, type AnchorPreset, type AnchorSnapEvent, type AnchorSnapResult, type AnchorState, type AutoHideCallbacks, AutoHideManager, type Bounds, type CSSClasses, type DebugLog, type DebugLogConfig, type DebugLogLevel, type DebugPanel, type DebugPanelConfig, type DragEndEvent, DragManager, type DragMode, type DragMoveEvent, type DragStartEvent, type DragState, type EventListener, type PanelAddedEvent, type PanelCollapseEvent, type PanelConfig, type PanelDetachedEvent, type PanelHideEvent, type PanelPinEvent, type PanelRemovedEvent, type PanelShowEvent, type PanelSnapEvent, type PanelState, type Position, type ResolvedTabManagerConfig, SnapPreview, type SnapSide, type SnapTarget, TabManager, type TabManagerConfig, type TabManagerEvents, areInSameChain, createDebugLog, createDebugPanelContent, createDebugPanelInterface, createPanelElement, createPanelState, createPresetAnchor, detachFromGroup, findSnapTarget, getConnectedGroup, getDefaultAnchorConfigs, getDefaultZIndex, getDragZIndex, getLeftmostPanel, getMovingGroupRespectingPins, getPanelDimensions, getPanelPosition, getRightmostPanel, hidePanel, setPanelPosition, setPanelZIndex, setupHoverEnlarge, showPanel, snapPanels, snapPanelsToTarget, toggleCollapse, togglePin, unsnap, updateSnappedPositions };
1019
+ export { type AnchorConfig, AnchorManager, type AnchorPreset, type AnchorSnapEvent, type AnchorSnapResult, type AnchorState, type AutoHideCallbacks, AutoHideManager, type Bounds, type CSSClasses, type DebugLog, type DebugLogConfig, type DebugLogLevel, type DebugPanel, type DebugPanelConfig, type DragEndEvent, DragManager, type DragMode, type DragMoveEvent, type DragStartEvent, type DragState, type EventListener, type PanelAddedEvent, type PanelCollapseEvent, type PanelConfig, type PanelDetachedEvent, type PanelHideEvent, type PanelPinEvent, type PanelRemovedEvent, type PanelShowEvent, type PanelSnapEvent, type PanelState, type Position, type ResolvedTabManagerConfig, SnapPreview, type SnapSide, type SnapTarget, TabManager, type TabManagerConfig, type TabManagerEvents, type TagButton, type TagButtonConfig, type TagButtonInputConfig, type TagButtonNumberInputConfig, type TagButtonSelectInputConfig, areInSameChain, createDebugLog, createDebugPanelContent, createDebugPanelInterface, createPanelElement, createPanelState, createPresetAnchor, createTagButton, detachFromGroup, findSnapTarget, getConnectedGroup, getDefaultAnchorConfigs, getDefaultZIndex, getDragZIndex, getLeftmostPanel, getMovingGroupRespectingPins, getPanelDimensions, getPanelPosition, getRightmostPanel, hidePanel, setPanelPosition, setPanelZIndex, setupHoverEnlarge, showPanel, snapPanels, snapPanelsToTarget, toggleCollapse, togglePin, unsnap, updateSnappedPositions };
package/dist/index.d.ts CHANGED
@@ -823,6 +823,67 @@ interface HoverEnlargeConfig {
823
823
  */
824
824
  declare function setupHoverEnlarge(config: HoverEnlargeConfig): void;
825
825
 
826
+ /**
827
+ * Tag button: a toggleable pill-shaped button that optionally reveals
828
+ * inline inputs (numbers, selects) when active.
829
+ *
830
+ * @example
831
+ * ```typescript
832
+ * // Simple toggle
833
+ * const staticBtn = createTagButton('static');
834
+ * container.appendChild(staticBtn.element);
835
+ *
836
+ * // Toggle with parameterized inputs
837
+ * const govBtn = createTagButton('gravity_override', {
838
+ * inputs: [
839
+ * { label: 'x', defaultValue: 0, step: 0.1 },
840
+ * { label: 'y', defaultValue: -1, step: 0.1 },
841
+ * ],
842
+ * });
843
+ * container.appendChild(govBtn.element);
844
+ *
845
+ * // Read state when needed
846
+ * if (govBtn.isActive()) {
847
+ * const gx = parseFloat(govBtn.getValue(0));
848
+ * const gy = parseFloat(govBtn.getValue(1));
849
+ * }
850
+ * ```
851
+ */
852
+ interface TagButtonNumberInputConfig {
853
+ type?: 'number';
854
+ /** Short label rendered before the input (e.g. 'x', 'y') */
855
+ label?: string;
856
+ defaultValue?: number;
857
+ step?: number;
858
+ min?: number;
859
+ max?: number;
860
+ }
861
+ interface TagButtonSelectInputConfig {
862
+ type: 'select';
863
+ label?: string;
864
+ options?: Array<{
865
+ value: string;
866
+ label: string;
867
+ }>;
868
+ }
869
+ type TagButtonInputConfig = TagButtonNumberInputConfig | TagButtonSelectInputConfig;
870
+ interface TagButtonConfig {
871
+ defaultActive?: boolean;
872
+ inputs?: TagButtonInputConfig[];
873
+ onChange?: (active: boolean) => void;
874
+ }
875
+ interface TagButton {
876
+ element: HTMLElement;
877
+ isActive(): boolean;
878
+ setActive(active: boolean): void;
879
+ toggle(): void;
880
+ /** Get the string value of the input at position index */
881
+ getValue(index: number): string;
882
+ /** Direct access to the underlying input or select element */
883
+ getInput(index: number): HTMLInputElement | HTMLSelectElement | undefined;
884
+ }
885
+ declare function createTagButton(label: string, config?: TagButtonConfig): TagButton;
886
+
826
887
  /**
827
888
  * @blorkfield/blork-tabs - Panel
828
889
  * Individual panel component with collapse/expand functionality
@@ -955,4 +1016,4 @@ declare function getMovingGroupRespectingPins(grabbedPanel: PanelState, panels:
955
1016
  */
956
1017
  declare function unsnap(leftPanel: PanelState, rightPanel: PanelState): void;
957
1018
 
958
- export { type AnchorConfig, AnchorManager, type AnchorPreset, type AnchorSnapEvent, type AnchorSnapResult, type AnchorState, type AutoHideCallbacks, AutoHideManager, type Bounds, type CSSClasses, type DebugLog, type DebugLogConfig, type DebugLogLevel, type DebugPanel, type DebugPanelConfig, type DragEndEvent, DragManager, type DragMode, type DragMoveEvent, type DragStartEvent, type DragState, type EventListener, type PanelAddedEvent, type PanelCollapseEvent, type PanelConfig, type PanelDetachedEvent, type PanelHideEvent, type PanelPinEvent, type PanelRemovedEvent, type PanelShowEvent, type PanelSnapEvent, type PanelState, type Position, type ResolvedTabManagerConfig, SnapPreview, type SnapSide, type SnapTarget, TabManager, type TabManagerConfig, type TabManagerEvents, areInSameChain, createDebugLog, createDebugPanelContent, createDebugPanelInterface, createPanelElement, createPanelState, createPresetAnchor, detachFromGroup, findSnapTarget, getConnectedGroup, getDefaultAnchorConfigs, getDefaultZIndex, getDragZIndex, getLeftmostPanel, getMovingGroupRespectingPins, getPanelDimensions, getPanelPosition, getRightmostPanel, hidePanel, setPanelPosition, setPanelZIndex, setupHoverEnlarge, showPanel, snapPanels, snapPanelsToTarget, toggleCollapse, togglePin, unsnap, updateSnappedPositions };
1019
+ export { type AnchorConfig, AnchorManager, type AnchorPreset, type AnchorSnapEvent, type AnchorSnapResult, type AnchorState, type AutoHideCallbacks, AutoHideManager, type Bounds, type CSSClasses, type DebugLog, type DebugLogConfig, type DebugLogLevel, type DebugPanel, type DebugPanelConfig, type DragEndEvent, DragManager, type DragMode, type DragMoveEvent, type DragStartEvent, type DragState, type EventListener, type PanelAddedEvent, type PanelCollapseEvent, type PanelConfig, type PanelDetachedEvent, type PanelHideEvent, type PanelPinEvent, type PanelRemovedEvent, type PanelShowEvent, type PanelSnapEvent, type PanelState, type Position, type ResolvedTabManagerConfig, SnapPreview, type SnapSide, type SnapTarget, TabManager, type TabManagerConfig, type TabManagerEvents, type TagButton, type TagButtonConfig, type TagButtonInputConfig, type TagButtonNumberInputConfig, type TagButtonSelectInputConfig, areInSameChain, createDebugLog, createDebugPanelContent, createDebugPanelInterface, createPanelElement, createPanelState, createPresetAnchor, createTagButton, detachFromGroup, findSnapTarget, getConnectedGroup, getDefaultAnchorConfigs, getDefaultZIndex, getDragZIndex, getLeftmostPanel, getMovingGroupRespectingPins, getPanelDimensions, getPanelPosition, getRightmostPanel, hidePanel, setPanelPosition, setPanelZIndex, setupHoverEnlarge, showPanel, snapPanels, snapPanelsToTarget, toggleCollapse, togglePin, unsnap, updateSnappedPositions };
package/dist/index.js CHANGED
@@ -1574,6 +1574,80 @@ var TabManager = class {
1574
1574
  this.debugPanelElements.clear();
1575
1575
  }
1576
1576
  };
1577
+
1578
+ // src/TagButton.ts
1579
+ function createTagButton(label, config = {}) {
1580
+ const { defaultActive = false, inputs = [], onChange } = config;
1581
+ const hasInputs = inputs.length > 0;
1582
+ const el = document.createElement(hasInputs ? "div" : "button");
1583
+ el.className = "blork-tabs-tag-btn";
1584
+ if (hasInputs) {
1585
+ el.setAttribute("role", "button");
1586
+ el.setAttribute("tabindex", "0");
1587
+ }
1588
+ el.appendChild(document.createTextNode(label));
1589
+ const inputElements = [];
1590
+ if (hasInputs) {
1591
+ const inputsContainer = document.createElement("span");
1592
+ inputsContainer.className = "blork-tabs-tag-inputs";
1593
+ for (const inputConfig of inputs) {
1594
+ if (inputConfig.label) {
1595
+ const labelSpan = document.createElement("span");
1596
+ labelSpan.textContent = inputConfig.label;
1597
+ inputsContainer.appendChild(labelSpan);
1598
+ }
1599
+ if (inputConfig.type === "select") {
1600
+ const select = document.createElement("select");
1601
+ select.className = "blork-tabs-tag-select";
1602
+ for (const opt of inputConfig.options ?? []) {
1603
+ const option = document.createElement("option");
1604
+ option.value = opt.value;
1605
+ option.textContent = opt.label;
1606
+ select.appendChild(option);
1607
+ }
1608
+ select.addEventListener("click", (e) => e.stopPropagation());
1609
+ select.addEventListener("mousedown", (e) => e.stopPropagation());
1610
+ inputsContainer.appendChild(select);
1611
+ inputElements.push(select);
1612
+ } else {
1613
+ const input = document.createElement("input");
1614
+ input.type = "number";
1615
+ input.className = "blork-tabs-tag-input";
1616
+ if (inputConfig.defaultValue !== void 0) input.value = String(inputConfig.defaultValue);
1617
+ if (inputConfig.step !== void 0) input.step = String(inputConfig.step);
1618
+ if (inputConfig.min !== void 0) input.min = String(inputConfig.min);
1619
+ if (inputConfig.max !== void 0) input.max = String(inputConfig.max);
1620
+ input.addEventListener("click", (e) => e.stopPropagation());
1621
+ inputsContainer.appendChild(input);
1622
+ inputElements.push(input);
1623
+ }
1624
+ }
1625
+ el.appendChild(inputsContainer);
1626
+ }
1627
+ let active = defaultActive;
1628
+ if (active) el.classList.add("active");
1629
+ const setActive = (value) => {
1630
+ active = value;
1631
+ el.classList.toggle("active", active);
1632
+ onChange?.(active);
1633
+ };
1634
+ el.addEventListener("click", () => setActive(!active));
1635
+ el.addEventListener("keydown", (e) => {
1636
+ const ke = e;
1637
+ if (ke.key === "Enter" || ke.key === " ") {
1638
+ ke.preventDefault();
1639
+ setActive(!active);
1640
+ }
1641
+ });
1642
+ return {
1643
+ element: el,
1644
+ isActive: () => active,
1645
+ setActive,
1646
+ toggle: () => setActive(!active),
1647
+ getValue: (index) => inputElements[index]?.value ?? "",
1648
+ getInput: (index) => inputElements[index]
1649
+ };
1650
+ }
1577
1651
  export {
1578
1652
  AnchorManager,
1579
1653
  AutoHideManager,
@@ -1587,6 +1661,7 @@ export {
1587
1661
  createPanelElement,
1588
1662
  createPanelState,
1589
1663
  createPresetAnchor,
1664
+ createTagButton,
1590
1665
  detachFromGroup,
1591
1666
  findSnapTarget,
1592
1667
  getConnectedGroup,
package/dist/styles.css CHANGED
@@ -220,6 +220,88 @@
220
220
  transition: none !important;
221
221
  }
222
222
 
223
+ /* Tag Button */
224
+ .blork-tabs-tag-btn {
225
+ padding: 4px 10px;
226
+ background: var(--blork-tabs-panel-bg, #1a1a2e);
227
+ border: 1px solid #3a3a5a;
228
+ border-radius: 4px;
229
+ color: #aaa;
230
+ cursor: pointer;
231
+ font-size: 11px;
232
+ font-family: monospace;
233
+ transition: all 0.15s;
234
+ user-select: none;
235
+ display: inline-flex;
236
+ align-items: center;
237
+ gap: 6px;
238
+ }
239
+
240
+ .blork-tabs-tag-btn:hover {
241
+ border-color: #5a5a7a;
242
+ color: #ccc;
243
+ }
244
+
245
+ .blork-tabs-tag-btn.active {
246
+ border-color: var(--blork-tabs-accent, #4a90d9);
247
+ color: var(--blork-tabs-accent, #4a90d9);
248
+ box-shadow: 0 0 8px rgba(74, 144, 217, 0.5), 0 0 2px rgba(74, 144, 217, 0.8);
249
+ text-shadow: 0 0 8px rgba(74, 144, 217, 0.9);
250
+ }
251
+
252
+ /* Tag inputs (hidden until button is active) */
253
+ .blork-tabs-tag-inputs {
254
+ display: none;
255
+ align-items: center;
256
+ gap: 3px;
257
+ font-size: 10px;
258
+ }
259
+
260
+ .blork-tabs-tag-btn.active .blork-tabs-tag-inputs {
261
+ display: inline-flex;
262
+ }
263
+
264
+ .blork-tabs-tag-input {
265
+ width: 36px;
266
+ background: transparent;
267
+ border: none;
268
+ border-bottom: 1px solid #555;
269
+ color: inherit;
270
+ font-size: 10px;
271
+ font-family: monospace;
272
+ text-align: center;
273
+ padding: 0 2px;
274
+ outline: none;
275
+ -moz-appearance: textfield;
276
+ }
277
+
278
+ .blork-tabs-tag-input::-webkit-outer-spin-button,
279
+ .blork-tabs-tag-input::-webkit-inner-spin-button {
280
+ -webkit-appearance: none;
281
+ margin: 0;
282
+ }
283
+
284
+ .blork-tabs-tag-btn.active .blork-tabs-tag-input {
285
+ border-bottom-color: rgba(74, 144, 217, 0.6);
286
+ }
287
+
288
+ .blork-tabs-tag-select {
289
+ background: var(--blork-tabs-panel-bg, #1a1a2e);
290
+ border: none;
291
+ border-bottom: 1px solid #555;
292
+ color: #ccc;
293
+ font-size: 10px;
294
+ font-family: monospace;
295
+ outline: none;
296
+ cursor: pointer;
297
+ max-width: 80px;
298
+ }
299
+
300
+ .blork-tabs-tag-btn.active .blork-tabs-tag-select {
301
+ border-bottom-color: rgba(74, 144, 217, 0.6);
302
+ color: var(--blork-tabs-accent, #4a90d9);
303
+ }
304
+
223
305
  /* Debug Panel Log */
224
306
  .blork-tabs-debug-log {
225
307
  min-height: 100px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blorkfield/blork-tabs",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A framework-agnostic tab/panel management system with snapping and docking",
5
5
  "packageManager": "pnpm@10.28.2",
6
6
  "type": "module",
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Tag button: a toggleable pill-shaped button that optionally reveals
3
+ * inline inputs (numbers, selects) when active.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * // Simple toggle
8
+ * const staticBtn = createTagButton('static');
9
+ * container.appendChild(staticBtn.element);
10
+ *
11
+ * // Toggle with parameterized inputs
12
+ * const govBtn = createTagButton('gravity_override', {
13
+ * inputs: [
14
+ * { label: 'x', defaultValue: 0, step: 0.1 },
15
+ * { label: 'y', defaultValue: -1, step: 0.1 },
16
+ * ],
17
+ * });
18
+ * container.appendChild(govBtn.element);
19
+ *
20
+ * // Read state when needed
21
+ * if (govBtn.isActive()) {
22
+ * const gx = parseFloat(govBtn.getValue(0));
23
+ * const gy = parseFloat(govBtn.getValue(1));
24
+ * }
25
+ * ```
26
+ */
27
+
28
+ export interface TagButtonNumberInputConfig {
29
+ type?: 'number';
30
+ /** Short label rendered before the input (e.g. 'x', 'y') */
31
+ label?: string;
32
+ defaultValue?: number;
33
+ step?: number;
34
+ min?: number;
35
+ max?: number;
36
+ }
37
+
38
+ export interface TagButtonSelectInputConfig {
39
+ type: 'select';
40
+ label?: string;
41
+ options?: Array<{ value: string; label: string }>;
42
+ }
43
+
44
+ export type TagButtonInputConfig = TagButtonNumberInputConfig | TagButtonSelectInputConfig;
45
+
46
+ export interface TagButtonConfig {
47
+ defaultActive?: boolean;
48
+ inputs?: TagButtonInputConfig[];
49
+ onChange?: (active: boolean) => void;
50
+ }
51
+
52
+ export interface TagButton {
53
+ element: HTMLElement;
54
+ isActive(): boolean;
55
+ setActive(active: boolean): void;
56
+ toggle(): void;
57
+ /** Get the string value of the input at position index */
58
+ getValue(index: number): string;
59
+ /** Direct access to the underlying input or select element */
60
+ getInput(index: number): HTMLInputElement | HTMLSelectElement | undefined;
61
+ }
62
+
63
+ export function createTagButton(label: string, config: TagButtonConfig = {}): TagButton {
64
+ const { defaultActive = false, inputs = [], onChange } = config;
65
+
66
+ const hasInputs = inputs.length > 0;
67
+ const el = document.createElement(hasInputs ? 'div' : 'button') as HTMLElement;
68
+ el.className = 'blork-tabs-tag-btn';
69
+ if (hasInputs) {
70
+ el.setAttribute('role', 'button');
71
+ el.setAttribute('tabindex', '0');
72
+ }
73
+
74
+ el.appendChild(document.createTextNode(label));
75
+
76
+ const inputElements: Array<HTMLInputElement | HTMLSelectElement> = [];
77
+
78
+ if (hasInputs) {
79
+ const inputsContainer = document.createElement('span');
80
+ inputsContainer.className = 'blork-tabs-tag-inputs';
81
+
82
+ for (const inputConfig of inputs) {
83
+ if (inputConfig.label) {
84
+ const labelSpan = document.createElement('span');
85
+ labelSpan.textContent = inputConfig.label;
86
+ inputsContainer.appendChild(labelSpan);
87
+ }
88
+
89
+ if (inputConfig.type === 'select') {
90
+ const select = document.createElement('select');
91
+ select.className = 'blork-tabs-tag-select';
92
+ for (const opt of (inputConfig.options ?? [])) {
93
+ const option = document.createElement('option');
94
+ option.value = opt.value;
95
+ option.textContent = opt.label;
96
+ select.appendChild(option);
97
+ }
98
+ select.addEventListener('click', e => e.stopPropagation());
99
+ select.addEventListener('mousedown', e => e.stopPropagation());
100
+ inputsContainer.appendChild(select);
101
+ inputElements.push(select);
102
+ } else {
103
+ const input = document.createElement('input');
104
+ input.type = 'number';
105
+ input.className = 'blork-tabs-tag-input';
106
+ if (inputConfig.defaultValue !== undefined) input.value = String(inputConfig.defaultValue);
107
+ if (inputConfig.step !== undefined) input.step = String(inputConfig.step);
108
+ if (inputConfig.min !== undefined) input.min = String(inputConfig.min);
109
+ if (inputConfig.max !== undefined) input.max = String(inputConfig.max);
110
+ input.addEventListener('click', e => e.stopPropagation());
111
+ inputsContainer.appendChild(input);
112
+ inputElements.push(input);
113
+ }
114
+ }
115
+
116
+ el.appendChild(inputsContainer);
117
+ }
118
+
119
+ let active = defaultActive;
120
+ if (active) el.classList.add('active');
121
+
122
+ const setActive = (value: boolean) => {
123
+ active = value;
124
+ el.classList.toggle('active', active);
125
+ onChange?.(active);
126
+ };
127
+
128
+ el.addEventListener('click', () => setActive(!active));
129
+ el.addEventListener('keydown', (e: Event) => {
130
+ const ke = e as KeyboardEvent;
131
+ if (ke.key === 'Enter' || ke.key === ' ') {
132
+ ke.preventDefault();
133
+ setActive(!active);
134
+ }
135
+ });
136
+
137
+ return {
138
+ element: el,
139
+ isActive: () => active,
140
+ setActive,
141
+ toggle: () => setActive(!active),
142
+ getValue: (index: number) => inputElements[index]?.value ?? '',
143
+ getInput: (index: number) => inputElements[index],
144
+ };
145
+ }
package/src/index.ts CHANGED
@@ -43,6 +43,8 @@ export { DragManager } from './DragManager';
43
43
  export { SnapPreview } from './SnapPreview';
44
44
  export { AutoHideManager } from './AutoHideManager';
45
45
  export { createDebugPanelContent, createDebugPanelInterface, createDebugLog, setupHoverEnlarge } from './DebugPanel';
46
+ export { createTagButton } from './TagButton';
47
+ export type { TagButtonConfig, TagButtonInputConfig, TagButtonNumberInputConfig, TagButtonSelectInputConfig, TagButton } from './TagButton';
46
48
  export type { AutoHideCallbacks } from './AutoHideManager';
47
49
  export {
48
50
  createPanelElement,