public function makeOptionList($items, callable $optionArgs, array &$valuesRendered = [])
	{
		$ret = [];
		foreach ($items as $value => $caption) {
			if (is_int($value)) {
				$value = (string)$value;
			}

			if (is_array($caption)) {
				// subgroup
				$option = Html::el('optgroup', ['label' => $value]);

				// options within the group
				$nested = $this->makeOptionList($caption, $optionArgs, $valuesRendered);

				foreach ($nested as $item) {
					$option->addHtml($item);
				}
			}
			else {
				if (in_array($value, $valuesRendered)) {
					throw new InvalidArgumentException("Value '$value' is used multiple times.");
				}
				$valuesRendered[] = $value;

				// normal option
				$option = Html::el('option',
					array_merge(['value' => (string)$value], $optionArgs($value, $caption)));
				$option->setText($caption);
			}
			$ret[] = $option;
		}

		return $ret;
	}