├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── assets ├── javascripts │ ├── bootstrap-global-this-define.js │ ├── bootstrap-global-this-undefine.js │ ├── bootstrap-sprockets.js │ ├── bootstrap.js │ ├── bootstrap.min.js │ └── bootstrap │ │ ├── alert.js │ │ ├── base-component.js │ │ ├── button.js │ │ ├── carousel.js │ │ ├── collapse.js │ │ ├── dom │ │ ├── data.js │ │ ├── event-handler.js │ │ ├── manipulator.js │ │ └── selector-engine.js │ │ ├── dropdown.js │ │ ├── modal.js │ │ ├── offcanvas.js │ │ ├── popover.js │ │ ├── scrollspy.js │ │ ├── tab.js │ │ ├── toast.js │ │ ├── tooltip.js │ │ └── util │ │ ├── backdrop.js │ │ ├── component-functions.js │ │ ├── config.js │ │ ├── focustrap.js │ │ ├── index.js │ │ ├── sanitizer.js │ │ ├── scrollbar.js │ │ ├── swipe.js │ │ └── template-factory.js └── stylesheets │ ├── _bootstrap-grid.scss │ ├── _bootstrap-reboot.scss │ ├── _bootstrap-utilities.scss │ ├── _bootstrap.scss │ └── bootstrap │ ├── _accordion.scss │ ├── _alert.scss │ ├── _badge.scss │ ├── _breadcrumb.scss │ ├── _button-group.scss │ ├── _buttons.scss │ ├── _card.scss │ ├── _carousel.scss │ ├── _close.scss │ ├── _containers.scss │ ├── _dropdown.scss │ ├── _forms.scss │ ├── _functions.scss │ ├── _grid.scss │ ├── _helpers.scss │ ├── _images.scss │ ├── _list-group.scss │ ├── _maps.scss │ ├── _mixins.scss │ ├── _modal.scss │ ├── _nav.scss │ ├── _navbar.scss │ ├── _offcanvas.scss │ ├── _pagination.scss │ ├── _placeholders.scss │ ├── _popover.scss │ ├── _progress.scss │ ├── _reboot.scss │ ├── _root.scss │ ├── _spinners.scss │ ├── _tables.scss │ ├── _toasts.scss │ ├── _tooltip.scss │ ├── _transitions.scss │ ├── _type.scss │ ├── _utilities.scss │ ├── _variables-dark.scss │ ├── _variables.scss │ ├── forms │ ├── _floating-labels.scss │ ├── _form-check.scss │ ├── _form-control.scss │ ├── _form-range.scss │ ├── _form-select.scss │ ├── _form-text.scss │ ├── _input-group.scss │ ├── _labels.scss │ └── _validation.scss │ ├── helpers │ ├── _clearfix.scss │ ├── _color-bg.scss │ ├── _colored-links.scss │ ├── _focus-ring.scss │ ├── _icon-link.scss │ ├── _position.scss │ ├── _ratio.scss │ ├── _stacks.scss │ ├── _stretched-link.scss │ ├── _text-truncation.scss │ ├── _visually-hidden.scss │ └── _vr.scss │ ├── mixins │ ├── _alert.scss │ ├── _backdrop.scss │ ├── _banner.scss │ ├── _border-radius.scss │ ├── _box-shadow.scss │ ├── _breakpoints.scss │ ├── _buttons.scss │ ├── _caret.scss │ ├── _clearfix.scss │ ├── _color-mode.scss │ ├── _color-scheme.scss │ ├── _container.scss │ ├── _deprecate.scss │ ├── _forms.scss │ ├── _gradients.scss │ ├── _grid.scss │ ├── _image.scss │ ├── _list-group.scss │ ├── _lists.scss │ ├── _pagination.scss │ ├── _reset-text.scss │ ├── _resize.scss │ ├── _table-variants.scss │ ├── _text-truncate.scss │ ├── _transition.scss │ ├── _utilities.scss │ └── _visually-hidden.scss │ ├── utilities │ └── _api.scss │ └── vendor │ └── _rfs.scss ├── bootstrap.gemspec ├── lib ├── bootstrap.rb └── bootstrap │ ├── engine.rb │ └── version.rb ├── tasks ├── updater.rb └── updater │ ├── js.rb │ ├── logger.rb │ ├── network.rb │ └── scss.rb └── test ├── dummy_rails ├── README.rdoc ├── Rakefile ├── app │ ├── assets │ │ ├── config │ │ │ └── manifest.js │ │ ├── images │ │ │ └── .keep │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ ├── .browserslistrc │ │ │ └── application.sass │ ├── controllers │ │ ├── application_controller.rb │ │ └── pages_controller.rb │ ├── helpers │ │ └── application_helper.rb │ └── views │ │ ├── layouts │ │ └── application.html.erb │ │ └── pages │ │ └── root.html ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── secret_token.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ ├── en.yml │ │ └── es.yml │ └── routes.rb ├── log │ └── .keep └── public │ └── favicon.ico ├── gemfiles ├── rails_4_2.gemfile ├── rails_5_0.gemfile ├── rails_5_1.gemfile ├── rails_5_2.gemfile ├── rails_6_0.gemfile ├── rails_6_1.gemfile ├── rails_7_0_dartsass.gemfile └── rails_7_0_sassc.gemfile ├── rails_test.rb ├── support ├── dummy_rails_integration.rb └── reporting.rb ├── test_helper.rb └── test_helper_rails.rb /.gitattributes: -------------------------------------------------------------------------------- 1 | .* text eol=lf 2 | Gemfile text eol=lf 3 | LICENSE text eol=lf 4 | Rakefile text eol=lf 5 | *.gemfile text eol=lf 6 | *.gemspec text eol=lf 7 | *.html.erb text eol=lf 8 | *.html.slim text eol=lf 9 | *.js text eol=lf 10 | *.rb text eol=lf 11 | *.ru text eol=lf 12 | *.sass text eol=lf 13 | *.scss text eol=lf 14 | *.yml text eol=lf 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | - workflow_dispatch 7 | 8 | jobs: 9 | test: 10 | env: 11 | BUNDLE_GEMFILE: ${{ matrix.gemfile }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | ruby_version: ['2.5', '2.6', '2.7', '3.0', '3.1'] 16 | gemfile: 17 | - test/gemfiles/rails_5_0.gemfile 18 | - test/gemfiles/rails_5_1.gemfile 19 | - test/gemfiles/rails_5_2.gemfile 20 | - test/gemfiles/rails_6_0.gemfile 21 | - test/gemfiles/rails_6_1.gemfile 22 | - test/gemfiles/rails_7_0_sassc.gemfile 23 | - test/gemfiles/rails_7_0_dartsass.gemfile 24 | include: 25 | - ruby_version: '2.5' 26 | gemfile: test/gemfiles/rails_4_2.gemfile 27 | - ruby_version: '2.6' 28 | gemfile: test/gemfiles/rails_4_2.gemfile 29 | exclude: 30 | - ruby_version: '2.5' 31 | gemfile: test/gemfiles/rails_7_0_sassc.gemfile 32 | - ruby_version: '2.5' 33 | gemfile: test/gemfiles/rails_7_0_dartsass.gemfile 34 | - ruby_version: '2.6' 35 | gemfile: test/gemfiles/rails_7_0_sassc.gemfile 36 | - ruby_version: '2.6' 37 | gemfile: test/gemfiles/rails_7_0_dartsass.gemfile 38 | - ruby_version: '3.0' 39 | gemfile: test/gemfiles/rails_5_0.gemfile 40 | - ruby_version: '3.0' 41 | gemfile: test/gemfiles/rails_5_1.gemfile 42 | - ruby_version: '3.0' 43 | gemfile: test/gemfiles/rails_5_2.gemfile 44 | - ruby_version: '3.1' 45 | gemfile: test/gemfiles/rails_5_0.gemfile 46 | - ruby_version: '3.1' 47 | gemfile: test/gemfiles/rails_5_1.gemfile 48 | - ruby_version: '3.1' 49 | gemfile: test/gemfiles/rails_5_2.gemfile 50 | - ruby_version: '3.1' 51 | gemfile: test/gemfiles/rails_6_0.gemfile 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v3 55 | - name: Set up Ruby 56 | uses: ruby/setup-ruby@v1 57 | with: 58 | ruby-version: ${{ matrix.ruby_version }} 59 | bundler-cache: true # 'bundle install' and cache 60 | - name: Build and test with Rake 61 | run: bundle exec rake --trace 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .sass-cache 3 | bootstrap.css 4 | bootstrap-responsive.css 5 | Gemfile.lock 6 | *.gemfile.lock 7 | .rvmrc 8 | .rbenv-version 9 | 10 | # Ignore bundler config 11 | /.bundle 12 | /vendor/cache 13 | /vendor/bundle 14 | tmp/ 15 | test/screenshots/ 16 | test/dummy_rails/log/*.log 17 | test/dummy_rails/public/assets/ 18 | .DS_Store 19 | node_modules 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The changelog is tracked here but also in [the Releases section of the GitHub project](https://github.com/twbs/bootstrap-rubygem/releases). 4 | The changelog only includes changes specific to the RubyGem. 5 | 6 | The Bootstrap framework changes can be found in [the Releases section of twbs/bootstrap](https://github.com/twbs/bootstrap/releases). 7 | Release announcement posts on [the official Bootstrap blog](http://blog.getbootstrap.com) contain summaries of the most noteworthy changes made in each release of Bootstrap. 8 | 9 | # 5.3.4 10 | 11 | * Autoprefixer is now optional. 12 | [#283](https://github.com/twbs/bootstrap-rubygem/pull/283) 13 | 14 | # 5.3.3 15 | 16 | * Adds support for other Sass engines: dartsass-sprockets, dartsass-rails, and cssbundling-rails. 17 | 18 | # 4.2.1 19 | 20 | * Bootstrap rubygem now depends on SassC instead of Sass. 21 | 22 | # 4.0.0.beta2.1 23 | 24 | Fixes an extraneous `sourceMappingURL` in `bootstrap.js`. 25 | [#124](https://github.com/twbs/bootstrap-rubygem/issues/124) 26 | 27 | # 4.0.0.beta2 28 | 29 | Compass is no longer supported. Minimum required Sass version is now v3.5.2. 30 | [#122](https://github.com/twbs/bootstrap-rubygem/pull/122) 31 | 32 | # 4.0.0.alpha3.1 33 | 34 | This release corresponds to the upstream Bootstrap 4 Alpha 3. 35 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'popper_js', '>= 1.12.3' 7 | gem 'dartsass-sprockets' 8 | end 9 | 10 | group :debug do 11 | gem 'byebug', platforms: [:mri], require: false 12 | end 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2016 Twitter, Inc 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'bundler/setup' 3 | 4 | lib_path = File.join(File.dirname(__FILE__), 'lib') 5 | $:.unshift(lib_path) unless $:.include?(lib_path) 6 | 7 | require 'rake/testtask' 8 | Rake::TestTask.new do |t| 9 | t.libs << 'test' 10 | t.test_files = FileList['test/**/*_test.rb'] 11 | t.verbose = false 12 | t.warning = false 13 | end 14 | 15 | desc 'Test all Gemfiles from test/*.gemfile' 16 | task :test_all_gemfiles do 17 | require 'term/ansicolor' 18 | require 'pty' 19 | require 'shellwords' 20 | cmd = 'bundle install --quiet && bundle exec rake --trace' 21 | statuses = Dir.glob('./test/gemfiles/*{[!.lock]}').map do |gemfile| 22 | env = {'BUNDLE_GEMFILE' => gemfile} 23 | cmd_with_env = " (#{env.map { |k, v| "export #{k}=#{Shellwords.escape v}" } * ' '}; #{cmd})" 24 | $stderr.puts Term::ANSIColor.cyan("Testing\n#{cmd_with_env}") 25 | Bundler.with_clean_env do 26 | PTY.spawn(env, cmd) do |r, _w, pid| 27 | begin 28 | r.each_line { |l| puts l } 29 | rescue Errno::EIO 30 | # Errno:EIO error means that the process has finished giving output. 31 | ensure 32 | ::Process.wait pid 33 | end 34 | end 35 | end 36 | [$? && $?.exitstatus == 0, cmd_with_env] 37 | end 38 | failed_cmds = statuses.reject(&:first).map { |(_status, cmd_with_env)| cmd_with_env } 39 | if failed_cmds.empty? 40 | $stderr.puts Term::ANSIColor.green('Tests pass with all gemfiles') 41 | else 42 | $stderr.puts Term::ANSIColor.red("Failing (#{failed_cmds.size} / #{statuses.size})\n#{failed_cmds * "\n"}") 43 | exit 1 44 | end 45 | end 46 | 47 | desc 'Dumps output to a CSS file for testing' 48 | task :debug do 49 | begin 50 | require 'sass-embedded' 51 | rescue LoadError 52 | begin 53 | require 'sassc' 54 | rescue LoadError 55 | raise LoadError.new("bootstrap-rubygem requires a Sass engine. Please add dartsass-sprockets or sassc-rails to your dependencies.") 56 | end 57 | end 58 | require './lib/bootstrap' 59 | require 'term/ansicolor' 60 | path = Bootstrap.stylesheets_path 61 | %w(_bootstrap _bootstrap-reboot _bootstrap-grid).each do |file| 62 | filename = "#{path}/#{file}.scss" 63 | css = if defined?(SassC::Engine) 64 | SassC::Engine.new(File.read(filename), filename: filename, syntax: :scss).render 65 | else 66 | Sass.compile(filename).css 67 | end 68 | out = File.join('tmp', "#{file[1..-1]}.css") 69 | File.write(out, css) 70 | $stderr.puts Term::ANSIColor.green "Compiled #{out}" 71 | end 72 | end 73 | 74 | desc 'Update bootstrap from upstream' 75 | task :update, :branch do |t, args| 76 | require './tasks/updater' 77 | Updater.new(branch: args[:branch]).update_bootstrap 78 | end 79 | 80 | desc 'Start a dummy Rails app server' 81 | task :rails_server do 82 | require 'rack' 83 | require 'term/ansicolor' 84 | port = ENV['PORT'] || 9292 85 | puts %Q(Starting on #{Term::ANSIColor.cyan "http://localhost:#{port}"}) 86 | Rack::Server.start( 87 | config: 'test/dummy_rails/config.ru', 88 | Port: port) 89 | end 90 | 91 | task default: :test 92 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap-global-this-define.js: -------------------------------------------------------------------------------- 1 | // Set a `globalThis` so that bootstrap components are defined on window.bootstrap instead of window. 2 | window['bootstrap'] = { 3 | "@popperjs/core": window.Popper, 4 | _originalGlobalThis: window['globalThis'] 5 | }; 6 | window['globalThis'] = window['bootstrap']; 7 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap-global-this-undefine.js: -------------------------------------------------------------------------------- 1 | window['globalThis'] = window['bootstrap']._originalGlobalThis; 2 | window['bootstrap']._originalGlobalThis = null; 3 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap-sprockets.js: -------------------------------------------------------------------------------- 1 | //= require ./bootstrap-global-this-define 2 | //= require ./bootstrap/dom/data 3 | //= require ./bootstrap/util/index 4 | //= require ./bootstrap/dom/event-handler 5 | //= require ./bootstrap/dom/manipulator 6 | //= require ./bootstrap/util/config 7 | //= require ./bootstrap/base-component 8 | //= require ./bootstrap/button 9 | //= require ./bootstrap/dom/selector-engine 10 | //= require ./bootstrap/scrollspy 11 | //= require ./bootstrap/util/scrollbar 12 | //= require ./bootstrap/util/sanitizer 13 | //= require ./bootstrap/util/swipe 14 | //= require ./bootstrap/carousel 15 | //= require ./bootstrap/collapse 16 | //= require ./bootstrap/util/backdrop 17 | //= require ./bootstrap/util/component-functions 18 | //= require ./bootstrap/util/focustrap 19 | //= require ./bootstrap/modal 20 | //= require ./bootstrap/alert 21 | //= require ./bootstrap/util/template-factory 22 | //= require ./bootstrap/tooltip 23 | //= require ./bootstrap/popover 24 | //= require ./bootstrap/offcanvas 25 | //= require ./bootstrap/toast 26 | //= require ./bootstrap/dropdown 27 | //= require ./bootstrap/tab 28 | //= require ./bootstrap-global-this-undefine 29 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/alert.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap alert.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/component-functions.js'), require('./util/index.js')) : 8 | typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/component-functions', './util/index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Alert = factory(global.BaseComponent, global.EventHandler, global.ComponentFunctions, global.Index)); 10 | })(this, (function (BaseComponent, EventHandler, componentFunctions_js, index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap alert.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | /** 21 | * Constants 22 | */ 23 | 24 | const NAME = 'alert'; 25 | const DATA_KEY = 'bs.alert'; 26 | const EVENT_KEY = `.${DATA_KEY}`; 27 | const EVENT_CLOSE = `close${EVENT_KEY}`; 28 | const EVENT_CLOSED = `closed${EVENT_KEY}`; 29 | const CLASS_NAME_FADE = 'fade'; 30 | const CLASS_NAME_SHOW = 'show'; 31 | 32 | /** 33 | * Class definition 34 | */ 35 | 36 | class Alert extends BaseComponent { 37 | // Getters 38 | static get NAME() { 39 | return NAME; 40 | } 41 | 42 | // Public 43 | close() { 44 | const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE); 45 | if (closeEvent.defaultPrevented) { 46 | return; 47 | } 48 | this._element.classList.remove(CLASS_NAME_SHOW); 49 | const isAnimated = this._element.classList.contains(CLASS_NAME_FADE); 50 | this._queueCallback(() => this._destroyElement(), this._element, isAnimated); 51 | } 52 | 53 | // Private 54 | _destroyElement() { 55 | this._element.remove(); 56 | EventHandler.trigger(this._element, EVENT_CLOSED); 57 | this.dispose(); 58 | } 59 | 60 | // Static 61 | static jQueryInterface(config) { 62 | return this.each(function () { 63 | const data = Alert.getOrCreateInstance(this); 64 | if (typeof config !== 'string') { 65 | return; 66 | } 67 | if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { 68 | throw new TypeError(`No method named "${config}"`); 69 | } 70 | data[config](this); 71 | }); 72 | } 73 | } 74 | 75 | /** 76 | * Data API implementation 77 | */ 78 | 79 | componentFunctions_js.enableDismissTrigger(Alert, 'close'); 80 | 81 | /** 82 | * jQuery 83 | */ 84 | 85 | index_js.defineJQueryPlugin(Alert); 86 | 87 | return Alert; 88 | 89 | })); 90 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/base-component.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap base-component.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./dom/data.js'), require('./dom/event-handler.js'), require('./util/config.js'), require('./util/index.js')) : 8 | typeof define === 'function' && define.amd ? define(['./dom/data', './dom/event-handler', './util/config', './util/index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BaseComponent = factory(global.Data, global.EventHandler, global.Config, global.Index)); 10 | })(this, (function (Data, EventHandler, Config, index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap base-component.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | /** 21 | * Constants 22 | */ 23 | 24 | const VERSION = '5.3.5'; 25 | 26 | /** 27 | * Class definition 28 | */ 29 | 30 | class BaseComponent extends Config { 31 | constructor(element, config) { 32 | super(); 33 | element = index_js.getElement(element); 34 | if (!element) { 35 | return; 36 | } 37 | this._element = element; 38 | this._config = this._getConfig(config); 39 | Data.set(this._element, this.constructor.DATA_KEY, this); 40 | } 41 | 42 | // Public 43 | dispose() { 44 | Data.remove(this._element, this.constructor.DATA_KEY); 45 | EventHandler.off(this._element, this.constructor.EVENT_KEY); 46 | for (const propertyName of Object.getOwnPropertyNames(this)) { 47 | this[propertyName] = null; 48 | } 49 | } 50 | _queueCallback(callback, element, isAnimated = true) { 51 | index_js.executeAfterTransition(callback, element, isAnimated); 52 | } 53 | _getConfig(config) { 54 | config = this._mergeConfigObj(config, this._element); 55 | config = this._configAfterMerge(config); 56 | this._typeCheckConfig(config); 57 | return config; 58 | } 59 | 60 | // Static 61 | static getInstance(element) { 62 | return Data.get(index_js.getElement(element), this.DATA_KEY); 63 | } 64 | static getOrCreateInstance(element, config = {}) { 65 | return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null); 66 | } 67 | static get VERSION() { 68 | return VERSION; 69 | } 70 | static get DATA_KEY() { 71 | return `bs.${this.NAME}`; 72 | } 73 | static get EVENT_KEY() { 74 | return `.${this.DATA_KEY}`; 75 | } 76 | static eventName(name) { 77 | return `${name}${this.EVENT_KEY}`; 78 | } 79 | } 80 | 81 | return BaseComponent; 82 | 83 | })); 84 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/button.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap button.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/index.js')) : 8 | typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Button = factory(global.BaseComponent, global.EventHandler, global.Index)); 10 | })(this, (function (BaseComponent, EventHandler, index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap button.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | /** 21 | * Constants 22 | */ 23 | 24 | const NAME = 'button'; 25 | const DATA_KEY = 'bs.button'; 26 | const EVENT_KEY = `.${DATA_KEY}`; 27 | const DATA_API_KEY = '.data-api'; 28 | const CLASS_NAME_ACTIVE = 'active'; 29 | const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="button"]'; 30 | const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`; 31 | 32 | /** 33 | * Class definition 34 | */ 35 | 36 | class Button extends BaseComponent { 37 | // Getters 38 | static get NAME() { 39 | return NAME; 40 | } 41 | 42 | // Public 43 | toggle() { 44 | // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method 45 | this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE)); 46 | } 47 | 48 | // Static 49 | static jQueryInterface(config) { 50 | return this.each(function () { 51 | const data = Button.getOrCreateInstance(this); 52 | if (config === 'toggle') { 53 | data[config](); 54 | } 55 | }); 56 | } 57 | } 58 | 59 | /** 60 | * Data API implementation 61 | */ 62 | 63 | EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => { 64 | event.preventDefault(); 65 | const button = event.target.closest(SELECTOR_DATA_TOGGLE); 66 | const data = Button.getOrCreateInstance(button); 67 | data.toggle(); 68 | }); 69 | 70 | /** 71 | * jQuery 72 | */ 73 | 74 | index_js.defineJQueryPlugin(Button); 75 | 76 | return Button; 77 | 78 | })); 79 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/dom/data.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap data.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 8 | typeof define === 'function' && define.amd ? define(factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Data = factory()); 10 | })(this, (function () { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap dom/data.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | /** 20 | * Constants 21 | */ 22 | 23 | const elementMap = new Map(); 24 | const data = { 25 | set(element, key, instance) { 26 | if (!elementMap.has(element)) { 27 | elementMap.set(element, new Map()); 28 | } 29 | const instanceMap = elementMap.get(element); 30 | 31 | // make it clear we only want one instance per element 32 | // can be removed later when multiple key/instances are fine to be used 33 | if (!instanceMap.has(key) && instanceMap.size !== 0) { 34 | // eslint-disable-next-line no-console 35 | console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`); 36 | return; 37 | } 38 | instanceMap.set(key, instance); 39 | }, 40 | get(element, key) { 41 | if (elementMap.has(element)) { 42 | return elementMap.get(element).get(key) || null; 43 | } 44 | return null; 45 | }, 46 | remove(element, key) { 47 | if (!elementMap.has(element)) { 48 | return; 49 | } 50 | const instanceMap = elementMap.get(element); 51 | instanceMap.delete(key); 52 | 53 | // free up element references if there are no instances left for an element 54 | if (instanceMap.size === 0) { 55 | elementMap.delete(element); 56 | } 57 | } 58 | }; 59 | 60 | return data; 61 | 62 | })); 63 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/dom/manipulator.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap manipulator.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 8 | typeof define === 'function' && define.amd ? define(factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Manipulator = factory()); 10 | })(this, (function () { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap dom/manipulator.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | function normalizeData(value) { 20 | if (value === 'true') { 21 | return true; 22 | } 23 | if (value === 'false') { 24 | return false; 25 | } 26 | if (value === Number(value).toString()) { 27 | return Number(value); 28 | } 29 | if (value === '' || value === 'null') { 30 | return null; 31 | } 32 | if (typeof value !== 'string') { 33 | return value; 34 | } 35 | try { 36 | return JSON.parse(decodeURIComponent(value)); 37 | } catch (_unused) { 38 | return value; 39 | } 40 | } 41 | function normalizeDataKey(key) { 42 | return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`); 43 | } 44 | const Manipulator = { 45 | setDataAttribute(element, key, value) { 46 | element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value); 47 | }, 48 | removeDataAttribute(element, key) { 49 | element.removeAttribute(`data-bs-${normalizeDataKey(key)}`); 50 | }, 51 | getDataAttributes(element) { 52 | if (!element) { 53 | return {}; 54 | } 55 | const attributes = {}; 56 | const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig')); 57 | for (const key of bsKeys) { 58 | let pureKey = key.replace(/^bs/, ''); 59 | pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1); 60 | attributes[pureKey] = normalizeData(element.dataset[key]); 61 | } 62 | return attributes; 63 | }, 64 | getDataAttribute(element, key) { 65 | return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)); 66 | } 67 | }; 68 | 69 | return Manipulator; 70 | 71 | })); 72 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/dom/selector-engine.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap selector-engine.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../util/index.js')) : 8 | typeof define === 'function' && define.amd ? define(['../util/index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SelectorEngine = factory(global.Index)); 10 | })(this, (function (index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap dom/selector-engine.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | const getSelector = element => { 20 | let selector = element.getAttribute('data-bs-target'); 21 | if (!selector || selector === '#') { 22 | let hrefAttribute = element.getAttribute('href'); 23 | 24 | // The only valid content that could double as a selector are IDs or classes, 25 | // so everything starting with `#` or `.`. If a "real" URL is used as the selector, 26 | // `document.querySelector` will rightfully complain it is invalid. 27 | // See https://github.com/twbs/bootstrap/issues/32273 28 | if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) { 29 | return null; 30 | } 31 | 32 | // Just in case some CMS puts out a full URL with the anchor appended 33 | if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { 34 | hrefAttribute = `#${hrefAttribute.split('#')[1]}`; 35 | } 36 | selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null; 37 | } 38 | return selector ? selector.split(',').map(sel => index_js.parseSelector(sel)).join(',') : null; 39 | }; 40 | const SelectorEngine = { 41 | find(selector, element = document.documentElement) { 42 | return [].concat(...Element.prototype.querySelectorAll.call(element, selector)); 43 | }, 44 | findOne(selector, element = document.documentElement) { 45 | return Element.prototype.querySelector.call(element, selector); 46 | }, 47 | children(element, selector) { 48 | return [].concat(...element.children).filter(child => child.matches(selector)); 49 | }, 50 | parents(element, selector) { 51 | const parents = []; 52 | let ancestor = element.parentNode.closest(selector); 53 | while (ancestor) { 54 | parents.push(ancestor); 55 | ancestor = ancestor.parentNode.closest(selector); 56 | } 57 | return parents; 58 | }, 59 | prev(element, selector) { 60 | let previous = element.previousElementSibling; 61 | while (previous) { 62 | if (previous.matches(selector)) { 63 | return [previous]; 64 | } 65 | previous = previous.previousElementSibling; 66 | } 67 | return []; 68 | }, 69 | // TODO: this is now unused; remove later along with prev() 70 | next(element, selector) { 71 | let next = element.nextElementSibling; 72 | while (next) { 73 | if (next.matches(selector)) { 74 | return [next]; 75 | } 76 | next = next.nextElementSibling; 77 | } 78 | return []; 79 | }, 80 | focusableChildren(element) { 81 | const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map(selector => `${selector}:not([tabindex^="-"])`).join(','); 82 | return this.find(focusables, element).filter(el => !index_js.isDisabled(el) && index_js.isVisible(el)); 83 | }, 84 | getSelectorFromElement(element) { 85 | const selector = getSelector(element); 86 | if (selector) { 87 | return SelectorEngine.findOne(selector) ? selector : null; 88 | } 89 | return null; 90 | }, 91 | getElementFromSelector(element) { 92 | const selector = getSelector(element); 93 | return selector ? SelectorEngine.findOne(selector) : null; 94 | }, 95 | getMultipleElementsFromSelector(element) { 96 | const selector = getSelector(element); 97 | return selector ? SelectorEngine.find(selector) : []; 98 | } 99 | }; 100 | 101 | return SelectorEngine; 102 | 103 | })); 104 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/popover.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap popover.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./tooltip.js'), require('./util/index.js')) : 8 | typeof define === 'function' && define.amd ? define(['./tooltip', './util/index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Popover = factory(global.Tooltip, global.Index)); 10 | })(this, (function (Tooltip, index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap popover.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | /** 21 | * Constants 22 | */ 23 | 24 | const NAME = 'popover'; 25 | const SELECTOR_TITLE = '.popover-header'; 26 | const SELECTOR_CONTENT = '.popover-body'; 27 | const Default = { 28 | ...Tooltip.Default, 29 | content: '', 30 | offset: [0, 8], 31 | placement: 'right', 32 | template: '', 33 | trigger: 'click' 34 | }; 35 | const DefaultType = { 36 | ...Tooltip.DefaultType, 37 | content: '(null|string|element|function)' 38 | }; 39 | 40 | /** 41 | * Class definition 42 | */ 43 | 44 | class Popover extends Tooltip { 45 | // Getters 46 | static get Default() { 47 | return Default; 48 | } 49 | static get DefaultType() { 50 | return DefaultType; 51 | } 52 | static get NAME() { 53 | return NAME; 54 | } 55 | 56 | // Overrides 57 | _isWithContent() { 58 | return this._getTitle() || this._getContent(); 59 | } 60 | 61 | // Private 62 | _getContentForTemplate() { 63 | return { 64 | [SELECTOR_TITLE]: this._getTitle(), 65 | [SELECTOR_CONTENT]: this._getContent() 66 | }; 67 | } 68 | _getContent() { 69 | return this._resolvePossibleFunction(this._config.content); 70 | } 71 | 72 | // Static 73 | static jQueryInterface(config) { 74 | return this.each(function () { 75 | const data = Popover.getOrCreateInstance(this, config); 76 | if (typeof config !== 'string') { 77 | return; 78 | } 79 | if (typeof data[config] === 'undefined') { 80 | throw new TypeError(`No method named "${config}"`); 81 | } 82 | data[config](); 83 | }); 84 | } 85 | } 86 | 87 | /** 88 | * jQuery 89 | */ 90 | 91 | index_js.defineJQueryPlugin(Popover); 92 | 93 | return Popover; 94 | 95 | })); 96 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/util/backdrop.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap backdrop.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler.js'), require('./config.js'), require('./index.js')) : 8 | typeof define === 'function' && define.amd ? define(['../dom/event-handler', './config', './index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Backdrop = factory(global.EventHandler, global.Config, global.Index)); 10 | })(this, (function (EventHandler, Config, index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap util/backdrop.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | /** 21 | * Constants 22 | */ 23 | 24 | const NAME = 'backdrop'; 25 | const CLASS_NAME_FADE = 'fade'; 26 | const CLASS_NAME_SHOW = 'show'; 27 | const EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`; 28 | const Default = { 29 | className: 'modal-backdrop', 30 | clickCallback: null, 31 | isAnimated: false, 32 | isVisible: true, 33 | // if false, we use the backdrop helper without adding any element to the dom 34 | rootElement: 'body' // give the choice to place backdrop under different elements 35 | }; 36 | const DefaultType = { 37 | className: 'string', 38 | clickCallback: '(function|null)', 39 | isAnimated: 'boolean', 40 | isVisible: 'boolean', 41 | rootElement: '(element|string)' 42 | }; 43 | 44 | /** 45 | * Class definition 46 | */ 47 | 48 | class Backdrop extends Config { 49 | constructor(config) { 50 | super(); 51 | this._config = this._getConfig(config); 52 | this._isAppended = false; 53 | this._element = null; 54 | } 55 | 56 | // Getters 57 | static get Default() { 58 | return Default; 59 | } 60 | static get DefaultType() { 61 | return DefaultType; 62 | } 63 | static get NAME() { 64 | return NAME; 65 | } 66 | 67 | // Public 68 | show(callback) { 69 | if (!this._config.isVisible) { 70 | index_js.execute(callback); 71 | return; 72 | } 73 | this._append(); 74 | const element = this._getElement(); 75 | if (this._config.isAnimated) { 76 | index_js.reflow(element); 77 | } 78 | element.classList.add(CLASS_NAME_SHOW); 79 | this._emulateAnimation(() => { 80 | index_js.execute(callback); 81 | }); 82 | } 83 | hide(callback) { 84 | if (!this._config.isVisible) { 85 | index_js.execute(callback); 86 | return; 87 | } 88 | this._getElement().classList.remove(CLASS_NAME_SHOW); 89 | this._emulateAnimation(() => { 90 | this.dispose(); 91 | index_js.execute(callback); 92 | }); 93 | } 94 | dispose() { 95 | if (!this._isAppended) { 96 | return; 97 | } 98 | EventHandler.off(this._element, EVENT_MOUSEDOWN); 99 | this._element.remove(); 100 | this._isAppended = false; 101 | } 102 | 103 | // Private 104 | _getElement() { 105 | if (!this._element) { 106 | const backdrop = document.createElement('div'); 107 | backdrop.className = this._config.className; 108 | if (this._config.isAnimated) { 109 | backdrop.classList.add(CLASS_NAME_FADE); 110 | } 111 | this._element = backdrop; 112 | } 113 | return this._element; 114 | } 115 | _configAfterMerge(config) { 116 | // use getElement() with the default "body" to get a fresh Element on each instantiation 117 | config.rootElement = index_js.getElement(config.rootElement); 118 | return config; 119 | } 120 | _append() { 121 | if (this._isAppended) { 122 | return; 123 | } 124 | const element = this._getElement(); 125 | this._config.rootElement.append(element); 126 | EventHandler.on(element, EVENT_MOUSEDOWN, () => { 127 | index_js.execute(this._config.clickCallback); 128 | }); 129 | this._isAppended = true; 130 | } 131 | _emulateAnimation(callback) { 132 | index_js.executeAfterTransition(callback, this._getElement(), this._config.isAnimated); 133 | } 134 | } 135 | 136 | return Backdrop; 137 | 138 | })); 139 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/util/component-functions.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap component-functions.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('../dom/event-handler.js'), require('../dom/selector-engine.js'), require('./index.js')) : 8 | typeof define === 'function' && define.amd ? define(['exports', '../dom/event-handler', '../dom/selector-engine', './index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ComponentFunctions = {}, global.EventHandler, global.SelectorEngine, global.Index)); 10 | })(this, (function (exports, EventHandler, SelectorEngine, index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap util/component-functions.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | const enableDismissTrigger = (component, method = 'hide') => { 20 | const clickEvent = `click.dismiss${component.EVENT_KEY}`; 21 | const name = component.NAME; 22 | EventHandler.on(document, clickEvent, `[data-bs-dismiss="${name}"]`, function (event) { 23 | if (['A', 'AREA'].includes(this.tagName)) { 24 | event.preventDefault(); 25 | } 26 | if (index_js.isDisabled(this)) { 27 | return; 28 | } 29 | const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`); 30 | const instance = component.getOrCreateInstance(target); 31 | 32 | // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method 33 | instance[method](); 34 | }); 35 | }; 36 | 37 | exports.enableDismissTrigger = enableDismissTrigger; 38 | 39 | Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 40 | 41 | })); 42 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/util/config.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap config.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/manipulator.js'), require('./index.js')) : 8 | typeof define === 'function' && define.amd ? define(['../dom/manipulator', './index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Config = factory(global.Manipulator, global.Index)); 10 | })(this, (function (Manipulator, index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap util/config.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | /** 21 | * Class definition 22 | */ 23 | 24 | class Config { 25 | // Getters 26 | static get Default() { 27 | return {}; 28 | } 29 | static get DefaultType() { 30 | return {}; 31 | } 32 | static get NAME() { 33 | throw new Error('You have to implement the static method "NAME", for each component!'); 34 | } 35 | _getConfig(config) { 36 | config = this._mergeConfigObj(config); 37 | config = this._configAfterMerge(config); 38 | this._typeCheckConfig(config); 39 | return config; 40 | } 41 | _configAfterMerge(config) { 42 | return config; 43 | } 44 | _mergeConfigObj(config, element) { 45 | const jsonConfig = index_js.isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse 46 | 47 | return { 48 | ...this.constructor.Default, 49 | ...(typeof jsonConfig === 'object' ? jsonConfig : {}), 50 | ...(index_js.isElement(element) ? Manipulator.getDataAttributes(element) : {}), 51 | ...(typeof config === 'object' ? config : {}) 52 | }; 53 | } 54 | _typeCheckConfig(config, configTypes = this.constructor.DefaultType) { 55 | for (const [property, expectedTypes] of Object.entries(configTypes)) { 56 | const value = config[property]; 57 | const valueType = index_js.isElement(value) ? 'element' : index_js.toType(value); 58 | if (!new RegExp(expectedTypes).test(valueType)) { 59 | throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`); 60 | } 61 | } 62 | } 63 | } 64 | 65 | return Config; 66 | 67 | })); 68 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/util/focustrap.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap focustrap.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler.js'), require('../dom/selector-engine.js'), require('./config.js')) : 8 | typeof define === 'function' && define.amd ? define(['../dom/event-handler', '../dom/selector-engine', './config'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Focustrap = factory(global.EventHandler, global.SelectorEngine, global.Config)); 10 | })(this, (function (EventHandler, SelectorEngine, Config) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap util/focustrap.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | /** 21 | * Constants 22 | */ 23 | 24 | const NAME = 'focustrap'; 25 | const DATA_KEY = 'bs.focustrap'; 26 | const EVENT_KEY = `.${DATA_KEY}`; 27 | const EVENT_FOCUSIN = `focusin${EVENT_KEY}`; 28 | const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`; 29 | const TAB_KEY = 'Tab'; 30 | const TAB_NAV_FORWARD = 'forward'; 31 | const TAB_NAV_BACKWARD = 'backward'; 32 | const Default = { 33 | autofocus: true, 34 | trapElement: null // The element to trap focus inside of 35 | }; 36 | const DefaultType = { 37 | autofocus: 'boolean', 38 | trapElement: 'element' 39 | }; 40 | 41 | /** 42 | * Class definition 43 | */ 44 | 45 | class FocusTrap extends Config { 46 | constructor(config) { 47 | super(); 48 | this._config = this._getConfig(config); 49 | this._isActive = false; 50 | this._lastTabNavDirection = null; 51 | } 52 | 53 | // Getters 54 | static get Default() { 55 | return Default; 56 | } 57 | static get DefaultType() { 58 | return DefaultType; 59 | } 60 | static get NAME() { 61 | return NAME; 62 | } 63 | 64 | // Public 65 | activate() { 66 | if (this._isActive) { 67 | return; 68 | } 69 | if (this._config.autofocus) { 70 | this._config.trapElement.focus(); 71 | } 72 | EventHandler.off(document, EVENT_KEY); // guard against infinite focus loop 73 | EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event)); 74 | EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event)); 75 | this._isActive = true; 76 | } 77 | deactivate() { 78 | if (!this._isActive) { 79 | return; 80 | } 81 | this._isActive = false; 82 | EventHandler.off(document, EVENT_KEY); 83 | } 84 | 85 | // Private 86 | _handleFocusin(event) { 87 | const { 88 | trapElement 89 | } = this._config; 90 | if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) { 91 | return; 92 | } 93 | const elements = SelectorEngine.focusableChildren(trapElement); 94 | if (elements.length === 0) { 95 | trapElement.focus(); 96 | } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) { 97 | elements[elements.length - 1].focus(); 98 | } else { 99 | elements[0].focus(); 100 | } 101 | } 102 | _handleKeydown(event) { 103 | if (event.key !== TAB_KEY) { 104 | return; 105 | } 106 | this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD; 107 | } 108 | } 109 | 110 | return FocusTrap; 111 | 112 | })); 113 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/util/sanitizer.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap sanitizer.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 8 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Sanitizer = {})); 10 | })(this, (function (exports) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap util/sanitizer.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | // js-docs-start allow-list 20 | const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; 21 | const DefaultAllowlist = { 22 | // Global attributes allowed on any supplied element below. 23 | '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], 24 | a: ['target', 'href', 'title', 'rel'], 25 | area: [], 26 | b: [], 27 | br: [], 28 | col: [], 29 | code: [], 30 | dd: [], 31 | div: [], 32 | dl: [], 33 | dt: [], 34 | em: [], 35 | hr: [], 36 | h1: [], 37 | h2: [], 38 | h3: [], 39 | h4: [], 40 | h5: [], 41 | h6: [], 42 | i: [], 43 | img: ['src', 'srcset', 'alt', 'title', 'width', 'height'], 44 | li: [], 45 | ol: [], 46 | p: [], 47 | pre: [], 48 | s: [], 49 | small: [], 50 | span: [], 51 | sub: [], 52 | sup: [], 53 | strong: [], 54 | u: [], 55 | ul: [] 56 | }; 57 | // js-docs-end allow-list 58 | 59 | const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); 60 | 61 | /** 62 | * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation 63 | * contexts. 64 | * 65 | * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38 66 | */ 67 | // eslint-disable-next-line unicorn/better-regex 68 | const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i; 69 | const allowedAttribute = (attribute, allowedAttributeList) => { 70 | const attributeName = attribute.nodeName.toLowerCase(); 71 | if (allowedAttributeList.includes(attributeName)) { 72 | if (uriAttributes.has(attributeName)) { 73 | return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue)); 74 | } 75 | return true; 76 | } 77 | 78 | // Check if a regular expression validates the attribute. 79 | return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName)); 80 | }; 81 | function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { 82 | if (!unsafeHtml.length) { 83 | return unsafeHtml; 84 | } 85 | if (sanitizeFunction && typeof sanitizeFunction === 'function') { 86 | return sanitizeFunction(unsafeHtml); 87 | } 88 | const domParser = new window.DOMParser(); 89 | const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); 90 | const elements = [].concat(...createdDocument.body.querySelectorAll('*')); 91 | for (const element of elements) { 92 | const elementName = element.nodeName.toLowerCase(); 93 | if (!Object.keys(allowList).includes(elementName)) { 94 | element.remove(); 95 | continue; 96 | } 97 | const attributeList = [].concat(...element.attributes); 98 | const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []); 99 | for (const attribute of attributeList) { 100 | if (!allowedAttribute(attribute, allowedAttributes)) { 101 | element.removeAttribute(attribute.nodeName); 102 | } 103 | } 104 | } 105 | return createdDocument.body.innerHTML; 106 | } 107 | 108 | exports.DefaultAllowlist = DefaultAllowlist; 109 | exports.sanitizeHtml = sanitizeHtml; 110 | 111 | Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 112 | 113 | })); 114 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/util/scrollbar.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap scrollbar.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/manipulator.js'), require('../dom/selector-engine.js'), require('./index.js')) : 8 | typeof define === 'function' && define.amd ? define(['../dom/manipulator', '../dom/selector-engine', './index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Scrollbar = factory(global.Manipulator, global.SelectorEngine, global.Index)); 10 | })(this, (function (Manipulator, SelectorEngine, index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap util/scrollBar.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | /** 21 | * Constants 22 | */ 23 | 24 | const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'; 25 | const SELECTOR_STICKY_CONTENT = '.sticky-top'; 26 | const PROPERTY_PADDING = 'padding-right'; 27 | const PROPERTY_MARGIN = 'margin-right'; 28 | 29 | /** 30 | * Class definition 31 | */ 32 | 33 | class ScrollBarHelper { 34 | constructor() { 35 | this._element = document.body; 36 | } 37 | 38 | // Public 39 | getWidth() { 40 | // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes 41 | const documentWidth = document.documentElement.clientWidth; 42 | return Math.abs(window.innerWidth - documentWidth); 43 | } 44 | hide() { 45 | const width = this.getWidth(); 46 | this._disableOverFlow(); 47 | // give padding to element to balance the hidden scrollbar width 48 | this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); 49 | // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth 50 | this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width); 51 | this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width); 52 | } 53 | reset() { 54 | this._resetElementAttributes(this._element, 'overflow'); 55 | this._resetElementAttributes(this._element, PROPERTY_PADDING); 56 | this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING); 57 | this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN); 58 | } 59 | isOverflowing() { 60 | return this.getWidth() > 0; 61 | } 62 | 63 | // Private 64 | _disableOverFlow() { 65 | this._saveInitialAttribute(this._element, 'overflow'); 66 | this._element.style.overflow = 'hidden'; 67 | } 68 | _setElementAttributes(selector, styleProperty, callback) { 69 | const scrollbarWidth = this.getWidth(); 70 | const manipulationCallBack = element => { 71 | if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { 72 | return; 73 | } 74 | this._saveInitialAttribute(element, styleProperty); 75 | const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty); 76 | element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`); 77 | }; 78 | this._applyManipulationCallback(selector, manipulationCallBack); 79 | } 80 | _saveInitialAttribute(element, styleProperty) { 81 | const actualValue = element.style.getPropertyValue(styleProperty); 82 | if (actualValue) { 83 | Manipulator.setDataAttribute(element, styleProperty, actualValue); 84 | } 85 | } 86 | _resetElementAttributes(selector, styleProperty) { 87 | const manipulationCallBack = element => { 88 | const value = Manipulator.getDataAttribute(element, styleProperty); 89 | // We only want to remove the property if the value is `null`; the value can also be zero 90 | if (value === null) { 91 | element.style.removeProperty(styleProperty); 92 | return; 93 | } 94 | Manipulator.removeDataAttribute(element, styleProperty); 95 | element.style.setProperty(styleProperty, value); 96 | }; 97 | this._applyManipulationCallback(selector, manipulationCallBack); 98 | } 99 | _applyManipulationCallback(selector, callBack) { 100 | if (index_js.isElement(selector)) { 101 | callBack(selector); 102 | return; 103 | } 104 | for (const sel of SelectorEngine.find(selector, this._element)) { 105 | callBack(sel); 106 | } 107 | } 108 | } 109 | 110 | return ScrollBarHelper; 111 | 112 | })); 113 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/util/swipe.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap swipe.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler.js'), require('./config.js'), require('./index.js')) : 8 | typeof define === 'function' && define.amd ? define(['../dom/event-handler', './config', './index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Swipe = factory(global.EventHandler, global.Config, global.Index)); 10 | })(this, (function (EventHandler, Config, index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap util/swipe.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | /** 21 | * Constants 22 | */ 23 | 24 | const NAME = 'swipe'; 25 | const EVENT_KEY = '.bs.swipe'; 26 | const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`; 27 | const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`; 28 | const EVENT_TOUCHEND = `touchend${EVENT_KEY}`; 29 | const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`; 30 | const EVENT_POINTERUP = `pointerup${EVENT_KEY}`; 31 | const POINTER_TYPE_TOUCH = 'touch'; 32 | const POINTER_TYPE_PEN = 'pen'; 33 | const CLASS_NAME_POINTER_EVENT = 'pointer-event'; 34 | const SWIPE_THRESHOLD = 40; 35 | const Default = { 36 | endCallback: null, 37 | leftCallback: null, 38 | rightCallback: null 39 | }; 40 | const DefaultType = { 41 | endCallback: '(function|null)', 42 | leftCallback: '(function|null)', 43 | rightCallback: '(function|null)' 44 | }; 45 | 46 | /** 47 | * Class definition 48 | */ 49 | 50 | class Swipe extends Config { 51 | constructor(element, config) { 52 | super(); 53 | this._element = element; 54 | if (!element || !Swipe.isSupported()) { 55 | return; 56 | } 57 | this._config = this._getConfig(config); 58 | this._deltaX = 0; 59 | this._supportPointerEvents = Boolean(window.PointerEvent); 60 | this._initEvents(); 61 | } 62 | 63 | // Getters 64 | static get Default() { 65 | return Default; 66 | } 67 | static get DefaultType() { 68 | return DefaultType; 69 | } 70 | static get NAME() { 71 | return NAME; 72 | } 73 | 74 | // Public 75 | dispose() { 76 | EventHandler.off(this._element, EVENT_KEY); 77 | } 78 | 79 | // Private 80 | _start(event) { 81 | if (!this._supportPointerEvents) { 82 | this._deltaX = event.touches[0].clientX; 83 | return; 84 | } 85 | if (this._eventIsPointerPenTouch(event)) { 86 | this._deltaX = event.clientX; 87 | } 88 | } 89 | _end(event) { 90 | if (this._eventIsPointerPenTouch(event)) { 91 | this._deltaX = event.clientX - this._deltaX; 92 | } 93 | this._handleSwipe(); 94 | index_js.execute(this._config.endCallback); 95 | } 96 | _move(event) { 97 | this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX; 98 | } 99 | _handleSwipe() { 100 | const absDeltaX = Math.abs(this._deltaX); 101 | if (absDeltaX <= SWIPE_THRESHOLD) { 102 | return; 103 | } 104 | const direction = absDeltaX / this._deltaX; 105 | this._deltaX = 0; 106 | if (!direction) { 107 | return; 108 | } 109 | index_js.execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback); 110 | } 111 | _initEvents() { 112 | if (this._supportPointerEvents) { 113 | EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event)); 114 | EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event)); 115 | this._element.classList.add(CLASS_NAME_POINTER_EVENT); 116 | } else { 117 | EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event)); 118 | EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event)); 119 | EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event)); 120 | } 121 | } 122 | _eventIsPointerPenTouch(event) { 123 | return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH); 124 | } 125 | 126 | // Static 127 | static isSupported() { 128 | return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; 129 | } 130 | } 131 | 132 | return Swipe; 133 | 134 | })); 135 | -------------------------------------------------------------------------------- /assets/javascripts/bootstrap/util/template-factory.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap template-factory.js v5.3.5 (https://getbootstrap.com/) 3 | * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/selector-engine.js'), require('./config.js'), require('./sanitizer.js'), require('./index.js')) : 8 | typeof define === 'function' && define.amd ? define(['../dom/selector-engine', './config', './sanitizer', './index'], factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.TemplateFactory = factory(global.SelectorEngine, global.Config, global.Sanitizer, global.Index)); 10 | })(this, (function (SelectorEngine, Config, sanitizer_js, index_js) { 'use strict'; 11 | 12 | /** 13 | * -------------------------------------------------------------------------- 14 | * Bootstrap util/template-factory.js 15 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 16 | * -------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | /** 21 | * Constants 22 | */ 23 | 24 | const NAME = 'TemplateFactory'; 25 | const Default = { 26 | allowList: sanitizer_js.DefaultAllowlist, 27 | content: {}, 28 | // { selector : text , selector2 : text2 , } 29 | extraClass: '', 30 | html: false, 31 | sanitize: true, 32 | sanitizeFn: null, 33 | template: '
' 34 | }; 35 | const DefaultType = { 36 | allowList: 'object', 37 | content: 'object', 38 | extraClass: '(string|function)', 39 | html: 'boolean', 40 | sanitize: 'boolean', 41 | sanitizeFn: '(null|function)', 42 | template: 'string' 43 | }; 44 | const DefaultContentType = { 45 | entry: '(string|element|function|null)', 46 | selector: '(string|element)' 47 | }; 48 | 49 | /** 50 | * Class definition 51 | */ 52 | 53 | class TemplateFactory extends Config { 54 | constructor(config) { 55 | super(); 56 | this._config = this._getConfig(config); 57 | } 58 | 59 | // Getters 60 | static get Default() { 61 | return Default; 62 | } 63 | static get DefaultType() { 64 | return DefaultType; 65 | } 66 | static get NAME() { 67 | return NAME; 68 | } 69 | 70 | // Public 71 | getContent() { 72 | return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean); 73 | } 74 | hasContent() { 75 | return this.getContent().length > 0; 76 | } 77 | changeContent(content) { 78 | this._checkContent(content); 79 | this._config.content = { 80 | ...this._config.content, 81 | ...content 82 | }; 83 | return this; 84 | } 85 | toHtml() { 86 | const templateWrapper = document.createElement('div'); 87 | templateWrapper.innerHTML = this._maybeSanitize(this._config.template); 88 | for (const [selector, text] of Object.entries(this._config.content)) { 89 | this._setContent(templateWrapper, text, selector); 90 | } 91 | const template = templateWrapper.children[0]; 92 | const extraClass = this._resolvePossibleFunction(this._config.extraClass); 93 | if (extraClass) { 94 | template.classList.add(...extraClass.split(' ')); 95 | } 96 | return template; 97 | } 98 | 99 | // Private 100 | _typeCheckConfig(config) { 101 | super._typeCheckConfig(config); 102 | this._checkContent(config.content); 103 | } 104 | _checkContent(arg) { 105 | for (const [selector, content] of Object.entries(arg)) { 106 | super._typeCheckConfig({ 107 | selector, 108 | entry: content 109 | }, DefaultContentType); 110 | } 111 | } 112 | _setContent(template, content, selector) { 113 | const templateElement = SelectorEngine.findOne(selector, template); 114 | if (!templateElement) { 115 | return; 116 | } 117 | content = this._resolvePossibleFunction(content); 118 | if (!content) { 119 | templateElement.remove(); 120 | return; 121 | } 122 | if (index_js.isElement(content)) { 123 | this._putElementInTemplate(index_js.getElement(content), templateElement); 124 | return; 125 | } 126 | if (this._config.html) { 127 | templateElement.innerHTML = this._maybeSanitize(content); 128 | return; 129 | } 130 | templateElement.textContent = content; 131 | } 132 | _maybeSanitize(arg) { 133 | return this._config.sanitize ? sanitizer_js.sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg; 134 | } 135 | _resolvePossibleFunction(arg) { 136 | return index_js.execute(arg, [undefined, this]); 137 | } 138 | _putElementInTemplate(element, templateElement) { 139 | if (this._config.html) { 140 | templateElement.innerHTML = ''; 141 | templateElement.append(element); 142 | return; 143 | } 144 | templateElement.textContent = element.textContent; 145 | } 146 | } 147 | 148 | return TemplateFactory; 149 | 150 | })); 151 | -------------------------------------------------------------------------------- /assets/stylesheets/_bootstrap-grid.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap/mixins/banner"; 2 | @include bsBanner(Grid); 3 | 4 | $include-column-box-sizing: true !default; 5 | 6 | @import "bootstrap/functions"; 7 | @import "bootstrap/variables"; 8 | @import "bootstrap/variables-dark"; 9 | @import "bootstrap/maps"; 10 | 11 | @import "bootstrap/mixins/breakpoints"; 12 | @import "bootstrap/mixins/container"; 13 | @import "bootstrap/mixins/grid"; 14 | @import "bootstrap/mixins/utilities"; 15 | 16 | @import "bootstrap/vendor/rfs"; 17 | 18 | @import "bootstrap/containers"; 19 | @import "bootstrap/grid"; 20 | 21 | @import "bootstrap/utilities"; 22 | // Only use the utilities we need 23 | // stylelint-disable-next-line scss/dollar-variable-default 24 | $utilities: map-get-multiple( 25 | $utilities, 26 | ( 27 | "bootstrap/display", 28 | "bootstrap/order", 29 | "bootstrap/flex", 30 | "bootstrap/flex-direction", 31 | "bootstrap/flex-grow", 32 | "bootstrap/flex-shrink", 33 | "bootstrap/flex-wrap", 34 | "bootstrap/justify-content", 35 | "bootstrap/align-items", 36 | "bootstrap/align-content", 37 | "bootstrap/align-self", 38 | "bootstrap/margin", 39 | "bootstrap/margin-x", 40 | "bootstrap/margin-y", 41 | "bootstrap/margin-top", 42 | "bootstrap/margin-end", 43 | "bootstrap/margin-bottom", 44 | "bootstrap/margin-start", 45 | "bootstrap/negative-margin", 46 | "bootstrap/negative-margin-x", 47 | "bootstrap/negative-margin-y", 48 | "bootstrap/negative-margin-top", 49 | "bootstrap/negative-margin-end", 50 | "bootstrap/negative-margin-bottom", 51 | "bootstrap/negative-margin-start", 52 | "bootstrap/padding", 53 | "bootstrap/padding-x", 54 | "bootstrap/padding-y", 55 | "bootstrap/padding-top", 56 | "bootstrap/padding-end", 57 | "bootstrap/padding-bottom", 58 | "bootstrap/padding-start", 59 | ) 60 | ); 61 | 62 | @import "bootstrap/utilities/api"; 63 | -------------------------------------------------------------------------------- /assets/stylesheets/_bootstrap-reboot.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap/mixins/banner"; 2 | @include bsBanner(Reboot); 3 | 4 | @import "bootstrap/functions"; 5 | @import "bootstrap/variables"; 6 | @import "bootstrap/variables-dark"; 7 | @import "bootstrap/maps"; 8 | @import "bootstrap/mixins"; 9 | @import "bootstrap/root"; 10 | @import "bootstrap/reboot"; 11 | -------------------------------------------------------------------------------- /assets/stylesheets/_bootstrap-utilities.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap/mixins/banner"; 2 | @include bsBanner(Utilities); 3 | 4 | // Configuration 5 | @import "bootstrap/functions"; 6 | @import "bootstrap/variables"; 7 | @import "bootstrap/variables-dark"; 8 | @import "bootstrap/maps"; 9 | @import "bootstrap/mixins"; 10 | @import "bootstrap/utilities"; 11 | 12 | // Layout & components 13 | @import "bootstrap/root"; 14 | 15 | // Helpers 16 | @import "bootstrap/helpers"; 17 | 18 | // Utilities 19 | @import "bootstrap/utilities/api"; 20 | -------------------------------------------------------------------------------- /assets/stylesheets/_bootstrap.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap/mixins/banner"; 2 | @include bsBanner(""); 3 | 4 | 5 | // scss-docs-start import-stack 6 | // Configuration 7 | @import "bootstrap/functions"; 8 | @import "bootstrap/variables"; 9 | @import "bootstrap/variables-dark"; 10 | @import "bootstrap/maps"; 11 | @import "bootstrap/mixins"; 12 | @import "bootstrap/utilities"; 13 | 14 | // Layout & components 15 | @import "bootstrap/root"; 16 | @import "bootstrap/reboot"; 17 | @import "bootstrap/type"; 18 | @import "bootstrap/images"; 19 | @import "bootstrap/containers"; 20 | @import "bootstrap/grid"; 21 | @import "bootstrap/tables"; 22 | @import "bootstrap/forms"; 23 | @import "bootstrap/buttons"; 24 | @import "bootstrap/transitions"; 25 | @import "bootstrap/dropdown"; 26 | @import "bootstrap/button-group"; 27 | @import "bootstrap/nav"; 28 | @import "bootstrap/navbar"; 29 | @import "bootstrap/card"; 30 | @import "bootstrap/accordion"; 31 | @import "bootstrap/breadcrumb"; 32 | @import "bootstrap/pagination"; 33 | @import "bootstrap/badge"; 34 | @import "bootstrap/alert"; 35 | @import "bootstrap/progress"; 36 | @import "bootstrap/list-group"; 37 | @import "bootstrap/close"; 38 | @import "bootstrap/toasts"; 39 | @import "bootstrap/modal"; 40 | @import "bootstrap/tooltip"; 41 | @import "bootstrap/popover"; 42 | @import "bootstrap/carousel"; 43 | @import "bootstrap/spinners"; 44 | @import "bootstrap/offcanvas"; 45 | @import "bootstrap/placeholders"; 46 | 47 | // Helpers 48 | @import "bootstrap/helpers"; 49 | 50 | // Utilities 51 | @import "bootstrap/utilities/api"; 52 | // scss-docs-end import-stack 53 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_alert.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Base styles 3 | // 4 | 5 | .alert { 6 | // scss-docs-start alert-css-vars 7 | --#{$prefix}alert-bg: transparent; 8 | --#{$prefix}alert-padding-x: #{$alert-padding-x}; 9 | --#{$prefix}alert-padding-y: #{$alert-padding-y}; 10 | --#{$prefix}alert-margin-bottom: #{$alert-margin-bottom}; 11 | --#{$prefix}alert-color: inherit; 12 | --#{$prefix}alert-border-color: transparent; 13 | --#{$prefix}alert-border: #{$alert-border-width} solid var(--#{$prefix}alert-border-color); 14 | --#{$prefix}alert-border-radius: #{$alert-border-radius}; 15 | --#{$prefix}alert-link-color: inherit; 16 | // scss-docs-end alert-css-vars 17 | 18 | position: relative; 19 | padding: var(--#{$prefix}alert-padding-y) var(--#{$prefix}alert-padding-x); 20 | margin-bottom: var(--#{$prefix}alert-margin-bottom); 21 | color: var(--#{$prefix}alert-color); 22 | background-color: var(--#{$prefix}alert-bg); 23 | border: var(--#{$prefix}alert-border); 24 | @include border-radius(var(--#{$prefix}alert-border-radius)); 25 | } 26 | 27 | // Headings for larger alerts 28 | .alert-heading { 29 | // Specified to prevent conflicts of changing $headings-color 30 | color: inherit; 31 | } 32 | 33 | // Provide class for links that match alerts 34 | .alert-link { 35 | font-weight: $alert-link-font-weight; 36 | color: var(--#{$prefix}alert-link-color); 37 | } 38 | 39 | 40 | // Dismissible alerts 41 | // 42 | // Expand the right padding and account for the close button's positioning. 43 | 44 | .alert-dismissible { 45 | padding-right: $alert-dismissible-padding-r; 46 | 47 | // Adjust close link position 48 | .btn-close { 49 | position: absolute; 50 | top: 0; 51 | right: 0; 52 | z-index: $stretched-link-z-index + 1; 53 | padding: $alert-padding-y * 1.25 $alert-padding-x; 54 | } 55 | } 56 | 57 | 58 | // scss-docs-start alert-modifiers 59 | // Generate contextual modifier classes for colorizing the alert 60 | @each $state in map-keys($theme-colors) { 61 | .alert-#{$state} { 62 | --#{$prefix}alert-color: var(--#{$prefix}#{$state}-text-emphasis); 63 | --#{$prefix}alert-bg: var(--#{$prefix}#{$state}-bg-subtle); 64 | --#{$prefix}alert-border-color: var(--#{$prefix}#{$state}-border-subtle); 65 | --#{$prefix}alert-link-color: var(--#{$prefix}#{$state}-text-emphasis); 66 | } 67 | } 68 | // scss-docs-end alert-modifiers 69 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_badge.scss: -------------------------------------------------------------------------------- 1 | // Base class 2 | // 3 | // Requires one of the contextual, color modifier classes for `color` and 4 | // `background-color`. 5 | 6 | .badge { 7 | // scss-docs-start badge-css-vars 8 | --#{$prefix}badge-padding-x: #{$badge-padding-x}; 9 | --#{$prefix}badge-padding-y: #{$badge-padding-y}; 10 | @include rfs($badge-font-size, --#{$prefix}badge-font-size); 11 | --#{$prefix}badge-font-weight: #{$badge-font-weight}; 12 | --#{$prefix}badge-color: #{$badge-color}; 13 | --#{$prefix}badge-border-radius: #{$badge-border-radius}; 14 | // scss-docs-end badge-css-vars 15 | 16 | display: inline-block; 17 | padding: var(--#{$prefix}badge-padding-y) var(--#{$prefix}badge-padding-x); 18 | @include font-size(var(--#{$prefix}badge-font-size)); 19 | font-weight: var(--#{$prefix}badge-font-weight); 20 | line-height: 1; 21 | color: var(--#{$prefix}badge-color); 22 | text-align: center; 23 | white-space: nowrap; 24 | vertical-align: baseline; 25 | @include border-radius(var(--#{$prefix}badge-border-radius)); 26 | @include gradient-bg(); 27 | 28 | // Empty badges collapse automatically 29 | &:empty { 30 | display: none; 31 | } 32 | } 33 | 34 | // Quick fix for badges in buttons 35 | .btn .badge { 36 | position: relative; 37 | top: -1px; 38 | } 39 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_breadcrumb.scss: -------------------------------------------------------------------------------- 1 | .breadcrumb { 2 | // scss-docs-start breadcrumb-css-vars 3 | --#{$prefix}breadcrumb-padding-x: #{$breadcrumb-padding-x}; 4 | --#{$prefix}breadcrumb-padding-y: #{$breadcrumb-padding-y}; 5 | --#{$prefix}breadcrumb-margin-bottom: #{$breadcrumb-margin-bottom}; 6 | @include rfs($breadcrumb-font-size, --#{$prefix}breadcrumb-font-size); 7 | --#{$prefix}breadcrumb-bg: #{$breadcrumb-bg}; 8 | --#{$prefix}breadcrumb-border-radius: #{$breadcrumb-border-radius}; 9 | --#{$prefix}breadcrumb-divider-color: #{$breadcrumb-divider-color}; 10 | --#{$prefix}breadcrumb-item-padding-x: #{$breadcrumb-item-padding-x}; 11 | --#{$prefix}breadcrumb-item-active-color: #{$breadcrumb-active-color}; 12 | // scss-docs-end breadcrumb-css-vars 13 | 14 | display: flex; 15 | flex-wrap: wrap; 16 | padding: var(--#{$prefix}breadcrumb-padding-y) var(--#{$prefix}breadcrumb-padding-x); 17 | margin-bottom: var(--#{$prefix}breadcrumb-margin-bottom); 18 | @include font-size(var(--#{$prefix}breadcrumb-font-size)); 19 | list-style: none; 20 | background-color: var(--#{$prefix}breadcrumb-bg); 21 | @include border-radius(var(--#{$prefix}breadcrumb-border-radius)); 22 | } 23 | 24 | .breadcrumb-item { 25 | // The separator between breadcrumbs (by default, a forward-slash: "/") 26 | + .breadcrumb-item { 27 | padding-left: var(--#{$prefix}breadcrumb-item-padding-x); 28 | 29 | &::before { 30 | float: left; // Suppress inline spacings and underlining of the separator 31 | padding-right: var(--#{$prefix}breadcrumb-item-padding-x); 32 | color: var(--#{$prefix}breadcrumb-divider-color); 33 | content: var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"}; 34 | } 35 | } 36 | 37 | &.active { 38 | color: var(--#{$prefix}breadcrumb-item-active-color); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_button-group.scss: -------------------------------------------------------------------------------- 1 | // Make the div behave like a button 2 | .btn-group, 3 | .btn-group-vertical { 4 | position: relative; 5 | display: inline-flex; 6 | vertical-align: middle; // match .btn alignment given font-size hack above 7 | 8 | > .btn { 9 | position: relative; 10 | flex: 1 1 auto; 11 | } 12 | 13 | // Bring the hover, focused, and "active" buttons to the front to overlay 14 | // the borders properly 15 | > .btn-check:checked + .btn, 16 | > .btn-check:focus + .btn, 17 | > .btn:hover, 18 | > .btn:focus, 19 | > .btn:active, 20 | > .btn.active { 21 | z-index: 1; 22 | } 23 | } 24 | 25 | // Optional: Group multiple button groups together for a toolbar 26 | .btn-toolbar { 27 | display: flex; 28 | flex-wrap: wrap; 29 | justify-content: flex-start; 30 | 31 | .input-group { 32 | width: auto; 33 | } 34 | } 35 | 36 | .btn-group { 37 | @include border-radius($btn-border-radius); 38 | 39 | // Prevent double borders when buttons are next to each other 40 | > :not(.btn-check:first-child) + .btn, 41 | > .btn-group:not(:first-child) { 42 | margin-left: calc(-1 * #{$btn-border-width}); // stylelint-disable-line function-disallowed-list 43 | } 44 | 45 | // Reset rounded corners 46 | > .btn:not(:last-child):not(.dropdown-toggle), 47 | > .btn.dropdown-toggle-split:first-child, 48 | > .btn-group:not(:last-child) > .btn { 49 | @include border-end-radius(0); 50 | } 51 | 52 | // The left radius should be 0 if the button is: 53 | // - the "third or more" child 54 | // - the second child and the previous element isn't `.btn-check` (making it the first child visually) 55 | // - part of a btn-group which isn't the first child 56 | > .btn:nth-child(n + 3), 57 | > :not(.btn-check) + .btn, 58 | > .btn-group:not(:first-child) > .btn { 59 | @include border-start-radius(0); 60 | } 61 | } 62 | 63 | // Sizing 64 | // 65 | // Remix the default button sizing classes into new ones for easier manipulation. 66 | 67 | .btn-group-sm > .btn { @extend .btn-sm; } 68 | .btn-group-lg > .btn { @extend .btn-lg; } 69 | 70 | 71 | // 72 | // Split button dropdowns 73 | // 74 | 75 | .dropdown-toggle-split { 76 | padding-right: $btn-padding-x * .75; 77 | padding-left: $btn-padding-x * .75; 78 | 79 | &::after, 80 | .dropup &::after, 81 | .dropend &::after { 82 | margin-left: 0; 83 | } 84 | 85 | .dropstart &::before { 86 | margin-right: 0; 87 | } 88 | } 89 | 90 | .btn-sm + .dropdown-toggle-split { 91 | padding-right: $btn-padding-x-sm * .75; 92 | padding-left: $btn-padding-x-sm * .75; 93 | } 94 | 95 | .btn-lg + .dropdown-toggle-split { 96 | padding-right: $btn-padding-x-lg * .75; 97 | padding-left: $btn-padding-x-lg * .75; 98 | } 99 | 100 | 101 | // The clickable button for toggling the menu 102 | // Set the same inset shadow as the :active state 103 | .btn-group.show .dropdown-toggle { 104 | @include box-shadow($btn-active-box-shadow); 105 | 106 | // Show no shadow for `.btn-link` since it has no other button styles. 107 | &.btn-link { 108 | @include box-shadow(none); 109 | } 110 | } 111 | 112 | 113 | // 114 | // Vertical button groups 115 | // 116 | 117 | .btn-group-vertical { 118 | flex-direction: column; 119 | align-items: flex-start; 120 | justify-content: center; 121 | 122 | > .btn, 123 | > .btn-group { 124 | width: 100%; 125 | } 126 | 127 | > .btn:not(:first-child), 128 | > .btn-group:not(:first-child) { 129 | margin-top: calc(-1 * #{$btn-border-width}); // stylelint-disable-line function-disallowed-list 130 | } 131 | 132 | // Reset rounded corners 133 | > .btn:not(:last-child):not(.dropdown-toggle), 134 | > .btn-group:not(:last-child) > .btn { 135 | @include border-bottom-radius(0); 136 | } 137 | 138 | // The top radius should be 0 if the button is: 139 | // - the "third or more" child 140 | // - the second child and the previous element isn't `.btn-check` (making it the first child visually) 141 | // - part of a btn-group which isn't the first child 142 | > .btn:nth-child(n + 3), 143 | > :not(.btn-check) + .btn, 144 | > .btn-group:not(:first-child) > .btn { 145 | @include border-top-radius(0); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_close.scss: -------------------------------------------------------------------------------- 1 | // Transparent background and border properties included for button version. 2 | // iOS requires the button element instead of an anchor tag. 3 | // If you want the anchor version, it requires `href="#"`. 4 | // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile 5 | 6 | .btn-close { 7 | // scss-docs-start close-css-vars 8 | --#{$prefix}btn-close-color: #{$btn-close-color}; 9 | --#{$prefix}btn-close-bg: #{ escape-svg($btn-close-bg) }; 10 | --#{$prefix}btn-close-opacity: #{$btn-close-opacity}; 11 | --#{$prefix}btn-close-hover-opacity: #{$btn-close-hover-opacity}; 12 | --#{$prefix}btn-close-focus-shadow: #{$btn-close-focus-shadow}; 13 | --#{$prefix}btn-close-focus-opacity: #{$btn-close-focus-opacity}; 14 | --#{$prefix}btn-close-disabled-opacity: #{$btn-close-disabled-opacity}; 15 | // scss-docs-end close-css-vars 16 | 17 | box-sizing: content-box; 18 | width: $btn-close-width; 19 | height: $btn-close-height; 20 | padding: $btn-close-padding-y $btn-close-padding-x; 21 | color: var(--#{$prefix}btn-close-color); 22 | background: transparent var(--#{$prefix}btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements 23 | filter: var(--#{$prefix}btn-close-filter); 24 | border: 0; // for button elements 25 | @include border-radius(); 26 | opacity: var(--#{$prefix}btn-close-opacity); 27 | 28 | // Override 's hover style 29 | &:hover { 30 | color: var(--#{$prefix}btn-close-color); 31 | text-decoration: none; 32 | opacity: var(--#{$prefix}btn-close-hover-opacity); 33 | } 34 | 35 | &:focus { 36 | outline: 0; 37 | box-shadow: var(--#{$prefix}btn-close-focus-shadow); 38 | opacity: var(--#{$prefix}btn-close-focus-opacity); 39 | } 40 | 41 | &:disabled, 42 | &.disabled { 43 | pointer-events: none; 44 | user-select: none; 45 | opacity: var(--#{$prefix}btn-close-disabled-opacity); 46 | } 47 | } 48 | 49 | @mixin btn-close-white() { 50 | --#{$prefix}btn-close-filter: #{$btn-close-filter-dark}; 51 | } 52 | 53 | .btn-close-white { 54 | @include btn-close-white(); 55 | } 56 | 57 | :root, 58 | [data-bs-theme="light"] { 59 | --#{$prefix}btn-close-filter: #{$btn-close-filter}; 60 | } 61 | 62 | @if $enable-dark-mode { 63 | @include color-mode(dark, true) { 64 | @include btn-close-white(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_containers.scss: -------------------------------------------------------------------------------- 1 | // Container widths 2 | // 3 | // Set the container width, and override it for fixed navbars in media queries. 4 | 5 | @if $enable-container-classes { 6 | // Single container class with breakpoint max-widths 7 | .container, 8 | // 100% wide container at all breakpoints 9 | .container-fluid { 10 | @include make-container(); 11 | } 12 | 13 | // Responsive containers that are 100% wide until a breakpoint 14 | @each $breakpoint, $container-max-width in $container-max-widths { 15 | .container-#{$breakpoint} { 16 | @extend .container-fluid; 17 | } 18 | 19 | @include media-breakpoint-up($breakpoint, $grid-breakpoints) { 20 | %responsive-container-#{$breakpoint} { 21 | max-width: $container-max-width; 22 | } 23 | 24 | // Extend each breakpoint which is smaller or equal to the current breakpoint 25 | $extend-breakpoint: true; 26 | 27 | @each $name, $width in $grid-breakpoints { 28 | @if ($extend-breakpoint) { 29 | .container#{breakpoint-infix($name, $grid-breakpoints)} { 30 | @extend %responsive-container-#{$breakpoint}; 31 | } 32 | 33 | // Once the current breakpoint is reached, stop extending 34 | @if ($breakpoint == $name) { 35 | $extend-breakpoint: false; 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_forms.scss: -------------------------------------------------------------------------------- 1 | @import "forms/labels"; 2 | @import "forms/form-text"; 3 | @import "forms/form-control"; 4 | @import "forms/form-select"; 5 | @import "forms/form-check"; 6 | @import "forms/form-range"; 7 | @import "forms/floating-labels"; 8 | @import "forms/input-group"; 9 | @import "forms/validation"; 10 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_grid.scss: -------------------------------------------------------------------------------- 1 | // Row 2 | // 3 | // Rows contain your columns. 4 | 5 | :root { 6 | @each $name, $value in $grid-breakpoints { 7 | --#{$prefix}breakpoint-#{$name}: #{$value}; 8 | } 9 | } 10 | 11 | @if $enable-grid-classes { 12 | .row { 13 | @include make-row(); 14 | 15 | > * { 16 | @include make-col-ready(); 17 | } 18 | } 19 | } 20 | 21 | @if $enable-cssgrid { 22 | .grid { 23 | display: grid; 24 | grid-template-rows: repeat(var(--#{$prefix}rows, 1), 1fr); 25 | grid-template-columns: repeat(var(--#{$prefix}columns, #{$grid-columns}), 1fr); 26 | gap: var(--#{$prefix}gap, #{$grid-gutter-width}); 27 | 28 | @include make-cssgrid(); 29 | } 30 | } 31 | 32 | 33 | // Columns 34 | // 35 | // Common styles for small and large grid columns 36 | 37 | @if $enable-grid-classes { 38 | @include make-grid-columns(); 39 | } 40 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_helpers.scss: -------------------------------------------------------------------------------- 1 | @import "helpers/clearfix"; 2 | @import "helpers/color-bg"; 3 | @import "helpers/colored-links"; 4 | @import "helpers/focus-ring"; 5 | @import "helpers/icon-link"; 6 | @import "helpers/ratio"; 7 | @import "helpers/position"; 8 | @import "helpers/stacks"; 9 | @import "helpers/visually-hidden"; 10 | @import "helpers/stretched-link"; 11 | @import "helpers/text-truncation"; 12 | @import "helpers/vr"; 13 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_images.scss: -------------------------------------------------------------------------------- 1 | // Responsive images (ensure images don't scale beyond their parents) 2 | // 3 | // This is purposefully opt-in via an explicit class rather than being the default for all ``s. 4 | // We previously tried the "images are responsive by default" approach in Bootstrap v2, 5 | // and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps) 6 | // which weren't expecting the images within themselves to be involuntarily resized. 7 | // See also https://github.com/twbs/bootstrap/issues/18178 8 | .img-fluid { 9 | @include img-fluid(); 10 | } 11 | 12 | 13 | // Image thumbnails 14 | .img-thumbnail { 15 | padding: $thumbnail-padding; 16 | background-color: $thumbnail-bg; 17 | border: $thumbnail-border-width solid $thumbnail-border-color; 18 | @include border-radius($thumbnail-border-radius); 19 | @include box-shadow($thumbnail-box-shadow); 20 | 21 | // Keep them at most 100% wide 22 | @include img-fluid(); 23 | } 24 | 25 | // 26 | // Figures 27 | // 28 | 29 | .figure { 30 | // Ensures the caption's text aligns with the image. 31 | display: inline-block; 32 | } 33 | 34 | .figure-img { 35 | margin-bottom: $spacer * .5; 36 | line-height: 1; 37 | } 38 | 39 | .figure-caption { 40 | @include font-size($figure-caption-font-size); 41 | color: $figure-caption-color; 42 | } 43 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Toggles 2 | // 3 | // Used in conjunction with global variables to enable certain theme features. 4 | 5 | // Vendor 6 | @import "vendor/rfs"; 7 | 8 | // Deprecate 9 | @import "mixins/deprecate"; 10 | 11 | // Helpers 12 | @import "mixins/breakpoints"; 13 | @import "mixins/color-mode"; 14 | @import "mixins/color-scheme"; 15 | @import "mixins/image"; 16 | @import "mixins/resize"; 17 | @import "mixins/visually-hidden"; 18 | @import "mixins/reset-text"; 19 | @import "mixins/text-truncate"; 20 | 21 | // Utilities 22 | @import "mixins/utilities"; 23 | 24 | // Components 25 | @import "mixins/backdrop"; 26 | @import "mixins/buttons"; 27 | @import "mixins/caret"; 28 | @import "mixins/pagination"; 29 | @import "mixins/lists"; 30 | @import "mixins/forms"; 31 | @import "mixins/table-variants"; 32 | 33 | // Skins 34 | @import "mixins/border-radius"; 35 | @import "mixins/box-shadow"; 36 | @import "mixins/gradients"; 37 | @import "mixins/transition"; 38 | 39 | // Layout 40 | @import "mixins/clearfix"; 41 | @import "mixins/container"; 42 | @import "mixins/grid"; 43 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_pagination.scss: -------------------------------------------------------------------------------- 1 | .pagination { 2 | // scss-docs-start pagination-css-vars 3 | --#{$prefix}pagination-padding-x: #{$pagination-padding-x}; 4 | --#{$prefix}pagination-padding-y: #{$pagination-padding-y}; 5 | @include rfs($pagination-font-size, --#{$prefix}pagination-font-size); 6 | --#{$prefix}pagination-color: #{$pagination-color}; 7 | --#{$prefix}pagination-bg: #{$pagination-bg}; 8 | --#{$prefix}pagination-border-width: #{$pagination-border-width}; 9 | --#{$prefix}pagination-border-color: #{$pagination-border-color}; 10 | --#{$prefix}pagination-border-radius: #{$pagination-border-radius}; 11 | --#{$prefix}pagination-hover-color: #{$pagination-hover-color}; 12 | --#{$prefix}pagination-hover-bg: #{$pagination-hover-bg}; 13 | --#{$prefix}pagination-hover-border-color: #{$pagination-hover-border-color}; 14 | --#{$prefix}pagination-focus-color: #{$pagination-focus-color}; 15 | --#{$prefix}pagination-focus-bg: #{$pagination-focus-bg}; 16 | --#{$prefix}pagination-focus-box-shadow: #{$pagination-focus-box-shadow}; 17 | --#{$prefix}pagination-active-color: #{$pagination-active-color}; 18 | --#{$prefix}pagination-active-bg: #{$pagination-active-bg}; 19 | --#{$prefix}pagination-active-border-color: #{$pagination-active-border-color}; 20 | --#{$prefix}pagination-disabled-color: #{$pagination-disabled-color}; 21 | --#{$prefix}pagination-disabled-bg: #{$pagination-disabled-bg}; 22 | --#{$prefix}pagination-disabled-border-color: #{$pagination-disabled-border-color}; 23 | // scss-docs-end pagination-css-vars 24 | 25 | display: flex; 26 | @include list-unstyled(); 27 | } 28 | 29 | .page-link { 30 | position: relative; 31 | display: block; 32 | padding: var(--#{$prefix}pagination-padding-y) var(--#{$prefix}pagination-padding-x); 33 | @include font-size(var(--#{$prefix}pagination-font-size)); 34 | color: var(--#{$prefix}pagination-color); 35 | text-decoration: if($link-decoration == none, null, none); 36 | background-color: var(--#{$prefix}pagination-bg); 37 | border: var(--#{$prefix}pagination-border-width) solid var(--#{$prefix}pagination-border-color); 38 | @include transition($pagination-transition); 39 | 40 | &:hover { 41 | z-index: 2; 42 | color: var(--#{$prefix}pagination-hover-color); 43 | text-decoration: if($link-hover-decoration == underline, none, null); 44 | background-color: var(--#{$prefix}pagination-hover-bg); 45 | border-color: var(--#{$prefix}pagination-hover-border-color); 46 | } 47 | 48 | &:focus { 49 | z-index: 3; 50 | color: var(--#{$prefix}pagination-focus-color); 51 | background-color: var(--#{$prefix}pagination-focus-bg); 52 | outline: $pagination-focus-outline; 53 | box-shadow: var(--#{$prefix}pagination-focus-box-shadow); 54 | } 55 | 56 | &.active, 57 | .active > & { 58 | z-index: 3; 59 | color: var(--#{$prefix}pagination-active-color); 60 | @include gradient-bg(var(--#{$prefix}pagination-active-bg)); 61 | border-color: var(--#{$prefix}pagination-active-border-color); 62 | } 63 | 64 | &.disabled, 65 | .disabled > & { 66 | color: var(--#{$prefix}pagination-disabled-color); 67 | pointer-events: none; 68 | background-color: var(--#{$prefix}pagination-disabled-bg); 69 | border-color: var(--#{$prefix}pagination-disabled-border-color); 70 | } 71 | } 72 | 73 | .page-item { 74 | &:not(:first-child) .page-link { 75 | margin-left: $pagination-margin-start; 76 | } 77 | 78 | @if $pagination-margin-start == calc(-1 * #{$pagination-border-width}) { 79 | &:first-child { 80 | .page-link { 81 | @include border-start-radius(var(--#{$prefix}pagination-border-radius)); 82 | } 83 | } 84 | 85 | &:last-child { 86 | .page-link { 87 | @include border-end-radius(var(--#{$prefix}pagination-border-radius)); 88 | } 89 | } 90 | } @else { 91 | // Add border-radius to all pageLinks in case they have left margin 92 | .page-link { 93 | @include border-radius(var(--#{$prefix}pagination-border-radius)); 94 | } 95 | } 96 | } 97 | 98 | 99 | // 100 | // Sizing 101 | // 102 | 103 | .pagination-lg { 104 | @include pagination-size($pagination-padding-y-lg, $pagination-padding-x-lg, $font-size-lg, $pagination-border-radius-lg); 105 | } 106 | 107 | .pagination-sm { 108 | @include pagination-size($pagination-padding-y-sm, $pagination-padding-x-sm, $font-size-sm, $pagination-border-radius-sm); 109 | } 110 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_placeholders.scss: -------------------------------------------------------------------------------- 1 | .placeholder { 2 | display: inline-block; 3 | min-height: 1em; 4 | vertical-align: middle; 5 | cursor: wait; 6 | background-color: currentcolor; 7 | opacity: $placeholder-opacity-max; 8 | 9 | &.btn::before { 10 | display: inline-block; 11 | content: ""; 12 | } 13 | } 14 | 15 | // Sizing 16 | .placeholder-xs { 17 | min-height: .6em; 18 | } 19 | 20 | .placeholder-sm { 21 | min-height: .8em; 22 | } 23 | 24 | .placeholder-lg { 25 | min-height: 1.2em; 26 | } 27 | 28 | // Animation 29 | .placeholder-glow { 30 | .placeholder { 31 | animation: placeholder-glow 2s ease-in-out infinite; 32 | } 33 | } 34 | 35 | @keyframes placeholder-glow { 36 | 50% { 37 | opacity: $placeholder-opacity-min; 38 | } 39 | } 40 | 41 | .placeholder-wave { 42 | mask-image: linear-gradient(130deg, $black 55%, rgba(0, 0, 0, (1 - $placeholder-opacity-min)) 75%, $black 95%); 43 | mask-size: 200% 100%; 44 | animation: placeholder-wave 2s linear infinite; 45 | } 46 | 47 | @keyframes placeholder-wave { 48 | 100% { 49 | mask-position: -200% 0%; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_progress.scss: -------------------------------------------------------------------------------- 1 | // Disable animation if transitions are disabled 2 | 3 | // scss-docs-start progress-keyframes 4 | @if $enable-transitions { 5 | @keyframes progress-bar-stripes { 6 | 0% { background-position-x: var(--#{$prefix}progress-height); } 7 | } 8 | } 9 | // scss-docs-end progress-keyframes 10 | 11 | .progress, 12 | .progress-stacked { 13 | // scss-docs-start progress-css-vars 14 | --#{$prefix}progress-height: #{$progress-height}; 15 | @include rfs($progress-font-size, --#{$prefix}progress-font-size); 16 | --#{$prefix}progress-bg: #{$progress-bg}; 17 | --#{$prefix}progress-border-radius: #{$progress-border-radius}; 18 | --#{$prefix}progress-box-shadow: #{$progress-box-shadow}; 19 | --#{$prefix}progress-bar-color: #{$progress-bar-color}; 20 | --#{$prefix}progress-bar-bg: #{$progress-bar-bg}; 21 | --#{$prefix}progress-bar-transition: #{$progress-bar-transition}; 22 | // scss-docs-end progress-css-vars 23 | 24 | display: flex; 25 | height: var(--#{$prefix}progress-height); 26 | overflow: hidden; // force rounded corners by cropping it 27 | @include font-size(var(--#{$prefix}progress-font-size)); 28 | background-color: var(--#{$prefix}progress-bg); 29 | @include border-radius(var(--#{$prefix}progress-border-radius)); 30 | @include box-shadow(var(--#{$prefix}progress-box-shadow)); 31 | } 32 | 33 | .progress-bar { 34 | display: flex; 35 | flex-direction: column; 36 | justify-content: center; 37 | overflow: hidden; 38 | color: var(--#{$prefix}progress-bar-color); 39 | text-align: center; 40 | white-space: nowrap; 41 | background-color: var(--#{$prefix}progress-bar-bg); 42 | @include transition(var(--#{$prefix}progress-bar-transition)); 43 | } 44 | 45 | .progress-bar-striped { 46 | @include gradient-striped(); 47 | background-size: var(--#{$prefix}progress-height) var(--#{$prefix}progress-height); 48 | } 49 | 50 | .progress-stacked > .progress { 51 | overflow: visible; 52 | } 53 | 54 | .progress-stacked > .progress > .progress-bar { 55 | width: 100%; 56 | } 57 | 58 | @if $enable-transitions { 59 | .progress-bar-animated { 60 | animation: $progress-bar-animation-timing progress-bar-stripes; 61 | 62 | @if $enable-reduced-motion { 63 | @media (prefers-reduced-motion: reduce) { 64 | animation: none; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_spinners.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Rotating border 3 | // 4 | 5 | .spinner-grow, 6 | .spinner-border { 7 | display: inline-block; 8 | width: var(--#{$prefix}spinner-width); 9 | height: var(--#{$prefix}spinner-height); 10 | vertical-align: var(--#{$prefix}spinner-vertical-align); 11 | // stylelint-disable-next-line property-disallowed-list 12 | border-radius: 50%; 13 | animation: var(--#{$prefix}spinner-animation-speed) linear infinite var(--#{$prefix}spinner-animation-name); 14 | } 15 | 16 | // scss-docs-start spinner-border-keyframes 17 | @keyframes spinner-border { 18 | to { transform: rotate(360deg) #{"/* rtl:ignore */"}; } 19 | } 20 | // scss-docs-end spinner-border-keyframes 21 | 22 | .spinner-border { 23 | // scss-docs-start spinner-border-css-vars 24 | --#{$prefix}spinner-width: #{$spinner-width}; 25 | --#{$prefix}spinner-height: #{$spinner-height}; 26 | --#{$prefix}spinner-vertical-align: #{$spinner-vertical-align}; 27 | --#{$prefix}spinner-border-width: #{$spinner-border-width}; 28 | --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed}; 29 | --#{$prefix}spinner-animation-name: spinner-border; 30 | // scss-docs-end spinner-border-css-vars 31 | 32 | border: var(--#{$prefix}spinner-border-width) solid currentcolor; 33 | border-right-color: transparent; 34 | } 35 | 36 | .spinner-border-sm { 37 | // scss-docs-start spinner-border-sm-css-vars 38 | --#{$prefix}spinner-width: #{$spinner-width-sm}; 39 | --#{$prefix}spinner-height: #{$spinner-height-sm}; 40 | --#{$prefix}spinner-border-width: #{$spinner-border-width-sm}; 41 | // scss-docs-end spinner-border-sm-css-vars 42 | } 43 | 44 | // 45 | // Growing circle 46 | // 47 | 48 | // scss-docs-start spinner-grow-keyframes 49 | @keyframes spinner-grow { 50 | 0% { 51 | transform: scale(0); 52 | } 53 | 50% { 54 | opacity: 1; 55 | transform: none; 56 | } 57 | } 58 | // scss-docs-end spinner-grow-keyframes 59 | 60 | .spinner-grow { 61 | // scss-docs-start spinner-grow-css-vars 62 | --#{$prefix}spinner-width: #{$spinner-width}; 63 | --#{$prefix}spinner-height: #{$spinner-height}; 64 | --#{$prefix}spinner-vertical-align: #{$spinner-vertical-align}; 65 | --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed}; 66 | --#{$prefix}spinner-animation-name: spinner-grow; 67 | // scss-docs-end spinner-grow-css-vars 68 | 69 | background-color: currentcolor; 70 | opacity: 0; 71 | } 72 | 73 | .spinner-grow-sm { 74 | --#{$prefix}spinner-width: #{$spinner-width-sm}; 75 | --#{$prefix}spinner-height: #{$spinner-height-sm}; 76 | } 77 | 78 | @if $enable-reduced-motion { 79 | @media (prefers-reduced-motion: reduce) { 80 | .spinner-border, 81 | .spinner-grow { 82 | --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed * 2}; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_toasts.scss: -------------------------------------------------------------------------------- 1 | .toast { 2 | // scss-docs-start toast-css-vars 3 | --#{$prefix}toast-zindex: #{$zindex-toast}; 4 | --#{$prefix}toast-padding-x: #{$toast-padding-x}; 5 | --#{$prefix}toast-padding-y: #{$toast-padding-y}; 6 | --#{$prefix}toast-spacing: #{$toast-spacing}; 7 | --#{$prefix}toast-max-width: #{$toast-max-width}; 8 | @include rfs($toast-font-size, --#{$prefix}toast-font-size); 9 | --#{$prefix}toast-color: #{$toast-color}; 10 | --#{$prefix}toast-bg: #{$toast-background-color}; 11 | --#{$prefix}toast-border-width: #{$toast-border-width}; 12 | --#{$prefix}toast-border-color: #{$toast-border-color}; 13 | --#{$prefix}toast-border-radius: #{$toast-border-radius}; 14 | --#{$prefix}toast-box-shadow: #{$toast-box-shadow}; 15 | --#{$prefix}toast-header-color: #{$toast-header-color}; 16 | --#{$prefix}toast-header-bg: #{$toast-header-background-color}; 17 | --#{$prefix}toast-header-border-color: #{$toast-header-border-color}; 18 | // scss-docs-end toast-css-vars 19 | 20 | width: var(--#{$prefix}toast-max-width); 21 | max-width: 100%; 22 | @include font-size(var(--#{$prefix}toast-font-size)); 23 | color: var(--#{$prefix}toast-color); 24 | pointer-events: auto; 25 | background-color: var(--#{$prefix}toast-bg); 26 | background-clip: padding-box; 27 | border: var(--#{$prefix}toast-border-width) solid var(--#{$prefix}toast-border-color); 28 | box-shadow: var(--#{$prefix}toast-box-shadow); 29 | @include border-radius(var(--#{$prefix}toast-border-radius)); 30 | 31 | &.showing { 32 | opacity: 0; 33 | } 34 | 35 | &:not(.show) { 36 | display: none; 37 | } 38 | } 39 | 40 | .toast-container { 41 | --#{$prefix}toast-zindex: #{$zindex-toast}; 42 | 43 | position: absolute; 44 | z-index: var(--#{$prefix}toast-zindex); 45 | width: max-content; 46 | max-width: 100%; 47 | pointer-events: none; 48 | 49 | > :not(:last-child) { 50 | margin-bottom: var(--#{$prefix}toast-spacing); 51 | } 52 | } 53 | 54 | .toast-header { 55 | display: flex; 56 | align-items: center; 57 | padding: var(--#{$prefix}toast-padding-y) var(--#{$prefix}toast-padding-x); 58 | color: var(--#{$prefix}toast-header-color); 59 | background-color: var(--#{$prefix}toast-header-bg); 60 | background-clip: padding-box; 61 | border-bottom: var(--#{$prefix}toast-border-width) solid var(--#{$prefix}toast-header-border-color); 62 | @include border-top-radius(calc(var(--#{$prefix}toast-border-radius) - var(--#{$prefix}toast-border-width))); 63 | 64 | .btn-close { 65 | margin-right: calc(-.5 * var(--#{$prefix}toast-padding-x)); // stylelint-disable-line function-disallowed-list 66 | margin-left: var(--#{$prefix}toast-padding-x); 67 | } 68 | } 69 | 70 | .toast-body { 71 | padding: var(--#{$prefix}toast-padding-x); 72 | word-wrap: break-word; 73 | } 74 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_tooltip.scss: -------------------------------------------------------------------------------- 1 | // Base class 2 | .tooltip { 3 | // scss-docs-start tooltip-css-vars 4 | --#{$prefix}tooltip-zindex: #{$zindex-tooltip}; 5 | --#{$prefix}tooltip-max-width: #{$tooltip-max-width}; 6 | --#{$prefix}tooltip-padding-x: #{$tooltip-padding-x}; 7 | --#{$prefix}tooltip-padding-y: #{$tooltip-padding-y}; 8 | --#{$prefix}tooltip-margin: #{$tooltip-margin}; 9 | @include rfs($tooltip-font-size, --#{$prefix}tooltip-font-size); 10 | --#{$prefix}tooltip-color: #{$tooltip-color}; 11 | --#{$prefix}tooltip-bg: #{$tooltip-bg}; 12 | --#{$prefix}tooltip-border-radius: #{$tooltip-border-radius}; 13 | --#{$prefix}tooltip-opacity: #{$tooltip-opacity}; 14 | --#{$prefix}tooltip-arrow-width: #{$tooltip-arrow-width}; 15 | --#{$prefix}tooltip-arrow-height: #{$tooltip-arrow-height}; 16 | // scss-docs-end tooltip-css-vars 17 | 18 | z-index: var(--#{$prefix}tooltip-zindex); 19 | display: block; 20 | margin: var(--#{$prefix}tooltip-margin); 21 | @include deprecate("`$tooltip-margin`", "v5", "v5.x", true); 22 | // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element. 23 | // So reset our font and text properties to avoid inheriting weird values. 24 | @include reset-text(); 25 | @include font-size(var(--#{$prefix}tooltip-font-size)); 26 | // Allow breaking very long words so they don't overflow the tooltip's bounds 27 | word-wrap: break-word; 28 | opacity: 0; 29 | 30 | &.show { opacity: var(--#{$prefix}tooltip-opacity); } 31 | 32 | .tooltip-arrow { 33 | display: block; 34 | width: var(--#{$prefix}tooltip-arrow-width); 35 | height: var(--#{$prefix}tooltip-arrow-height); 36 | 37 | &::before { 38 | position: absolute; 39 | content: ""; 40 | border-color: transparent; 41 | border-style: solid; 42 | } 43 | } 44 | } 45 | 46 | .bs-tooltip-top .tooltip-arrow { 47 | bottom: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list 48 | 49 | &::before { 50 | top: -1px; 51 | border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list 52 | border-top-color: var(--#{$prefix}tooltip-bg); 53 | } 54 | } 55 | 56 | /* rtl:begin:ignore */ 57 | .bs-tooltip-end .tooltip-arrow { 58 | left: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list 59 | width: var(--#{$prefix}tooltip-arrow-height); 60 | height: var(--#{$prefix}tooltip-arrow-width); 61 | 62 | &::before { 63 | right: -1px; 64 | border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list 65 | border-right-color: var(--#{$prefix}tooltip-bg); 66 | } 67 | } 68 | 69 | /* rtl:end:ignore */ 70 | 71 | .bs-tooltip-bottom .tooltip-arrow { 72 | top: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list 73 | 74 | &::before { 75 | bottom: -1px; 76 | border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list 77 | border-bottom-color: var(--#{$prefix}tooltip-bg); 78 | } 79 | } 80 | 81 | /* rtl:begin:ignore */ 82 | .bs-tooltip-start .tooltip-arrow { 83 | right: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list 84 | width: var(--#{$prefix}tooltip-arrow-height); 85 | height: var(--#{$prefix}tooltip-arrow-width); 86 | 87 | &::before { 88 | left: -1px; 89 | border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list 90 | border-left-color: var(--#{$prefix}tooltip-bg); 91 | } 92 | } 93 | 94 | /* rtl:end:ignore */ 95 | 96 | .bs-tooltip-auto { 97 | &[data-popper-placement^="top"] { 98 | @extend .bs-tooltip-top; 99 | } 100 | &[data-popper-placement^="right"] { 101 | @extend .bs-tooltip-end; 102 | } 103 | &[data-popper-placement^="bottom"] { 104 | @extend .bs-tooltip-bottom; 105 | } 106 | &[data-popper-placement^="left"] { 107 | @extend .bs-tooltip-start; 108 | } 109 | } 110 | 111 | // Wrapper for the tooltip content 112 | .tooltip-inner { 113 | max-width: var(--#{$prefix}tooltip-max-width); 114 | padding: var(--#{$prefix}tooltip-padding-y) var(--#{$prefix}tooltip-padding-x); 115 | color: var(--#{$prefix}tooltip-color); 116 | text-align: center; 117 | background-color: var(--#{$prefix}tooltip-bg); 118 | @include border-radius(var(--#{$prefix}tooltip-border-radius)); 119 | } 120 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_transitions.scss: -------------------------------------------------------------------------------- 1 | .fade { 2 | @include transition($transition-fade); 3 | 4 | &:not(.show) { 5 | opacity: 0; 6 | } 7 | } 8 | 9 | // scss-docs-start collapse-classes 10 | .collapse { 11 | &:not(.show) { 12 | display: none; 13 | } 14 | } 15 | 16 | .collapsing { 17 | height: 0; 18 | overflow: hidden; 19 | @include transition($transition-collapse); 20 | 21 | &.collapse-horizontal { 22 | width: 0; 23 | height: auto; 24 | @include transition($transition-collapse-width); 25 | } 26 | } 27 | // scss-docs-end collapse-classes 28 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/_type.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Headings 3 | // 4 | .h1 { 5 | @extend h1; 6 | } 7 | 8 | .h2 { 9 | @extend h2; 10 | } 11 | 12 | .h3 { 13 | @extend h3; 14 | } 15 | 16 | .h4 { 17 | @extend h4; 18 | } 19 | 20 | .h5 { 21 | @extend h5; 22 | } 23 | 24 | .h6 { 25 | @extend h6; 26 | } 27 | 28 | 29 | .lead { 30 | @include font-size($lead-font-size); 31 | font-weight: $lead-font-weight; 32 | } 33 | 34 | // Type display classes 35 | @each $display, $font-size in $display-font-sizes { 36 | .display-#{$display} { 37 | font-family: $display-font-family; 38 | font-style: $display-font-style; 39 | font-weight: $display-font-weight; 40 | line-height: $display-line-height; 41 | @include font-size($font-size); 42 | } 43 | } 44 | 45 | // 46 | // Emphasis 47 | // 48 | .small { 49 | @extend small; 50 | } 51 | 52 | .mark { 53 | @extend mark; 54 | } 55 | 56 | // 57 | // Lists 58 | // 59 | 60 | .list-unstyled { 61 | @include list-unstyled(); 62 | } 63 | 64 | // Inline turns list items into inline-block 65 | .list-inline { 66 | @include list-unstyled(); 67 | } 68 | .list-inline-item { 69 | display: inline-block; 70 | 71 | &:not(:last-child) { 72 | margin-right: $list-inline-padding; 73 | } 74 | } 75 | 76 | 77 | // 78 | // Misc 79 | // 80 | 81 | // Builds on `abbr` 82 | .initialism { 83 | @include font-size($initialism-font-size); 84 | text-transform: uppercase; 85 | } 86 | 87 | // Blockquotes 88 | .blockquote { 89 | margin-bottom: $blockquote-margin-y; 90 | @include font-size($blockquote-font-size); 91 | 92 | > :last-child { 93 | margin-bottom: 0; 94 | } 95 | } 96 | 97 | .blockquote-footer { 98 | margin-top: -$blockquote-margin-y; 99 | margin-bottom: $blockquote-margin-y; 100 | @include font-size($blockquote-footer-font-size); 101 | color: $blockquote-footer-color; 102 | 103 | &::before { 104 | content: "\2014\00A0"; // em dash, nbsp 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/forms/_floating-labels.scss: -------------------------------------------------------------------------------- 1 | .form-floating { 2 | position: relative; 3 | 4 | > .form-control, 5 | > .form-control-plaintext, 6 | > .form-select { 7 | height: $form-floating-height; 8 | min-height: $form-floating-height; 9 | line-height: $form-floating-line-height; 10 | } 11 | 12 | > label { 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | z-index: 2; 17 | max-width: 100%; 18 | height: 100%; // allow textareas 19 | padding: $form-floating-padding-y $form-floating-padding-x; 20 | overflow: hidden; 21 | color: rgba(var(--#{$prefix}body-color-rgb), #{$form-floating-label-opacity}); 22 | text-align: start; 23 | text-overflow: ellipsis; 24 | white-space: nowrap; 25 | pointer-events: none; 26 | border: $input-border-width solid transparent; // Required for aligning label's text with the input as it affects inner box model 27 | transform-origin: 0 0; 28 | @include transition($form-floating-transition); 29 | } 30 | 31 | > .form-control, 32 | > .form-control-plaintext { 33 | padding: $form-floating-padding-y $form-floating-padding-x; 34 | 35 | &::placeholder { 36 | color: transparent; 37 | } 38 | 39 | &:focus, 40 | &:not(:placeholder-shown) { 41 | padding-top: $form-floating-input-padding-t; 42 | padding-bottom: $form-floating-input-padding-b; 43 | } 44 | // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped 45 | &:-webkit-autofill { 46 | padding-top: $form-floating-input-padding-t; 47 | padding-bottom: $form-floating-input-padding-b; 48 | } 49 | } 50 | 51 | > .form-select { 52 | padding-top: $form-floating-input-padding-t; 53 | padding-bottom: $form-floating-input-padding-b; 54 | padding-left: $form-floating-padding-x; 55 | } 56 | 57 | > .form-control:focus, 58 | > .form-control:not(:placeholder-shown), 59 | > .form-control-plaintext, 60 | > .form-select { 61 | ~ label { 62 | transform: $form-floating-label-transform; 63 | } 64 | } 65 | // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped 66 | > .form-control:-webkit-autofill { 67 | ~ label { 68 | transform: $form-floating-label-transform; 69 | } 70 | } 71 | > textarea:focus, 72 | > textarea:not(:placeholder-shown) { 73 | ~ label::after { 74 | position: absolute; 75 | inset: $form-floating-padding-y ($form-floating-padding-x * .5); 76 | z-index: -1; 77 | height: $form-floating-label-height; 78 | content: ""; 79 | background-color: $input-bg; 80 | @include border-radius($input-border-radius); 81 | } 82 | } 83 | > textarea:disabled ~ label::after { 84 | background-color: $input-disabled-bg; 85 | } 86 | 87 | > .form-control-plaintext { 88 | ~ label { 89 | border-width: $input-border-width 0; // Required to properly position label text - as explained above 90 | } 91 | } 92 | 93 | > :disabled ~ label, 94 | > .form-control:disabled ~ label { // Required for `.form-control`s because of specificity 95 | color: $form-floating-label-disabled-color; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/forms/_form-range.scss: -------------------------------------------------------------------------------- 1 | // Range 2 | // 3 | // Style range inputs the same across browsers. Vendor-specific rules for pseudo 4 | // elements cannot be mixed. As such, there are no shared styles for focus or 5 | // active states on prefixed selectors. 6 | 7 | .form-range { 8 | width: 100%; 9 | height: add($form-range-thumb-height, $form-range-thumb-focus-box-shadow-width * 2); 10 | padding: 0; // Need to reset padding 11 | appearance: none; 12 | background-color: transparent; 13 | 14 | &:focus { 15 | outline: 0; 16 | 17 | // Pseudo-elements must be split across multiple rulesets to have an effect. 18 | // No box-shadow() mixin for focus accessibility. 19 | &::-webkit-slider-thumb { box-shadow: $form-range-thumb-focus-box-shadow; } 20 | &::-moz-range-thumb { box-shadow: $form-range-thumb-focus-box-shadow; } 21 | } 22 | 23 | &::-moz-focus-outer { 24 | border: 0; 25 | } 26 | 27 | &::-webkit-slider-thumb { 28 | width: $form-range-thumb-width; 29 | height: $form-range-thumb-height; 30 | margin-top: ($form-range-track-height - $form-range-thumb-height) * .5; // Webkit specific 31 | appearance: none; 32 | @include gradient-bg($form-range-thumb-bg); 33 | border: $form-range-thumb-border; 34 | @include border-radius($form-range-thumb-border-radius); 35 | @include box-shadow($form-range-thumb-box-shadow); 36 | @include transition($form-range-thumb-transition); 37 | 38 | &:active { 39 | @include gradient-bg($form-range-thumb-active-bg); 40 | } 41 | } 42 | 43 | &::-webkit-slider-runnable-track { 44 | width: $form-range-track-width; 45 | height: $form-range-track-height; 46 | color: transparent; // Why? 47 | cursor: $form-range-track-cursor; 48 | background-color: $form-range-track-bg; 49 | border-color: transparent; 50 | @include border-radius($form-range-track-border-radius); 51 | @include box-shadow($form-range-track-box-shadow); 52 | } 53 | 54 | &::-moz-range-thumb { 55 | width: $form-range-thumb-width; 56 | height: $form-range-thumb-height; 57 | appearance: none; 58 | @include gradient-bg($form-range-thumb-bg); 59 | border: $form-range-thumb-border; 60 | @include border-radius($form-range-thumb-border-radius); 61 | @include box-shadow($form-range-thumb-box-shadow); 62 | @include transition($form-range-thumb-transition); 63 | 64 | &:active { 65 | @include gradient-bg($form-range-thumb-active-bg); 66 | } 67 | } 68 | 69 | &::-moz-range-track { 70 | width: $form-range-track-width; 71 | height: $form-range-track-height; 72 | color: transparent; 73 | cursor: $form-range-track-cursor; 74 | background-color: $form-range-track-bg; 75 | border-color: transparent; // Firefox specific? 76 | @include border-radius($form-range-track-border-radius); 77 | @include box-shadow($form-range-track-box-shadow); 78 | } 79 | 80 | &:disabled { 81 | pointer-events: none; 82 | 83 | &::-webkit-slider-thumb { 84 | background-color: $form-range-thumb-disabled-bg; 85 | } 86 | 87 | &::-moz-range-thumb { 88 | background-color: $form-range-thumb-disabled-bg; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/forms/_form-select.scss: -------------------------------------------------------------------------------- 1 | // Select 2 | // 3 | // Replaces the browser default select with a custom one, mostly pulled from 4 | // https://primer.github.io/. 5 | 6 | .form-select { 7 | --#{$prefix}form-select-bg-img: #{escape-svg($form-select-indicator)}; 8 | 9 | display: block; 10 | width: 100%; 11 | padding: $form-select-padding-y $form-select-indicator-padding $form-select-padding-y $form-select-padding-x; 12 | font-family: $form-select-font-family; 13 | @include font-size($form-select-font-size); 14 | font-weight: $form-select-font-weight; 15 | line-height: $form-select-line-height; 16 | color: $form-select-color; 17 | appearance: none; 18 | background-color: $form-select-bg; 19 | background-image: var(--#{$prefix}form-select-bg-img), var(--#{$prefix}form-select-bg-icon, none); 20 | background-repeat: no-repeat; 21 | background-position: $form-select-bg-position; 22 | background-size: $form-select-bg-size; 23 | border: $form-select-border-width solid $form-select-border-color; 24 | @include border-radius($form-select-border-radius, 0); 25 | @include box-shadow($form-select-box-shadow); 26 | @include transition($form-select-transition); 27 | 28 | &:focus { 29 | border-color: $form-select-focus-border-color; 30 | outline: 0; 31 | @if $enable-shadows { 32 | @include box-shadow($form-select-box-shadow, $form-select-focus-box-shadow); 33 | } @else { 34 | // Avoid using mixin so we can pass custom focus shadow properly 35 | box-shadow: $form-select-focus-box-shadow; 36 | } 37 | } 38 | 39 | &[multiple], 40 | &[size]:not([size="1"]) { 41 | padding-right: $form-select-padding-x; 42 | background-image: none; 43 | } 44 | 45 | &:disabled { 46 | color: $form-select-disabled-color; 47 | background-color: $form-select-disabled-bg; 48 | border-color: $form-select-disabled-border-color; 49 | } 50 | 51 | // Remove outline from select box in FF 52 | &:-moz-focusring { 53 | color: transparent; 54 | text-shadow: 0 0 0 $form-select-color; 55 | } 56 | } 57 | 58 | .form-select-sm { 59 | padding-top: $form-select-padding-y-sm; 60 | padding-bottom: $form-select-padding-y-sm; 61 | padding-left: $form-select-padding-x-sm; 62 | @include font-size($form-select-font-size-sm); 63 | @include border-radius($form-select-border-radius-sm); 64 | } 65 | 66 | .form-select-lg { 67 | padding-top: $form-select-padding-y-lg; 68 | padding-bottom: $form-select-padding-y-lg; 69 | padding-left: $form-select-padding-x-lg; 70 | @include font-size($form-select-font-size-lg); 71 | @include border-radius($form-select-border-radius-lg); 72 | } 73 | 74 | @if $enable-dark-mode { 75 | @include color-mode(dark) { 76 | .form-select { 77 | --#{$prefix}form-select-bg-img: #{escape-svg($form-select-indicator-dark)}; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/forms/_form-text.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Form text 3 | // 4 | 5 | .form-text { 6 | margin-top: $form-text-margin-top; 7 | @include font-size($form-text-font-size); 8 | font-style: $form-text-font-style; 9 | font-weight: $form-text-font-weight; 10 | color: $form-text-color; 11 | } 12 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/forms/_input-group.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Base styles 3 | // 4 | 5 | .input-group { 6 | position: relative; 7 | display: flex; 8 | flex-wrap: wrap; // For form validation feedback 9 | align-items: stretch; 10 | width: 100%; 11 | 12 | > .form-control, 13 | > .form-select, 14 | > .form-floating { 15 | position: relative; // For focus state's z-index 16 | flex: 1 1 auto; 17 | width: 1%; 18 | min-width: 0; // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size 19 | } 20 | 21 | // Bring the "active" form control to the top of surrounding elements 22 | > .form-control:focus, 23 | > .form-select:focus, 24 | > .form-floating:focus-within { 25 | z-index: 5; 26 | } 27 | 28 | // Ensure buttons are always above inputs for more visually pleasing borders. 29 | // This isn't needed for `.input-group-text` since it shares the same border-color 30 | // as our inputs. 31 | .btn { 32 | position: relative; 33 | z-index: 2; 34 | 35 | &:focus { 36 | z-index: 5; 37 | } 38 | } 39 | } 40 | 41 | 42 | // Textual addons 43 | // 44 | // Serves as a catch-all element for any text or radio/checkbox input you wish 45 | // to prepend or append to an input. 46 | 47 | .input-group-text { 48 | display: flex; 49 | align-items: center; 50 | padding: $input-group-addon-padding-y $input-group-addon-padding-x; 51 | @include font-size($input-font-size); // Match inputs 52 | font-weight: $input-group-addon-font-weight; 53 | line-height: $input-line-height; 54 | color: $input-group-addon-color; 55 | text-align: center; 56 | white-space: nowrap; 57 | background-color: $input-group-addon-bg; 58 | border: $input-border-width solid $input-group-addon-border-color; 59 | @include border-radius($input-border-radius); 60 | } 61 | 62 | 63 | // Sizing 64 | // 65 | // Remix the default form control sizing classes into new ones for easier 66 | // manipulation. 67 | 68 | .input-group-lg > .form-control, 69 | .input-group-lg > .form-select, 70 | .input-group-lg > .input-group-text, 71 | .input-group-lg > .btn { 72 | padding: $input-padding-y-lg $input-padding-x-lg; 73 | @include font-size($input-font-size-lg); 74 | @include border-radius($input-border-radius-lg); 75 | } 76 | 77 | .input-group-sm > .form-control, 78 | .input-group-sm > .form-select, 79 | .input-group-sm > .input-group-text, 80 | .input-group-sm > .btn { 81 | padding: $input-padding-y-sm $input-padding-x-sm; 82 | @include font-size($input-font-size-sm); 83 | @include border-radius($input-border-radius-sm); 84 | } 85 | 86 | .input-group-lg > .form-select, 87 | .input-group-sm > .form-select { 88 | padding-right: $form-select-padding-x + $form-select-indicator-padding; 89 | } 90 | 91 | 92 | // Rounded corners 93 | // 94 | // These rulesets must come after the sizing ones to properly override sm and lg 95 | // border-radius values when extending. They're more specific than we'd like 96 | // with the `.input-group >` part, but without it, we cannot override the sizing. 97 | 98 | // stylelint-disable-next-line no-duplicate-selectors 99 | .input-group { 100 | &:not(.has-validation) { 101 | > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), 102 | > .dropdown-toggle:nth-last-child(n + 3), 103 | > .form-floating:not(:last-child) > .form-control, 104 | > .form-floating:not(:last-child) > .form-select { 105 | @include border-end-radius(0); 106 | } 107 | } 108 | 109 | &.has-validation { 110 | > :nth-last-child(n + 3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), 111 | > .dropdown-toggle:nth-last-child(n + 4), 112 | > .form-floating:nth-last-child(n + 3) > .form-control, 113 | > .form-floating:nth-last-child(n + 3) > .form-select { 114 | @include border-end-radius(0); 115 | } 116 | } 117 | 118 | $validation-messages: ""; 119 | @each $state in map-keys($form-validation-states) { 120 | $validation-messages: $validation-messages + ":not(." + unquote($state) + "-tooltip)" + ":not(." + unquote($state) + "-feedback)"; 121 | } 122 | 123 | > :not(:first-child):not(.dropdown-menu)#{$validation-messages} { 124 | margin-left: calc(-1 * #{$input-border-width}); // stylelint-disable-line function-disallowed-list 125 | @include border-start-radius(0); 126 | } 127 | 128 | > .form-floating:not(:first-child) > .form-control, 129 | > .form-floating:not(:first-child) > .form-select { 130 | @include border-start-radius(0); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/forms/_labels.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Labels 3 | // 4 | 5 | .form-label { 6 | margin-bottom: $form-label-margin-bottom; 7 | @include font-size($form-label-font-size); 8 | font-style: $form-label-font-style; 9 | font-weight: $form-label-font-weight; 10 | color: $form-label-color; 11 | } 12 | 13 | // For use with horizontal and inline forms, when you need the label (or legend) 14 | // text to align with the form controls. 15 | .col-form-label { 16 | padding-top: add($input-padding-y, $input-border-width); 17 | padding-bottom: add($input-padding-y, $input-border-width); 18 | margin-bottom: 0; // Override the `` default 19 | @include font-size(inherit); // Override the `` default 20 | font-style: $form-label-font-style; 21 | font-weight: $form-label-font-weight; 22 | line-height: $input-line-height; 23 | color: $form-label-color; 24 | } 25 | 26 | .col-form-label-lg { 27 | padding-top: add($input-padding-y-lg, $input-border-width); 28 | padding-bottom: add($input-padding-y-lg, $input-border-width); 29 | @include font-size($input-font-size-lg); 30 | } 31 | 32 | .col-form-label-sm { 33 | padding-top: add($input-padding-y-sm, $input-border-width); 34 | padding-bottom: add($input-padding-y-sm, $input-border-width); 35 | @include font-size($input-font-size-sm); 36 | } 37 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/forms/_validation.scss: -------------------------------------------------------------------------------- 1 | // Form validation 2 | // 3 | // Provide feedback to users when form field values are valid or invalid. Works 4 | // primarily for client-side validation via scoped `:invalid` and `:valid` 5 | // pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for 6 | // server-side validation. 7 | 8 | // scss-docs-start form-validation-states-loop 9 | @each $state, $data in $form-validation-states { 10 | @include form-validation-state($state, $data...); 11 | } 12 | // scss-docs-end form-validation-states-loop 13 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_clearfix.scss: -------------------------------------------------------------------------------- 1 | .clearfix { 2 | @include clearfix(); 3 | } 4 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_color-bg.scss: -------------------------------------------------------------------------------- 1 | // All-caps `RGBA()` function used because of this Sass bug: https://github.com/sass/node-sass/issues/2251 2 | @each $color, $value in $theme-colors { 3 | .text-bg-#{$color} { 4 | color: color-contrast($value) if($enable-important-utilities, !important, null); 5 | background-color: RGBA(var(--#{$prefix}#{$color}-rgb), var(--#{$prefix}bg-opacity, 1)) if($enable-important-utilities, !important, null); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_colored-links.scss: -------------------------------------------------------------------------------- 1 | // All-caps `RGBA()` function used because of this Sass bug: https://github.com/sass/node-sass/issues/2251 2 | @each $color, $value in $theme-colors { 3 | .link-#{$color} { 4 | color: RGBA(var(--#{$prefix}#{$color}-rgb), var(--#{$prefix}link-opacity, 1)) if($enable-important-utilities, !important, null); 5 | text-decoration-color: RGBA(var(--#{$prefix}#{$color}-rgb), var(--#{$prefix}link-underline-opacity, 1)) if($enable-important-utilities, !important, null); 6 | 7 | @if $link-shade-percentage != 0 { 8 | &:hover, 9 | &:focus { 10 | $hover-color: if(color-contrast($value) == $color-contrast-light, shade-color($value, $link-shade-percentage), tint-color($value, $link-shade-percentage)); 11 | color: RGBA(#{to-rgb($hover-color)}, var(--#{$prefix}link-opacity, 1)) if($enable-important-utilities, !important, null); 12 | text-decoration-color: RGBA(to-rgb($hover-color), var(--#{$prefix}link-underline-opacity, 1)) if($enable-important-utilities, !important, null); 13 | } 14 | } 15 | } 16 | } 17 | 18 | // One-off special link helper as a bridge until v6 19 | .link-body-emphasis { 20 | color: RGBA(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-opacity, 1)) if($enable-important-utilities, !important, null); 21 | text-decoration-color: RGBA(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-underline-opacity, 1)) if($enable-important-utilities, !important, null); 22 | 23 | @if $link-shade-percentage != 0 { 24 | &:hover, 25 | &:focus { 26 | color: RGBA(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-opacity, .75)) if($enable-important-utilities, !important, null); 27 | text-decoration-color: RGBA(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-underline-opacity, .75)) if($enable-important-utilities, !important, null); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_focus-ring.scss: -------------------------------------------------------------------------------- 1 | .focus-ring:focus { 2 | outline: 0; 3 | // By default, there is no `--bs-focus-ring-x`, `--bs-focus-ring-y`, or `--bs-focus-ring-blur`, but we provide CSS variables with fallbacks to initial `0` values 4 | box-shadow: var(--#{$prefix}focus-ring-x, 0) var(--#{$prefix}focus-ring-y, 0) var(--#{$prefix}focus-ring-blur, 0) var(--#{$prefix}focus-ring-width) var(--#{$prefix}focus-ring-color); 5 | } 6 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_icon-link.scss: -------------------------------------------------------------------------------- 1 | .icon-link { 2 | display: inline-flex; 3 | gap: $icon-link-gap; 4 | align-items: center; 5 | text-decoration-color: rgba(var(--#{$prefix}link-color-rgb), var(--#{$prefix}link-opacity, .5)); 6 | text-underline-offset: $icon-link-underline-offset; 7 | backface-visibility: hidden; 8 | 9 | > .bi { 10 | flex-shrink: 0; 11 | width: $icon-link-icon-size; 12 | height: $icon-link-icon-size; 13 | fill: currentcolor; 14 | @include transition($icon-link-icon-transition); 15 | } 16 | } 17 | 18 | .icon-link-hover { 19 | &:hover, 20 | &:focus-visible { 21 | > .bi { 22 | transform: var(--#{$prefix}icon-link-transform, $icon-link-icon-transform); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_position.scss: -------------------------------------------------------------------------------- 1 | // Shorthand 2 | 3 | .fixed-top { 4 | position: fixed; 5 | top: 0; 6 | right: 0; 7 | left: 0; 8 | z-index: $zindex-fixed; 9 | } 10 | 11 | .fixed-bottom { 12 | position: fixed; 13 | right: 0; 14 | bottom: 0; 15 | left: 0; 16 | z-index: $zindex-fixed; 17 | } 18 | 19 | // Responsive sticky top and bottom 20 | @each $breakpoint in map-keys($grid-breakpoints) { 21 | @include media-breakpoint-up($breakpoint) { 22 | $infix: breakpoint-infix($breakpoint, $grid-breakpoints); 23 | 24 | .sticky#{$infix}-top { 25 | position: sticky; 26 | top: 0; 27 | z-index: $zindex-sticky; 28 | } 29 | 30 | .sticky#{$infix}-bottom { 31 | position: sticky; 32 | bottom: 0; 33 | z-index: $zindex-sticky; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_ratio.scss: -------------------------------------------------------------------------------- 1 | // Credit: Nicolas Gallagher and SUIT CSS. 2 | 3 | .ratio { 4 | position: relative; 5 | width: 100%; 6 | 7 | &::before { 8 | display: block; 9 | padding-top: var(--#{$prefix}aspect-ratio); 10 | content: ""; 11 | } 12 | 13 | > * { 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | width: 100%; 18 | height: 100%; 19 | } 20 | } 21 | 22 | @each $key, $ratio in $aspect-ratios { 23 | .ratio-#{$key} { 24 | --#{$prefix}aspect-ratio: #{$ratio}; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_stacks.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start stacks 2 | .hstack { 3 | display: flex; 4 | flex-direction: row; 5 | align-items: center; 6 | align-self: stretch; 7 | } 8 | 9 | .vstack { 10 | display: flex; 11 | flex: 1 1 auto; 12 | flex-direction: column; 13 | align-self: stretch; 14 | } 15 | // scss-docs-end stacks 16 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_stretched-link.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Stretched link 3 | // 4 | 5 | .stretched-link { 6 | &::#{$stretched-link-pseudo-element} { 7 | position: absolute; 8 | top: 0; 9 | right: 0; 10 | bottom: 0; 11 | left: 0; 12 | z-index: $stretched-link-z-index; 13 | content: ""; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_text-truncation.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Text truncation 3 | // 4 | 5 | .text-truncate { 6 | @include text-truncate(); 7 | } 8 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_visually-hidden.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Visually hidden 3 | // 4 | 5 | .visually-hidden, 6 | .visually-hidden-focusable:not(:focus):not(:focus-within) { 7 | @include visually-hidden(); 8 | } 9 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/helpers/_vr.scss: -------------------------------------------------------------------------------- 1 | .vr { 2 | display: inline-block; 3 | align-self: stretch; 4 | width: $vr-border-width; 5 | min-height: 1em; 6 | background-color: currentcolor; 7 | opacity: $hr-opacity; 8 | } 9 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_alert.scss: -------------------------------------------------------------------------------- 1 | @include deprecate("`alert-variant()`", "v5.3.0", "v6.0.0"); 2 | 3 | // scss-docs-start alert-variant-mixin 4 | @mixin alert-variant($background, $border, $color) { 5 | --#{$prefix}alert-color: #{$color}; 6 | --#{$prefix}alert-bg: #{$background}; 7 | --#{$prefix}alert-border-color: #{$border}; 8 | --#{$prefix}alert-link-color: #{shade-color($color, 20%)}; 9 | 10 | @if $enable-gradients { 11 | background-image: var(--#{$prefix}gradient); 12 | } 13 | 14 | .alert-link { 15 | color: var(--#{$prefix}alert-link-color); 16 | } 17 | } 18 | // scss-docs-end alert-variant-mixin 19 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_backdrop.scss: -------------------------------------------------------------------------------- 1 | // Shared between modals and offcanvases 2 | @mixin overlay-backdrop($zindex, $backdrop-bg, $backdrop-opacity) { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | z-index: $zindex; 7 | width: 100vw; 8 | height: 100vh; 9 | background-color: $backdrop-bg; 10 | 11 | // Fade for backdrop 12 | &.fade { opacity: 0; } 13 | &.show { opacity: $backdrop-opacity; } 14 | } 15 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_banner.scss: -------------------------------------------------------------------------------- 1 | @mixin bsBanner($file) { 2 | /*! 3 | * Bootstrap #{$file} v5.3.5 (https://getbootstrap.com/) 4 | * Copyright 2011-2025 The Bootstrap Authors 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | */ 7 | } 8 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_border-radius.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable property-disallowed-list 2 | // Single side border-radius 3 | 4 | // Helper function to replace negative values with 0 5 | @function valid-radius($radius) { 6 | $return: (); 7 | @each $value in $radius { 8 | @if type-of($value) == number { 9 | $return: append($return, max($value, 0)); 10 | } @else { 11 | $return: append($return, $value); 12 | } 13 | } 14 | @return $return; 15 | } 16 | 17 | // scss-docs-start border-radius-mixins 18 | @mixin border-radius($radius: $border-radius, $fallback-border-radius: false) { 19 | @if $enable-rounded { 20 | border-radius: valid-radius($radius); 21 | } 22 | @else if $fallback-border-radius != false { 23 | border-radius: $fallback-border-radius; 24 | } 25 | } 26 | 27 | @mixin border-top-radius($radius: $border-radius) { 28 | @if $enable-rounded { 29 | border-top-left-radius: valid-radius($radius); 30 | border-top-right-radius: valid-radius($radius); 31 | } 32 | } 33 | 34 | @mixin border-end-radius($radius: $border-radius) { 35 | @if $enable-rounded { 36 | border-top-right-radius: valid-radius($radius); 37 | border-bottom-right-radius: valid-radius($radius); 38 | } 39 | } 40 | 41 | @mixin border-bottom-radius($radius: $border-radius) { 42 | @if $enable-rounded { 43 | border-bottom-right-radius: valid-radius($radius); 44 | border-bottom-left-radius: valid-radius($radius); 45 | } 46 | } 47 | 48 | @mixin border-start-radius($radius: $border-radius) { 49 | @if $enable-rounded { 50 | border-top-left-radius: valid-radius($radius); 51 | border-bottom-left-radius: valid-radius($radius); 52 | } 53 | } 54 | 55 | @mixin border-top-start-radius($radius: $border-radius) { 56 | @if $enable-rounded { 57 | border-top-left-radius: valid-radius($radius); 58 | } 59 | } 60 | 61 | @mixin border-top-end-radius($radius: $border-radius) { 62 | @if $enable-rounded { 63 | border-top-right-radius: valid-radius($radius); 64 | } 65 | } 66 | 67 | @mixin border-bottom-end-radius($radius: $border-radius) { 68 | @if $enable-rounded { 69 | border-bottom-right-radius: valid-radius($radius); 70 | } 71 | } 72 | 73 | @mixin border-bottom-start-radius($radius: $border-radius) { 74 | @if $enable-rounded { 75 | border-bottom-left-radius: valid-radius($radius); 76 | } 77 | } 78 | // scss-docs-end border-radius-mixins 79 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_box-shadow.scss: -------------------------------------------------------------------------------- 1 | @mixin box-shadow($shadow...) { 2 | @if $enable-shadows { 3 | $result: (); 4 | 5 | @each $value in $shadow { 6 | @if $value != null { 7 | $result: append($result, $value, "comma"); 8 | } 9 | @if $value == none and length($shadow) > 1 { 10 | @warn "The keyword 'none' must be used as a single argument."; 11 | } 12 | } 13 | 14 | @if (length($result) > 0) { 15 | box-shadow: $result; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | // Breakpoint viewport sizes and media queries. 2 | // 3 | // Breakpoints are defined as a map of (name: minimum width), order from small to large: 4 | // 5 | // (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px) 6 | // 7 | // The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default. 8 | 9 | // Name of the next breakpoint, or null for the last breakpoint. 10 | // 11 | // >> breakpoint-next(sm) 12 | // md 13 | // >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)) 14 | // md 15 | // >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl xxl)) 16 | // md 17 | @function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) { 18 | $n: index($breakpoint-names, $name); 19 | @if not $n { 20 | @error "breakpoint `#{$name}` not found in `#{$breakpoints}`"; 21 | } 22 | @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null); 23 | } 24 | 25 | // Minimum breakpoint width. Null for the smallest (first) breakpoint. 26 | // 27 | // >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)) 28 | // 576px 29 | @function breakpoint-min($name, $breakpoints: $grid-breakpoints) { 30 | $min: map-get($breakpoints, $name); 31 | @return if($min != 0, $min, null); 32 | } 33 | 34 | // Maximum breakpoint width. 35 | // The maximum value is reduced by 0.02px to work around the limitations of 36 | // `min-` and `max-` prefixes and viewports with fractional widths. 37 | // See https://www.w3.org/TR/mediaqueries-4/#mq-min-max 38 | // Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari. 39 | // See https://bugs.webkit.org/show_bug.cgi?id=178261 40 | // 41 | // >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)) 42 | // 767.98px 43 | @function breakpoint-max($name, $breakpoints: $grid-breakpoints) { 44 | $max: map-get($breakpoints, $name); 45 | @return if($max and $max > 0, $max - .02, null); 46 | } 47 | 48 | // Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front. 49 | // Useful for making responsive utilities. 50 | // 51 | // >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)) 52 | // "" (Returns a blank string) 53 | // >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)) 54 | // "-sm" 55 | @function breakpoint-infix($name, $breakpoints: $grid-breakpoints) { 56 | @return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}"); 57 | } 58 | 59 | // Media of at least the minimum breakpoint width. No query for the smallest breakpoint. 60 | // Makes the @content apply to the given breakpoint and wider. 61 | @mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { 62 | $min: breakpoint-min($name, $breakpoints); 63 | @if $min { 64 | @media (min-width: $min) { 65 | @content; 66 | } 67 | } @else { 68 | @content; 69 | } 70 | } 71 | 72 | // Media of at most the maximum breakpoint width. No query for the largest breakpoint. 73 | // Makes the @content apply to the given breakpoint and narrower. 74 | @mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) { 75 | $max: breakpoint-max($name, $breakpoints); 76 | @if $max { 77 | @media (max-width: $max) { 78 | @content; 79 | } 80 | } @else { 81 | @content; 82 | } 83 | } 84 | 85 | // Media that spans multiple breakpoint widths. 86 | // Makes the @content apply between the min and max breakpoints 87 | @mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) { 88 | $min: breakpoint-min($lower, $breakpoints); 89 | $max: breakpoint-max($upper, $breakpoints); 90 | 91 | @if $min != null and $max != null { 92 | @media (min-width: $min) and (max-width: $max) { 93 | @content; 94 | } 95 | } @else if $max == null { 96 | @include media-breakpoint-up($lower, $breakpoints) { 97 | @content; 98 | } 99 | } @else if $min == null { 100 | @include media-breakpoint-down($upper, $breakpoints) { 101 | @content; 102 | } 103 | } 104 | } 105 | 106 | // Media between the breakpoint's minimum and maximum widths. 107 | // No minimum for the smallest breakpoint, and no maximum for the largest one. 108 | // Makes the @content apply only to the given breakpoint, not viewports any wider or narrower. 109 | @mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) { 110 | $min: breakpoint-min($name, $breakpoints); 111 | $next: breakpoint-next($name, $breakpoints); 112 | $max: breakpoint-max($next, $breakpoints); 113 | 114 | @if $min != null and $max != null { 115 | @media (min-width: $min) and (max-width: $max) { 116 | @content; 117 | } 118 | } @else if $max == null { 119 | @include media-breakpoint-up($name, $breakpoints) { 120 | @content; 121 | } 122 | } @else if $min == null { 123 | @include media-breakpoint-down($next, $breakpoints) { 124 | @content; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_buttons.scss: -------------------------------------------------------------------------------- 1 | // Button variants 2 | // 3 | // Easily pump out default styles, as well as :hover, :focus, :active, 4 | // and disabled options for all buttons 5 | 6 | // scss-docs-start btn-variant-mixin 7 | @mixin button-variant( 8 | $background, 9 | $border, 10 | $color: color-contrast($background), 11 | $hover-background: if($color == $color-contrast-light, shade-color($background, $btn-hover-bg-shade-amount), tint-color($background, $btn-hover-bg-tint-amount)), 12 | $hover-border: if($color == $color-contrast-light, shade-color($border, $btn-hover-border-shade-amount), tint-color($border, $btn-hover-border-tint-amount)), 13 | $hover-color: color-contrast($hover-background), 14 | $active-background: if($color == $color-contrast-light, shade-color($background, $btn-active-bg-shade-amount), tint-color($background, $btn-active-bg-tint-amount)), 15 | $active-border: if($color == $color-contrast-light, shade-color($border, $btn-active-border-shade-amount), tint-color($border, $btn-active-border-tint-amount)), 16 | $active-color: color-contrast($active-background), 17 | $disabled-background: $background, 18 | $disabled-border: $border, 19 | $disabled-color: color-contrast($disabled-background) 20 | ) { 21 | --#{$prefix}btn-color: #{$color}; 22 | --#{$prefix}btn-bg: #{$background}; 23 | --#{$prefix}btn-border-color: #{$border}; 24 | --#{$prefix}btn-hover-color: #{$hover-color}; 25 | --#{$prefix}btn-hover-bg: #{$hover-background}; 26 | --#{$prefix}btn-hover-border-color: #{$hover-border}; 27 | --#{$prefix}btn-focus-shadow-rgb: #{to-rgb(mix($color, $border, 15%))}; 28 | --#{$prefix}btn-active-color: #{$active-color}; 29 | --#{$prefix}btn-active-bg: #{$active-background}; 30 | --#{$prefix}btn-active-border-color: #{$active-border}; 31 | --#{$prefix}btn-active-shadow: #{$btn-active-box-shadow}; 32 | --#{$prefix}btn-disabled-color: #{$disabled-color}; 33 | --#{$prefix}btn-disabled-bg: #{$disabled-background}; 34 | --#{$prefix}btn-disabled-border-color: #{$disabled-border}; 35 | } 36 | // scss-docs-end btn-variant-mixin 37 | 38 | // scss-docs-start btn-outline-variant-mixin 39 | @mixin button-outline-variant( 40 | $color, 41 | $color-hover: color-contrast($color), 42 | $active-background: $color, 43 | $active-border: $color, 44 | $active-color: color-contrast($active-background) 45 | ) { 46 | --#{$prefix}btn-color: #{$color}; 47 | --#{$prefix}btn-border-color: #{$color}; 48 | --#{$prefix}btn-hover-color: #{$color-hover}; 49 | --#{$prefix}btn-hover-bg: #{$active-background}; 50 | --#{$prefix}btn-hover-border-color: #{$active-border}; 51 | --#{$prefix}btn-focus-shadow-rgb: #{to-rgb($color)}; 52 | --#{$prefix}btn-active-color: #{$active-color}; 53 | --#{$prefix}btn-active-bg: #{$active-background}; 54 | --#{$prefix}btn-active-border-color: #{$active-border}; 55 | --#{$prefix}btn-active-shadow: #{$btn-active-box-shadow}; 56 | --#{$prefix}btn-disabled-color: #{$color}; 57 | --#{$prefix}btn-disabled-bg: transparent; 58 | --#{$prefix}btn-disabled-border-color: #{$color}; 59 | --#{$prefix}gradient: none; 60 | } 61 | // scss-docs-end btn-outline-variant-mixin 62 | 63 | // scss-docs-start btn-size-mixin 64 | @mixin button-size($padding-y, $padding-x, $font-size, $border-radius) { 65 | --#{$prefix}btn-padding-y: #{$padding-y}; 66 | --#{$prefix}btn-padding-x: #{$padding-x}; 67 | @include rfs($font-size, --#{$prefix}btn-font-size); 68 | --#{$prefix}btn-border-radius: #{$border-radius}; 69 | } 70 | // scss-docs-end btn-size-mixin 71 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_caret.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start caret-mixins 2 | @mixin caret-down($width: $caret-width) { 3 | border-top: $width solid; 4 | border-right: $width solid transparent; 5 | border-bottom: 0; 6 | border-left: $width solid transparent; 7 | } 8 | 9 | @mixin caret-up($width: $caret-width) { 10 | border-top: 0; 11 | border-right: $width solid transparent; 12 | border-bottom: $width solid; 13 | border-left: $width solid transparent; 14 | } 15 | 16 | @mixin caret-end($width: $caret-width) { 17 | border-top: $width solid transparent; 18 | border-right: 0; 19 | border-bottom: $width solid transparent; 20 | border-left: $width solid; 21 | } 22 | 23 | @mixin caret-start($width: $caret-width) { 24 | border-top: $width solid transparent; 25 | border-right: $width solid; 26 | border-bottom: $width solid transparent; 27 | } 28 | 29 | @mixin caret( 30 | $direction: down, 31 | $width: $caret-width, 32 | $spacing: $caret-spacing, 33 | $vertical-align: $caret-vertical-align 34 | ) { 35 | @if $enable-caret { 36 | &::after { 37 | display: inline-block; 38 | margin-left: $spacing; 39 | vertical-align: $vertical-align; 40 | content: ""; 41 | @if $direction == down { 42 | @include caret-down($width); 43 | } @else if $direction == up { 44 | @include caret-up($width); 45 | } @else if $direction == end { 46 | @include caret-end($width); 47 | } 48 | } 49 | 50 | @if $direction == start { 51 | &::after { 52 | display: none; 53 | } 54 | 55 | &::before { 56 | display: inline-block; 57 | margin-right: $spacing; 58 | vertical-align: $vertical-align; 59 | content: ""; 60 | @include caret-start($width); 61 | } 62 | } 63 | 64 | &:empty::after { 65 | margin-left: 0; 66 | } 67 | } 68 | } 69 | // scss-docs-end caret-mixins 70 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_clearfix.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start clearfix 2 | @mixin clearfix() { 3 | &::after { 4 | display: block; 5 | clear: both; 6 | content: ""; 7 | } 8 | } 9 | // scss-docs-end clearfix 10 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_color-mode.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start color-mode-mixin 2 | @mixin color-mode($mode: light, $root: false) { 3 | @if $color-mode-type == "media-query" { 4 | @if $root == true { 5 | @media (prefers-color-scheme: $mode) { 6 | :root { 7 | @content; 8 | } 9 | } 10 | } @else { 11 | @media (prefers-color-scheme: $mode) { 12 | @content; 13 | } 14 | } 15 | } @else { 16 | [data-bs-theme="#{$mode}"] { 17 | @content; 18 | } 19 | } 20 | } 21 | // scss-docs-end color-mode-mixin 22 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_color-scheme.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start mixin-color-scheme 2 | @mixin color-scheme($name) { 3 | @media (prefers-color-scheme: #{$name}) { 4 | @content; 5 | } 6 | } 7 | // scss-docs-end mixin-color-scheme 8 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_container.scss: -------------------------------------------------------------------------------- 1 | // Container mixins 2 | 3 | @mixin make-container($gutter: $container-padding-x) { 4 | --#{$prefix}gutter-x: #{$gutter}; 5 | --#{$prefix}gutter-y: 0; 6 | width: 100%; 7 | padding-right: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list 8 | padding-left: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list 9 | margin-right: auto; 10 | margin-left: auto; 11 | } 12 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_deprecate.scss: -------------------------------------------------------------------------------- 1 | // Deprecate mixin 2 | // 3 | // This mixin can be used to deprecate mixins or functions. 4 | // `$enable-deprecation-messages` is a global variable, `$ignore-warning` is a variable that can be passed to 5 | // some deprecated mixins to suppress the warning (for example if the mixin is still be used in the current version of Bootstrap) 6 | @mixin deprecate($name, $deprecate-version, $remove-version, $ignore-warning: false) { 7 | @if ($enable-deprecation-messages != false and $ignore-warning != true) { 8 | @warn "#{$name} has been deprecated as of #{$deprecate-version}. It will be removed entirely in #{$remove-version}."; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_forms.scss: -------------------------------------------------------------------------------- 1 | // This mixin uses an `if()` technique to be compatible with Dart Sass 2 | // See https://github.com/sass/sass/issues/1873#issuecomment-152293725 for more details 3 | 4 | // scss-docs-start form-validation-mixins 5 | @mixin form-validation-state-selector($state) { 6 | @if ($state == "valid" or $state == "invalid") { 7 | .was-validated #{if(&, "&", "")}:#{$state}, 8 | #{if(&, "&", "")}.is-#{$state} { 9 | @content; 10 | } 11 | } @else { 12 | #{if(&, "&", "")}.is-#{$state} { 13 | @content; 14 | } 15 | } 16 | } 17 | 18 | @mixin form-validation-state( 19 | $state, 20 | $color, 21 | $icon, 22 | $tooltip-color: color-contrast($color), 23 | $tooltip-bg-color: rgba($color, $form-feedback-tooltip-opacity), 24 | $focus-box-shadow: 0 0 $input-btn-focus-blur $input-focus-width rgba($color, $input-btn-focus-color-opacity), 25 | $border-color: $color 26 | ) { 27 | .#{$state}-feedback { 28 | display: none; 29 | width: 100%; 30 | margin-top: $form-feedback-margin-top; 31 | @include font-size($form-feedback-font-size); 32 | font-style: $form-feedback-font-style; 33 | color: $color; 34 | } 35 | 36 | .#{$state}-tooltip { 37 | position: absolute; 38 | top: 100%; 39 | z-index: 5; 40 | display: none; 41 | max-width: 100%; // Contain to parent when possible 42 | padding: $form-feedback-tooltip-padding-y $form-feedback-tooltip-padding-x; 43 | margin-top: .1rem; 44 | @include font-size($form-feedback-tooltip-font-size); 45 | line-height: $form-feedback-tooltip-line-height; 46 | color: $tooltip-color; 47 | background-color: $tooltip-bg-color; 48 | @include border-radius($form-feedback-tooltip-border-radius); 49 | } 50 | 51 | @include form-validation-state-selector($state) { 52 | ~ .#{$state}-feedback, 53 | ~ .#{$state}-tooltip { 54 | display: block; 55 | } 56 | } 57 | 58 | .form-control { 59 | @include form-validation-state-selector($state) { 60 | border-color: $border-color; 61 | 62 | @if $enable-validation-icons { 63 | padding-right: $input-height-inner; 64 | background-image: escape-svg($icon); 65 | background-repeat: no-repeat; 66 | background-position: right $input-height-inner-quarter center; 67 | background-size: $input-height-inner-half $input-height-inner-half; 68 | } 69 | 70 | &:focus { 71 | border-color: $border-color; 72 | @if $enable-shadows { 73 | @include box-shadow($input-box-shadow, $focus-box-shadow); 74 | } @else { 75 | // Avoid using mixin so we can pass custom focus shadow properly 76 | box-shadow: $focus-box-shadow; 77 | } 78 | } 79 | } 80 | } 81 | 82 | // stylelint-disable-next-line selector-no-qualifying-type 83 | textarea.form-control { 84 | @include form-validation-state-selector($state) { 85 | @if $enable-validation-icons { 86 | padding-right: $input-height-inner; 87 | background-position: top $input-height-inner-quarter right $input-height-inner-quarter; 88 | } 89 | } 90 | } 91 | 92 | .form-select { 93 | @include form-validation-state-selector($state) { 94 | border-color: $border-color; 95 | 96 | @if $enable-validation-icons { 97 | &:not([multiple]):not([size]), 98 | &:not([multiple])[size="1"] { 99 | --#{$prefix}form-select-bg-icon: #{escape-svg($icon)}; 100 | padding-right: $form-select-feedback-icon-padding-end; 101 | background-position: $form-select-bg-position, $form-select-feedback-icon-position; 102 | background-size: $form-select-bg-size, $form-select-feedback-icon-size; 103 | } 104 | } 105 | 106 | &:focus { 107 | border-color: $border-color; 108 | @if $enable-shadows { 109 | @include box-shadow($form-select-box-shadow, $focus-box-shadow); 110 | } @else { 111 | // Avoid using mixin so we can pass custom focus shadow properly 112 | box-shadow: $focus-box-shadow; 113 | } 114 | } 115 | } 116 | } 117 | 118 | .form-control-color { 119 | @include form-validation-state-selector($state) { 120 | @if $enable-validation-icons { 121 | width: add($form-color-width, $input-height-inner); 122 | } 123 | } 124 | } 125 | 126 | .form-check-input { 127 | @include form-validation-state-selector($state) { 128 | border-color: $border-color; 129 | 130 | &:checked { 131 | background-color: $color; 132 | } 133 | 134 | &:focus { 135 | box-shadow: $focus-box-shadow; 136 | } 137 | 138 | ~ .form-check-label { 139 | color: $color; 140 | } 141 | } 142 | } 143 | .form-check-inline .form-check-input { 144 | ~ .#{$state}-feedback { 145 | margin-left: .5em; 146 | } 147 | } 148 | 149 | .input-group { 150 | > .form-control:not(:focus), 151 | > .form-select:not(:focus), 152 | > .form-floating:not(:focus-within) { 153 | @include form-validation-state-selector($state) { 154 | @if $state == "valid" { 155 | z-index: 3; 156 | } @else if $state == "invalid" { 157 | z-index: 4; 158 | } 159 | } 160 | } 161 | } 162 | } 163 | // scss-docs-end form-validation-mixins 164 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_gradients.scss: -------------------------------------------------------------------------------- 1 | // Gradients 2 | 3 | // scss-docs-start gradient-bg-mixin 4 | @mixin gradient-bg($color: null) { 5 | background-color: $color; 6 | 7 | @if $enable-gradients { 8 | background-image: var(--#{$prefix}gradient); 9 | } 10 | } 11 | // scss-docs-end gradient-bg-mixin 12 | 13 | // scss-docs-start gradient-mixins 14 | // Horizontal gradient, from left to right 15 | // 16 | // Creates two color stops, start and end, by specifying a color and position for each color stop. 17 | @mixin gradient-x($start-color: $gray-700, $end-color: $gray-800, $start-percent: 0%, $end-percent: 100%) { 18 | background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent); 19 | } 20 | 21 | // Vertical gradient, from top to bottom 22 | // 23 | // Creates two color stops, start and end, by specifying a color and position for each color stop. 24 | @mixin gradient-y($start-color: $gray-700, $end-color: $gray-800, $start-percent: null, $end-percent: null) { 25 | background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent); 26 | } 27 | 28 | @mixin gradient-directional($start-color: $gray-700, $end-color: $gray-800, $deg: 45deg) { 29 | background-image: linear-gradient($deg, $start-color, $end-color); 30 | } 31 | 32 | @mixin gradient-x-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) { 33 | background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color); 34 | } 35 | 36 | @mixin gradient-y-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) { 37 | background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color); 38 | } 39 | 40 | @mixin gradient-radial($inner-color: $gray-700, $outer-color: $gray-800) { 41 | background-image: radial-gradient(circle, $inner-color, $outer-color); 42 | } 43 | 44 | @mixin gradient-striped($color: rgba($white, .15), $angle: 45deg) { 45 | background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent); 46 | } 47 | // scss-docs-end gradient-mixins 48 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_grid.scss: -------------------------------------------------------------------------------- 1 | // Grid system 2 | // 3 | // Generate semantic grid columns with these mixins. 4 | 5 | @mixin make-row($gutter: $grid-gutter-width) { 6 | --#{$prefix}gutter-x: #{$gutter}; 7 | --#{$prefix}gutter-y: 0; 8 | display: flex; 9 | flex-wrap: wrap; 10 | // TODO: Revisit calc order after https://github.com/react-bootstrap/react-bootstrap/issues/6039 is fixed 11 | margin-top: calc(-1 * var(--#{$prefix}gutter-y)); // stylelint-disable-line function-disallowed-list 12 | margin-right: calc(-.5 * var(--#{$prefix}gutter-x)); // stylelint-disable-line function-disallowed-list 13 | margin-left: calc(-.5 * var(--#{$prefix}gutter-x)); // stylelint-disable-line function-disallowed-list 14 | } 15 | 16 | @mixin make-col-ready() { 17 | // Add box sizing if only the grid is loaded 18 | box-sizing: if(variable-exists(include-column-box-sizing) and $include-column-box-sizing, border-box, null); 19 | // Prevent columns from becoming too narrow when at smaller grid tiers by 20 | // always setting `width: 100%;`. This works because we set the width 21 | // later on to override this initial width. 22 | flex-shrink: 0; 23 | width: 100%; 24 | max-width: 100%; // Prevent `.col-auto`, `.col` (& responsive variants) from breaking out the grid 25 | padding-right: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list 26 | padding-left: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list 27 | margin-top: var(--#{$prefix}gutter-y); 28 | } 29 | 30 | @mixin make-col($size: false, $columns: $grid-columns) { 31 | @if $size { 32 | flex: 0 0 auto; 33 | width: percentage(divide($size, $columns)); 34 | 35 | } @else { 36 | flex: 1 1 0; 37 | max-width: 100%; 38 | } 39 | } 40 | 41 | @mixin make-col-auto() { 42 | flex: 0 0 auto; 43 | width: auto; 44 | } 45 | 46 | @mixin make-col-offset($size, $columns: $grid-columns) { 47 | $num: divide($size, $columns); 48 | margin-left: if($num == 0, 0, percentage($num)); 49 | } 50 | 51 | // Row columns 52 | // 53 | // Specify on a parent element(e.g., .row) to force immediate children into NN 54 | // number of columns. Supports wrapping to new lines, but does not do a Masonry 55 | // style grid. 56 | @mixin row-cols($count) { 57 | > * { 58 | flex: 0 0 auto; 59 | width: percentage(divide(1, $count)); 60 | } 61 | } 62 | 63 | // Framework grid generation 64 | // 65 | // Used only by Bootstrap to generate the correct number of grid classes given 66 | // any value of `$grid-columns`. 67 | 68 | @mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) { 69 | @each $breakpoint in map-keys($breakpoints) { 70 | $infix: breakpoint-infix($breakpoint, $breakpoints); 71 | 72 | @include media-breakpoint-up($breakpoint, $breakpoints) { 73 | // Provide basic `.col-{bp}` classes for equal-width flexbox columns 74 | .col#{$infix} { 75 | flex: 1 0 0; 76 | } 77 | 78 | .row-cols#{$infix}-auto > * { 79 | @include make-col-auto(); 80 | } 81 | 82 | @if $grid-row-columns > 0 { 83 | @for $i from 1 through $grid-row-columns { 84 | .row-cols#{$infix}-#{$i} { 85 | @include row-cols($i); 86 | } 87 | } 88 | } 89 | 90 | .col#{$infix}-auto { 91 | @include make-col-auto(); 92 | } 93 | 94 | @if $columns > 0 { 95 | @for $i from 1 through $columns { 96 | .col#{$infix}-#{$i} { 97 | @include make-col($i, $columns); 98 | } 99 | } 100 | 101 | // `$columns - 1` because offsetting by the width of an entire row isn't possible 102 | @for $i from 0 through ($columns - 1) { 103 | @if not ($infix == "" and $i == 0) { // Avoid emitting useless .offset-0 104 | .offset#{$infix}-#{$i} { 105 | @include make-col-offset($i, $columns); 106 | } 107 | } 108 | } 109 | } 110 | 111 | // Gutters 112 | // 113 | // Make use of `.g-*`, `.gx-*` or `.gy-*` utilities to change spacing between the columns. 114 | @each $key, $value in $gutters { 115 | .g#{$infix}-#{$key}, 116 | .gx#{$infix}-#{$key} { 117 | --#{$prefix}gutter-x: #{$value}; 118 | } 119 | 120 | .g#{$infix}-#{$key}, 121 | .gy#{$infix}-#{$key} { 122 | --#{$prefix}gutter-y: #{$value}; 123 | } 124 | } 125 | } 126 | } 127 | } 128 | 129 | @mixin make-cssgrid($columns: $grid-columns, $breakpoints: $grid-breakpoints) { 130 | @each $breakpoint in map-keys($breakpoints) { 131 | $infix: breakpoint-infix($breakpoint, $breakpoints); 132 | 133 | @include media-breakpoint-up($breakpoint, $breakpoints) { 134 | @if $columns > 0 { 135 | @for $i from 1 through $columns { 136 | .g-col#{$infix}-#{$i} { 137 | grid-column: auto / span $i; 138 | } 139 | } 140 | 141 | // Start with `1` because `0` is an invalid value. 142 | // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible. 143 | @for $i from 1 through ($columns - 1) { 144 | .g-start#{$infix}-#{$i} { 145 | grid-column-start: $i; 146 | } 147 | } 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_image.scss: -------------------------------------------------------------------------------- 1 | // Image Mixins 2 | // - Responsive image 3 | // - Retina image 4 | 5 | 6 | // Responsive image 7 | // 8 | // Keep images from scaling beyond the width of their parents. 9 | 10 | @mixin img-fluid { 11 | // Part 1: Set a maximum relative to the parent 12 | max-width: 100%; 13 | // Part 2: Override the height to auto, otherwise images will be stretched 14 | // when setting a width and height attribute on the img element. 15 | height: auto; 16 | } 17 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_list-group.scss: -------------------------------------------------------------------------------- 1 | @include deprecate("`list-group-item-variant()`", "v5.3.0", "v6.0.0"); 2 | 3 | // List Groups 4 | 5 | // scss-docs-start list-group-mixin 6 | @mixin list-group-item-variant($state, $background, $color) { 7 | .list-group-item-#{$state} { 8 | color: $color; 9 | background-color: $background; 10 | 11 | &.list-group-item-action { 12 | &:hover, 13 | &:focus { 14 | color: $color; 15 | background-color: shade-color($background, 10%); 16 | } 17 | 18 | &.active { 19 | color: $white; 20 | background-color: $color; 21 | border-color: $color; 22 | } 23 | } 24 | } 25 | } 26 | // scss-docs-end list-group-mixin 27 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_lists.scss: -------------------------------------------------------------------------------- 1 | // Lists 2 | 3 | // Unstyled keeps list items block level, just removes default browser padding and list-style 4 | @mixin list-unstyled { 5 | padding-left: 0; 6 | list-style: none; 7 | } 8 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_pagination.scss: -------------------------------------------------------------------------------- 1 | // Pagination 2 | 3 | // scss-docs-start pagination-mixin 4 | @mixin pagination-size($padding-y, $padding-x, $font-size, $border-radius) { 5 | --#{$prefix}pagination-padding-x: #{$padding-x}; 6 | --#{$prefix}pagination-padding-y: #{$padding-y}; 7 | @include rfs($font-size, --#{$prefix}pagination-font-size); 8 | --#{$prefix}pagination-border-radius: #{$border-radius}; 9 | } 10 | // scss-docs-end pagination-mixin 11 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_reset-text.scss: -------------------------------------------------------------------------------- 1 | @mixin reset-text { 2 | font-family: $font-family-base; 3 | // We deliberately do NOT reset font-size or overflow-wrap / word-wrap. 4 | font-style: normal; 5 | font-weight: $font-weight-normal; 6 | line-height: $line-height-base; 7 | text-align: left; // Fallback for where `start` is not supported 8 | text-align: start; 9 | text-decoration: none; 10 | text-shadow: none; 11 | text-transform: none; 12 | letter-spacing: normal; 13 | word-break: normal; 14 | white-space: normal; 15 | word-spacing: normal; 16 | line-break: auto; 17 | } 18 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_resize.scss: -------------------------------------------------------------------------------- 1 | // Resize anything 2 | 3 | @mixin resizable($direction) { 4 | overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` 5 | resize: $direction; // Options: horizontal, vertical, both 6 | } 7 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_table-variants.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start table-variant 2 | @mixin table-variant($state, $background) { 3 | .table-#{$state} { 4 | $color: color-contrast(opaque($body-bg, $background)); 5 | $hover-bg: mix($color, $background, percentage($table-hover-bg-factor)); 6 | $striped-bg: mix($color, $background, percentage($table-striped-bg-factor)); 7 | $active-bg: mix($color, $background, percentage($table-active-bg-factor)); 8 | $table-border-color: mix($color, $background, percentage($table-border-factor)); 9 | 10 | --#{$prefix}table-color: #{$color}; 11 | --#{$prefix}table-bg: #{$background}; 12 | --#{$prefix}table-border-color: #{$table-border-color}; 13 | --#{$prefix}table-striped-bg: #{$striped-bg}; 14 | --#{$prefix}table-striped-color: #{color-contrast($striped-bg)}; 15 | --#{$prefix}table-active-bg: #{$active-bg}; 16 | --#{$prefix}table-active-color: #{color-contrast($active-bg)}; 17 | --#{$prefix}table-hover-bg: #{$hover-bg}; 18 | --#{$prefix}table-hover-color: #{color-contrast($hover-bg)}; 19 | 20 | color: var(--#{$prefix}table-color); 21 | border-color: var(--#{$prefix}table-border-color); 22 | } 23 | } 24 | // scss-docs-end table-variant 25 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_text-truncate.scss: -------------------------------------------------------------------------------- 1 | // Text truncate 2 | // Requires inline-block or block for proper styling 3 | 4 | @mixin text-truncate() { 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | } 9 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_transition.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable property-disallowed-list 2 | @mixin transition($transition...) { 3 | @if length($transition) == 0 { 4 | $transition: $transition-base; 5 | } 6 | 7 | @if length($transition) > 1 { 8 | @each $value in $transition { 9 | @if $value == null or $value == none { 10 | @warn "The keyword 'none' or 'null' must be used as a single argument."; 11 | } 12 | } 13 | } 14 | 15 | @if $enable-transitions { 16 | @if nth($transition, 1) != null { 17 | transition: $transition; 18 | } 19 | 20 | @if $enable-reduced-motion and nth($transition, 1) != null and nth($transition, 1) != none { 21 | @media (prefers-reduced-motion: reduce) { 22 | transition: none; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_utilities.scss: -------------------------------------------------------------------------------- 1 | // Utility generator 2 | // Used to generate utilities & print utilities 3 | @mixin generate-utility($utility, $infix: "", $is-rfs-media-query: false) { 4 | $values: map-get($utility, values); 5 | 6 | // If the values are a list or string, convert it into a map 7 | @if type-of($values) == "string" or type-of(nth($values, 1)) != "list" { 8 | $values: zip($values, $values); 9 | } 10 | 11 | @each $key, $value in $values { 12 | $properties: map-get($utility, property); 13 | 14 | // Multiple properties are possible, for example with vertical or horizontal margins or paddings 15 | @if type-of($properties) == "string" { 16 | $properties: append((), $properties); 17 | } 18 | 19 | // Use custom class if present 20 | $property-class: if(map-has-key($utility, class), map-get($utility, class), nth($properties, 1)); 21 | $property-class: if($property-class == null, "", $property-class); 22 | 23 | // Use custom CSS variable name if present, otherwise default to `class` 24 | $css-variable-name: if(map-has-key($utility, css-variable-name), map-get($utility, css-variable-name), map-get($utility, class)); 25 | 26 | // State params to generate pseudo-classes 27 | $state: if(map-has-key($utility, state), map-get($utility, state), ()); 28 | 29 | $infix: if($property-class == "" and str-slice($infix, 1, 1) == "-", str-slice($infix, 2), $infix); 30 | 31 | // Don't prefix if value key is null (e.g. with shadow class) 32 | $property-class-modifier: if($key, if($property-class == "" and $infix == "", "", "-") + $key, ""); 33 | 34 | @if map-get($utility, rfs) { 35 | // Inside the media query 36 | @if $is-rfs-media-query { 37 | $val: rfs-value($value); 38 | 39 | // Do not render anything if fluid and non fluid values are the same 40 | $value: if($val == rfs-fluid-value($value), null, $val); 41 | } 42 | @else { 43 | $value: rfs-fluid-value($value); 44 | } 45 | } 46 | 47 | $is-css-var: map-get($utility, css-var); 48 | $is-local-vars: map-get($utility, local-vars); 49 | $is-rtl: map-get($utility, rtl); 50 | 51 | @if $value != null { 52 | @if $is-rtl == false { 53 | /* rtl:begin:remove */ 54 | } 55 | 56 | @if $is-css-var { 57 | .#{$property-class + $infix + $property-class-modifier} { 58 | --#{$prefix}#{$css-variable-name}: #{$value}; 59 | } 60 | 61 | @each $pseudo in $state { 62 | .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} { 63 | --#{$prefix}#{$css-variable-name}: #{$value}; 64 | } 65 | } 66 | } @else { 67 | .#{$property-class + $infix + $property-class-modifier} { 68 | @each $property in $properties { 69 | @if $is-local-vars { 70 | @each $local-var, $variable in $is-local-vars { 71 | --#{$prefix}#{$local-var}: #{$variable}; 72 | } 73 | } 74 | #{$property}: $value if($enable-important-utilities, !important, null); 75 | } 76 | } 77 | 78 | @each $pseudo in $state { 79 | .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} { 80 | @each $property in $properties { 81 | @if $is-local-vars { 82 | @each $local-var, $variable in $is-local-vars { 83 | --#{$prefix}#{$local-var}: #{$variable}; 84 | } 85 | } 86 | #{$property}: $value if($enable-important-utilities, !important, null); 87 | } 88 | } 89 | } 90 | } 91 | 92 | @if $is-rtl == false { 93 | /* rtl:end:remove */ 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/mixins/_visually-hidden.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable declaration-no-important 2 | 3 | // Hide content visually while keeping it accessible to assistive technologies 4 | // 5 | // See: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/ 6 | // See: https://kittygiraudel.com/2016/10/13/css-hide-and-seek/ 7 | 8 | @mixin visually-hidden() { 9 | width: 1px !important; 10 | height: 1px !important; 11 | padding: 0 !important; 12 | margin: -1px !important; // Fix for https://github.com/twbs/bootstrap/issues/25686 13 | overflow: hidden !important; 14 | clip: rect(0, 0, 0, 0) !important; 15 | white-space: nowrap !important; 16 | border: 0 !important; 17 | 18 | // Fix for positioned table caption that could become anonymous cells 19 | &:not(caption) { 20 | position: absolute !important; 21 | } 22 | } 23 | 24 | // Use to only display content when it's focused, or one of its child elements is focused 25 | // (i.e. when focus is within the element/container that the class was applied to) 26 | // 27 | // Useful for "Skip to main content" links; see https://www.w3.org/WAI/WCAG22/Techniques/general/G1.html 28 | 29 | @mixin visually-hidden-focusable() { 30 | &:not(:focus):not(:focus-within) { 31 | @include visually-hidden(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets/stylesheets/bootstrap/utilities/_api.scss: -------------------------------------------------------------------------------- 1 | // Loop over each breakpoint 2 | @each $breakpoint in map-keys($grid-breakpoints) { 3 | 4 | // Generate media query if needed 5 | @include media-breakpoint-up($breakpoint) { 6 | $infix: breakpoint-infix($breakpoint, $grid-breakpoints); 7 | 8 | // Loop over each utility property 9 | @each $key, $utility in $utilities { 10 | // The utility can be disabled with `false`, thus check if the utility is a map first 11 | // Only proceed if responsive media queries are enabled or if it's the base media query 12 | @if type-of($utility) == "map" and (map-get($utility, responsive) or $infix == "") { 13 | @include generate-utility($utility, $infix); 14 | } 15 | } 16 | } 17 | } 18 | 19 | // RFS rescaling 20 | @media (min-width: $rfs-mq-value) { 21 | @each $breakpoint in map-keys($grid-breakpoints) { 22 | $infix: breakpoint-infix($breakpoint, $grid-breakpoints); 23 | 24 | @if (map-get($grid-breakpoints, $breakpoint) < $rfs-breakpoint) { 25 | // Loop over each utility property 26 | @each $key, $utility in $utilities { 27 | // The utility can be disabled with `false`, thus check if the utility is a map first 28 | // Only proceed if responsive media queries are enabled or if it's the base media query 29 | @if type-of($utility) == "map" and map-get($utility, rfs) and (map-get($utility, responsive) or $infix == "") { 30 | @include generate-utility($utility, $infix, true); 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | 38 | // Print utilities 39 | @media print { 40 | @each $key, $utility in $utilities { 41 | // The utility can be disabled with `false`, thus check if the utility is a map first 42 | // Then check if the utility needs print styles 43 | @if type-of($utility) == "map" and map-get($utility, print) == true { 44 | @include generate-utility($utility, "-print"); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bootstrap.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'bootstrap/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'bootstrap' 7 | s.version = Bootstrap::VERSION 8 | s.authors = ['Twitter, Inc.'] 9 | s.email = 'glex.spb@gmail.com' 10 | s.summary = 'The most popular HTML, CSS, and JavaScript framework for developing responsive, mobile first projects on the web. http://getbootstrap.com' 11 | s.homepage = 'https://github.com/twbs/bootstrap-rubygem' 12 | s.license = 'MIT' 13 | 14 | # SassC requires Ruby 2.3.3. Also specify here to make it obvious. 15 | s.required_ruby_version = '>= 2.3.3' 16 | 17 | s.add_runtime_dependency 'popper_js', '>= 2.11.8', '< 3' 18 | 19 | s.add_development_dependency 'rake' 20 | 21 | # Testing dependencies 22 | s.add_development_dependency 'minitest', '~> 5.14.4' 23 | s.add_development_dependency 'minitest-reporters', '~> 1.4.3' 24 | s.add_development_dependency 'term-ansicolor' 25 | # Integration testing 26 | s.add_development_dependency 'capybara', '>= 2.6.0' 27 | s.add_development_dependency 'cuprite' 28 | # Dummy Rails app dependencies 29 | s.add_development_dependency 'railties' 30 | s.add_development_dependency 'actionpack', '>= 4.1.5' 31 | s.add_development_dependency 'activesupport', '>= 4.1.5' 32 | s.add_development_dependency 'json', '>= 1.8.1' 33 | s.add_development_dependency 'sprockets-rails', '>= 2.3.2' 34 | s.add_development_dependency 'uglifier' 35 | 36 | s.files = `git ls-files`.split("\n") 37 | s.test_files = `git ls-files -- test/*`.split("\n") 38 | end 39 | -------------------------------------------------------------------------------- /lib/bootstrap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bootstrap/version' 4 | require 'popper_js' 5 | 6 | module Bootstrap 7 | class << self 8 | # Inspired by Kaminari 9 | def load! 10 | if rails? 11 | register_rails_engine 12 | elsif hanami? 13 | register_hanami 14 | elsif sprockets? 15 | register_sprockets 16 | elsif defined?(::Sass) && ::Sass.respond_to?(:load_paths) 17 | # The deprecated `sass` gem: 18 | ::Sass.load_paths << stylesheets_path 19 | end 20 | 21 | if defined?(::Sass::Script::Value::Number) 22 | # Set precision to 6 as per: 23 | # https://github.com/twbs/bootstrap/blob/da717b03e6e72d7a61c007acb9223b9626ae5ee5/package.json#L28 24 | ::Sass::Script::Value::Number.precision = [6, ::Sass::Script::Value::Number.precision].max 25 | end 26 | end 27 | 28 | # Paths 29 | def gem_path 30 | @gem_path ||= File.expand_path '..', File.dirname(__FILE__) 31 | end 32 | 33 | def stylesheets_path 34 | File.join assets_path, 'stylesheets' 35 | end 36 | 37 | def javascripts_path 38 | File.join assets_path, 'javascripts' 39 | end 40 | 41 | def assets_path 42 | @assets_path ||= File.join gem_path, 'assets' 43 | end 44 | 45 | # Environment detection helpers 46 | def sprockets? 47 | defined?(::Sprockets) 48 | end 49 | 50 | def rails? 51 | defined?(::Rails) 52 | end 53 | 54 | def hanami? 55 | defined?(::Hanami) 56 | end 57 | 58 | private 59 | 60 | def register_rails_engine 61 | require 'bootstrap/engine' 62 | end 63 | 64 | def register_sprockets 65 | Sprockets.append_path(stylesheets_path) 66 | Sprockets.append_path(javascripts_path) 67 | end 68 | 69 | def register_hanami 70 | Hanami::Assets.sources << assets_path 71 | end 72 | end 73 | end 74 | 75 | Bootstrap.load! 76 | -------------------------------------------------------------------------------- /lib/bootstrap/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'dartsass-sprockets' 5 | rescue LoadError 6 | begin 7 | require 'sassc-rails' 8 | rescue LoadError 9 | begin 10 | require 'dartsass-rails' 11 | rescue LoadError 12 | begin 13 | require 'cssbundling-rails' 14 | rescue LoadError 15 | raise LoadError.new("bootstrap-rubygem requires a Sass engine. Please add dartsass-sprockets, sassc-rails, dartsass-rails or cssbundling-rails to your dependencies.") 16 | end 17 | end 18 | end 19 | end 20 | 21 | module Bootstrap 22 | module Rails 23 | class Engine < ::Rails::Engine 24 | initializer 'bootstrap.assets' do |app| 25 | %w(stylesheets javascripts).each do |sub| 26 | app.config.assets.paths << root.join('assets', sub).to_s 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/bootstrap/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Bootstrap 4 | VERSION = '5.3.5' 5 | BOOTSTRAP_SHA = '85f23534bd2de8041354b297516cf21959091b31' 6 | end 7 | -------------------------------------------------------------------------------- /tasks/updater.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'open-uri' 4 | require 'json' 5 | require 'strscan' 6 | require 'forwardable' 7 | require 'term/ansicolor' 8 | require 'fileutils' 9 | 10 | require_relative 'updater/scss' 11 | require_relative 'updater/js' 12 | require_relative 'updater/logger' 13 | require_relative 'updater/network' 14 | 15 | class Updater 16 | extend Forwardable 17 | include Network 18 | include Js 19 | include Scss 20 | 21 | def initialize(repo: 'twbs/bootstrap', branch: 'main', save_to: {}, cache_path: 'tmp/bootstrap-cache') 22 | @logger = Logger.new 23 | @repo = repo 24 | @branch = branch || 'main' 25 | @branch_sha = get_branch_sha 26 | @cache_path = cache_path 27 | @repo_url = "https://github.com/#@repo" 28 | @save_to = { 29 | js: 'assets/javascripts/bootstrap', 30 | scss: 'assets/stylesheets/bootstrap'}.merge(save_to) 31 | end 32 | 33 | def_delegators :@logger, :log, :log_status, :log_processing, :log_transform, :log_file_info, :log_processed, :log_http_get_file, :log_http_get_files, :silence_log 34 | 35 | def update_bootstrap 36 | log_status 'Updating Bootstrap' 37 | puts " repo : #@repo_url" 38 | puts " branch : #@branch_sha #@repo_url/tree/#@branch" 39 | puts " save to: #{@save_to.to_json}" 40 | puts " twbs cache: #{@cache_path}" 41 | puts '-' * 60 42 | 43 | FileUtils.rm_rf('assets') 44 | @save_to.each { |_, v| FileUtils.mkdir_p(v) } 45 | 46 | update_scss_assets 47 | update_javascript_assets 48 | store_version 49 | end 50 | 51 | def save_file(path, content, mode='w') 52 | dir = File.dirname(path) 53 | FileUtils.mkdir_p(dir) unless File.directory?(dir) 54 | File.open(path, mode) { |file| file.write(content) } 55 | end 56 | 57 | def upstream_version 58 | @upstream_version ||= get_json(file_url 'package.json')['version'] 59 | end 60 | 61 | # Update version.rb file with BOOTSTRAP_SHA 62 | def store_version 63 | path = 'lib/bootstrap/version.rb' 64 | content = File.read(path).sub(/BOOTSTRAP_SHA\s*=\s*['"][^'"]*['"]/, "BOOTSTRAP_SHA = '#@branch_sha'") 65 | File.open(path, 'w') { |f| f.write(content) } 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /tasks/updater/js.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'tsort' 3 | 4 | class Updater 5 | module Js 6 | INLINED_SRCS = %w[].freeze 7 | 8 | def update_javascript_assets 9 | log_status 'Updating javascripts...' 10 | save_to = @save_to[:js] 11 | read_files('js/dist', bootstrap_js_files).each do |name, content| 12 | save_file("#{save_to}/#{name}", remove_source_mapping_url(content)) 13 | end 14 | log_processed "#{bootstrap_js_files * ' '}" 15 | 16 | log_status 'Updating javascript manifest' 17 | manifest = "//= require ./bootstrap-global-this-define\n" 18 | bootstrap_js_files.each do |name| 19 | name = name.gsub(/\.js$/, '') 20 | manifest << "//= require ./bootstrap/#{name}\n" 21 | end 22 | manifest << "//= require ./bootstrap-global-this-undefine\n" 23 | dist_js = read_files('dist/js', %w(bootstrap.js bootstrap.min.js)) 24 | { 25 | 'assets/javascripts/bootstrap-global-this-define.js' => <<~JS, 26 | // Set a `globalThis` so that bootstrap components are defined on window.bootstrap instead of window. 27 | window['bootstrap'] = { 28 | "@popperjs/core": window.Popper, 29 | _originalGlobalThis: window['globalThis'] 30 | }; 31 | window['globalThis'] = window['bootstrap']; 32 | JS 33 | 'assets/javascripts/bootstrap-global-this-undefine.js' => <<~JS, 34 | window['globalThis'] = window['bootstrap']._originalGlobalThis; 35 | window['bootstrap']._originalGlobalThis = null; 36 | JS 37 | 'assets/javascripts/bootstrap-sprockets.js' => manifest, 38 | 'assets/javascripts/bootstrap.js' => dist_js['bootstrap.js'], 39 | 'assets/javascripts/bootstrap.min.js' => dist_js['bootstrap.min.js'], 40 | }.each do |path, content| 41 | save_file path, remove_source_mapping_url(content) 42 | log_processed path 43 | end 44 | end 45 | 46 | def bootstrap_js_files 47 | @bootstrap_js_files ||= begin 48 | src_files = get_paths_by_type('js/src', /\.js$/) - INLINED_SRCS 49 | puts "src_files: #{src_files.inspect}" 50 | imports = Deps.new 51 | # Get the imports from the ES6 files to order requires correctly. 52 | read_files('js/src', src_files).each do |name, content| 53 | file_imports = content.scan(%r{import *(?:[a-zA-Z]*|\{[a-zA-Z ,]*\}) *from '([\w/.-]+)}).flatten(1).map do |f| 54 | Pathname.new(name).dirname.join(f).cleanpath.to_s 55 | end.uniq 56 | imports.add name, *(file_imports - INLINED_SRCS) 57 | end 58 | imports.tsort 59 | end 60 | end 61 | 62 | def remove_source_mapping_url(content) 63 | content.sub(%r{^//# sourceMappingURL=.*\n?\z}, '') 64 | end 65 | 66 | class Deps 67 | include TSort 68 | 69 | def initialize 70 | @imports = {} 71 | end 72 | 73 | def add(from, *tos) 74 | imports = (@imports[from] ||= []) 75 | imports.push(*tos) 76 | imports.sort! 77 | end 78 | 79 | def tsort_each_child(node, &block) 80 | node_imports = @imports[node] 81 | if node_imports.nil? 82 | raise "No imports found for #{node.inspect}\nImports:\n#{@imports.inspect}" 83 | end 84 | node_imports.each(&block) 85 | end 86 | 87 | def tsort_each_node(&block) 88 | @imports.each_key(&block) 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /tasks/updater/logger.rb: -------------------------------------------------------------------------------- 1 | class Updater 2 | class Logger 3 | include Term::ANSIColor 4 | 5 | def log_status(status) 6 | puts bold status 7 | end 8 | 9 | def log_file_info(s) 10 | puts " #{magenta s}" 11 | end 12 | 13 | def log_transform(*args, from: caller[1][/`.*'/][1..-2].sub(/^block in /, '')) 14 | puts " #{cyan from}#{cyan ": #{args * ', '}" unless args.empty?}" 15 | end 16 | 17 | def log_processing(name) 18 | puts yellow " #{File.basename(name)}" 19 | end 20 | 21 | def log_processed(name) 22 | puts green " #{name}" 23 | end 24 | 25 | def log_http_get_file(url, cached = false) 26 | s = " #{'CACHED ' if cached}GET #{url}..." 27 | if cached 28 | puts dark green s 29 | else 30 | puts dark cyan s 31 | end 32 | end 33 | 34 | def log_http_get_files(files, from, cached = false) 35 | return if files.empty? 36 | s = " #{'CACHED ' if cached}GET #{files.length} files from #{from} #{files * ' '}..." 37 | if cached 38 | puts dark green s 39 | else 40 | puts dark cyan s 41 | end 42 | end 43 | 44 | def puts(*args) 45 | STDERR.puts *args unless @silence 46 | end 47 | 48 | alias log puts 49 | 50 | def silence_log 51 | @silence = true 52 | yield 53 | ensure 54 | @silence = false 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /tasks/updater/network.rb: -------------------------------------------------------------------------------- 1 | require 'shellwords' 2 | class Updater 3 | module Network 4 | protected 5 | 6 | WRITE_FILES_MUTEX = Mutex.new 7 | 8 | def get_paths_by_type(dir, file_re, recursive = true) 9 | get_file_paths(dir, recursive).select { |path| path =~ file_re } 10 | end 11 | 12 | def get_file_paths(dir, recursive = true) 13 | get_tree(get_tree_sha(dir), recursive)['tree'].select { |f| f['type'] == 'blob' }.map { |f| f['path'] } 14 | end 15 | 16 | def file_url(path) 17 | "https://raw.githubusercontent.com/#@repo/#@branch_sha/#{path}" 18 | end 19 | 20 | def read_files(path, files) 21 | path_url = file_url path 22 | contents = read_cached_files(path, files) 23 | log_http_get_files contents.keys, path_url, true if contents.keys 24 | files -= contents.keys 25 | log_http_get_files files, path_url, false 26 | files.map do |name| 27 | Thread.start { 28 | begin 29 | url = "#{path_url}/#{name}" 30 | contents[name] = URI.open(url).read 31 | rescue Exception => e 32 | log "Error downloading #{url}: #{e}" 33 | exit 1 34 | end 35 | WRITE_FILES_MUTEX.synchronize { write_cached_files path, name => contents[name] } 36 | } 37 | end.each(&:join) 38 | contents 39 | end 40 | 41 | def read_cached_files(path, files) 42 | full_path = "#@cache_path/#@branch_sha/#{path}" 43 | contents = {} 44 | if File.directory?(full_path) 45 | files.each do |name| 46 | path = "#{full_path}/#{name}" 47 | contents[name] = File.read(path, mode: 'rb') if File.exist?(path) 48 | end 49 | end 50 | contents 51 | end 52 | 53 | def write_cached_files(path, files) 54 | full_path = "./#@cache_path/#@branch_sha/#{path}" 55 | files.each do |name, content| 56 | FileUtils.mkdir_p File.dirname(File.join(full_path, name)) 57 | File.open("#{full_path}/#{name}", 'wb') { |f| f.write content } 58 | end 59 | end 60 | 61 | 62 | def get_file(url) 63 | uri = URI(url) 64 | cache_path = "./#@cache_path#{uri.path}#{uri.query.tr('?&=', '-') if uri.query}" 65 | FileUtils.mkdir_p File.dirname(cache_path) 66 | if File.exist?(cache_path) 67 | log_http_get_file url, true 68 | File.read(cache_path, mode: 'rb') 69 | else 70 | log_http_get_file url, false 71 | content = URI.open(url).read 72 | File.open(cache_path, 'wb') { |f| f.write content } 73 | content 74 | end 75 | end 76 | 77 | # get sha of the branch (= the latest commit) 78 | def get_branch_sha 79 | @branch_sha ||= begin 80 | if @branch =~ /\A[0-9a-f]{40,}\z/ 81 | @branch 82 | else 83 | cmd = "git ls-remote #{Shellwords.escape "https://github.com/#@repo"} #@branch" 84 | log cmd 85 | result = %x[#{cmd}] 86 | raise 'Could not get branch sha!' unless $?.success? && !result.empty? 87 | result.split(/\s+/).first 88 | end 89 | end 90 | end 91 | 92 | # Get the sha of a dir 93 | def get_tree_sha(dir, tree = get_trees) 94 | tree['tree'].find { |t| t['path'] == dir }['sha'] 95 | end 96 | 97 | def get_trees 98 | @trees ||= get_tree(@branch_sha) 99 | end 100 | 101 | def get_tree(sha, recursive = true) 102 | get_json("https://api.github.com/repos/#@repo/git/trees/#{sha}#{'?recursive=1' if recursive}") 103 | end 104 | 105 | def get_json(url) 106 | JSON.parse get_file(url) 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /tasks/updater/scss.rb: -------------------------------------------------------------------------------- 1 | class Updater 2 | module Scss 3 | def update_scss_assets 4 | log_status 'Updating scss...' 5 | save_to = @save_to[:scss] 6 | contents = {} 7 | bootstrap_scss_files = get_paths_by_type('scss', /\.scss$/).reject { |p| p.start_with?('tests/') } 8 | read_files('scss', bootstrap_scss_files).each do |name, file| 9 | contents[name] = file 10 | save_file("#{save_to}/#{name}", file) 11 | end 12 | log_processed "#{bootstrap_scss_files * ' '}" 13 | 14 | log_status 'Updating scss main files' 15 | %w(bootstrap bootstrap-grid bootstrap-reboot bootstrap-utilities).each do |name| 16 | # Compass treats non-partials as targets to copy into the main project, so make them partials. 17 | # Also move them up a level to clearly indicate entry points. 18 | from = "#{save_to}/#{name}.scss" 19 | to = "#{save_to}/../_#{name}.scss" 20 | FileUtils.mv from, to 21 | # As we moved the files, adjust imports accordingly. 22 | File.write to, File.read(to).gsub(/ "/, ' "bootstrap/') 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/dummy_rails/README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This is a minimal Rails app for testing 4 | -------------------------------------------------------------------------------- /test/dummy_rails/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Dummy::Application.load_tasks 7 | -------------------------------------------------------------------------------- /test/dummy_rails/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /test/dummy_rails/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twbs/bootstrap-rubygem/decb9616d0cfb553c2b4bff37a929aa0f69011ec/test/dummy_rails/app/assets/images/.keep -------------------------------------------------------------------------------- /test/dummy_rails/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | //= require popper.js 2 | //= require bootstrap-sprockets 3 | 4 | document.addEventListener('DOMContentLoaded', () => { 5 | for (const tooltipTriggerEl of document.querySelectorAll('[data-bs-toggle="tooltip"]')) { 6 | new bootstrap.Tooltip(tooltipTriggerEl) 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /test/dummy_rails/app/assets/stylesheets/.browserslistrc: -------------------------------------------------------------------------------- 1 | Chrome >= 25 2 | -------------------------------------------------------------------------------- /test/dummy_rails/app/assets/stylesheets/application.sass: -------------------------------------------------------------------------------- 1 | @import 'bootstrap' 2 | 3 | .test-mixin 4 | +make-container 5 | -------------------------------------------------------------------------------- /test/dummy_rails/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy_rails/app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | def root 3 | end 4 | end -------------------------------------------------------------------------------- /test/dummy_rails/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy_rails/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bootstrap Dummy App 6 | 7 | <%= stylesheet_link_tag 'application', media: "all", 'data-turbolinks-track' => true %> 8 | <%= csrf_meta_tags %> 9 | 10 | 11 | 12 | <%= yield %> 13 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/dummy_rails/app/views/pages/root.html: -------------------------------------------------------------------------------- 1 | 41 | 42 |
43 |
44 |
45 |
46 |
Card title
47 |

Some quick example text to build on the card title and make up the bulk of the card's 48 | content.

49 | Go somewhere 50 |
51 |
52 |
53 |
54 |
55 |
56 |
    57 |
  • An item
  • 58 |
  • A second item
  • 59 |
  • A third item
  • 60 |
61 |
62 |
63 |
64 |
65 | 66 | 67 | 68 |
69 |
70 |
71 | 75 | 79 | 83 | 87 |
88 |
89 |
90 | -------------------------------------------------------------------------------- /test/dummy_rails/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /test/dummy_rails/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails' 4 | 5 | %w( 6 | action_controller 7 | action_view 8 | sprockets 9 | ).each do |framework| 10 | require "#{framework}/railtie" 11 | end 12 | 13 | require 'uglifier' 14 | require 'bootstrap' 15 | 16 | module Dummy 17 | class Application < Rails::Application 18 | config.assets.enabled = true if config.assets.respond_to?(:enabled) 19 | if Rails::VERSION::MAJOR > 4 20 | # Rails 4 precompiles application.css|js by default, but future version of Rails do not. 21 | config.assets.precompile += %w( application.css application.js ) 22 | end 23 | config.to_prepare do 24 | if ENV['VERBOSE'] 25 | STDERR.puts "Loaded Rails #{Rails::VERSION::STRING}, Sprockets #{Sprockets::VERSION}", 26 | "Asset paths: #{Rails.application.config.assets.paths}" 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/dummy_rails/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) 6 | -------------------------------------------------------------------------------- /test/dummy_rails/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /test/dummy_rails/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Print deprecation notices to the Rails logger. 17 | config.active_support.deprecation = :log 18 | 19 | # Debug mode disables concatenation and preprocessing of assets. 20 | # This option may cause significant delays in view rendering with a large 21 | # number of complex assets. 22 | config.assets.debug = true 23 | end 24 | -------------------------------------------------------------------------------- /test/dummy_rails/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both thread web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | if config.respond_to?(:serve_static_files) 24 | # rails >= 4.2 25 | config.serve_static_files = true 26 | elsif config.respond_to?(:serve_static_assets) 27 | # rails < 4.2 28 | config.serve_static_assets = true 29 | end 30 | 31 | # Compress JavaScripts and CSS. 32 | config.assets.js_compressor = :uglifier 33 | # config.assets.css_compressor = :sass 34 | 35 | # Do not fallback to assets pipeline if a precompiled asset is missed. 36 | config.assets.compile = false 37 | 38 | # Generate digests for assets URLs. 39 | config.assets.digest = true 40 | 41 | # Version of your assets, change this if you want to expire all your assets. 42 | config.assets.version = '1.0' 43 | 44 | # Specifies the header that your server uses for sending files. 45 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 46 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 47 | 48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 49 | # config.force_ssl = true 50 | 51 | # Set to :debug to see everything in the log. 52 | config.log_level = :info 53 | 54 | # Prepend all log lines with the following tags. 55 | # config.log_tags = [ :subdomain, :uuid ] 56 | 57 | # Use a different logger for distributed setups. 58 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 59 | 60 | # Use a different cache store in production. 61 | # config.cache_store = :mem_cache_store 62 | 63 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 64 | # config.action_controller.asset_host = "http://assets.example.com" 65 | 66 | # Precompile additional assets. 67 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 68 | # config.assets.precompile += %w( search.js ) 69 | 70 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 71 | # the I18n.default_locale when a translation can not be found). 72 | config.i18n.fallbacks = true 73 | 74 | # Send deprecation notices to registered listeners. 75 | config.active_support.deprecation = :notify 76 | 77 | # Disable automatic flushing of the log to improve performance. 78 | # config.autoflush_log = false 79 | 80 | # Use default logging formatter so that PID and timestamp are not suppressed. 81 | config.log_formatter = ::Logger::Formatter.new 82 | end 83 | -------------------------------------------------------------------------------- /test/dummy_rails/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | if config.respond_to?(:serve_static_files) 17 | # rails >= 4.2 18 | config.serve_static_files = true 19 | elsif config.respond_to?(:serve_static_assets) 20 | # rails < 4.2 21 | config.serve_static_assets = true 22 | end 23 | config.static_cache_control = "public, max-age=3600" 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | 29 | # Raise exceptions instead of rendering exception templates. 30 | config.action_dispatch.show_exceptions = false 31 | 32 | # Disable request forgery protection in test environment. 33 | config.action_controller.allow_forgery_protection = false 34 | 35 | config.active_support.test_order = :random 36 | 37 | config.active_support.deprecation = :stderr 38 | end 39 | -------------------------------------------------------------------------------- /test/dummy_rails/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/dummy_rails/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /test/dummy_rails/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /test/dummy_rails/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /test/dummy_rails/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure your secret_key_base is kept private 11 | # if you're sharing your code publicly. 12 | token = '4380f36fda304251bf48f12ad4474b6d11447f1f959bd5b77a5d56c92b97f4c403ee0ae13d31a85ed88058ff8795bf31ec17e70e5c229b3707a77a2ee7e81cc' 13 | 14 | if Dummy::Application.config.respond_to?(:secret_key_base=) 15 | Dummy::Application.config.secret_key_base = token 16 | else 17 | Dummy::Application.config.secret_token = token 18 | end -------------------------------------------------------------------------------- /test/dummy_rails/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' 4 | -------------------------------------------------------------------------------- /test/dummy_rails/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /test/dummy_rails/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | dummy: 3 | hello: Hello 4 | -------------------------------------------------------------------------------- /test/dummy_rails/config/locales/es.yml: -------------------------------------------------------------------------------- 1 | es: 2 | dummy: 3 | hello: Hola -------------------------------------------------------------------------------- /test/dummy_rails/config/routes.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.routes.draw do 2 | root to: 'pages#root' 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy_rails/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twbs/bootstrap-rubygem/decb9616d0cfb553c2b4bff37a929aa0f69011ec/test/dummy_rails/log/.keep -------------------------------------------------------------------------------- /test/dummy_rails/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twbs/bootstrap-rubygem/decb9616d0cfb553c2b4bff37a929aa0f69011ec/test/dummy_rails/public/favicon.ico -------------------------------------------------------------------------------- /test/gemfiles/rails_4_2.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'actionpack', '~> 4.2.7' 4 | gem 'activesupport', '~> 4.2.7' 5 | gem 'sassc-rails', '~> 2.0' 6 | gem 'bigdecimal', '1.3.5' 7 | 8 | gemspec path: '../../' 9 | -------------------------------------------------------------------------------- /test/gemfiles/rails_5_0.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'actionpack', '~> 5.0.0' 4 | gem 'activesupport', '~> 5.0.0' 5 | gem 'sassc-rails', '~> 2.0' 6 | 7 | gemspec path: '../../' 8 | -------------------------------------------------------------------------------- /test/gemfiles/rails_5_1.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'actionpack', '~> 5.1.0' 4 | gem 'activesupport', '~> 5.1.0' 5 | gem 'sassc-rails', '~> 2.0' 6 | 7 | gemspec path: '../../' 8 | -------------------------------------------------------------------------------- /test/gemfiles/rails_5_2.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'actionpack', '~> 5.2.8' 4 | gem 'activesupport', '~> 5.2.8' 5 | gem 'sassc-rails', '~> 2.0' 6 | 7 | gemspec path: '../../' 8 | -------------------------------------------------------------------------------- /test/gemfiles/rails_6_0.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'actionpack', '~> 6.0.3' 4 | gem 'activesupport', '~> 6.0.3' 5 | gem 'sassc-rails', '~> 2.0' 6 | 7 | gemspec path: '../../' 8 | -------------------------------------------------------------------------------- /test/gemfiles/rails_6_1.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'actionpack', '~> 6.1.3' 4 | gem 'activesupport', '~> 6.1.3' 5 | gem 'sassc-rails', '~> 2.0' 6 | 7 | gemspec path: '../../' 8 | -------------------------------------------------------------------------------- /test/gemfiles/rails_7_0_dartsass.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'actionpack', '~> 7.0.4' 4 | gem 'activesupport', '~> 7.0.4' 5 | gem 'dartsass-sprockets', '~> 3.0' 6 | 7 | gemspec path: '../../' 8 | -------------------------------------------------------------------------------- /test/gemfiles/rails_7_0_sassc.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'actionpack', '~> 7.0.4' 4 | gem 'activesupport', '~> 7.0.4' 5 | gem 'sassc-rails', '~> 2.0' 6 | 7 | gemspec path: '../../' 8 | -------------------------------------------------------------------------------- /test/rails_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper_rails' 2 | 3 | class RailsTest < ActionDispatch::IntegrationTest 4 | include ::DummyRailsIntegration 5 | 6 | def test_visit_root 7 | visit root_path 8 | # ^ will raise on JS errors 9 | 10 | assert_equal 200, page.status_code 11 | 12 | screenshot! 13 | end 14 | 15 | def test_precompile 16 | Dummy::Application.load_tasks 17 | Rake::Task['assets:precompile'].invoke 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/support/dummy_rails_integration.rb: -------------------------------------------------------------------------------- 1 | require 'capybara/dsl' 2 | require 'fileutils' 3 | module DummyRailsIntegration 4 | include Capybara::DSL 5 | 6 | def setup 7 | super 8 | cleanup_dummy_rails_files 9 | end 10 | 11 | def teardown 12 | super 13 | cleanup_dummy_rails_files 14 | Capybara.reset_sessions! 15 | Capybara.use_default_driver 16 | end 17 | 18 | def screenshot! 19 | path = "tmp/#{name}.png" 20 | full_path = File.join(GEM_PATH, path) 21 | FileUtils.mkdir_p(File.dirname(full_path)) 22 | page.driver.render(full_path, full: true) 23 | STDERR.puts "Screenshot saved to #{path}" 24 | end 25 | 26 | private 27 | def cleanup_dummy_rails_files 28 | FileUtils.rm_rf('test/dummy_rails/tmp/cache', secure: true) 29 | FileUtils.rm Dir.glob('test/dummy_rails/public/assets/{.[^\.]*,*}') 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/support/reporting.rb: -------------------------------------------------------------------------------- 1 | module Kernel 2 | def silence_stdout_if(cond, &run) 3 | silence_stream_if(cond, STDOUT, &run) 4 | end 5 | 6 | def silence_stderr_if(cond, &run) 7 | silence_stream_if(cond, STDERR, &run) 8 | end 9 | 10 | def silence_stream_if(cond, stream, &run) 11 | if cond 12 | silence_stream(stream, &run) 13 | else 14 | run.call 15 | end 16 | end 17 | 18 | def silence_stream(stream) 19 | old_stream = stream.dup 20 | stream.reopen(File::NULL) 21 | stream.sync = true 22 | yield 23 | ensure 24 | stream.reopen(old_stream) 25 | old_stream.close 26 | end unless method_defined?(:silence_stream) 27 | end 28 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'minitest/reporters' 3 | Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new 4 | 5 | require 'active_support/core_ext/kernel/reporting' 6 | 7 | Dir['test/support/**/*.rb'].each do |file| 8 | # strip ^test/ and .rb$ 9 | file = file[5..-4] 10 | require_relative File.join('.', file) 11 | end 12 | 13 | GEM_PATH = File.expand_path('../', File.dirname(__FILE__)) 14 | 15 | #= Capybara 16 | require 'capybara/cuprite' 17 | 18 | browser_path = ENV['CHROMIUM_BIN'] || %w[ 19 | /usr/bin/chromium-browser 20 | /snap/bin/chromium 21 | /Applications/Chromium.app/Contents/MacOS/Chromium 22 | /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome 23 | ].find { |path| File.executable?(path) } 24 | 25 | Capybara.register_driver :cuprite do |app| 26 | options = { 27 | window_size: [1280, 1024], 28 | timeout: 30, 29 | process_timeout: 30 30 | } 31 | options[:browser_path] = browser_path if browser_path 32 | Capybara::Cuprite::Driver.new(app, options) 33 | end 34 | 35 | Capybara.configure do |config| 36 | config.server = :webrick 37 | config.app_host = 'http://localhost:7000' 38 | config.default_driver = :cuprite 39 | config.javascript_driver = :cuprite 40 | config.server_port = 7000 41 | config.default_max_wait_time = 10 42 | end 43 | -------------------------------------------------------------------------------- /test/test_helper_rails.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] = ENV['RACK_ENV'] = 'test' 2 | 3 | require 'test_helper' 4 | require 'dummy_rails/config/environment' 5 | require 'rails/test_help' 6 | require 'capybara/rails' 7 | --------------------------------------------------------------------------------