# Adaptive Stepper

A numeric HMI stepper with bounds, units and adaptive increments.

## Installation

Download: /api/catalog/adaptive-stepper/download

Ignition Perspective 8.3. In a development project, choose Designer > File > Import and select the ZIP. Preserve the view and style-class paths. Review resource conflicts before replacing anything.

Append stylesheet-snippet.css to Perspective Styles > stylesheet.css (enable Advanced Stylesheet). Preserve existing CSS. This supporting file is not imported automatically.

Add an Embedded View (ia.display.view), set props.path to Viewcraft/Components/UI/AdaptiveStepper/AdaptiveStepper, and configure props.params using the contract below. Connect your own data in the host view. The package does not include live tags, page configuration or Gateway settings.

## Parameters

Types are inferred from saved defaults; directions come from native propConfig. These are not full schemas or validated ranges.

| Parameter | Type | Direction | Default |

| --- | --- | --- | --- |

| value | number | inout | 1450 |

| min | number | input | 0 |

| max | number | input | 3000 |

| step | number | input | 10 |

| unit | string | input | "rpm" |

| label | string | input | "SP Bomba-01" |

| disabled | boolean | input | false |

| reducedMotion | boolean | input | false |

| showLabel | boolean | input | true |

## Embedded View usage

```json

{
  "type": "ia.display.view",
  "props": {
    "path": "Viewcraft/Components/UI/AdaptiveStepper/AdaptiveStepper",
    "params": {
      "value": 1450,
      "min": 0,
      "max": 3000,
      "step": 10,
      "unit": "rpm",
      "label": "SP Bomba-01",
      "disabled": false,
      "reducedMotion": false,
      "showLabel": true
    },
    "useDefaultViewWidth": false,
    "useDefaultViewHeight": false
  }
}

```

## Verification

Source and archive hashes are checked. Designer import acceptance has not been completed for this archive; test in your development project before production.

## Files

### com.inductiveautomation.perspective/views/Viewcraft/Components/UI/AdaptiveStepper/AdaptiveStepper/view.json

```json

{
  "custom": {},
  "params": {
    "value": 1450,
    "min": 0,
    "max": 3000,
    "step": 10,
    "unit": "rpm",
    "label": "SP Bomba-01",
    "disabled": false,
    "reducedMotion": false,
    "showLabel": true
  },
  "propConfig": {
    "params.value": {
      "paramDirection": "inout",
      "persistent": true
    },
    "params.min": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.max": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.step": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.unit": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.label": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.disabled": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.reducedMotion": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.showLabel": {
      "paramDirection": "input",
      "persistent": true
    }
  },
  "props": {
    "defaultSize": {
      "width": 280,
      "height": 64
    }
  },
  "root": {
    "type": "ia.container.flex",
    "version": 0,
    "meta": {
      "name": "AdaptiveRoot"
    },
    "props": {
      "direction": "column",
      "justify": "flex-start",
      "alignItems": "stretch",
      "style": {
        "classes": "vc/stepper-adaptive-shell",
        "gap": "6px"
      }
    },
    "propConfig": {
      "meta.visible": {
        "binding": {
          "type": "expr",
          "config": {
            "expression": "true"
          }
        }
      }
    },
    "children": [
      {
        "type": "ia.display.label",
        "version": 0,
        "meta": {
          "name": "Caption"
        },
        "position": {
          "grow": 0,
          "shrink": 0,
          "basis": "auto"
        },
        "props": {
          "text": "SP Bomba-01",
          "align": "left",
          "style": {
            "classes": "vc/stepper-adaptive-meta"
          }
        },
        "propConfig": {
          "props.text": {
            "binding": {
              "type": "property",
              "config": {
                "path": "view.params.label"
              }
            }
          },
          "meta.visible": {
            "binding": {
              "type": "expr",
              "config": {
                "expression": "coalesce({view.params.showLabel}, true) && len(coalesce({view.params.label}, '')) > 0"
              }
            }
          }
        }
      },
      {
        "type": "ia.container.flex",
        "version": 0,
        "meta": {
          "name": "Row"
        },
        "position": {
          "grow": 0,
          "shrink": 0,
          "basis": "auto"
        },
        "props": {
          "direction": "row",
          "justify": "flex-start",
          "alignItems": "center",
          "style": {
            "classes": "vc/stepper-adaptive",
            "gap": "8px",
            "width": "auto",
            "alignSelf": "flex-start"
          }
        },
        "children": [
          {
            "type": "ia.display.label",
            "version": 0,
            "meta": {
              "name": "DecBtn"
            },
            "position": {
              "grow": 0,
              "shrink": 0,
              "basis": "36px"
            },
            "props": {
              "text": "−",
              "align": "center",
              "style": {
                "classes": "vc/stepper-adaptive-btn",
                "padding": "0px",
                "margin": "0px"
              }
            },
            "events": {
              "dom": {
                "onClick": {
                  "type": "script",
                  "scope": "G",
                  "config": {
                    "script": "\tif self.view.params.disabled:\n\t\treturn\n\ttry:\n\t\tv = float(self.view.params.value)\n\texcept:\n\t\tv = 0.0\n\ttry:\n\t\tstep = float(self.view.params.step)\n\texcept:\n\t\tstep = 1.0\n\ttry:\n\t\tmn = float(self.view.params.min)\n\texcept:\n\t\tmn = 0.0\n\tnv = v - step\n\tif nv < mn:\n\t\tnv = mn\n\tself.view.params.value = nv\n"
                  }
                }
              }
            },
            "propConfig": {
              "props.style.classes": {
                "binding": {
                  "type": "expr",
                  "config": {
                    "expression": "if(coalesce({view.params.disabled}, false) || ({view.params.value} <= {view.params.min}), \"vc/stepper-adaptive-btn vc/stepper-adaptive-btn-disabled\", \"vc/stepper-adaptive-btn\")"
                  }
                }
              }
            }
          },
          {
            "type": "ia.display.label",
            "version": 0,
            "meta": {
              "name": "Value"
            },
            "position": {
              "grow": 0,
              "shrink": 0,
              "basis": "120px"
            },
            "props": {
              "text": "1450 rpm",
              "align": "center",
              "style": {
                "classes": "vc/stepper-adaptive-value",
                "padding": "0px",
                "margin": "0px"
              }
            },
            "propConfig": {
              "props.text": {
                "binding": {
                  "type": "expr",
                  "config": {
                    "expression": "toStr({view.params.value}) + if(len(coalesce({view.params.unit}, '')) > 0, ' ' + {view.params.unit}, '')"
                  }
                }
              },
              "props.style.classes": {
                "binding": {
                  "type": "expr",
                  "config": {
                    "expression": "if(({view.params.value} < {view.params.min}) || ({view.params.value} > {view.params.max}), \"vc/stepper-adaptive-value vc/stepper-adaptive-value-warn\", \"vc/stepper-adaptive-value\")"
                  }
                }
              }
            }
          },
          {
            "type": "ia.display.label",
            "version": 0,
            "meta": {
              "name": "IncBtn"
            },
            "position": {
              "grow": 0,
              "shrink": 0,
              "basis": "36px"
            },
            "props": {
              "text": "+",
              "align": "center",
              "style": {
                "classes": "vc/stepper-adaptive-btn",
                "padding": "0px",
                "margin": "0px"
              }
            },
            "events": {
              "dom": {
                "onClick": {
                  "type": "script",
                  "scope": "G",
                  "config": {
                    "script": "\tif self.view.params.disabled:\n\t\treturn\n\ttry:\n\t\tv = float(self.view.params.value)\n\texcept:\n\t\tv = 0.0\n\ttry:\n\t\tstep = float(self.view.params.step)\n\texcept:\n\t\tstep = 1.0\n\ttry:\n\t\tmx = float(self.view.params.max)\n\texcept:\n\t\tmx = 100.0\n\tnv = v + step\n\tif nv > mx:\n\t\tnv = mx\n\tself.view.params.value = nv\n"
                  }
                }
              }
            },
            "propConfig": {
              "props.style.classes": {
                "binding": {
                  "type": "expr",
                  "config": {
                    "expression": "if(coalesce({view.params.disabled}, false) || ({view.params.value} >= {view.params.max}), \"vc/stepper-adaptive-btn vc/stepper-adaptive-btn-disabled\", \"vc/stepper-adaptive-btn\")"
                  }
                }
              }
            }
          }
        ]
      }
    ]
  }
}


```

### com.inductiveautomation.perspective/views/Viewcraft/Components/UI/AdaptiveStepper/AdaptiveStepper/resource.json

```json

{
  "scope": "G",
  "version": 1,
  "restricted": false,
  "overridable": true,
  "files": [
    "view.json"
  ]
}

```

### stylesheet-snippet.css

```css


[class~="psc-vc/stepper-adaptive"], .psc-vc\/stepper-adaptive {
  display: flex !important;
  flex-direction: row !important;
  align-items: center !important;
  gap: 8px !important;
  box-sizing: border-box !important;
  min-width: 0 !important;
}
[class~="psc-vc/stepper-adaptive-btn"], .psc-vc\/stepper-adaptive-btn {
  width: 36px !important;
  height: 36px !important;
  min-width: 36px !important;
  min-height: 36px !important;
  line-height: 36px !important;
  text-align: center !important;
  border-radius: 8px !important;
  border: 1px solid var(--vc-border, #3f3f46) !important;
  background-color: var(--vc-secondary, #27272a) !important;
  color: var(--vc-foreground, #fafafa) !important;
  font-size: 18px !important;
  font-weight: 500 !important;
  cursor: pointer !important;
  user-select: none !important;
  box-sizing: border-box !important;
  padding: 0 !important;
}
[class~="psc-vc/stepper-adaptive-btn"]:hover, .psc-vc\/stepper-adaptive-btn:hover {
  background-color: var(--vc-panel, #18181b) !important;
}
[class~="psc-vc/stepper-adaptive-btn-disabled"], .psc-vc\/stepper-adaptive-btn-disabled {
  opacity: 0.5 !important;
  cursor: not-allowed !important;
  pointer-events: none !important;
}
[class~="psc-vc/stepper-adaptive-value"], .psc-vc\/stepper-adaptive-value {
  min-width: 96px !important;
  height: 36px !important;
  line-height: 36px !important;
  text-align: center !important;
  border-radius: 8px !important;
  border: 1px solid var(--vc-border, #3f3f46) !important;
  background-color: var(--vc-panel, #171717) !important;
  color: var(--vc-foreground, #fafafa) !important;
  font-size: 15px !important;
  font-weight: 600 !important;
  font-variant-numeric: tabular-nums !important;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
  box-sizing: border-box !important;
  padding: 0 12px !important;
}
[class~="psc-vc/stepper-adaptive-value-warn"], .psc-vc\/stepper-adaptive-value-warn {
  border-color: hsl(38 92% 50%) !important;
}
[class~="psc-vc/stepper-adaptive-meta"], .psc-vc\/stepper-adaptive-meta {
  color: var(--vc-muted-fg, #a1a1aa) !important;
  font-size: 12px !important;
  font-weight: 500 !important;
}
[class~="psc-vc/stepper-adaptive-shell"], .psc-vc\/stepper-adaptive-shell {
  display: flex !important;
  flex-direction: column !important;
  gap: 6px !important;
  min-width: 0 !important;
}
[class~="psc-vc/lab-hmi-page"] [class~="psc-vc/stepper-adaptive-btn"], .psc-vc\/lab-hmi-page .psc-vc\/stepper-adaptive-btn{
  min-height:44px!important;
  box-sizing:border-box!important;
}
[class~="psc-vc/lab-hmi-page"] [class~="psc-vc/stepper-adaptive-btn"], .psc-vc\/lab-hmi-page .psc-vc\/stepper-adaptive-btn{
  transition:opacity 160ms var(--vc-hmi-ease,cubic-bezier(0.23, 1, 0.32, 1)),background-color 160ms var(--vc-hmi-ease,cubic-bezier(0.23, 1, 0.32, 1)),border-color 160ms var(--vc-hmi-ease,cubic-bezier(0.23, 1, 0.32, 1)),color 160ms var(--vc-hmi-ease,cubic-bezier(0.23, 1, 0.32, 1))!important;
}
@media (prefers-reduced-motion:reduce){
  [class~="psc-vc/lab-hmi-page"] [class~="psc-vc/stepper-adaptive-btn"], .psc-vc\/lab-hmi-page .psc-vc\/stepper-adaptive-btn{
    transition:none!important;
  }
}
[class~="psc-vc/lab-hmi-page"] [class~="psc-vc/stepper-adaptive-btn"], .psc-vc\/lab-hmi-page .psc-vc\/stepper-adaptive-btn{
  min-height:44px!important;
}
[class~="psc-vc/lab-hmi-page"] [class~="psc-vc/stepper-adaptive"], .psc-vc\/lab-hmi-page .psc-vc\/stepper-adaptive{
  align-items:center !important;
  height:36px !important;
  min-height:36px !important;
}
[class~="psc-vc/lab-hmi-page"] [class~="psc-vc/stepper-adaptive-btn"], .psc-vc\/lab-hmi-page .psc-vc\/stepper-adaptive-btn, [class~="psc-vc/lab-hmi-page"] [class~="psc-vc/stepper-adaptive-value"], .psc-vc\/lab-hmi-page .psc-vc\/stepper-adaptive-value{
  height:36px !important;
  min-height:36px !important;
  max-height:36px !important;
  line-height:36px !important;
  box-sizing:border-box !important;
}

```