├── .config
└── dotnet-tools.json
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── Sutil.Generator.sln
├── build.ps1
└── src
├── Generator
├── Fast.fs
├── Generation.fs
├── Generator.fsproj
├── Program.fs
├── Shoelace.fs
├── Types.fs
├── package.json
└── pnpm-lock.yaml
└── website
├── .editorconfig
├── .firebaserc
├── .gitignore
├── CHANGELOG.md
├── README.md
├── firebase.json
├── markdown.pl.js
├── package.json
├── pnpm-lock.yaml
├── public
├── fable.ico
└── index.html
├── snowpack.config.js
└── src
├── App.fs
├── App.fsproj
├── Components
└── Sidenav.fs
├── Main.fs
├── Pages
├── Docs.fs
├── Fast.fs
├── Home.fs
└── Shoelace.fs
├── Router.fs
├── Router.js
├── Routes.fs
├── Theme.js
├── Types.fs
├── docs
├── fast
│ ├── getting-started.md
│ ├── index.md
│ └── themes.md
├── getting-started.md
├── home-2.md
├── home.md
└── shoelace
│ ├── components.md
│ ├── elmish.md
│ ├── getting-started.md
│ ├── index.md
│ └── stores.md
└── styles.css
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "fable": {
6 | "version": "3.2.2",
7 | "commands": [
8 | "fable"
9 | ]
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # Tye
65 | .tye/
66 |
67 | # StyleCop
68 | StyleCopReport.xml
69 |
70 | # Files built by Visual Studio
71 | *_i.c
72 | *_p.c
73 | *_h.h
74 | *.ilk
75 | *.meta
76 | *.obj
77 | *.iobj
78 | *.pch
79 | *.pdb
80 | *.ipdb
81 | *.pgc
82 | *.pgd
83 | *.rsp
84 | *.sbr
85 | *.tlb
86 | *.tli
87 | *.tlh
88 | *.tmp
89 | *.tmp_proj
90 | *_wpftmp.csproj
91 | *.log
92 | *.vspscc
93 | *.vssscc
94 | .builds
95 | *.pidb
96 | *.svclog
97 | *.scc
98 |
99 | # Chutzpah Test files
100 | _Chutzpah*
101 |
102 | # Visual C++ cache files
103 | ipch/
104 | *.aps
105 | *.ncb
106 | *.opendb
107 | *.opensdf
108 | *.sdf
109 | *.cachefile
110 | *.VC.db
111 | *.VC.VC.opendb
112 |
113 | # Visual Studio profiler
114 | *.psess
115 | *.vsp
116 | *.vspx
117 | *.sap
118 |
119 | # Visual Studio Trace Files
120 | *.e2e
121 |
122 | # TFS 2012 Local Workspace
123 | $tf/
124 |
125 | # Guidance Automation Toolkit
126 | *.gpState
127 |
128 | # ReSharper is a .NET coding add-in
129 | _ReSharper*/
130 | *.[Rr]e[Ss]harper
131 | *.DotSettings.user
132 |
133 | # TeamCity is a build add-in
134 | _TeamCity*
135 |
136 | # DotCover is a Code Coverage Tool
137 | *.dotCover
138 |
139 | # AxoCover is a Code Coverage Tool
140 | .axoCover/*
141 | !.axoCover/settings.json
142 |
143 | # Coverlet is a free, cross platform Code Coverage Tool
144 | coverage*[.json, .xml, .info]
145 |
146 | # Visual Studio code coverage results
147 | *.coverage
148 | *.coveragexml
149 |
150 | # NCrunch
151 | _NCrunch_*
152 | .*crunch*.local.xml
153 | nCrunchTemp_*
154 |
155 | # MightyMoose
156 | *.mm.*
157 | AutoTest.Net/
158 |
159 | # Web workbench (sass)
160 | .sass-cache/
161 |
162 | # Installshield output folder
163 | [Ee]xpress/
164 |
165 | # DocProject is a documentation generator add-in
166 | DocProject/buildhelp/
167 | DocProject/Help/*.HxT
168 | DocProject/Help/*.HxC
169 | DocProject/Help/*.hhc
170 | DocProject/Help/*.hhk
171 | DocProject/Help/*.hhp
172 | DocProject/Help/Html2
173 | DocProject/Help/html
174 |
175 | # Click-Once directory
176 | publish/
177 |
178 | # Publish Web Output
179 | *.[Pp]ublish.xml
180 | *.azurePubxml
181 | # Note: Comment the next line if you want to checkin your web deploy settings,
182 | # but database connection strings (with potential passwords) will be unencrypted
183 | *.pubxml
184 | *.publishproj
185 |
186 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
187 | # checkin your Azure Web App publish settings, but sensitive information contained
188 | # in these scripts will be unencrypted
189 | PublishScripts/
190 |
191 | # NuGet Packages
192 | *.nupkg
193 | # NuGet Symbol Packages
194 | *.snupkg
195 | # The packages folder can be ignored because of Package Restore
196 | **/[Pp]ackages/*
197 | # except build/, which is used as an MSBuild target.
198 | !**/[Pp]ackages/build/
199 | # Uncomment if necessary however generally it will be regenerated when needed
200 | #!**/[Pp]ackages/repositories.config
201 | # NuGet v3's project.json files produces more ignorable files
202 | *.nuget.props
203 | *.nuget.targets
204 |
205 | # Microsoft Azure Build Output
206 | csx/
207 | *.build.csdef
208 |
209 | # Microsoft Azure Emulator
210 | ecf/
211 | rcf/
212 |
213 | # Windows Store app package directories and files
214 | AppPackages/
215 | BundleArtifacts/
216 | Package.StoreAssociation.xml
217 | _pkginfo.txt
218 | *.appx
219 | *.appxbundle
220 | *.appxupload
221 |
222 | # Visual Studio cache files
223 | # files ending in .cache can be ignored
224 | *.[Cc]ache
225 | # but keep track of directories ending in .cache
226 | !?*.[Cc]ache/
227 |
228 | # Others
229 | ClientBin/
230 | ~$*
231 | *~
232 | *.dbmdl
233 | *.dbproj.schemaview
234 | *.jfm
235 | *.pfx
236 | *.publishsettings
237 | orleans.codegen.cs
238 |
239 | # Including strong name files can present a security risk
240 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
241 | #*.snk
242 |
243 | # Since there are multiple workflows, uncomment next line to ignore bower_components
244 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
245 | #bower_components/
246 |
247 | # RIA/Silverlight projects
248 | Generated_Code/
249 |
250 | # Backup & report files from converting an old project file
251 | # to a newer Visual Studio version. Backup files are not needed,
252 | # because we have git ;-)
253 | _UpgradeReport_Files/
254 | Backup*/
255 | UpgradeLog*.XML
256 | UpgradeLog*.htm
257 | ServiceFabricBackup/
258 | *.rptproj.bak
259 |
260 | # SQL Server files
261 | *.mdf
262 | *.ldf
263 | *.ndf
264 |
265 | # Business Intelligence projects
266 | *.rdl.data
267 | *.bim.layout
268 | *.bim_*.settings
269 | *.rptproj.rsuser
270 | *- [Bb]ackup.rdl
271 | *- [Bb]ackup ([0-9]).rdl
272 | *- [Bb]ackup ([0-9][0-9]).rdl
273 |
274 | # Microsoft Fakes
275 | FakesAssemblies/
276 |
277 | # GhostDoc plugin setting file
278 | *.GhostDoc.xml
279 |
280 | # Node.js Tools for Visual Studio
281 | .ntvs_analysis.dat
282 | node_modules/
283 |
284 | # Visual Studio 6 build log
285 | *.plg
286 |
287 | # Visual Studio 6 workspace options file
288 | *.opt
289 |
290 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
291 | *.vbw
292 |
293 | # Visual Studio LightSwitch build output
294 | **/*.HTMLClient/GeneratedArtifacts
295 | **/*.DesktopClient/GeneratedArtifacts
296 | **/*.DesktopClient/ModelManifest.xml
297 | **/*.Server/GeneratedArtifacts
298 | **/*.Server/ModelManifest.xml
299 | _Pvt_Extensions
300 |
301 | # Paket dependency manager
302 | .paket/paket.exe
303 | paket-files/
304 |
305 | # FAKE - F# Make
306 | .fake/
307 |
308 | # Ionide - VsCode extension for F# Support
309 | .ionide/
310 |
311 | # CodeRush personal settings
312 | .cr/personal
313 |
314 | # Python Tools for Visual Studio (PTVS)
315 | __pycache__/
316 | *.pyc
317 |
318 | # Cake - Uncomment if you are using it
319 | # tools/**
320 | # !tools/packages.config
321 |
322 | # Tabs Studio
323 | *.tss
324 |
325 | # Telerik's JustMock configuration file
326 | *.jmconfig
327 |
328 | # BizTalk build output
329 | *.btp.cs
330 | *.btm.cs
331 | *.odx.cs
332 | *.xsd.cs
333 |
334 | # OpenCover UI analysis results
335 | OpenCover/
336 |
337 | # Azure Stream Analytics local run output
338 | ASALocalRun/
339 |
340 | # MSBuild Binary and Structured Log
341 | *.binlog
342 |
343 | # NVidia Nsight GPU debugger configuration file
344 | *.nvuser
345 |
346 | # MFractors (Xamarin productivity tool) working folder
347 | .mfractor/
348 |
349 | # Local History for Visual Studio
350 | .localhistory/
351 |
352 | # BeatPulse healthcheck temp database
353 | healthchecksdb
354 |
355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
356 | MigrationBackup/
357 |
358 | # Ionide (cross platform F# VS Code tools) working folder
359 | .ionide/
360 |
361 | ##
362 | ## Visual studio for Mac
363 | ##
364 |
365 |
366 | # globs
367 | Makefile.in
368 | *.userprefs
369 | *.usertasks
370 | config.make
371 | config.status
372 | aclocal.m4
373 | install-sh
374 | autom4te.cache/
375 | *.tar.gz
376 | tarballs/
377 | test-results/
378 |
379 | # Mac bundle stuff
380 | *.dmg
381 | *.app
382 |
383 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
384 | # General
385 | .DS_Store
386 | .AppleDouble
387 | .LSOverride
388 |
389 | # Icon must end with two \r
390 | Icon
391 |
392 |
393 | # Thumbnails
394 | ._*
395 |
396 | # Files that might appear in the root of a volume
397 | .DocumentRevisions-V100
398 | .fseventsd
399 | .Spotlight-V100
400 | .TemporaryItems
401 | .Trashes
402 | .VolumeIcon.icns
403 | .com.apple.timemachine.donotpresent
404 |
405 | # Directories potentially created on remote AFP share
406 | .AppleDB
407 | .AppleDesktop
408 | Network Trash Folder
409 | Temporary Items
410 | .apdisk
411 |
412 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
413 | # Windows thumbnail cache files
414 | Thumbs.db
415 | ehthumbs.db
416 | ehthumbs_vista.db
417 |
418 | # Dump file
419 | *.stackdump
420 |
421 | # Folder config file
422 | [Dd]esktop.ini
423 |
424 | # Recycle Bin used on file shares
425 | $RECYCLE.BIN/
426 |
427 | # Windows Installer files
428 | *.cab
429 | *.msi
430 | *.msix
431 | *.msm
432 | *.msp
433 |
434 | # Windows shortcuts
435 | *.lnk
436 |
437 | # JetBrains Rider
438 | .idea/
439 | *.sln.iml
440 |
441 | ##
442 | ## Visual Studio Code
443 | ##
444 | .vscode/*
445 | !.vscode/settings.json
446 | !.vscode/tasks.json
447 | !.vscode/launch.json
448 | !.vscode/extensions.json
449 |
450 |
451 | Sutil.Shoelace
452 | Sutil.Fast
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Run Shoelace",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "program": "${workspaceFolder}/src/Generator/bin/Debug/net5.0/Generator.dll",
13 | "args": [
14 | "-cs",
15 | "shoelace"
16 | ],
17 | "cwd": "${workspaceFolder}/src/Generator",
18 | "stopAtEntry": false,
19 | "console": "internalConsole"
20 | },
21 | {
22 | "name": "Run Fast",
23 | "type": "coreclr",
24 | "request": "launch",
25 | "preLaunchTask": "build",
26 | "program": "${workspaceFolder}/src/Generator/bin/Debug/net5.0/Generator.dll",
27 | "args": [
28 | "-cs",
29 | "fast"
30 | ],
31 | "cwd": "${workspaceFolder}/src/Generator",
32 | "stopAtEntry": false,
33 | "console": "internalConsole"
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "build",
8 | "command": "dotnet",
9 | "type": "shell",
10 | "args": [
11 | "build",
12 | // Ask dotnet build to generate full paths for file names.
13 | "/property:GenerateFullPaths=true",
14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel
15 | "/consoleloggerparameters:NoSummary"
16 | ],
17 | "group": "build",
18 | "presentation": {
19 | "reveal": "silent"
20 | },
21 | "problemMatcher": "$msCompile"
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Shoelace.Sutil Documentation
2 | Sutil.Shoelace Docs are in the `website` directory at the root of the project.
3 |
4 | To be able to update existing documents or add them you'll need to:
5 | 1. Create a new `markdown` file at `website/src/docs/` in case it doesn't exist.
6 |
7 | Example: `website/src/docs/sl-button.md`
8 | 2. Add/Edit the contents of the document.
9 |
10 | you can use markdown or html including script tags. Example:
11 |
12 | ```markdown
13 | # My Article
14 | My explanation of how an SlButton works
15 | '''fsharp
16 | Shoelace.SlButton [
17 | type' "primary"
18 | onClick (fun _ -> printfn "clicked") []
19 | text "I'm a button"
20 | ]
21 | '''
22 | then a small sample on how would the result be (which can't be done with F# in the website yet)
23 |
24 | I'm a button
25 |
29 | ```
30 |
31 | 3. Once this is done the file will be available at `#/docs/sl-button`.
32 |
33 | 4. If you feel the need for it add an entry on the menu at `website/src/Routes.fs`.
34 |
35 | In this example you would add something like this inside the routes list
36 | ```fsharp
37 | { name = "SlButton"
38 | href = "#/docs/sl-button"
39 | category = Components }
40 | ```
41 |
42 |
43 | # Shoelace.Generator project
44 |
45 | The generator project does the following
46 |
47 | 1. Download the `@shoelace-style/shoelace` package from the npm registry
48 | 2. Reads `./node_modules/@shoelace-style/shoelace/dist/metadata.json`
49 | 3. Deserializes it's contents into the types available in `Types.fs`
50 | 4. Writes a complete F# project to `./Sutil.Shoelace` including the `.fsproj` file
51 |
52 | Each file that is generated corresponds to a component that exists inside the `metadata.json`
53 |
54 | If you want to add functionality to the modules/components then you'll need to write the corresponding string template in `Templates.fs` the file is quite messy right now but any help to improve the clarity in this file is welcome.
55 |
56 |
57 | ## Getting to know the end product
58 | For the next section I'll talk about what is expected to have on the generated output although, for brevity I'll take out doc comments written in the final files and put my own comments instead
59 |
60 | ## Anatomy of a generated file (with attributes)
61 |
62 |
63 | ```fsharp
64 | // declation of the module and open statements
65 | module Sutil.Shoelace.MenuItem
66 | open Browser.Types
67 | open Sutil
68 | open Sutil.DOM
69 |
70 | (* Every Shoelace component has a native HTML element ideally we need
71 | to provide bindings of these so there is typing information
72 | when the users access them at event handlers
73 | *)
74 | []
75 | type SlMenuItem =
76 | inherit HTMLElement
77 | (* there are certain reserved words from F# that are often used in javascript
78 | in these cases we use double backticks " ` " to preserve the original names *)
79 | abstract member ``checked`` : bool with get, set
80 | abstract member disabled : bool with get, set
81 | abstract member value : string with get, set
82 | abstract member blur : unit
83 | (* in the cases we know what the parameters are we should provide anonymous types for them
84 | the reason being anonymous types get translated as plain javascript objects in Fable
85 | if the type is not known or problematic then obj should be good enough *)
86 | abstract member focus : {| preventScroll: bool option |} option -> unit
87 |
88 | (* IF the component has documented attributes we'll emit a type and module
89 | named like this `SlAttributes`
90 | this is to provide a few helpers described below *)
91 | type SlMenuItemAttributes = {
92 | checked' : bool option
93 | disabled : bool option
94 | value : string option
95 | }
96 |
97 | []
98 | module SlMenuItemAttributes =
99 | (* every attributes module will have a created function
100 | that provides an object with optional values and for some
101 | of them their default values *)
102 | let create(): SlMenuItemAttributes = {
103 | checked' = Some false
104 | disabled = Some false
105 | value = Some ""
106 | }
107 |
108 | (* from there on we'll provide simple functions in the form of
109 | `with (: type) (attrs: attribute type)`
110 | in the case of reserved names since these are handled in the library code
111 | we can use ` ' ` to sufix the property name and this will not impact the HTMLELement *)
112 |
113 | let withChecked (checked': bool) (attrs: SlMenuItemAttributes) =
114 | { attrs with checked' = Some checked' }
115 |
116 | let withDisabled (disabled: bool) (attrs: SlMenuItemAttributes) =
117 | { attrs with disabled = Some disabled }
118 |
119 |
120 | let withValue (value: string) (attrs: SlMenuItemAttributes) =
121 | { attrs with value = Some value }
122 | (* We'll provide a module with the name of the component
123 | and two methods in the case of components with attributes *)
124 | []
125 | module SlMenuItem =
126 |
127 | (* stateless is a bad name since it's not stateless
128 | the idea behind is that we're not providing any kind of binding by default
129 | and we're just providing a simple function that returns an html element *)
130 | let stateless (content: NodeFactory seq) = Html.custom("sl-menu-item", content)
131 | (* The "stateful" function provides a simple mechanism to bind all of the attributes
132 | to the html element via observables and `Bind.attr(, )` where "ATTRIBUTE_NAME" is the actual html attribute (checked, disabled, no-fieldset (Note: dash cased instead of cammel case when writing these attributes))
133 | then we just call the stateless function and yield everything to the Html.custom function from Sutil
134 | *)
135 | let stateful (attrs: IStore) (nodes: NodeFactory seq) =
136 |
137 | let checked' = attrs .> (fun attrs -> attrs.checked')
138 | let disabled = attrs .> (fun attrs -> attrs.disabled)
139 | let value = attrs .> (fun attrs -> attrs.value)
140 | stateless
141 | [ Bind.attr("checked", checked')
142 | Bind.attr("disabled", disabled)
143 | Bind.attr("value", value)
144 | yield! nodes ]
145 | ```
146 |
147 | ## Anatomy of a generated file (without attributes)
148 |
149 |
150 | ```fsharp
151 | // declation of the module and open statements
152 | module Sutil.Shoelace.MenuLabel
153 | open Browser.Types
154 | open Sutil
155 | open Sutil.DOM
156 | (* Every Shoelace component has a native HTML element ideally we need
157 | to provide bindings of these so there is typing information
158 | when the users access them at event handlers
159 | *)
160 | []
161 | type SlMenuLabel =
162 | inherit HTMLElement
163 |
164 |
165 | (* We'll provide a module with the name of the component
166 | In cases like this that the components don't provide documented attributes
167 | we just emit the stateless function *)
168 | []
169 | module SlMenuLabel =
170 |
171 | let stateless (content: NodeFactory seq) = Html.custom("sl-menu-label", content)
172 | ```
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Angel D. Munoz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # STATUS UPDATE
2 |
3 | The shoelace spec has changed a little bit and this generator won't work anymore, there is a more standards compatible metadata format which is here
4 | https://github.com/webcomponents/custom-elements-manifest if you want to make this a reality ping me on twitter @angel_d_munoz, if we can generate an F# AST from that metadata then Fantomas can write the code for us rather than us using strings
5 |
6 |
7 | This project for the moment will be put to rest in the meantime
8 |
9 |
10 | # Sutil.Generator
11 |
12 | This is a [Shoelace](https://github.com/shoelace-style/shoelace) and [Fast](https://fast.design) wrapper generator for [Sutil](https://github.com/davedawkins/Sutil) heavily inspider in [react-generator](https://github.com/shoelace-style/react-generator)
13 |
14 | # Generate `Sutil.Shoelace` or `Sutil.Fast` project
15 | To generate the `Sutil.Shoelace` or `Sutil.Fast` project you will need to have node installed in your machine.
16 | - We download the `@shoelace-style/shoelace` package which contains a `metadata.json` file that allows us to automate the generation of the F# source code.
17 | > In the case of Fast we read each component's metadata file to do the file generation
18 | - Once the package is downloaded the project proceeds to generate one file for each component listed in the metadata file
19 |
20 | To kick off these events run
21 | ```sh
22 | ./build.ps1 fast
23 | ```
24 | Normally you would do this to either
25 |
26 | 1. Draft a new release
27 | 2. Use Sutil.Shoelace or Sutil.Fast to improve the docs website
28 |
29 |
30 | # Future Ideas
31 |
32 | - [x] ~Propose a json schema to different libraries~ There's already a [Custom Elements Manifest](https://github.com/open-wc/custom-elements-manifest)
33 | - [ ] Support Custom Elements Manifest to generate libraries agnostically
34 | - [ ] Decouple Shoelace to the generator
35 | - [ ] Decouple Sutil from the generation phase (e.g allow this generator to create Feliz components or other DSL Flavor / Library)
36 |
--------------------------------------------------------------------------------
/Sutil.Generator.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F359596B-74AA-488C-82C8-187DCF72C281}"
7 | EndProject
8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Generator", "src\Generator\Generator.fsproj", "{B41BFB87-1949-435B-B89B-2B49C9DE7C8B}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "website", "website", "{550A6B28-0B40-4579-9B26-72EF75BCC632}"
11 | EndProject
12 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "App", "src\website\src\App.fsproj", "{4A995969-F98A-4B29-BE11-295DD45AC617}"
13 | EndProject
14 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Sutil.Shoelace", "src\Sutil.Shoelace\Sutil.Shoelace.fsproj", "{F6C84529-7F46-4A2B-B466-9E418BC5958E}"
15 | EndProject
16 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Sutil.Fast", "src\Sutil.Fast\Sutil.Fast.fsproj", "{756D5AF7-4F50-4DDA-95D9-38C825CA57B7}"
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Debug|x64 = Debug|x64
22 | Debug|x86 = Debug|x86
23 | Release|Any CPU = Release|Any CPU
24 | Release|x64 = Release|x64
25 | Release|x86 = Release|x86
26 | EndGlobalSection
27 | GlobalSection(SolutionProperties) = preSolution
28 | HideSolutionNode = FALSE
29 | EndGlobalSection
30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
31 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Debug|x64.ActiveCfg = Debug|Any CPU
34 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Debug|x64.Build.0 = Debug|Any CPU
35 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Debug|x86.ActiveCfg = Debug|Any CPU
36 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Debug|x86.Build.0 = Debug|Any CPU
37 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Release|x64.ActiveCfg = Release|Any CPU
40 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Release|x64.Build.0 = Release|Any CPU
41 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Release|x86.ActiveCfg = Release|Any CPU
42 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B}.Release|x86.Build.0 = Release|Any CPU
43 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Debug|Any CPU.Build.0 = Debug|Any CPU
45 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Debug|x64.ActiveCfg = Debug|Any CPU
46 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Debug|x64.Build.0 = Debug|Any CPU
47 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Debug|x86.ActiveCfg = Debug|Any CPU
48 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Debug|x86.Build.0 = Debug|Any CPU
49 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Release|Any CPU.ActiveCfg = Release|Any CPU
50 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Release|Any CPU.Build.0 = Release|Any CPU
51 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Release|x64.ActiveCfg = Release|Any CPU
52 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Release|x64.Build.0 = Release|Any CPU
53 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Release|x86.ActiveCfg = Release|Any CPU
54 | {4A995969-F98A-4B29-BE11-295DD45AC617}.Release|x86.Build.0 = Release|Any CPU
55 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
56 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Debug|Any CPU.Build.0 = Debug|Any CPU
57 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Debug|x64.ActiveCfg = Debug|Any CPU
58 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Debug|x64.Build.0 = Debug|Any CPU
59 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Debug|x86.ActiveCfg = Debug|Any CPU
60 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Debug|x86.Build.0 = Debug|Any CPU
61 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Release|Any CPU.ActiveCfg = Release|Any CPU
62 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Release|Any CPU.Build.0 = Release|Any CPU
63 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Release|x64.ActiveCfg = Release|Any CPU
64 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Release|x64.Build.0 = Release|Any CPU
65 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Release|x86.ActiveCfg = Release|Any CPU
66 | {F6C84529-7F46-4A2B-B466-9E418BC5958E}.Release|x86.Build.0 = Release|Any CPU
67 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
68 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
69 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Debug|x64.ActiveCfg = Debug|Any CPU
70 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Debug|x64.Build.0 = Debug|Any CPU
71 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Debug|x86.ActiveCfg = Debug|Any CPU
72 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Debug|x86.Build.0 = Debug|Any CPU
73 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
74 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Release|Any CPU.Build.0 = Release|Any CPU
75 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Release|x64.ActiveCfg = Release|Any CPU
76 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Release|x64.Build.0 = Release|Any CPU
77 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Release|x86.ActiveCfg = Release|Any CPU
78 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7}.Release|x86.Build.0 = Release|Any CPU
79 | EndGlobalSection
80 | GlobalSection(NestedProjects) = preSolution
81 | {B41BFB87-1949-435B-B89B-2B49C9DE7C8B} = {F359596B-74AA-488C-82C8-187DCF72C281}
82 | {550A6B28-0B40-4579-9B26-72EF75BCC632} = {F359596B-74AA-488C-82C8-187DCF72C281}
83 | {4A995969-F98A-4B29-BE11-295DD45AC617} = {550A6B28-0B40-4579-9B26-72EF75BCC632}
84 | {756D5AF7-4F50-4DDA-95D9-38C825CA57B7} = {F359596B-74AA-488C-82C8-187DCF72C281}
85 | EndGlobalSection
86 | EndGlobal
87 |
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [Parameter(Mandatory = $true)]
4 | [string]
5 | $library = "shoelace"
6 | )
7 |
8 | $project = $library -eq "shoelace" ? "Sutil.Shoelace" : "Sutil.Fast"
9 |
10 | $root = Get-Location
11 |
12 |
13 | Remove-Item $library -R -Force -ErrorAction Ignore;
14 | Remove-Item dist -R -Force -ErrorAction Ignore;
15 | dotnet tool restore
16 | set-location $root/src/Generator
17 | dotnet run -C Release -- -cs $library
18 | if ($LASTEXITCODE -gt 0) {
19 | Exit 1
20 | }
21 | dotnet build
22 | if ($LASTEXITCODE -gt 0) {
23 | Exit 1
24 | }
25 | Set-Location $root
26 | dotnet fable --cwd src/$project
27 | if ($LASTEXITCODE -gt 0) {
28 | Exit 1
29 | }
30 | dotnet pack src/$project -o dist
--------------------------------------------------------------------------------
/src/Generator/Fast.fs:
--------------------------------------------------------------------------------
1 | namespace Sutil.Generator.Fast
2 |
3 |
4 | (*
5 | DO NOT USE FANTOMAS OR ANOTHER FORMATTER
6 | THAT CHANGES THE MULTI-LINE STRING STRUCTURE ON THIS FILE
7 | IT WILL BREAK THE DOC COMMENTS GENERATION
8 | *)
9 |
10 |
11 | open System
12 | open Sutil.Generator.Types
13 | open System.Web
14 | open System.Text.RegularExpressions
15 |
16 |
17 | type FastPackageJson =
18 | FSharp.Data.JsonProvider<"node_modules/@microsoft/fast-components/package.json", InferTypesFromValues=false>
19 |
20 | []
21 | module Templates =
22 | let private getUpercasedName (name: string) (includefirstWord: bool) (cammelCase: bool) =
23 |
24 | let inline pascalCase (i: int) (word: string) =
25 | if i = 0 && cammelCase then
26 | word.[0]
27 | else
28 | Char.ToUpperInvariant(word.[0])
29 |
30 | let upercased =
31 | name.Split('-')
32 | let upercased =
33 | if includefirstWord then
34 | upercased |> Array.mapi (fun i word -> $"{pascalCase i word}{word.[1..]}")
35 | else
36 | upercased |> Array.tail |> Array.mapi (fun i word -> $"{pascalCase i word}{word.[1..]}")
37 | String.Join("", upercased)
38 |
39 | let getAttributeName (name: string) =
40 | match name with
41 | | "type" -> "``type``"
42 | | "open" -> "``open``"
43 | | "inline" -> "``inline``"
44 | | "checked" -> "``checked``"
45 | | rest when rest.Contains('-') ->
46 | getUpercasedName rest true true
47 | | rest -> rest
48 |
49 | let getAttributeType (type': string) =
50 | match type' with
51 | | "boolean" -> "bool"
52 | | "void" -> "unit"
53 | | "number" -> "float"
54 | | type' when type'.StartsWith('{') || type'.EndsWith('}') -> "obj"
55 | | type' -> type'
56 |
57 | let getDefaultValue (value: string) =
58 | match value with
59 | | "undefined"
60 | | "null"
61 | | ""
62 | | null -> "None"
63 | | value ->
64 | let regex = new Regex(@"^[+-]?(([1-9][0-9]*)?[0-9](\.[0-9]*)?|\.[0-9]+)$")
65 | match value with
66 | | "true"
67 | | "false" ->
68 | $"""Some {value}"""
69 | | value when regex.IsMatch (value) ->
70 | $"""Some {value}"""
71 | | value -> $"""Some "{value}" """
72 |
73 | let getDescription (description: string option) =
74 | match description with
75 | | Some description ->
76 | HttpUtility.HtmlEncode(description.Replace("\n", "\n /// "))
77 | | None -> ""
78 |
79 |
80 | let private getFastAttr (prop: AttributeVscodeDefinition) =
81 | let name = getAttributeName prop.name
82 | let type' = getAttributeType prop.``type``
83 |
84 | let description = getDescription prop.description
85 |
86 | $""" /// {description}
87 | abstract member {name} : {type'} with get, set"""
88 | let private getFastElementAttributes (props: AttributeVscodeDefinition seq) =
89 | props
90 | |> Seq.fold (fun (current: string) (next: AttributeVscodeDefinition) -> $"{current}\n{getFastAttr next}") ""
91 |
92 | let private getFastAttributesTypeTpl (prop: AttributeVscodeDefinition) =
93 | let name = getAttributeName prop.name
94 | let type' = getAttributeType prop.``type``
95 |
96 | let description = getDescription prop.description
97 |
98 | $""" /// {description}
99 | {name} : {type'} option"""
100 |
101 | let private getAttrs (props: AttributeVscodeDefinition seq) =
102 | props
103 | |> Seq.fold (fun (current: string) (next: AttributeVscodeDefinition) -> $"{current}\n{getFastAttributesTypeTpl next}") ""
104 |
105 |
106 | let getSlotList (slots: SlotVsCodeDefinition seq) (padding: int option) =
107 | let padding = defaultArg padding 0
108 | let padding = " " |> String.replicate padding
109 |
110 | let getSlots =
111 | slots
112 | |> Seq.fold
113 | (fun (current: string) next ->
114 | let description = getDescription next.description
115 |
116 | let name =
117 | if next.name.Length = 0 then
118 | "default"
119 | else
120 | next.name
121 |
122 | let current =
123 | if current.Length > 0 then
124 | $"{current}\n"
125 | else
126 | ""
127 |
128 | $"{padding}{current}{padding}/// - `{name}`: {description}")
129 | ""
130 |
131 | $"""{padding}/// Slots:
132 | {padding}{getSlots.Trim()}"""
133 |
134 | let getComponentCommentTpl (comp: TagVsCodeDefinition) (padding: int option) =
135 | let spacePadding = defaultArg padding 0
136 | let padding = " " |> String.replicate spacePadding
137 | $"""
138 | {padding}///
139 | {padding}/// Title: {comp.title}
140 | {padding}///
141 | {padding}/// Tag: {comp.name}
142 | {padding}///
143 | {getSlotList comp.slots (Some spacePadding)}
144 | {padding}///
145 | {padding}/// """
146 |
147 | let getCompModule (comp: TagVsCodeDefinition) (attrs: AttributeVscodeDefinition seq) =
148 | let tag = comp.name
149 | let name = getUpercasedName comp.name true false
150 |
151 | let getBindingsTpl =
152 | let props =
153 | attrs
154 | |> Seq.fold
155 | (fun current next ->
156 | let name = getAttributeName next.name
157 | $"{current}\n let {name} = attrs .> (fun attrs -> attrs.{name})")
158 | ""
159 |
160 | let bindings =
161 | attrs
162 | |> Seq.fold
163 | (fun (current: string) next ->
164 | let name = getAttributeName next.name
165 |
166 | let tag =
167 | next.name
168 |
169 | let current =
170 | if current.Length > 0 then
171 | $"{current}\n "
172 | else
173 | ""
174 |
175 | $"{current}Bind.attr(\"{tag}\", {name})")
176 | ""
177 |
178 | if props.Length > 0 then
179 | $"""
180 | /// Provides all of the bindings available for the HTML element
181 | /// It leverages "Bind.attr("attribute-name", observable)" to provide reactive changes
182 | let stateful (attrs: IStore<{name}Attributes>) (nodes: NodeFactory seq) =
183 | {props}
184 | stateless
185 | [ {bindings}
186 | yield! nodes ]"""
187 | else
188 | ""
189 | $"""
190 | ///
191 | []
192 | module {name} =
193 | /// doesn't provide any binding helper logic and allows the user to take full
194 | /// control over the HTML Element either to create static HTML or do custom bindings
195 | /// via "bindFragment" or "Bind.attr("", binding)"
196 | let stateless (content: NodeFactory seq) = Html.custom("{tag}", content)
197 | {getBindingsTpl}"""
198 |
199 |
200 | let getWithFunction (prop: AttributeVscodeDefinition) (attrsName: string) =
201 | let name = getUpercasedName prop.name true false
202 | let propAttr = getAttributeName prop.name
203 | let type' = getAttributeType prop.``type``
204 |
205 | let valuesComment =
206 | let values =
207 | (prop.values |> Option.defaultValue Seq.empty)
208 |
209 | let description = getDescription prop.description
210 |
211 | $"\n /// {description}\n /// Default Value: {prop.``default``}\n /// Type: {prop.``type``}"
212 |
213 | $"""{valuesComment}
214 | let with{name} ({propAttr}: {type'}) (attrs: {attrsName}) =
215 | {{ attrs with {propAttr} = Some {propAttr} }}"""
216 |
217 | let private getAttrRecordMemberValue (prop: AttributeVscodeDefinition) =
218 | let name = getAttributeName prop.name
219 | let type' = getAttributeType prop.``type``
220 | let defValue =
221 | prop.``default`` |> Option.defaultValue ("undefined" :> obj)
222 | let value = getDefaultValue (defValue.ToString().ToLowerInvariant())
223 |
224 | let addDot =
225 | match type' with
226 | | "float" when value.Contains('.') && value <> "None" -> ""
227 | | "float" when value <> "None" -> "."
228 | | _ -> ""
229 |
230 | let value =
231 | match value with
232 | | "Some true" when type' = "string" -> "Some \"true\""
233 | | "Some false" when type' = "string" -> "Some \"false\""
234 | | value -> value
235 |
236 | $"{name} = {value}{addDot}"
237 |
238 | let getAttrModule (name: string) (attrs: AttributeVscodeDefinition seq) =
239 | let getAttrs =
240 | attrs
241 | |> Seq.fold (fun current next -> $"{current}\n {getAttrRecordMemberValue next}") ""
242 |
243 | let withFunctions =
244 | let attrName = $"{name}Attributes"
245 |
246 | attrs
247 | |> Seq.fold (fun current next -> $"{current}\n {getWithFunction next attrName}") ""
248 |
249 | $"""
250 | ///
251 | []
252 | module {name}Attributes =
253 | let create(): {name}Attributes = {{ {getAttrs}
254 | }}
255 | {withFunctions}"""
256 |
257 | let getComponentTpl (comp: TagVsCodeDefinition) =
258 | let moduleName = getUpercasedName comp.name true false
259 | let props = getFastElementAttributes comp.attributes
260 | let attrs = getAttrs comp.attributes
261 | let hasAttributes = comp.attributes |> Seq.length > 0
262 | let attrsTpl =
263 | if hasAttributes then
264 | $"\n///\ntype {getUpercasedName comp.name true false}Attributes = {{ {attrs}\n}}"
265 | else
266 | ""
267 |
268 | let attrsModule =
269 | if hasAttributes then
270 | getAttrModule (getUpercasedName comp.name true false) comp.attributes
271 | else
272 | ""
273 |
274 | $"""
275 | module Sutil.Fast.{moduleName}
276 | open Browser.Types
277 | open Sutil
278 | open Sutil.DOM
279 | {getComponentCommentTpl comp None}
280 | []
281 | type {getUpercasedName comp.name true false} =
282 | inherit HTMLElement
283 | {props}
284 | {attrsTpl}{attrsModule}
285 | {getCompModule comp comp.attributes}"""
286 |
287 |
288 | let getStatefulComponentTpl (comp: TagVsCodeDefinition) =
289 | if comp.attributes |> Seq.length > 0 then
290 | let name = getUpercasedName comp.name true false
291 | $"""{getComponentCommentTpl comp (Some 4)}
292 | static member inline {name} (attrs: IStore<{name}Attributes>, content: NodeFactory seq) =
293 | {name}.stateful attrs content"""
294 | else
295 | ""
296 |
297 | let getStatelessComponentTpl (comp: TagVsCodeDefinition) =
298 | let name = getUpercasedName comp.name true false
299 | $"""{getComponentCommentTpl comp (Some 4)}
300 | static member inline {name} (content: NodeFactory seq) =
301 | {name}.stateless content"""
302 |
303 | let getFastAPIClass (components: TagVsCodeDefinition array) =
304 | let opens =
305 | components
306 | |> Array.fold
307 | (fun (current: string) next ->
308 | let name = getUpercasedName next.name true false
309 |
310 | let current =
311 | if current.Length > 0 then
312 | $"{current}\n"
313 | else
314 | ""
315 |
316 | $"{current}open Sutil.Fast.{name}")
317 | ""
318 |
319 | let methods =
320 | components
321 | |> Array.fold
322 | (fun (current: string) next ->
323 | let current =
324 | if current.Length > 0 then
325 | $"{current}\n "
326 | else
327 | ""
328 |
329 | let stateful = getStatefulComponentTpl next
330 |
331 | let stateless = getStatelessComponentTpl next
332 |
333 | $"{current}{stateless}{stateful}")
334 | ""
335 |
336 | $"""namespace Sutil.Fast
337 | open Sutil
338 | open Sutil.DOM
339 | {opens}
340 | ///
341 | /// Provides a simple API to access all Shoelace Components available
342 | ///
343 | type Fast =
344 | {methods}"""
345 |
346 |
347 | let getFsFileReference (components: TagVsCodeDefinition array) =
348 |
349 | components
350 | |> Array.fold
351 | (fun (current: string) (next: TagVsCodeDefinition) ->
352 | $"{current}\n ")
353 | """"""
354 |
355 | let getFsProjTpl (comps: string) (package: string) (version: string) =
356 | $"""
357 |
358 |
359 | Sutil bindings for {package} Web Components.
360 | The contents of this package are auto-generated
361 |
362 | https://github.com/AngelMunoz/Sutil.Generator
363 | https://github.com/AngelMunoz/Sutil.Generator
364 |
365 | fsharp;fable;svelte
366 | Angel D. Munoz
367 | {version}-beta
368 | {version}-beta
369 | netstandard2.0
370 | true
371 | $(DefineConstants);FABLE_COMPILER;
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 | {comps}
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 | {'\n'}"""
389 |
--------------------------------------------------------------------------------
/src/Generator/Generation.fs:
--------------------------------------------------------------------------------
1 | namespace Sutil.Generator
2 |
3 | open FSharp.Control.Tasks
4 | open System
5 |
6 | open type Text.Encoding
7 |
8 | open System.Runtime.InteropServices
9 | open System.IO
10 | open System.Text.Json
11 | open System.Text.Json.Serialization
12 | open CliWrap
13 | open Types
14 | open System.Threading.Tasks
15 | open Sutil.Generator
16 |
17 |
18 | module Generation =
19 | let private isWindows =
20 | RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
21 |
22 | let private getBytesFromStr (strval: string) =
23 | let b = UTF8.GetBytes(strval)
24 |
25 | ReadOnlySpan b
26 |
27 | let downloadPackage (package: string) =
28 | let cmd =
29 | Cli
30 | .Wrap(if isWindows then "npx.cmd" else "npx")
31 | .WithArguments($"pnpm install {package}")
32 | .WithStandardErrorPipe(PipeTarget.ToStream(System.Console.OpenStandardError()))
33 | .WithStandardOutputPipe(PipeTarget.ToStream(System.Console.OpenStandardOutput()))
34 |
35 | cmd.ExecuteAsync()
36 |
37 | let private getJsonOptions () =
38 | let opts = JsonSerializerOptions()
39 |
40 | opts.AllowTrailingCommas <- true
41 | opts.IgnoreNullValues <- true
42 | opts.ReadCommentHandling <- JsonCommentHandling.Skip
43 | opts.Converters.Add(JsonFSharpConverter())
44 | opts
45 |
46 | let private parseShoelaceMetadata () =
47 | task {
48 | try
49 | let path =
50 | let combined =
51 | Path.Combine("./", "node_modules", "@shoelace-style", "shoelace", "dist", "metadata.json")
52 |
53 | Path.GetFullPath combined
54 |
55 | use fileStr = File.OpenRead path
56 |
57 | let! serialized = JsonSerializer.DeserializeAsync(fileStr, getJsonOptions ())
58 | return Some serialized
59 | with ex ->
60 | eprintfn "%s" ex.Message
61 | return None
62 | }
63 |
64 | let private writeShelaceComponentFile (root: string) (comp: SlComponent) =
65 | let name = comp.className.[2..]
66 | let path = Path.Combine(root, $"{name}.fs")
67 | use file = File.Create path
68 |
69 | let bytes =
70 | getBytesFromStr (Shoelace.Templates.getComponentTpl comp)
71 |
72 | file.Write bytes
73 |
74 | let private writeShoelaceLibraryFsProj (root: string) (version: string) (components: SlComponent array) =
75 | let library = Path.Combine(root, "Library.fs")
76 |
77 | let fsproj =
78 | Path.Combine(root, "Sutil.Shoelace.fsproj")
79 |
80 | use library = File.Create library
81 |
82 | let bytes =
83 | getBytesFromStr (Shoelace.Templates.getShoelaceAPIClass components)
84 |
85 | library.Write bytes
86 | use fsproj = File.Create fsproj
87 |
88 | let writeComponents =
89 | Shoelace.Templates.getFsFileReference components
90 |
91 | let bytes =
92 | getBytesFromStr (Shoelace.Templates.getFsProjTpl writeComponents version)
93 |
94 | fsproj.Write bytes
95 |
96 | let private generateShoelaceLib () =
97 | task {
98 | printfn "Generating Shoelace Library..."
99 | printfn "Downloading package @shoelace-style/shoelace"
100 |
101 | let! result = downloadPackage "@shoelace-style/shoelace"
102 |
103 | if result.ExitCode <> 0 then
104 | raise (Exception("Failed to Download the package"))
105 |
106 | let! metadata = parseShoelaceMetadata ()
107 | let path = Path.Combine("../", "Sutil.Shoelace")
108 |
109 | let dir = Directory.CreateDirectory(path)
110 |
111 | match metadata with
112 | | Some metadata ->
113 | printfn $"Using Shoelace - {metadata.version} from {metadata.author}, {metadata.license}"
114 |
115 | metadata.components
116 | |> Array.Parallel.iter (writeShelaceComponentFile dir.FullName)
117 |
118 | writeShoelaceLibraryFsProj dir.FullName metadata.version metadata.components
119 | printfn $"Generated {metadata.components.Length} Components"
120 | | None ->
121 | printfn "Failed to parse the metadata.json file, will not continue."
122 | ()
123 | }
124 |
125 |
126 | let private writeFastComponentFile (root: string) (comp: TagVsCodeDefinition) =
127 | let name =
128 | let upercased =
129 | comp.name.Split('-')
130 | |> Array.tail
131 | |> Array.map (fun word -> $"{Char.ToUpperInvariant(word.[0])}{word.[1..]}")
132 |
133 | String.Join("", upercased)
134 |
135 | let path = Path.Combine(root, $"{name}.fs")
136 | use file = File.Create path
137 |
138 | let bytes =
139 | getBytesFromStr (Fast.Templates.getComponentTpl comp)
140 |
141 | file.Write bytes
142 |
143 | let private writeFastLibraryFsProj
144 | (root: string)
145 | (package: string)
146 | (version: string)
147 | (components: TagVsCodeDefinition array)
148 | =
149 | let library = Path.Combine(root, "Library.fs")
150 |
151 | let fsproj = Path.Combine(root, "Sutil.Fast.fsproj")
152 |
153 | use library = File.Create library
154 |
155 | let bytes =
156 | getBytesFromStr (Fast.Templates.getFastAPIClass components)
157 |
158 | library.Write bytes
159 | use fsproj = File.Create fsproj
160 |
161 | let writeComponents =
162 | Fast.Templates.getFsFileReference components
163 |
164 | let bytes =
165 | getBytesFromStr (Fast.Templates.getFsProjTpl writeComponents package version)
166 |
167 | fsproj.Write bytes
168 |
169 | let private getFastComponents () =
170 | let tryDeserializeFile (path: string) =
171 | task {
172 | use content = File.OpenRead path
173 |
174 | try
175 | let! definition =
176 | JsonSerializer.DeserializeAsync(content, getJsonOptions ())
177 |
178 | return definition.tags |> Seq.tryHead
179 | with ex ->
180 | eprintfn "Failed to parse File %s %s" path ex.Message
181 | return None
182 | }
183 |
184 | task {
185 | let path =
186 | let combined =
187 | Path.Combine("./", "node_modules", "@microsoft", "fast-components", "dist", "esm")
188 |
189 | Path.GetFullPath combined
190 |
191 | return!
192 | Directory.GetFiles(path, "*.vscode.definition.json", SearchOption.AllDirectories)
193 | |> Array.Parallel.map tryDeserializeFile
194 | |> Task.WhenAll
195 | }
196 |
197 | let generateFastLib () =
198 | task {
199 | printfn "Generate FAST library..."
200 | printfn "Downloading package @microsoft/fast-components"
201 |
202 | let! result = downloadPackage "@microsoft/fast-components"
203 |
204 | if result.ExitCode <> 0 then
205 | raise (Exception("Failed to Download the package"))
206 |
207 | let! components = getFastComponents ()
208 | let result = components |> Array.Parallel.choose id
209 | let path = Path.Combine("../", "Sutil.Fast")
210 |
211 | let dir = Directory.CreateDirectory(path)
212 | let package = Fast.FastPackageJson.GetSample()
213 | let version = package.Version
214 |
215 | printfn $"Using {package.Name} - {version} from {package.Author.Name}, {package.License}"
216 |
217 | result
218 | |> Array.Parallel.iter (writeFastComponentFile dir.FullName)
219 |
220 | writeFastLibraryFsProj dir.FullName package.Name version result
221 | printfn $"Generated {result.Length} Components"
222 | }
223 |
224 | let generateLibrary (componentSystem: ComponentSystem) =
225 | match componentSystem with
226 | | ComponentSystem.Shoelace -> generateShoelaceLib ()
227 | | ComponentSystem.Fast -> generateFastLib ()
228 |
--------------------------------------------------------------------------------
/src/Generator/Generator.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net5.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Generator/Program.fs:
--------------------------------------------------------------------------------
1 | // Learn more about F# at http://docs.microsoft.com/dotnet/fsharp
2 |
3 | open System.Threading.Tasks
4 | open FSharp.Control.Tasks
5 | open Sutil.Generator.Generation
6 | open Argu
7 | open Sutil.Generator.Types
8 |
9 |
10 |
11 | []
12 | let main argv =
13 |
14 | let parser =
15 | ArgumentParser.Create(programName = "Sutil.Generator")
16 |
17 | let result =
18 | parser.Parse(argv, ignoreUnrecognized = true)
19 |
20 | let activeTask () =
21 | match result.GetAllResults() with
22 | | [ Component_System ComponentSystem.Fast ] ->
23 | task {
24 | do! generateLibrary ComponentSystem.Fast
25 | return 0
26 | }
27 | | [ Component_System ComponentSystem.Shoelace ]
28 | | _ ->
29 | task {
30 | do! generateLibrary ComponentSystem.Shoelace
31 | return 0
32 | }
33 |
34 | activeTask ()
35 | |> Async.AwaitTask
36 | |> Async.RunSynchronously
37 |
--------------------------------------------------------------------------------
/src/Generator/Shoelace.fs:
--------------------------------------------------------------------------------
1 | namespace Sutil.Generator.Shoelace
2 | open Sutil.Generator.Types
3 |
4 | (*
5 | DO NOT USE FANTOMAS OR ANOTHER FORMATTER
6 | THAT CHANGES THE MULTI-LINE STRING STRUCTURE ON THIS FILE
7 | IT WILL BREAK THE DOC COMMENTS GENERATION
8 | *)
9 |
10 | []
11 | module Templates =
12 |
13 | open System.Web
14 |
15 |
16 | let getPropName (name: string) =
17 | match name with
18 | | "type" -> "``type``"
19 | | "open" -> "``open``"
20 | | "inline" -> "``inline``"
21 | | "checked" -> "``checked``"
22 | | rest -> rest
23 |
24 | let getAttrName (name: string) =
25 | match name with
26 | | "type" -> "type'"
27 | | "open" -> "open'"
28 | | "inline" -> "inline'"
29 | | "checked" -> "checked'"
30 | | name -> name
31 |
32 | let getTypeType (type': string) =
33 | match type' with
34 | | "boolean" -> "bool"
35 | | "void" -> "unit"
36 | | "number" -> "float"
37 | | "" -> "string array"
38 | | "PlaybackDirection" -> "string"
39 | | "FillMode" -> "string"
40 | | "FocusOptions" -> "{| preventScroll: bool option |}"
41 | | type' when type'.StartsWith('{') || type'.EndsWith('}') -> "obj"
42 | | type' when type'.Contains('|') -> "string"
43 | | type' -> type'.Replace("'", "")
44 |
45 | let getDefaultValue (defaultValue: string) =
46 | match defaultValue with
47 | | "..." -> "None"
48 | | "undefined"
49 | | "null"
50 | | null -> "None"
51 | | value -> $"""Some {value.Replace("'", "\"")}"""
52 |
53 | let getSlPropTpl (prop: SlProp) =
54 | let name = getPropName prop.name
55 | let type' = getTypeType prop.``type``
56 |
57 | let description =
58 | HttpUtility.HtmlEncode(prop.description.Replace("\n", "\n /// "))
59 |
60 | $""" /// {description}
61 | abstract member {name} : {type'} with get, set"""
62 |
63 |
64 | let private getEventList (events: SlEvents array) (padding: int option) =
65 | let padding = defaultArg padding 0
66 | let padding = " " |> String.replicate padding
67 |
68 | let getEvents =
69 | events
70 | |> Array.fold
71 | (fun (current: string) next ->
72 | let description =
73 | HttpUtility.HtmlEncode(next.description.Replace("\n", "\n /// "))
74 |
75 | let eventDetails =
76 | match next.details with
77 | | "void" -> "unit"
78 | | rest -> rest
79 |
80 | let current =
81 | if current.Length > 0 then
82 | $"{current}\n"
83 | else
84 | ""
85 |
86 | $"{padding}{current}{padding}/// - `{next.name}`: {description}")
87 | ""
88 |
89 | $"""{padding}/// Events:
90 | {padding}{getEvents.Trim()}"""
91 |
92 | let private getSlotList (slots: SlSlots array) (padding: int option) =
93 | let padding = defaultArg padding 0
94 | let padding = " " |> String.replicate padding
95 |
96 | let getSlots =
97 | slots
98 | |> Array.fold
99 | (fun (current: string) next ->
100 | let description =
101 | HttpUtility.HtmlEncode(next.description.Replace("\n", "\n /// "))
102 |
103 | let name =
104 | if next.name.Length = 0 then
105 | "default"
106 | else
107 | next.name
108 |
109 | let current =
110 | if current.Length > 0 then
111 | $"{current}\n"
112 | else
113 | ""
114 |
115 | $"{padding}{current}{padding}/// - `{name}`: {description}")
116 | ""
117 |
118 | $"""{padding}/// Slots:
119 | {padding}{getSlots.Trim()}"""
120 |
121 |
122 | let getComponentCommentTpl (comp: SlComponent) (padding: int option) =
123 | let spacePadding = defaultArg padding 0
124 | let padding = " " |> String.replicate spacePadding
125 |
126 | $"""
127 | {padding}///
128 | {padding}/// Tag: {comp.tag}
129 | {padding}///
130 | {padding}/// Since: {comp.since}
131 | {padding}///
132 | {padding}/// Status: {comp.status}.
133 | {padding}///
134 | {padding}/// File: {comp.file}
135 | {padding}///
136 | {getEventList comp.events (Some spacePadding)}
137 | {padding}///
138 | {getSlotList comp.slots (Some spacePadding)}
139 | {padding}///
140 | {padding}/// """
141 |
142 |
143 | let slMethodTpl (method: SlMethod) =
144 | let name = method.name
145 |
146 | let description =
147 | HttpUtility.HtmlEncode(method.description.Replace("\n", "\n /// "))
148 |
149 | let prams =
150 | method.``params``
151 | |> Array.fold
152 | (fun (current: string) (next: {| isOptional: option
153 | name: string
154 | ``type``: string |}) ->
155 | let current =
156 | if current.Length > 0 then
157 | $"{current}"
158 | else
159 | ""
160 |
161 | let type' = getTypeType next.``type``
162 |
163 | let isOption =
164 | match next.isOptional |> Option.defaultValue false with
165 | | true -> " option"
166 | | false -> ""
167 |
168 |
169 | $"{current} {type'}{isOption} ->")
170 | ""
171 |
172 | $""" /// {description}
173 | abstract member {name} : {prams} unit"""
174 |
175 | let slPropAttrTpl (prop: SlProp) =
176 | let name = getAttrName prop.name
177 | let type' = getTypeType prop.``type``
178 |
179 | let description =
180 | HttpUtility.HtmlEncode(prop.description.Replace("\n", "\n /// "))
181 |
182 | $""" /// {description}
183 | {name} : {type'} option"""
184 |
185 |
186 | let private getProps (props: SlProp array) =
187 | props
188 | |> Array.fold (fun (current: string) (next: SlProp) -> $"{current}\n{getSlPropTpl next}") ""
189 |
190 | let private getAttrs (props: SlProp array) =
191 | props
192 | |> Array.fold (fun (current: string) (next: SlProp) -> $"{current}\n{slPropAttrTpl next}") ""
193 |
194 | let private getMethods (methods: SlMethod array) =
195 | methods
196 | |> Array.fold (fun (current: string) (next: SlMethod) -> $"{current}\n{slMethodTpl next}") ""
197 |
198 | let private getAttrRecordMemberValue (prop: SlProp) =
199 | let name = getAttrName prop.name
200 | let type' = getTypeType prop.``type``
201 | let value = getDefaultValue prop.defaultValue
202 |
203 | let addDot =
204 | if type' = "float"
205 | && value <> "None"
206 | && value <> "Some Fable.Core.JS.Infinity" then
207 | "."
208 | else
209 | ""
210 |
211 | $"{name} = {value}{addDot}"
212 |
213 |
214 | let getWithFunction (prop: SlProp) (attrsName: string) =
215 | let name = prop.name
216 | let propAttr = getAttrName prop.name
217 | let type' = getTypeType prop.``type``
218 |
219 | let valuesComment =
220 | let values =
221 | (prop.values |> Option.defaultValue [||])
222 |
223 | let description =
224 | HttpUtility.HtmlEncode(prop.description.Replace("\n", "\n /// "))
225 |
226 | $"\n /// {description}\n /// Default Value: {prop.defaultValue}\n /// Type: {prop.``type``}"
227 |
228 | $"""{valuesComment}
229 | let with{name.[0] |> System.Char.ToUpper}{name.[1..]} ({propAttr}: {type'}) (attrs: {attrsName}) =
230 | {{ attrs with {propAttr} = Some {propAttr} }}"""
231 |
232 | let getAttrModule (className: string) (comps: SlProp array) =
233 | let getAttrs =
234 | comps
235 | |> Array.fold (fun current next -> $"{current}\n {getAttrRecordMemberValue next}") ""
236 |
237 | let withFunctions =
238 | let attrName = $"{className}Attributes"
239 |
240 | comps
241 | |> Array.fold (fun current next -> $"{current}\n {getWithFunction next attrName}") ""
242 |
243 | $"""
244 | ///
245 | []
246 | module {className}Attributes =
247 | let create(): {className}Attributes = {{ {getAttrs}
248 | }}
249 | {withFunctions}"""
250 |
251 | let getCompModule (tagAndName: string * string) (comp: SlProp array) =
252 | let (tag, className) = tagAndName
253 |
254 | let getBindingsTpl =
255 | let props =
256 | comp
257 | |> Array.fold
258 | (fun current next ->
259 | let name = getAttrName next.name
260 | $"{current}\n let {name} = attrs .> (fun attrs -> attrs.{name})")
261 | ""
262 |
263 | let bindings =
264 | comp
265 | |> Array.fold
266 | (fun (current: string) next ->
267 | let name = getAttrName next.name
268 |
269 | let tag =
270 | next.attribute |> Option.defaultValue next.name
271 |
272 | let current =
273 | if current.Length > 0 then
274 | $"{current}\n "
275 | else
276 | ""
277 |
278 | $"{current}Bind.attr(\"{tag}\", {name})")
279 | ""
280 |
281 | if props.Length > 0 then
282 | $"""
283 | /// Provides all of the bindings available for the HTML element
284 | /// It leverages "Bind.attr("attribute-name", observable)" to provide reactive changes
285 | let stateful (attrs: IStore<{className}Attributes>) (nodes: NodeFactory seq) =
286 | {props}
287 | stateless
288 | [ {bindings}
289 | yield! nodes ]"""
290 | else
291 | ""
292 |
293 | $"""
294 | ///
295 | []
296 | module {className} =
297 | /// doesn't provide any binding helper logic and allows the user to take full
298 | /// control over the HTML Element either to create static HTML or do custom bindings
299 | /// via "bindFragment" or "Bind.attr("", binding)"
300 | let stateless (content: NodeFactory seq) = Html.custom("{tag}", content)
301 | {getBindingsTpl}"""
302 |
303 | let getComponentTpl (comp: SlComponent) =
304 | let moduleName = comp.className.[2..]
305 | let props = getProps comp.props
306 | let attrs = getAttrs comp.props
307 | let methods = getMethods comp.methods
308 |
309 | let attrsTpl =
310 | if comp.props.Length > 0 then
311 | $"\n///\ntype {comp.className}Attributes = {{ {attrs}\n}}"
312 | else
313 | ""
314 |
315 | let attrsModule =
316 | if comp.props.Length > 0 then
317 | getAttrModule comp.className comp.props
318 | else
319 | ""
320 |
321 | $"""
322 | module Sutil.Shoelace.{moduleName}
323 | open Browser.Types
324 | open Sutil
325 | open Sutil.DOM{getComponentCommentTpl comp None}
326 | []
327 | type {comp.className} =
328 | inherit HTMLElement
329 | {props}
330 | {methods}
331 | {attrsTpl}{attrsModule}{getCompModule (comp.tag, comp.className) (comp.props)}"""
332 |
333 |
334 | let getStatefulComponentTpl (comp: SlComponent) =
335 | if comp.props.Length > 0 then
336 | $"""{getComponentCommentTpl comp (Some 4)}
337 | static member inline {comp.className} (attrs: IStore<{comp.className}Attributes>, content: NodeFactory seq) =
338 | {comp.className}.stateful attrs content"""
339 | else
340 | ""
341 |
342 | let getStatelessComponentTpl (comp: SlComponent) =
343 | $"""{getComponentCommentTpl comp (Some 4)}
344 | static member inline {comp.className} (content: NodeFactory seq) =
345 | {comp.className}.stateless content"""
346 |
347 | let getShoelaceAPIClass (components: SlComponent array) =
348 | let opens =
349 | components
350 | |> Array.fold
351 | (fun (current: string) next ->
352 | let name = next.className.[2..]
353 |
354 | let current =
355 | if current.Length > 0 then
356 | $"{current}\n"
357 | else
358 | ""
359 |
360 | $"{current}open Sutil.Shoelace.{name}")
361 | ""
362 |
363 | let methods =
364 | components
365 | |> Array.fold
366 | (fun (current: string) next ->
367 | let current =
368 | if current.Length > 0 then
369 | $"{current}\n "
370 | else
371 | ""
372 |
373 | let stateful = getStatefulComponentTpl next
374 |
375 | let stateless = getStatelessComponentTpl next
376 |
377 | $"{current}{stateless}{stateful}")
378 | ""
379 |
380 | $"""namespace Sutil.Shoelace
381 | open Sutil
382 | open Sutil.DOM
383 | {opens}
384 | ///
385 | /// Provides a simple API to access all Shoelace Components available
386 | ///
387 | type Shoelace =
388 | {methods}"""
389 |
390 |
391 | let getFsFileReference (components: SlComponent array) =
392 | components
393 | |> Array.fold
394 | (fun (current: string) (next: SlComponent) ->
395 | $"{current}\n ")
396 | """"""
397 |
398 | let getFsProjTpl (comps: string) (version: string) =
399 | $"""
400 |
401 |
402 | Sutil bindings for Shoelace Web Components.
403 | The contents of this package are auto-generated
404 |
405 | https://github.com/AngelMunoz/Sutil.Generator
406 | https://github.com/AngelMunoz/Sutil.Generator
407 |
408 | fsharp;fable;svelte
409 | Angel D. Munoz
410 | {version}
411 | {version}
412 | netstandard2.0
413 | true
414 | $(DefineConstants);FABLE_COMPILER;
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 | {comps}
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 | {'\n'}"""
431 |
--------------------------------------------------------------------------------
/src/Generator/Types.fs:
--------------------------------------------------------------------------------
1 | namespace Sutil.Generator
2 |
3 | open Argu
4 |
5 | module Types =
6 |
7 | []
8 | type ComponentSystem =
9 | | Fast
10 | | Shoelace
11 |
12 | type Args =
13 | | [] Component_System of ComponentSystem
14 | interface IArgParserTemplate with
15 | member this.Usage =
16 | match this with
17 | | Component_System _ -> "You need to specify the component System"
18 |
19 | type SlProp =
20 | { name: string
21 | description: string
22 | ``type``: string
23 | attribute: string option
24 | defaultValue: string
25 | values: string array option }
26 |
27 | type SlMethod =
28 | { name: string
29 | description: string
30 | ``params``: {| name: string
31 | ``type``: string
32 | isOptional: bool option |} array }
33 |
34 | type SlEvents =
35 | { name: string
36 | description: string
37 | details: string }
38 |
39 | type SlSlots = { name: string; description: string }
40 | type SlCssCustomProperties = { name: string; description: string }
41 | type SlParts = { name: string; description: string }
42 | type SlAnimation = { name: string; description: string }
43 |
44 | type SlComponent =
45 | { className: string
46 | tag: string
47 | file: string
48 | since: string
49 | status: string
50 | props: SlProp array
51 | methods: SlMethod array
52 | events: SlEvents array
53 | slots: SlSlots array
54 | cssCustomProperties: SlCssCustomProperties array
55 | parts: SlParts array
56 | dependencies: string array
57 | animations: SlAnimation array }
58 |
59 | type ShoelaceMetadata =
60 | { name: string
61 | description: string
62 | version: string
63 | author: string
64 | homepage: string
65 | license: string
66 | components: SlComponent array }
67 |
68 | type AttributeVscodeDefinition =
69 | { name: string
70 | title: string
71 | ``type``: string
72 | description: string option
73 | ``default``: obj option
74 | required: bool option
75 | values: seq<{| name: string |}> option
76 | value: obj option }
77 |
78 | type SlotVsCodeDefinition =
79 | { name: string
80 | title: string
81 | description: string option }
82 |
83 | type TagVsCodeDefinition =
84 | { name: string
85 | title: string
86 | description: string
87 | attributes: seq
88 | slots: seq }
89 |
90 |
91 | type HtmlCustomDataVSC =
92 | { version: float
93 | tags: seq }
94 |
--------------------------------------------------------------------------------
/src/Generator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "dependencies": {
4 | "@microsoft/fast-components": "1.21.6",
5 | "@shoelace-style/shoelace": "2.0.0-beta.43"
6 | },
7 | "peerDependencies": {
8 | "lodash-es": "4.17.21"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Generator/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: 5.3
2 |
3 | specifiers:
4 | '@microsoft/fast-components': 1.21.6
5 | '@shoelace-style/shoelace': 2.0.0-beta.43
6 |
7 | dependencies:
8 | '@microsoft/fast-components': 1.21.6
9 | '@shoelace-style/shoelace': 2.0.0-beta.43
10 |
11 | packages:
12 |
13 | /@lit/reactive-element/1.0.0-rc.2:
14 | resolution: {integrity: sha512-cujeIl5Ei8FC7UHf4/4Q3bRJOtdTe1vpJV/JEBYCggedmQ+2P8A2oz7eE+Vxi6OJ4nc0X+KZxXnBoH4QrEbmEQ==}
15 | dev: false
16 |
17 | /@microsoft/fast-colors/5.1.3:
18 | resolution: {integrity: sha512-XDEnRYxPO5P3Jsizm4TCxLu1osS/uV3Lym6SfRhq2PxfXPTgEcdvOYDUXyV2drqebs3U5VQnOcYcJiSp73xhng==}
19 | dev: false
20 |
21 | /@microsoft/fast-components/1.21.6:
22 | resolution: {integrity: sha512-z7/kaeEehmCiwFoCNKsXeEZNZNe595HVRmLG6SIOkbkdHMFaw/0Gq2M8j8kkPq+cL+ZDxuSmiCdMsaqSgbNGhA==}
23 | peerDependencies:
24 | lodash-es: ^4.0.0
25 | dependencies:
26 | '@microsoft/fast-colors': 5.1.3
27 | '@microsoft/fast-element': 1.4.0
28 | '@microsoft/fast-foundation': 1.24.6
29 | '@microsoft/fast-web-utilities': 4.8.0
30 | tslib: 1.14.1
31 | vscode-html-languageservice: 4.0.5
32 | dev: false
33 |
34 | /@microsoft/fast-element/1.4.0:
35 | resolution: {integrity: sha512-7BC/juFc7S4HMGt/tNCP9bjwtUslheGiPM2jtIibe1bj+PO34woUfH5TxaklOT6sRStig/0fODX4R5oqY4DYLA==}
36 | dev: false
37 |
38 | /@microsoft/fast-foundation/1.24.6:
39 | resolution: {integrity: sha512-uZH/E+4hrwc0/rcOUg+JzqzxS4HiQidgGCFQwWQIh0sNduveoaKDKIHD4STokmkWhr02TTDyqizrFD0hPmCHNA==}
40 | dependencies:
41 | '@microsoft/fast-element': 1.4.0
42 | '@microsoft/fast-web-utilities': 4.8.0
43 | '@microsoft/tsdoc-config': 0.13.9
44 | tabbable: 5.2.0
45 | tslib: 1.14.1
46 | transitivePeerDependencies:
47 | - lodash-es
48 | dev: false
49 |
50 | /@microsoft/fast-web-utilities/4.8.0:
51 | resolution: {integrity: sha512-+MroMIP5yGD8mqbegqSZoIbQVjvmsQRQtn87Gc8TJk00KIfRu2x9sFAq8q5m8H61sjCRHreJ0Bq5telz09h55g==}
52 | peerDependencies:
53 | lodash-es: ^4.17.10
54 | dependencies:
55 | exenv-es6: 1.0.0
56 | dev: false
57 |
58 | /@microsoft/tsdoc-config/0.13.9:
59 | resolution: {integrity: sha512-VqqZn+rT9f6XujFPFR2aN9XKF/fuir/IzKVzoxI0vXIzxysp4ee6S2jCakmlGFHEasibifFTsJr7IYmRPxfzYw==}
60 | dependencies:
61 | '@microsoft/tsdoc': 0.12.24
62 | ajv: 6.12.6
63 | jju: 1.4.0
64 | resolve: 1.19.0
65 | dev: false
66 |
67 | /@microsoft/tsdoc/0.12.24:
68 | resolution: {integrity: sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==}
69 | dev: false
70 |
71 | /@nodelib/fs.scandir/2.1.5:
72 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
73 | engines: {node: '>= 8'}
74 | dependencies:
75 | '@nodelib/fs.stat': 2.0.5
76 | run-parallel: 1.2.0
77 | dev: false
78 |
79 | /@nodelib/fs.stat/2.0.5:
80 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
81 | engines: {node: '>= 8'}
82 | dev: false
83 |
84 | /@nodelib/fs.walk/1.2.7:
85 | resolution: {integrity: sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==}
86 | engines: {node: '>= 8'}
87 | dependencies:
88 | '@nodelib/fs.scandir': 2.1.5
89 | fastq: 1.11.0
90 | dev: false
91 |
92 | /@popperjs/core/2.9.2:
93 | resolution: {integrity: sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==}
94 | dev: false
95 |
96 | /@shoelace-style/animations/1.1.0:
97 | resolution: {integrity: sha512-Be+cahtZyI2dPKRm8EZSx3YJQ+jLvEcn3xzRP7tM4tqBnvd/eW/64Xh0iOf0t2w5P8iJKfdBbpVNE9naCaOf2g==}
98 | dev: false
99 |
100 | /@shoelace-style/shoelace/2.0.0-beta.43:
101 | resolution: {integrity: sha512-7CAE5umw7pr3QiYyQmp5dcKnBazV9gcP4TtEUyKUTHLi2Bag3FTOyedXTjFB3XpfRmfwL5Y1lhiQu7cje7BBlw==}
102 | dependencies:
103 | '@popperjs/core': 2.9.2
104 | '@shoelace-style/animations': 1.1.0
105 | color: 3.1.3
106 | globby: 11.0.4
107 | lit: 2.0.0-rc.2
108 | lit-html: 2.0.0-rc.3
109 | qr-creator: 1.0.0
110 | dev: false
111 |
112 | /@types/trusted-types/1.0.6:
113 | resolution: {integrity: sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==}
114 | dev: false
115 |
116 | /ajv/6.12.6:
117 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
118 | dependencies:
119 | fast-deep-equal: 3.1.3
120 | fast-json-stable-stringify: 2.1.0
121 | json-schema-traverse: 0.4.1
122 | uri-js: 4.4.1
123 | dev: false
124 |
125 | /array-union/2.1.0:
126 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
127 | engines: {node: '>=8'}
128 | dev: false
129 |
130 | /braces/3.0.2:
131 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
132 | engines: {node: '>=8'}
133 | dependencies:
134 | fill-range: 7.0.1
135 | dev: false
136 |
137 | /color-convert/1.9.3:
138 | resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
139 | dependencies:
140 | color-name: 1.1.3
141 | dev: false
142 |
143 | /color-name/1.1.3:
144 | resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=}
145 | dev: false
146 |
147 | /color-name/1.1.4:
148 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
149 | dev: false
150 |
151 | /color-string/1.5.5:
152 | resolution: {integrity: sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==}
153 | dependencies:
154 | color-name: 1.1.4
155 | simple-swizzle: 0.2.2
156 | dev: false
157 |
158 | /color/3.1.3:
159 | resolution: {integrity: sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==}
160 | dependencies:
161 | color-convert: 1.9.3
162 | color-string: 1.5.5
163 | dev: false
164 |
165 | /dir-glob/3.0.1:
166 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
167 | engines: {node: '>=8'}
168 | dependencies:
169 | path-type: 4.0.0
170 | dev: false
171 |
172 | /exenv-es6/1.0.0:
173 | resolution: {integrity: sha512-fcG/TX8Ruv9Ma6PBaiNsUrHRJzVzuFMP6LtPn/9iqR+nr9mcLeEOGzXQGLC5CVQSXGE98HtzW2mTZkrCA3XrDg==}
174 | dev: false
175 |
176 | /fast-deep-equal/3.1.3:
177 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
178 | dev: false
179 |
180 | /fast-glob/3.2.5:
181 | resolution: {integrity: sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==}
182 | engines: {node: '>=8'}
183 | dependencies:
184 | '@nodelib/fs.stat': 2.0.5
185 | '@nodelib/fs.walk': 1.2.7
186 | glob-parent: 5.1.2
187 | merge2: 1.4.1
188 | micromatch: 4.0.4
189 | picomatch: 2.3.0
190 | dev: false
191 |
192 | /fast-json-stable-stringify/2.1.0:
193 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
194 | dev: false
195 |
196 | /fastq/1.11.0:
197 | resolution: {integrity: sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==}
198 | dependencies:
199 | reusify: 1.0.4
200 | dev: false
201 |
202 | /fill-range/7.0.1:
203 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
204 | engines: {node: '>=8'}
205 | dependencies:
206 | to-regex-range: 5.0.1
207 | dev: false
208 |
209 | /function-bind/1.1.1:
210 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
211 | dev: false
212 |
213 | /glob-parent/5.1.2:
214 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
215 | engines: {node: '>= 6'}
216 | dependencies:
217 | is-glob: 4.0.1
218 | dev: false
219 |
220 | /globby/11.0.4:
221 | resolution: {integrity: sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==}
222 | engines: {node: '>=10'}
223 | dependencies:
224 | array-union: 2.1.0
225 | dir-glob: 3.0.1
226 | fast-glob: 3.2.5
227 | ignore: 5.1.8
228 | merge2: 1.4.1
229 | slash: 3.0.0
230 | dev: false
231 |
232 | /has/1.0.3:
233 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
234 | engines: {node: '>= 0.4.0'}
235 | dependencies:
236 | function-bind: 1.1.1
237 | dev: false
238 |
239 | /ignore/5.1.8:
240 | resolution: {integrity: sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==}
241 | engines: {node: '>= 4'}
242 | dev: false
243 |
244 | /is-arrayish/0.3.2:
245 | resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
246 | dev: false
247 |
248 | /is-core-module/2.4.0:
249 | resolution: {integrity: sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==}
250 | dependencies:
251 | has: 1.0.3
252 | dev: false
253 |
254 | /is-extglob/2.1.1:
255 | resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=}
256 | engines: {node: '>=0.10.0'}
257 | dev: false
258 |
259 | /is-glob/4.0.1:
260 | resolution: {integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==}
261 | engines: {node: '>=0.10.0'}
262 | dependencies:
263 | is-extglob: 2.1.1
264 | dev: false
265 |
266 | /is-number/7.0.0:
267 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
268 | engines: {node: '>=0.12.0'}
269 | dev: false
270 |
271 | /jju/1.4.0:
272 | resolution: {integrity: sha1-o6vicYryQaKykE+EpiWXDzia4yo=}
273 | dev: false
274 |
275 | /json-schema-traverse/0.4.1:
276 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
277 | dev: false
278 |
279 | /lit-element/3.0.0-rc.2:
280 | resolution: {integrity: sha512-2Z7DabJ3b5K+p5073vFjMODoaWqy5PIaI4y6ADKm+fCGc8OnX9fU9dMoUEBZjFpd/bEFR9PBp050tUtBnT9XTQ==}
281 | dependencies:
282 | '@lit/reactive-element': 1.0.0-rc.2
283 | lit-html: 2.0.0-rc.3
284 | dev: false
285 |
286 | /lit-html/2.0.0-rc.3:
287 | resolution: {integrity: sha512-Y6P8LlAyQuqvzq6l/Nc4z5/P5M/rVLYKQIRxcNwSuGajK0g4kbcBFQqZmgvqKG+ak+dHZjfm2HUw9TF5N/pkCw==}
288 | dependencies:
289 | '@types/trusted-types': 1.0.6
290 | dev: false
291 |
292 | /lit/2.0.0-rc.2:
293 | resolution: {integrity: sha512-BOCuoJR04WaTV8UqTKk09cNcQA10Aq2LCcBOiHuF7TzWH5RNDsbCBP5QM9sLBSotGTXbDug/gFO08jq6TbyEtw==}
294 | dependencies:
295 | '@lit/reactive-element': 1.0.0-rc.2
296 | lit-element: 3.0.0-rc.2
297 | lit-html: 2.0.0-rc.3
298 | dev: false
299 |
300 | /merge2/1.4.1:
301 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
302 | engines: {node: '>= 8'}
303 | dev: false
304 |
305 | /micromatch/4.0.4:
306 | resolution: {integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==}
307 | engines: {node: '>=8.6'}
308 | dependencies:
309 | braces: 3.0.2
310 | picomatch: 2.3.0
311 | dev: false
312 |
313 | /path-parse/1.0.7:
314 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
315 | dev: false
316 |
317 | /path-type/4.0.0:
318 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
319 | engines: {node: '>=8'}
320 | dev: false
321 |
322 | /picomatch/2.3.0:
323 | resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==}
324 | engines: {node: '>=8.6'}
325 | dev: false
326 |
327 | /punycode/2.1.1:
328 | resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
329 | engines: {node: '>=6'}
330 | dev: false
331 |
332 | /qr-creator/1.0.0:
333 | resolution: {integrity: sha512-C0cqfbS1P5hfqN4NhsYsUXePlk9BO+a45bAQ3xLYjBL3bOIFzoVEjs79Fado9u9BPBD3buHi3+vY+C8tHh4qMQ==}
334 | dev: false
335 |
336 | /queue-microtask/1.2.3:
337 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
338 | dev: false
339 |
340 | /resolve/1.19.0:
341 | resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==}
342 | dependencies:
343 | is-core-module: 2.4.0
344 | path-parse: 1.0.7
345 | dev: false
346 |
347 | /reusify/1.0.4:
348 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
349 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
350 | dev: false
351 |
352 | /run-parallel/1.2.0:
353 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
354 | dependencies:
355 | queue-microtask: 1.2.3
356 | dev: false
357 |
358 | /simple-swizzle/0.2.2:
359 | resolution: {integrity: sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=}
360 | dependencies:
361 | is-arrayish: 0.3.2
362 | dev: false
363 |
364 | /slash/3.0.0:
365 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
366 | engines: {node: '>=8'}
367 | dev: false
368 |
369 | /tabbable/5.2.0:
370 | resolution: {integrity: sha512-0uyt8wbP0P3T4rrsfYg/5Rg3cIJ8Shl1RJ54QMqYxm1TLdWqJD1u6+RQjr2Lor3wmfT7JRHkirIwy99ydBsyPg==}
371 | dev: false
372 |
373 | /to-regex-range/5.0.1:
374 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
375 | engines: {node: '>=8.0'}
376 | dependencies:
377 | is-number: 7.0.0
378 | dev: false
379 |
380 | /tslib/1.14.1:
381 | resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
382 | dev: false
383 |
384 | /uri-js/4.4.1:
385 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
386 | dependencies:
387 | punycode: 2.1.1
388 | dev: false
389 |
390 | /vscode-html-languageservice/4.0.5:
391 | resolution: {integrity: sha512-9ZKp7nfR6ObUA+K65GfgDPdOmXaPH8MOWxE2RwWF3tVnVMq2w+COKjDNHMvv+uNxtmaRT7/skls7CD/HzrW99w==}
392 | dependencies:
393 | vscode-languageserver-textdocument: 1.0.1
394 | vscode-languageserver-types: 3.16.0
395 | vscode-nls: 5.0.0
396 | vscode-uri: 3.0.2
397 | dev: false
398 |
399 | /vscode-languageserver-textdocument/1.0.1:
400 | resolution: {integrity: sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA==}
401 | dev: false
402 |
403 | /vscode-languageserver-types/3.16.0:
404 | resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==}
405 | dev: false
406 |
407 | /vscode-nls/5.0.0:
408 | resolution: {integrity: sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA==}
409 | dev: false
410 |
411 | /vscode-uri/3.0.2:
412 | resolution: {integrity: sha512-jkjy6pjU1fxUvI51P+gCsxg1u2n8LSt0W6KrCNQceaziKzff74GoWmjVG46KieVzybO1sttPQmYfrwSHey7GUA==}
413 | dev: false
414 |
--------------------------------------------------------------------------------
/src/website/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.fs]
2 | indent_size=2
3 | max_line_length=80
4 | fsharp_single_argument_web_mode=true
--------------------------------------------------------------------------------
/src/website/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "sutil-shoelace"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/website/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | [Bb]in/
4 | [Oo]bj/
5 |
6 | package-lock.json
7 | bundle.js
8 | *.fs.js
9 | .fake
10 | .ionide
11 |
12 | dist/
13 | .cache/
--------------------------------------------------------------------------------
/src/website/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Sutil Elmish
2 |
3 | ## Changelog (most recent first)
4 |
5 | - Updated to Sutil 1.0.0-alpha-004
6 | - Converted to Sutil 0.2-*-* (latest Feliz.Engine)
7 | - Remove transitive dependencies from fsproj
8 | - Remove use of .> operator
9 | - Rename counter to getCounter
10 | - Formatting
--------------------------------------------------------------------------------
/src/website/README.md:
--------------------------------------------------------------------------------
1 | ## Sutil Template for Elmish
2 |
3 | This is a Sutil (**Svelte**) application template which kind of shows a bit how you can* structure sutil applications and work with stores
4 |
5 | ### Quick Start
6 |
7 | ```
8 | dotnet tool restore
9 | pnpm install # or npm install or yarn install
10 | pnpm start # or npm run start or yarn run start
11 | ```
12 |
13 | > \* this is not a strict way to do it it's just *A Way* to do it so feel free to remove/add whatever you need in your day to day
14 |
--------------------------------------------------------------------------------
/src/website/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "dist",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/website/markdown.pl.js:
--------------------------------------------------------------------------------
1 | const hljs = require('highlight.js');
2 | const fs = require('fs/promises');
3 |
4 | /**
5 | *
6 | * @param {import('snowpack').SnowpackConfig} snowpackConfig
7 | * @param {Record | undefined | null} pluginOptions
8 | * @returns {import('snowpack').SnowpackPlugin}
9 | */
10 | function markdownPlugin(snowpackConfig, pluginOptions) {
11 | const Md = require('markdown-it')({
12 | breaks: true,
13 | typographer: true,
14 | linkify: true,
15 | html: true,
16 | highlight(str, lang) {
17 | if (lang && hljs.getLanguage(lang)) {
18 | try {
19 | return hljs.highlight(str, { language: lang }).value;
20 | } catch (__) { }
21 | }
22 | return ''; // use external default escaping
23 | },
24 | ...(pluginOptions && pluginOptions)
25 | });
26 |
27 | return {
28 | resolve: {
29 | input: [".md"],
30 | output: [".html"]
31 | },
32 | async load({ filePath }) {
33 | const content = await fs.readFile(filePath, { encoding: 'utf8' });
34 | return Md.render(content);
35 | }
36 | };
37 |
38 | }
39 |
40 |
41 | module.exports = markdownPlugin;
--------------------------------------------------------------------------------
/src/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "prestart": "dotnet tool restore",
5 | "start": "dotnet fable watch src --run snowpack dev",
6 | "build": "pnpm prestart && dotnet fable src --run snowpack build",
7 | "deploy": "pnpm build && firebase deploy"
8 | },
9 | "devDependencies": {
10 | "@snowpack/plugin-dotenv": "~2.1.0",
11 | "firebase-tools": "^9.12.1",
12 | "markdown-it": "~12.0.6",
13 | "rollup": "~2.50.2",
14 | "snowpack": "~3.5.1"
15 | },
16 | "dependencies": {
17 | "@microsoft/fast-components": "^1.21.6",
18 | "@microsoft/fast-foundation": "1.24.6",
19 | "@shoelace-style/shoelace": "2.0.0-beta.43",
20 | "firacode": "^5.2.0",
21 | "highlight.js": "~11.0.1",
22 | "lodash-es": "4.17.21",
23 | "navigo": "~8.11.1"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/website/public/fable.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngelMunoz/Sutil.Generator/ff146e00929ba371cb36740e51418538c79d01d7/src/website/public/fable.ico
--------------------------------------------------------------------------------
/src/website/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sutil.Shoelace
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/website/snowpack.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import("snowpack").SnowpackUserConfig } */
2 | module.exports = {
3 |
4 | mount: {
5 | public: { url: '/', static: true },
6 | src: { url: '/dist' },
7 | 'node_modules/@shoelace-style/shoelace/dist/assets': { url: '/shoelace/assets', static: true },
8 | 'node_modules/firacode/distr/woff': { url: '/woff', static: true },
9 | 'node_modules/firacode/distr/woff2': { url: '/woff2', static: true },
10 | '../Sutil.Shoelace': { url: '/Sutil.Shoelace', static: true },
11 | '../Sutil.Fast': { url: '/Sutil.Fast', static: true },
12 | },
13 | plugins: [
14 | '@snowpack/plugin-dotenv',
15 | './markdown.pl.js'
16 | ],
17 | routes: [],
18 | optimize: {
19 | /* Example: Bundle your final build: */
20 | bundle: true,
21 | splitting: true,
22 | treeshake: true,
23 | manifest: true,
24 | target: 'es2017',
25 | minify: true
26 | },
27 | packageOptions: {
28 | /* ... */
29 | polyfillNode: true
30 | },
31 | devOptions: {
32 | /* ... */
33 | },
34 | buildOptions: {
35 | /* ... */
36 | clean: true,
37 | out: "dist",
38 | htmlFragments: true
39 | },
40 | exclude: [
41 | "**/*.{fs,fsproj}",
42 | "**/bin/**",
43 | "**/obj/**"
44 | ],
45 | /* ... */
46 | };
--------------------------------------------------------------------------------
/src/website/src/App.fs:
--------------------------------------------------------------------------------
1 | module App
2 |
3 | open Browser.Dom
4 | open Browser.Types
5 | open Fable.Core
6 | open Fable.Core.DynamicExtensions
7 | open Sutil
8 | open Sutil.DOM
9 | open Sutil.Attr
10 | open Sutil.Styling
11 | open Sutil.Shoelace
12 | open Sutil.Fast
13 | open Types
14 | open Router
15 | open Components
16 | open Sutil.Fast.FastDesignSystemProvider
17 |
18 | type State = { page: Page; navOpen: bool }
19 |
20 | type Msg =
21 | | NavigateTo of Page
22 | | ToggleNav
23 |
24 | []
25 | let registerThemeEventListener (onThemeChange: bool -> unit) = jsNative
26 |
27 | []
28 | let getPalette (color: string) = jsNative
29 |
30 | let init () : State * Cmd =
31 | { page = Home; navOpen = false }, Cmd.none
32 |
33 | let update (msg: Msg) (state: State) : State * Cmd =
34 | match msg with
35 | | NavigateTo page -> { state with page = page }, Cmd.none
36 | | ToggleNav ->
37 | { state with
38 | navOpen = not state.navOpen },
39 | Cmd.none
40 |
41 | let navigateTo (dispatch: Dispatch) (page: Page) =
42 | NavigateTo page |> dispatch
43 |
44 | let view () =
45 | let state, dispatch = Store.makeElmish init update ignore ()
46 |
47 | let theme =
48 | Store.make
49 | {| luminance = 1.
50 | backgroundColor = "#E1E1E1" |}
51 |
52 | let navigateTo = navigateTo dispatch
53 |
54 | let changeTheme isDark =
55 | if isDark then
56 | document.body.classList.add ("sl-theme-dark")
57 |
58 | theme
59 | <~ {| luminance = 0.23
60 | backgroundColor = "#1E1E1E" |}
61 | else
62 | document.body.classList.remove ("sl-theme-dark")
63 |
64 | theme
65 | <~ {| luminance = 1.
66 | backgroundColor = "#FFFFFF" |}
67 |
68 | registerThemeEventListener changeTheme
69 |
70 | Router.on "/" (fun _ -> navigateTo Home) |> ignore
71 |
72 | Router.on
73 | ":library/docs/:page"
74 | (fun (mtc: Match option) ->
75 | match mtc with
76 | | Some mtc ->
77 | match mtc.data with
78 | | Some { library = library; page = None } ->
79 | let page = "getting-started"
80 | let docs = Docs(library, page)
81 | navigateTo docs
82 | | Some { library = library; page = page } ->
83 | let page =
84 | page |> Option.defaultValue "getting-started"
85 |
86 | let docs = Docs(library, page)
87 | navigateTo docs
88 | | None -> navigateTo Home
89 | | None -> navigateTo Home)
90 | |> ignore
91 |
92 | Router
93 | .notFound(fun _ -> navigateTo NotFound)
94 | .resolve()
95 |
96 | let (|Shoelace|Fast|) =
97 | function
98 | | "fast" -> Fast
99 | | _ -> Shoelace
100 |
101 | let page =
102 | let location = getCurrentLocation ()
103 |
104 | match location with
105 | | [||] -> Page.Home
106 | | [| "fast"; "docs"; name |] -> Page.Docs("fast", name)
107 | | [| "shoelace"; "docs"; name |] -> Page.Docs("shoelace", name)
108 | | _ -> Page.NotFound
109 |
110 | navigateTo page
111 |
112 |
113 | let background =
114 | theme .> (fun theme -> theme.backgroundColor)
115 |
116 | let luminance = theme .> (fun theme -> theme.luminance)
117 |
118 | let setColorPallete (e: Event) =
119 | let el =
120 | // We'll cast this heare for clarity although, it is not necessary
121 | e.target :?> FastDesignSystemProvider
122 | // accentPallete is not an attribute hence why we'll assign it dynamically
123 | el.["accentPalette"] <- getPalette "#0ea5e9"
124 |
125 | Html.app [
126 | disposeOnUnmount [ state ]
127 | Fast.FastDesignSystemProvider [
128 | class' "main-provider"
129 | onMount setColorPallete []
130 | Attr.custom ("use-defaults", "true")
131 | Attr.custom ("density", "1")
132 | Attr.custom ("corner-radius", "5")
133 | // workaround we usually would bind to the attribute not the property
134 | Bind.attr ("backgroundColor", background)
135 | Bind.attr ("baseLayerLuminance", luminance)
136 | Html.nav [
137 | class' "app-nav"
138 | Html.section [
139 | Shoelace.SlButton [
140 | type' "text"
141 | text "Sutil.Shoelace"
142 | onClick (fun _ -> Router.navigate "/" None) []
143 | ]
144 | Shoelace.SlButton [
145 | type' "text"
146 | text "Shoelace"
147 | Shoelace.SlIcon [
148 | Attr.name "book"
149 | Attr.slot "prefix"
150 | ]
151 | onClick (fun _ -> Router.navigate "shoelace/docs/index" None) []
152 | ]
153 | Shoelace.SlButton [
154 | type' "text"
155 | text "Fast"
156 | Shoelace.SlIcon [
157 | Attr.name "book"
158 | Attr.slot "prefix"
159 | ]
160 | onClick (fun _ -> Router.navigate "fast/docs/index" None) []
161 | ]
162 | ]
163 | Html.section [
164 | class' "show-on-mobile"
165 | Shoelace.SlButton [
166 | type' "text"
167 | text "Menu"
168 | Shoelace.SlIcon [
169 | Attr.name "list"
170 | Attr.slot "prefix"
171 | ]
172 | onClick (fun _ -> dispatch ToggleNav) []
173 | ]
174 | ]
175 | ]
176 | Html.main [
177 | Desktop.Sidenav
178 | Mobile.Sidenav [
179 | Bind.attr ("open", (state .> fun state -> state.navOpen))
180 | on "sl-hide" (fun _ -> dispatch ToggleNav) []
181 | ]
182 |
183 | bindFragment state
184 | <| fun state ->
185 | match state.page with
186 | | Home -> Pages.Home.view ()
187 | | Docs (library, page) -> Pages.Docs.view library page
188 | | Library library ->
189 | match library with
190 | | Shoelace -> Pages.Shoelace.view ()
191 | | Fast -> Pages.Fast.view ()
192 | | NotFound -> Html.article [ text "NotFound" ]
193 | ]
194 | ]
195 |
196 | ]
197 |
--------------------------------------------------------------------------------
/src/website/src/App.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net5.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/website/src/Components/Sidenav.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Components.Sidenav
3 |
4 | open Sutil
5 | open Sutil.Attr
6 | open Sutil.Shoelace
7 |
8 | open Routes
9 | open Sutil.DOM
10 |
11 | let shoelace =
12 | routes
13 | |> List.filter (fun route -> route.library = Shoelace)
14 | |> List.groupBy (fun r -> r.category)
15 |
16 | let fast =
17 | routes
18 | |> List.filter (fun route -> route.library = Fast)
19 | |> List.groupBy (fun r -> r.category)
20 |
21 |
22 | module Desktop =
23 | let private libraryMenu (library: list>) =
24 | Html.ul [
25 | class' "menu-categories"
26 | for (category, routes) in library do
27 | Html.li [
28 | class' "menu-categories-category"
29 | Html.h4 [ text (category.AsString()) ]
30 |
31 | Html.ul [
32 | for value in routes do
33 | Html.li [
34 | Shoelace.SlButton [
35 | type' "text"
36 | text value.name
37 | Attr.href value.href
38 | ]
39 | ]
40 | ]
41 | ]
42 | ]
43 |
44 | let Sidenav =
45 | Html.aside [
46 | class' "site-menu desktop"
47 | Html.ul [
48 | Html.h4 [
49 | Attr.slot "summary"
50 | text "Shoelace"
51 | ]
52 | libraryMenu shoelace
53 | ]
54 | Html.ul [
55 | Html.h4 [
56 | Attr.slot "summary"
57 | text "FAST"
58 | ]
59 | libraryMenu fast
60 | ]
61 | ]
62 |
63 |
64 |
65 | module Mobile =
66 | let private mobileMenu (library: list>) =
67 | Html.ul [
68 | class' "menu-categories"
69 | for (category, routes) in library do
70 | Html.li [
71 | class' "menu-categories-category"
72 | Html.h4 [ text (category.AsString()) ]
73 |
74 | Html.ul [
75 | for value in routes do
76 | Html.li [
77 | Shoelace.SlButton [
78 | type' "text"
79 | text value.name
80 | Attr.href value.href
81 | ]
82 | ]
83 | ]
84 | ]
85 | ]
86 |
87 | let Sidenav (content: NodeFactory seq) =
88 | Shoelace.SlDrawer [
89 | yield! content
90 | class' "site-menu"
91 | Html.label [
92 | Attr.slot "label"
93 | text "Select a library Documentation"
94 | ]
95 | Html.ul [
96 | Html.li [
97 | Html.h3 "Shoelace"
98 | mobileMenu shoelace
99 | ]
100 | ]
101 | Html.ul [
102 | Html.li [
103 | Html.h3 "Fast"
104 | mobileMenu fast
105 | ]
106 | ]
107 | ]
108 |
--------------------------------------------------------------------------------
/src/website/src/Main.fs:
--------------------------------------------------------------------------------
1 | module Main
2 |
3 | open Sutil
4 | open Sutil.DOM
5 | open Fable.Core
6 | open Fable.Core.JsInterop
7 | open Sutil.Fast.FastDesignSystemProvider
8 | open Sutil.Fast.FastButton
9 | open Sutil.Fast.FastAnchor
10 |
11 | // CSS Imports
12 |
13 | importSideEffects "@shoelace-style/shoelace/dist/themes/base.css"
14 | importSideEffects "@shoelace-style/shoelace/dist/themes/dark.css"
15 | importSideEffects "highlight.js/styles/nord.css"
16 | importSideEffects "firacode/distr/fira_code.css"
17 | importSideEffects "./styles.css"
18 |
19 | // JS Imports
20 |
21 | importSideEffects "@shoelace-style/shoelace/dist/components/alert/alert.js"
22 | importSideEffects "@shoelace-style/shoelace/dist/components/button/button.js"
23 |
24 | importSideEffects
25 | "@shoelace-style/shoelace/dist/components/checkbox/checkbox.js"
26 |
27 | importSideEffects "@shoelace-style/shoelace/dist/components/icon/icon.js"
28 |
29 | importSideEffects
30 | "@shoelace-style/shoelace/dist/components/icon-button/icon-button.js"
31 |
32 | importSideEffects "@shoelace-style/shoelace/dist/components/menu/menu.js"
33 |
34 | importSideEffects
35 | "@shoelace-style/shoelace/dist/components/menu-divider/menu-divider.js"
36 |
37 | importSideEffects
38 | "@shoelace-style/shoelace/dist/components/menu-item/menu-item.js"
39 |
40 | importSideEffects "@shoelace-style/shoelace/dist/components/drawer/drawer.js"
41 | importSideEffects "@shoelace-style/shoelace/dist/components/include/include.js"
42 |
43 | importSideEffects
44 | "@shoelace-style/shoelace/dist/components/dropdown/dropdown.js"
45 |
46 | importSideEffects "./Theme.js"
47 |
48 | let private FASTDesignSystemProvider =
49 | importMember "@microsoft/fast-components"
50 |
51 | let private FASTButton =
52 | importMember "@microsoft/fast-components"
53 |
54 | let private FASTAnchor =
55 | importMember "@microsoft/fast-components"
56 |
57 |
58 |
59 | []
60 | let setBasePath (path: string) : unit = jsNative
61 |
62 | setBasePath "shoelace"
63 | // Start the app
64 | App.view () |> mountElement "sutil-app"
65 |
--------------------------------------------------------------------------------
/src/website/src/Pages/Docs.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Pages.Docs
3 |
4 | open Sutil
5 | open Sutil.DOM
6 | open Sutil.Shoelace
7 | open Sutil.Styling
8 | open Sutil.Attr
9 |
10 | let view (library: string) (page: string) =
11 | Html.article [
12 | class' "doc-page"
13 | Shoelace.SlInclude [
14 | Attr.src $"/dist/docs/{library}/{page}.html"
15 | Attr.custom ("allow-scripts", "true")
16 | ]
17 | ]
18 |
--------------------------------------------------------------------------------
/src/website/src/Pages/Fast.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Pages.Fast
3 |
4 | open Sutil
5 |
6 | let view () = Pages.Docs.view "fast" "index"
7 |
--------------------------------------------------------------------------------
/src/website/src/Pages/Home.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Pages.Home
3 |
4 | open Sutil
5 | open Sutil.Shoelace
6 | open Sutil.Fast
7 | open Sutil.Styling
8 | open Sutil.DOM
9 | open Sutil.Attr
10 |
11 | open type Feliz.length
12 |
13 | let view () =
14 | Html.article [
15 | class' "home-page"
16 | Html.section [
17 | Shoelace.SlInclude [
18 | Attr.src "/dist/docs/home.html"
19 | ]
20 | ]
21 | Html.section [
22 | class' "row"
23 | Fast.FastAnchor [
24 | Attr.href "https://fast.design"
25 | Attr.target "_blank"
26 | Attr.custom ("appearance", "outline")
27 | text "Get to know more about FAST"
28 | ]
29 | Shoelace.SlButton [
30 | Attr.target "_blank"
31 | Attr.href "https://shoelace.style"
32 | text "Get to know more about Shoelace"
33 | ]
34 | ]
35 | Html.section [
36 | Shoelace.SlInclude [
37 | Attr.src "/dist/docs/home-2.html"
38 | ]
39 | ]
40 | ]
41 | |> withStyle [
42 | rule
43 | "section.row"
44 | [ Css.marginTop (em 2)
45 | Css.marginBottom (em 2)
46 | Css.displayFlex
47 | Css.custom ("justify-content", "space-evenly")
48 | Css.alignItemsCenter ]
49 | ]
50 |
--------------------------------------------------------------------------------
/src/website/src/Pages/Shoelace.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Pages.Shoelace
3 |
4 | open Sutil
5 |
6 | let view () = Html.article [ text "" ]
7 |
--------------------------------------------------------------------------------
/src/website/src/Router.fs:
--------------------------------------------------------------------------------
1 | module Router
2 |
3 | open Fable.Core
4 |
5 | // handler
6 | // hooks
7 | type Route = {| name: string; path: string |}
8 |
9 | type Match<'UrlParams, 'QueryParams> =
10 | {| url: string
11 | queryString: string
12 | hashString: string
13 | Route: Route
14 | data: 'UrlParams option
15 | ``params``: 'QueryParams option |}
16 |
17 | type RouteHandler<'UrlParams, 'QueryParams> =
18 | Match<'UrlParams, 'QueryParams> option -> unit
19 |
20 | type Router =
21 | abstract member on :
22 | string ->
23 | RouteHandler<'UrlParams, 'QueryParams> ->
24 | Router
25 |
26 | abstract member notFound : (unit -> unit) -> Router
27 | abstract member navigate : string -> obj option -> unit
28 | abstract member navigateByName : string -> 'T option -> unit
29 | abstract member getCurrentLocation : unit -> Match<_, _>
30 | abstract member resolve : unit -> unit
31 |
32 |
33 | []
34 | let Router : Router = jsNative
35 |
36 | []
37 | let getCurrentLocation : unit -> string array = jsNative
38 |
--------------------------------------------------------------------------------
/src/website/src/Router.js:
--------------------------------------------------------------------------------
1 | import Navigo from 'navigo';
2 |
3 | export const Router = new Navigo("/", { hash: true });
4 |
5 | export function getCurrentLocation() {
6 | return Router
7 | .getCurrentLocation()
8 | .hashString
9 | .split("/")
10 | .filter(s => s);
11 | }
--------------------------------------------------------------------------------
/src/website/src/Routes.fs:
--------------------------------------------------------------------------------
1 | module Routes
2 |
3 | type Category =
4 | | Guides
5 | | Components
6 | | Uncategorized
7 |
8 | member this.AsString() =
9 | match this with
10 | | Guides -> "Guides"
11 | | Components -> "Components"
12 | | Uncategorized -> ""
13 |
14 | type Library =
15 | | Shoelace
16 | | Fast
17 |
18 | type DocsRoute =
19 | { name: string
20 | href: string
21 | library: Library
22 | category: Category }
23 |
24 |
25 | let routes =
26 | [ { name = "Getting Started"
27 | href = "#/shoelace/docs/getting-started"
28 | library = Shoelace
29 | category = Guides }
30 | { name = "Getting Started"
31 | href = "#/fast/docs/getting-started"
32 | library = Fast
33 | category = Guides }
34 | { name = "Themes"
35 | href = "#/fast/docs/themes"
36 | library = Fast
37 | category = Guides }
38 | { name = "How to use components"
39 | href = "#/shoelace/docs/components"
40 | library = Shoelace
41 | category = Guides }
42 | { name = "Elmish"
43 | href = "#/shoelace/docs/elmish"
44 | library = Shoelace
45 | category = Guides }
46 | { name = "Stores"
47 | href = "#/shoelace/docs/stores"
48 | library = Shoelace
49 | category = Guides } ]
50 |
--------------------------------------------------------------------------------
/src/website/src/Theme.js:
--------------------------------------------------------------------------------
1 | import { parseColorString, createColorPalette } from '@microsoft/fast-components';
2 | const prefersDarkQuery = window.matchMedia('(prefers-color-scheme: dark)');
3 | const isDark = () => prefersDarkQuery.matches;
4 | export function registerThemeEventListener(cb) {
5 | cb(isDark());
6 | prefersDarkQuery.addEventListener('change', () => cb(isDark()));
7 | }
8 |
9 | export function getPalette(color) {
10 | return createColorPalette(parseColorString(color));
11 | }
--------------------------------------------------------------------------------
/src/website/src/Types.fs:
--------------------------------------------------------------------------------
1 | module Types
2 |
3 | type Page =
4 | | Home
5 | | Library of string
6 | | Docs of library: string * docSite: string
7 | | NotFound
8 |
9 | type Theme =
10 | | Light
11 | | Dark
12 |
13 | type DocsUrlData =
14 | { library: string
15 | page: string option }
16 |
--------------------------------------------------------------------------------
/src/website/src/docs/fast/getting-started.md:
--------------------------------------------------------------------------------
1 | [Javascript Modules]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
2 | [femto]: https://github.com/Zaid-Ajaj/Femto
3 | [Theming]: #/fast/docs/theming
4 |
5 | # Getting Started
6 |
7 | ```sh
8 | dotnet add package Sutil.Fast --version
9 | pnpm install @microsoft/fast-components@ @microsoft/fast-foundation lodash-es # or npm install @microsoft/Fast@
10 | ```
11 | If you are using [femto] then you just need to do
12 |
13 | - `femto install Sutil.Fast --version `
14 |
15 | # After Install
16 |
17 | > Please feel free to also check Fast's [documentation](https://www.fast.design/docs/introduction/).
18 |
19 |
20 | Once you're done installing Sutil.Fast, you should be able to do the following:
21 |
22 | ```fsharp
23 | open Sutil.Fast
24 |
25 | Fast.FastDesignSystemProvider [
26 | Attr.custom ("use-defaults", "true")
27 | Attr.style "padding: 1em"
28 | Fast.FastButton [
29 | Attr.custom("appearance","primary")
30 | text "Hello, there!"
31 | ]
32 | ]
33 | ```
34 |
35 | but that doesn't mean that you will see yout components right away, Fast uses [Javascript Modules] to load its components.
36 |
37 | To do that we need to load the components in one of the following ways in your `Main.fs` or entry point file of your Sutil application
38 |
39 | ```fsharp
40 | // Main.fs or App.fs
41 | importSideEffects "@microsoft/fast-components"
42 | ```
43 | This will load all the Fast components to the browser and then you should be able to see the following
44 |
45 |
46 |
47 | Hello, there!
48 |
49 |
50 | Fast uses a design system provider that allows you to easily theme your application or parts of your application as much as you desire, if you want to learn more about that check
51 |
52 | Fast Themes
53 |
54 | For development purposes this should be enough. If you want to optimize for production I'd encourage you to keep reading the next section
55 | ## Cherry-pick components
56 |
57 | > Please feel free to also check Fast's [documentation](https://www.fast.design/docs/components/getting-started#from-npm)
58 |
59 |
60 | To optimize your bundle sizes and prevent unused code ending up in your user's storage, then cherry-picking is the ideal option since you will only load and bundle the components you're using.
61 |
62 | ```fsharp
63 | open Sutil.Fast.FastDesignSystemProvider
64 | open Sutil.Fast.FastButton
65 | open Sutil.Fast.FastAnchor
66 |
67 | let private FASTDesignSystemProvider = importMember"@microsoft/fast-components"
68 | let private FASTButton = importMember"@microsoft/fast-components"
69 | let private FASTAnchor = importMember"@microsoft/fast-components"
70 | ```
71 |
72 | This will help your bundler to prevent tree-shaking the elements that you're using.
73 | Now you're ready to start doing some Sutil.Fast!
--------------------------------------------------------------------------------
/src/website/src/docs/fast/index.md:
--------------------------------------------------------------------------------
1 | [Sutil.Generator]: https://github.com/AngelMunoz/Sutil.Generator
2 | [FAST]: https://fast.design/
3 | [Sutil]: https://davedawkins.github.io/Sutil/
4 | [femto]: https://github.com/Zaid-Ajaj/Femto
5 |
6 | # Sutil.Fast
7 | [](https://www.nuget.org/packages/Sutil.Fast)
8 |
9 | **Sutil.Fast** is a small [Sutil] wrapper built on top of [FAST] a web component library.
10 |
11 | **Sutil.Fast** is autogenerated thanks to the [Sutil.Generator] project. Sutil.Fast also adds some helpers to allow you interact in a seamless way with Sutil
12 |
13 | ### Installation
14 |
15 | ```sh
16 | dotnet add package Sutil.Fast --version
17 | pnpm install @microsoft/fast-components@ @microsoft/fast-foundation lodash-es # or npm install @microsoft/shoelace@
18 | ```
19 | If you are using [femto] then you just need to do
20 |
21 | - `femto install Sutil.Fast --version `
22 |
23 | # Getting Started
24 |
25 | - [Documentation](#/fast/docs/getting-started)
26 | - [Components](#/fast/docs/components)
27 |
28 |
29 | ## Versioning
30 | Sutil.Fast is built against the latest version of [Fast] in case there are bug fixes on the generator itself or additions for quality of life the nuget version will add a last dot and a number to indicate this was a new version of the generated package.
31 |
32 | Example:
33 | - `Fast version: v1.21.6`
34 |
35 | **Normal Release ->** `Sutil.Fast version: v1.21.6`
36 |
37 | - `Fast version: v1.21.6`
38 | **Sutil.Generator Fix/QL Update ->** `Sutil.Fast version: Fast version: v1.21.6.1`
39 |
40 | This is expected to be done in rare ocations when a fix or QL update really is needed, for the most part most of the QL updates will be sent with the next Fast release to prevent distancing from the shoelace version
41 |
--------------------------------------------------------------------------------
/src/website/src/docs/fast/themes.md:
--------------------------------------------------------------------------------
1 | [Themes]: https://www.fast.design/docs/design-systems/overview
2 |
3 | # FAST Themes
4 |
5 | > Please also check FAST documentation on [Themes] for more and better information.
6 |
7 | Fast includes a system design provider which allows you to write theming in your website from the start.
8 |
9 | In the case of `Sutil.Fast` once you [have imported](#/fast/docs/getting-started) the required scripts you can do the following
10 |
11 | ```fsharp
12 | open Sutil.Fast
13 |
14 | Fast.FastDesignSystemProvider [
15 | // initialize all of the properties
16 | Attr.custom ("use-defaults", "true")
17 | Attr.custom ("density", "1")
18 | Attr.custom ("corner-radius", "5")
19 | Attr.custom ("background-color", "#000000")
20 | Attr.custom ("base-layer-luminance", "0.23")
21 |
22 | Fast.FastButton [
23 | Attr.custom("appearance", "accent")
24 | text "Hey there!"
25 | ]
26 | ]
27 |
28 | ```
29 | that will give you a dark style with the default accent
30 |
31 |
32 | Hey there!
33 |
34 |
35 |
36 | # Accent color pallete
37 |
38 | > Please also note that FAST uses a lot of javascript for creating custom design systems, so in those cases you are advised to simply add a javascript file for interop (unless you have the time to create bindings for the javascript code in the FAST library)
39 |
40 |
41 |
42 | This part is not obligatory but it might be the easiest way to do it until there's a bindings package for the javascript part of FAST
43 | ```js
44 | import { parseColorString, createColorPalette } from '@microsoft/fast-components';
45 |
46 | // generate a new color palette for the color you chose
47 | export function getPalette(color) {
48 | return createColorPalette(parseColorString(color));
49 | }
50 | ```
51 |
52 |
53 | ```fsharp
54 | open Browser.Types
55 |
56 | open Fable.Core
57 | open Fable.Core.DynamicExtensions
58 |
59 | open Sutil.Fast
60 | open Sutil.Fast.FastDesignSystemProvider
61 |
62 | []
63 | let getPalette (color: string) = jsNative
64 |
65 | let setColorPallete (e: Event) =
66 | let el =
67 | // We'll cast this heare for clarity although, it is not necessary
68 | e.target :?> FastDesignSystemProvider
69 | // accentPallete is not an attribute hence why we'll assign it dynamically
70 | el.["accentPalette"] <- getPalette "#0ea5e9"
71 |
72 | let view() =
73 | Fast.FastDesignSystemProvider [
74 | // when the component is mounted set the collor palette
75 | onMount setColorPallete []
76 | // initialize all of the properties
77 | Attr.custom ("use-defaults", "true")
78 | // set custom values for existing attributes
79 | Attr.custom ("density", "1")
80 | Attr.custom ("corner-radius", "5")
81 | Attr.custom ("background-color", "#000000")
82 | Attr.custom ("base-layer-luminance", "0.23")
83 |
84 | Fast.FastButton [
85 | Attr.custom("appearance", "accent")
86 | text "Hey there!"
87 | ]
88 | ]
89 | ```
90 | That should look like this:
91 |
92 |
93 | Hey there!
94 |
95 |
96 |
101 |
102 | # Dark/Light Mode
103 |
104 | Supporting Light and Dark modes is not too hard and should be simple to do. for that we just need to listen for the media query change, add a store and register a callback to react to those changes.
105 |
106 | Let's add a few things to our JS file
107 |
108 | ```js
109 | import { parseColorString, createColorPalette } from '@microsoft/fast-components';
110 |
111 | const prefersDarkQuery = window.matchMedia('(prefers-color-scheme: dark)');
112 | const isDark = () => prefersDarkQuery.matches;
113 |
114 | export function registerThemeEventListener(cb) {
115 | cb(isDark());
116 | prefersDarkQuery.addEventListener('change', () => cb(isDark()));
117 | }
118 |
119 | export function getPalette(color) {
120 | return createColorPalette(parseColorString(color));
121 | }
122 | ```
123 |
124 | ```fsharp
125 | open Browser.Types
126 |
127 | open Fable.Core
128 | open Fable.Core.DynamicExtensions
129 | open Sutil
130 | open Sutil.Fast
131 | open Sutil.Fast.FastDesignSystemProvider
132 |
133 |
134 | []
135 | let registerThemeEventListener (onThemeChange: bool -> unit) = jsNative
136 |
137 | []
138 | let getPalette (color: string) = jsNative
139 |
140 | let setColorPallete (e: Event) =
141 | let el =
142 | // We'll cast this heare for clarity although, it is not necessary
143 | e.target :?> FastDesignSystemProvider
144 | // accentPallete is not an attribute hence why we'll assign it dynamically
145 | el.["accentPalette"] <- getPalette "#0ea5e9"
146 |
147 | let changeTheme isDark =
148 | // theme <~ is the equivalent to
149 | // Store.set {| ...values ... |} theme
150 | if isDark then
151 | theme
152 | <~ {| luminance = 0.23
153 | backgroundColor = "#1E1E1E" |}
154 | else
155 | theme
156 | <~ {| luminance = 1.
157 | backgroundColor = "#FFFFFF" |}
158 | // register our callback
159 | registerThemeEventListener changeTheme
160 |
161 | let background =
162 | // theme .> (fun theme -> theme.backgroundColor) is the equivalent to
163 | // Store.map (fun theme -> theme.backgroundColor) theme
164 | theme .> (fun theme -> theme.backgroundColor)
165 |
166 | let luminance = theme .> (fun theme -> theme.luminance)
167 |
168 | let view() =
169 | let theme =
170 | Store.make
171 | // Initial values for your theme
172 | {| luminance = 1.
173 | backgroundColor = "#1E1E1E" |}
174 | Fast.FastDesignSystemProvider [
175 | // when the component is mounted set the collor palette
176 | onMount setColorPallete []
177 | // initialize all of the properties
178 | Attr.custom ("use-defaults", "true")
179 | // set custom values for existing attributes
180 | Attr.custom ("density", "1")
181 | Attr.custom ("corner-radius", "5")
182 | // workaround we usually would bind to the attribute not the property
183 | // e.g Bind.attr ("background-color", background)
184 | Bind.attr ("backgroundColor", background)
185 | Bind.attr ("baseLayerLuminance", luminance)
186 |
187 | Fast.FastButton [
188 | Attr.custom("appearance", "accent")
189 | text "Hey there!"
190 | ]
191 | ]
192 | ```
193 |
194 | That's precisely how this website is configured right now! for the sample we'll inver the colors just to show that you can use multiple providers and have multiple sections with different themes
195 |
196 |
197 | Hey there!
198 |
199 |
200 |
218 |
--------------------------------------------------------------------------------
/src/website/src/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | [Javascript Modules]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
2 | [femto]: https://github.com/Zaid-Ajaj/Femto
3 |
4 | # Getting Started
5 |
6 | ```sh
7 | dotnet add package Sutil.Shoelace --version
8 | pnpm install @shoelace-style/shoelace@ # or npm install @shoelace-style/shoelace@
9 | ```
10 | If you are using [femto] then you just need to do
11 |
12 | - `femto install Sutil.Shoelace --version `
13 |
14 | # After Install
15 |
16 | > Please feel free to also check shoelace's [documentation](https://shoelace.style/getting-started/installation).
17 |
18 |
19 | Once you're done installing Sutil.Shoelace, you should be able to do the following:
20 |
21 | ```fsharp
22 | open Sutil.Shoelace
23 |
24 | Shoelace.SlButton [
25 | type' "primary"
26 | text "Hello, there!"
27 | ]
28 | ```
29 |
30 | but that doesn't mean that you will see yout components right away, Shoelace uses [Javascript Modules] to load its components.
31 |
32 | To do that we need to load the components in one of the following ways in your `Main.fs` or entry point file of your Sutil application
33 |
34 | ```fsharp
35 | // Main.fs or App.fs
36 |
37 | importSideEffects "@shoelace-style/shoelace/dist/themes/base.css"
38 | // In case you want to support a dark theme check the guide at
39 | // https://shoelace.style/getting-started/themes?id=dark-mode
40 | // importSideEffects "@shoelace-style/shoelace/dist/themes/dark.css"
41 |
42 | importSideEffects "@shoelace-style/shoelace.js"
43 | ```
44 | This will load all the shoelace components to the browserand then you should be able to see the following
45 |
46 | Hello, there!
47 |
48 | For development purposes this should be enough. If you want to optimize for production I'd encourage you to keep reading the next section
49 | ## Cherry-pick components
50 |
51 | > Please feel free to also check shoelace's [documentation](https://shoelace.style/getting-started/installation?id=bundling)
52 |
53 |
54 | To optimize your bundle sizes and prevent unused code ending up in your user's storage, then cherry-picking is the ideal option since you will only load and bundle the components you're using.
55 |
56 | ```fsharp
57 |
58 | importSideEffects "@shoelace-style/shoelace/dist/components/button/button.js"
59 |
60 | importSideEffects "@shoelace-style/shoelace/dist/components/icon/icon.js"
61 | ```
62 |
63 | then if you try the following:
64 | ```fsharp
65 | Shoelace.SlButton [
66 | Shoelace.SlIcon [
67 | Attr.slot "prefix"
68 | Attr.name "info-circle"
69 | ]
70 | ]
71 | ```
72 | it should look like this
73 |
74 |
75 |
76 |
77 | If you wonder why the icon is not showing it means that you're not serving the icons statically
78 |
79 | you can fix that by selecting where are you going to mount the icon assets with your bundler
80 |
81 | ```fsharp
82 | []
83 | let setBasePath (path: string) : unit = jsNative
84 |
85 | setBasePath "shoelace"
86 | ```
87 |
88 |
89 | ### Snowpack
90 | In the case of snowpack its quite simple
91 |
92 | ```javascript
93 | /** @type {import("snowpack").SnowpackUserConfig } */
94 | module.exports = {
95 |
96 | mount: {
97 | public: { url: '/', static: true },
98 | src: { url: '/dist' },
99 | // tell snowpack to mount your assets in "shoelace/assets"
100 | 'node_modules/@shoelace-style/shoelace/dist/assets': { url: '/shoelace/assets', static: true }
101 | /* ... the rest of your config */
102 | },
103 | /* ... the rest of your config */
104 | };
105 | ```
106 |
107 | ### Webpack
108 | In the case of webpack we can use the copy plugin
109 | ```javascript
110 | module.exports = {
111 | /* ... the rest of your config ... */
112 | plugins: [
113 | new CopyPlugin({
114 | patterns: [
115 | // Copy Shoelace assets to dist/shoelace
116 | {
117 | from: path.resolve(__dirname, 'node_modules/@shoelace-style/shoelace/dist/assets'),
118 | to: path.resolve(__dirname, 'dist/shoelace/assets')
119 | }
120 | ]
121 | })
122 | /* ... the rest of your config ... */
123 | ]
124 | };
125 | ```
126 |
127 | Once we've done that either with webpack or snowpack our button will show like this
128 |
129 |
130 | Info
131 |
132 |
133 | Now you're ready to start doing some Sutil.Shoelace!
--------------------------------------------------------------------------------
/src/website/src/docs/home-2.md:
--------------------------------------------------------------------------------
1 | If you take a quick inspection in the browser devloper tools you'll find that both libraries work side by side, in fact the whole site has both libraries' elements scattered! and this doesn't really impact on the performance as long as you cherry pick components (check `getting started` on your favorite library).
2 |
3 | If you want to see more details on how these libraries look in F# take a look at the [website docs](https://github.com/AngelMunoz/Sutil.Generator/tree/master/src/website)!
--------------------------------------------------------------------------------
/src/website/src/docs/home.md:
--------------------------------------------------------------------------------
1 | [Sutil]: https://github.com/davedawkins/Sutil
2 | [Sutil.Generator]: https://github.com/AngelMunoz/Sutil.Generator
3 | [Web Components]: https://developer.mozilla.org/en-US/docs/Web/Web_Components
4 | [Shoelace]: https://shoelace.style/
5 | [Fast]: https://fast.design/
6 |
7 | # Sutil.Generator
8 |
9 | [Sutil.Generator] is a project that aims to bring the goodness of [Web Components] to F# thanks to [Sutil]
10 |
11 | Since Sutil is a pure F# And JS framework without other dependencies rather than Fable.Core and Feliz.Engine it allows itself to be quite interoperable with the rest of the web
12 |
13 | In this page we're using HTML elements, [Shoelace], and [FAST] components, all in the same place they don't fight each other, they just work completely fine
14 |
15 | See them working side by side below 👇🏽
16 |
--------------------------------------------------------------------------------
/src/website/src/docs/shoelace/components.md:
--------------------------------------------------------------------------------
1 | [Shoelace]: https://shoelace.style/
2 |
3 | # Sutil.Shoelace Components
4 |
5 | > For the full list of components please visit [Shoelace]'s website even though the components have been annotated with doc coments so VSCode, Rider or VS2019 pick them up you can follow the original docs to guide you through the components
6 |
7 | We build `Sutil.Shoelace` against a metadata file provided by the npm package, and we follow a few conventions to keep the API consistent and F# friendly
8 |
9 | ```html
10 |
11 | I Agree to send some feedback on this lackluster docs website
12 |
13 |
14 | This is a standard alert. You can customize its content and even the icon.
15 |
16 | ```
17 | In the case of Sutil.Shoelace we adopt the naming convention used for their React bindings
18 |
19 | ```fsharp
20 | // prefix with `Sl`
21 | Shoelace.SlAlert
22 | Shoelace.SlButton
23 | Shoelace.SlCheckbox
24 | ```
25 |
26 | We'll show a more complete example below.
27 |
28 | ```fsharp
29 | open Sutil.Shoelace
30 | // Optional open the static class so you don't need to type
31 | // Shoelace.Sl all the time
32 | open type Shoelace.Shoelace
33 | // If you need to access the element's attributes or the
34 | // binding to the native Web Component open the module
35 | // open Sutil.Shoelace.
36 | open Sutil.Shoelace.Alert
37 | open Sutil.Shoelace.Checkbox
38 |
39 | // the simple call signature is like any other Feliz' flavored DSL
40 | // pass a sequence of attributes/nodes to the component
41 | Shoelace.SlButton [
42 | // with open type Shoelace.Shoelace
43 | SlIcon [
44 | // many components have slots check shoelace's documentation website!
45 | Attr.slot "prefix"
46 | name "info-circle"
47 | ]
48 | text "My Button"
49 | ]
50 |
51 | let onChkChange (e: Browser.Types.Event) =
52 | // the SlCheckbox is a binding of the native Web Component
53 | // and inside Sutil.Shoelace.Checkbox
54 | let target = (e.target :?> SlCheckbox)
55 | printfn $"{target.``checked``}"
56 |
57 | SlCheckbox [
58 | text "I Agree to send some feedback on this lackluster docs website"
59 | // listen to events on the elements and their children
60 | // (no need to drill callback props!)
61 | on "sl-change" onChkChange []
62 | ]
63 |
64 | // once you open a particular component module you'll have access to it's attributes
65 | let alertAttrs =
66 | Store.make (
67 | // create an attribute record and set the initial values
68 | SlAlertAttributes.create ()
69 | |> SlAlertAttributes.withOpen false
70 | |> SlAlertAttributes.withType "primary"
71 | |> SlAlertAttributes.withClosable false
72 | )
73 |
74 | // the call signature is different, pass an IStore
75 | // then your nodes, becareful you might override an existing binding from the
76 | // attributes here if you try to put it again
77 | SlAlert(
78 | alertAttrs,
79 | [ SlIcon [
80 | Attr.slot "icon"
81 | Attr.name "info-circle"
82 | ]
83 | text
84 | "This is a standard alert. You can customize its content and even the icon." ]
85 | )
86 | ```
87 | That's the general idea of how to use them, there is not much more to it.
88 |
89 | Feel free to send some feedback in case something is weird or not comprehensible
--------------------------------------------------------------------------------
/src/website/src/docs/shoelace/elmish.md:
--------------------------------------------------------------------------------
1 | [Stores]: #/shoelace/docs/stores
2 |
3 | # Elmish
4 |
5 | > To check how to work without elmish check out [Stores]
6 |
7 | Working with elmish is fairly simple Sutil provides a few Store helpers that allow you to use elmish based components in a sutil'ish way
8 |
9 | ```fsharp
10 | let state, dispatch = Store.makeElmishSimple init update ignore ()
11 | // and
12 | let state, dispatch = Store.makeElmish init update ignore ()
13 | ```
14 | Differences:
15 | - `state` is now an `IStore` rather than just `State`
16 | - we can also dispose things when this elmish component is disposed (pass a function which disposes things instead of ignore)
17 | - The initial params are the last parameter
18 |
19 | Let's say for example you have the following elmish component:
20 |
21 | ```fsharp
22 | open Sutil
23 | open Sutil.Store
24 | open Sutil.Attr
25 | open Sutil.DOM
26 | // you can open Shoelace statically as well!
27 | open Sutil.Shoelace
28 |
29 | type State = { isOpen: bool }
30 |
31 | type Msg =
32 | | SetIsOpen of bool
33 |
34 | let init() =
35 | { isOpen = false }
36 |
37 | let update (msg: Msg) (state: State) =
38 | match msg with
39 | | SetIsOpen isOpen -> { state with isOpen = isOpen }
40 |
41 | let view() =
42 | let state, dispatch = Store.makeElmishSimple init update ignore ()
43 | Html.article [
44 | // don't forget to disposte the store
45 | // when the component gets disposed
46 | disposeOnUnmount [ state ]
47 |
48 | Shoelace.SlButton [ text "Open Externally" ]
49 | Shoelace.SlMenu [
50 | Shoelace.SlButton [ Attr.slot "trigger"; Attr.custom("caret", "true"); text "Edit" ]
51 | Shoelace.SlMenu [
52 | Shoelace.SlMenuItem [ text "Cut" ]
53 | Shoelace.SlMenuItem [ text "Copy" ]
54 | Shoelace.SlMenuItem [ text "Paste" ]
55 | Shoelace.SlMenuDivider []
56 | Shoelace.SlMenuItem [ text "Find" ]
57 | Shoelace.SlMenuItem [ text "Replace" ]
58 | ]
59 | ]
60 | ]
61 | ```
62 | If you click on the menu, it will work by itself, but if you wanted to do that from a different element that is not the menu, you'd like to trace such event with the elmish loop
63 |
64 |
65 | Open Externally
66 |
67 | Edit
68 |
69 | Cut
70 | Copy
71 | Paste
72 |
73 | Find
74 | Replace
75 |
76 |
77 |
78 |
79 | the normal thing here would be to dispatch an event
80 |
81 | ```fsharp
82 | Shoelace.SlButton [ text "Open Externally"; onClick (fun _ -> dispatch (SetIsOpen true)) [] ]
83 | ```
84 | this would trigger our elmish update, let's check how to bind that to our components
85 |
86 | ```fsharp
87 | let state, dispatch = Store.makeElmishSimple init update ignore ()
88 | // We'll create an observable that reads only the isOpen property
89 | let isOpenHelper = Store.map (fun state -> state.isOpen) state
90 |
91 | Html.article [
92 | Shoelace.SlButton [ text "Open Externally"; onClick (fun _ -> dispatch (SetIsOpen true)) [] ]
93 | Shoelace.SlMenu [
94 | // Bind.attr takes an observable
95 | // each time isOpen changes it will also flect these changes on the element
96 | Bind.attr("open", isOpenHelper)
97 | Shoelace.SlButton [ Attr.slot "trigger"; Attr.custom("caret", "true"); text "Edit" ]
98 | // ... omit the rest for brebity ...
99 | ]
100 | ]
101 | ```
102 |
103 |
104 | Open Externally
105 |
106 | Edit
107 |
108 | Cut
109 | Copy
110 | Paste
111 |
112 | Find
113 | Replace
114 |
115 |
116 |
117 |
118 | You can use this technique to bind any kind of value you want, be it either checked, open, closable, label you name it, binding stores and observables fits pretty well the elmish way of doing things
119 |
120 | ## Complete sample
121 |
122 | ```fsharp
123 | open Sutil
124 | open Sutil.Store
125 | open Sutil.Attr
126 | open Sutil.DOM
127 | // this time we'll do an open type
128 | open type Sutil.Shoelace
129 |
130 | type State = { isOpen: bool }
131 |
132 | type Msg =
133 | | SetIsOpen of bool
134 |
135 | let init() =
136 | { isOpen = false }
137 |
138 | let update (msg: Msg) (state: State) =
139 | match msg with
140 | | SetIsOpen isOpen -> { state with isOpen = isOpen }
141 |
142 | let view() =
143 | let state, dispatch = Store.makeElmishSimple init update ignore ()
144 | // We'll create an observable that reads only the isOpen property
145 | let isOpen = Store.map (fun state -> state.isOpen) state
146 |
147 | Html.article [
148 | // don't forget to disposte the store
149 | // when the component gets disposed
150 | disposeOnUnmount [ state ]
151 |
152 | SlButton [
153 | text "Open Externally"
154 | // trigger the elmish update
155 | onClick (fun _ -> dispatch (SetIsOpen true)) []
156 | ]
157 | SlMenu [
158 | // bind the observable
159 | Bind.attr("open", isOpen)
160 | SlButton [ Attr.slot "trigger"; Attr.custom("caret", "true"); text "Edit" ]
161 | SlMenu [
162 | SlMenuItem [ text "Cut" ]
163 | SlMenuItem [ text "Copy" ]
164 | SlMenuItem [ text "Paste" ]
165 | SlMenuDivider []
166 | SlMenuItem [ text "Find" ]
167 | SlMenuItem [ text "Replace" ]
168 | ]
169 | ]
170 | ]
171 | ```
--------------------------------------------------------------------------------
/src/website/src/docs/shoelace/getting-started.md:
--------------------------------------------------------------------------------
1 | [Javascript Modules]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
2 | [femto]: https://github.com/Zaid-Ajaj/Femto
3 |
4 | # Getting Started
5 |
6 | ```sh
7 | dotnet add package Sutil.Shoelace --version
8 | pnpm install @shoelace-style/shoelace@ # or npm install @shoelace-style/shoelace@
9 | ```
10 | If you are using [femto] then you just need to do
11 |
12 | - `femto install Sutil.Shoelace --version `
13 |
14 | # After Install
15 |
16 | > Please feel free to also check shoelace's [documentation](https://shoelace.style/getting-started/installation).
17 |
18 |
19 | Once you're done installing Sutil.Shoelace, you should be able to do the following:
20 |
21 | ```fsharp
22 | open Sutil.Shoelace
23 |
24 | Shoelace.SlButton [
25 | type' "primary"
26 | text "Hello, there!"
27 | ]
28 | ```
29 |
30 | but that doesn't mean that you will see yout components right away, Shoelace uses [Javascript Modules] to load its components.
31 |
32 | To do that we need to load the components in one of the following ways in your `Main.fs` or entry point file of your Sutil application
33 |
34 | ```fsharp
35 | // Main.fs or App.fs
36 |
37 | importSideEffects "@shoelace-style/shoelace/dist/themes/base.css"
38 | // In case you want to support a dark theme check the guide at
39 | // https://shoelace.style/getting-started/themes?id=dark-mode
40 | // importSideEffects "@shoelace-style/shoelace/dist/themes/dark.css"
41 |
42 | importSideEffects "@shoelace-style/shoelace.js"
43 | ```
44 | This will load all the shoelace components to the browserand then you should be able to see the following
45 |
46 | Hello, there!
47 |
48 | For development purposes this should be enough. If you want to optimize for production I'd encourage you to keep reading the next section
49 | ## Cherry-pick components
50 |
51 | > Please feel free to also check shoelace's [documentation](https://shoelace.style/getting-started/installation?id=bundling)
52 |
53 |
54 | To optimize your bundle sizes and prevent unused code ending up in your user's storage, then cherry-picking is the ideal option since you will only load and bundle the components you're using.
55 |
56 | ```fsharp
57 |
58 | importSideEffects "@shoelace-style/shoelace/dist/components/button/button.js"
59 |
60 | importSideEffects "@shoelace-style/shoelace/dist/components/icon/icon.js"
61 | ```
62 |
63 | then if you try the following:
64 | ```fsharp
65 | Shoelace.SlButton [
66 | Shoelace.SlIcon [
67 | Attr.slot "prefix"
68 | Attr.name "info-circle"
69 | ]
70 | ]
71 | ```
72 | it should look like this
73 |
74 |
75 |
76 |
77 | If you wonder why the icon is not showing it means that you're not serving the icons statically
78 |
79 | you can fix that by selecting where are you going to mount the icon assets with your bundler
80 |
81 | ```fsharp
82 | []
83 | let setBasePath (path: string) : unit = jsNative
84 |
85 | setBasePath "shoelace"
86 | ```
87 |
88 |
89 | ### Snowpack
90 | In the case of snowpack its quite simple
91 |
92 | ```javascript
93 | /** @type {import("snowpack").SnowpackUserConfig } */
94 | module.exports = {
95 |
96 | mount: {
97 | public: { url: '/', static: true },
98 | src: { url: '/dist' },
99 | // tell snowpack to mount your assets in "shoelace/assets"
100 | 'node_modules/@shoelace-style/shoelace/dist/assets': { url: '/shoelace/assets', static: true }
101 | /* ... the rest of your config */
102 | },
103 | /* ... the rest of your config */
104 | };
105 | ```
106 |
107 | ### Webpack
108 | In the case of webpack we can use the copy plugin
109 | ```javascript
110 | module.exports = {
111 | /* ... the rest of your config ... */
112 | plugins: [
113 | new CopyPlugin({
114 | patterns: [
115 | // Copy Shoelace assets to dist/shoelace
116 | {
117 | from: path.resolve(__dirname, 'node_modules/@shoelace-style/shoelace/dist/assets'),
118 | to: path.resolve(__dirname, 'dist/shoelace/assets')
119 | }
120 | ]
121 | })
122 | /* ... the rest of your config ... */
123 | ]
124 | };
125 | ```
126 |
127 | Once we've done that either with webpack or snowpack our button will show like this
128 |
129 |
130 | Info
131 |
132 |
133 | Now you're ready to start doing some Sutil.Shoelace!
--------------------------------------------------------------------------------
/src/website/src/docs/shoelace/index.md:
--------------------------------------------------------------------------------
1 | [Sutil.Generator]: https://github.com/AngelMunoz/Sutil.Generator
2 | [Shoelace]: https://shoelace.style/
3 | [Sutil]: https://davedawkins.github.io/Sutil/
4 | [official React components]: https://shoelace.style/getting-started/usage?id=react
5 | [femto]: https://github.com/Zaid-Ajaj/Femto
6 |
7 | # Sutil.Shoelace
8 | [](https://www.nuget.org/packages/Sutil.Shoelace)
9 |
10 | **Sutil.Shoelace** is a small [Sutil] wrapper built on top of [Shoelace] a web component library.
11 |
12 | **Sutil.Shoelace** is autogenerated thanks to the [Sutil.Generator] project which takes a similar approach used to generate the [official React components]. Sutil.Shoelace also adds some helpers to allow you interact in a seamless way with Sutil
13 |
14 | ### Installation
15 |
16 | ```sh
17 | dotnet add package Sutil.Shoelace --version
18 | pnpm install @shoelace-style/shoelace@ # or npm install @shoelace-style/shoelace@
19 | ```
20 | If you are using [femto] then you just need to do
21 |
22 | - `femto install Sutil.Shoelace --version `
23 |
24 | # Getting Started
25 |
26 | - [Documentation](#/docs/getting-started)
27 | - [Components](#/docs/components)
28 |
29 |
30 | ## Versioning
31 | Sutil.Shoelace is built against the latest version of [Shoelace] in case there are bug fixes on the generator itself or additions for quality of life the nuget version will add a last dot and a number to indicate this was a new version of the generated package.
32 |
33 | Example:
34 | - `Shoelace version: v2.0.0-beta.43`
35 |
36 | **Normal Release ->** `Sutil.Shoelace version: v2.0.0-beta.43`
37 |
38 | - `Shoelace version: v2.0.0-beta.43`
39 | **Sutil.Generator Fix/QL Update ->** `Sutil.Shoelace version: Shoelace version: v2.0.0-beta.43.1`
40 |
41 | This is expected to be done in rare ocations when a fix or QL update really is needed, for the most part most of the QL updates will be sent with the next Shoelace release to prevent distancing from the shoelace version
42 |
--------------------------------------------------------------------------------
/src/website/src/docs/shoelace/stores.md:
--------------------------------------------------------------------------------
1 | [Elmish]: #/shoelace/docs/elmish
2 | # Stores
3 |
4 | > To check how to work with elmish check out [Elmish]
5 |
6 | Sutil uses the concept of stores (observable objects that *store* state) to manage state in a web application (or part of it) and they are really convenient when it comes to reflect state updates in the UI, and since they don't require a lot of boilerplate it might be the preferred way for smaller components to handle state.
7 |
8 | Let's see with an example. We'll try to open and close an ***Alert*** which looks like this:
9 | ```fsharp
10 | open Sutil
11 | open Sutil.Store
12 | open Sutil.Attr
13 | open Sutil.DOM
14 | // you can open type Shoelace well!
15 | open type Sutil.Shoelace
16 |
17 | let view() =
18 | Html.article [
19 | SlAlert [
20 | Attr.custom("open", "true")
21 | Attr.custom("closable", "true")
22 | SlIcon [ Attr.slot "icon"; Attr.name "info-circle" ]
23 | text "This is a standard alert. You can customize its content and even the icon."
24 | ]
25 | ]
26 | ```
27 | This is a standard alert. You can customize its content and even the icon.
28 |
29 | If you click on the close button it effectively closes the alert, but how can we open that again programatically?
30 | The answer is by binding the `open` attribute (and this technique applies to any other component/element and any other attribute/property).
31 |
32 | We'll now add a checkbox that will toggle the alert open and closed also, it will check/uncheck itself everytime the alert opens or closes.
33 | To accomplish that we'll use a single store for both elements
34 |
35 | ```fsharp
36 | let view() =
37 | // create an store with a bool value
38 | let isOpen = Store.make true
39 | Html.article [
40 | // don't forget to disposte the store
41 | // when the component gets disposed
42 | disposeOnUnmount [ isOpen ]
43 | SlCheckbox [
44 | /// Notice the`Bind.attr`
45 | Bind.attr("checked", isOpen)
46 | text "Toggle Alert"
47 | ]
48 | SlAlert [
49 | /// Notice the`Bind.attr`
50 | Bind.attr("open", isOpen)
51 | Attr.custom("closable", "true")
52 | SlIcon [ Attr.slot "icon"; Attr.name "info-circle" ]
53 | text "This is a standard alert. You can customize its content and even the icon."
54 | ]
55 | ]
56 | ```
57 | Toggle Alert
58 |
59 | This is a standard alert. You can customize its content and even the icon.
60 |
61 |
62 | In this case that's all we need to do and the reason is that both `SlAlert` and `SlCheckbox` [reflect](https://lit.dev/docs/components/properties/#reflected-attributes) their `open` and `checked` attribute accordingly and the Sutil binding engine tracks those changes and updates the store.
63 |
64 |
65 | ```fsharp
66 | open Sutil
67 | open Sutil.Store
68 | open Sutil.Attr
69 | open Sutil.DOM
70 | // you can open type Shoelace well!
71 | open type Sutil.Shoelace
72 |
73 | let view() =
74 | // create an store with a bool value
75 | let isOpen = Store.make true
76 | Html.article [
77 | // don't forget to disposte the store
78 | // when the component gets disposed
79 | disposeOnUnmount [ isOpen ]
80 | SlCheckbox [
81 | Bind.attr("checked", isOpen)
82 | text "Toggle Alert"
83 | ]
84 | SlAlert [
85 | Bind.attr("open", isOpen)
86 | Attr.custom("closable", "true")
87 | SlIcon [ Attr.slot "icon"; Attr.name "info-circle" ]
88 | text "This is a standard alert. You can customize its content and even the icon."
89 | ]
90 | ]
91 | ```
92 |
93 |
94 | #### Quality of life improvement helpers
95 |
96 | Sutil.Shoelace adds a standard way to bind multiple attributes at once in a single store because it might get cumbersome when you have multiple stores tracking multiple values all over the place.
97 |
98 |
99 | ```fsharp
100 | open Sutil
101 | open Sutil.DOM
102 | open Sutil.Attr
103 | open Sutil.Shoelace
104 | open type Shoelace.Shoelace
105 | // Access SlAlertAttributes
106 | open Sutil.Shoelace.Alert
107 | // Access SlCheckbox
108 | open Sutil.Shoelace.Checkbox
109 |
110 | let view (page: string) =
111 | let alertAttrs =
112 | Store.make (
113 | // create an attribute record and set the initial values
114 | SlAlertAttributes.create ()
115 | |> SlAlertAttributes.withOpen false
116 | |> SlAlertAttributes.withType "primary"
117 | |> SlAlertAttributes.withClosable false
118 | )
119 |
120 | let onChkChange (e: Browser.Types.Event) =
121 | // the SlCheckbox is a binding of the native Web Component
122 | let target = (e.target :?> SlCheckbox)
123 | Store.modify (SlAlertAttributes.withClosable target.``checked``) alertAttrs
124 |
125 | let onBtnClick _ =
126 | // try to open the alert if it has been closed
127 | Store.modify (SlAlertAttributes.withOpen true) alertAttrs
128 |
129 | Html.article [
130 | disposeOnUnmount [ alertAttrs ]
131 | SlCheckbox [
132 | text "Closable"
133 | // react to multiple events that might be affecting your components
134 | on "sl-change" onChkChange []
135 | ]
136 | SlButton [
137 | text "Open Alert"
138 | // react to multiple events that might be affecting your components
139 | onClick onBtnClick []
140 | ]
141 | SlAlert(
142 | // the call signature is different, pass an IStore
143 | alertAttrs,
144 | // then your nodes, which can include attributes and other components/html elements
145 | [ SlIcon [
146 | Attr.slot "icon"
147 | Attr.name "info-circle"
148 | ]
149 | text
150 | "This is a standard alert. You can customize its content and even the icon." ]
151 | )
152 | ]
153 | ```
154 | > If you try to bind an existing attribute when using the `IStore, NodeFactory seq` signature (e.g. `Bind.attr("closable", myotherStore)`) you might run into weird behavior given that the `SlAttributes` types bind all of the the existing documented attributes here's a sample of the generated code
155 | >
156 | > ```fsharp
157 | > let stateful (attrs: IStore) (nodes: NodeFactory seq) =
158 | > /// here `.>` acts as an operator for `Store.map (fun attrs -> attr.prop) attrs`
159 | > let closable = attrs .> (fun attrs -> attrs.closable)
160 | > let duration = attrs .> (fun attrs -> attrs.duration)
161 | > let open' = attrs .> (fun attrs -> attrs.open')
162 | > let type' = attrs .> (fun attrs -> attrs.type')
163 | > stateless
164 | > [ Bind.attr("closable", closable)
165 | > Bind.attr("duration", duration)
166 | > Bind.attr("open", open')
167 | > Bind.attr("type", type')
168 | > // We merge your nodes at the end
169 | > // so you can take further control of the component
170 | > yield! nodes ]
171 | > ```
172 |
173 | This is an example that might show when you want to do something like this also, keep in mind that since you're using `Stores` (`Observables`) you should be able to filter, map, different values to different stores with some of the Store's methods. The sky is the limit
174 |
175 |
176 |
--------------------------------------------------------------------------------
/src/website/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can use usual css as well */
2 |
3 | :root {
4 | --su-color: var(--sl-color-primary-500);
5 | --su-background-color: var(--sl-color-white);
6 | }
7 |
8 |
9 | html, body {
10 | margin: 0;
11 | padding: 0;
12 | width: 100vw;
13 | height: 100vh;
14 | color: var(--su-color);
15 | background-color: var(--su-background-color);
16 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
17 | }
18 |
19 | main {
20 | display: flex;
21 | justify-content: center;
22 | }
23 | fast-design-system-provider.main-provider {
24 | height: 100vh;
25 | width: 100vw;
26 | overflow-y: auto;
27 | }
28 |
29 |
30 | a {
31 | color: var(--sl-color-primary-600);
32 | }
33 | a:visited {
34 | color: var(--sl-color-info-500);
35 | }
36 | .app-nav {
37 | display: flex;
38 | flex-wrap: wrap;
39 | border-bottom: 2px solid;
40 | padding-bottom: 1em;
41 | justify-content: space-between;
42 | }
43 |
44 | pre, blockquote {
45 | background-color: var(--sl-color-success-50);
46 | padding: 0.5em;
47 | }
48 |
49 | pre {
50 | overflow-x: auto;
51 | }
52 |
53 | blockquote {
54 | border-left: 5px solid var(--su-color);
55 | }
56 |
57 | .site-menu {
58 | flex-direction: column;
59 | margin: 0 1em;
60 | }
61 |
62 | .site-menu.desktop {
63 | display: none;
64 | }
65 |
66 | .show-on-mobile {
67 | display: none;
68 | }
69 |
70 | .menu-categories {
71 | padding-left: 0;
72 | }
73 |
74 | .menu-categories .menu-categories-category {
75 | list-style-type: none;
76 | }
77 |
78 | article.doc-page, article.home-page {
79 | width: 82vw;
80 | display: flex;
81 | margin-right: auto;
82 | margin-left: auto;
83 | margin-bottom: 2em;
84 | }
85 | .home-page {
86 | flex-direction: column;
87 | }
88 |
89 | article.doc-page sl-include {
90 | overflow-y: auto;
91 | flex: 1 0;
92 | }
93 |
94 | @media (prefers-color-scheme: dark) {
95 | :root {
96 | --su-background-color: #1e1e1e;
97 | --su-color: var(--sl-color-gray-100);
98 | --sl-panel-background-color: var(--su-background-color)
99 | }
100 | pre {
101 | background-color: var(--sl-color-gray-950);
102 | }
103 | blockquote {
104 | color: var(--sl-color-gray-50);
105 | background-color: var(--sl-color-gray-800);
106 | }
107 | sl-menu-item::part(label) {
108 | color: var(--su-color);
109 | }
110 |
111 | sl-alert::part(base) {
112 | color: var(--su-color);
113 | background-color: var(--sl-panel-background-color);
114 | }
115 | }
116 |
117 | @media screen and (min-width: 1024px) {
118 | .site-menu.desktop {
119 | display: flex;
120 | margin-right: auto;
121 | flex: 0 1;
122 | }
123 | }
124 |
125 | @media screen and (min-width: 1024px) {
126 | .site-menu.desktop {
127 | display: flex;
128 | margin-right: auto;
129 | flex: 0 1;
130 | }
131 | }
132 | @media screen and (min-width: 1220px) {
133 | article.home-page,
134 | article.doc-page,
135 | article.home-page section.row {
136 | width: 62vw;
137 | margin-left: unset;
138 | }
139 | }
140 |
141 | @media screen and (min-width: 1024px) {
142 | article.home-page,
143 | article.doc-page,
144 | article.home-page section.row {
145 | width: 66vw;
146 | margin-left: unset;
147 | }
148 | }
149 |
150 | @media screen and (max-width: 502px) {
151 | .show-on-mobile {
152 | display: initial;
153 | }
154 | article.home-page section.row {
155 | flex-direction: column;
156 | align-items: stretch;
157 | }
158 | }
159 |
160 | pre, code {
161 | font-family: 'Fira Code', monospace;
162 | }
--------------------------------------------------------------------------------