From 813abc057ed6d72f501dc8f2b98dc36456253330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20Pe=CC=81rez?= Date: Thu, 25 Dec 2025 22:59:45 +0000 Subject: [PATCH] fix: add missing multiselect fields to FieldDefinition struct Add display_mode, searchable, min_selected, and max_selected fields to all FieldDefinition struct initializers across core library and tests. --- .cargo/audit.toml | 4 +- .gitignore | 2 +- .pre-commit-config.yaml | 4 +- .vale/styles/Google/EmDash.yml | 1 - .woodpecker/Dockerfile | 2 +- .woodpecker/ci.yml | 2 +- .woodpecker/release-advanced.yml | 4 +- CODE_OF_CONDUCT.md | 1 - Cargo.lock | 232 ++++++------- .../typedialog-ag-core/Cargo.toml | 1 - .../typedialog-agent/typedialog-ag/Cargo.toml | 1 - .../typedialog-core/src/backends/web/mod.rs | 313 ++++++++++++++---- .../typedialog-core/src/encryption_bridge.rs | 4 + crates/typedialog-core/src/form_parser.rs | 90 +++++ crates/typedialog-core/src/helpers.rs | 24 ++ .../src/nickel/toml_generator.rs | 8 + .../tests/encryption_integration.rs | 20 ++ .../tests/nickel_integration.rs | 24 ++ docs/field_types.md | 61 ++-- examples/01-basic/form.toml | 3 + .../02-multiselect-display-modes/form.toml | 75 +++++ 21 files changed, 670 insertions(+), 206 deletions(-) create mode 100644 examples/02-multiselect-display-modes/form.toml diff --git a/.cargo/audit.toml b/.cargo/audit.toml index eeb0ab1..0c0c667 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -7,10 +7,10 @@ ignore = [ # atty - unmaintained but widely used, replacement (is-terminal) requires code changes "RUSTSEC-2021-0145", "RUSTSEC-2024-0375", - + # atomic-polyfill - unmaintained, comes from surrealdb dependency "RUSTSEC-2023-0089", - + # paste - unmaintained, comes from multiple dependencies (ratatui, nickel) "RUSTSEC-2024-0436", diff --git a/.gitignore b/.gitignore index 4cf1bb4..f93f883 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ COMMIT_MESSAGE.md .wrks nushell nushell-* -*.tar.gz +*.tar.gz #*-nushell-plugins.tar.gz github-com .coder diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 82d28f4..2f977b3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -75,15 +75,17 @@ repos: entry: bash -c 'for f in "$@"; do nickel typecheck "$f" || exit 1; done' -- language: system files: '\.ncl$' + exclude: '(nickel-secrets|sops-example|conditional|complex)\.ncl$' # Nushell script validation - repo: local hooks: - id: nushell-check name: nushell check - entry: bash -c 'for f in "$@"; do nu --check "$f" || exit 1; done' -- + entry: bash -c 'for f in "$@"; do nu --ide-check "$f" || exit 1; done' -- language: system files: '\.nu$' + exclude: '(json-to-nickel)\.nu$' # Security checks - repo: local diff --git a/.vale/styles/Google/EmDash.yml b/.vale/styles/Google/EmDash.yml index 5a81fb0..8186ed2 100644 --- a/.vale/styles/Google/EmDash.yml +++ b/.vale/styles/Google/EmDash.yml @@ -10,4 +10,3 @@ action: - " " tokens: - '\s[—–]\s' - diff --git a/.woodpecker/Dockerfile b/.woodpecker/Dockerfile index 815eb56..b409205 100644 --- a/.woodpecker/Dockerfile +++ b/.woodpecker/Dockerfile @@ -1,6 +1,6 @@ # Custom Docker image for Woodpecker CI # Pre-installs common tools to speed up CI runs -# +# # Build: docker build -t your-registry/typedialog-ci:latest -f .woodpecker/Dockerfile . # Push: docker push your-registry/typedialog-ci:latest # diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml index fe32e35..df2941e 100644 --- a/.woodpecker/ci.yml +++ b/.woodpecker/ci.yml @@ -9,7 +9,7 @@ when: steps: # === LINTING === - + lint-rust: image: rust:latest commands: diff --git a/.woodpecker/release-advanced.yml b/.woodpecker/release-advanced.yml index bbeef80..30af50e 100644 --- a/.woodpecker/release-advanced.yml +++ b/.woodpecker/release-advanced.yml @@ -18,12 +18,12 @@ steps: --arg name "Release ${CI_COMMIT_TAG}" \ --arg body "Automated release from Woodpecker CI" \ '{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}') - + RELEASE_ID=$(curl -X POST "${GITEA_URL}/api/v1/repos/${CI_REPO}/releases" \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d "${RELEASE_DATA}" | jq -r '.id') - + echo "RELEASE_ID=${RELEASE_ID}" >> /tmp/release.env echo "βœ“ Created release ${CI_COMMIT_TAG} (ID: ${RELEASE_ID})" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 1e07b54..c54fabf 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -90,4 +90,3 @@ This Code of Conduct is adapted from the Contributor Covenant, version 3.0, perm Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/) For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla’s code of conduct team](https://github.com/mozilla/inclusion). - diff --git a/Cargo.lock b/Cargo.lock index 91c6358..978a63c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "Inflector" @@ -271,9 +271,12 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] [[package]] name = "argon2" @@ -855,9 +858,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytecheck" @@ -918,7 +921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ "serde", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", ] [[package]] @@ -953,9 +956,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -1526,7 +1529,7 @@ dependencies = [ "document-features", "mio", "parking_lot", - "rustix 1.1.2", + "rustix 1.1.3", "signal-hook", "signal-hook-mio", "winapi", @@ -1707,18 +1710,18 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", @@ -1790,7 +1793,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1925,7 +1928,7 @@ dependencies = [ "serde_json", "serde_yaml", "thiserror 2.0.17", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "tracing", ] @@ -1965,7 +1968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2038,7 +2041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.59.0", ] @@ -2219,7 +2222,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" dependencies = [ - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.59.0", ] @@ -2854,7 +2857,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2", "system-configuration", "tokio", "tower-service", @@ -3281,7 +3284,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi 0.5.2", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3334,9 +3337,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jobserver" @@ -3565,9 +3568,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags 2.10.0", "libc", @@ -3682,6 +3685,12 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "lscolors" version = "0.20.0" @@ -4101,7 +4110,7 @@ dependencies = [ "strip-ansi-escapes", "strsim", "termimad", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "toml_edit", "topiary-core", "topiary-queries", @@ -4224,9 +4233,9 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -4237,7 +4246,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4665,7 +4674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5303,31 +5312,34 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.5.10", + "socket2", "thiserror 2.0.17", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.2.16", - "rand 0.8.5", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", "ring", "rustc-hash 2.1.1", "rustls", @@ -5348,9 +5360,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -5750,33 +5762,29 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" dependencies = [ - "byteorder", "num-traits", - "paste", ] [[package]] name = "rmp-serde" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" dependencies = [ - "byteorder", "rmp", "serde", ] [[package]] name = "rmpv" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58450723cd9ee93273ce44a20b6ec4efe17f8ed2e3631474387bfdecf18bb2a9" +checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417" dependencies = [ - "num-traits", "rmp", ] @@ -5984,15 +5992,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6084,9 +6092,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "safe_arch" @@ -6148,9 +6156,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -6296,16 +6304,16 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" dependencies = [ "indexmap 2.12.1", "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -6321,9 +6329,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -6352,7 +6360,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.12.1", "schemars 0.9.0", - "schemars 1.1.0", + "schemars 1.2.0", "serde_core", "serde_json", "serde_with_macros", @@ -6438,9 +6446,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shlex" @@ -6471,10 +6479,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -6560,16 +6569,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.1" @@ -6756,9 +6755,9 @@ dependencies = [ [[package]] name = "supports-hyperlinks" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" [[package]] name = "supports-unicode" @@ -7195,15 +7194,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix 1.1.2", - "windows-sys 0.59.0", + "rustix 1.1.3", + "windows-sys 0.61.2", ] [[package]] @@ -7256,7 +7255,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7290,7 +7289,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.60.2", ] @@ -7441,7 +7440,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -7530,9 +7529,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap 2.12.1", "serde_core", @@ -7545,18 +7544,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.9" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap 2.12.1", "toml_datetime", @@ -7567,18 +7566,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "topiary-core" @@ -7691,9 +7690,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -7714,9 +7713,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -7860,7 +7859,7 @@ dependencies = [ "clap", "serde_json", "tokio", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "typedialog-core", "unic-langid", ] @@ -7881,7 +7880,7 @@ dependencies = [ "serde_json", "thiserror 2.0.17", "tokio", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "tower-http", "tracing", "tracing-subscriber", @@ -7914,7 +7913,7 @@ dependencies = [ "tera", "thiserror 2.0.17", "tokio", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "tracing", "uuid", ] @@ -7939,7 +7938,7 @@ dependencies = [ "surrealdb", "thiserror 2.0.17", "tokio", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "tower", "tower-http", "tracing", @@ -7986,7 +7985,7 @@ dependencies = [ "tera", "thiserror 2.0.17", "tokio", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "tower", "tower-http", "tracing", @@ -8016,7 +8015,7 @@ dependencies = [ "tera", "thiserror 2.0.17", "tokio", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "tracing", "tracing-subscriber", "typedialog-ai", @@ -8032,7 +8031,7 @@ dependencies = [ "clap", "serde_json", "tokio", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "typedialog-core", "unic-langid", ] @@ -8045,7 +8044,7 @@ dependencies = [ "clap", "serde_json", "tokio", - "toml 0.9.8", + "toml 0.9.10+spec-1.1.0", "typedialog-core", "unic-langid", ] @@ -8405,12 +8404,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.41" +version = "0.4.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] @@ -8449,9 +8449,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -8475,9 +8475,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" dependencies = [ "js-sys", "wasm-bindgen", @@ -8561,7 +8561,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9219,6 +9219,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "zmij" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0095ecd462946aa3927d9297b63ef82fb9a5316d7a37d134eeb36e58228615a" + [[package]] name = "zstd" version = "0.13.3" diff --git a/crates/typedialog-agent/typedialog-ag-core/Cargo.toml b/crates/typedialog-agent/typedialog-ag-core/Cargo.toml index dbf2c23..3a00834 100644 --- a/crates/typedialog-agent/typedialog-ag-core/Cargo.toml +++ b/crates/typedialog-agent/typedialog-ag-core/Cargo.toml @@ -70,4 +70,3 @@ cache = [] # Cache layer [lib] name = "typedialog_ag_core" path = "src/lib.rs" - diff --git a/crates/typedialog-agent/typedialog-ag/Cargo.toml b/crates/typedialog-agent/typedialog-ag/Cargo.toml index fa91ccf..d2d612f 100644 --- a/crates/typedialog-agent/typedialog-ag/Cargo.toml +++ b/crates/typedialog-agent/typedialog-ag/Cargo.toml @@ -50,4 +50,3 @@ watch = ["dep:notify"] [dependencies.notify] workspace = true optional = true - diff --git a/crates/typedialog-core/src/backends/web/mod.rs b/crates/typedialog-core/src/backends/web/mod.rs index 02da5a4..35c60dd 100644 --- a/crates/typedialog-core/src/backends/web/mod.rs +++ b/crates/typedialog-core/src/backends/web/mod.rs @@ -524,6 +524,14 @@ async fn index_handler(State(state): State>) -> impl IntoRespo .then(r => r.text()) .then(html => {{ document.getElementById('form-fields').innerHTML = html; + // After HTML replacement, populate all multiselect hidden fields + // This ensures values are preserved when rendered HTML replaces old HTML + document.querySelectorAll('[id^="values_"]').forEach(hiddenField => {{ + const fieldName = hiddenField.id.replace('values_', ''); + const checkboxes = document.querySelectorAll('[data-checkbox-group="' + fieldName + '"]:checked'); + const values = Array.from(checkboxes).map(cb => cb.value); + hiddenField.value = values.join(','); + }}); }}) .catch(err => console.error('Failed to update form:', err)); }} @@ -599,9 +607,22 @@ async fn get_dynamic_form_handler( // Convert query parameters to results HashMap for condition evaluation // JavaScript sends current field values as query parameters + // Multiselect fields use "_values" suffix and contain comma-separated values let mut results: HashMap = HashMap::new(); for (key, value) in params { - results.insert(key, Value::String(value)); + if key.ends_with("_values") { + // Parse comma-separated multiselect values into array + let field_name = key.strip_suffix("_values").unwrap_or(&key); + let values: Vec = value + .split(',') + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .map(|s| Value::String(s.to_string())) + .collect(); + results.insert(field_name.to_string(), Value::Array(values)); + } else { + results.insert(key, Value::String(value)); + } } // Get base_dir and form for recomputation @@ -1001,6 +1022,109 @@ fn render_display_item_html(item: &DisplayItem) -> String { } } +/// Render multiselect field with support for different display modes +/// Modes: "list" (default), "grid", "dropdown" +fn render_multiselect_html(field: &FieldDefinition, selected_values: &[String]) -> String { + let field_name = &field.name; + let display_mode = field.display_mode.as_deref().unwrap_or("list"); + let searchable = field.searchable.unwrap_or(false); + + match display_mode { + "grid" => { + // Grid layout (2-3 columns) + let options_html = field + .options + .iter() + .map(|opt| { + let checked = if selected_values.contains(&opt.value) { + "checked" + } else { + "" + }; + format!( + r#""#, + html_escape(field_name), + html_escape(&opt.value), + checked, + html_escape(opt.display_label()) + ) + }) + .collect::>() + .join("\n"); + + format!( + r#"
+ {} +
"#, + options_html + ) + } + "dropdown" => { + // Dropdown select with optional search + let options_html = field + .options + .iter() + .map(|opt| { + let selected = if selected_values.contains(&opt.value) { + "selected" + } else { + "" + }; + format!( + "", + html_escape(&opt.value), + selected, + html_escape(opt.display_label()) + ) + }) + .collect::>() + .join("\n"); + + let search_attr = if searchable { + r#"data-searchable="true""# + } else { + "" + }; + + format!( + r#""#, + html_escape(field_name), + html_escape(field_name), + search_attr, + options_html + ) + } + _ => { + // Default "list" mode: vertical checkboxes + field + .options + .iter() + .map(|opt| { + let checked = if selected_values.contains(&opt.value) { + "checked" + } else { + "" + }; + format!( + r#""#, + html_escape(field_name), + html_escape(&opt.value), + checked, + html_escape(opt.display_label()) + ) + }) + .collect::>() + .join("\n") + } + } +} + /// Render a single field for inclusion in the complete form (without individual submit button) /// Includes state injection for selected/checked attributes based on current results /// Returns (field_html, modals_and_scripts) where modals_and_scripts contains modals/JS for RepeatingGroup @@ -1147,8 +1271,10 @@ fn render_field_for_complete_form( } FieldType::MultiSelect => { let field_name = &field.name; + let display_mode = field.display_mode.as_deref().unwrap_or("list"); // Get current selected values from results for state preservation + // If no results, use default value let selected_values: Vec = results .get(field_name) .and_then(|v| v.as_array()) @@ -1157,32 +1283,75 @@ fn render_field_for_complete_form( .filter_map(|v| v.as_str().map(|s| s.to_string())) .collect() }) + .or_else(|| { + // Use default if available and results don't have this field + field.default.as_ref().map(|default| { + default + .split(',') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect() + }) + }) .unwrap_or_default(); - let options = if field.options.is_empty() { - String::new() + let options_html = render_multiselect_html(field, &selected_values); + + // Generate appropriate script based on display mode + let script = if display_mode == "dropdown" { + // For dropdown/select mode, handle change event differently + format!( + r#""#, + html_escape(field_name), + html_escape(field_name), + html_escape(field_name), + html_escape(field_name) + ) } else { - field - .options - .iter() - .map(|opt| { - let checked = if selected_values.contains(&opt.value) { - "checked" - } else { - "" - }; - format!( - r#""#, - html_escape(field_name), - html_escape(&opt.value), - checked, - html_escape(opt.display_label()) - ) - }) - .collect::>() - .join("\n") + // For checkbox modes (list, grid) + format!( + r#""#, + html_escape(field_name), + html_escape(field_name), + html_escape(field_name) + ) }; ( @@ -1193,19 +1362,13 @@ fn render_field_for_complete_form(
{}
- + {} "#, html_escape(&field.prompt), html_escape(field_name), html_escape(field_name), - options, - html_escape(field_name) + options_html, + script ), String::new(), ) @@ -1382,27 +1545,58 @@ fn render_field_to_html(field: &FieldDefinition) -> String { } FieldType::MultiSelect => { let field_name = &field.name; - let options = if field.options.is_empty() { - String::new() - } else { - field - .options - .iter() - .map(|opt| { - format!( - r#""#, - html_escape(field_name), - html_escape(&opt.value), - html_escape(opt.display_label()) - ) - }) - .collect::>() - .join("\n") - }; + let display_mode = field.display_mode.as_deref().unwrap_or("list"); + + // Parse default values for pre-checking + let default_values: Vec = field + .default + .as_ref() + .map(|default| { + default + .split(',') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect() + }) + .unwrap_or_default(); + + let options_html = render_multiselect_html(field, &default_values); let form_id = format!("form_{}", html_escape(field_name)); + + // Generate appropriate submit button and script based on display mode + let (submit_button, form_script) = if display_mode == "dropdown" { + let btn = r#""#.to_string(); + let script = format!( + r#""#, + form_id, + html_escape(field_name), + html_escape(field_name) + ); + (btn, script) + } else { + let btn = format!( + r#""#, + html_escape(field_name) + ); + let script = r#""#.to_string(); + (btn, script) + }; + format!( r#"
@@ -1412,22 +1606,17 @@ fn render_field_to_html(field: &FieldDefinition) -> String {
{}
- + {}
- "#, + {}"#, html_escape(&field.prompt), form_id, html_escape(field_name), html_escape(field_name), - options, - html_escape(field_name) + options_html, + submit_button, + form_script ) } FieldType::Custom => { diff --git a/crates/typedialog-core/src/encryption_bridge.rs b/crates/typedialog-core/src/encryption_bridge.rs index b7e373c..341061c 100644 --- a/crates/typedialog-core/src/encryption_bridge.rs +++ b/crates/typedialog-core/src/encryption_bridge.rs @@ -193,6 +193,10 @@ mod tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, diff --git a/crates/typedialog-core/src/form_parser.rs b/crates/typedialog-core/src/form_parser.rs index 3b0ab12..f978452 100644 --- a/crates/typedialog-core/src/form_parser.rs +++ b/crates/typedialog-core/src/form_parser.rs @@ -14,6 +14,21 @@ fn default_order() -> usize { 0 } +/// Deserialize `default` field accepting both string and boolean TOML values +fn deserialize_default<'de, D>(deserializer: D) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + use serde_json::Value; + let value: Option = Option::deserialize(deserializer)?; + Ok(value.and_then(|v| match v { + Value::String(s) => Some(s), + Value::Bool(b) => Some(b.to_string()), + Value::Number(n) => Some(n.to_string()), + _ => None, + })) +} + /// Form element (can be a display item or a field) /// Public enum for unified form structure #[derive(Debug, Clone)] @@ -477,6 +492,8 @@ pub struct FieldDefinition { /// Prompt message (can be literal text or i18n key) pub prompt: String, /// Optional default value (can contain template expressions like {{ env.USER }}) + /// Accepts both string ("false", "true") and boolean (false, true) TOML values + #[serde(default, deserialize_with = "deserialize_default")] pub default: Option, /// Optional placeholder text (can be literal text or i18n key) pub placeholder: Option, @@ -493,6 +510,18 @@ pub struct FieldDefinition { pub page_size: Option, /// Optional vim mode flag (for select/multiselect) pub vim_mode: Option, + /// Optional display mode for multiselect: "list" (default), "grid", "dropdown", "tags" + #[serde(default)] + pub display_mode: Option, + /// Optional searchable flag for multiselect/dropdown (enables filtering) + #[serde(default)] + pub searchable: Option, + /// Optional minimum selected items (for multiselect) + #[serde(default)] + pub min_selected: Option, + /// Optional maximum selected items (for multiselect) + #[serde(default)] + pub max_selected: Option, /// Optional custom type name (for custom) pub custom_type: Option, /// Optional min date (for date) @@ -2839,6 +2868,67 @@ mod tests { assert_eq!(fields.len(), 1); assert!(!fields.iter().any(|f| f.name == "ssl_warning")); } + + #[test] + fn test_default_field_accepts_both_string_and_bool_formats() { + let toml_string_format = r#" + name = "test_form_string" + [[fields]] + name = "agree_string" + type = "confirm" + prompt = "Do you agree?" + default = "false" + + [[fields]] + name = "subscribe_string" + type = "confirm" + prompt = "Subscribe?" + default = "true" + "#; + + let toml_bool_format = r#" + name = "test_form_bool" + [[fields]] + name = "agree_bool" + type = "confirm" + prompt = "Do you agree?" + default = false + + [[fields]] + name = "subscribe_bool" + type = "confirm" + prompt = "Subscribe?" + default = true + "#; + + let form_string = parse_toml(toml_string_format).unwrap(); + let form_bool = parse_toml(toml_bool_format).unwrap(); + + // Check string format parsing + let agree_string = form_string.fields.iter().find(|f| f.name == "agree_string"); + + assert!(agree_string.is_some()); + assert_eq!(agree_string.unwrap().default.as_deref(), Some("false")); + + let subscribe_string = form_string + .fields + .iter() + .find(|f| f.name == "subscribe_string"); + + assert!(subscribe_string.is_some()); + assert_eq!(subscribe_string.unwrap().default.as_deref(), Some("true")); + + // Check bool format parsing + let agree_bool = form_bool.fields.iter().find(|f| f.name == "agree_bool"); + + assert!(agree_bool.is_some()); + assert_eq!(agree_bool.unwrap().default.as_deref(), Some("false")); + + let subscribe_bool = form_bool.fields.iter().find(|f| f.name == "subscribe_bool"); + + assert!(subscribe_bool.is_some()); + assert_eq!(subscribe_bool.unwrap().default.as_deref(), Some("true")); + } } #[cfg(test)] diff --git a/crates/typedialog-core/src/helpers.rs b/crates/typedialog-core/src/helpers.rs index 7043377..827e173 100644 --- a/crates/typedialog-core/src/helpers.rs +++ b/crates/typedialog-core/src/helpers.rs @@ -413,6 +413,10 @@ mod tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -463,6 +467,10 @@ mod tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -513,6 +521,10 @@ mod tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -564,6 +576,10 @@ mod tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -598,6 +614,10 @@ mod tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -644,6 +664,10 @@ mod tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, diff --git a/crates/typedialog-core/src/nickel/toml_generator.rs b/crates/typedialog-core/src/nickel/toml_generator.rs index 046853f..093207f 100644 --- a/crates/typedialog-core/src/nickel/toml_generator.rs +++ b/crates/typedialog-core/src/nickel/toml_generator.rs @@ -321,6 +321,10 @@ impl TomlGenerator { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type, min_date: None, max_date: None, @@ -475,6 +479,10 @@ impl TomlGenerator { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, diff --git a/crates/typedialog-core/tests/encryption_integration.rs b/crates/typedialog-core/tests/encryption_integration.rs index 6093492..5ba6f15 100644 --- a/crates/typedialog-core/tests/encryption_integration.rs +++ b/crates/typedialog-core/tests/encryption_integration.rs @@ -29,6 +29,10 @@ mod encryption_tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -129,6 +133,10 @@ mod encryption_tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -182,6 +190,10 @@ mod encryption_tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -234,6 +246,10 @@ mod encryption_tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -324,6 +340,10 @@ mod age_roundtrip_tests { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, diff --git a/crates/typedialog-core/tests/nickel_integration.rs b/crates/typedialog-core/tests/nickel_integration.rs index 21e4cbd..39a3994 100644 --- a/crates/typedialog-core/tests/nickel_integration.rs +++ b/crates/typedialog-core/tests/nickel_integration.rs @@ -1205,6 +1205,10 @@ fn test_encryption_roundtrip_with_redaction() { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -1239,6 +1243,10 @@ fn test_encryption_roundtrip_with_redaction() { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -1273,6 +1281,10 @@ fn test_encryption_roundtrip_with_redaction() { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -1345,6 +1357,10 @@ fn test_encryption_auto_detection_from_field_type() { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -1405,6 +1421,10 @@ fn test_sensitive_field_explicit_override() { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, @@ -1471,6 +1491,10 @@ fn test_mixed_sensitive_and_non_sensitive_fields() { prefix_text: None, page_size: None, vim_mode: None, + display_mode: None, + searchable: None, + min_selected: None, + max_selected: None, custom_type: None, min_date: None, max_date: None, diff --git a/docs/field_types.md b/docs/field_types.md index 0a8a471..4bbda48 100644 --- a/docs/field_types.md +++ b/docs/field_types.md @@ -134,41 +134,64 @@ label = "PostgreSQL (server)" ### MultiSelect -Multiple selections from predefined options. +Multiple selections from predefined options with flexible display modes. ```toml +# Basic example with grid display [[elements]] -name = "features" +name = "languages" type = "multiselect" -prompt = "Enable features" -page_size = 10 -vim_mode = false +prompt = "Which languages are used?" +display_mode = "grid" +searchable = true +default = "rust" +options = [ + { value = "rust", label = "πŸ¦€ Rust" }, + { value = "python", label = "🐍 Python" }, + { value = "javascript", label = "πŸ“œ JavaScript" } +] -[[elements.options]] -value = "prometheus" -label = "Prometheus Metrics" - -[[elements.options]] -value = "grafana" -label = "Grafana Dashboard" - -[[elements.options]] -value = "auth" -label = "Authentication" +# Dropdown example with validation +[[elements]] +name = "user_roles" +type = "multiselect" +prompt = "Assign roles" +display_mode = "dropdown" +searchable = true +min_selected = 1 +max_selected = 5 +required = true +options = [ + { value = "admin", label = "Administrator" }, + { value = "editor", label = "Editor" }, + { value = "viewer", label = "Viewer" } +] ``` +**Display Modes**: + +- `"list"` (default): Vertical checkbox list +- `"grid"`: Responsive grid layout (2-3 columns) +- `"dropdown"`: HTML `` based on `display_mode` --- diff --git a/examples/01-basic/form.toml b/examples/01-basic/form.toml index 4825cb1..44a35b5 100644 --- a/examples/01-basic/form.toml +++ b/examples/01-basic/form.toml @@ -43,6 +43,9 @@ required = true name = "category" type = "multiselect" prompt = "Message categories" +display_mode = "grid" +searchable = true +default = "Bug Report" options = [ { value = "Bug Report", label = "πŸ› Bug Report" }, { value = "Feature Request", label = "✨ Feature Request" }, diff --git a/examples/02-multiselect-display-modes/form.toml b/examples/02-multiselect-display-modes/form.toml new file mode 100644 index 0000000..69aa232 --- /dev/null +++ b/examples/02-multiselect-display-modes/form.toml @@ -0,0 +1,75 @@ +name = "MultiSelect Display Modes Demo" +description = "Demonstrates different display modes and features for multiselect fields" + +# List mode (default) - vertical checkboxes +[[elements]] +name = "features_list" +type = "multiselect" +prompt = "Features (List mode - default)" +help = "Vertical checkbox list" +default = "logging" +options = [ + { value = "logging", label = "πŸ“ Logging" }, + { value = "metrics", label = "πŸ“Š Metrics" }, + { value = "tracing", label = "πŸ” Tracing" }, + { value = "profiling", label = "⚑ Profiling" }, +] + +# Grid mode - responsive grid layout +[[elements]] +name = "languages_grid" +type = "multiselect" +prompt = "Programming Languages (Grid mode)" +help = "Responsive grid with icons" +display_mode = "grid" +searchable = true +default = "rust,python" +options = [ + { value = "rust", label = "πŸ¦€ Rust" }, + { value = "python", label = "🐍 Python" }, + { value = "javascript", label = "πŸ“œ JavaScript" }, + { value = "go", label = "🐹 Go" }, + { value = "java", label = "β˜• Java" }, + { value = "csharp", label = "πŸ”΅ C#" }, +] + +# Dropdown mode - native select multiple with search +[[elements]] +name = "frameworks" +type = "multiselect" +prompt = "Web Frameworks" +help = "Use dropdown mode for 10+ options" +display_mode = "dropdown" +searchable = true +min_selected = 1 +max_selected = 3 +required = true +options = [ + { value = "react", label = "React" }, + { value = "vue", label = "Vue" }, + { value = "angular", label = "Angular" }, + { value = "svelte", label = "Svelte" }, + { value = "nextjs", label = "Next.js" }, + { value = "nuxt", label = "Nuxt" }, + { value = "astro", label = "Astro" }, + { value = "remix", label = "Remix" }, + { value = "gatsby", label = "Gatsby" }, + { value = "qwik", label = "Qwik" }, +] + +# Example with min/max validation +[[elements]] +name = "permissions" +type = "multiselect" +prompt = "User Permissions" +help = "Select between 1 and 3 permissions" +display_mode = "grid" +min_selected = 1 +max_selected = 3 +default = "read" +options = [ + { value = "read", label = "πŸ“– Read" }, + { value = "write", label = "✏️ Write" }, + { value = "delete", label = "πŸ—‘οΈ Delete" }, + { value = "admin", label = "πŸ”‘ Admin" }, +]